Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autocomplete #3371

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
24 changes: 23 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,27 @@
"gui/dist/**": true
// "sync/**": true
},
"eslint.workingDirectories": ["./core"]
"eslint.workingDirectories": ["./core"],
"cSpell.words": [
"aiohttp",
"cerebras",
"codellama",
"debouncer",
"deepseek",
"huggingface",
"ILLM",
"lancedb",
"llms",
"Ollama",
"posthog",
"Prefilter",
"prefiltering",
"qwen",
"rerank",
"Reranker",
"starcoder",
"vectordb",
"wasms",
"wizardcoder"
]
}
89 changes: 50 additions & 39 deletions core/autocomplete/CompletionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@
import { TRIAL_FIM_MODEL } from "../config/onboarding.js";
import { IDE, ILLM } from "../index.js";
import OpenAI from "../llm/llms/OpenAI.js";
import { DEFAULT_AUTOCOMPLETE_OPTS } from "../util/parameters.js";
import { PosthogFeatureFlag, Telemetry } from "../util/posthog.js";

import { shouldCompleteMultiline } from "./classification/shouldCompleteMultiline.js";
import { ContextRetrievalService } from "./context/ContextRetrievalService.js";

Check warning on line 8 in core/autocomplete/CompletionProvider.ts

View workflow job for this annotation

GitHub Actions / core-checks

There should be no empty line within import group
// @prettier-ignore

import { BracketMatchingService } from "./filtering/BracketMatchingService.js";
Expand All @@ -14,16 +13,17 @@
import { postprocessCompletion } from "./postprocessing/index.js";
import { shouldPrefilter } from "./prefiltering/index.js";
import { getAllSnippets } from "./snippets/index.js";
import {
DEFAULT_AUTOCOMPLETE_OPTS,
TabAutocompleteOptions,
} from "./TabAutocompleteOptions.js";
import { renderPrompt } from "./templating/index.js";
import { GetLspDefinitionsFunction } from "./types.js";
import { AutocompleteContext } from "./util/AutocompleteContext.js";
import { AutocompleteDebouncer } from "./util/AutocompleteDebouncer.js";
import { AutocompleteLoggingService } from "./util/AutocompleteLoggingService.js";
import AutocompleteLruCache from "./util/AutocompleteLruCache.js";
import { HelperVars } from "./util/HelperVars.js";
import { AutocompleteInput, AutocompleteOutcome } from "./util/types.js";

const autocompleteCache = AutocompleteLruCache.get();

