Skip to content

Commit

Permalink
save wip
Browse files Browse the repository at this point in the history
  • Loading branch information
alexdima committed Aug 7, 2024
1 parent e2e66ad commit d92fc8e
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 28 deletions.
9 changes: 6 additions & 3 deletions src/vs/editor/common/services/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ITextBufferFactory, ITextModel, ITextModelCreationOptions } from 'vs/ed
import { ILanguageSelection } from 'vs/editor/common/languages/language';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { DocumentSemanticTokensProvider, DocumentRangeSemanticTokensProvider } from 'vs/editor/common/languages';
import { IModelContentChangedEvent } from 'vs/editor/common/textModelEvents';

export const IModelService = createDecorator<IModelService>('modelService');

Expand All @@ -21,6 +22,8 @@ export interface IModelService {

updateModel(model: ITextModel, value: string | ITextBufferFactory): void;

getRecentModelContentChangeEvents(model: ITextModel): readonly IModelContentChangedEvent[];

destroyModel(resource: URI): void;

getModels(): ITextModel[];
Expand All @@ -29,9 +32,9 @@ export interface IModelService {

getModel(resource: URI): ITextModel | null;

onModelAdded: Event<ITextModel>;
readonly onModelAdded: Event<ITextModel>;

onModelRemoved: Event<ITextModel>;
readonly onModelRemoved: Event<ITextModel>;

onModelLanguageChanged: Event<{ model: ITextModel; oldLanguageId: string }>;
readonly onModelLanguageChanged: Event<{ model: ITextModel; oldLanguageId: string }>;
}
46 changes: 44 additions & 2 deletions src/vs/editor/common/services/modelService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { Range } from 'vs/editor/common/core/range';
import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions } from 'vs/editor/common/model';
import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel';
import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/core/textModelDefaults';
import { IModelLanguageChangedEvent } from 'vs/editor/common/textModelEvents';
import { IModelContentChangedEvent, IModelLanguageChangedEvent } from 'vs/editor/common/textModelEvents';
import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry';
import { ILanguageSelection, ILanguageService } from 'vs/editor/common/languages/language';
import { IModelService } from 'vs/editor/common/services/model';
Expand All @@ -24,6 +24,7 @@ import { isEditStackElement } from 'vs/editor/common/model/editStack';
import { Schemas } from 'vs/base/common/network';
import { equals } from 'vs/base/common/objects';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { IntervalTimer } from 'vs/base/common/async';

function MODEL_ID(resource: URI): string {
return resource.toString();
Expand All @@ -32,20 +33,42 @@ function MODEL_ID(resource: URI): string {
class ModelData implements IDisposable {

private readonly _modelEventListeners = new DisposableStore();
private readonly _recentModelContentChangeEvents: RecentModelContentChangedEvent[] = [];

constructor(
public readonly model: TextModel,
onWillDispose: (model: ITextModel) => void,
onDidChangeLanguage: (model: ITextModel, e: IModelLanguageChangedEvent) => void
onDidChangeLanguage: (model: ITextModel, e: IModelLanguageChangedEvent) => void,
) {
this.model = model;
this._modelEventListeners.add(model.onWillDispose(() => onWillDispose(model)));
this._modelEventListeners.add(model.onDidChangeLanguage((e) => onDidChangeLanguage(model, e)));
this._modelEventListeners.add(model.onDidChangeContent((e) => {
this._recentModelContentChangeEvents.push(new RecentModelContentChangedEvent(e));
}));
}

public dispose(): void {
this._modelEventListeners.dispose();
}

public getRecentModelContentChangedEvents(): readonly IModelContentChangedEvent[] {
return this._recentModelContentChangeEvents.map(e => e.event);
}

public pruneRecentModelContentChangedEvents(): void {
const timeCutOff = Date.now() - 30 * 1000 /* 30 seconds ago */;
while (this._recentModelContentChangeEvents.length > 0 && this._recentModelContentChangeEvents[0].time < timeCutOff) {
this._recentModelContentChangeEvents.shift();
}
}
}

class RecentModelContentChangedEvent {
constructor(
public readonly event: IModelContentChangedEvent,
public readonly time = Date.now()
) { }
}

interface IRawEditorConfig {
Expand Down Expand Up @@ -118,6 +141,17 @@ export class ModelService extends Disposable implements IModelService {

this._register(this._configurationService.onDidChangeConfiguration(e => this._updateModelOptions(e)));
this._updateModelOptions(undefined);

// Clean up recent model content change events
const timer = this._register(new IntervalTimer());
timer.cancelAndSet(() => {
const keys = Object.keys(this._models);
for (let i = 0, len = keys.length; i < len; i++) {
const modelId = keys[i];
const modelData = this._models[modelId];
modelData.pruneRecentModelContentChangedEvents();
}
}, 10 * 1000);
}

private static _readModelOptions(config: IRawConfig, isForSimpleWidget: boolean): ITextModelCreationOptions {
Expand Down Expand Up @@ -445,6 +479,14 @@ export class ModelService extends Disposable implements IModelService {
return [EditOperation.replaceMove(oldRange, textBuffer.getValueInRange(newRange, EndOfLinePreference.TextDefined))];
}

public getRecentModelContentChangeEvents(model: ITextModel): readonly IModelContentChangedEvent[] {
const modelData = this._models[MODEL_ID(model.uri)];
if (!modelData) {
return [];
}
return modelData.getRecentModelContentChangedEvents();
}

public createModel(value: string | ITextBufferFactory, languageSelection: ILanguageSelection | null, resource?: URI, isForSimpleWidget: boolean = false): ITextModel {
let modelData: ModelData;

Expand Down
135 changes: 118 additions & 17 deletions src/vs/workbench/api/browser/mainThreadEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ import { ITextModel, ITextModelUpdateOptions } from 'vs/editor/common/model';
import { ISingleEditOperation } from 'vs/editor/common/core/editOperation';
import { IModelService } from 'vs/editor/common/services/model';
import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2';
import { IApplyEditsOptions, IEditorPropertiesChangeData, IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate, IUndoStopOptions, TextEditorRevealType } from 'vs/workbench/api/common/extHost.protocol';
import { IApplyEditsOptions, IEditorPropertiesChangeData, IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate, IUndoStopOptions, SetDecorationsResult, TextEditorRevealType } from 'vs/workbench/api/common/extHost.protocol';
import { IEditorPane } from 'vs/workbench/common/editor';
import { equals } from 'vs/base/common/arrays';
import { CodeEditorStateFlag, EditorState } from 'vs/editor/contrib/editorState/browser/editorState';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser';
import { MainThreadDocuments } from 'vs/workbench/api/browser/mainThreadDocuments';
import { ISnippetEdit } from 'vs/editor/contrib/snippet/browser/snippetSession';
import { IModelContentChange, IModelContentChangedEvent } from 'vs/editor/common/textModelEvents';
import { Position } from 'vs/editor/common/core/position';
import { splitLines } from 'vs/base/common/strings';

export interface IFocusTracker {
onGainedFocus(): void;
Expand Down Expand Up @@ -420,34 +423,49 @@ export class MainThreadTextEditor {
}
}

public setDecorations(key: string, versionIdCheck: number, ranges: IDecorationOptions[]): boolean {
public setDecorations(key: string, versionIdCheck: number, ranges: IDecorationOptions[]): SetDecorationsResult {
if (!this._codeEditor) {
return false;
}
if (this._model.getVersionId() !== versionIdCheck) {
// throw new Error('Model has changed in the meantime!');
// model changed in the meantime
return false;
return { type: 'error' };
}
const currentModelVersionId = this._model.getVersionId();
const modelChangedInMeantime = currentModelVersionId !== versionIdCheck;
this._codeEditor.setDecorationsByType('exthost-api', key, ranges);
return true;
return (
modelChangedInMeantime
? { type: 'warn', versionId: currentModelVersionId }
: { type: 'ok' }
);
}

public setDecorationsFast(key: string, versionIdCheck: number, _ranges: number[]): boolean {
public setDecorationsFast(key: string, versionIdCheck: number, _ranges: number[]): SetDecorationsResult {
if (!this._codeEditor) {
return false;
}
if (this._model.getVersionId() !== versionIdCheck) {
// throw new Error('Model has changed in the meantime!');
// model changed in the meantime
return false;
return { type: 'error' };
}
const currentModelVersionId = this._model.getVersionId();
const modelChangedInMeantime = currentModelVersionId !== versionIdCheck;
const ranges: Range[] = [];
for (let i = 0, len = Math.floor(_ranges.length / 4); i < len; i++) {
ranges[i] = new Range(_ranges[4 * i], _ranges[4 * i + 1], _ranges[4 * i + 2], _ranges[4 * i + 3]);
}
this._codeEditor.setDecorationsByTypeFast(key, ranges);
return true;
return (
modelChangedInMeantime
? { type: 'warn', versionId: currentModelVersionId }
: { type: 'ok' }
);
}

private _createRangeTransformer(range: Range[], rangeVersionId: number): (rng: Range)Range[] {
const recentChangeEvents = this._modelService.getRecentModelContentChangeEvents(this._model);
const missedRecentChangeEvent = recentChangeEvents.filter(change => change.versionId > rangeVersionId);

// const pendingChanges = ;
// this._modelService.getRecentModelContentChangeEvents(this._model).
// if (this._model.getVersionId() === modelVersionId) {
// // the model version is still the same
// return selections;
// }
// return selections.map(selection => this._model.normalizeSelectionRange(selection));
}

public revealRange(range: IRange, revealType: TextEditorRevealType): void {
Expand Down Expand Up @@ -560,3 +578,86 @@ export class MainThreadTextEditor {
return true;
}
}

interface IRangeTransformer {
(range: Range): Range;
}

type CreateRecentChangesRangeTransformerResult = (
{ kind: 'versionTooOld' }
| { kind: 'versionUpToDate' }
| { kind: 'transformer', value: IRangeTransformer }
);

function createRecentChangesTransformer(modelService: IModelService, model: ITextModel, versionId: number): CreateRecentChangesRangeTransformerResult {
if (model.getVersionId() === versionId) {
return { kind: 'versionUpToDate' };
}
const recentChangeEvents = modelService.getRecentModelContentChangeEvents(model);
const missedRecentChangeEvents = recentChangeEvents.filter(change => change.versionId > versionId);
if (missedRecentChangeEvents.length === 0) {
// versionId is too old and we no longer have these recent changes
return { kind: 'versionTooOld' };
}
const firstChangeEvent = missedRecentChangeEvents[0];
if (firstChangeEvent.versionId !== versionId + 1) {
// cannot compute transformer because some changes have been dropped
return { kind: 'versionTooOld' };
}
return { kind: 'transformer', value: createRangeTransformer(missedRecentChangeEvents) };
}

function createRangeTransformer(changes: IModelContentChangedEvent[]): IRangeTransformer {
return (range: Range): Range => {
// let result = range;
let startPosition = range.getStartPosition();
let endPosition = range.getEndPosition();
for (const change of changes) {
for (const innerChange of change.changes) {
result = applyEditToRange(result, innerChange);
}
}
return result;
};
}

function applyEditToRange(range: Range, edit: IModelContentChange): Range {
// const [startLineNumber, startColumn] = RangeUtils.getLineNumberAndColumnFromOffset(range.startLineNumber, range.startColumn, edit.range.startLineNumber, edit.range.startColumn, edit.text);
// const [endLineNumber, endColumn] = RangeUtils.getLineNumberAndColumnFromOffset(range.endLineNumber, range.endColumn, edit.range.endLineNumber, edit.range.endColumn, edit.text);
// return new Range(startLineNumber, startColumn, endLineNumber, endColumn);
}

//function createPositionTransformer(edit: IModelContentChange[]): (position: Position) => Position {

function convertPositionAgainstEdit(position: Position, edit: IModelContentChange) {
const lineNumber = position.lineNumber;
const column = position.column;

const editStartLineNumber = edit.range.startLineNumber;
const editStartColumn = edit.range.startColumn;
const editEndLineNumber = edit.range.endLineNumber;
const editEndColumn = edit.range.endColumn;

if (lineNumber < editStartLineNumber || (lineNumber === editStartLineNumber && column < editStartColumn)) {
// Position is before the edit range, no need to adjust
return position;
}

if (lineNumber > editEndLineNumber || (lineNumber === editEndLineNumber && column >= editEndColumn)) {
// Position is after the edit range, adjust by the difference in length
const newLines = splitLines(edit.text);
const lineDelta = newLines.length - (editEndLineNumber - editStartLineNumber + 1);
const columnDelta = newLines.length > 0 ? newLines[0].length - editEndColumn + column : 0;
return new Position(lineNumber + lineDelta, column + columnDelta);
}

if (lineNumber === editStartLineNumber && column >= editStartColumn) {
// Position is at the start of the edit range, adjust by the difference in length
const newLines = splitLines(edit.text);
const columnDelta = newLines.length > 0 ? newLines[0].length - editStartColumn + column : 0;
return new Position(lineNumber, column + columnDelta);
}

// Position is inside the edit range, return null to indicate that the position is deleted
return null;
}
9 changes: 5 additions & 4 deletions src/vs/workbench/api/browser/mainThreadEditors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ITextEditorOptions, IResourceEditorInput, EditorActivation, EditorResolution } from 'vs/platform/editor/common/editor';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { MainThreadTextEditor } from 'vs/workbench/api/browser/mainThreadEditor';
import { ExtHostContext, ExtHostEditorsShape, IApplyEditsOptions, ITextDocumentShowOptions, ITextEditorConfigurationUpdate, ITextEditorPositionData, IUndoStopOptions, MainThreadTextEditorsShape, TextEditorRevealType } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostContext, ExtHostEditorsShape, IApplyEditsOptions, ITextDocumentShowOptions, ITextEditorConfigurationUpdate, ITextEditorPositionData, IUndoStopOptions, MainThreadTextEditorsShape, SetDecorationsResult, TextEditorRevealType } from 'vs/workbench/api/common/extHost.protocol';
import { editorGroupToColumn, columnToEditorGroup, EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
Expand Down Expand Up @@ -75,7 +75,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
this._textEditorsListenersMap = Object.create(null);
this._toDispose.dispose();
for (const decorationType in this._registeredDecorationTypes) {
this._codeEditorService.removeDecorationType(decorationType);
// this._codeEditorService.removeDecorationType(decorationType);
}
this._registeredDecorationTypes = Object.create(null);
}
Expand Down Expand Up @@ -180,16 +180,17 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
return Promise.resolve(undefined);
}

$trySetDecorations(id: string, modelVersionId: number, key: string, ranges: IDecorationOptions[]): Promise<boolean> {
$trySetDecorations(id: string, modelVersionId: number, key: string, ranges: IDecorationOptions[]): Promise<SetDecorationsResult> {
key = `${this._instanceId}-${key}`;

const editor = this._editorLocator.getEditor(id);
if (!editor) {
return Promise.reject(illegalArgument(`TextEditor(${id})`));
}
return Promise.resolve(editor.setDecorations(key, modelVersionId, ranges));
}

$trySetDecorationsFast(id: string, modelVersionId: number, key: string, ranges: number[]): Promise<boolean> {
$trySetDecorationsFast(id: string, modelVersionId: number, key: string, ranges: number[]): Promise<SetDecorationsResult> {
key = `${this._instanceId}-${key}`;
const editor = this._editorLocator.getEditor(id);
if (!editor) {
Expand Down
6 changes: 4 additions & 2 deletions src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,15 +272,17 @@ export interface MainThreadTextEditorsShape extends IDisposable {
$tryShowEditor(id: string, position: EditorGroupColumn): Promise<void>;
$tryHideEditor(id: string): Promise<void>;
$trySetOptions(id: string, options: ITextEditorConfigurationUpdate): Promise<void>;
$trySetDecorations(id: string, key: string, ranges: editorCommon.IDecorationOptions[]): Promise<void>;
$trySetDecorationsFast(id: string, key: string, ranges: number[]): Promise<void>;
$trySetDecorations(id: string, modelVersionId: number, key: string, ranges: editorCommon.IDecorationOptions[]): Promise<SetDecorationsResult>;
$trySetDecorationsFast(id: string, modelVersionId: number, key: string, ranges: number[]): Promise<SetDecorationsResult>;
$tryRevealRange(id: string, range: IRange, revealType: TextEditorRevealType): Promise<void>;
$trySetSelections(id: string, selections: ISelection[]): Promise<void>;
$tryApplyEdits(id: string, modelVersionId: number, edits: ISingleEditOperation[], opts: IApplyEditsOptions): Promise<boolean>;
$tryInsertSnippet(id: string, modelVersionId: number, template: string, selections: readonly IRange[], opts: IUndoStopOptions): Promise<boolean>;
$getDiffInformation(id: string): Promise<IChange[]>;
}

export type SetDecorationsResult = { type: 'ok' } | { type: 'warn'; versionId: number } | { type: 'error' };

export interface MainThreadTreeViewsShape extends IDisposable {
$registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean; canSelectMany: boolean; dropMimeTypes: readonly string[]; dragMimeTypes: readonly string[]; hasHandleDrag: boolean; hasHandleDrop: boolean; manuallyManageCheckboxes: boolean }): Promise<void>;
$refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem }): Promise<void>;
Expand Down

0 comments on commit d92fc8e

Please sign in to comment.