Skip to content

Commit 89715b5

Browse files
authored
Merge pull request #2621 from microsoft/ulugbekna/candidate-joint-provider
nes: joint: New Joint provider strategy to invoke a provider depending if the cursor is at the end of line
2 parents ad46d98 + 877daea commit 89715b5

File tree

3 files changed

+71
-16
lines changed

3 files changed

+71
-16
lines changed

src/extension/inlineEdits/vscode-node/jointInlineCompletionProvider.ts

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { IVSCodeExtensionContext } from '../../../platform/extContext/common/ext
1313
import { JointCompletionsProviderStrategy, JointCompletionsProviderTriggerChangeStrategy } from '../../../platform/inlineEdits/common/dataTypes/jointCompletionsProviderOptions';
1414
import { InlineEditRequestLogContext } from '../../../platform/inlineEdits/common/inlineEditLogContext';
1515
import { ObservableGit } from '../../../platform/inlineEdits/common/observableGit';
16-
import { shortenOpportunityId } from '../../../platform/inlineEdits/common/utils/utils';
16+
import { checkIfCursorAtEndOfLine, shortenOpportunityId } from '../../../platform/inlineEdits/common/utils/utils';
1717
import { NesHistoryContextProvider } from '../../../platform/inlineEdits/common/workspaceEditTracker/nesHistoryContextProvider';
1818
import { ILogService } from '../../../platform/log/common/logService';
1919
import * as errors from '../../../util/common/errors';
@@ -239,6 +239,7 @@ type LastNesSuggestion = {
239239
docUri: vscode.Uri;
240240
docVersionId: number;
241241
docWithNesEditApplied: StringText;
242+
completionItem: NesCompletionItem;
242243
};
243244

244245
class JointCompletionsProvider extends Disposable implements vscode.InlineCompletionItemProvider {
@@ -317,11 +318,57 @@ class JointCompletionsProvider extends Disposable implements vscode.InlineComple
317318
switch (strategy) {
318319
case JointCompletionsProviderStrategy.Regular:
319320
return this.provideInlineCompletionItemsRegular(document, position, context, token, tracer);
321+
case JointCompletionsProviderStrategy.CursorEndOfLine:
322+
return this.provideInlineCompletionItemsCursorEndOfLine(document, position, context, token, tracer);
320323
default:
321324
assertNever(strategy);
322325
}
323326
}
324327

328+
private async provideInlineCompletionItemsCursorEndOfLine(document: vscode.TextDocument, position: vscode.Position, context: vscode.InlineCompletionContext, token: vscode.CancellationToken, tracer: ITracer): Promise<SingularCompletionList | undefined> {
329+
const sw = new StopWatch();
330+
331+
this._requestsInFlight.add(token);
332+
const disp = token.onCancellationRequested(() => {
333+
this._requestsInFlight.delete(token);
334+
});
335+
try {
336+
if (this._completionsProvider === undefined && this._inlineEditProvider === undefined) {
337+
tracer.returns('neither completions nor NES provider available');
338+
return undefined;
339+
340+
} else if (this._completionsProvider === undefined && this._inlineEditProvider !== undefined) {
341+
tracer.trace('only NES provider is available, invoking it');
342+
const r = await this._invokeNESProvider(tracer, document, position, false, context, token, sw);
343+
return r ? toInlineEditsList(r) : undefined;
344+
345+
} else if (this._completionsProvider !== undefined && this._inlineEditProvider === undefined) {
346+
tracer.trace('only completions provider is available, invoking it');
347+
const r = await this._invokeCompletionsProvider(tracer, document, position, context, token, sw);
348+
return r ? toCompletionsList(r) : undefined;
349+
} else {
350+
351+
const cursorLine = document.lineAt(position.line).text;
352+
const isCursorAtEndOfLine = checkIfCursorAtEndOfLine(cursorLine, position.character);
353+
354+
if (isCursorAtEndOfLine) {
355+
tracer.trace('cursor is at end of line, invoking ghost-text provider only');
356+
const r = await this._invokeCompletionsProvider(tracer, document, position, context, token, sw);
357+
return r ? toCompletionsList(r) : undefined;
358+
}
359+
360+
const r = await this._invokeNESProvider(tracer, document, position, false, context, token, sw);
361+
return r ? toInlineEditsList(r) : undefined;
362+
}
363+
} finally {
364+
if (!token.isCancellationRequested) {
365+
this._tracer.trace('request in flight: false -- due to provider finishing');
366+
this._requestsInFlight.delete(token);
367+
}
368+
disp.dispose();
369+
}
370+
}
371+
325372
private lastNesSuggestion: null | LastNesSuggestion = null;
326373
private provideInlineCompletionItemsInvocationCount = 0;
327374

