Skip to content

[Enhancement] Add an independentColorPoolPerBracketType option #13

Description

@YangSiJun528

Summary

Add an independentColorPoolPerBracketType option equivalent to VS Code's editor.bracketPairColorization.independentColorPoolPerBracketType.

When enabled, each bracket type should advance through the existing six-color palette independently. The option should default to false to preserve the current behavior and match the VS Code default.

Current behavior

All bracket types within the same brace-matcher group share one nesting level.

Source Current levels
() {} () L1, {} L1
{ () } {} L1, () L2
{ [ () ] } {} L1, [] L2, () L3

Proposed behavior

Add an Independent color pools per bracket type setting.

When enabled, only nesting of the same bracket type advances that type's color level.

Source Option disabled Option enabled
{ () } {} L1, () L2 {} L1, () L1
{ (()) } {} L1, outer () L2, inner () L3 {} L1, outer () L1, inner () L2
{ ( { () } ) } L1, L2, L3, L4 outer {} L1, inner {} L2; outer () L1, inner () L2

Opening and closing tokens belonging to the same pair must always use the same level.

The examples use one-based UI levels. Internal depth values should remain zero-based.

The selected level should affect every component derived from the pair color:

  • bracket token foreground;
  • active guide;
  • active-pair border; and
  • active-pair background.

Scope

  • Add independentColorPoolPerBracketType: Boolean = false to persisted preferences.
  • Add a checkbox to the Colors section of the settings page.
  • Apply independent levels within the existing brace-matcher group boundary.
    • Different languages and matcher token groups should remain independent as they are now.
  • Refresh open editors after the setting changes.
  • Preserve existing pairing, malformed-input recovery, and structural-brace behavior.
  • Update the configuration documentation.

This option does not add separate color palettes for (), {}, [], or <>. Every bracket type continues to use the same configured six-color palette. Only the position within that palette is calculated independently.

Suggested implementation

1. Represent the depth policy explicitly

Add a core-level mode instead of passing the persisted UI setting directly into the pairing state machine.

public enum NestingDepthMode {
    SHARED,
    PER_BRACKET_TYPE
}

Pass this mode into PairingMachine.Session. The mode remains fixed for the lifetime of a pairing session.

2. Reuse the existing pending-token counts

The shared depth remains the current group stack size:

int sharedDepth = state.stack.size();

For independent mode, the depth is the number of pending openers with the same opening token type.

PairingMachine already maintains this value in state.allCounts.tokenCounts. No additional map should be introduced.

Avoid performing a separate lookup followed immediately by the existing count increment. Instead, refactor the increment operation so it returns the previous token count:

int sharedDepth = state.stack.size();

int perTypeDepth = incrementAllCountsAndGetPrevious(
        state.allCounts,
        token,
        context,
        strictContext
);

int depth = depthMode == NestingDepthMode.PER_BRACKET_TYPE
        ? perTypeDepth
        : sharedDepth;

The helper should perform the same work currently performed by increment(state.allCounts, open):

  • initialize tokenCounts when necessary;
  • increment the opening-token count;
  • initialize and increment the strict-context count when strictContext is enabled; and
  • return the token count from before the increment.

The token-count operation can use the following pattern:

private <K> int incrementAndGetPrevious(Map<K, Integer> counts, K key) {
    int previous = counts.getOrDefault(key, 0);
    counts.put(key, previous + 1);
    return previous;
}

The existing increment(state.allCounts, open) call must be removed when the new helper is introduced. Calling both would increment the pending count twice.

Counts for regularScopes should continue to be maintained independently because they are required by malformed-input recovery.

Within a BraceGroup, the opening token type should serve as the bracket-type key. Existing (language, tokenGroup) boundaries remain unchanged.

3. Store only the selected depth

Continue storing the effective depth in the existing PairTable.depths array.

This allows the existing token, guide, border, and background presentation code to consume the selected depth without adding parallel depth fields throughout:

  • PairTable;
  • BracketPair;
  • BracketTokenIndex; and
  • detached snapshot metadata.

Changing the option should trigger a new background analysis pass. This is preferable to permanently storing both depth values for every pair because the setting changes rarely, while the additional retained-memory cost would apply to every analyzed document.

