Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
## [2026.2.5]

### Added

- Added [customizable Git Scope gutter marker colors](https://github.com/comod/git-scope-pro/issues/118) under
**Settings → Editor → Color Scheme → Git Scope**
- Added, modified and deleted markers can be given their own colors per color scheme; by default they inherit the
IDE's change marker colors as before. Color schemes and themes can define them too.
- Added a setting to fade the Git Scope gutter markers (20–80% opacity) so they stand back from the IDE's own markers,
in both merged and separate gutter mode

### Fixes

- Fixed the Git Scope window reopening on the HEAD tab instead of the tab that was selected when the IDE was closed
- A regression in 2026.2.2: switching to a scope tab was often not remembered (always for the last tab), so the
window fell back to an earlier selection such as HEAD.
- Fixed compatibility with the upcoming IntelliJ IDEA 2026.3 (file opening and VCS API changes)

## [2026.2.4]

### Added
Expand Down
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ experienced when hooking into the IDE's own gutter system.
> **Version Control → Confirmation → Highlight modified lines in gutter** is enabled.

Each changed line is marked with a colored bar — inserted, modified, and deleted lines each get their own dedicated
color as defined by the active IDE theme — relative to the selected Git Scope target. Clicking any marker opens a popup
with the following actions:
color (see **Gutter colors and fading** below) — relative to the selected Git Scope target. Clicking any marker opens
a popup with the following actions:

- **↑ / ↓** — Navigate to the previous or next change
- **↺** — Rollback this change to the scope base version
Expand All @@ -124,6 +124,18 @@ the IDE's native HEAD diff markers:

![](docs/separate_gutter.png)

**Gutter colors and fading** — By default the scope markers use the same colors as the IDE's own change markers. To
tell them apart:

- **Custom colors:** Set your own added/modified/deleted colors under
**Settings → Editor → Color Scheme → Git Scope** (also reachable via the link in **Settings → Tools → Git Scope**).
Each color inherits the IDE's matching change marker color until you turn inheriting off for it. Colors are stored
per color scheme, so light and dark schemes can differ, and theme or color scheme plugins can define them too
(`GIT_SCOPE_ADDED_LINES_COLOR`, `GIT_SCOPE_MODIFIED_LINES_COLOR`, `GIT_SCOPE_DELETED_LINES_COLOR`).
- **Fading:** Enable **Fade Git Scope gutter markers** in **Settings → Tools → Git Scope** to paint the scope markers
semi-transparent (20–80% opacity), so they stand back from the IDE's HEAD markers while keeping their colors. Works in
both merged and separate mode.

![](docs/icon.svg) **Reviewing a Scope (Keyboard Navigation):**

Git Scope is well suited for *reviewing* an entire scope — walking through every change on a branch, tag or commit range
Expand Down Expand Up @@ -230,6 +242,7 @@ Plugin settings are available under **Settings → Tools → Git Scope**:
| Setting | Description |
|-------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Separate Git Scope and IDE gutter rendering** | When enabled, Git Scope gutter markers are rendered in a dedicated column to the left of the line numbers, separate from the IDE's native markers. When disabled (default), both sets of markers share the same gutter column. |
| **Fade Git Scope gutter markers** / **Opacity** | When enabled, Git Scope gutter markers are painted semi-transparent at the chosen opacity (20–80%, default 60%), in both merged and separate mode. Off by default. Marker colors themselves are set under **Settings → Editor → Color Scheme → Git Scope**. |
| **Color files based on Git Scope** | When enabled (default), file colors in the project tree and editor tabs reflect the active Git Scope status. Disable to use the IDE's default HEAD-based file coloring. |
| **Show untracked files** / **Show deleted files** *(Working Tree)* | Initial state of the matching toggle button in the Git Scope window toolbar, applied the first time a project is opened. Already-open projects keep their own toggle state. Off by default. |

Expand Down
20 changes: 19 additions & 1 deletion backend/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,40 @@ repositories {

dependencies {
intellijPlatform {
intellijIdea(providers.gradleProperty("platformVersion"))
// Snapshot builds have no installer, so they come from the Maven snapshot repository.
val platformVersion = providers.gradleProperty("platformVersion").get()
intellijIdea(platformVersion) {
useInstaller = !platformVersion.endsWith("EAP-SNAPSHOT")
}
bundledPlugins(providers.gradleProperty("platformBundledPlugins").map { it.split(',') })
bundledModule("intellij.platform.backend")
bundledModule("intellij.platform.kernel.backend")
bundledModule("intellij.platform.rpc.backend")
// 2026.2 split VCS APIs into finer content modules that are no longer pulled in
// transitively via the Git4Idea plugin, so declare them explicitly.
bundledModule("intellij.platform.vcs.core")
bundledModule("intellij.platform.vcs.shared")
bundledModule("intellij.platform.vcs.impl")
bundledModule("intellij.platform.vcs.impl.shared")
bundledModule("intellij.platform.vcs.dvcs")
bundledModule("intellij.platform.vcs.dvcs.impl")
bundledModule("intellij.platform.vcs.dvcs.impl.shared")
bundledModule("intellij.platform.vcs.log.impl")
// 2026.3 moved the platform VCS APIs out of the core into the bundled Version Control plugin.
if (isVcsBundledPlugin(platformVersion)) {
bundledPlugin("com.intellij.platform.vcs")
}
}

implementation(project(":shared"))
compileOnly("com.google.code.gson:gson:2.14.0")
testImplementation("junit:junit:4.13.2")
}

/** True for platform versions where the VCS APIs ship as the com.intellij.platform.vcs plugin (2026.3+). */
fun isVcsBundledPlugin(version: String): Boolean {
if (version.endsWith("EAP-SNAPSHOT")) return true
val parts = version.split('.').map { it.toIntOrNull() ?: 0 }
val major = parts[0]
return if (major > 1000) major > 2026 || (major == 2026 && parts.getOrElse(1) { 0 } >= 3) else major >= 263
}
2 changes: 2 additions & 0 deletions backend/src/main/java/rpc/BackendGutterRpcImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ class BackendGutterRpcImpl : GutterRpcApi {
scopeRanges = scopeRanges?.map { GutterRangeDto(it.line1, it.line2, it.vcsLine1, it.vcsLine2) },
scopeDisplayName = scopeDisplayName,
separateGutterRendering = GitScopeSettings.getInstance().isSeparateGutterRendering,
fadeGutterMarkers = GitScopeSettings.getInstance().isFadeGutterMarkers,
gutterMarkerOpacity = GitScopeSettings.getInstance().gutterMarkerOpacity,
contentsIncluded = includeContents
)
}
Expand Down
10 changes: 5 additions & 5 deletions backend/src/main/java/service/GitService.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
import git4idea.repo.GitRepository;
import git4idea.repo.GitRepositoryManager;
import git4idea.ui.branch.GitBranchManager;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;

import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;

public class GitService {
public static final String BRANCH_HEAD = "HEAD";
Expand All @@ -34,7 +34,7 @@ public GitService(Project project) {
@NotNull
public List<BranchTreeEntry> listOfLocalBranches(GitRepository repo) {
Collection<GitLocalBranch> branches = repo.getBranches().getLocalBranches();
return StreamEx.of(branches)
return branches.stream()
.map(branch -> {
String name = branch.getName();
boolean isFav = gitBranchManager.isFavorite(GitBranchType.LOCAL, repo, name);
Expand All @@ -45,13 +45,13 @@ public List<BranchTreeEntry> listOfLocalBranches(GitRepository repo) {
if (delta != 0) return delta;
return StringUtil.naturalCompare(b1.getName(), b2.getName());
})
.toList();
.collect(Collectors.toList());
}

@NotNull
public List<BranchTreeEntry> listOfRemoteBranches(GitRepository repo) {
Collection<GitRemoteBranch> branches = repo.getBranches().getRemoteBranches();
return StreamEx.of(branches)
return branches.stream()
.map(branch -> {
String name = branch.getName();
boolean isFav = gitBranchManager.isFavorite(GitBranchType.REMOTE, repo, name);
Expand All @@ -62,7 +62,7 @@ public List<BranchTreeEntry> listOfRemoteBranches(GitRepository repo) {
if (delta != 0) return delta;
return StringUtil.naturalCompare(b1.getName(), b2.getName());
})
.toList();
.collect(Collectors.toList());
}

/**
Expand Down
14 changes: 10 additions & 4 deletions backend/src/main/java/service/ViewService.java
Original file line number Diff line number Diff line change
Expand Up @@ -363,11 +363,17 @@ public void save() {
skipped from modelData): persisting that index would, after the next load(), point
past the restored (shorter) collection -- landing on the "+" tab and showing a
half-baked "New" page instead of the tab the user actually left open. Leaving the
previously persisted index alone in that case keeps it pointing at the last real tab. */
MyModel activeModel = (currentTabIndex != null && currentTabIndex >= 0 && currentTabIndex < collection.size())
? collection.get(currentTabIndex)
previously persisted index alone in that case keeps it pointing at the last real tab.
currentTabIndex is a tab index: 0 is the HEAD tab, which has no model in collection and
is always restorable; tab i holds collection[i - 1]. */
if (currentTabIndex == null) {
return;
}
int modelIndex = getModelIndex(currentTabIndex);
MyModel activeModel = (modelIndex >= 0 && modelIndex < collection.size())
? collection.get(modelIndex)
: null;
if (activeModel != null && activeModel.getTargetBranchMap() != null) {
if (currentTabIndex == 0 || (activeModel != null && activeModel.getTargetBranchMap() != null)) {
this.state.setCurrentTabIndex(this.currentTabIndex);
}
}
Expand Down
97 changes: 0 additions & 97 deletions backend/src/main/java/settings/GitScopeSettingsComponent.java

This file was deleted.

16 changes: 12 additions & 4 deletions backend/src/main/java/settings/GitScopeSettingsConfigurable.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* This creates the settings page in IntelliJ's Settings dialog.
*/
public class GitScopeSettingsConfigurable implements Configurable {
private GitScopeSettingsComponent settingsComponent;
private GitScopeSettingsPanel settingsComponent;

@Nls(capitalization = Nls.Capitalization.Title)
@Override
Expand All @@ -30,14 +30,16 @@ public JComponent getPreferredFocusedComponent() {
@Nullable
@Override
public JComponent createComponent() {
settingsComponent = new GitScopeSettingsComponent();
settingsComponent = new GitScopeSettingsPanel();
return settingsComponent.getPanel();
}

@Override
public boolean isModified() {
GitScopeSettings settings = GitScopeSettings.getInstance();
return settingsComponent.isSeparateGutterRendering() != settings.isSeparateGutterRendering()
|| settingsComponent.isFadeGutterMarkers() != settings.isFadeGutterMarkers()
|| settingsComponent.getGutterMarkerOpacity() != settings.getGutterMarkerOpacity()
|| settingsComponent.isScopeFileColors() != settings.isScopeFileColors()
|| settingsComponent.isShowUntrackedFiles() != settings.isShowUntrackedFiles()
|| settingsComponent.isShowDeletedFiles() != settings.isShowDeletedFiles();
Expand All @@ -47,8 +49,12 @@ public boolean isModified() {
public void apply() throws ConfigurationException {
GitScopeSettings settings = GitScopeSettings.getInstance();
boolean tabColorsChanged = settingsComponent.isScopeFileColors() != settings.isScopeFileColors();
boolean separateGutterChanged = settingsComponent.isSeparateGutterRendering() != settings.isSeparateGutterRendering();
boolean gutterChanged = settingsComponent.isSeparateGutterRendering() != settings.isSeparateGutterRendering()
|| settingsComponent.isFadeGutterMarkers() != settings.isFadeGutterMarkers()
|| settingsComponent.getGutterMarkerOpacity() != settings.getGutterMarkerOpacity();
settings.setSeparateGutterRendering(settingsComponent.isSeparateGutterRendering());
settings.setFadeGutterMarkers(settingsComponent.isFadeGutterMarkers());
settings.setGutterMarkerOpacity(settingsComponent.getGutterMarkerOpacity());
settings.setScopeFileColors(settingsComponent.isScopeFileColors());
/* showUntrackedFiles/showDeletedFiles are only the new-project default (#111): already-open
* projects have their own toggle state (see State.showUntrackedFiles/showDeletedFiles,
Expand All @@ -67,7 +73,7 @@ public void apply() throws ConfigurationException {
}
}

if (separateGutterChanged) {
if (gutterChanged) {
for (var project : ProjectManager.getInstance().getOpenProjects()) {
if (!project.isDisposed()) {
var gds = project.getService(service.GutterDataService.class);
Expand All @@ -81,6 +87,8 @@ public void apply() throws ConfigurationException {
public void reset() {
GitScopeSettings settings = GitScopeSettings.getInstance();
settingsComponent.setSeparateGutterRendering(settings.isSeparateGutterRendering());
settingsComponent.setFadeGutterMarkers(settings.isFadeGutterMarkers());
settingsComponent.setGutterMarkerOpacity(settings.getGutterMarkerOpacity());
settingsComponent.setScopeFileColors(settings.isScopeFileColors());
settingsComponent.setShowUntrackedFiles(settings.isShowUntrackedFiles());
settingsComponent.setShowDeletedFiles(settings.isShowDeletedFiles());
Expand Down
Loading
Loading