@@ -358,7 +405,7 @@ class JointCompletionsProvider extends Disposable implements vscode.InlineComple
358405
return list;
359406
}
360407

361-
const firstItem = list.items[0];
408+
const firstItem = (list.items as NesCompletionItem[])[0];
362409
if (!firstItem.range || typeof firstItem.insertText !== 'string') {
363410
return list;
364411
}
@@ -373,6 +420,7 @@ class JointCompletionsProvider extends Disposable implements vscode.InlineComple
373420
docUri: document.uri,
374421
docVersionId,
375422
docWithNesEditApplied: new StringText(applied),
423+
completionItem: firstItem,
376424
};
377425

378426
return list;
@@ -418,21 +466,21 @@ class JointCompletionsProvider extends Disposable implements vscode.InlineComple
418466

419467
tracer.trace('requesting completions and/or NES');
420468

421-
if (!lastNesSuggestion) {
469+
if (!lastNesSuggestion || !lastNesSuggestion.completionItem.wasShown) {
422470
// prefer completions unless there are none
423-
tracer.trace(`no last NES suggestion to consider`);
424-
const completionsP = this._invokeCompletionsProvider(tracer, document, position, context, tokens, sw);
425-
const nesP = this._invokeNESProvider(tracer, document, position, true, context, tokens, sw);
471+
tracer.trace(`defaulting to yielding to completions; last NES suggestion is ${lastNesSuggestion ? 'not shown' : 'not available'}`);
472+
const completionsP = this._invokeCompletionsProvider(tracer, document, position, context, tokens.completionsCts.token, sw);
473+
const nesP = this._invokeNESProvider(tracer, document, position, true, context, tokens.nesCts.token, sw);
426474
return this._returnCompletionsOrOtherwiseNES(completionsP, nesP, docSnapshot, sw, tracer, tokens);
427475
}
428476

429477
tracer.trace(`last NES suggestion is for the current document, checking if it agrees with the current suggestion`);
430478

431479
const enforceCacheDelay = (lastNesSuggestion.docVersionId !== document.version);
432-
const nesP = this._invokeNESProvider(tracer, document, position, enforceCacheDelay, context, tokens, sw);
480+
const nesP = this._invokeNESProvider(tracer, document, position, enforceCacheDelay, context, tokens.nesCts.token, sw);
433481
if (!nesP) {
434482
tracer.trace(`no NES provider`);
435-
const completionsP = this._invokeCompletionsProvider(tracer, document, position, context, tokens, sw);
483+
const completionsP = this._invokeCompletionsProvider(tracer, document, position, context, tokens.completionsCts.token, sw);
436484
return this._returnCompletionsOrOtherwiseNES(completionsP, nesP, docSnapshot, sw, tracer, tokens);
437485
}
438486

@@ -467,7 +515,7 @@ class JointCompletionsProvider extends Disposable implements vscode.InlineComple
467515
}
468516

469517
tracer.trace(`the NES provider did not return in ${NES_CACHE_WAIT_MS}ms so we are triggering the completions provider too`);
470-
const completionsP = this._invokeCompletionsProvider(tracer, document, position, context, tokens, sw);
518+
const completionsP = this._invokeCompletionsProvider(tracer, document, position, context, tokens.completionsCts.token, sw);
471519