4. Include the option in analysis identity

Thread the selected mode through the analysis configuration and identity:

  • AnalysisInput;
  • AnalysisStamp;
  • current-result matching;
  • canonical index identity, where applicable; and
  • BracketGuidePreferences.hasDifferentAnalysisFrom().

A result produced with one mode must not be accepted as current after the setting changes.

The editor session should clear decorations based on the previous mode before requesting replacement analysis, similar to a language-selection change. This prevents stale colors from remaining visible while the new background pass is pending.

Performance

The per-type depth calculation should reuse the pending-token counts already maintained by PairingMachine. It should not introduce another map or store an additional depth value for every completed pair.

With the increment-and-return refactoring, the expected steady-state cost is limited primarily to one session-stable depth-mode branch per opener:

  • no additional map;
  • no additional field per pair;
  • no additional retained memory; and
  • no redundant token-count lookup.

Changing the setting causes a full background reanalysis, but setting changes are infrequent and do not justify retaining both depth values for every pair.

Extend PairingMachineBenchmark to compare SHARED and PER_BRACKET_TYPE modes. The benchmark can run in the standalone JMH module because PairingMachine and PairTable do not require an IntelliJ IDE runtime. JMH executes them in forked JVM processes; no IDE process is required.

Use a JMH parameter for the mode:

@Param({"SHARED", "PER_BRACKET_TYPE"})
public NestingDepthMode depthMode;

Cover at least the following input shapes:

Input shape Purpose
Sequential pairs of one type Measures overhead for common shallow code
Fully nested pairs of one type Exercises continuously increasing same-type counts
Fully nested alternating types Exercises cases where the two modes produce different depths
Shallow mixed-type nesting Approximates code such as { call(array[index]); }

A mixed-type benchmark can use tokens such as:

private enum Token {
    ROUND_OPEN,
    ROUND_CLOSE,
    SQUARE_OPEN,
    SQUARE_CLOSE,
    CURLY_OPEN,
    CURLY_CLOSE
}

The benchmark should pass the complete token stream through PairingMachine and return the resulting PairTable. It should not benchmark the count helper in isolation.

Two comparisons are required:

  1. Compare the existing branch with the feature branch in SHARED mode to detect regressions in the default behavior.
  2. Compare SHARED with PER_BRACKET_TYPE within the feature branch to measure the new mode's incremental cost.

Run the pairing benchmark with:

./gradlew :benchmarks:jmh \
  -PbenchmarkInclude='.*PairingMachineBenchmark'

Compare:

  • average execution time;
  • JMH error ranges; and
  • gc.alloc.rate.norm or allocated bytes per operation.

The independent mode should not materially increase allocated bytes per operation. Timing differences should only be treated as regressions when they remain consistent across repeated runs and exceed the reported error ranges.

Do not add a fixed performance threshold to CI because JMH results are sensitive to host noise. The existing benchmark compilation check is sufficient for CI.

Tests

Add or update tests covering:

  • default persisted value is false;
  • settings persistence and reset behavior;
  • settings-page binding and accessibility text;
  • changing the option requires analysis refresh;
  • analysis stamps from different modes are not interchangeable;
  • shared depth across mixed bracket types;
  • independent depth across mixed bracket types;
  • nested pairs of the same type still increase depth;
  • opening and closing tokens retain the same effective depth;
  • different language or matcher groups remain independent;
  • malformed recovery correctly decrements reused token counts;
  • token, guide, border, and background colors use the effective depth; and
  • JMH coverage for both depth modes and all required input shapes.

Acceptance criteria

  • The option defaults to false.
  • Existing users retain the current shared-depth behavior.
  • When enabled, different bracket types start at the first palette color independently.
  • Nested pairs of the same type continue to advance through the palette.
  • Matching opening and closing tokens always receive the same color.
  • Token, guide, border, and background colors use the independently calculated level.
  • Language and matcher-group isolation remains unchanged.
  • Changing the option refreshes all open editor sessions without retaining stale colors.
  • No additional per-pair depth field or pending-count map is introduced.
  • The existing shared mode does not show a material performance or allocation regression.
  • The new mode is covered by unit, integration, settings, and JMH benchmark tests.

This issue was drafted with GPT-5.6-sol.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions