Skip to content

Commit c3f26cc

Browse files
authored
Nate/dev (#1739)
* JetBrains status bar spinner for autocomplete * Remove unused images * partial accept jetbrains * fix double-rendering of jetbrains completions * fix offset of multi-line jetbrains completions * enable/disable actions * upgrade to version 0.0.54 * llm-info package * small profile switching improvements * instruct cmd+I not to leave placeholders * @ files in context-providers docs * onboarding fix
1 parent e2d78c8 commit c3f26cc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+694
-224
lines changed

core/config/ConfigHandler.ts

+7
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,13 @@ export class ConfigHandler {
188188
this.selectedProfileId = profileId;
189189
const newConfig = await this.loadConfig();
190190
this.notifyConfigListerners(newConfig);
191+
const selectedProfiles =
192+
this.globalContext.get("lastSelectedProfileForWorkspace") ?? {};
193+
selectedProfiles[await this.getWorkspaceId()] = profileId;
194+
this.globalContext.update(
195+
"lastSelectedProfileForWorkspace",
196+
selectedProfiles,
197+
);
191198
}
192199

193200
// A unique ID for the current workspace, built from folder names

core/config/onboarding.ts

-5
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,6 @@ export function setupApiKeysMode(
1010
return {
1111
...config,
1212
models: config.models.filter((model) => model.provider !== "free-trial"),
13-
tabAutocompleteModel: {
14-
title: "Tab Autocomplete",
15-
provider: "free-trial",
16-
model: TRIAL_FIM_MODEL,
17-
},
1813
embeddingsProvider: {
1914
provider: "free-trial",
2015
},

core/llm/index.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { findLlmInfo } from "@continuedev/llm-info";
12
import Handlebars from "handlebars";
23
import {
34
ChatMessage,
@@ -114,14 +115,17 @@ export abstract class BaseLLM implements ILLM {
114115
..._options,
115116
};
116117

118+
this.model = options.model;
119+
const llmInfo = findLlmInfo(this.model);
120+
117121
const templateType =
118122
options.template ?? autodetectTemplateType(options.model);
119123

120124
this.title = options.title;
121125
this.uniqueId = options.uniqueId ?? "None";
122-
this.model = options.model;
123126
this.systemMessage = options.systemMessage;
124-
this.contextLength = options.contextLength ?? DEFAULT_CONTEXT_LENGTH;
127+
this.contextLength =
128+
options.contextLength ?? llmInfo?.contextLength ?? DEFAULT_CONTEXT_LENGTH;
125129
this.completionOptions = {
126130
...options.completionOptions,
127131
model: options.model || "gpt-4",

core/llm/templates/edit.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ ${otherData.codeToEdit}
9595
${suffixTag}
9696
\`\`\`
9797
98-
Please rewrite the entire code block above in order to satisfy the following request: "${otherData.userInput}".${suffixExplanation}`,
98+
Please rewrite the entire code block above in order to satisfy the following request: "${otherData.userInput}". You should rewrite the entire code block without leaving placeholders, even if the code is the same as before.${suffixExplanation}`,
9999
},
100100
{
101101
role: "assistant",
@@ -115,7 +115,7 @@ ${otherData.codeToEdit}
115115
${suffixTag}
116116
\`\`\`
117117
118-
Please rewrite the entire code block above, editing the portion below "${START_TAG}" in order to satisfy the following request: "${otherData.userInput}".${suffixExplanation}
118+
Please rewrite the entire code block above, editing the portion below "${START_TAG}" in order to satisfy the following request: "${otherData.userInput}". You should rewrite the entire code block without leaving placeholders, even if the code is the same as before.${suffixExplanation}
119119
`,
120120
},
121121
{

core/package-lock.json

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"@aws-sdk/client-bedrock-runtime": "^3.574.0",
3333
"@aws-sdk/credential-providers": "^3.596.0",
3434
"@continuedev/config-types": "^1.0.6",
35+
"@continuedev/llm-info": "^1.0.1",
3536
"@mozilla/readability": "^0.5.0",
3637
"@octokit/rest": "^20.0.2",
3738
"@types/jsdom": "^21.1.6",

core/util/paths.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -195,16 +195,17 @@ function getMigrationsFolderPath(): string {
195195
return migrationsPath;
196196
}
197197

198-
export function migrate(
198+
export async function migrate(
199199
id: string,
200-
callback: () => void,
200+
callback: () => void | Promise<void>,
201201
onAlreadyComplete?: () => void,
202202
) {
203203
const migrationsPath = getMigrationsFolderPath();
204204
const migrationPath = path.join(migrationsPath, id);
205+
205206
if (!fs.existsSync(migrationPath)) {
206207
try {
207-
callback();
208+
await Promise.resolve(callback());
208209
fs.writeFileSync(migrationPath, "");
209210
} catch (e) {
210211
console.error(`Migration ${id} failed`, e);

docs/docs/customization/context-providers.md

+12-8
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ As an example, say you are working on solving a new GitHub Issue. You type '@iss
1616

1717
To use any of the built-in context providers, open `~/.continue/config.json` and add it to the `contextProviders` list.
1818

19+
### Files
20+
21+
Type '@file' to reference any file in your current workspace.
22+
23+
```json
24+
{ "name": "file" }
25+
```
26+
1927
### Code
2028

2129
Type '@code' to reference specific functions or classes from throughout your project.
@@ -498,9 +506,7 @@ Continue exposes an API for registering context providers from a 3rd party VSCod
498506

499507
```json
500508
{
501-
"extensionDependencies": [
502-
"continue.continue"
503-
],
509+
"extensionDependencies": ["continue.continue"]
504510
}
505511
```
506512

@@ -513,7 +519,6 @@ Here is an example:
513519
import * as vscode from "vscode";
514520

515521
class MyCustomProvider implements IContextProvider {
516-
517522
get description(): ContextProviderDescription {
518523
return {
519524
title: "custom",
@@ -525,7 +530,7 @@ class MyCustomProvider implements IContextProvider {
525530

526531
async getContextItems(
527532
query: string,
528-
extras: ContextProviderExtras
533+
extras: ContextProviderExtras,
529534
): Promise<ContextItem[]> {
530535
return [
531536
{
@@ -537,7 +542,7 @@ class MyCustomProvider implements IContextProvider {
537542
}
538543

539544
async loadSubmenuItems(
540-
args: LoadSubmenuItemsArgs
545+
args: LoadSubmenuItemsArgs,
541546
): Promise<ContextSubmenuItem[]> {
542547
return [];
543548
}
@@ -554,5 +559,4 @@ const continueApi = continueExt?.exports;
554559

555560
// register your custom provider
556561
continueApi?.registerCustomContextProvider(customProvider);
557-
558-
```
562+
```

extensions/intellij/gradle.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ pluginGroup = com.github.continuedev.continueintellijextension
44
pluginName = continue-intellij-extension
55
pluginRepositoryUrl = https://github.com/continuedev/continue
66
# SemVer format -> https://semver.org
7-
pluginVersion = 0.0.53
7+
pluginVersion = 0.0.54
88

99
# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
1010
pluginSinceBuild = 223
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.github.continuedev.continueintellijextension.autocomplete
2+
3+
import com.github.continuedev.continueintellijextension.services.ContinueExtensionSettings
4+
import com.intellij.openapi.actionSystem.ActionUpdateThread
5+
import com.intellij.openapi.actionSystem.AnActionEvent
6+
import com.intellij.openapi.actionSystem.DefaultActionGroup
7+
import com.intellij.openapi.components.service
8+
9+
class AutocompleteActionGroup : DefaultActionGroup() {
10+
override fun getActionUpdateThread(): ActionUpdateThread {
11+
return ActionUpdateThread.EDT
12+
}
13+
14+
override fun update(e: AnActionEvent) {
15+
super.update(e)
16+
removeAll()
17+
18+
val continueSettingsService = service<ContinueExtensionSettings>()
19+
if (continueSettingsService.continueState.enableTabAutocomplete) {
20+
addAll(
21+
DisableTabAutocompleteAction(),
22+
)
23+
} else {
24+
addAll(
25+
EnableTabAutocompleteAction(),
26+
)
27+
}
28+
}
29+
}

extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/autocomplete/AutocompleteEditorListener.kt

+13-2
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,15 @@ import com.intellij.openapi.util.TextRange
1212
class AutocompleteCaretListener: CaretListener {
1313
override fun caretPositionChanged(event: CaretEvent) {
1414
val caret = event.caret ?: return
15-
val oldPosition = event.oldPosition
1615
val offset = caret.offset
1716
val editor = caret.editor
1817
val autocompleteService = editor.project?.service<AutocompleteService>() ?: return
18+
19+
if (autocompleteService.lastChangeWasPartialAccept) {
20+
autocompleteService.lastChangeWasPartialAccept = false
21+
return
22+
}
23+
1924
val pending = autocompleteService.pendingCompletion;
2025
if (pending != null && pending.editor == editor && pending.offset == offset) {
2126
return
@@ -29,10 +34,16 @@ class AutocompleteDocumentListener(private val editorManager: FileEditorManager,
2934
if (editor != editorManager.selectedTextEditor) {
3035
return
3136
}
37+
38+
val service = editor.project?.service<AutocompleteService>() ?: return
39+
if (service.lastChangeWasPartialAccept) {
40+
return
41+
}
42+
3243
// Invoke later is important, otherwise the completion will be triggered before the document is updated
3344
// causing the old caret offset to be used
3445
invokeLater {
35-
editor.project?.service<AutocompleteService>()?.triggerCompletion(editor)
46+
service.triggerCompletion(editor)
3647
}
3748
}
3849
}

extensions/intellij/src/main/kotlin/com/github/continuedev/continueintellijextension/autocomplete/AutocompleteService.kt

+74-5
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,29 @@ import com.intellij.openapi.editor.Editor
1414
import com.intellij.openapi.editor.InlayProperties
1515
import com.intellij.openapi.fileEditor.FileDocumentManager
1616
import com.intellij.openapi.project.Project
17+
import com.intellij.openapi.wm.WindowManager
1718

1819
data class PendingCompletion (
19-
val editor: Editor,
20-
val offset: Int,
21-
val completionId: String,
22-
val text: String?
20+
val editor: Editor,
21+
var offset: Int,
22+
val completionId: String,
23+
var text: String?
2324
)
2425

2526
@Service(Service.Level.PROJECT)
2627
class AutocompleteService(private val project: Project) {
2728
var pendingCompletion: PendingCompletion? = null;
2829
private val autocompleteLookupListener = project.service<AutocompleteLookupListener>()
30+
private var widget: AutocompleteSpinnerWidget? = null
31+
32+
// To avoid triggering another completion on partial acceptance,
33+
// we need to keep track of whether the last change was a partial accept
34+
var lastChangeWasPartialAccept = false
35+
36+
init {
37+
val statusBar = WindowManager.getInstance().getStatusBar(project)
38+
widget = statusBar.getWidget("AutocompleteSpinnerWidget") as? AutocompleteSpinnerWidget
39+
}
2940

3041
fun triggerCompletion(editor: Editor) {
3142
val settings =
@@ -42,6 +53,7 @@ class AutocompleteService(private val project: Project) {
4253
val completionId = uuid()
4354
val offset = editor.caretModel.primaryCaret.offset
4455
pendingCompletion = PendingCompletion(editor, offset, completionId, null)
56+
widget?.setLoading(true)
4557

4658
// Request a completion from the core
4759
val virtualFile = FileDocumentManager.getInstance().getFile(editor.document)
@@ -63,11 +75,13 @@ class AutocompleteService(private val project: Project) {
6375
val lineLength = lineEnd - lineStart
6476

6577
project.service<ContinuePluginService>().coreMessenger?.request("autocomplete/complete", input, null, ({ response ->
78+
widget?.setLoading(false)
79+
6680
val completions = response as List<*>
6781
if (completions.isNotEmpty()) {
6882
val completion = completions[0].toString()
6983

70-
if (completion.lines().size === 1 || column >= lineLength) {
84+
if (completion.isNotEmpty() && (completion.lines().size === 1 || column >= lineLength)) {
7185
// Do not render if completion is multi-line and caret is in middle of line
7286
renderCompletion(editor, offset, completion)
7387
pendingCompletion = pendingCompletion?.copy(text = completion)
@@ -80,12 +94,18 @@ class AutocompleteService(private val project: Project) {
8094
}
8195

8296
private fun renderCompletion(editor: Editor, offset: Int, text: String) {
97+
if (text.isEmpty()) {
98+
return
99+
}
83100
// Don't render completions when code completion dropdown is visible
84101
if (!autocompleteLookupListener.isLookupEmpty()) {
85102
return
86103
}
87104
ApplicationManager.getApplication().invokeLater {
88105
WriteAction.run<Throwable> {
106+
// Clear existing completions
107+
hideCompletions(editor)
108+
89109
val properties = InlayProperties()
90110
properties.relatesToPrecedingText(true)
91111
properties.disableSoftWrapping(true)
@@ -120,8 +140,57 @@ class AutocompleteService(private val project: Project) {
120140
}
121141
}
122142

143+
private fun splitKeepingDelimiters(input: String, delimiterPattern: String = "\\s+"): List<String> {
144+
val initialSplit = input.split("(?<=$delimiterPattern)|(?=$delimiterPattern)".toRegex())
145+
.filter { it.isNotEmpty() }
146+
147+
val result = mutableListOf<String>()
148+
var currentDelimiter = ""
149+
150+
for (part in initialSplit) {
151+
if (part.matches(delimiterPattern.toRegex())) {
152+
currentDelimiter += part
153+
} else {
154+
if (currentDelimiter.isNotEmpty()) {
155+
result.add(currentDelimiter)
156+
currentDelimiter = ""
157+
}
158+
result.add(part)
159+
}
160+
}
161+
162+
if (currentDelimiter.isNotEmpty()) {
163+
result.add(currentDelimiter)
164+
}
165+
166+
return result
167+
}
168+
169+
fun partialAccept() {
170+
val completion = pendingCompletion ?: return
171+
val text = completion.text ?: return
172+
val editor = completion.editor
173+
val offset = completion.offset
174+
175+
lastChangeWasPartialAccept = true
176+
177+
// Split the text into words, keeping delimiters
178+
val words = splitKeepingDelimiters(text)
179+
println(words)
180+
val word = words[0]
181+
editor.document.insertString(offset, word)
182+
editor.caretModel.moveToOffset(offset + word.length)
183+
184+
// Remove the completion and re-display it
185+
hideCompletions(editor)
186+
completion.text = text.substring(word.length)
187+
completion.offset += word.length
188+
renderCompletion(editor, completion.offset, completion.text!!)
189+
}
190+
123191
private fun cancelCompletion(completion: PendingCompletion) {
124192
// Send cancellation message to core
193+
widget?.setLoading(false)
125194
project.service<ContinuePluginService>().coreMessenger?.request("autocomplete/cancel", null,null, ({}))
126195
}
127196

0 commit comments

Comments
 (0)