Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { IVSCodeExtensionContext } from '../../../platform/extContext/common/ext
import { JointCompletionsProviderStrategy, JointCompletionsProviderTriggerChangeStrategy } from '../../../platform/inlineEdits/common/dataTypes/jointCompletionsProviderOptions';
import { InlineEditRequestLogContext } from '../../../platform/inlineEdits/common/inlineEditLogContext';
import { ObservableGit } from '../../../platform/inlineEdits/common/observableGit';
import { shortenOpportunityId } from '../../../platform/inlineEdits/common/utils/utils';
import { checkIfCursorAtEndOfLine, shortenOpportunityId } from '../../../platform/inlineEdits/common/utils/utils';
import { NesHistoryContextProvider } from '../../../platform/inlineEdits/common/workspaceEditTracker/nesHistoryContextProvider';
import { ILogService } from '../../../platform/log/common/logService';
import * as errors from '../../../util/common/errors';
Expand Down Expand Up @@ -239,6 +239,7 @@ type LastNesSuggestion = {
docUri: vscode.Uri;
docVersionId: number;
docWithNesEditApplied: StringText;
completionItem: NesCompletionItem;
};

class JointCompletionsProvider extends Disposable implements vscode.InlineCompletionItemProvider {
Expand Down Expand Up @@ -317,11 +318,57 @@ class JointCompletionsProvider extends Disposable implements vscode.InlineComple
switch (strategy) {
case JointCompletionsProviderStrategy.Regular:
return this.provideInlineCompletionItemsRegular(document, position, context, token, tracer);
case JointCompletionsProviderStrategy.CursorEndOfLine:
return this.provideInlineCompletionItemsCursorEndOfLine(document, position, context, token, tracer);
default:
assertNever(strategy);
}
}

private async provideInlineCompletionItemsCursorEndOfLine(document: vscode.TextDocument, position: vscode.Position, context: vscode.InlineCompletionContext, token: vscode.CancellationToken, tracer: ITracer): Promise<SingularCompletionList | undefined> {
const sw = new StopWatch();

this._requestsInFlight.add(token);
const disp = token.onCancellationRequested(() => {
this._requestsInFlight.delete(token);
});
try {
if (this._completionsProvider === undefined && this._inlineEditProvider === undefined) {
tracer.returns('neither completions nor NES provider available');
return undefined;

} else if (this._completionsProvider === undefined && this._inlineEditProvider !== undefined) {
tracer.trace('only NES provider is available, invoking it');
const r = await this._invokeNESProvider(tracer, document, position, false, context, token, sw);
return r ? toInlineEditsList(r) : undefined;

} else if (this._completionsProvider !== undefined && this._inlineEditProvider === undefined) {
tracer.trace('only completions provider is available, invoking it');
const r = await this._invokeCompletionsProvider(tracer, document, position, context, token, sw);
return r ? toCompletionsList(r) : undefined;
} else {

const cursorLine = document.lineAt(position.line).text;
const isCursorAtEndOfLine = checkIfCursorAtEndOfLine(cursorLine, position.character);

if (isCursorAtEndOfLine) {
tracer.trace('cursor is at end of line, invoking ghost-text provider only');
const r = await this._invokeCompletionsProvider(tracer, document, position, context, token, sw);
return r ? toCompletionsList(r) : undefined;
}

const r = await this._invokeNESProvider(tracer, document, position, false, context, token, sw);
return r ? toInlineEditsList(r) : undefined;
}
} finally {
if (!token.isCancellationRequested) {
this._tracer.trace('request in flight: false -- due to provider finishing');
this._requestsInFlight.delete(token);
}
disp.dispose();
}
}

private lastNesSuggestion: null | LastNesSuggestion = null;
private provideInlineCompletionItemsInvocationCount = 0;

Expand Down Expand Up @@ -358,7 +405,7 @@ class JointCompletionsProvider extends Disposable implements vscode.InlineComple
return list;
}

