mirror of
https://github.com/Microsoft/vscode
synced 2024-10-04 02:14:06 +00:00
use diff information to size and crop live previews views (#180341)
also fixes https://github.com/microsoft/vscode-internalbacklog/issues/3874
This commit is contained in:
parent
c28ca0b1e8
commit
dbf4b14dfa
|
@ -63,6 +63,10 @@ export class LineRangeMapping {
|
|||
public toString(): string {
|
||||
return `{${this.originalRange.toString()}->${this.modifiedRange.toString()}}`;
|
||||
}
|
||||
|
||||
public get changedLineCount() {
|
||||
return Math.max(this.originalRange.length, this.modifiedRange.length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
1
src/vs/monaco.d.ts
vendored
1
src/vs/monaco.d.ts
vendored
|
@ -2431,6 +2431,7 @@ declare namespace monaco.editor {
|
|||
readonly innerChanges: RangeMapping[] | undefined;
|
||||
constructor(originalRange: LineRange, modifiedRange: LineRange, innerChanges: RangeMapping[] | undefined);
|
||||
toString(): string;
|
||||
get changedLineCount(): any;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,7 +14,6 @@ import { DisposableStore } from 'vs/base/common/lifecycle';
|
|||
import { LRUCache } from 'vs/base/common/map';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
import { splitLines } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import 'vs/css!./interactiveEditor';
|
||||
import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
|
@ -23,11 +22,12 @@ import { EditOperation } from 'vs/editor/common/core/editOperation';
|
|||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { LineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
|
||||
import { IEditorContribution, IEditorDecorationsCollection, ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { LanguageSelector } from 'vs/editor/common/languageSelector';
|
||||
import { CompletionContext, CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList, ProviderResult, TextEdit } from 'vs/editor/common/languages';
|
||||
import { ICursorStateComputer, IModelDecorationOptions, IModelDeltaDecoration, ITextModel, IValidEditOperation } from 'vs/editor/common/model';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { ModelDecorationOptions, createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
|
||||
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
||||
import { IModelService } from 'vs/editor/common/services/model';
|
||||
|
@ -381,15 +381,18 @@ export class InteractiveEditorController implements IEditorContribution {
|
|||
// CANCEL when input changes
|
||||
this._editor.onDidChangeModel(this.cancelSession, this, store);
|
||||
|
||||
// REposition the zone widget whenever the block decoration changes
|
||||
let lastPost: Position | undefined;
|
||||
wholeRangeDecoration.onDidChange(e => {
|
||||
const range = wholeRangeDecoration.getRange(0);
|
||||
if (range && (!lastPost || !lastPost.equals(range.getEndPosition()))) {
|
||||
lastPost = range.getEndPosition();
|
||||
this._zone.updatePosition(lastPost);
|
||||
}
|
||||
}, undefined, store);
|
||||
if (editMode === 'live') {
|
||||
|
||||
// REposition the zone widget whenever the block decoration changes
|
||||
let lastPost: Position | undefined;
|
||||
wholeRangeDecoration.onDidChange(e => {
|
||||
const range = wholeRangeDecoration.getRange(0);
|
||||
if (range && (!lastPost || !lastPost.equals(range.getEndPosition()))) {
|
||||
lastPost = range.getEndPosition();
|
||||
this._zone.updatePosition(lastPost);
|
||||
}
|
||||
}, undefined, store);
|
||||
}
|
||||
|
||||
let ignoreModelChanges = false;
|
||||
this._editor.onDidChangeModelContent(e => {
|
||||
|
@ -424,9 +427,15 @@ export class InteractiveEditorController implements IEditorContribution {
|
|||
const roundStore = new DisposableStore();
|
||||
store.add(roundStore);
|
||||
|
||||
const diffBaseModel = this._modelService.createModel(textModel.getValue(), { languageId: textModel.getLanguageId(), onDidChange: Event.None }, undefined, true);
|
||||
store.add(diffBaseModel);
|
||||
const diffZone = this._instaService.createInstance(InteractiveEditorDiffWidget, this._editor, diffBaseModel);
|
||||
let textModel0Changes: LineRangeMapping[] | undefined;
|
||||
const textModel0 = this._modelService.createModel(
|
||||
createTextBufferFactoryFromSnapshot(textModel.createSnapshot()),
|
||||
{ languageId: textModel.getLanguageId(), onDidChange: Event.None },
|
||||
undefined, true
|
||||
);
|
||||
|
||||
store.add(textModel0);
|
||||
const diffZone = this._instaService.createInstance(InteractiveEditorDiffWidget, this._editor, textModel0);
|
||||
|
||||
do {
|
||||
|
||||
|
@ -439,13 +448,6 @@ export class InteractiveEditorController implements IEditorContribution {
|
|||
break;
|
||||
}
|
||||
|
||||
if (round > 1 && editMode === 'livePreview') {
|
||||
if (!textModel.equalsTextBuffer(diffBaseModel.getTextBuffer())) {
|
||||
diffZone.show(wholeRangeDecoration.getRange(0)!);
|
||||
} else {
|
||||
diffZone.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// visuals: add block decoration
|
||||
blockDecoration.set([{
|
||||
|
@ -457,8 +459,21 @@ export class InteractiveEditorController implements IEditorContribution {
|
|||
this._ctsRequest = new CancellationTokenSource(this._ctsSession.token);
|
||||
|
||||
this._historyOffset = -1;
|
||||
|
||||
const inputPromise = this._zone.getInput(wholeRange.getEndPosition(), placeholder, value, this._ctsRequest.token);
|
||||
|
||||
if (textModel0Changes && editMode === 'livePreview') {
|
||||
const diffPosition = diffZone.getEndPositionForChanges(wholeRange, textModel0Changes);
|
||||
if (diffPosition) {
|
||||
const newInputPosition = diffPosition.delta(0, 1);
|
||||
if (wholeRange.getEndPosition().isBefore(newInputPosition)) {
|
||||
this._zone.updatePosition(newInputPosition);
|
||||
}
|
||||
}
|
||||
diffZone.showDiff(
|
||||
() => wholeRangeDecoration.getRange(0)!, // TODO@jrieken if it can be null it will be null
|
||||
textModel0Changes
|
||||
);
|
||||
}
|
||||
this._ctxLastFeedbackKind.reset();
|
||||
// reveal the line after the whole range to ensure that the input box is visible
|
||||
this._editor.revealPosition({ lineNumber: wholeRange.endLineNumber + 1, column: 1 }, ScrollType.Smooth);
|
||||
|
@ -570,9 +585,17 @@ export class InteractiveEditorController implements IEditorContribution {
|
|||
|
||||
} else {
|
||||
// make edits more minimal
|
||||
const moreMinimalEdits = (await this._editorWorkerService.computeHumanReadableDiff(textModel.uri, editResponse.localEdits));
|
||||
|
||||
const moreMinimalEdits = (await this._editorWorkerService.computeMoreMinimalEdits(textModel.uri, editResponse.localEdits));
|
||||
const editOperations = (moreMinimalEdits ?? editResponse.localEdits).map(edit => EditOperation.replace(Range.lift(edit.range), edit.text));
|
||||
this._logService.trace('[IE] edits from PROVIDER and after making them MORE MINIMAL', provider.debugName, editResponse.localEdits, moreMinimalEdits);
|
||||
|
||||
const textModelNplus1 = this._modelService.createModel(createTextBufferFactoryFromSnapshot(textModel.createSnapshot()), null, undefined, true);
|
||||
textModelNplus1.applyEdits(editOperations);
|
||||
const diff = await this._editorWorkerService.computeDiff(textModel0.uri, textModelNplus1.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: 5000 }, 'advanced');
|
||||
textModel0Changes = diff?.changes ?? undefined;
|
||||
textModelNplus1.dispose();
|
||||
|
||||
try {
|
||||
ignoreModelChanges = true;
|
||||
|
||||
|
@ -588,7 +611,7 @@ export class InteractiveEditorController implements IEditorContribution {
|
|||
this._editor.pushUndoStop();
|
||||
this._editor.executeEdits(
|
||||
'interactive-editor',
|
||||
(moreMinimalEdits ?? editResponse.localEdits).map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)),
|
||||
editOperations,
|
||||
cursorStateComputerAndInlineDiffCollection
|
||||
);
|
||||
this._editor.pushUndoStop();
|
||||
|
@ -601,32 +624,22 @@ export class InteractiveEditorController implements IEditorContribution {
|
|||
inlineDiffDecorations.update();
|
||||
}
|
||||
|
||||
// line count
|
||||
const lineSet = new Set<number>();
|
||||
let addRemoveCount = 0;
|
||||
for (const edit of moreMinimalEdits ?? editResponse.localEdits) {
|
||||
|
||||
const len2 = splitLines(edit.text).length - 1;
|
||||
|
||||
if (Range.isEmpty(edit.range) && len2 > 0) {
|
||||
// insert lines
|
||||
addRemoveCount += len2;
|
||||
} else if (Range.isEmpty(edit.range) && edit.text.length === 0) {
|
||||
// delete
|
||||
addRemoveCount += edit.range.endLineNumber - edit.range.startLineNumber + 1;
|
||||
} else {
|
||||
// edit
|
||||
for (let line = edit.range.startLineNumber; line <= edit.range.endLineNumber; line++) {
|
||||
lineSet.add(line);
|
||||
}
|
||||
// summary message
|
||||
let linesChanged = 0;
|
||||
if (textModel0Changes) {
|
||||
for (const change of textModel0Changes) {
|
||||
linesChanged += change.changedLineCount;
|
||||
}
|
||||
}
|
||||
const linesChanged = addRemoveCount + lineSet.size;
|
||||
|
||||
this._zone.widget.updateMessage(linesChanged === 1
|
||||
? localize('lines.1', "Generated reply and changed 1 line.")
|
||||
: localize('lines.N', "Generated reply and changed {0} lines.", linesChanged)
|
||||
);
|
||||
let message: string;
|
||||
if (linesChanged === 0) {
|
||||
message = localize('lines.0', "Generated reply");
|
||||
} else if (linesChanged === 1) {
|
||||
message = localize('lines.1', "Generated reply and changed 1 line.");
|
||||
} else {
|
||||
message = localize('lines.N', "Generated reply and changed {0} lines.", linesChanged);
|
||||
}
|
||||
this._zone.widget.updateMessage(message);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -6,12 +6,10 @@
|
|||
import { Dimension, h } from 'vs/base/browser/dom';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { assertType } from 'vs/base/common/types';
|
||||
import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IActiveCodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
@ -19,6 +17,9 @@ import * as colorRegistry from 'vs/platform/theme/common/colorRegistry';
|
|||
import * as editorColorRegistry from 'vs/editor/common/core/editorColorRegistry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { interactiveEditorDiffInserted, interactiveEditorDiffRemoved, interactiveEditorRegionHighlight } from 'vs/workbench/contrib/interactiveEditor/common/interactiveEditor';
|
||||
import { LineRange } from 'vs/editor/common/core/lineRange';
|
||||
import { LineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
|
||||
export class InteractiveEditorDiffWidget extends ZoneWidget {
|
||||
|
||||
|
@ -31,8 +32,8 @@ export class InteractiveEditorDiffWidget extends ZoneWidget {
|
|||
private _dim: Dimension | undefined;
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
private readonly _originalModel: ITextModel,
|
||||
editor: IActiveCodeEditor,
|
||||
private readonly _textModelv0: ITextModel,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
) {
|
||||
|
@ -52,6 +53,7 @@ export class InteractiveEditorDiffWidget extends ZoneWidget {
|
|||
modifiedEditor: { contributions: [] }
|
||||
}, editor);
|
||||
this._disposables.add(this._diffEditor);
|
||||
this._diffEditor.setModel({ original: this._textModelv0, modified: editor.getModel() });
|
||||
|
||||
const doStyle = () => {
|
||||
const theme = themeService.getColorTheme();
|
||||
|
@ -73,60 +75,107 @@ export class InteractiveEditorDiffWidget extends ZoneWidget {
|
|||
};
|
||||
doStyle();
|
||||
this._disposables.add(themeService.onDidColorThemeChange(doStyle));
|
||||
|
||||
}
|
||||
|
||||
protected override _fillContainer(container: HTMLElement): void {
|
||||
container.appendChild(this._elements.domNode);
|
||||
}
|
||||
|
||||
override show(range: IRange): void {
|
||||
// --- show / hide --------------------
|
||||
|
||||
override show(): void {
|
||||
throw new Error('not supported like this');
|
||||
}
|
||||
|
||||
getEndPositionForChanges(range: Range, changes: LineRangeMapping[]): Position | undefined {
|
||||
assertType(this.editor.hasModel());
|
||||
|
||||
const modified = this.editor.getModel();
|
||||
const ranges = this._computeHiddenRanges(modified, range, changes);
|
||||
if (!ranges) {
|
||||
return undefined;
|
||||
}
|
||||
return ranges.modifiedHidden.getEndPosition();
|
||||
}
|
||||
|
||||
showDiff(range: () => Range, changes: LineRangeMapping[]): void {
|
||||
assertType(this.editor.hasModel());
|
||||
this._sessionStore.clear();
|
||||
|
||||
this.editor.setHiddenAreas([range], InteractiveEditorDiffWidget._hideId);
|
||||
this._sessionStore.add(this._diffEditor.onDidUpdateDiff(() => {
|
||||
const result = this._diffEditor.getDiffComputationResult();
|
||||
this._doShowForChanges(range(), result?.changes2 ?? []);
|
||||
}));
|
||||
this._doShowForChanges(range(), changes);
|
||||
}
|
||||
|
||||
private _doShowForChanges(range: Range, changes: LineRangeMapping[]): void {
|
||||
assertType(this.editor.hasModel());
|
||||
|
||||
const modified = this.editor.getModel();
|
||||
const lineHeightDiff = Math.max(1, Math.abs(modified.getLineCount() - this._originalModel.getLineCount()) + (range.endLineNumber - range.startLineNumber));
|
||||
const ranges = this._computeHiddenRanges(modified, range, changes);
|
||||
|
||||
if (!ranges) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
this.editor.setHiddenAreas([ranges.modifiedHidden], InteractiveEditorDiffWidget._hideId);
|
||||
this._diffEditor.getOriginalEditor().setHiddenAreas(ranges.originalDiffHidden, InteractiveEditorDiffWidget._hideId);
|
||||
this._diffEditor.getModifiedEditor().setHiddenAreas(ranges.modifiedDiffHidden, InteractiveEditorDiffWidget._hideId);
|
||||
|
||||
const lineCountModified = ranges.modifiedHidden.endLineNumber - ranges.modifiedHidden.startLineNumber;
|
||||
const lineCountOriginal = ranges.originalHidden.endLineNumber - ranges.originalHidden.startLineNumber;
|
||||
|
||||
const lineHeightDiff = 1 + Math.max(lineCountModified, lineCountOriginal);
|
||||
const lineHeightPadding = (this.editor.getOption(EditorOption.lineHeight) / 12) /* padding-top/bottom*/;
|
||||
|
||||
this._diffEditor.setModel({ original: this._originalModel, modified });
|
||||
this._diffEditor.revealRange(range, ScrollType.Immediate);
|
||||
this._diffEditor.getModifiedEditor().setHiddenAreas(invertRange(range, modified), InteractiveEditorDiffWidget._hideId);
|
||||
const position = ranges.modifiedHidden.getEndPosition();
|
||||
super.show(position, lineHeightDiff + lineHeightPadding);
|
||||
}
|
||||
|
||||
const updateHiddenAreasOriginal = () => {
|
||||
// todo@jrieken this needs work when both are equal
|
||||
const changes = this._diffEditor.getLineChanges();
|
||||
if (!changes) {
|
||||
return;
|
||||
}
|
||||
let startLine = Number.MAX_VALUE;
|
||||
let endLine = 0;
|
||||
for (const change of changes) {
|
||||
startLine = Math.min(startLine, change.originalStartLineNumber, change.modifiedStartLineNumber);
|
||||
endLine = Math.max(endLine, change.originalEndLineNumber || change.originalStartLineNumber, change.modifiedEndLineNumber || change.modifiedStartLineNumber);
|
||||
}
|
||||
const combinedRange = this._originalModel.validateRange({ startLineNumber: startLine, startColumn: 1, endLineNumber: endLine, endColumn: Number.MAX_VALUE });
|
||||
private _computeHiddenRanges(model: ITextModel, range: Range, changes: LineRangeMapping[]) {
|
||||
if (changes.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const hiddenRangesOriginal = invertRange(combinedRange, this._originalModel);
|
||||
this._diffEditor.getOriginalEditor().setHiddenAreas(hiddenRangesOriginal, InteractiveEditorDiffWidget._hideId);
|
||||
let originalLineRange = changes[0].originalRange;
|
||||
let modifiedLineRange = changes[0].modifiedRange;
|
||||
for (let i = 1; i < changes.length; i++) {
|
||||
originalLineRange = originalLineRange.join(changes[i].originalRange);
|
||||
modifiedLineRange = modifiedLineRange.join(changes[i].modifiedRange);
|
||||
}
|
||||
|
||||
// const hiddenRangesModified = invertRange(combinedRange, modified);
|
||||
// this._diffEditor.getModifiedEditor().setHiddenAreas(hiddenRangesModified, InteractiveEditorDiffWidget._hideId);
|
||||
};
|
||||
this._diffEditor.onDidUpdateDiff(updateHiddenAreasOriginal, undefined, this._sessionStore);
|
||||
updateHiddenAreasOriginal();
|
||||
const startDelta = modifiedLineRange.startLineNumber - range.startLineNumber;
|
||||
if (startDelta > 0) {
|
||||
modifiedLineRange = new LineRange(modifiedLineRange.startLineNumber - startDelta, modifiedLineRange.endLineNumberExclusive);
|
||||
originalLineRange = new LineRange(originalLineRange.startLineNumber - startDelta, originalLineRange.endLineNumberExclusive);
|
||||
}
|
||||
|
||||
super.show(new Position(range.endLineNumber, range.endColumn), lineHeightDiff + lineHeightPadding);
|
||||
const endDelta = range.endLineNumber - (modifiedLineRange.endLineNumberExclusive - 1);
|
||||
if (endDelta > 0) {
|
||||
modifiedLineRange = new LineRange(modifiedLineRange.startLineNumber, modifiedLineRange.endLineNumberExclusive + endDelta);
|
||||
originalLineRange = new LineRange(originalLineRange.startLineNumber, originalLineRange.endLineNumberExclusive + endDelta);
|
||||
}
|
||||
|
||||
|
||||
const originalHidden = asRange(originalLineRange, this._textModelv0);
|
||||
const originalDiffHidden = invertRange(originalHidden, this._textModelv0);
|
||||
const modifiedHidden = asRange(modifiedLineRange, model);
|
||||
const modifiedDiffHidden = invertRange(modifiedHidden, model);
|
||||
|
||||
return { originalHidden, originalDiffHidden, modifiedHidden, modifiedDiffHidden };
|
||||
}
|
||||
|
||||
override hide(): void {
|
||||
this.editor.setHiddenAreas([], InteractiveEditorDiffWidget._hideId);
|
||||
this._diffEditor.getOriginalEditor().setHiddenAreas([], InteractiveEditorDiffWidget._hideId);
|
||||
this._diffEditor.getModifiedEditor().setHiddenAreas([], InteractiveEditorDiffWidget._hideId);
|
||||
this._diffEditor.setModel(null);
|
||||
super.hide();
|
||||
}
|
||||
|
||||
// --- layout -------------------------
|
||||
|
||||
protected override _onWidth(widthInPixel: number): void {
|
||||
if (this._dim) {
|
||||
this._doLayout(this._dim.height, widthInPixel);
|
||||
|
@ -143,13 +192,29 @@ export class InteractiveEditorDiffWidget extends ZoneWidget {
|
|||
}
|
||||
}
|
||||
|
||||
function invertRange(range: IRange, model: ITextModel): IRange[] {
|
||||
const result: IRange[] = [];
|
||||
if (range.startLineNumber > 1) {
|
||||
result.push({ startLineNumber: 1, startColumn: 1, endLineNumber: range.startLineNumber - 1, endColumn: 1 });
|
||||
}
|
||||
if (range.endLineNumber < model.getLineCount()) {
|
||||
result.push({ startLineNumber: range.endLineNumber + 1, startColumn: 1, endLineNumber: model.getLineCount(), endColumn: 1 });
|
||||
function invertRange(range: IRange, model: ITextModel): Range[] {
|
||||
const result: Range[] = [];
|
||||
if (Range.isEmpty(range)) {
|
||||
//result.push(model.getFullModelRange());
|
||||
// todo@jrieken
|
||||
// cannot hide everything, return [] instead
|
||||
|
||||
} else {
|
||||
if (range.startLineNumber > 1) {
|
||||
result.push(new Range(1, 1, range.startLineNumber - 1, 1));
|
||||
}
|
||||
if (range.endLineNumber < model.getLineCount()) {
|
||||
result.push(new Range(range.endLineNumber + 1, 1, model.getLineCount(), 1));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function asRange(lineRange: LineRange, model: ITextModel): Range {
|
||||
if (lineRange.isEmpty) {
|
||||
return new Range(lineRange.startLineNumber, 1, lineRange.startLineNumber, 1);
|
||||
} else {
|
||||
const endLine = lineRange.endLineNumberExclusive - 1;
|
||||
return new Range(lineRange.startLineNumber, 1, endLine, model.getLineMaxColumn(endLine));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue