mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
Implement red diff highlighting in VS Code extension
- Create RedDiffHighlightController in VS Code core - Update editor.all.ts to include new controller - Modify ApprovalCodeLensProvider to handle red highlights - Integrate red and green diff display in extension UI
This commit is contained in:
parent
4d76fd80b9
commit
dd32435eb1
3 changed files with 156 additions and 91 deletions
|
|
@ -1,11 +1,13 @@
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { SuggestedEdit } from './getDiffedLines';
|
import { SuggestedEdit } from './getDiffedLines';
|
||||||
|
import { RedDiffHighlightController } from 'vs/editor/contrib/redDiffHighlight/redDiffHighlightController';
|
||||||
|
|
||||||
// each diff on the user's screen right now
|
// each diff on the user's screen right now
|
||||||
type DiffType = {
|
type DiffType = {
|
||||||
diffid: number,
|
diffid: number,
|
||||||
lenses: vscode.CodeLens[],
|
lenses: vscode.CodeLens[],
|
||||||
greenRange: vscode.Range,
|
greenRange: vscode.Range,
|
||||||
|
redRange: vscode.Range,
|
||||||
originalCode: string, // If a revert happens, we replace the greenRange with this content.
|
originalCode: string, // If a revert happens, we replace the greenRange with this content.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -24,6 +26,7 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider {
|
||||||
private _onDidChangeCodeLenses: vscode.EventEmitter<void> = new vscode.EventEmitter<void>(); // signals a UI refresh on .fire() events
|
private _onDidChangeCodeLenses: vscode.EventEmitter<void> = new vscode.EventEmitter<void>(); // signals a UI refresh on .fire() events
|
||||||
|
|
||||||
private _weAreEditing: boolean = false
|
private _weAreEditing: boolean = false
|
||||||
|
private _redDiffController: RedDiffHighlightController | undefined;
|
||||||
|
|
||||||
// used internally by vscode
|
// used internally by vscode
|
||||||
public readonly onDidChangeCodeLenses: vscode.Event<void> = this._onDidChangeCodeLenses.event;
|
public readonly onDidChangeCodeLenses: vscode.Event<void> = this._onDidChangeCodeLenses.event;
|
||||||
|
|
@ -37,6 +40,12 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider {
|
||||||
|
|
||||||
// declared by us, registered with vscode.languages.registerCodeLensProvider()
|
// declared by us, registered with vscode.languages.registerCodeLensProvider()
|
||||||
constructor() {
|
constructor() {
|
||||||
|
// Initialize RedDiffHighlightController
|
||||||
|
const editor = vscode.window.activeTextEditor;
|
||||||
|
if (editor) {
|
||||||
|
this._redDiffController = editor.getContribution(RedDiffHighlightController.ID) as RedDiffHighlightController;
|
||||||
|
}
|
||||||
|
|
||||||
// this acts as a useEffect. Every time text changes, clear the diffs in this editor
|
// this acts as a useEffect. Every time text changes, clear the diffs in this editor
|
||||||
vscode.workspace.onDidChangeTextDocument((e) => {
|
vscode.workspace.onDidChangeTextDocument((e) => {
|
||||||
const editor = vscode.window.activeTextEditor
|
const editor = vscode.window.activeTextEditor
|
||||||
|
|
@ -47,18 +56,22 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider {
|
||||||
const docUri = editor.document.uri
|
const docUri = editor.document.uri
|
||||||
const docUriStr = docUri.toString()
|
const docUriStr = docUri.toString()
|
||||||
this._diffsOfDocument[docUriStr].splice(0) // clear diffs
|
this._diffsOfDocument[docUriStr].splice(0) // clear diffs
|
||||||
editor.setDecorations(greenDecoration, []) // clear decorations
|
editor.setDecorations(greenDecoration, []) // clear green decorations
|
||||||
|
this._redDiffController?.removeRedHighlight() // clear red decorations
|
||||||
this._computedLensesOfDocument[docUriStr] = this._diffsOfDocument[docUriStr].flatMap(diff => diff.lenses) // recompute
|
this._computedLensesOfDocument[docUriStr] = this._diffsOfDocument[docUriStr].flatMap(diff => diff.lenses) // recompute
|
||||||
this._onDidChangeCodeLenses.fire() // refresh
|
this._onDidChangeCodeLenses.fire() // refresh
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// used by us only
|
// used by us only
|
||||||
private refreshLenses = (editor: vscode.TextEditor, docUriStr: string) => {
|
private refreshLenses = (editor: vscode.TextEditor, docUriStr: string) => {
|
||||||
editor.setDecorations(greenDecoration, this._diffsOfDocument[docUriStr].map(diff => diff.greenRange)) // refresh highlighting
|
editor.setDecorations(greenDecoration, this._diffsOfDocument[docUriStr].map(diff => diff.greenRange)) // refresh green highlighting
|
||||||
this._computedLensesOfDocument[docUriStr] = this._diffsOfDocument[docUriStr].flatMap(diff => diff.lenses) // recompute _computedLensesOfDocument (can optimize this later)
|
this._diffsOfDocument[docUriStr].forEach(diff => {
|
||||||
this._onDidChangeCodeLenses.fire() // fire event for vscode to refresh lenses
|
this._redDiffController?.addRedHighlight(diff.redRange); // refresh red highlighting
|
||||||
}
|
});
|
||||||
|
this._computedLensesOfDocument[docUriStr] = this._diffsOfDocument[docUriStr].flatMap(diff => diff.lenses) // recompute _computedLensesOfDocument (can optimize this later)
|
||||||
|
this._onDidChangeCodeLenses.fire() // fire event for vscode to refresh lenses
|
||||||
|
}
|
||||||
|
|
||||||
// used by us only
|
// used by us only
|
||||||
public async addNewApprovals(editor: vscode.TextEditor, suggestedEdits: SuggestedEdit[]) {
|
public async addNewApprovals(editor: vscode.TextEditor, suggestedEdits: SuggestedEdit[]) {
|
||||||
|
|
@ -76,40 +89,45 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider {
|
||||||
// must do this before adding codelenses or highlighting so that codelens and highlights will apply to the fresh code and not the old code
|
// must do this before adding codelenses or highlighting so that codelens and highlights will apply to the fresh code and not the old code
|
||||||
// apply changes in reverse order so additions don't push down the line numbers of the next edit
|
// apply changes in reverse order so additions don't push down the line numbers of the next edit
|
||||||
let workspaceEdit = new vscode.WorkspaceEdit();
|
let workspaceEdit = new vscode.WorkspaceEdit();
|
||||||
for (let i = suggestedEdits.length - 1; i > -1; i -= 1) {
|
for (let i = suggestedEdits.length - 1; i > -1; i -= 1) {
|
||||||
let suggestedEdit = suggestedEdits[i]
|
let suggestedEdit = suggestedEdits[i]
|
||||||
|
|
||||||
let greenRange: vscode.Range
|
let greenRange: vscode.Range
|
||||||
|
let redRange: vscode.Range
|
||||||
|
|
||||||
// INSERTIONS (e.g. {originalStartLine: 0, originalEndLine: -1})
|
// INSERTIONS (e.g. {originalStartLine: 0, originalEndLine: -1})
|
||||||
if (suggestedEdit.originalStartLine > suggestedEdit.originalEndLine) {
|
if (suggestedEdit.originalStartLine > suggestedEdit.originalEndLine) {
|
||||||
const originalPosition = new vscode.Position(suggestedEdit.originalStartLine, 0)
|
const originalPosition = new vscode.Position(suggestedEdit.originalStartLine, 0)
|
||||||
workspaceEdit.insert(docUri, originalPosition, suggestedEdit.newContent + '\n') // add back in the line we deleted when we made the startline->endline range go negative
|
workspaceEdit.insert(docUri, originalPosition, suggestedEdit.newContent + '\n') // add back in the line we deleted when we made the startline->endline range go negative
|
||||||
greenRange = new vscode.Range(suggestedEdit.startLine, 0, suggestedEdit.endLine + 1, 0)
|
greenRange = new vscode.Range(suggestedEdit.startLine, 0, suggestedEdit.endLine + 1, 0)
|
||||||
}
|
redRange = new vscode.Range(suggestedEdit.originalStartLine, 0, suggestedEdit.originalStartLine, 0)
|
||||||
// DELETIONS
|
}
|
||||||
else if (suggestedEdit.startLine > suggestedEdit.endLine) {
|
// DELETIONS
|
||||||
const deleteRange = new vscode.Range(suggestedEdit.originalStartLine, 0, suggestedEdit.originalEndLine + 1, 0)
|
else if (suggestedEdit.startLine > suggestedEdit.endLine) {
|
||||||
workspaceEdit.delete(docUri, deleteRange)
|
const deleteRange = new vscode.Range(suggestedEdit.originalStartLine, 0, suggestedEdit.originalEndLine + 1, 0)
|
||||||
greenRange = new vscode.Range(suggestedEdit.startLine, 0, suggestedEdit.startLine, 0)
|
workspaceEdit.delete(docUri, deleteRange)
|
||||||
suggestedEdit.originalContent += '\n' // add back in the line we deleted when we made the startline->endline range go negative
|
greenRange = new vscode.Range(suggestedEdit.startLine, 0, suggestedEdit.startLine, 0)
|
||||||
}
|
redRange = deleteRange
|
||||||
// REPLACEMENTS
|
suggestedEdit.originalContent += '\n' // add back in the line we deleted when we made the startline->endline range go negative
|
||||||
else {
|
}
|
||||||
const originalRange = new vscode.Range(suggestedEdit.originalStartLine, 0, suggestedEdit.originalEndLine, Number.MAX_SAFE_INTEGER)
|
// REPLACEMENTS
|
||||||
workspaceEdit.replace(docUri, originalRange, suggestedEdit.newContent)
|
else {
|
||||||
greenRange = new vscode.Range(suggestedEdit.startLine, 0, suggestedEdit.endLine, Number.MAX_SAFE_INTEGER)
|
const originalRange = new vscode.Range(suggestedEdit.originalStartLine, 0, suggestedEdit.originalEndLine, Number.MAX_SAFE_INTEGER)
|
||||||
}
|
workspaceEdit.replace(docUri, originalRange, suggestedEdit.newContent)
|
||||||
|
greenRange = new vscode.Range(suggestedEdit.startLine, 0, suggestedEdit.endLine, Number.MAX_SAFE_INTEGER)
|
||||||
|
redRange = originalRange
|
||||||
|
}
|
||||||
|
|
||||||
this._diffsOfDocument[docUriStr].push({
|
this._diffsOfDocument[docUriStr].push({
|
||||||
diffid: this._diffidPool,
|
diffid: this._diffidPool,
|
||||||
greenRange: greenRange,
|
greenRange: greenRange,
|
||||||
originalCode: suggestedEdit.originalContent,
|
redRange: redRange,
|
||||||
lenses: [
|
originalCode: suggestedEdit.originalContent,
|
||||||
new vscode.CodeLens(greenRange, { title: 'Accept', command: 'void.approveDiff', arguments: [{ diffid: this._diffidPool }] }),
|
lenses: [
|
||||||
new vscode.CodeLens(greenRange, { title: 'Reject', command: 'void.discardDiff', arguments: [{ diffid: this._diffidPool }] })
|
new vscode.CodeLens(greenRange, { title: 'Accept', command: 'void.approveDiff', arguments: [{ diffid: this._diffidPool }] }),
|
||||||
]
|
new vscode.CodeLens(greenRange, { title: 'Reject', command: 'void.discardDiff', arguments: [{ diffid: this._diffidPool }] })
|
||||||
});
|
]
|
||||||
|
});
|
||||||
this._diffidPool += 1
|
this._diffidPool += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,63 +142,69 @@ export class ApprovalCodeLensProvider implements vscode.CodeLensProvider {
|
||||||
console.log('diffs after added:', this._diffsOfDocument[docUriStr])
|
console.log('diffs after added:', this._diffsOfDocument[docUriStr])
|
||||||
}
|
}
|
||||||
|
|
||||||
// called on void.approveDiff
|
// called on void.approveDiff
|
||||||
public async approveDiff({ diffid }: { diffid: number }) {
|
public async approveDiff({ diffid }: { diffid: number }) {
|
||||||
const editor = vscode.window.activeTextEditor
|
const editor = vscode.window.activeTextEditor
|
||||||
if (!editor)
|
if (!editor)
|
||||||
return
|
return
|
||||||
|
|
||||||
const docUri = editor.document.uri
|
const docUri = editor.document.uri
|
||||||
const docUriStr = docUri.toString()
|
const docUriStr = docUri.toString()
|
||||||
|
|
||||||
// get index of this diff in diffsOfDocument
|
// get index of this diff in diffsOfDocument
|
||||||
const index = this._diffsOfDocument[docUriStr].findIndex(diff => diff.diffid === diffid);
|
const index = this._diffsOfDocument[docUriStr].findIndex(diff => diff.diffid === diffid);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
console.error('Error: DiffID could not be found: ', diffid, this._diffsOfDocument[docUriStr])
|
console.error('Error: DiffID could not be found: ', diffid, this._diffsOfDocument[docUriStr])
|
||||||
return
|
return
|
||||||
}
|
|
||||||
|
|
||||||
// remove this diff from the diffsOfDocument[docStr] (can change this behavior in future if add something like history)
|
|
||||||
this._diffsOfDocument[docUriStr].splice(index, 1)
|
|
||||||
|
|
||||||
// refresh
|
|
||||||
this.refreshLenses(editor, docUriStr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove red highlight for this diff
|
||||||
|
this._redDiffController?.removeRedHighlight(this._diffsOfDocument[docUriStr][index].redRange);
|
||||||
|
|
||||||
// called on void.discardDiff
|
// remove this diff from the diffsOfDocument[docStr] (can change this behavior in future if add something like history)
|
||||||
public async discardDiff({ diffid }: { diffid: number }) {
|
this._diffsOfDocument[docUriStr].splice(index, 1)
|
||||||
const editor = vscode.window.activeTextEditor
|
|
||||||
if (!editor)
|
|
||||||
return
|
|
||||||
|
|
||||||
const docUri = editor.document.uri
|
// refresh
|
||||||
const docUriStr = docUri.toString()
|
this.refreshLenses(editor, docUriStr)
|
||||||
|
}
|
||||||
// get index of this diff in diffsOfDocument
|
|
||||||
const index = this._diffsOfDocument[docUriStr].findIndex(diff => diff.diffid === diffid);
|
|
||||||
if (index === -1) {
|
// called on void.discardDiff
|
||||||
console.error('Void error: DiffID could not be found: ', diffid, this._diffsOfDocument[docUriStr])
|
public async discardDiff({ diffid }: { diffid: number }) {
|
||||||
return
|
const editor = vscode.window.activeTextEditor
|
||||||
}
|
if (!editor)
|
||||||
|
return
|
||||||
const { greenRange: range, lenses, originalCode } = this._diffsOfDocument[docUriStr][index] // do this before we splice and mess up index
|
|
||||||
|
const docUri = editor.document.uri
|
||||||
// remove this diff from the diffsOfDocument[docStr] (can change this behavior in future if add something like history)
|
const docUriStr = docUri.toString()
|
||||||
this._diffsOfDocument[docUriStr].splice(index, 1)
|
|
||||||
|
// get index of this diff in diffsOfDocument
|
||||||
// clear the decoration in this diffs range
|
const index = this._diffsOfDocument[docUriStr].findIndex(diff => diff.diffid === diffid);
|
||||||
editor.setDecorations(greenDecoration, this._diffsOfDocument[docUriStr].map(diff => diff.greenRange))
|
if (index === -1) {
|
||||||
|
console.error('Void error: DiffID could not be found: ', diffid, this._diffsOfDocument[docUriStr])
|
||||||
// REVERT THE CHANGE (this is the only part that's different from approveDiff)
|
return
|
||||||
let workspaceEdit = new vscode.WorkspaceEdit();
|
}
|
||||||
workspaceEdit.replace(docUri, range, originalCode);
|
|
||||||
this._weAreEditing = true
|
const { greenRange, redRange, originalCode } = this._diffsOfDocument[docUriStr][index] // do this before we splice and mess up index
|
||||||
await vscode.workspace.applyEdit(workspaceEdit)
|
|
||||||
await vscode.workspace.save(docUri)
|
// remove this diff from the diffsOfDocument[docStr] (can change this behavior in future if add something like history)
|
||||||
this._weAreEditing = false
|
this._diffsOfDocument[docUriStr].splice(index, 1)
|
||||||
|
|
||||||
// refresh
|
// clear the green decoration in this diff's range
|
||||||
this.refreshLenses(editor, docUriStr)
|
editor.setDecorations(greenDecoration, this._diffsOfDocument[docUriStr].map(diff => diff.greenRange))
|
||||||
}
|
|
||||||
|
// clear the red decoration in this diff's range
|
||||||
|
this._redDiffController?.removeRedHighlight(redRange);
|
||||||
|
|
||||||
|
// REVERT THE CHANGE
|
||||||
|
let workspaceEdit = new vscode.WorkspaceEdit();
|
||||||
|
workspaceEdit.replace(docUri, greenRange, originalCode);
|
||||||
|
this._weAreEditing = true
|
||||||
|
await vscode.workspace.applyEdit(workspaceEdit)
|
||||||
|
await vscode.workspace.save(docUri)
|
||||||
|
this._weAreEditing = false
|
||||||
|
|
||||||
|
// refresh
|
||||||
|
this.refreshLenses(editor, docUriStr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||||
|
import { EditorAction, ServicesAccessor, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||||
|
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||||
|
|
||||||
|
export class RedDiffHighlightController implements IEditorContribution {
|
||||||
|
public static readonly ID = 'editor.contrib.redDiffHighlight';
|
||||||
|
|
||||||
|
private readonly _editor: ICodeEditor;
|
||||||
|
private readonly _redDecoration: vscode.TextEditorDecorationType;
|
||||||
|
|
||||||
|
constructor(editor: ICodeEditor) {
|
||||||
|
this._editor = editor;
|
||||||
|
this._redDecoration = vscode.window.createTextEditorDecorationType({
|
||||||
|
backgroundColor: 'rgba(255, 0, 0, 0.2)',
|
||||||
|
isWholeLine: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
this._redDecoration.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public addRedHighlight(range: vscode.Range): void {
|
||||||
|
const editor = vscode.window.activeTextEditor;
|
||||||
|
if (editor) {
|
||||||
|
editor.setDecorations(this._redDecoration, [range]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeRedHighlight(): void {
|
||||||
|
const editor = vscode.window.activeTextEditor;
|
||||||
|
if (editor) {
|
||||||
|
editor.setDecorations(this._redDecoration, []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerEditorContribution(RedDiffHighlightController.ID, RedDiffHighlightController);
|
||||||
|
|
@ -63,6 +63,8 @@ import 'vs/editor/contrib/wordOperations/browser/wordOperations';
|
||||||
import 'vs/editor/contrib/wordPartOperations/browser/wordPartOperations';
|
import 'vs/editor/contrib/wordPartOperations/browser/wordPartOperations';
|
||||||
import 'vs/editor/contrib/readOnlyMessage/browser/contribution';
|
import 'vs/editor/contrib/readOnlyMessage/browser/contribution';
|
||||||
import 'vs/editor/contrib/diffEditorBreadcrumbs/browser/contribution';
|
import 'vs/editor/contrib/diffEditorBreadcrumbs/browser/contribution';
|
||||||
|
import 'vs/editor/contrib/redDiffHighlight/redDiffHighlightController';
|
||||||
|
|
||||||
|
|
||||||
// Load up these strings even in VSCode, even if they are not used
|
// Load up these strings even in VSCode, even if they are not used
|
||||||
// in order to get them translated
|
// in order to get them translated
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue