Skip to content

Commit 72d14ad

Browse files
committed
refactor suggest land to read clipboard async and only when needed, microsoft#98497
1 parent b49d4cf commit 72d14ad

14 files changed

Lines changed: 105 additions & 74 deletions

File tree

src/vs/editor/contrib/snippet/snippetController2.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,6 @@ export class SnippetController2 implements IEditorContribution {
4545
return editor.getContribution<SnippetController2>(SnippetController2.ID);
4646
}
4747

48-
static guessNeedsClipboard(template: string): boolean {
49-
return /\${?CLIPBOARD/.test(template);
50-
}
51-
5248
static readonly InSnippetMode = new RawContextKey('inSnippetMode', false);
5349
static readonly HasNextTabstop = new RawContextKey('hasNextTabstop', false);
5450
static readonly HasPrevTabstop = new RawContextKey('hasPrevTabstop', false);

src/vs/editor/contrib/snippet/snippetParser.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,10 @@ export class SnippetParser {
586586
return value.replace(/\$|}|\\/g, '\\$&');
587587
}
588588

589+
static guessNeedsClipboard(template: string): boolean {
590+
return /\${?CLIPBOARD/.test(template);
591+
}
592+
589593
private _scanner: Scanner = new Scanner();
590594
private _token: Token = { type: TokenType.EOF, pos: 0, len: 0 };
591595

src/vs/editor/contrib/snippet/snippetSession.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import { Range } from 'vs/editor/common/core/range';
1414
import { Selection } from 'vs/editor/common/core/selection';
1515
import { IIdentifiedSingleEditOperation, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model';
1616
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
17-
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
1817
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
1918
import { optional } from 'vs/platform/instantiation/common/instantiation';
2019
import { Choice, Placeholder, SnippetParser, Text, TextmateSnippet, Marker } from './snippetParser';
@@ -396,9 +395,7 @@ export class SnippetSession {
396395

397396
const workspaceService = editor.invokeWithinContext(accessor => accessor.get(IWorkspaceContextService, optional));
398397
const modelBasedVariableResolver = editor.invokeWithinContext(accessor => new ModelBasedVariableResolver(accessor.get(ILabelService, optional), model));
399-
400-
const clipboardService = editor.invokeWithinContext(accessor => accessor.get(IClipboardService, optional));
401-
const readClipboardText = () => clipboardText || clipboardService && clipboardService.readTextSync();
398+
const readClipboardText = () => clipboardText;
402399

403400
let delta = 0;
404401

src/vs/editor/contrib/suggest/completionModel.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ const enum Refilter {
4141
Incr = 2
4242
}
4343

44+
/**
45+
* Sorted, filtered completion view model
46+
* */
4447
export class CompletionModel {
4548

4649
private readonly _items: CompletionItem[];
@@ -61,7 +64,8 @@ export class CompletionModel {
6164
lineContext: LineContext,
6265
wordDistance: WordDistance,
6366
options: InternalSuggestOptions,
64-
snippetSuggestions: 'top' | 'bottom' | 'inline' | 'none'
67+
snippetSuggestions: 'top' | 'bottom' | 'inline' | 'none',
68+
readonly clipboardText: string | undefined
6569
) {
6670
this._items = items;
6771
this._column = column;

src/vs/editor/contrib/suggest/suggest.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
1414
import { CancellationToken } from 'vs/base/common/cancellation';
1515
import { Range } from 'vs/editor/common/core/range';
1616
import { FuzzyScore } from 'vs/base/common/filters';
17-
import { isDisposable, DisposableStore } from 'vs/base/common/lifecycle';
17+
import { isDisposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
1818
import { MenuId } from 'vs/platform/actions/common/actions';
19+
import { SnippetParser } from 'vs/editor/contrib/snippet/snippetParser';
1920

2021
export const Context = {
2122
Visible: new RawContextKey<boolean>('suggestWidgetVisible', false),
@@ -163,13 +164,21 @@ export function setSnippetSuggestSupport(support: modes.CompletionItemProvider):
163164
return old;
164165
}
165166

167+
class CompletionItemModel {
168+
constructor(
169+
readonly items: CompletionItem[],
170+
readonly needsClipboard: boolean,
171+
readonly dispoables: IDisposable,
172+
) { }
173+
}
174+
166175
export async function provideSuggestionItems(
167176
model: ITextModel,
168177
position: Position,
169178
options: CompletionOptions = CompletionOptions.default,
170179
context: modes.CompletionContext = { triggerKind: modes.CompletionTriggerKind.Invoke },
171180
token: CancellationToken = CancellationToken.None
172-
): Promise<CompletionItem[]> {
181+
): Promise<CompletionItemModel> {
173182

174183
// const t1 = Date.now();
175184
position = position.clone();
@@ -180,6 +189,7 @@ export async function provideSuggestionItems(
180189

181190
const result: CompletionItem[] = [];
182191
const disposables = new DisposableStore();
192+
let needsClipboard = false;
183193

184194
const onCompletionList = (provider: modes.CompletionItemProvider, container: modes.CompletionList | null | undefined) => {
185195
if (!container) {
@@ -195,6 +205,9 @@ export async function provideSuggestionItems(
195205
if (!suggestion.sortText) {
196206
suggestion.sortText = typeof suggestion.label === 'string' ? suggestion.label : suggestion.label.name;
197207
}
208+
if (!needsClipboard && suggestion.insertTextRules && suggestion.insertTextRules & modes.CompletionItemInsertTextRule.InsertAsSnippet) {
209+
needsClipboard = SnippetParser.guessNeedsClipboard(suggestion.insertText);
210+
}
198211
result.push(new CompletionItem(position, suggestion, container, provider));
199212
}
200213
}
@@ -248,7 +261,11 @@ export async function provideSuggestionItems(
248261
return Promise.reject<any>(canceled());
249262
}
250263
// console.log(`${result.length} items AFTER ${Date.now() - t1}ms`);
251-
return result.sort(getSuggestionComparator(options.snippetSortOrder));
264+
return new CompletionItemModel(
265+
result.sort(getSuggestionComparator(options.snippetSortOrder)),
266+
needsClipboard,
267+
disposables
268+
);
252269
}
253270

254271

@@ -310,27 +327,23 @@ registerDefaultLanguageCommand('_executeCompletionItemProvider', async (model, p
310327
suggestions: []
311328
};
312329

313-
const disposables = new DisposableStore();
314330
const resolving: Promise<any>[] = [];
315331
const maxItemsToResolve = args['maxItemsToResolve'] || 0;
316332

317-
const items = await provideSuggestionItems(model, position);
318-
for (const item of items) {
333+
const completions = await provideSuggestionItems(model, position);
334+
for (const item of completions.items) {
319335
if (resolving.length < maxItemsToResolve) {
320336
resolving.push(item.resolve(CancellationToken.None));
321337
}
322338
result.incomplete = result.incomplete || item.container.incomplete;
323339
result.suggestions.push(item.completion);
324-
if (isDisposable(item.container)) {
325-
disposables.add(item.container);
326-
}
327340
}
328341

329342
try {
330343
await Promise.all(resolving);
331344
return result;
332345
} finally {
333-
setTimeout(() => disposables.dispose(), 100);
346+
setTimeout(() => completions.dispoables.dispose(), 100);
334347
}
335348
});
336349

src/vs/editor/contrib/suggest/suggestController.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import { MenuRegistry } from 'vs/platform/actions/common/actions';
4242
import { CancellationTokenSource } from 'vs/base/common/cancellation';
4343
import { ILogService } from 'vs/platform/log/common/log';
4444
import { StopWatch } from 'vs/base/common/stopwatch';
45+
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
4546

4647
// sticky suggest widget which doesn't disappear on focus out and such
4748
let _sticky = false;
@@ -120,9 +121,10 @@ export class SuggestController implements IEditorContribution {
120121
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
121122
@IInstantiationService private readonly _instantiationService: IInstantiationService,
122123
@ILogService private readonly _logService: ILogService,
124+
@IClipboardService clipboardService: IClipboardService,
123125
) {
124126
this.editor = editor;
125-
this.model = new SuggestModel(this.editor, editorWorker);
127+
this.model = new SuggestModel(this.editor, editorWorker, clipboardService);
126128

127129
this.widget = this._toDispose.add(new IdleValue(() => {
128130

@@ -358,7 +360,8 @@ export class SuggestController implements IEditorContribution {
358360
overwriteAfter: info.overwriteAfter,
359361
undoStopBefore: false,
360362
undoStopAfter: false,
361-
adjustWhitespace: !(item.completion.insertTextRules! & CompletionItemInsertTextRule.KeepWhitespace)
363+
adjustWhitespace: !(item.completion.insertTextRules! & CompletionItemInsertTextRule.KeepWhitespace),
364+
clipboardText: event.model.clipboardText
362365
});
363366

364367
if (!(flags & InsertFlags.NoAfterUndoStop)) {

src/vs/editor/contrib/suggest/suggestModel.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { isNonEmptyArray } from 'vs/base/common/arrays';
77
import { TimeoutTimer } from 'vs/base/common/async';
88
import { onUnexpectedError } from 'vs/base/common/errors';
99
import { Emitter, Event } from 'vs/base/common/event';
10-
import { IDisposable, dispose, DisposableStore, isDisposable } from 'vs/base/common/lifecycle';
10+
import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
1111
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
1212
import { CursorChangeReason, ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
1313
import { Position, IPosition } from 'vs/editor/common/core/position';
@@ -22,6 +22,7 @@ import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerServ
2222
import { WordDistance } from 'vs/editor/contrib/suggest/wordDistance';
2323
import { EditorOption } from 'vs/editor/common/config/editorOptions';
2424
import { isLowSurrogate, isHighSurrogate } from 'vs/base/common/strings';
25+
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
2526

2627
export interface ICancelEvent {
2728
readonly retrigger: boolean;
@@ -116,7 +117,8 @@ export class SuggestModel implements IDisposable {
116117

117118
constructor(
118119
private readonly _editor: ICodeEditor,
119-
private readonly _editorWorker: IEditorWorkerService
120+
private readonly _editorWorkerService: IEditorWorkerService,
121+
private readonly _clipboardService: IClipboardService
120122
) {
121123
this._currentSelection = this._editor.getSelection() || new Selection(1, 1, 1, 1);
122124

@@ -422,17 +424,17 @@ export class SuggestModel implements IDisposable {
422424
}
423425

424426
let itemKindFilter = SuggestModel._createItemKindFilter(this._editor);
425-
let wordDistance = WordDistance.create(this._editorWorker, this._editor);
427+
let wordDistance = WordDistance.create(this._editorWorkerService, this._editor);
426428

427-
let items = provideSuggestionItems(
429+
let completions = provideSuggestionItems(
428430
model,
429431
this._editor.getPosition(),
430432
new CompletionOptions(snippetSortOrder, itemKindFilter, onlyFrom),
431433
suggestCtx,
432434
this._requestToken.token
433435
);
434436

435-
Promise.all([items, wordDistance]).then(([items, wordDistance]) => {
437+
Promise.all([completions, wordDistance]).then(async ([completions, wordDistance]) => {
436438

437439
dispose(this._requestToken);
438440

@@ -444,7 +446,13 @@ export class SuggestModel implements IDisposable {
444446
return;
445447
}
446448

449+
let clipboardText: string | undefined;
450+
if (completions.needsClipboard) {
451+
clipboardText = await this._clipboardService.readText();
452+
}
453+
447454
const model = this._editor.getModel();
455+
let items = completions.items;
448456

449457
if (isNonEmptyArray(existingItems)) {
450458
const cmpFn = getSuggestionComparator(snippetSortOrder);
@@ -458,15 +466,12 @@ export class SuggestModel implements IDisposable {
458466
},
459467
wordDistance,
460468
this._editor.getOption(EditorOption.suggest),
461-
this._editor.getOption(EditorOption.snippetSuggestions)
469+
this._editor.getOption(EditorOption.snippetSuggestions),
470+
clipboardText
462471
);
463472

464473
// store containers so that they can be disposed later
465-
for (const item of items) {
466-
if (isDisposable(item.container)) {
467-
this._completionDisposables.add(item.container);
468-
}
469-
}
474+
this._completionDisposables.add(completions.dispoables);
470475

471476
this._onNewContext(ctx);
472477

src/vs/editor/contrib/suggest/test/completionModel.test.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ suite('CompletionModel', function () {
7979
], 1, {
8080
leadingLineContent: 'foo',
8181
characterCountDelta: 0
82-
}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue);
82+
}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined);
8383
});
8484

8585
test('filtering - cached', function () {
@@ -110,7 +110,7 @@ suite('CompletionModel', function () {
110110
], 1, {
111111
leadingLineContent: 'foo',
112112
characterCountDelta: 0
113-
}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue);
113+
}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined);
114114
assert.equal(incompleteModel.incomplete.size, 1);
115115
});
116116

@@ -119,7 +119,7 @@ suite('CompletionModel', function () {
119119
const completeItem = createSuggestItem('foobar', 1, undefined, false, { lineNumber: 1, column: 2 });
120120
const incompleteItem = createSuggestItem('foofoo', 1, undefined, true, { lineNumber: 1, column: 2 });
121121

122-
const model = new CompletionModel([completeItem, incompleteItem], 2, { leadingLineContent: 'f', characterCountDelta: 0 }, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue);
122+
const model = new CompletionModel([completeItem, incompleteItem], 2, { leadingLineContent: 'f', characterCountDelta: 0 }, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined);
123123
assert.equal(model.incomplete.size, 1);
124124
assert.equal(model.items.length, 2);
125125

@@ -148,7 +148,7 @@ suite('CompletionModel', function () {
148148
completeItem4,
149149
completeItem5,
150150
incompleteItem1,
151-
], 2, { leadingLineContent: 'f', characterCountDelta: 0 }, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue
151+
], 2, { leadingLineContent: 'f', characterCountDelta: 0 }, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined
152152
);
153153
assert.equal(model.incomplete.size, 1);
154154
assert.equal(model.items.length, 6);
@@ -172,7 +172,7 @@ suite('CompletionModel', function () {
172172
], 1, {
173173
leadingLineContent: ' <',
174174
characterCountDelta: 0
175-
}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue);
175+
}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined);
176176

177177
assert.equal(model.items.length, 4);
178178

@@ -192,7 +192,7 @@ suite('CompletionModel', function () {
192192
], 1, {
193193
leadingLineContent: 's',
194194
characterCountDelta: 0
195-
}, WordDistance.None, defaultOptions, 'top');
195+
}, WordDistance.None, defaultOptions, 'top', undefined);
196196

197197
assert.equal(model.items.length, 2);
198198
const [a, b] = model.items;
@@ -211,7 +211,7 @@ suite('CompletionModel', function () {
211211
], 1, {
212212
leadingLineContent: 's',
213213
characterCountDelta: 0
214-
}, WordDistance.None, defaultOptions, 'bottom');
214+
}, WordDistance.None, defaultOptions, 'bottom', undefined);
215215

216216
assert.equal(model.items.length, 2);
217217
const [a, b] = model.items;
@@ -229,7 +229,7 @@ suite('CompletionModel', function () {
229229
], 1, {
230230
leadingLineContent: 's',
231231
characterCountDelta: 0
232-
}, WordDistance.None, defaultOptions, 'inline');
232+
}, WordDistance.None, defaultOptions, 'inline', undefined);
233233

234234
assert.equal(model.items.length, 2);
235235
const [a, b] = model.items;
@@ -246,7 +246,7 @@ suite('CompletionModel', function () {
246246
model = new CompletionModel([item1, item2], 1, {
247247
leadingLineContent: 'M',
248248
characterCountDelta: 0
249-
}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue);
249+
}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined);
250250

251251
assert.equal(model.items.length, 2);
252252

@@ -266,7 +266,7 @@ suite('CompletionModel', function () {
266266
model = new CompletionModel(items, 3, {
267267
leadingLineContent: ' ',
268268
characterCountDelta: 0
269-
}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue);
269+
}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined);
270270

271271
assert.equal(model.items.length, 2);
272272

@@ -285,7 +285,7 @@ suite('CompletionModel', function () {
285285
], 1, {
286286
leadingLineContent: '',
287287
characterCountDelta: 0
288-
}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue);
288+
}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined);
289289

290290
assert.equal(model.items.length, 5);
291291

@@ -312,7 +312,7 @@ suite('CompletionModel', function () {
312312
], 1, {
313313
leadingLineContent: '',
314314
characterCountDelta: 0
315-
}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue);
315+
}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined);
316316

317317
// query gets longer, narrow down the narrow-down'ed-set from before
318318
model.lineContext = { leadingLineContent: 'rlut', characterCountDelta: 4 };
@@ -334,7 +334,7 @@ suite('CompletionModel', function () {
334334
], 1, {
335335
leadingLineContent: '',
336336
characterCountDelta: 0
337-
}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue);
337+
}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined);
338338

339339
model.lineContext = { leadingLineContent: 'form', characterCountDelta: 4 };
340340
assert.equal(model.items.length, 5);

0 commit comments

Comments
 (0)