Skip to content

Commit 46d61e6

Browse files
committed
feat: additional inline edit improvements
1 parent c3fc404 commit 46d61e6

Some content is hidden

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

48 files changed

+1672
-944
lines changed

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jsoup = "1.21.2"
1212
jtokkit = "1.1.0"
1313
junit = "5.13.4"
1414
kotlin = "2.2.10"
15-
llm-client = "0.8.47"
15+
llm-client = "0.8.49"
1616
okio = "3.15.0"
1717
tree-sitter = "0.24.5"
1818
grpc = "1.73.0"

src/main/java/ee/carlrobert/codegpt/CodeGPTKeys.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ public class CodeGPTKeys {
3030
Key.create("codegpt.editorInlineEditRenderer");
3131
public static final Key<JComponent> EDITOR_INLINE_EDIT_COMPARE_LINK =
3232
Key.create("codegpt.editorInlineEditCompareLink");
33+
public static final Key<JComponent> EDITOR_INLINE_EDIT_ACCEPT_ALL_CHIP =
34+
Key.create("codegpt.editorInlineEditAcceptAllChip");
35+
public static final Key<JComponent> EDITOR_INLINE_EDIT_REJECT_ALL_CHIP =
36+
Key.create("codegpt.editorInlineEditRejectAllChip");
3337
public static final Key<PartialCodeCompletionResponse> REMAINING_CODE_COMPLETION =
3438
Key.create("codegpt.remainingCodeCompletion");
3539
public static final Key<NextEditResponse> REMAINING_PREDICTION_RESPONSE =

src/main/java/ee/carlrobert/codegpt/ReferencedFile.java

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
package ee.carlrobert.codegpt;
22

3-
import com.intellij.openapi.application.ApplicationManager;
4-
import com.intellij.openapi.editor.Document;
5-
import com.intellij.openapi.fileEditor.FileDocumentManager;
6-
import com.intellij.openapi.util.Computable;
73
import com.intellij.openapi.vfs.VirtualFile;
4+
import ee.carlrobert.codegpt.util.EditorUtil;
85
import ee.carlrobert.codegpt.util.file.FileUtil;
96
import java.io.File;
107
import java.util.Objects;
@@ -18,13 +15,6 @@ public ReferencedFile(String fileName, String filePath, String fileContent) {
1815
this(fileName, filePath, fileContent, false);
1916
}
2017

21-
public ReferencedFile(String fileName, String filePath, String fileContent, boolean directory) {
22-
this.fileName = fileName;
23-
this.filePath = filePath;
24-
this.fileContent = fileContent;
25-
this.directory = directory;
26-
}
27-
2818
public static ReferencedFile from(File file) {
2919
return new ReferencedFile(
3020
file.getName(),
@@ -47,14 +37,7 @@ private static String getVirtualFileContent(VirtualFile virtualFile) {
4737
if (virtualFile.isDirectory()) {
4838
return "";
4939
}
50-
51-
var documentManager = FileDocumentManager.getInstance();
52-
var document = ApplicationManager.getApplication()
53-
.runReadAction((Computable<Document>) () -> documentManager.getDocument(virtualFile));
54-
if (document != null && documentManager.isDocumentUnsaved(document)) {
55-
return document.getText();
56-
}
57-
return FileUtil.readContent(virtualFile);
40+
return EditorUtil.getFileContent(virtualFile);
5841
}
5942

6043
public String getFileExtension() {

src/main/java/ee/carlrobert/codegpt/actions/ActionType.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public enum ActionType {
99
DISCARD_TOKEN_LIMIT,
1010
OPEN_CONVERSATION_IN_EDITOR,
1111
DIFF_CODE,
12-
EDIT_CODE,
12+
INLINE_EDIT,
1313
CREATE_NEW_FILE,
1414
COPY_CODE,
1515
AUTO_APPLY,

src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestService.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import ee.carlrobert.codegpt.settings.service.ServiceType;
1212
import ee.carlrobert.llm.client.DeserializationUtil;
1313
import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionRequest;
14+
import ee.carlrobert.llm.client.codegpt.request.InlineEditRequest;
1415
import ee.carlrobert.llm.client.codegpt.request.chat.ChatCompletionRequest;
1516
import ee.carlrobert.llm.client.google.completion.GoogleCompletionRequest;
1617
import ee.carlrobert.llm.client.llama.completion.LlamaCompletionRequest;
@@ -116,6 +117,10 @@ public EventSource getChatCompletionAsync(
116117
CompletionEventListener<String> eventListener,
117118
ServiceType serviceType,
118119
FeatureType featureType) {
120+
if (request instanceof InlineEditRequest completionRequest) {
121+
return CompletionClientProvider.getCodeGPTClient()
122+
.getInlineEditAsync(completionRequest, eventListener);
123+
}
119124
if (request instanceof OpenAIChatCompletionRequest completionRequest) {
120125
return switch (serviceType) {
121126
case OPENAI -> CompletionClientProvider.getOpenAIClient()
@@ -254,4 +259,4 @@ private Optional<String> tryExtractContent(OpenAIChatCompletionResponse response
254259
.filter(c -> c != null && !c.isBlank())
255260
.findFirst();
256261
}
257-
}
262+
}

src/main/java/ee/carlrobert/codegpt/completions/ConversationType.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ public enum ConversationType {
77
FIX_COMPILE_ERRORS,
88
MULTI_FILE,
99
INLINE_COMPLETION,
10-
EDIT_CODE,
10+
INLINE_EDIT,
1111
REVIEW_CHANGES
1212
}

src/main/kotlin/ee/carlrobert/codegpt/FileWatcher.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,31 +28,31 @@ class FileWatcher : Disposable {
2828
val watchService = FileSystems.getDefault().newWatchService()
2929
path.register(watchService, ENTRY_CREATE)
3030
watchServices.add(watchService)
31-
logger.debug("Successfully registered watch service for path: $pathString (absolute: ${path.toAbsolutePath()})")
31+
logger.info("Successfully registered watch service for path: $pathString (absolute: ${path.toAbsolutePath()})")
3232

3333
val monitor = thread {
3434
try {
35-
logger.debug("File watch monitor thread started for path: $pathString")
35+
logger.info("File watch monitor thread started for path: $pathString")
3636
generateSequence { watchService.take() }.forEach { key ->
37-
logger.trace("Watch event received for path: $pathString")
37+
logger.info("Watch event received for path: $pathString")
3838
key.pollEvents().forEach { event ->
3939
val fileName = event.context() as Path
4040
val fullPath = path.resolve(fileName)
41-
logger.debug("File event detected: ${event.kind()} - fileName=$fileName, fullPath=$fullPath")
41+
logger.info("File event detected: ${event.kind()} - fileName=$fileName, fullPath=$fullPath")
4242
onFileCreated(fileName, pathString)
4343
}
4444
val resetResult = key.reset()
4545
if (!resetResult) {
46-
logger.warn("Watch key reset failed for path: $pathString - watch may have become invalid")
46+
logger.info("Watch key reset failed for path: $pathString - watch may have become invalid")
4747
}
4848
}
4949
} catch (e: InterruptedException) {
50-
logger.debug("File watch monitor thread interrupted for path: $pathString")
50+
logger.error("File watch monitor thread interrupted for path: $pathString", e)
5151
Thread.currentThread().interrupt()
5252
} catch (e: Exception) {
53-
logger.warn("Error in file watcher for path: $pathString", e)
53+
logger.error("Error in file watcher for path: $pathString", e)
5454
} finally {
55-
logger.debug("File watch monitor thread stopped for path: $pathString")
55+
logger.error("File watch monitor thread stopped for path: $pathString")
5656
}
5757
}
5858
fileMonitors.add(monitor)

src/main/kotlin/ee/carlrobert/codegpt/actions/InlayActionPromoter.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,19 @@ import ee.carlrobert.codegpt.predictions.OpenPredictionAction
1212
import ee.carlrobert.codegpt.predictions.TriggerCustomPredictionAction
1313
import ee.carlrobert.codegpt.inlineedit.AcceptCurrentInlineEditAction
1414
import ee.carlrobert.codegpt.actions.editor.AcceptInlineEditAction
15+
import ee.carlrobert.codegpt.actions.editor.InlineEditContextMenuAction
16+
import ee.carlrobert.codegpt.actions.editor.InlineEditFloatingMenuAction
1517
import ee.carlrobert.codegpt.inlineedit.RejectCurrentInlineEditAction
1618

1719
class InlayActionPromoter : ActionPromoter {
1820
override fun promote(actions: List<AnAction>, context: DataContext): List<AnAction> {
1921
val editor = CommonDataKeys.EDITOR.getData(context) ?: return emptyList()
2022

23+
if (editor.document.textLength > 0) {
24+
actions.filterIsInstance<InlineEditContextMenuAction>().takeIf { it.isNotEmpty() }?.let { return it }
25+
actions.filterIsInstance<InlineEditFloatingMenuAction>().takeIf { it.isNotEmpty() }?.let { return it }
26+
}
27+
2128
val hasInlineEdit = editor.getUserData(CodeGPTKeys.EDITOR_INLINE_EDIT_SESSION) != null ||
2229
editor.getUserData(CodeGPTKeys.EDITOR_INLINE_EDIT_RENDERER) != null
2330
if (hasInlineEdit) {

src/main/kotlin/ee/carlrobert/codegpt/actions/editor/EditorComponentInlaysManager.kt

Lines changed: 80 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import com.intellij.openapi.util.Key
1212
import com.intellij.ui.components.JBScrollPane
1313
import com.intellij.util.concurrency.annotations.RequiresEdt
1414
import com.intellij.util.ui.JBUI
15+
import java.awt.Component
16+
import java.awt.Container
17+
import java.awt.Cursor
1518
import java.awt.Dimension
1619
import java.awt.Font
1720
import java.awt.event.ComponentAdapter
@@ -39,35 +42,27 @@ class EditorComponentInlaysManager(val editor: EditorImpl) : Disposable {
3942
}
4043

4144
@RequiresEdt
42-
fun insert(lineIndex: Int, component: JComponent, showAbove: Boolean = false): Disposable? {
45+
fun insert(offset: Int, component: JComponent, showAbove: Boolean = false): Disposable? {
4346
val wrappedComponent = ComponentWrapper(component)
44-
val offset = if (lineIndex < editor.document.lineCount) {
45-
editor.document.getLineStartOffset(lineIndex)
46-
} else {
47-
editor.document.textLength
48-
}
49-
50-
val result = EditorEmbeddedComponentManager.getInstance()
47+
return EditorEmbeddedComponentManager.getInstance()
5148
.addComponent(
52-
editor, wrappedComponent,
49+
editor,
50+
wrappedComponent,
5351
EditorEmbeddedComponentManager.Properties(
54-
EditorEmbeddedComponentManager.ResizePolicy.none(),
52+
EditorEmbeddedComponentManager.ResizePolicy.any(),
5553
null,
5654
true,
5755
showAbove,
5856
0,
5957
offset
6058
)
6159
)
62-
63-
return result?.also {
64-
managedInlays[wrappedComponent] = it
65-
Disposer.register(it, Disposable {
66-
managedInlays.remove(wrappedComponent)
67-
})
68-
} ?: run {
69-
null
70-
}
60+
?.also {
61+
managedInlays[wrappedComponent] = it
62+
Disposer.register(it, Disposable {
63+
managedInlays.remove(wrappedComponent)
64+
})
65+
}
7166
}
7267

7368
private inner class ComponentWrapper(val component: JComponent) : JBScrollPane(component) {
@@ -82,14 +77,78 @@ class EditorComponentInlaysManager(val editor: EditorImpl) : Disposable {
8277
verticalScrollBar.preferredSize = Dimension(0, 0)
8378
setViewportView(component)
8479

80+
isFocusable = false
81+
viewport.isFocusable = false
82+
83+
cursor = Cursor.getDefaultCursor()
84+
viewport.cursor = Cursor.getDefaultCursor()
85+
86+
isFocusTraversalPolicyProvider = true
87+
focusTraversalPolicy = object : java.awt.FocusTraversalPolicy() {
88+
override fun getFirstComponent(aContainer: Container?) = component
89+
override fun getLastComponent(aContainer: Container?) = component
90+
override fun getDefaultComponent(aContainer: Container?) = component
91+
override fun getComponentAfter(
92+
aContainer: Container?,
93+
aComponent: Component?
94+
) = component
95+
96+
override fun getComponentBefore(
97+
aContainer: Container?,
98+
aComponent: Component?
99+
) = component
100+
}
101+
85102
component.addComponentListener(object : ComponentAdapter() {
86-
override fun componentResized(e: ComponentEvent) =
103+
override fun componentResized(e: ComponentEvent) {
87104
dispatchEvent(ComponentEvent(component, ComponentEvent.COMPONENT_RESIZED))
105+
revalidate()
106+
repaint()
107+
}
88108
})
109+
110+
component.addPropertyChangeListener("preferredSize") { _ ->
111+
invalidate()
112+
revalidate()
113+
repaint()
114+
115+
val newSize = component.preferredSize
116+
val oldPref = preferredSize
117+
118+
if (newSize.height != oldPref.height) {
119+
preferredSize = Dimension(getPreferredSize().width, newSize.height)
120+
121+
editor.contentComponent.invalidate()
122+
editor.contentComponent.revalidate()
123+
editor.contentComponent.repaint()
124+
}
125+
}
126+
}
127+
128+
override fun requestFocus() {
129+
component.requestFocus()
130+
}
131+
132+
override fun requestFocusInWindow(): Boolean {
133+
return component.requestFocusInWindow()
89134
}
90135

91136
override fun getPreferredSize(): Dimension {
92-
return Dimension(editor.contentComponent.width, component.preferredSize.height)
137+
val fixed =
138+
(component.getClientProperty("codegpt.fixedWidth") as? Int)?.let { JBUI.scale(it) }
139+
val height = component.preferredSize.height
140+
val width = fixed ?: run {
141+
val contentW = editor.contentComponent.visibleRect.width
142+
if (contentW > 0) contentW else editor.contentComponent.width
143+
}
144+
return Dimension(width, height)
145+
}
146+
147+
override fun getMinimumSize(): Dimension {
148+
val fixed =
149+
(component.getClientProperty("codegpt.fixedWidth") as? Int)?.let { JBUI.scale(it) }
150+
?: 0
151+
return Dimension(fixed, component.minimumSize.height)
93152
}
94153
}
95154

src/main/kotlin/ee/carlrobert/codegpt/actions/editor/InlineEditAction.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import com.intellij.openapi.application.runInEdt
44
import com.intellij.openapi.editor.Editor
55
import com.intellij.openapi.project.Project
66
import ee.carlrobert.codegpt.Icons
7-
import ee.carlrobert.codegpt.ui.InlineEditPopover
7+
import ee.carlrobert.codegpt.inlineedit.InlineEditInlay
88
import javax.swing.Icon
99

1010
open class InlineEditAction(icon: Icon) : BaseEditorAction(icon) {
1111
override fun actionPerformed(project: Project, editor: Editor, selectedText: String) {
1212
runInEdt {
13-
InlineEditPopover(editor).show()
13+
editor.getUserData(InlineEditInlay.INLAY_KEY)?.dispose()
14+
15+
InlineEditInlay(editor).show()
1416
}
1517
}
1618
}

0 commit comments

Comments
 (0)