Merge branch 'main' into aiday/indentationOnInitOnly

This commit is contained in:
Aiday Marlen Kyzy
2023-06-14 09:21:53 +02:00
36 changed files with 402 additions and 206 deletions
+1 -1
View File
@@ -7,7 +7,7 @@
{
"kind": 2,
"language": "github-issues",
"value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-markdown-languageservice\n\n// current milestone name\n$milestone=milestone:\"June 2023\""
"value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release\n\n// current milestone name\n$milestone=milestone:\"June 2023\""
},
{
"kind": 1,
+1 -1
View File
@@ -165,7 +165,7 @@ function nodejs(platform, arch) {
if (platform === 'win32') {
if (product.nodejsRepository) {
log(`Downloading node.js ${nodeVersion} ${platform} ${arch} from ${product.nodejsRepository}...`);
return assetFromGithub(product.nodejsRepository, nodeVersion, name => name === `win-${arch}-node.exe`)
return assetFromGithub(product.nodejsRepository, nodeVersion, name => name === `win-${arch}-node-patched.exe`)
.pipe(rename('node.exe'));
}
log(`Downloading node.js ${nodeVersion} ${platform} ${arch} from https://nodejs.org`);
+2 -2
View File
@@ -76,7 +76,7 @@ function darwinBundleDocumentTypes(types, icon) {
});
}
exports.config = {
version: product.electronRepository ? '22.5.5' : util.getElectronVersion(),
version: product.electronRepository ? '22.5.7' : util.getElectronVersion(),
productAppName: product.nameLong,
companyName: 'Microsoft Corporation',
copyright: 'Copyright (C) 2023 Microsoft. All rights reserved',
@@ -193,7 +193,7 @@ function getElectron(arch) {
};
}
async function main(arch = process.arch) {
const version = product.electronRepository ? '22.5.5' : util.getElectronVersion();
const version = product.electronRepository ? '22.5.7' : util.getElectronVersion();
const electronPath = path.join(root, '.build', 'electron');
const versionFile = path.join(electronPath, 'version');
const isUpToDate = fs.existsSync(versionFile) && fs.readFileSync(versionFile, 'utf8') === `${version}`;
+2 -2
View File
@@ -91,7 +91,7 @@ function darwinBundleDocumentTypes(types: { [name: string]: string | string[] },
}
export const config = {
version: product.electronRepository ? '22.5.5' : util.getElectronVersion(),
version: product.electronRepository ? '22.5.7' : util.getElectronVersion(),
productAppName: product.nameLong,
companyName: 'Microsoft Corporation',
copyright: 'Copyright (C) 2023 Microsoft. All rights reserved',
@@ -212,7 +212,7 @@ function getElectron(arch: string): () => NodeJS.ReadWriteStream {
}
async function main(arch = process.arch): Promise<void> {
const version = product.electronRepository ? '22.5.5' : util.getElectronVersion();
const version = product.electronRepository ? '22.5.7' : util.getElectronVersion();
const electronPath = path.join(root, '.build', 'electron');
const versionFile = path.join(electronPath, 'version');
const isUpToDate = fs.existsSync(versionFile) && fs.readFileSync(versionFile, 'utf8') === `${version}`;
+1 -1
View File
@@ -140,7 +140,7 @@ export async function getPackageManager(extensionContext: ExtensionContext, fold
window.showInformationMessage(multiplePMWarning, learnMore, neverShowAgain).then(result => {
switch (result) {
case neverShowAgain: extensionContext.globalState.update(neverShowWarning, true); break;
case learnMore: env.openExternal(Uri.parse('https://nodejs.dev/learn/the-package-lock-json-file'));
case learnMore: env.openExternal(Uri.parse('https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json'));
}
});
}
+2 -1
View File
@@ -152,7 +152,8 @@ class Trait<T> implements ISpliceable<boolean>, IDisposable {
const length = this.length + diff;
if (this.sortedIndexes.length > 0 && sortedIndexes.length === 0 && length > 0) {
sortedIndexes.push(Math.min(firstSortedIndex ?? length - 1, length - 1));
const first = this.sortedIndexes.find(index => index >= start) ?? length - 1;
sortedIndexes.push(Math.min(first, length - 1));
}
this.renderer.splice(start, deleteCount, elements.length);
+1 -1
View File
@@ -382,7 +382,7 @@ class CodeMain {
// Print --status usage info
if (environmentMainService.args.status) {
logService.warn(localize('statusWarning', "Warning: The --status argument can only be used if {0} is already running. Please run it again after {0} has started.", productService.nameShort));
console.log(localize('statusWarning', "Warning: The --status argument can only be used if {0} is already running. Please run it again after {0} has started.", productService.nameShort));
throw new ExpectedError('Terminating...');
}
+3 -1
View File
@@ -1157,6 +1157,8 @@ export interface IDiffEditor extends editorCommon.IEditor {
*/
getModel(): editorCommon.IDiffEditorModel | null;
createViewModel(model: editorCommon.IDiffEditorModel): editorCommon.IDiffEditorViewModel;
/**
* Sets the current model attached to this editor.
* If the previous model was created by the editor via the value key in the options
@@ -1165,7 +1167,7 @@ export interface IDiffEditor extends editorCommon.IEditor {
* will not be destroyed.
* It is safe to call setModel(null) to simply detach the current model from the editor.
*/
setModel(model: editorCommon.IDiffEditorModel | null): void;
setModel(model: editorCommon.IDiffEditorModel | editorCommon.IDiffEditorViewModel | null): void;
/**
* Get the `original` editor.
@@ -488,7 +488,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
return this._modelData.model;
}
public setModel(_model: ITextModel | editorCommon.IDiffEditorModel | null = null): void {
public setModel(_model: ITextModel | editorCommon.IDiffEditorModel | editorCommon.IDiffEditorViewModel | null = null): void {
const model = <ITextModel | null>_model;
if (this._modelData === null && model === null) {
// Current model is the new model
@@ -829,7 +829,20 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
};
}
public setModel(model: editorCommon.IDiffEditorModel | null): void {
public createViewModel(model: editorCommon.IDiffEditorModel): editorCommon.IDiffEditorViewModel {
return {
model,
async waitForDiff() {
// noop
},
};
}
public setModel(model: editorCommon.IDiffEditorModel | editorCommon.IDiffEditorViewModel | null): void {
if (model && 'model' in model) {
model = model.model;
}
// Guard us against partial null model
if (model && (!model.original || !model.modified)) {
throw new Error(!model.original ? 'DiffEditorWidget.setModel: Original model is null' : 'DiffEditorWidget.setModel: Modified model is null');
@@ -11,7 +11,7 @@ import { IDimension } from 'vs/editor/common/core/dimension';
import { IPosition, Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { ISelection, Selection } from 'vs/editor/common/core/selection';
import { IEditor, IEditorAction, IEditorDecorationsCollection, IEditorModel, IEditorViewState, ScrollType } from 'vs/editor/common/editorCommon';
import { IDiffEditorViewModel, IEditor, IEditorAction, IEditorDecorationsCollection, IEditorModel, IEditorViewState, ScrollType } from 'vs/editor/common/editorCommon';
import { IModelDecorationsChangeAccessor, IModelDeltaDecoration } from 'vs/editor/common/model';
export abstract class DelegatingEditor extends Disposable implements IEditor {
@@ -34,7 +34,7 @@ export abstract class DelegatingEditor extends Disposable implements IEditor {
abstract saveViewState(): IEditorViewState | null;
abstract restoreViewState(state: IEditorViewState | null): void;
abstract getModel(): IEditorModel | null;
abstract setModel(model: IEditorModel | null): void;
abstract setModel(model: IEditorModel | null | IDiffEditorViewModel): void;
// #region editorBrowser.IDiffEditor: Delegating to modified Editor
@@ -7,8 +7,8 @@ import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash';
import { findLast } from 'vs/base/common/arrays';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { IObservable, ISettableObservable, autorun, derived, keepAlive, observableValue, waitForState } from 'vs/base/common/observable';
import { disposableObservableValue } from 'vs/base/common/observableImpl/base';
import { IObservable, ISettableObservable, autorun, derived, keepAlive, observableValue } from 'vs/base/common/observable';
import { disposableObservableValue, transaction } from 'vs/base/common/observableImpl/base';
import { derivedWithStore } from 'vs/base/common/observableImpl/derived';
import { isDefined } from 'vs/base/common/types';
import { Constants } from 'vs/base/common/uint';
@@ -32,7 +32,7 @@ import { IDimension } from 'vs/editor/common/core/dimension';
import { LineRange } from 'vs/editor/common/core/lineRange';
import { Position } from 'vs/editor/common/core/position';
import { IDiffComputationResult, ILineChange } from 'vs/editor/common/diff/smartLinesDiffComputer';
import { EditorType, IContentSizeChangedEvent, IDiffEditorModel, IDiffEditorViewState } from 'vs/editor/common/editorCommon';
import { EditorType, IContentSizeChangedEvent, IDiffEditorModel, IDiffEditorViewModel, IDiffEditorViewState } from 'vs/editor/common/editorCommon';
import { IModelDeltaDecoration } from 'vs/editor/common/model';
import { localize } from 'vs/nls';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
@@ -306,7 +306,6 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
if (!m) { return; }
const movedText = m.diff.get()!.movedTexts.find(m => m.lineRangeMapping.originalRange.contains(e.position.lineNumber));
m.syncedMovedTexts.set(movedText, undefined);
}));
return editor;
@@ -322,7 +321,6 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
if (!m) { return; }
const movedText = m.diff.get()!.movedTexts.find(m => m.lineRangeMapping.modifiedRange.contains(e.position.lineNumber));
m.syncedMovedTexts.set(movedText, undefined);
}));
// Revert change when an arrow is clicked.
@@ -484,22 +482,27 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
}
}
override getModel(): IDiffEditorModel | null { return this._model.get(); }
override setModel(model: IDiffEditorModel | null): void {
this._originalEditor.setModel(model ? model.original : null);
this._modifiedEditor.setModel(model ? model.modified : null);
this._model.set(model, undefined);
this._diffModel.set(model ? new DiffModel(
public createViewModel(model: IDiffEditorModel): IDiffEditorViewModel {
return new DiffModel(
model,
this._options.map(o => o.ignoreTrimWhitespace),
this._options.map(o => o.maxComputationTime),
this._options.map(o => o.experimental.collapseUnchangedRegions!),
this._options.map(o => o.experimental.showMoves! && o.renderSideBySide),
this._instantiationService.createInstance(WorkerBasedDocumentDiffProvider, this._options.get())
) : undefined, undefined);
);
}
override getModel(): IDiffEditorModel | null { return this._model.get(); }
override setModel(model: IDiffEditorModel | null | IDiffEditorViewModel): void {
const vm = model ? ('model' in model) ? model : this.createViewModel(model) : undefined;
this._originalEditor.setModel(vm ? vm.model.original : null);
this._modifiedEditor.setModel(vm ? vm.model.modified : null);
transaction(tx => {
this._model.set(vm?.model ?? null, tx);
this._diffModel.set(vm as (DiffModel | undefined), tx);
});
}
override updateOptions(_newOptions: IDiffEditorOptions): void {
@@ -641,7 +644,7 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
return;
}
// wait for the diff computation to finish
waitForState(diffModel.isDiffUpToDate, s => s).then(() => {
this.waitForDiff().then(() => {
const diffs = diffModel.diff.get()?.mappings;
if (!diffs || diffs.length === 0) {
return;
@@ -649,6 +652,15 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor {
this._goTo(diffs[0]);
});
}
public async waitForDiff(): Promise<void> {
const diffModel = this._diffModel.get();
if (!diffModel) {
return;
}
await diffModel.waitForDiff();
}
}
function validateDiffEditorOptions(options: Readonly<IDiffEditorOptions>, defaults: ValidDiffEditorBaseOptions): ValidDiffEditorBaseOptions {
@@ -5,27 +5,32 @@
import { RunOnceScheduler } from 'vs/base/common/async';
import { Disposable } from 'vs/base/common/lifecycle';
import { IObservable, IReader, ITransaction, derived, observableSignal, observableSignalFromEvent, observableValue, transaction } from 'vs/base/common/observable';
import { IObservable, IReader, ITransaction, derived, observableSignal, observableSignalFromEvent, observableValue, transaction, waitForState } from 'vs/base/common/observable';
import { autorunWithStore2 } from 'vs/base/common/observableImpl/autorun';
import { isDefined } from 'vs/base/common/types';
import { LineRange } from 'vs/editor/common/core/lineRange';
import { Range } from 'vs/editor/common/core/range';
import { IDocumentDiff, IDocumentDiffProvider } from 'vs/editor/common/diff/documentDiffProvider';
import { LineRangeMapping, MovedText, RangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
import { LineRangeMapping, MovedText, RangeMapping, SimpleLineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
import { lineRangeMappingFromRangeMappings } from 'vs/editor/common/diff/standardLinesDiffComputer';
import { IDiffEditorModel } from 'vs/editor/common/editorCommon';
import { IDiffEditorModel, IDiffEditorViewModel } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { TextEditInfo } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper';
import { combineTextEditInfos } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/combineTextEditInfos';
import { lengthAdd, lengthDiffNonNegative, lengthOfRange, lengthToPosition, lengthZero, positionToLength } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length';
import { lengthAdd, lengthDiffNonNegative, lengthGetLineCount, lengthOfRange, lengthToPosition, lengthZero, positionToLength } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length';
export class DiffModel extends Disposable {
export class DiffModel extends Disposable implements IDiffEditorViewModel {
private readonly _isDiffUpToDate = observableValue<boolean>('isDiffUpToDate', false);
public readonly isDiffUpToDate: IObservable<boolean> = this._isDiffUpToDate;
private _lastDiff: IDocumentDiff | undefined;
private readonly _diff = observableValue<DiffState | undefined>('diff', undefined);
public readonly diff: IObservable<DiffState | undefined> = this._diff;
private readonly _unchangedRegions = observableValue<{ regions: UnchangedRegion[]; originalDecorationIds: string[]; modifiedDecorationIds: string[] }>('unchangedRegion', { regions: [], originalDecorationIds: [], modifiedDecorationIds: [] });
private readonly _unchangedRegions = observableValue<{ regions: UnchangedRegion[]; originalDecorationIds: string[]; modifiedDecorationIds: string[] }>(
'unchangedRegion',
{ regions: [], originalDecorationIds: [], modifiedDecorationIds: [] }
);
public readonly unchangedRegions: IObservable<UnchangedRegion[]> = derived('unchangedRegions', r =>
this._hideUnchangedRegions.read(r) ? this._unchangedRegions.read(r).regions : []
);
@@ -50,11 +55,14 @@ export class DiffModel extends Disposable {
if (!diff) {
return;
}
/*const textEdits = TextEditInfo.fromModelContentChanges(e.changes);
this._diff.set(
applyModifiedEdits(diff, textEdits, model.original, model.modified),
undefined
);*/
if (!this._showMoves.get()) {
const textEdits = TextEditInfo.fromModelContentChanges(e.changes);
this._lastDiff = applyModifiedEdits(this._lastDiff!, textEdits, model.original, model.modified);
this._diff.set(DiffState.fromDiffResult(this._lastDiff), undefined);
const currentSyncedMovedText = this.syncedMovedTexts.get();
this.syncedMovedTexts.set(currentSyncedMovedText ? this._lastDiff.moves.find(m => m.lineRangeMapping.modifiedRange.intersect(currentSyncedMovedText.lineRangeMapping.modifiedRange)) : undefined, undefined);
}
debouncer.schedule();
}));
this._register(model.original.onDidChangeContent((e) => {
@@ -62,11 +70,13 @@ export class DiffModel extends Disposable {
if (!diff) {
return;
}
/*const textEdits = TextEditInfo.fromModelContentChanges(e.changes);
this._diff.set(
applyOriginalEdits(diff, textEdits, model.original, model.modified),
undefined
);*/
if (!this._showMoves.get()) {
const textEdits = TextEditInfo.fromModelContentChanges(e.changes);
this._lastDiff = applyOriginalEdits(this._lastDiff!, textEdits, model.original, model.modified);
this._diff.set(DiffState.fromDiffResult(this._lastDiff), undefined);
const currentSyncedMovedText = this.syncedMovedTexts.get();
this.syncedMovedTexts.set(currentSyncedMovedText ? this._lastDiff.moves.find(m => m.lineRangeMapping.modifiedRange.intersect(currentSyncedMovedText.lineRangeMapping.modifiedRange)) : undefined, undefined);
}
debouncer.schedule();
}));
@@ -137,8 +147,11 @@ export class DiffModel extends Disposable {
);
transaction(tx => {
this._lastDiff = result;
this._diff.set(DiffState.fromDiffResult(result), tx);
this._isDiffUpToDate.set(true, tx);
const currentSyncedMovedText = this.syncedMovedTexts.get();
this.syncedMovedTexts.set(currentSyncedMovedText ? this._lastDiff.moves.find(m => m.lineRangeMapping.modifiedRange.intersect(currentSyncedMovedText.lineRangeMapping.modifiedRange)) : undefined, tx);
this._unchangedRegions.set(
{
@@ -152,7 +165,7 @@ export class DiffModel extends Disposable {
}));
}
public revealModifiedLine(lineNumber: number, tx: ITransaction): void {
public ensureModifiedLineIsVisible(lineNumber: number, tx: ITransaction): void {
const unchangedRegions = this._unchangedRegions.get().regions;
for (const r of unchangedRegions) {
if (r.getHiddenModifiedRange(undefined).contains(lineNumber)) {
@@ -162,7 +175,7 @@ export class DiffModel extends Disposable {
}
}
public revealOriginalLine(lineNumber: number, tx: ITransaction): void {
public ensureOriginalLineIsVisible(lineNumber: number, tx: ITransaction): void {
const unchangedRegions = this._unchangedRegions.get().regions;
for (const r of unchangedRegions) {
if (r.getHiddenOriginalRange(undefined).contains(lineNumber)) {
@@ -171,6 +184,10 @@ export class DiffModel extends Disposable {
}
}
}
public async waitForDiff(): Promise<void> {
await waitForState(this.isDiffUpToDate, s => s);
}
}
export class DiffState {
@@ -315,38 +332,17 @@ function applyOriginalEdits(diff: IDocumentDiff, textEdits: TextEditInfo[], orig
return diff;
}
const diffTextEdits = diff.changes.flatMap(c => c.innerChanges!.map(c => new TextEditInfo(
positionToLength(c.modifiedRange.getStartPosition()),
positionToLength(c.modifiedRange.getEndPosition()),
lengthOfRange(c.originalRange).toLength(),
)));
const combined = combineTextEditInfos(diffTextEdits, textEdits);
let lastModifiedEndOffset = lengthZero;
let lastOriginalEndOffset = lengthZero;
const rangeMappings = combined.map(c => {
const originalStartOffset = lengthAdd(lastOriginalEndOffset, lengthDiffNonNegative(lastModifiedEndOffset, c.startOffset));
lastModifiedEndOffset = c.endOffset;
lastOriginalEndOffset = lengthAdd(originalStartOffset, c.newLength);
return new RangeMapping(
Range.fromPositions(lengthToPosition(originalStartOffset), lengthToPosition(lastOriginalEndOffset)),
Range.fromPositions(lengthToPosition(c.startOffset), lengthToPosition(c.endOffset)),
);
});
const changes = lineRangeMappingFromRangeMappings(
rangeMappings,
originalTextModel.getLinesContent(),
modifiedTextModel.getLinesContent(),
);
const diff2 = flip(diff);
const diff3 = applyModifiedEdits(diff2, textEdits, modifiedTextModel, originalTextModel);
return flip(diff3);
}
function flip(diff: IDocumentDiff): IDocumentDiff {
return {
identical: false,
quitEarly: false,
changes,
moves: [],
changes: diff.changes.map(c => c.flip()),
moves: diff.moves.map(m => m.flip()),
identical: diff.identical,
quitEarly: diff.quitEarly,
};
}
@@ -355,7 +351,63 @@ function applyModifiedEdits(diff: IDocumentDiff, textEdits: TextEditInfo[], orig
return diff;
}
const diffTextEdits = diff.changes.flatMap(c => c.innerChanges!.map(c => new TextEditInfo(
const changes = applyModifiedEditsToLineRangeMappings(diff.changes, textEdits, originalTextModel, modifiedTextModel);
const moves = diff.moves.map(m => {
const newModifiedRange = applyEditToLineRange(m.lineRangeMapping.modifiedRange, textEdits);
return newModifiedRange ? new MovedText(
new SimpleLineRangeMapping(m.lineRangeMapping.originalRange, newModifiedRange),
applyModifiedEditsToLineRangeMappings(m.changes, textEdits, originalTextModel, modifiedTextModel),
) : undefined;
}).filter(isDefined);
return {
identical: false,
quitEarly: false,
changes,
moves,
};
}
function applyEditToLineRange(range: LineRange, textEdits: TextEditInfo[]): LineRange | undefined {
let rangeStartLineNumber = range.startLineNumber;
let rangeEndLineNumberEx = range.endLineNumberExclusive;
for (let i = textEdits.length - 1; i >= 0; i--) {
const textEdit = textEdits[i];
const textEditStartLineNumber = lengthGetLineCount(textEdit.startOffset) + 1;
const textEditEndLineNumber = lengthGetLineCount(textEdit.endOffset) + 1;
const newLengthLineCount = lengthGetLineCount(textEdit.newLength);
const delta = newLengthLineCount - (textEditEndLineNumber - textEditStartLineNumber);
if (textEditEndLineNumber < rangeStartLineNumber) {
// the text edit is before us
rangeStartLineNumber += delta;
rangeEndLineNumberEx += delta;
} else if (textEditStartLineNumber > rangeEndLineNumberEx) {
// the text edit is after us
// NOOP
} else if (textEditStartLineNumber < rangeStartLineNumber && rangeEndLineNumberEx < textEditEndLineNumber) {
// the range is fully contained in the text edit
return undefined;
} else if (textEditStartLineNumber < rangeStartLineNumber && textEditEndLineNumber <= rangeEndLineNumberEx) {
// the text edit ends inside our range
rangeStartLineNumber = textEditEndLineNumber + 1;
rangeStartLineNumber += delta;
rangeEndLineNumberEx += delta;
} else if (rangeStartLineNumber <= textEditStartLineNumber && textEditEndLineNumber < rangeStartLineNumber) {
// the text edit starts inside our range
rangeEndLineNumberEx = textEditStartLineNumber;
} else {
rangeEndLineNumberEx += delta;
}
}
return new LineRange(rangeStartLineNumber, rangeEndLineNumberEx);
}
function applyModifiedEditsToLineRangeMappings(changes: readonly LineRangeMapping[], textEdits: TextEditInfo[], originalTextModel: ITextModel, modifiedTextModel: ITextModel): LineRangeMapping[] {
const diffTextEdits = changes.flatMap(c => c.innerChanges!.map(c => new TextEditInfo(
positionToLength(c.originalRange.getStartPosition()),
positionToLength(c.originalRange.getEndPosition()),
lengthOfRange(c.modifiedRange).toLength(),
@@ -376,16 +428,10 @@ function applyModifiedEdits(diff: IDocumentDiff, textEdits: TextEditInfo[], orig
);
});
const changes = lineRangeMappingFromRangeMappings(
const newChanges = lineRangeMappingFromRangeMappings(
rangeMappings,
originalTextModel.getLinesContent(),
modifiedTextModel.getLinesContent(),
);
return {
identical: false,
quitEarly: false,
changes,
moves: [],
};
return newChanges;
}
@@ -42,8 +42,9 @@ export class MovedBlocksLinesPart extends Disposable {
const originalScrollTop = observableFromEvent(this._originalEditor.onDidScrollChange, () => this._originalEditor.getScrollTop());
const modifiedScrollTop = observableFromEvent(this._modifiedEditor.onDidScrollChange, () => this._modifiedEditor.getScrollTop());
this._register(autorun('update', (reader) => {
element.replaceChildren();
const info = this._originalEditorLayoutInfo.read(reader);
const info2 = this._modifiedEditorLayoutInfo.read(reader);
if (!info || !info2) {
@@ -56,8 +57,6 @@ export class MovedBlocksLinesPart extends Disposable {
return;
}
element.replaceChildren();
let idx = 0;
for (const m of moves) {
function computeLineStart(range: LineRange, editor: ICodeEditor) {
@@ -28,8 +28,8 @@ export class UnchangedRangesFeature extends Disposable {
const m = this._diffModel.get();
transaction(tx => {
for (const s of this._originalEditor.getSelections() || []) {
m?.revealOriginalLine(s.getStartPosition().lineNumber, tx);
m?.revealOriginalLine(s.getEndPosition().lineNumber, tx);
m?.ensureOriginalLineIsVisible(s.getStartPosition().lineNumber, tx);
m?.ensureOriginalLineIsVisible(s.getEndPosition().lineNumber, tx);
}
});
}));
@@ -38,8 +38,8 @@ export class UnchangedRangesFeature extends Disposable {
const m = this._diffModel.get();
transaction(tx => {
for (const s of this._modifiedEditor.getSelections() || []) {
m?.revealModifiedLine(s.getStartPosition().lineNumber, tx);
m?.revealModifiedLine(s.getEndPosition().lineNumber, tx);
m?.ensureModifiedLineIsVisible(s.getStartPosition().lineNumber, tx);
m?.ensureModifiedLineIsVisible(s.getEndPosition().lineNumber, tx);
}
});
}));
@@ -20,6 +20,8 @@ export class WorkerBasedDocumentDiffProvider implements IDocumentDiffProvider, I
private diffAlgorithm: DiffAlgorithmName | IDocumentDiffProvider = 'advanced';
private diffAlgorithmOnDidChangeSubscription: IDisposable | undefined = undefined;
private static readonly diffCache = new Map<string, { result: IDocumentDiff; context: string }>();
constructor(
options: IWorkerBasedDocumentDiffProviderOptions,
@IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService,
@@ -58,6 +60,13 @@ export class WorkerBasedDocumentDiffProvider implements IDocumentDiffProvider, I
};
}
const uriKey = JSON.stringify([original.uri.toString(), modified.uri.toString()]);
const context = JSON.stringify([original.id, modified.id, original.getAlternativeVersionId(), modified.getAlternativeVersionId(), JSON.stringify(options)]);
const c = WorkerBasedDocumentDiffProvider.diffCache.get(uriKey);
if (c && c.context === context) {
return c.result;
}
const sw = StopWatch.create(true);
const result = await this.editorWorkerService.computeDiff(original.uri, modified.uri, options, this.diffAlgorithm);
const timeMs = sw.elapsed();
@@ -81,6 +90,12 @@ export class WorkerBasedDocumentDiffProvider implements IDocumentDiffProvider, I
throw new Error('no diff result available');
}
// max 10 items in cache
if (WorkerBasedDocumentDiffProvider.diffCache.size > 10) {
WorkerBasedDocumentDiffProvider.diffCache.delete(WorkerBasedDocumentDiffProvider.diffCache.keys().next().value);
}
WorkerBasedDocumentDiffProvider.diffCache.set(uriKey, { result, context });
return result;
}
@@ -102,6 +102,10 @@ export class LineRangeMapping {
public get changedLineCount() {
return Math.max(this.originalRange.length, this.modifiedRange.length);
}
public flip(): LineRangeMapping {
return new LineRangeMapping(this.modifiedRange, this.originalRange, this.innerChanges?.map(c => c.flip()));
}
}
/**
@@ -130,6 +134,10 @@ export class RangeMapping {
public toString(): string {
return `{${this.originalRange.toString()}->${this.modifiedRange.toString()}}`;
}
public flip(): RangeMapping {
return new RangeMapping(this.modifiedRange, this.originalRange);
}
}
export class SimpleLineRangeMapping {
@@ -142,6 +150,10 @@ export class SimpleLineRangeMapping {
public toString(): string {
return `{${this.originalRange.toString()}->${this.modifiedRange.toString()}}`;
}
public flip(): SimpleLineRangeMapping {
return new SimpleLineRangeMapping(this.modifiedRange, this.originalRange);
}
}
export class MovedText {
@@ -161,4 +173,8 @@ export class MovedText {
this.lineRangeMapping = lineRangeMapping;
this.changes = changes;
}
public flip(): MovedText {
return new MovedText(this.lineRangeMapping.flip(), this.changes.map(c => c.flip()));
}
}
+7 -1
View File
@@ -104,6 +104,12 @@ export interface IDiffEditorModel {
modified: ITextModel;
}
export interface IDiffEditorViewModel {
readonly model: IDiffEditorModel;
waitForDiff(): Promise<void>;
}
/**
* An event describing that an editor has had its model reset (i.e. `editor.setModel()`).
*/
@@ -153,7 +159,7 @@ export interface IEditorAction {
run(args?: unknown): Promise<void>;
}
export type IEditorModel = ITextModel | IDiffEditorModel;
export type IEditorModel = ITextModel | IDiffEditorModel | IDiffEditorViewModel;
/**
* A (serializable) state of the cursors.
+12 -2
View File
@@ -2501,6 +2501,7 @@ declare namespace monaco.editor {
constructor(originalRange: LineRange, modifiedRange: LineRange, innerChanges: RangeMapping[] | undefined);
toString(): string;
get changedLineCount(): any;
flip(): LineRangeMapping;
}
/**
@@ -2517,6 +2518,7 @@ declare namespace monaco.editor {
readonly modifiedRange: Range;
constructor(originalRange: Range, modifiedRange: Range);
toString(): string;
flip(): RangeMapping;
}
export class MovedText {
@@ -2528,6 +2530,7 @@ declare namespace monaco.editor {
*/
readonly changes: readonly LineRangeMapping[];
constructor(lineRangeMapping: SimpleLineRangeMapping, changes: readonly LineRangeMapping[]);
flip(): MovedText;
}
export class SimpleLineRangeMapping {
@@ -2535,6 +2538,7 @@ declare namespace monaco.editor {
readonly modifiedRange: LineRange;
constructor(originalRange: LineRange, modifiedRange: LineRange);
toString(): string;
flip(): SimpleLineRangeMapping;
}
export interface IDimension {
width: number;
@@ -2619,6 +2623,11 @@ declare namespace monaco.editor {
modified: ITextModel;
}
export interface IDiffEditorViewModel {
readonly model: IDiffEditorModel;
waitForDiff(): Promise<void>;
}
/**
* An event describing that an editor has had its model reset (i.e. `editor.setModel()`).
*/
@@ -2653,7 +2662,7 @@ declare namespace monaco.editor {
run(args?: unknown): Promise<void>;
}
export type IEditorModel = ITextModel | IDiffEditorModel;
export type IEditorModel = ITextModel | IDiffEditorModel | IDiffEditorViewModel;
/**
* A (serializable) state of the cursors.
@@ -6022,6 +6031,7 @@ declare namespace monaco.editor {
* Type the getModel() of IEditor.
*/
getModel(): IDiffEditorModel | null;
createViewModel(model: IDiffEditorModel): IDiffEditorViewModel;
/**
* Sets the current model attached to this editor.
* If the previous model was created by the editor via the value key in the options
@@ -6030,7 +6040,7 @@ declare namespace monaco.editor {
* will not be destroyed.
* It is safe to call setModel(null) to simply detach the current model from the editor.
*/
setModel(model: IDiffEditorModel | null): void;
setModel(model: IDiffEditorModel | IDiffEditorViewModel | null): void;
/**
* Get the `original` editor.
*/
+1 -1
View File
@@ -145,7 +145,7 @@ export async function main(desc: ProductDescription, args: string[]): Promise<vo
// Usage: `[[ "$TERM_PROGRAM" == "vscode" ]] && . "$(code --locate-shell-integration-path zsh)"`
case 'zsh': file = 'shellIntegration-rc.zsh'; break;
// Usage: `string match -q "$TERM_PROGRAM" "vscode"; and . (code --locate-shell-integration-path fish)`
case 'fish': file = 'shellIntegration.fish'; break;
case 'fish': file = 'fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish'; break;
default: throw new Error('Error using --locate-shell-integration-path: Invalid shell type');
}
console.log(resolve(__dirname, '../..', 'workbench', 'contrib', 'terminal', 'browser', 'media', file));
@@ -816,15 +816,12 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
}
private _getTerminalObjectIndexById<T extends ExtHostTerminal>(array: T[], id: ExtHostTerminalIdentifier): number | null {
let index: number | null = null;
array.some((item, i) => {
const thisId = item._id;
if (thisId === id) {
index = i;
return true;
}
return false;
const index = array.findIndex(item => {
return item._id === id;
});
if (index === -1) {
return null;
}
return index;
}
+3 -3
View File
@@ -17,7 +17,7 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio
import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files';
import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRelativePatternDto } from 'vs/workbench/api/common/extHost.protocol';
import { CellEditType, ICellPartialMetadataEdit, IDocumentMetadataEdit, isTextStreamMime } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellEditType, ICellMetadataEdit, IDocumentMetadataEdit, isTextStreamMime } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import type * as vscode from 'vscode';
@@ -775,7 +775,7 @@ export interface IFileSnippetTextEdit {
export interface IFileCellEdit {
readonly _type: FileEditType.Cell;
readonly uri: URI;
readonly edit?: ICellPartialMetadataEdit | IDocumentMetadataEdit;
readonly edit?: ICellMetadataEdit | IDocumentMetadataEdit;
readonly notebookMetadata?: Record<string, any>;
readonly metadata?: vscode.WorkspaceEditEntryMetadata;
}
@@ -832,7 +832,7 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
}
private replaceNotebookCellMetadata(uri: URI, index: number, cellMetadata: Record<string, any>, metadata?: vscode.WorkspaceEditEntryMetadata): void {
this._edits.push({ _type: FileEditType.Cell, metadata, uri, edit: { editType: CellEditType.PartialMetadata, index, metadata: cellMetadata } });
this._edits.push({ _type: FileEditType.Cell, metadata, uri, edit: { editType: CellEditType.Metadata, index, metadata: cellMetadata } });
}
// --- text
@@ -5,7 +5,7 @@
import { localize } from 'vs/nls';
import { deepClone } from 'vs/base/common/objects';
import { isObject, assertIsDefined, withUndefinedAsNull, withNullAsUndefined } from 'vs/base/common/types';
import { isObject, assertIsDefined, withNullAsUndefined } from 'vs/base/common/types';
import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser';
import { IDiffEditorOptions, IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions';
import { AbstractTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor';
@@ -133,7 +133,10 @@ export class TextDiffEditor extends AbstractTextEditor<IDiffEditorViewState> imp
// Set Editor Model
const control = assertIsDefined(this.diffEditorControl);
const resolvedDiffEditorModel = resolvedModel as TextDiffEditorModel;
control.setModel(withUndefinedAsNull(resolvedDiffEditorModel.textDiffEditorModel));
const vm = resolvedDiffEditorModel.textDiffEditorModel ? control.createViewModel(resolvedDiffEditorModel.textDiffEditorModel) : null;
await vm?.waitForDiff();
control.setModel(vm);
// Restore view state (unless provided by options)
let hasPreviousViewState = false;
@@ -87,9 +87,9 @@
height: 22px;
}
.monaco-workbench .notifications-list-container .monaco-list:focus-within .notification-list-item .notification-list-item-toolbar-container,
.monaco-workbench .notifications-list-container .notification-list-item:hover .notification-list-item-toolbar-container,
.monaco-workbench .notifications-list-container .monaco-list-row.focused .notification-list-item .notification-list-item-toolbar-container,
.monaco-workbench .notifications-list-container .notification-list-item.expanded .notification-list-item-toolbar-container {
.monaco-workbench .notifications-list-container .monaco-list-row.focused .notification-list-item .notification-list-item-toolbar-container {
display: block;
}
@@ -13,22 +13,26 @@ import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController';
const inputBox = localize('chat.requestHistory', 'In the input box, use up and down arrows to navigate your request history. Edit input and use enter or the submit button to run a new request.');
export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'chat' | 'inline'): string {
const keybindingService = accessor.get(IKeybindingService);
const content = [];
if (type === 'chat') {
content.push(localize('chat.overview', 'Chat responses will be announced as they come in. A response will indicate the number of code blocks, if any, and then the rest of the response.'));
content.push(localize('chat.requestHistory', 'In the input box, use up and down arrows to navigate your request history. Edit input and use enter to run a new request.'));
content.push(descriptionForCommand('chat.action.focus', localize('workbench.action.chat.focus', 'The Focus Chat command ({0}) focuses the chat request/response list, which can be navigated with UpArrow/DownArrow.',), localize('workbench.action.chat.focusNoKb', 'The Focus Chat List command focuses the chat request/response list, which can be navigated with UpArrow/DownArrow and is currently not triggerable by a keybinding.'), keybindingService));
content.push(localize('chat.overview', 'The chat view is comprised of an input box and a request/response list. The input box is used to make requests and the list is used to display responses.'));
content.push(inputBox);
content.push(localize('chat.announcement', 'Chat responses will be announced as they come in. A response will indicate the number of code blocks, if any, and then the rest of the response.'));
content.push(descriptionForCommand('chat.action.focus', localize('workbench.action.chat.focus', 'The Focus Chat command ({0}) focuses the chat request/response list, which can be navigated with up and down arrows.',), localize('workbench.action.chat.focusNoKb', 'The Focus Chat List command focuses the chat request/response list, which can be navigated with UpArrow/DownArrow and is currently not triggerable by a keybinding.'), keybindingService));
content.push(descriptionForCommand('workbench.action.chat.focusInput', localize('workbench.action.chat.focusInput', 'The Focus Chat Input command ({0}) focuses the input box for chat requests.'), localize('workbench.action.interactiveSession.focusInputNoKb', 'Focus Chat Input command focuses the input box for chat requests and is currently not triggerable by a keybinding.'), keybindingService));
content.push(descriptionForCommand('workbench.action.chat.nextCodeBlock', localize('workbench.action.chat.nextCodeBlock', 'The Chat: Next Code Block command ({0}) focuses the next code block within a response.'), localize('workbench.action.chat.nextCodeBlockNoKb', 'The Chat: Next Code Block command focuses the next code block within a response and is currently not triggerable by a keybinding.'), keybindingService));
content.push(descriptionForCommand('workbench.action.chat.clear', localize('workbench.action.chat.clear', 'The Chat Clear command ({0}) clears the request/response list.'), localize('workbench.action.chat.clearNoKb', 'The Chat Clear command clears the request/response list and is currently not triggerable by a keybinding.'), keybindingService));
} else {
const startChatKeybinding = keybindingService.lookupKeybinding('inlineChat.start')?.getAriaLabel();
content.push(localize('inlineChat.overview', "Inline chat occurs within a code editor and takes into account current selection. It is useful for refactoring, fixing, and more. Keep in mind that Copilot generated code may be incorrect."));
content.push(localize('inlineChat.access', "It can be activated via the Fix and Explain with Copilot context menu actions or directly using the command: Inline Chat: Start Code Chat ({0}).", startChatKeybinding));
content.push(localize('chat.requestHistoryInline', 'In the input box, use up and down arrows to navigate your request history. Edit input and use enter or the make request button to run a new request.'));
content.push(localize('inlineChat.contextActions', "Explain and Fix with Copilot actions run a request prefixed with /fix or /explain. These prefixes can be used directly in the input box to apply those specific actions."));
content.push(localize('inlineChat.overview', "Inline chat occurs within a code editor and takes into account the current selection. It is useful for refactoring, fixing, and more. Keep in mind that AI generated code may be incorrect."));
content.push(localize('inlineChat.access', "It can be activated via quick fix actions or directly using the command: Inline Chat: Start Code Chat ({0}).", startChatKeybinding));
content.push(inputBox);
content.push(localize('inlineChat.contextActions', "Context menu actions may run a request prefixed with /fix or /explain. These prefixes can be used directly in the input box to apply those specific actions."));
content.push(localize('inlineChat.fix', "When a request is prefixed with /fix, a response will indicate the problem with the current code. A diff editor will be rendered and can be reached by tabbing."));
const diffReviewKeybinding = keybindingService.lookupKeybinding('editor.action.diffReview.next')?.getAriaLabel();
content.push(diffReviewKeybinding ? localize('inlineChat.diff', "Once in the diff editor, enter review mode with ({0}). Use up and down arrows to navigate lines with the proposed changes.", diffReviewKeybinding) : localize('inlineChat.diffNoKb', "Tab again to enter the Diff editor with the changes and enter review mode with the Go to Next Difference Command. Use Up/DownArrow to navigate lines with the proposed changes."));
@@ -82,8 +82,8 @@
padding: 2px;
}
.interactive-item-container .header .monaco-toolbar .checked .action-label,
.interactive-item-container .header .monaco-toolbar .checked .action-label:hover {
.interactive-item-container .header .monaco-toolbar .checked.action-label,
.interactive-item-container .header .monaco-toolbar .checked.action-label:hover {
color: var(--vscode-inputOption-activeForeground) !important;
border-color: var(--vscode-inputOption-activeBorder);
background-color: var(--vscode-inputOption-activeBackground);
@@ -707,14 +707,15 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
hasEdits = true;
const contents = encodeBase64((await this.fileService.readFile(uri)).value);
editSessionSize += contents.length;
if (editSessionSize > this.editSessionsStorageService.SIZE_LIMIT) {
this.notificationService.error(localize('payload too large', 'Your working changes exceed the size limit and cannot be stored.'));
return undefined;
}
if (await this.fileService.exists(uri)) {
const contents = encodeBase64((await this.fileService.readFile(uri)).value);
editSessionSize += contents.length;
if (editSessionSize > this.editSessionsStorageService.SIZE_LIMIT) {
this.notificationService.error(localize('payload too large', 'Your working changes exceed the size limit and cannot be stored.'));
return undefined;
}
workingChanges.push({ type: ChangeType.Addition, fileType: FileType.File, contents: contents, relativeFilePath: relativeFilePath });
} else {
// Assume it's a deletion
@@ -33,7 +33,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
declare _serviceBrand: undefined;
public readonly SIZE_LIMIT = 1024 * 1024 * 2; // 2 MB
public readonly SIZE_LIMIT = Math.floor(1024 * 1024 * 1.9); // 2 MB
private serverConfiguration = this.productService['editSessions.store'];
private machineClient: IUserDataSyncMachinesService | undefined;
@@ -26,6 +26,7 @@ registerAction2(InlineChatActions.StartSessionAction);
registerAction2(InlineChatActions.UnstashSessionAction);
registerAction2(InlineChatActions.MakeRequestAction);
registerAction2(InlineChatActions.StopRequestAction);
registerAction2(InlineChatActions.ReRunRequestAction);
registerAction2(InlineChatActions.DiscardAction);
registerAction2(InlineChatActions.DiscardToClipboardAction);
registerAction2(InlineChatActions.DiscardUndoToNewFileAction);
@@ -159,6 +159,28 @@ export class MakeRequestAction extends AbstractInlineChatAction {
}
}
export class ReRunRequestAction extends AbstractInlineChatAction {
constructor() {
super({
id: 'inlineChat.regenerate',
title: localize('rerun', 'Regenerate Response'),
icon: Codicon.refresh,
precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_EMPTY.negate(), CTX_INLINE_CHAT_LAST_RESPONSE_TYPE),
menu: {
id: MENU_INLINE_CHAT_WIDGET_STATUS,
group: '0_main',
order: 1,
}
});
}
override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController): void {
ctrl.regenerate();
}
}
export class StopRequestAction extends AbstractInlineChatAction {
constructor() {
@@ -28,7 +28,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { EditResponse, EmptyResponse, ErrorResponse, ExpansionState, IInlineChatSessionService, MarkdownResponse, Session, SessionExchange } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
import { EditResponse, EmptyResponse, ErrorResponse, ExpansionState, IInlineChatSessionService, MarkdownResponse, Session, SessionExchange, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
import { EditModeStrategy, LivePreviewStrategy, LiveStrategy, PreviewStrategy } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies';
import { InlineChatZoneWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget';
import { CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_LAST_FEEDBACK, IInlineChatRequest, IInlineChatResponse, INLINE_CHAT_ID, EditMode, InlineChatResponseFeedbackKind, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, InlineChatResponseType, CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_STASHED_SESSION } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
@@ -58,7 +58,8 @@ const enum Message {
PAUSE_SESSION = 1 << 2,
CANCEL_REQUEST = 1 << 3,
CANCEL_INPUT = 1 << 4,
ACCEPT_INPUT = 1 << 5
ACCEPT_INPUT = 1 << 5,
RERUN_INPUT = 1 << 6,
}
export interface InlineChatRunOptions {
@@ -196,14 +197,15 @@ export class InlineChatController implements IEditorContribution {
private _showWidget(initialRender: boolean = false) {
assertType(this._activeSession);
assertType(this._strategy);
assertType(this._editor.hasModel());
let widgetPosition: Position | null;
let widgetPosition: Position | undefined;
if (initialRender) {
widgetPosition = this._editor.getPosition();
} else {
widgetPosition = this._strategy.getWidgetPosition();
widgetPosition = this._strategy.getWidgetPosition() ?? this._zone.value.position ?? this._activeSession.wholeRange.value.getEndPosition();
}
const position = ((widgetPosition ?? this._zone.value.position) ?? this._activeSession.wholeRange.value.getEndPosition());
const position = (widgetPosition);
this._zone.value.show(position);
if (initialRender) {
this._zone.value.setMargins(position);
@@ -284,7 +286,7 @@ export class InlineChatController implements IEditorContribution {
this._zone.value.widget.updateSlashCommands(this._activeSession.session.slashCommands ?? []);
this._zone.value.widget.placeholder = this._getPlaceholderText();
this._zone.value.widget.value = this._activeSession.lastInput ?? '';
this._zone.value.widget.value = this._activeSession.lastInput?.value ?? '';
this._zone.value.widget.updateInfo(this._activeSession.session.message ?? localize('welcome.1', "AI-generated code may be incorrect"));
this._zone.value.widget.preferredExpansionState = this._activeSession.lastExpansionState;
this._showWidget(true);
@@ -374,6 +376,7 @@ export class InlineChatController implements IEditorContribution {
private async [State.WAIT_FOR_INPUT](options: InlineChatRunOptions | undefined): Promise<State.ACCEPT | State.CANCEL | State.PAUSE | State.WAIT_FOR_INPUT | State.MAKE_REQUEST> {
assertType(this._activeSession);
assertType(this._strategy);
this._zone.value.widget.placeholder = this._getPlaceholderText();
@@ -413,6 +416,15 @@ export class InlineChatController implements IEditorContribution {
return State.PAUSE;
}
if (message & Message.RERUN_INPUT && this._activeSession.lastExchange) {
const { lastExchange } = this._activeSession;
this._activeSession.addInput(lastExchange.prompt.retry());
if (lastExchange.response instanceof EditResponse) {
await this._strategy.undoChanges(lastExchange.response);
}
return State.MAKE_REQUEST;
}
if (!this._zone.value.widget.value) {
return State.WAIT_FOR_INPUT;
}
@@ -436,7 +448,7 @@ export class InlineChatController implements IEditorContribution {
return State.WAIT_FOR_INPUT;
}
this._activeSession.addInput(input);
this._activeSession.addInput(new SessionPrompt(input));
return State.MAKE_REQUEST;
}
@@ -460,10 +472,10 @@ export class InlineChatController implements IEditorContribution {
const sw = StopWatch.create();
const request: IInlineChatRequest = {
prompt: this._activeSession.lastInput,
prompt: this._activeSession.lastInput.value,
attempt: this._activeSession.lastInput.attempt,
selection: this._editor.getSelection(),
wholeRange: this._activeSession.wholeRange.value,
attempt: 0,
};
const task = this._activeSession.provider.provideResponse(this._activeSession.session, request, requestCts.token);
this._log('request started', this._activeSession.provider.debugName, this._activeSession.session, request);
@@ -479,7 +491,7 @@ export class InlineChatController implements IEditorContribution {
if (reply?.type === 'message') {
response = new MarkdownResponse(this._activeSession.textModelN.uri, reply);
} else if (reply) {
response = new EditResponse(this._activeSession.textModelN.uri, reply);
response = new EditResponse(this._activeSession.textModelN.uri, this._activeSession.textModelN.getAlternativeVersionId(), reply);
} else {
response = new EmptyResponse();
}
@@ -499,7 +511,7 @@ export class InlineChatController implements IEditorContribution {
msgListener.dispose();
typeListener.dispose();
this._activeSession.addExchange(new SessionExchange(request.prompt, response));
this._activeSession.addExchange(new SessionExchange(this._activeSession.lastInput, response));
if (message & Message.CANCEL_SESSION) {
return State.CANCEL;
@@ -663,6 +675,10 @@ export class InlineChatController implements IEditorContribution {
this._messages.fire(Message.ACCEPT_INPUT);
}
regenerate(): void {
this._messages.fire(Message.RERUN_INPUT);
}
cancelCurrentRequest(): void {
this._messages.fire(Message.CANCEL_INPUT | Message.CANCEL_REQUEST);
}
@@ -700,7 +716,7 @@ export class InlineChatController implements IEditorContribution {
viewInChat() {
if (this._activeSession?.lastExchange?.response instanceof MarkdownResponse) {
this._instaService.invokeFunction(showMessageResponse, this._activeSession.lastExchange.prompt, this._activeSession.lastExchange.response.raw.message.value);
this._instaService.invokeFunction(showMessageResponse, this._activeSession.lastExchange.prompt.value, this._activeSession.lastExchange.response.raw.message.value);
}
}
@@ -44,6 +44,7 @@ export class InlineChatLivePreviewWidget extends ZoneWidget {
private readonly _inlineDiffDecorations: IEditorDecorationsCollection;
private _dim: Dimension | undefined;
private _isVisible: boolean = false;
private _isDiffLocked: boolean = false;
constructor(
editor: ICodeEditor,
@@ -134,6 +135,8 @@ export class InlineChatLivePreviewWidget extends ZoneWidget {
override show(): void {
assertType(this.editor.hasModel());
this._sessionStore.clear();
this._isDiffLocked = false;
this._isVisible = true;
this._sessionStore.add(this._diffEditor.onDidUpdateDiff(() => {
const result = this._diffEditor.getDiffComputationResult();
@@ -148,12 +151,19 @@ export class InlineChatLivePreviewWidget extends ZoneWidget {
}
}));
this._updateFromChanges(this._session.wholeRange.value, this._session.lastTextModelChanges);
this._isVisible = true;
}
lockToDiff(): void {
this._isDiffLocked = true;
}
private _updateFromChanges(range: Range, changes: readonly LineRangeMapping[]): void {
assertType(this.editor.hasModel());
if (this._isDiffLocked) {
return;
}
if (changes.length === 0 || this._session.textModel0.getValueLength() === 0) {
// no change or changes to an empty file
this._logService.debug('[IE] livePreview-mode: no diff');
@@ -108,7 +108,7 @@ class SessionWholeRange {
export class Session {
private _lastInput: string | undefined;
private _lastInput: SessionPrompt | undefined;
private _lastExpansionState: ExpansionState | undefined;
private _lastTextModelChanges: readonly LineRangeMapping[] | undefined;
private _isUnstashed: boolean = false;
@@ -139,10 +139,14 @@ export class Session {
};
}
addInput(input: string): void {
addInput(input: SessionPrompt): void {
this._lastInput = input;
}
get lastInput() {
return this._lastInput;
}
get isUnstashed(): boolean {
return this._isUnstashed;
}
@@ -151,10 +155,6 @@ export class Session {
this._isUnstashed = true;
}
get lastInput() {
return this._lastInput;
}
get lastExpansionState(): ExpansionState | undefined {
return this._lastExpansionState;
}
@@ -229,7 +229,7 @@ export class Session {
for (const exchange of this._exchange) {
const response = exchange.response;
if (response instanceof MarkdownResponse || response instanceof EditResponse) {
result.exchanges.push({ prompt: exchange.prompt, res: response.raw });
result.exchanges.push({ prompt: exchange.prompt.value, res: response.raw });
}
}
return result;
@@ -237,9 +237,29 @@ export class Session {
}
export class SessionExchange {
export class SessionPrompt {
private _attempt: number = 0;
constructor(
readonly prompt: string,
readonly value: string,
) { }
get attempt() {
return this._attempt;
}
retry() {
const result = new SessionPrompt(this.value);
result._attempt = this._attempt + 1;
return result;
}
}
export class SessionExchange {
constructor(
readonly prompt: SessionPrompt,
readonly response: MarkdownResponse | EditResponse | EmptyResponse | ErrorResponse
) { }
}
@@ -275,7 +295,11 @@ export class EditResponse {
readonly workspaceEdits: ResourceEdit[] | undefined;
readonly workspaceEditsIncludeLocalEdits: boolean = false;
constructor(localUri: URI, readonly raw: IInlineChatBulkEditResponse | IInlineChatEditResponse) {
constructor(
localUri: URI,
readonly modelAltVersionId: number,
readonly raw: IInlineChatBulkEditResponse | IInlineChatEditResponse
) {
if (raw.type === 'editorEdit') {
//
this.localEdits = raw.edits;
@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { Lazy } from 'vs/base/common/lazy';
import { IDisposable } from 'vs/base/common/lifecycle';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
@@ -13,7 +14,7 @@ import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { IEditorDecorationsCollection } from 'vs/editor/common/editorCommon';
import { ICursorStateComputer, IModelDecorationOptions, IModelDeltaDecoration, IValidEditOperation } from 'vs/editor/common/model';
import { ICursorStateComputer, IModelDecorationOptions, IModelDeltaDecoration, ITextModel, IValidEditOperation } from 'vs/editor/common/model';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
import { localize } from 'vs/nls';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
@@ -37,13 +38,15 @@ export abstract class EditModeStrategy {
abstract makeChanges(edits: ISingleEditOperation[]): Promise<void>;
abstract undoChanges(response: EditResponse): Promise<void>;
abstract renderChanges(response: EditResponse): Promise<void>;
abstract toggleDiff(): void;
abstract hasFocus(): boolean;
abstract getWidgetPosition(): Position | null;
abstract getWidgetPosition(): Position | undefined;
}
export class PreviewStrategy extends EditModeStrategy {
@@ -112,6 +115,10 @@ export class PreviewStrategy extends EditModeStrategy {
// nothing to do
}
override async undoChanges(_response: EditResponse): Promise<void> {
// nothing to do
}
override async renderChanges(response: EditResponse): Promise<void> {
if (response.localEdits.length > 0) {
const edits = response.localEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text));
@@ -131,8 +138,8 @@ export class PreviewStrategy extends EditModeStrategy {
// nothing to do
}
getWidgetPosition(): Position | null {
return null;
getWidgetPosition(): Position | undefined {
return;
}
hasFocus(): boolean {
@@ -281,9 +288,7 @@ export class LiveStrategy extends EditModeStrategy {
return;
}
const targetAltVersion = textModelNSnapshotAltVersion ?? textModelNAltVersion;
while (targetAltVersion < modelN.getAlternativeVersionId() && modelN.canUndo()) {
modelN.undo();
}
LiveStrategy._undoModelUntil(modelN, targetAltVersion);
}
override async makeChanges(edits: ISingleEditOperation[], ignoreInlineDiff?: boolean): Promise<void> {
@@ -303,6 +308,11 @@ export class LiveStrategy extends EditModeStrategy {
this._editor.executeEdits('inline-chat-live', edits, ignoreInlineDiff ? undefined : cursorStateComputerAndInlineDiffCollection);
}
override async undoChanges(response: EditResponse): Promise<void> {
const { textModelN } = this._session;
LiveStrategy._undoModelUntil(textModelN, response.modelAltVersionId);
}
override async renderChanges(response: EditResponse) {
this._inlineDiffDecorations.update();
@@ -315,6 +325,12 @@ export class LiveStrategy extends EditModeStrategy {
}
}
private static _undoModelUntil(model: ITextModel, targetAltVersion: number): void {
while (targetAltVersion < model.getAlternativeVersionId() && model.canUndo()) {
model.undo();
}
}
protected _updateSummaryMessage() {
let linesChanged = 0;
for (const change of this._session.lastTextModelChanges) {
@@ -331,7 +347,7 @@ export class LiveStrategy extends EditModeStrategy {
this._widget.updateStatus(message);
}
private _lastLineOfLocalEdits(): number | undefined {
override getWidgetPosition(): Position | undefined {
const lastTextModelChanges = this._session.lastTextModelChanges;
let lastLineOfLocalEdits: number | undefined;
for (const change of lastTextModelChanges) {
@@ -340,16 +356,7 @@ export class LiveStrategy extends EditModeStrategy {
lastLineOfLocalEdits = changeEndLineNumber;
}
}
return lastLineOfLocalEdits;
}
override getWidgetPosition(): Position | null {
const isEditResponse = this._session.lastExchange?.response instanceof EditResponse;
if (isEditResponse) {
const lastLineOfLocalEdits = this._lastLineOfLocalEdits();
return lastLineOfLocalEdits ? new Position(lastLineOfLocalEdits, 1) : null;
}
return null;
return lastLineOfLocalEdits ? new Position(lastLineOfLocalEdits, 1) : undefined;
}
hasFocus(): boolean {
@@ -359,8 +366,8 @@ export class LiveStrategy extends EditModeStrategy {
export class LivePreviewStrategy extends LiveStrategy {
private readonly _diffZone: InlineChatLivePreviewWidget;
private readonly _previewZone: InlineChatFileCreatePreviewWidget;
private readonly _diffZone: Lazy<InlineChatLivePreviewWidget>;
private readonly _previewZone: Lazy<InlineChatFileCreatePreviewWidget>;
constructor(
session: Session,
@@ -374,15 +381,15 @@ export class LivePreviewStrategy extends LiveStrategy {
) {
super(session, editor, widget, contextKeyService, storageService, bulkEditService, editorWorkerService, instaService);
this._diffZone = instaService.createInstance(InlineChatLivePreviewWidget, editor, session);
this._previewZone = instaService.createInstance(InlineChatFileCreatePreviewWidget, editor);
this._diffZone = new Lazy(() => instaService.createInstance(InlineChatLivePreviewWidget, editor, session));
this._previewZone = new Lazy(() => instaService.createInstance(InlineChatFileCreatePreviewWidget, editor));
}
override dispose(): void {
this._diffZone.hide();
this._diffZone.dispose();
this._previewZone.hide();
this._previewZone.dispose();
this._diffZone.rawValue?.hide();
this._diffZone.rawValue?.dispose();
this._previewZone.rawValue?.hide();
this._previewZone.rawValue?.dispose();
super.dispose();
}
@@ -390,36 +397,33 @@ export class LivePreviewStrategy extends LiveStrategy {
this._updateSummaryMessage();
if (this._diffEnabled) {
this._diffZone.show();
this._diffZone.value.show();
}
if (response.singleCreateFileEdit) {
this._previewZone.showCreation(this._session.wholeRange.value, response.singleCreateFileEdit.uri, await Promise.all(response.singleCreateFileEdit.edits));
this._previewZone.value.showCreation(this._session.wholeRange.value, response.singleCreateFileEdit.uri, await Promise.all(response.singleCreateFileEdit.edits));
} else {
this._previewZone.hide();
this._previewZone.value.hide();
}
}
override async undoChanges(response: EditResponse): Promise<void> {
this._diffZone.value.lockToDiff();
super.undoChanges(response);
}
protected override _doToggleDiff(): void {
const scrollState = StableEditorScrollState.capture(this._editor);
if (this._diffEnabled) {
this._diffZone.show();
this._diffZone.value.show();
} else {
this._diffZone.hide();
this._diffZone.value.hide();
}
scrollState.restore(this._editor);
}
override getWidgetPosition(): Position | null {
const isEditResponse = this._session.lastExchange?.response instanceof EditResponse;
if (isEditResponse) {
return this._session.wholeRange.value.getEndPosition();
}
return null;
}
override hasFocus(): boolean {
return super.hasFocus() || this._diffZone.hasFocus() || this._previewZone.hasFocus();
return super.hasFocus() || this._diffZone.value.hasFocus() || this._previewZone.value.hasFocus();
}
}
@@ -321,7 +321,7 @@ class TimerCellStatusBarItem extends Disposable {
return <INotebookCellStatusBarItem>{
text: formatCellDuration(duration, false),
alignment: CellStatusbarAlignment.Left,
priority: Number.MAX_SAFE_INTEGER - 1,
priority: Number.MAX_SAFE_INTEGER - 5,
tooltip
};
}
@@ -33,7 +33,6 @@ import { ThemeIcon } from 'vs/base/common/themables';
import { isWorkspaceFolder, IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { settingsEditIcon, settingsScopeDropDownIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { CONTEXT_SETTINGS_EDITOR_IN_USER_TAB } from 'vs/workbench/contrib/preferences/common/preferences';
@@ -52,7 +51,6 @@ export class FolderSettingsActionViewItem extends BaseActionViewItem {
action: IAction,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IPreferencesService private readonly preferencesService: IPreferencesService,
) {
super(null, action);
const workspace = this.contextService.getWorkspace();
@@ -142,14 +140,14 @@ export class FolderSettingsActionViewItem extends BaseActionViewItem {
}
}
private async update(): Promise<void> {
private update(): void {
let total = 0;
this._folderSettingCounts.forEach(n => total += n);
const workspace = this.contextService.getWorkspace();
if (this._folder) {
this.labelElement.textContent = this._folder.name;
this.anchorElement.title = (await this.preferencesService.getEditableSettingsURI(ConfigurationTarget.WORKSPACE_FOLDER, this._folder.uri))?.fsPath || '';
this.anchorElement.title = this._folder.name;
const detailsText = this.labelWithCount(this._action.label, total);
this.detailsElement.textContent = detailsText;
this.dropDownElement.classList.toggle('hide', workspace.folders.length === 1 || !this._action.checked);
@@ -233,7 +231,6 @@ export class SettingsTargetsWidget extends Widget {
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@ILabelService private readonly labelService: ILabelService,
@IPreferencesService private readonly preferencesService: IPreferencesService,
@ILanguageService private readonly languageService: ILanguageService,
@IContextKeyService contextKeyService: IContextKeyService,
) {
@@ -265,15 +262,12 @@ export class SettingsTargetsWidget extends Widget {
}));
this.userLocalSettings = new Action('userSettings', '', '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_LOCAL));
this.preferencesService.getEditableSettingsURI(ConfigurationTarget.USER_LOCAL).then(uri => {
// Don't wait to create UI on resolving remote
this.userLocalSettings.tooltip = uri?.fsPath ?? '';
});
this.userLocalSettings.tooltip = localize('userSettings', "User");
this.userRemoteSettings = new Action('userSettingsRemote', '', '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_REMOTE));
this.preferencesService.getEditableSettingsURI(ConfigurationTarget.USER_REMOTE).then(uri => {
this.userRemoteSettings.tooltip = uri?.fsPath ?? '';
});
const remoteAuthority = this.environmentService.remoteAuthority;
const hostLabel = remoteAuthority && this.labelService.getHostLabel(Schemas.vscodeRemote, remoteAuthority);
this.userRemoteSettings.tooltip = localize('userSettingsRemote', "Remote") + (hostLabel ? ` [${hostLabel}]` : '');
this.workspaceSettings = new Action('workspaceSettings', '', '.settings-tab', false, () => this.updateTarget(ConfigurationTarget.WORKSPACE));
@@ -368,7 +362,7 @@ export class SettingsTargetsWidget extends Widget {
this.workspaceSettings.enabled = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY;
this.folderSettings.action.enabled = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE && this.contextService.getWorkspace().folders.length > 0;
this.workspaceSettings.tooltip = (await this.preferencesService.getEditableSettingsURI(ConfigurationTarget.WORKSPACE))?.fsPath || '';
this.workspaceSettings.tooltip = localize('workspaceSettings', "Workspace");
}
}