diff --git a/core/autocomplete/templating/AutocompleteTemplate.ts b/core/autocomplete/templating/AutocompleteTemplate.ts index 8f7bbd66d4..7f5366df07 100644 --- a/core/autocomplete/templating/AutocompleteTemplate.ts +++ b/core/autocomplete/templating/AutocompleteTemplate.ts @@ -51,6 +51,8 @@ const stableCodeFimTemplate: AutocompleteTemplate = { }; // https://github.com/QwenLM/Qwen2.5-Coder?tab=readme-ov-file#3-file-level-code-completion-fill-in-the-middle +// This issue asks about the use of <|repo_name|> and <|file_sep|> together with <|fim_prefix|>, <|fim_suffix|> and <|fim_middle|> +// https://github.com/QwenLM/Qwen2.5-Coder/issues/343 const qwenCoderFimTemplate: AutocompleteTemplate = { template: "<|fim_prefix|>{{{prefix}}}<|fim_suffix|>{{{suffix}}}<|fim_middle|>", @@ -85,6 +87,11 @@ const codestralMultifileFimTemplate: AutocompleteTemplate = { snippets, workspaceUris, ): [string, string] => { + + function getFileName(snippet: { uri: string, uniquePath: string }) { + return snippet.uri.startsWith("file://") ? snippet.uniquePath : snippet.uri + } + if (snippets.length === 0) { if (suffix.trim().length === 0 && prefix.trim().length === 0) { return [ @@ -111,14 +118,12 @@ const codestralMultifileFimTemplate: AutocompleteTemplate = { return snippet.content; } - return `+++++ ${relativePaths[i].uri} \n${snippet.content}`; + return `+++++ ${getFileName(relativePaths[i])} \n${snippet.content}`; }) .join("\n\n"); return [ - `${otherFiles}\n\n+++++ ${ - relativePaths[relativePaths.length - 1].uri - }\n${prefix}`, + `${otherFiles}\n\n+++++ ${getFileName(relativePaths[relativePaths.length - 1])}\n${prefix}`, suffix, ]; }, @@ -126,7 +131,7 @@ const codestralMultifileFimTemplate: AutocompleteTemplate = { return `[SUFFIX]${suffix}[PREFIX]${prefix}`; }, completionOptions: { - stop: ["[PREFIX]", "[SUFFIX]"], + stop: ["[PREFIX]", "[SUFFIX]", "\n+++++ "], }, }; @@ -218,9 +223,8 @@ const codegeexFimTemplate: AutocompleteTemplate = { [...snippets.map((snippet) => snippet.filepath), filepath], workspaceUris, ); - const baseTemplate = `###PATH:${ - relativePaths[relativePaths.length - 1] - }\n###LANGUAGE:${language}\n###MODE:BLOCK\n<|code_suffix|>${suffix}<|code_prefix|>${prefix}<|code_middle|>`; + const baseTemplate = `###PATH:${relativePaths[relativePaths.length - 1] + }\n###LANGUAGE:${language}\n###MODE:BLOCK\n<|code_suffix|>${suffix}<|code_prefix|>${prefix}<|code_middle|>`; if (snippets.length === 0) { return `<|user|>\n${baseTemplate}<|assistant|>\n`; } diff --git a/core/autocomplete/templating/filtering.ts b/core/autocomplete/templating/filtering.ts index c741dedc8b..02dbade362 100644 --- a/core/autocomplete/templating/filtering.ts +++ b/core/autocomplete/templating/filtering.ts @@ -42,24 +42,100 @@ export const getSnippets = ( helper: HelperVars, payload: SnippetPayload, ): AutocompleteSnippet[] => { - const snippets = [ - ...payload.diffSnippets, - ...payload.clipboardSnippets, - ...payload.recentlyVisitedRangesSnippets, - ...shuffleArray( - filterSnippetsAlreadyInCaretWindow( - [...payload.rootPathSnippets, ...payload.importDefinitionSnippets], - helper.prunedCaretWindow, - ), - ), - ]; - const finalSnippets = []; + const snippets = { + "clipboard": payload.clipboardSnippets, + "recentlyVisitedRanges": payload.recentlyVisitedRangesSnippets, + "recentlyEditedRanges": payload.recentlyEditedRangeSnippets, + "diff": payload.diffSnippets, + "base": shuffleArray(filterSnippetsAlreadyInCaretWindow( + [...payload.rootPathSnippets, ...payload.importDefinitionSnippets], + helper.prunedCaretWindow, + )), + } + + // Define snippets with their priorities + const snippetConfigs: { + key: keyof typeof snippets; + enabledOrPriority: boolean | number; + defaultPriority: number; + snippets: AutocompleteSnippet[]; + }[] = [ + { + key: "clipboard", + enabledOrPriority: helper.options.experimental_includeClipboard, + defaultPriority: 1, + snippets: payload.clipboardSnippets, + }, + { + key: "recentlyVisitedRanges", + enabledOrPriority: helper.options.experimental_includeRecentlyVisitedRanges, + defaultPriority: 2, + snippets: payload.recentlyVisitedRangesSnippets, + /* TODO: recentlyVisitedRanges also contain contents from other windows like terminal or output + if they are visible. We should handle them separately so that we can control their priority + and whether they should be included or not. */ + }, + { + key: "recentlyEditedRanges", + enabledOrPriority: helper.options.experimental_includeRecentlyEditedRanges, + defaultPriority: 3, + snippets: payload.recentlyEditedRangeSnippets, + }, + { + key: "diff", + enabledOrPriority: helper.options.experimental_includeDiff, + defaultPriority: 4, + snippets: payload.diffSnippets, + // TODO: diff is commonly too large, thus anything lower in priority is not included. + }, + { + key: "base", + enabledOrPriority: true, + defaultPriority: 99, // make sure it's the last one to be processed, but still possible to override + snippets: shuffleArray(filterSnippetsAlreadyInCaretWindow( + [...payload.rootPathSnippets, ...payload.importDefinitionSnippets], + helper.prunedCaretWindow, + )), + // TODO: Add this too to experimental config, maybe move upper in the order, since it's almost + // always not inlucded due to diff being commonly large + }, + ]; + + // Create a readable order of enabled snippets + const snippetOrder = snippetConfigs + .filter(({ enabledOrPriority }) => enabledOrPriority) + .map(({ key, enabledOrPriority, defaultPriority }) => ({ + key, + priority: typeof enabledOrPriority === 'number' ? enabledOrPriority : defaultPriority, + })) + .sort((a, b) => a.priority - b.priority); + // Log the snippet order for debugging - uncomment if needed + /* console.log( + 'Snippet processing order:', + snippetOrder + .map(({ key, priority }) => `${key} (priority: ${priority})`).join("\n") + ); */ + + // Convert configs to prioritized snippets + let prioritizedSnippets = snippetOrder + .flatMap(({ key, priority }) => + snippets[key].map(snippet => ({ snippet, priority })) + ) + .sort((a, b) => a.priority - b.priority) + .map(({ snippet }) => snippet); + + // Exclude Continue's own output as it makes it super-hard for users to test the autocomplete feature + // while looking at the prompts in the Continue's output + prioritizedSnippets = prioritizedSnippets.filter((snippet) => + !(snippet as AutocompleteCodeSnippet).filepath?.startsWith("output:extension-output-Continue.continue")); + + const finalSnippets = []; let remainingTokenCount = getRemainingTokenCount(helper); - while (remainingTokenCount > 0 && snippets.length > 0) { - const snippet = snippets.shift(); + while (remainingTokenCount > 0 && prioritizedSnippets.length > 0) { + const snippet = prioritizedSnippets.shift(); if (!snippet || !isValidSnippet(snippet)) { continue; } diff --git a/core/index.d.ts b/core/index.d.ts index 1184b412b2..06242206cb 100644 --- a/core/index.d.ts +++ b/core/index.d.ts @@ -43,13 +43,13 @@ export interface IndexingProgressUpdate { desc: string; shouldClearIndexes?: boolean; status: - | "loading" - | "indexing" - | "done" - | "failed" - | "paused" - | "disabled" - | "cancelled"; + | "loading" + | "indexing" + | "done" + | "failed" + | "paused" + | "disabled" + | "cancelled"; debugInfo?: string; } @@ -666,10 +666,10 @@ export interface IDE { getCurrentFile(): Promise< | undefined | { - isUntitled: boolean; - path: string; - contents: string; - } + isUntitled: boolean; + path: string; + contents: string; + } >; getPinnedFiles(): Promise; @@ -848,11 +848,11 @@ export interface CustomCommand { export interface Prediction { type: "content"; content: - | string - | { - type: "text"; - text: string; - }[]; + | string + | { + type: "text"; + text: string; + }[]; } export interface ToolExtras { @@ -971,6 +971,11 @@ export interface TabAutocompleteOptions { disableInFiles?: string[]; useImports?: boolean; showWhateverWeHaveAtXMs?: number; + // true = enabled, false = disabled, number = enabled with priority + experimental_includeClipboard: boolean | number; + experimental_includeRecentlyVisitedRanges: boolean | number; + experimental_includeRecentlyEditedRanges: boolean | number; + experimental_includeDiff: boolean | number; } export interface StdioOptions { @@ -1171,9 +1176,9 @@ export interface Config { embeddingsProvider?: EmbeddingsProviderDescription | ILLM; /** The model that Continue will use for tab autocompletions. */ tabAutocompleteModel?: - | CustomLLM - | ModelDescription - | (CustomLLM | ModelDescription)[]; + | CustomLLM + | ModelDescription + | (CustomLLM | ModelDescription)[]; /** Options for tab autocomplete */ tabAutocompleteOptions?: Partial; /** UI styles customization */ @@ -1264,9 +1269,9 @@ export type PackageDetailsSuccess = PackageDetails & { export type PackageDocsResult = { packageInfo: ParsedPackageInfo; } & ( - | { error: string; details?: never } - | { details: PackageDetailsSuccess; error?: never } -); + | { error: string; details?: never } + | { details: PackageDetailsSuccess; error?: never } + ); export interface TerminalOptions { reuseTerminal?: boolean; diff --git a/core/util/parameters.ts b/core/util/parameters.ts index 648253712b..05ab61e06b 100644 --- a/core/util/parameters.ts +++ b/core/util/parameters.ts @@ -18,6 +18,10 @@ export const DEFAULT_AUTOCOMPLETE_OPTS: TabAutocompleteOptions = { useImports: true, transform: true, showWhateverWeHaveAtXMs: 300, + experimental_includeClipboard: true, + experimental_includeRecentlyVisitedRanges: true, + experimental_includeRecentlyEditedRanges: true, + experimental_includeDiff: true, }; export const COUNT_COMPLETION_REJECTED_AFTER = 10_000; diff --git a/extensions/vscode/src/autocomplete/RecentlyVisitedRangesService.ts b/extensions/vscode/src/autocomplete/RecentlyVisitedRangesService.ts index 2dba327ef7..a20e88ac3d 100644 --- a/extensions/vscode/src/autocomplete/RecentlyVisitedRangesService.ts +++ b/extensions/vscode/src/autocomplete/RecentlyVisitedRangesService.ts @@ -16,10 +16,10 @@ export class RecentlyVisitedRangesService { Array >; // Default value, we override in initWithPostHog - private numSurroundingLines = 5; + private numSurroundingLines = 20; private maxRecentFiles = 3; private maxSnippetsPerFile = 3; - private isEnabled = false; + private isEnabled = true; constructor(private readonly ide: IDE) { this.cache = new LRUCache< @@ -38,12 +38,11 @@ export class RecentlyVisitedRangesService { PosthogFeatureFlag.RecentlyVisitedRangesNumSurroundingLines, ); - if (!recentlyVisitedRangesNumSurroundingLines) { - return; + if (recentlyVisitedRangesNumSurroundingLines) { + this.isEnabled = true; + this.numSurroundingLines = recentlyVisitedRangesNumSurroundingLines; } - this.isEnabled = true; - this.numSurroundingLines = recentlyVisitedRangesNumSurroundingLines; vscode.window.onDidChangeTextEditorSelection( this.cacheCurrentSelectionContext, ); @@ -114,7 +113,10 @@ export class RecentlyVisitedRangesService { } return allSnippets - .filter((s) => !currentFilepath || s.filepath !== currentFilepath) + .filter((s) => !currentFilepath || (s.filepath !== currentFilepath && + // Exclude Continue's own output as it makes it super-hard for users to test the autocomplete feature + // while looking at the prompts in the Continue's output + !s.filepath.startsWith("output:extension-output-Continue.continue"))) .sort((a, b) => b.timestamp - a.timestamp) .map(({ timestamp, ...snippet }) => snippet); } diff --git a/extensions/vscode/src/autocomplete/completionProvider.ts b/extensions/vscode/src/autocomplete/completionProvider.ts index c10d4d5f18..aca4c54784 100644 --- a/extensions/vscode/src/autocomplete/completionProvider.ts +++ b/extensions/vscode/src/autocomplete/completionProvider.ts @@ -41,8 +41,7 @@ interface VsCodeCompletionInput { } export class ContinueCompletionProvider - implements vscode.InlineCompletionItemProvider -{ + implements vscode.InlineCompletionItemProvider { private onError(e: any) { const options = ["Documentation"]; if (e.message.includes("Ollama may not be installed")) { @@ -304,7 +303,7 @@ export class ContinueCompletionProvider } else { // If the first part of the diff isn't an insertion, then the model is // probably rewriting other parts of the line - return undefined; + // return undefined; - Let's assume it's simply an insertion } } } else {