mirror of https://github.com/microsoft/vscode.git
edit reason refactoring (#252654)
* edit reason refactoring * Fixes missing service * Fixes tests * fixes tests
This commit is contained in:
parent
e7a37c24c2
commit
4c8ed58a9d
|
@ -990,6 +990,8 @@ export interface ICodeEditor extends editorCommon.IEditor {
|
|||
* @param endCursorState Cursor state after the edits were applied.
|
||||
*/
|
||||
executeEdits(source: string | null | undefined, edits: IIdentifiedSingleEditOperation[], endCursorState?: ICursorStateComputer | Selection[]): boolean;
|
||||
/** @internal */
|
||||
executeEdits(source: TextModelEditReason, edits: IIdentifiedSingleEditOperation[], endCursorState?: ICursorStateComputer | Selection[]): boolean;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
|
|
@ -60,7 +60,7 @@ import { INotificationService, Severity } from '../../../../platform/notificatio
|
|||
import { editorErrorForeground, editorHintForeground, editorInfoForeground, editorWarningForeground } from '../../../../platform/theme/common/colorRegistry.js';
|
||||
import { IThemeService, registerThemingParticipant } from '../../../../platform/theme/common/themeService.js';
|
||||
import { MenuId } from '../../../../platform/actions/common/actions.js';
|
||||
import { TextModelEditReason } from '../../../common/textModelEditReason.js';
|
||||
import { TextModelEditReason, EditReasons } from '../../../common/textModelEditReason.js';
|
||||
import { TextEdit } from '../../../common/core/edits/textEdit.js';
|
||||
|
||||
export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeEditor {
|
||||
|
@ -1242,10 +1242,10 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
|
|||
}
|
||||
|
||||
public edit(edit: TextEdit, reason: TextModelEditReason): boolean {
|
||||
return this.executeEdits(reason.metadata.source, edit.replacements.map<IIdentifiedSingleEditOperation>(e => ({ range: e.range, text: e.text })));
|
||||
return this.executeEdits(reason, edit.replacements.map<IIdentifiedSingleEditOperation>(e => ({ range: e.range, text: e.text })), undefined);
|
||||
}
|
||||
|
||||
public executeEdits(source: string | null | undefined, edits: IIdentifiedSingleEditOperation[], endCursorState?: ICursorStateComputer | Selection[], editReason?: TextModelEditReason): boolean {
|
||||
public executeEdits(source: string | null | undefined | TextModelEditReason, edits: IIdentifiedSingleEditOperation[], endCursorState?: ICursorStateComputer | Selection[]): boolean {
|
||||
if (!this._modelData) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1263,12 +1263,19 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
|
|||
cursorStateComputer = endCursorState;
|
||||
}
|
||||
|
||||
this._onBeforeExecuteEdit.fire({ source: source ?? undefined });
|
||||
let sourceStr: string | undefined | null;
|
||||
let reason: TextModelEditReason;
|
||||
|
||||
if (!editReason) {
|
||||
editReason = source ? new TextModelEditReason({ source: 'unknown', name: source }) : TextModelEditReason.Unknown;
|
||||
if (source instanceof TextModelEditReason) {
|
||||
reason = source;
|
||||
sourceStr = source.metadata.source;
|
||||
} else {
|
||||
reason = EditReasons.unknown({ name: sourceStr });
|
||||
sourceStr = source;
|
||||
}
|
||||
this._modelData.viewModel.executeEdits(source, edits, cursorStateComputer, editReason);
|
||||
|
||||
this._onBeforeExecuteEdit.fire({ source: sourceStr ?? undefined });
|
||||
this._modelData.viewModel.executeEdits(sourceStr, edits, cursorStateComputer, reason);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ import { VerticalRevealType, ViewCursorStateChangedEvent, ViewRevealRangeRequest
|
|||
import { dispose, Disposable } from '../../../base/common/lifecycle.js';
|
||||
import { ICoordinatesConverter } from '../viewModel.js';
|
||||
import { CursorStateChangedEvent, ViewModelEventsCollector } from '../viewModelEventDispatcher.js';
|
||||
import { TextModelEditReason } from '../textModelEditReason.js';
|
||||
import { TextModelEditReason, EditReasons } from '../textModelEditReason.js';
|
||||
|
||||
export class CursorsController extends Disposable {
|
||||
|
||||
|
@ -540,7 +540,7 @@ export class CursorsController extends Disposable {
|
|||
}
|
||||
|
||||
public endComposition(eventsCollector: ViewModelEventsCollector, source?: string | null | undefined): void {
|
||||
const reason = new TextModelEditReason({ source: 'cursor', kind: 'compositionEnd', detailedSource: source });
|
||||
const reason = EditReasons.cursor({ kind: 'compositionEnd', detailedSource: source });
|
||||
|
||||
const compositionOutcome = this._compositionState ? this._compositionState.deduceOutcome(this._model, this.getSelections()) : null;
|
||||
this._compositionState = null;
|
||||
|
@ -554,7 +554,7 @@ export class CursorsController extends Disposable {
|
|||
}
|
||||
|
||||
public type(eventsCollector: ViewModelEventsCollector, text: string, source?: string | null | undefined): void {
|
||||
const reason = new TextModelEditReason({ source: 'cursor', kind: 'type', detailedSource: source });
|
||||
const reason = EditReasons.cursor({ kind: 'type', detailedSource: source });
|
||||
|
||||
this._executeEdit(() => {
|
||||
if (source === 'keyboard') {
|
||||
|
@ -579,7 +579,7 @@ export class CursorsController extends Disposable {
|
|||
}
|
||||
|
||||
public compositionType(eventsCollector: ViewModelEventsCollector, text: string, replacePrevCharCnt: number, replaceNextCharCnt: number, positionDelta: number, source?: string | null | undefined): void {
|
||||
const reason = new TextModelEditReason({ source: 'cursor', kind: 'compositionType', detailedSource: source });
|
||||
const reason = EditReasons.cursor({ kind: 'compositionType', detailedSource: source });
|
||||
|
||||
if (text.length === 0 && replacePrevCharCnt === 0 && replaceNextCharCnt === 0) {
|
||||
// this edit is a no-op
|
||||
|
@ -599,7 +599,7 @@ export class CursorsController extends Disposable {
|
|||
}
|
||||
|
||||
public paste(eventsCollector: ViewModelEventsCollector, text: string, pasteOnNewLine: boolean, multicursorText?: string[] | null | undefined, source?: string | null | undefined): void {
|
||||
const reason = new TextModelEditReason({ source: 'cursor', kind: 'paste', detailedSource: source });
|
||||
const reason = EditReasons.cursor({ kind: 'paste', detailedSource: source });
|
||||
|
||||
this._executeEdit(() => {
|
||||
this._executeEditOperation(TypeOperations.paste(this.context.cursorConfig, this._model, this.getSelections(), text, pasteOnNewLine, multicursorText || []), reason);
|
||||
|
@ -607,14 +607,14 @@ export class CursorsController extends Disposable {
|
|||
}
|
||||
|
||||
public cut(eventsCollector: ViewModelEventsCollector, source?: string | null | undefined): void {
|
||||
const reason = new TextModelEditReason({ source: 'cursor', kind: 'cut', detailedSource: source });
|
||||
const reason = EditReasons.cursor({ kind: 'cut', detailedSource: source });
|
||||
this._executeEdit(() => {
|
||||
this._executeEditOperation(DeleteOperations.cut(this.context.cursorConfig, this._model, this.getSelections()), reason);
|
||||
}, eventsCollector, source);
|
||||
}
|
||||
|
||||
public executeCommand(eventsCollector: ViewModelEventsCollector, command: editorCommon.ICommand, source?: string | null | undefined): void {
|
||||
const reason = new TextModelEditReason({ source: 'cursor', kind: 'executeCommand', detailedSource: source });
|
||||
const reason = EditReasons.cursor({ kind: 'executeCommand', detailedSource: source });
|
||||
|
||||
this._executeEdit(() => {
|
||||
this._cursors.killSecondaryCursors();
|
||||
|
@ -627,7 +627,7 @@ export class CursorsController extends Disposable {
|
|||
}
|
||||
|
||||
public executeCommands(eventsCollector: ViewModelEventsCollector, commands: editorCommon.ICommand[], source?: string | null | undefined): void {
|
||||
const reason = new TextModelEditReason({ source: 'cursor', kind: 'executeCommands', detailedSource: source });
|
||||
const reason = EditReasons.cursor({ kind: 'executeCommands', detailedSource: source });
|
||||
|
||||
this._executeEdit(() => {
|
||||
this._executeEditOperation(new EditOperationResult(EditOperationType.Other, commands, {
|
||||
|
@ -756,7 +756,7 @@ interface ICommandsData {
|
|||
|
||||
export class CommandExecutor {
|
||||
|
||||
public static executeCommands(model: ITextModel, selectionsBefore: Selection[], commands: (editorCommon.ICommand | null)[], editReason: TextModelEditReason = TextModelEditReason.Unknown): Selection[] | null {
|
||||
public static executeCommands(model: ITextModel, selectionsBefore: Selection[], commands: (editorCommon.ICommand | null)[], editReason: TextModelEditReason = EditReasons.unknown({ name: 'executeCommands' })): Selection[] | null {
|
||||
|
||||
const ctx: IExecContext = {
|
||||
model: model,
|
||||
|
|
|
@ -15,7 +15,7 @@ import * as buffer from '../../../base/common/buffer.js';
|
|||
import { IDisposable } from '../../../base/common/lifecycle.js';
|
||||
import { basename } from '../../../base/common/resources.js';
|
||||
import { ISingleEditOperation } from '../core/editOperation.js';
|
||||
import { TextModelEditReason } from '../textModelEditReason.js';
|
||||
import { EditReasons, TextModelEditReason } from '../textModelEditReason.js';
|
||||
|
||||
function uriGetComparisonKey(resource: URI): string {
|
||||
return resource.toString();
|
||||
|
@ -425,7 +425,7 @@ export class EditStack {
|
|||
editStackElement.append(this._model, [], getModelEOL(this._model), this._model.getAlternativeVersionId(), null);
|
||||
}
|
||||
|
||||
public pushEditOperation(beforeCursorState: Selection[] | null, editOperations: ISingleEditOperation[], cursorStateComputer: ICursorStateComputer | null, group?: UndoRedoGroup, reason: TextModelEditReason = TextModelEditReason.Unknown): Selection[] | null {
|
||||
public pushEditOperation(beforeCursorState: Selection[] | null, editOperations: ISingleEditOperation[], cursorStateComputer: ICursorStateComputer | null, group?: UndoRedoGroup, reason: TextModelEditReason = EditReasons.unknown({ name: 'pushEditOperation' })): Selection[] | null {
|
||||
const editStackElement = this._getOrCreateEditStackElement(beforeCursorState, group);
|
||||
const inverseEditOperations = this._model.applyEdits(editOperations, true, reason);
|
||||
const afterCursorState = EditStack._computeCursorState(cursorStateComputer, inverseEditOperations);
|
||||
|
|
|
@ -48,7 +48,7 @@ import { IColorTheme } from '../../../platform/theme/common/themeService.js';
|
|||
import { IUndoRedoService, ResourceEditStackSnapshot, UndoRedoGroup } from '../../../platform/undoRedo/common/undoRedo.js';
|
||||
import { TokenArray } from '../tokens/lineTokens.js';
|
||||
import { SetWithKey } from '../../../base/common/collections.js';
|
||||
import { TextModelEditReason } from '../textModelEditReason.js';
|
||||
import { EditReasons, TextModelEditReason } from '../textModelEditReason.js';
|
||||
import { TextEdit } from '../core/edits/textEdit.js';
|
||||
|
||||
export function createTextBufferFactory(text: string): model.ITextBufferFactory {
|
||||
|
@ -451,7 +451,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
|
|||
this._eventEmitter.fire(new InternalModelContentChangeEvent(rawChange, change));
|
||||
}
|
||||
|
||||
public setValue(value: string | model.ITextSnapshot, reason = TextModelEditReason.SetValue): void {
|
||||
public setValue(value: string | model.ITextSnapshot, reason = EditReasons.setValue()): void {
|
||||
this._assertNotDisposed();
|
||||
|
||||
if (value === null || value === undefined) {
|
||||
|
@ -541,7 +541,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
|
|||
false,
|
||||
false
|
||||
),
|
||||
this._createContentChanged2(new Range(1, 1, endLineNumber, endColumn), 0, oldModelValueLength, new Position(endLineNumber, endColumn), this.getValue(), false, false, false, true, TextModelEditReason.EolChange)
|
||||
this._createContentChanged2(new Range(1, 1, endLineNumber, endColumn), 0, oldModelValueLength, new Position(endLineNumber, endColumn), this.getValue(), false, false, false, true, EditReasons.eolChange())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1443,7 +1443,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
|
|||
this._eventEmitter.beginDeferredEmit();
|
||||
const operations = this._validateEditOperations(rawOperations);
|
||||
|
||||
return this._doApplyEdits(operations, computeUndoEdits ?? false, reason ?? TextModelEditReason.ApplyEdits);
|
||||
return this._doApplyEdits(operations, computeUndoEdits ?? false, reason ?? EditReasons.applyEdits());
|
||||
} finally {
|
||||
this._eventEmitter.endDeferredEmit();
|
||||
this._onDidChangeDecorations.endDeferredEmit();
|
||||
|
|
|
@ -24,7 +24,7 @@ import { isEditStackElement } from '../model/editStack.js';
|
|||
import { Schemas } from '../../../base/common/network.js';
|
||||
import { equals } from '../../../base/common/objects.js';
|
||||
import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';
|
||||
import { TextModelEditReason } from '../textModelEditReason.js';
|
||||
import { EditReasons, TextModelEditReason } from '../textModelEditReason.js';
|
||||
|
||||
function MODEL_ID(resource: URI): string {
|
||||
return resource.toString();
|
||||
|
@ -369,7 +369,7 @@ export class ModelService extends Disposable implements IModelService {
|
|||
return modelData;
|
||||
}
|
||||
|
||||
public updateModel(model: ITextModel, value: string | ITextBufferFactory, reason: TextModelEditReason = TextModelEditReason.Unknown): void {
|
||||
public updateModel(model: ITextModel, value: string | ITextBufferFactory, reason: TextModelEditReason = EditReasons.unknown({ name: 'updateModel' })): void {
|
||||
const options = this.getCreationOptions(model.getLanguageId(), model.uri, model.isForSimpleWidget);
|
||||
const { textBuffer, disposable } = createTextBuffer(value, options.defaultEOL);
|
||||
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export class TextModelEditReason {
|
||||
public static readonly EolChange = new TextModelEditReason({ source: 'eolChange' });
|
||||
public static readonly SetValue = new TextModelEditReason({ source: 'setValue' });
|
||||
public static readonly ApplyEdits = new TextModelEditReason({ source: 'applyEdits' });
|
||||
public static readonly Unknown = new TextModelEditReason({ source: 'unknown', name: 'unknown' });
|
||||
const privateSymbol = Symbol('TextModelEditReason');
|
||||
|
||||
constructor(public readonly metadata: ITextModelEditReasonMetadata) { }
|
||||
export class TextModelEditReason {
|
||||
constructor(
|
||||
public readonly metadata: ITextModelEditReasonMetadata,
|
||||
_privateCtorGuard: typeof privateSymbol,
|
||||
) { }
|
||||
|
||||
public toString(): string {
|
||||
return `${this.metadata.source}`;
|
||||
|
@ -21,28 +21,92 @@ export class TextModelEditReason {
|
|||
case 'cursor':
|
||||
return metadata.kind;
|
||||
case 'inlineCompletionAccept':
|
||||
return metadata.source + (metadata.nes ? ':nes' : '');
|
||||
return metadata.source + (metadata.$nes ? ':nes' : '');
|
||||
case 'unknown':
|
||||
return metadata.name;
|
||||
return metadata.name || 'unknown';
|
||||
default:
|
||||
return metadata.source;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the metadata to a key string.
|
||||
* Only includes properties/values that have `level` many `$` prefixes or less.
|
||||
*/
|
||||
public toKey(level: number): string {
|
||||
const metadata = this.metadata;
|
||||
const keys = Object.entries(metadata).filter(([key, value]) => {
|
||||
const prefixCount = (key.match(/\$/g) || []).length;
|
||||
return prefixCount <= level && value !== undefined && value !== null && value !== '';
|
||||
}).map(([key, value]) => `${key}:${value}`);
|
||||
return keys.join('-');
|
||||
}
|
||||
}
|
||||
|
||||
export type ITextModelEditReasonMetadata = {
|
||||
source: 'Chat.applyEdits' | 'inlineChat.applyEdit' | 'reloadFromDisk' | 'eolChange' | 'setValue' | 'applyEdits';
|
||||
} | {
|
||||
source: 'inlineCompletionAccept';
|
||||
nes: boolean;
|
||||
type: 'word' | 'line' | undefined;
|
||||
requestUuid: string;
|
||||
extensionId: string | undefined;
|
||||
} | {
|
||||
source: 'cursor';
|
||||
kind: 'compositionType' | 'compositionEnd' | 'type' | 'paste' | 'cut' | 'executeCommands' | 'executeCommand';
|
||||
detailedSource?: string | null | undefined;
|
||||
} | {
|
||||
source: 'unknown';
|
||||
name: string;
|
||||
type TextModelEditReasonT<T> = TextModelEditReason & {
|
||||
metadataT: T;
|
||||
};
|
||||
|
||||
function createEditReason<T extends Record<string, any>>(metadata: T): TextModelEditReasonT<T> {
|
||||
return new TextModelEditReason(metadata as any, privateSymbol) as any;
|
||||
}
|
||||
|
||||
export const EditReasons = {
|
||||
unknown(data: { name?: string | null }) {
|
||||
return createEditReason({
|
||||
source: 'unknown',
|
||||
name: data.name,
|
||||
} as const);
|
||||
},
|
||||
|
||||
chatApplyEdits(data: { modelId: string | undefined }) {
|
||||
return createEditReason({
|
||||
source: 'Chat.applyEdits',
|
||||
$modelId: data.modelId,
|
||||
} as const);
|
||||
},
|
||||
|
||||
inlineCompletionAccept(data: { nes: boolean; requestUuid: string; extensionId: string }) {
|
||||
return createEditReason({
|
||||
source: 'inlineCompletionAccept',
|
||||
$nes: data.nes,
|
||||
$extensionId: data.extensionId,
|
||||
$$requestUuid: data.requestUuid,
|
||||
} as const);
|
||||
},
|
||||
|
||||
inlineCompletionPartialAccept(data: { nes: boolean; requestUuid: string; extensionId: string; type: 'word' | 'line' }) {
|
||||
return createEditReason({
|
||||
source: 'inlineCompletionPartialAccept',
|
||||
type: data.type,
|
||||
$extensionId: data.extensionId,
|
||||
$$requestUuid: data.requestUuid,
|
||||
} as const);
|
||||
},
|
||||
|
||||
inlineChatApplyEdit(data: { modelId: string | undefined }) {
|
||||
return createEditReason({
|
||||
source: 'inlineChat.applyEdits',
|
||||
$modelId: data.modelId,
|
||||
} as const);
|
||||
},
|
||||
|
||||
reloadFromDisk: () => createEditReason({ source: 'reloadFromDisk' } as const),
|
||||
|
||||
cursor(data: { kind: 'compositionType' | 'compositionEnd' | 'type' | 'paste' | 'cut' | 'executeCommands' | 'executeCommand'; detailedSource?: string | null }) {
|
||||
return createEditReason({
|
||||
source: 'cursor',
|
||||
kind: data.kind,
|
||||
detailedSource: data.detailedSource,
|
||||
} as const);
|
||||
},
|
||||
|
||||
setValue: () => createEditReason({ source: 'setValue' } as const),
|
||||
eolChange: () => createEditReason({ source: 'eolChange' } as const),
|
||||
applyEdits: () => createEditReason({ source: 'applyEdits' } as const),
|
||||
snippet: () => createEditReason({ source: 'snippet' } as const),
|
||||
suggest: (data: { extensionId: string | undefined }) => createEditReason({ source: 'suggest', $extensionId: data.extensionId } as const),
|
||||
};
|
||||
|
||||
type Values<T> = T[keyof T];
|
||||
type ITextModelEditReasonMetadata = Values<{ [TKey in keyof typeof EditReasons]: ReturnType<typeof EditReasons[TKey]>['metadataT'] }>;
|
||||
|
|
|
@ -7,7 +7,7 @@ import { IPosition } from './core/position.js';
|
|||
import { IRange, Range } from './core/range.js';
|
||||
import { Selection } from './core/selection.js';
|
||||
import { IModelDecoration, InjectedTextOptions } from './model.js';
|
||||
import { ITextModelEditReasonMetadata, TextModelEditReason } from './textModelEditReason.js';
|
||||
import { TextModelEditReason } from './textModelEditReason.js';
|
||||
|
||||
/**
|
||||
* An event describing that the current language associated with a model has changed.
|
||||
|
@ -137,7 +137,7 @@ export interface ISerializedModelContentChangedEvent {
|
|||
* Detailed reason information for the change
|
||||
* @internal
|
||||
*/
|
||||
readonly detailedReason: ITextModelEditReasonMetadata | undefined;
|
||||
readonly detailedReason: Record<string, unknown> | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,8 +4,9 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from '../../../../../base/common/lifecycle.js';
|
||||
import { autorunWithStore } from '../../../../../base/common/observable.js';
|
||||
import { autorun, observableFromEvent } from '../../../../../base/common/observable.js';
|
||||
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
|
||||
import { canLog, ILoggerService, LogLevel } from '../../../../../platform/log/common/log.js';
|
||||
import { ICodeEditor } from '../../../../browser/editorBrowser.js';
|
||||
import { CodeEditorWidget } from '../../../../browser/widget/codeEditor/codeEditorWidget.js';
|
||||
import { IDocumentEventDataSetChangeReason, IRecordableEditorLogEntry, StructuredLogger } from '../structuredLogger.js';
|
||||
|
@ -23,16 +24,36 @@ export class TextModelChangeRecorder extends Disposable {
|
|||
constructor(
|
||||
private readonly _editor: ICodeEditor,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@ILoggerService private readonly _loggerService: ILoggerService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._structuredLogger = this._register(this._instantiationService.createInstance(StructuredLogger.cast<IRecordableEditorLogEntry & IDocumentEventDataSetChangeReason>(),
|
||||
'editor.inlineSuggest.logChangeReason.commandId'
|
||||
));
|
||||
this._register(autorunWithStore((reader, store) => {
|
||||
|
||||
const logger = this._loggerService?.createLogger('textModelChanges', { hidden: false, name: 'Text Model Changes Reason' });
|
||||
|
||||
const loggingLevel = observableFromEvent(this, logger.onDidChangeLogLevel, () => logger.getLevel());
|
||||
|
||||
this._register(autorun(reader => {
|
||||
if (!canLog(loggingLevel.read(reader), LogLevel.Trace)) {
|
||||
return;
|
||||
}
|
||||
|
||||
reader.store.add(this._editor.onDidChangeModelContent((e) => {
|
||||
if (this._editor.getModel()?.uri.scheme === 'output') {
|
||||
return;
|
||||
}
|
||||
logger.trace('onDidChangeModelContent: ' + e.detailedReasons.map(r => r.toKey(Number.MAX_VALUE)).join(', '));
|
||||
}));
|
||||
}));
|
||||
|
||||
this._register(autorun(reader => {
|
||||
if (!(this._editor instanceof CodeEditorWidget)) { return; }
|
||||
if (!this._structuredLogger.isEnabled.read(reader)) { return; }
|
||||
|
||||
store.add(this._editor.onDidChangeModelContent(e => {
|
||||
reader.store.add(this._editor.onDidChangeModelContent(e => {
|
||||
const tm = this._editor.getModel();
|
||||
if (!tm) { return; }
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ import { InlineCompletionItem, InlineEditItem, InlineSuggestionItem } from './in
|
|||
import { InlineCompletionContextWithoutUuid, InlineCompletionEditorType, InlineSuggestRequestInfo } from './provideInlineCompletions.js';
|
||||
import { singleTextEditAugments, singleTextRemoveCommonPrefix } from './singleTextEditHelpers.js';
|
||||
import { SuggestItemInfo } from './suggestWidgetAdapter.js';
|
||||
import { TextModelEditReason } from '../../../../common/textModelEditReason.js';
|
||||
import { TextModelEditReason, EditReasons } from '../../../../common/textModelEditReason.js';
|
||||
import { ICodeEditorService } from '../../../../browser/services/codeEditorService.js';
|
||||
import { InlineCompletionViewData, InlineCompletionViewKind } from '../view/inlineEdits/inlineEditsViewInterface.js';
|
||||
|
||||
|
@ -774,13 +774,20 @@ export class InlineCompletionsModel extends Disposable {
|
|||
public async previous(): Promise<void> { await this._deltaSelectedInlineCompletionIndex(-1); }
|
||||
|
||||
private _getMetadata(completion: InlineSuggestionItem, type: 'word' | 'line' | undefined = undefined): TextModelEditReason {
|
||||
return new TextModelEditReason({
|
||||
source: 'inlineCompletionAccept',
|
||||
extensionId: completion.source.provider.groupId,
|
||||
nes: completion.isInlineEdit,
|
||||
type,
|
||||
requestUuid: completion.requestUuid,
|
||||
});
|
||||
if (type) {
|
||||
return EditReasons.inlineCompletionPartialAccept({
|
||||
nes: completion.isInlineEdit,
|
||||
requestUuid: completion.requestUuid,
|
||||
extensionId: completion.source.provider.groupId ?? 'unknown',
|
||||
type,
|
||||
});
|
||||
} else {
|
||||
return EditReasons.inlineCompletionAccept({
|
||||
nes: completion.isInlineEdit,
|
||||
requestUuid: completion.requestUuid,
|
||||
extensionId: completion.source.provider.groupId ?? 'unknown',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async accept(editor: ICodeEditor = this._editor): Promise<void> {
|
||||
|
|
|
@ -50,7 +50,7 @@ import { ILayoutService } from '../../../platform/layout/browser/layoutService.j
|
|||
import { StandaloneServicesNLS } from '../../common/standaloneStrings.js';
|
||||
import { basename } from '../../../base/common/resources.js';
|
||||
import { ICodeEditorService } from '../../browser/services/codeEditorService.js';
|
||||
import { ConsoleLogger, ILogService } from '../../../platform/log/common/log.js';
|
||||
import { ConsoleLogger, ILoggerService, ILogService, NullLoggerService } from '../../../platform/log/common/log.js';
|
||||
import { IWorkspaceTrustManagementService, IWorkspaceTrustTransitionParticipant, IWorkspaceTrustUriInfo } from '../../../platform/workspace/common/workspaceTrust.js';
|
||||
import { EditorOption } from '../../common/config/editorOptions.js';
|
||||
import { ICodeEditor, IDiffEditor } from '../../browser/editorBrowser.js';
|
||||
|
@ -1128,6 +1128,7 @@ export interface IEditorOverrideServices {
|
|||
[index: string]: any;
|
||||
}
|
||||
|
||||
|
||||
registerSingleton(ILogService, StandaloneLogService, InstantiationType.Eager);
|
||||
registerSingleton(IConfigurationService, StandaloneConfigurationService, InstantiationType.Eager);
|
||||
registerSingleton(ITextResourceConfigurationService, StandaloneResourceConfigurationService, InstantiationType.Eager);
|
||||
|
@ -1163,6 +1164,7 @@ registerSingleton(IContextMenuService, StandaloneContextMenuService, Instantiati
|
|||
registerSingleton(IMenuService, MenuService, InstantiationType.Eager);
|
||||
registerSingleton(IAccessibilitySignalService, StandaloneAccessbilitySignalService, InstantiationType.Eager);
|
||||
registerSingleton(ITreeSitterLibraryService, StandaloneTreeSitterLibraryService, InstantiationType.Eager);
|
||||
registerSingleton(ILoggerService, NullLoggerService, InstantiationType.Eager);
|
||||
|
||||
/**
|
||||
* We don't want to eagerly instantiate services because embedders get a one time chance
|
||||
|
|
|
@ -29,7 +29,7 @@ import { ITestCodeEditor, TestCodeEditorInstantiationOptions, createCodeEditorSe
|
|||
import { IRelaxedTextModelCreationOptions, createTextModel, instantiateTextModel } from '../../common/testTextModel.js';
|
||||
import { TestInstantiationService } from '../../../../platform/instantiation/test/common/instantiationServiceMock.js';
|
||||
import { InputMode } from '../../../common/inputMode.js';
|
||||
import { TextModelEditReason } from '../../../common/textModelEditReason.js';
|
||||
import { EditReasons } from '../../../common/textModelEditReason.js';
|
||||
|
||||
// --------- utils
|
||||
|
||||
|
@ -5651,7 +5651,7 @@ suite('Editor Controller', () => {
|
|||
}, (editor, model, viewModel) => {
|
||||
viewModel.setSelections('test', [new Selection(1, 8, 1, 8)]);
|
||||
|
||||
viewModel.executeEdits('snippet', [{ range: new Range(1, 6, 1, 8), text: 'id=""' }], () => [new Selection(1, 10, 1, 10)], TextModelEditReason.Unknown);
|
||||
viewModel.executeEdits('snippet', [{ range: new Range(1, 6, 1, 8), text: 'id=""' }], () => [new Selection(1, 10, 1, 10)], EditReasons.unknown({}));
|
||||
assert.strictEqual(model.getLineContent(1), '<div id=""');
|
||||
|
||||
viewModel.type('a', 'keyboard');
|
||||
|
|
|
@ -48,7 +48,7 @@ import { ServiceCollection } from '../../../platform/instantiation/common/servic
|
|||
import { TestInstantiationService } from '../../../platform/instantiation/test/common/instantiationServiceMock.js';
|
||||
import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js';
|
||||
import { MockContextKeyService, MockKeybindingService } from '../../../platform/keybinding/test/common/mockKeybindingService.js';
|
||||
import { ILogService, NullLogService } from '../../../platform/log/common/log.js';
|
||||
import { ILoggerService, ILogService, NullLoggerService, NullLogService } from '../../../platform/log/common/log.js';
|
||||
import { INotificationService } from '../../../platform/notification/common/notification.js';
|
||||
import { TestNotificationService } from '../../../platform/notification/test/common/testNotificationService.js';
|
||||
import { IOpenerService } from '../../../platform/opener/common/opener.js';
|
||||
|
@ -213,6 +213,7 @@ export function createCodeEditorServices(disposables: Pick<DisposableStore, 'add
|
|||
define(IContextKeyService, MockContextKeyService);
|
||||
define(ICommandService, TestCommandService);
|
||||
define(ITelemetryService, NullTelemetryServiceShape);
|
||||
define(ILoggerService, NullLoggerService);
|
||||
define(IEnvironmentService, class extends mock<IEnvironmentService>() {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
override isBuilt: boolean = true;
|
||||
|
|
|
@ -754,6 +754,15 @@ export class NullLogService extends NullLogger implements ILogService {
|
|||
declare readonly _serviceBrand: undefined;
|
||||
}
|
||||
|
||||
export class NullLoggerService extends AbstractLoggerService {
|
||||
constructor() {
|
||||
super(LogLevel.Off, URI.parse('log:///log'));
|
||||
}
|
||||
protected override doCreateLogger(resource: URI, logLevel: LogLevel, options?: ILoggerOptions): ILogger {
|
||||
return new NullLogger();
|
||||
}
|
||||
}
|
||||
|
||||
export function getLogLevel(environmentService: IEnvironmentService): LogLevel {
|
||||
if (environmentService.verbose) {
|
||||
return LogLevel.Trace;
|
||||
|
|
|
@ -217,7 +217,7 @@ export class ExtHostDocuments implements ExtHostDocumentsShape {
|
|||
}),
|
||||
reason,
|
||||
detailedReason: events.detailedReason ? {
|
||||
source: events.detailedReason.source,
|
||||
source: events.detailedReason.source as string,
|
||||
metadata: events.detailedReason,
|
||||
} : undefined,
|
||||
}));
|
||||
|
|
|
@ -181,7 +181,7 @@ export class ChatEditingModifiedDocumentEntry extends AbstractChatEditingModifie
|
|||
|
||||
async acceptAgentEdits(resource: URI, textEdits: (TextEdit | ICellEditOperation)[], isLastEdits: boolean, responseModel: IChatResponseModel): Promise<void> {
|
||||
|
||||
const result = await this._textModelChangeService.acceptAgentEdits(resource, textEdits, isLastEdits);
|
||||
const result = await this._textModelChangeService.acceptAgentEdits(resource, textEdits, isLastEdits, responseModel);
|
||||
|
||||
transaction((tx) => {
|
||||
this._waitsForLastEdits.set(!isLastEdits, tx);
|
||||
|
|
|
@ -23,12 +23,13 @@ import { IModelDeltaDecoration, ITextModel, ITextSnapshot, MinimapPosition, Over
|
|||
import { ModelDecorationOptions } from '../../../../../editor/common/model/textModel.js';
|
||||
import { offsetEditFromContentChanges, offsetEditFromLineRangeMapping, offsetEditToEditOperations } from '../../../../../editor/common/model/textModelStringEdit.js';
|
||||
import { IEditorWorkerService } from '../../../../../editor/common/services/editorWorker.js';
|
||||
import { TextModelEditReason } from '../../../../../editor/common/textModelEditReason.js';
|
||||
import { TextModelEditReason, EditReasons } from '../../../../../editor/common/textModelEditReason.js';
|
||||
import { IModelContentChangedEvent } from '../../../../../editor/common/textModelEvents.js';
|
||||
import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js';
|
||||
import { editorSelectionBackground } from '../../../../../platform/theme/common/colorRegistry.js';
|
||||
import { ICellEditOperation } from '../../../notebook/common/notebookCommon.js';
|
||||
import { ModifiedFileEntryState } from '../../common/chatEditingService.js';
|
||||
import { IChatResponseModel } from '../../common/chatModel.js';
|
||||
import { IDocumentDiff2 } from './chatEditingCodeEditorIntegration.js';
|
||||
import { pendingRewriteMinimap } from './chatEditingModifiedFileEntry.js';
|
||||
|
||||
|
@ -127,7 +128,7 @@ export class ChatEditingTextModelChangeService extends Disposable {
|
|||
return diff ? diff.identical : false;
|
||||
}
|
||||
|
||||
async acceptAgentEdits(resource: URI, textEdits: (TextEdit | ICellEditOperation)[], isLastEdits: boolean): Promise<{ rewriteRatio: number; maxLineNumber: number }> {
|
||||
async acceptAgentEdits(resource: URI, textEdits: (TextEdit | ICellEditOperation)[], isLastEdits: boolean, responseModel: IChatResponseModel): Promise<{ rewriteRatio: number; maxLineNumber: number }> {
|
||||
|
||||
assertType(textEdits.every(TextEdit.isTextEdit), 'INVALID args, can only handle text edits');
|
||||
assert(isEqual(resource, this.modifiedModel.uri), ' INVALID args, can only edit THIS document');
|
||||
|
@ -136,11 +137,14 @@ export class ChatEditingTextModelChangeService extends Disposable {
|
|||
let maxLineNumber = 0;
|
||||
let rewriteRatio = 0;
|
||||
|
||||
const modelId = responseModel.session.getRequests().at(-1)?.modelId;
|
||||
const reason = EditReasons.chatApplyEdits({ modelId: modelId });
|
||||
|
||||
if (isAtomicEdits) {
|
||||
// EDIT and DONE
|
||||
const minimalEdits = await this._editorWorkerService.computeMoreMinimalEdits(this.modifiedModel.uri, textEdits) ?? textEdits;
|
||||
const ops = minimalEdits.map(TextEdit.asEditOperation);
|
||||
const undoEdits = this._applyEdits(ops);
|
||||
const undoEdits = this._applyEdits(ops, reason);
|
||||
|
||||
if (undoEdits.length > 0) {
|
||||
let range: Range | undefined;
|
||||
|
@ -176,7 +180,7 @@ export class ChatEditingTextModelChangeService extends Disposable {
|
|||
} else {
|
||||
// EDIT a bit, then DONE
|
||||
const ops = textEdits.map(TextEdit.asEditOperation);
|
||||
const undoEdits = this._applyEdits(ops);
|
||||
const undoEdits = this._applyEdits(ops, reason);
|
||||
maxLineNumber = undoEdits.reduce((max, op) => Math.max(max, op.range.startLineNumber), 0);
|
||||
rewriteRatio = Math.min(1, maxLineNumber / this.modifiedModel.getLineCount());
|
||||
|
||||
|
@ -207,7 +211,10 @@ export class ChatEditingTextModelChangeService extends Disposable {
|
|||
return { rewriteRatio, maxLineNumber };
|
||||
}
|
||||
|
||||
private _applyEdits(edits: ISingleEditOperation[]) {
|
||||
private _applyEdits(edits: ISingleEditOperation[], reason?: TextModelEditReason) {
|
||||
if (!reason) {
|
||||
reason = EditReasons.chatApplyEdits({ modelId: undefined });
|
||||
}
|
||||
try {
|
||||
this._isEditFromUs = true;
|
||||
// make the actual edit
|
||||
|
@ -216,7 +223,7 @@ export class ChatEditingTextModelChangeService extends Disposable {
|
|||
this.modifiedModel.pushEditOperations(null, edits, (undoEdits) => {
|
||||
result = undoEdits;
|
||||
return null;
|
||||
}, undefined, new TextModelEditReason({ source: 'Chat.applyEdits' }));
|
||||
}, undefined, reason);
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
|
|
|
@ -83,7 +83,7 @@ export class ChatEditingNotebookCellEntry extends Disposable {
|
|||
}
|
||||
|
||||
async acceptAgentEdits(textEdits: TextEdit[], isLastEdits: boolean, responseModel: IChatResponseModel): Promise<void> {
|
||||
const { maxLineNumber } = await this._textModelChangeService.acceptAgentEdits(this.modifiedModel.uri, textEdits, isLastEdits);
|
||||
const { maxLineNumber } = await this._textModelChangeService.acceptAgentEdits(this.modifiedModel.uri, textEdits, isLastEdits, responseModel);
|
||||
|
||||
transaction((tx) => {
|
||||
if (!isLastEdits) {
|
||||
|
|
|
@ -11,7 +11,7 @@ import { IProgress } from '../../../../platform/progress/common/progress.js';
|
|||
import { IntervalTimer, AsyncIterableSource } from '../../../../base/common/async.js';
|
||||
import { CancellationToken } from '../../../../base/common/cancellation.js';
|
||||
import { getNWords } from '../../chat/common/chatWordCounter.js';
|
||||
import { TextModelEditReason } from '../../../../editor/common/textModelEditReason.js';
|
||||
import { EditReasons } from '../../../../editor/common/textModelEditReason.js';
|
||||
|
||||
|
||||
|
||||
|
@ -52,7 +52,7 @@ export async function performAsyncTextEdit(model: ITextModel, edit: AsyncTextEdi
|
|||
model.pushEditOperations(null, [edit], (undoEdits) => {
|
||||
progress?.report(undoEdits);
|
||||
return null;
|
||||
}, undefined, new TextModelEditReason({ source: 'inlineChat.applyEdit' }));
|
||||
}, undefined, EditReasons.inlineChatApplyEdit({ modelId: undefined }));
|
||||
|
||||
obs?.stop();
|
||||
first = false;
|
||||
|
|
|
@ -35,7 +35,7 @@ import { IExtensionService } from '../../extensions/common/extensions.js';
|
|||
import { IMarkdownString } from '../../../../base/common/htmlContent.js';
|
||||
import { IProgress, IProgressService, IProgressStep, ProgressLocation } from '../../../../platform/progress/common/progress.js';
|
||||
import { isCancellationError } from '../../../../base/common/errors.js';
|
||||
import { TextModelEditReason } from '../../../../editor/common/textModelEditReason.js';
|
||||
import { TextModelEditReason, EditReasons } from '../../../../editor/common/textModelEditReason.js';
|
||||
|
||||
interface IBackupMetaData extends IWorkingCopyBackupMeta {
|
||||
mtime: number;
|
||||
|
@ -536,7 +536,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
|||
|
||||
// Update Existing Model
|
||||
if (this.textEditorModel) {
|
||||
this.doUpdateTextModel(content.value, new TextModelEditReason({ source: 'reloadFromDisk' }));
|
||||
this.doUpdateTextModel(content.value, EditReasons.reloadFromDisk());
|
||||
}
|
||||
|
||||
// Create New Model
|
||||
|
|
Loading…
Reference in New Issue