Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved text selection by allowing multiple edit ranges to be highlighted #4725

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion extensions/vscode/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
setupStatusBar,
StatusBarStatus,
} from "./autocomplete/statusBar";
import { ContinueGUIWebviewViewProvider } from "./ContinueGUIWebviewViewProvider";

Check warning on line 30 in extensions/vscode/src/commands.ts

View workflow job for this annotation

GitHub Actions / vscode-checks

There should be no empty line within import group

import { VerticalDiffManager } from "./diff/vertical/manager";
import EditDecorationManager from "./quickEdit/EditDecorationManager";
Expand All @@ -36,9 +36,9 @@
import { getMetaKeyLabel } from "./util/util";
import { VsCodeIde } from "./VsCodeIde";

import { LOCAL_DEV_DATA_VERSION } from "core/data/log";

Check warning on line 39 in extensions/vscode/src/commands.ts

View workflow job for this annotation

GitHub Actions / vscode-checks

`core/data/log` import should occur before import of `core/indexing/walkDir`
import { isModelInstaller } from "core/llm";

Check warning on line 40 in extensions/vscode/src/commands.ts

View workflow job for this annotation

GitHub Actions / vscode-checks

`core/llm` import should occur before import of `core/util/paths`
import { startLocalOllama } from "core/util/ollamaHelper";

Check warning on line 41 in extensions/vscode/src/commands.ts

View workflow job for this annotation

GitHub Actions / vscode-checks

There should be at least one empty line between import groups
import type { VsCodeWebviewProtocol } from "./webviewProtocol";

let fullScreenPanel: vscode.WebviewPanel | undefined;
Expand Down Expand Up @@ -569,7 +569,7 @@
const range =
args?.range ?? new vscode.Range(startFromCharZero, endAtCharLast);

editDecorationManager.setDecoration(editor, range);
editDecorationManager.addDecorations(editor, [range]);

const rangeInFileWithContents = getRangeInFileWithContents(true, range);

Expand Down
46 changes: 42 additions & 4 deletions extensions/vscode/src/quickEdit/EditDecorationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import vscode from "vscode";
class EditDecorationManager {
private _lastEditor: vscode.TextEditor | undefined;
private decorationType: vscode.TextEditorDecorationType;
private activeRangesMap: Map<string, vscode.Range> = new Map(); // Store unique active ranges

constructor(context: vscode.ExtensionContext) {
this.decorationType = vscode.window.createTextEditorDecorationType({
Expand All @@ -23,18 +24,55 @@ class EditDecorationManager {
);
}

setDecoration(editor: vscode.TextEditor, range: vscode.Range) {
if (this._lastEditor) {
this._lastEditor.setDecorations(this.decorationType, []);
// Converts each range to a unique string for storing in the map
private rangeToString(range: vscode.Range): string {
return `(${range.start.line},${range.start.character})-(${range.end.line},${range.end.character})`;
}

// Checks if two ranges are adjacent or overlapping
private rangesCoincide(range1: vscode.Range, range2: vscode.Range): boolean {
return range1.start.isEqual(range2.start)
|| range1.end.isEqual(range2.start)
|| range2.end.isEqual(range1.start)
|| !!range1.intersection(range2);
}

// Merges new range with existing ranges in the map
private mergeNewRange(newRange: vscode.Range): void {
let mergedRange = newRange;
const rangesToPrune: string[] = [];

for (const [key, existingRange] of this.activeRangesMap.entries()) {
if (!this.rangesCoincide(mergedRange, existingRange)) continue;
mergedRange = mergedRange.union(existingRange);
rangesToPrune.push(key);
}

for (const key of rangesToPrune) this.activeRangesMap.delete(key);
this.activeRangesMap.set(this.rangeToString(mergedRange), mergedRange);
}

// Adds a new decoration to the editor and merges it with existing ranges
addDecorations(editor: vscode.TextEditor, ranges: vscode.Range[]): void {
if (this._lastEditor?.document !== editor.document) {
this.clear(); // Clear previous decorations if editor has changed
}
editor.setDecorations(this.decorationType, [range]);
this._lastEditor = editor;

for (const range of ranges) this.mergeNewRange(range);

const activeRanges = Array.from(this.activeRangesMap.values());
if (activeRanges.length === 0) return; // No ranges to highlight

// Update active ranges and apply decorations
editor.setDecorations(this.decorationType, activeRanges);
this.updateInEditMode(true);
}

clear() {
if (this._lastEditor) {
this._lastEditor.setDecorations(this.decorationType, []);
this.activeRangesMap.clear();
this._lastEditor = undefined;
this.updateInEditMode(false);
}
Expand Down
Loading