472520
const suggestionsList = await raceCancellation(
473521
Promise.race(coalesce([
@@ -497,12 +545,12 @@ class JointCompletionsProvider extends Disposable implements vscode.InlineComple
497545
return this._returnCompletionsOrOtherwiseNES(completionsP, nesP, docSnapshot, sw, tracer, tokens);
498546
}
499547

500-
private _invokeNESProvider(tracer: ITracer, document: vscode.TextDocument, position: vscode.Position, enforceCacheDelay: boolean, context: vscode.InlineCompletionContext, tokens: { coreToken: CancellationToken; completionsCts: CancellationTokenSource; nesCts: CancellationTokenSource }, sw: StopWatch) {
548+
private _invokeNESProvider(tracer: ITracer, document: vscode.TextDocument, position: vscode.Position, enforceCacheDelay: boolean, context: vscode.InlineCompletionContext, ct: CancellationToken, sw: StopWatch) {
501549
const nesContext: NESInlineCompletionContext = { ...context, enforceCacheDelay };
502550
let nesP: Promise<NesCompletionList | undefined> | undefined;
503551
if (this._inlineEditProvider) {
504552
tracer.trace(`- requesting NES provideInlineCompletionItems`);
505-
nesP = this._inlineEditProvider.provideInlineCompletionItems(document, position, nesContext, tokens.nesCts.token);
553+
nesP = this._inlineEditProvider.provideInlineCompletionItems(document, position, nesContext, ct);
506554
nesP.then((nesR) => {
507555
tracer.trace(`got NES response in ${sw.elapsed()}ms -- ${nesR === undefined ? 'undefined' : `with ${nesR.items.length} items`}`);
508556
}).catch((e) => {
@@ -515,18 +563,18 @@ class JointCompletionsProvider extends Disposable implements vscode.InlineComple
515563
return nesP;
516564
}
517565

518-
private _invokeCompletionsProvider(tracer: ITracer, document: vscode.TextDocument, position: vscode.Position, context: vscode.InlineCompletionContext, tokens: { coreToken: CancellationToken; completionsCts: CancellationTokenSource; nesCts: CancellationTokenSource }, sw: StopWatch) {
566+
private _invokeCompletionsProvider(tracer: ITracer, document: vscode.TextDocument, position: vscode.Position, context: vscode.InlineCompletionContext, ct: CancellationToken, sw: StopWatch) {
519567
let completionsP: Promise<vscode.InlineCompletionList | undefined> | undefined;
520568
if (this._completionsProvider) {
521-
this._completionsRequestsInFlight.add(tokens.completionsCts.token);
522-
const disp = tokens.completionsCts.token.onCancellationRequested(() => this._completionsRequestsInFlight.delete(tokens.completionsCts.token));
569+
this._completionsRequestsInFlight.add(ct);
570+
const disp = ct.onCancellationRequested(() => this._completionsRequestsInFlight.delete(ct));
523571
const cleanup = () => {
524-
this._completionsRequestsInFlight.delete(tokens.completionsCts.token);
572+
this._completionsRequestsInFlight.delete(ct);
525573
disp.dispose();
526574
};
527575
try { // in case the provider throws synchronously
528576
tracer.trace(`- requesting completions provideInlineCompletionItems`);
529-
completionsP = this._completionsProvider.provideInlineCompletionItems(document, position, context, tokens.completionsCts.token);
577+
completionsP = this._completionsProvider.provideInlineCompletionItems(document, position, context, ct);
530578
completionsP.then((completionsR) => {
531579
tracer.trace(`got completions response in ${sw.elapsed()}ms -- ${completionsR === undefined ? 'undefined' : `with ${completionsR.items.length} items`}`);
532580
}).catch((e) => {

src/platform/inlineEdits/common/dataTypes/jointCompletionsProviderOptions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
export enum JointCompletionsProviderStrategy {
77
Regular = 'regular',
8+
CursorEndOfLine = 'cursorEndOfLine',
89
}
910

1011
export enum JointCompletionsProviderTriggerChangeStrategy {

src/platform/inlineEdits/common/utils/utils.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,9 @@ export function shortenOpportunityId(opportunityId: string): string {
5959
// example: `icr-1234abcd5678efgh` -> `1234`, where we strip the `icr-` prefix and take the first 4 characters
6060
return opportunityId.substring(4, 8);
6161
}
62+
63+
export function checkIfCursorAtEndOfLine(lineWithCursor: string, cursorOffsetZeroBased: number): boolean {
64+
// check if there's any non-whitespace character after the cursor in the line
65+
const isCursorAtEndOfLine = lineWithCursor.substring(cursorOffsetZeroBased).match(/^\s*$/) !== null;
66+
return isCursorAtEndOfLine;
67+
}

0 commit comments

Comments
 (0)