// Errors that can be expected on occasion even during normal functioning should not be shown.
// Not worth disrupting the user to tell them that a single autocomplete request didn't go through
const ERRORS_TO_IGNORE = [
Expand All @@ -44,16 +44,16 @@
constructor(
private readonly configHandler: ConfigHandler,
private readonly ide: IDE,
private readonly _injectedGetLlm: () => Promise<ILLM | undefined>,
private readonly _onError: (e: any) => void,
private readonly getDefinitionsFromLsp: GetLspDefinitionsFunction,
private readonly injectedGetLlm: () => Promise<ILLM | undefined>,
private readonly onErrorCallback: (e: any) => void,
private readonly writeLog: (text: string) => void = () => {},
) {
this.completionStreamer = new CompletionStreamer(this.onError.bind(this));
this.contextRetrievalService = new ContextRetrievalService(this.ide);
}

private async _prepareLlm(): Promise<ILLM | undefined> {
const llm = await this._injectedGetLlm();
private async prepareLlm(): Promise<ILLM | undefined> {
const llm = await this.injectedGetLlm();

if (!llm) {
return undefined;
Expand Down Expand Up @@ -99,7 +99,7 @@
console.warn("Error generating autocompletion: ", e);
if (!this.errorsShown.has(e.message)) {
this.errorsShown.add(e.message);
this._onError(e);
this.onErrorCallback(e);
}
}

Expand All @@ -122,11 +122,19 @@
this.loggingService.markDisplayed(completionId, outcome);
}

private async _getAutocompleteOptions() {
private async getAutocompleteOptions(): Promise<TabAutocompleteOptions> {
const config = await this.configHandler.loadConfig();
const options = {
const options: TabAutocompleteOptions = {
...DEFAULT_AUTOCOMPLETE_OPTS,
...config.tabAutocompleteOptions,
defaultLanguageOptions: {
...DEFAULT_AUTOCOMPLETE_OPTS.defaultLanguageOptions,
...config.tabAutocompleteOptions?.defaultLanguageOptions,
},
languageOptions: {
...DEFAULT_AUTOCOMPLETE_OPTS.languageOptions,
...config.tabAutocompleteOptions?.languageOptions,
},
};
return options;
}
Expand All @@ -137,26 +145,27 @@
): Promise<AutocompleteOutcome | undefined> {
try {
const startTime = Date.now();
const options = await this._getAutocompleteOptions();
const options = await this.getAutocompleteOptions();

// Debounce
if (await this.debouncer.delayAndShouldDebounce(options.debounceDelay)) {
return undefined;
}

const llm = await this._prepareLlm();
const llm = await this.prepareLlm();
if (!llm) {
return undefined;
}

const helper = await HelperVars.create(
const ctx = await AutocompleteContext.create(
input,
options,
llm.model,
this.ide,
this.writeLog,
);

if (await shouldPrefilter(helper, this.ide)) {
if (await shouldPrefilter(ctx, this.ide)) {
return undefined;
}

Expand All @@ -168,37 +177,34 @@
token = controller.signal;
}

const [snippetPayload, workspaceDirs] = await Promise.all([
getAllSnippets({
helper,
ide: this.ide,
getDefinitionsFromLsp: this.getDefinitionsFromLsp,
contextRetrievalService: this.contextRetrievalService,
}),
const [snippets, workspaceDirs] = await Promise.all([
getAllSnippets(ctx, this.ide, this.contextRetrievalService),
this.ide.getWorkspaceDirs(),
]);

const { prompt, prefix, suffix, completionOptions } = renderPrompt({
snippetPayload,
snippets,
workspaceDirs,
helper,
helper: ctx,
});

// Completion
let completion: string | undefined = "";

const cache = await autocompleteCache;
const cachedCompletion = helper.options.useCache
? await cache.get(helper.prunedPrefix)
const cache = await this.autocompleteCache;
const cachedCompletion = ctx.options.useCache
? await cache.get(ctx.prunedPrefix)
: undefined;
let cacheHit = false;
if (cachedCompletion) {
// Cache
cacheHit = true;
completion = cachedCompletion;
if (ctx.options.logCompletionCache)
ctx.writeLog("Using cached completion");

Check warning on line 204 in core/autocomplete/CompletionProvider.ts

View workflow job for this annotation

GitHub Actions / core-checks

Expected { after 'if' condition
} else {
const multiline =
!helper.options.transform || shouldCompleteMultiline(helper);
!ctx.options.transform || shouldCompleteMultiline(ctx);

const completionStream =
this.completionStreamer.streamCompletionWithFilters(
Expand All @@ -209,7 +215,7 @@
prompt,
multiline,
completionOptions,
helper,
ctx,
);

for await (const update of completionStream) {
Expand All @@ -221,18 +227,23 @@
return undefined;
}

const processedCompletion = helper.options.transform
const processedCompletion = ctx.options.transform
? postprocessCompletion({
completion,
prefix: helper.prunedPrefix,
suffix: helper.prunedSuffix,
prefix: ctx.prunedPrefix,
suffix: ctx.prunedSuffix,
llm,
ctx,
})
: completion;

completion = processedCompletion;
}

if (ctx.options.logCompletionOutcome) {
ctx.writeLog("Completion Outcome: \n---\n" + completion + "\n---");
}

if (!completion) {
return undefined;
}
Expand All @@ -247,19 +258,19 @@
modelName: llm.model,
completionOptions,
cacheHit,
filepath: helper.filepath,
completionId: helper.input.completionId,
gitRepo: await this.ide.getRepoName(helper.filepath),
filepath: ctx.filepath,
completionId: ctx.input.completionId,
gitRepo: await this.ide.getRepoName(ctx.filepath),
uniqueId: await this.ide.getUniqueId(),
timestamp: Date.now(),
...helper.options,
...ctx.options,
};

//////////

// Save to cache
if (!outcome.cacheHit && helper.options.useCache) {
if (!outcome.cacheHit && ctx.options.useCache) {
(await this.autocompleteCache).put(outcome.prefix, outcome.completion);

Check warning on line 273 in core/autocomplete/CompletionProvider.ts

View workflow job for this annotation

GitHub Actions / core-checks

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
}

// When using the JetBrains extension, Mark as displayed
Expand Down
101 changes: 101 additions & 0 deletions core/autocomplete/TabAutocompleteOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { LanguageId } from "../util/languageId";

export interface TabAutocompleteLanguageOptions {
enableRootPathSnippets: boolean;
enableImportSnippets: boolean;
enableDiffSnippets: boolean;
enableClipboardSnippets: boolean;
outlineNodeReplacements: { [key: string]: string };
filterMaxRepeatingLines: number;
}

export interface TabAutocompleteOptions {
disable: boolean;
maxPromptTokens: number;
debounceDelay: number;
maxSuffixPercentage: number;
prefixPercentage: number;
transform?: boolean;
template?: string;
multilineCompletions: "always" | "never" | "auto";
slidingWindowPrefixPercentage: number;
slidingWindowSize: number;
useCache: boolean;
onlyMyCode: boolean;
useRecentlyEdited: boolean;
disableInFiles?: string[];
logDisableInFiles: boolean;
useImports?: boolean;
showWhateverWeHaveAtXMs: number;

logCompletionCache: boolean;

logSnippetLimiting: boolean;
logSnippetTimeouts: boolean;
logOutlineCreation: boolean;

logCompletionStop: boolean;
logDroppedLinesFilter: boolean;

logPostprocessing: any;
logCompletionOutcome: any;

logRootPathSnippets: boolean;
logImportSnippets: boolean;
logDiffSnippets: boolean;
logClipboardSnippets: boolean;

defaultLanguageOptions: TabAutocompleteLanguageOptions;
languageOptions: {
[languageId in LanguageId]?: TabAutocompleteLanguageOptions;
};
}

export const DEFAULT_AUTOCOMPLETE_OPTS: TabAutocompleteOptions = {
disable: false,
maxPromptTokens: 1024,
prefixPercentage: 0.3,
maxSuffixPercentage: 0.2,
debounceDelay: 350,
multilineCompletions: "auto",
// @deprecated TO BE REMOVED
slidingWindowPrefixPercentage: 0.75,
// @deprecated TO BE REMOVED
slidingWindowSize: 500,
useCache: true,
onlyMyCode: true,
useRecentlyEdited: true,
disableInFiles: undefined,
logDisableInFiles: false,
useImports: true,
transform: true,
showWhateverWeHaveAtXMs: 300,

logCompletionCache: false,

logSnippetLimiting: false,
logSnippetTimeouts: false,
logOutlineCreation: false,

logRootPathSnippets: false,
logImportSnippets: false,
logDiffSnippets: false,
logClipboardSnippets: false,
logCompletionStop: false,

logDroppedLinesFilter: false,
logPostprocessing: false,
logCompletionOutcome: false,

defaultLanguageOptions: {
enableRootPathSnippets: true,
enableImportSnippets: true,
enableDiffSnippets: true,
enableClipboardSnippets: true,
outlineNodeReplacements: {
statement_block: "{...}",
},
filterMaxRepeatingLines: 3,
},
languageOptions: {},
};
4 changes: 2 additions & 2 deletions core/autocomplete/classification/shouldCompleteMultiline.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AutocompleteLanguageInfo } from "../constants/AutocompleteLanguageInfo";
import { HelperVars } from "../util/HelperVars";
import { AutocompleteContext } from "../util/AutocompleteContext";

function isMidlineCompletion(prefix: string, suffix: string): boolean {
return !suffix.startsWith("\n");
Expand All @@ -13,7 +13,7 @@ function shouldCompleteMultilineBasedOnLanguage(
return language.useMultiline?.({ prefix, suffix }) ?? true;
}

export function shouldCompleteMultiline(helper: HelperVars) {
export function shouldCompleteMultiline(helper: AutocompleteContext) {
switch (helper.options.multilineCompletions) {
case "always":
return true;
Expand Down
Loading
Loading