const firstItem = list.items[0];
const firstItem = (list.items as NesCompletionItem[])[0];
if (!firstItem.range || typeof firstItem.insertText !== 'string') {
return list;
}
Expand All @@ -373,6 +420,7 @@ class JointCompletionsProvider extends Disposable implements vscode.InlineComple
docUri: document.uri,
docVersionId,
docWithNesEditApplied: new StringText(applied),
completionItem: firstItem,
};

return list;
Expand Down Expand Up @@ -418,21 +466,21 @@ class JointCompletionsProvider extends Disposable implements vscode.InlineComple

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

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

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

const enforceCacheDelay = (lastNesSuggestion.docVersionId !== document.version);
const nesP = this._invokeNESProvider(tracer, document, position, enforceCacheDelay, context, tokens, sw);
const nesP = this._invokeNESProvider(tracer, document, position, enforceCacheDelay, context, tokens.nesCts.token, sw);
if (!nesP) {
tracer.trace(`no NES provider`);
const completionsP = this._invokeCompletionsProvider(tracer, document, position, context, tokens, sw);
const completionsP = this._invokeCompletionsProvider(tracer, document, position, context, tokens.completionsCts.token, sw);
return this._returnCompletionsOrOtherwiseNES(completionsP, nesP, docSnapshot, sw, tracer, tokens);
}

Expand Down Expand Up @@ -467,7 +515,7 @@ class JointCompletionsProvider extends Disposable implements vscode.InlineComple
}

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

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

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

private _invokeCompletionsProvider(tracer: ITracer, document: vscode.TextDocument, position: vscode.Position, context: vscode.InlineCompletionContext, tokens: { coreToken: CancellationToken; completionsCts: CancellationTokenSource; nesCts: CancellationTokenSource }, sw: StopWatch) {
private _invokeCompletionsProvider(tracer: ITracer, document: vscode.TextDocument, position: vscode.Position, context: vscode.InlineCompletionContext, ct: CancellationToken, sw: StopWatch) {
let completionsP: Promise<vscode.InlineCompletionList | undefined> | undefined;
if (this._completionsProvider) {
this._completionsRequestsInFlight.add(tokens.completionsCts.token);
const disp = tokens.completionsCts.token.onCancellationRequested(() => this._completionsRequestsInFlight.delete(tokens.completionsCts.token));
this._completionsRequestsInFlight.add(ct);
const disp = ct.onCancellationRequested(() => this._completionsRequestsInFlight.delete(ct));
const cleanup = () => {
this._completionsRequestsInFlight.delete(tokens.completionsCts.token);
this._completionsRequestsInFlight.delete(ct);
disp.dispose();
};
try { // in case the provider throws synchronously
tracer.trace(`- requesting completions provideInlineCompletionItems`);
completionsP = this._completionsProvider.provideInlineCompletionItems(document, position, context, tokens.completionsCts.token);
completionsP = this._completionsProvider.provideInlineCompletionItems(document, position, context, ct);
completionsP.then((completionsR) => {
tracer.trace(`got completions response in ${sw.elapsed()}ms -- ${completionsR === undefined ? 'undefined' : `with ${completionsR.items.length} items`}`);
}).catch((e) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

export enum JointCompletionsProviderStrategy {
Regular = 'regular',
CursorEndOfLine = 'cursorEndOfLine',
}

export enum JointCompletionsProviderTriggerChangeStrategy {
Expand Down
6 changes: 6 additions & 0 deletions src/platform/inlineEdits/common/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,9 @@ export function shortenOpportunityId(opportunityId: string): string {
// example: `icr-1234abcd5678efgh` -> `1234`, where we strip the `icr-` prefix and take the first 4 characters
return opportunityId.substring(4, 8);
}

export function checkIfCursorAtEndOfLine(lineWithCursor: string, cursorOffsetZeroBased: number): boolean {
// check if there's any non-whitespace character after the cursor in the line
const isCursorAtEndOfLine = lineWithCursor.substring(cursorOffsetZeroBased).match(/^\s*$/) !== null;
return isCursorAtEndOfLine;
}