Skip to content

Commit 0db978c

Browse files
committed
feat(inline): add inline chat #257
1 parent fca0563 commit 0db978c

36 files changed

+1793
-36
lines changed

core/src/223/main/resources/META-INF/autodev-core.xml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,11 @@
176176
<extensionPoint qualifiedName="cc.unitmesh.jsonTextProvider"
177177
interface="cc.unitmesh.devti.provider.local.JsonTextProvider"
178178
dynamic="true"/>
179+
180+
<!-- Lang Sketch Provider -->
181+
<extensionPoint qualifiedName="cc.unitmesh.langSketchProvider"
182+
interface="cc.unitmesh.devti.sketch.LanguageSketchProvider"
183+
dynamic="true" />
179184
</extensionPoints>
180185

181186
<applicationListeners>
@@ -204,6 +209,8 @@
204209
<bundleName>messages.AutoDevBundle</bundleName>
205210
<categoryKey>intention.category.llm</categoryKey>
206211
</autoDevIntention>
212+
213+
<langSketchProvider implementation="cc.unitmesh.devti.sketch.patch.DiffLangSketchProvider"/>
207214
</extensions>
208215

209216
<actions>
@@ -257,8 +264,8 @@
257264
description="Ask AI chat with this code">
258265
</action>
259266

260-
<action id="cc.unitmesh.devti.actions.chat.GenTestDataAction"
261-
class="cc.unitmesh.devti.actions.chat.GenTestDataAction"
267+
<action id="cc.unitmesh.devti.actions.chat.GenerateApiTestAction"
268+
class="cc.unitmesh.devti.actions.chat.GenerateApiTestAction"
262269
description="Ask AI generate test data">
263270

264271
<add-to-group group-id="GenerateGroup" anchor="last"/>

core/src/233/main/resources/META-INF/autodev-core.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,11 @@
186186
<extensionPoint qualifiedName="cc.unitmesh.httpClientExecutor"
187187
interface="cc.unitmesh.devti.provider.http.HttpClientProvider"
188188
dynamic="true"/>
189+
190+
<!-- Lang Sketch Provider -->
191+
<extensionPoint qualifiedName="cc.unitmesh.langSketchProvider"
192+
interface="cc.unitmesh.devti.sketch.LanguageSketchProvider"
193+
dynamic="true" />
189194
</extensionPoints>
190195

191196
<applicationListeners>
@@ -214,6 +219,8 @@
214219
<bundleName>messages.AutoDevBundle</bundleName>
215220
<categoryKey>intention.category.llm</categoryKey>
216221
</autoDevIntention>
222+
223+
<langSketchProvider implementation="cc.unitmesh.devti.sketch.patch.DiffLangSketchProvider"/>
217224
</extensions>
218225

219226
<actions>

core/src/main/kotlin/cc/unitmesh/devti/gui/AutoDevToolWindowFactory.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import cc.unitmesh.devti.gui.chat.ChatActionType
44
import cc.unitmesh.devti.gui.chat.ChatCodingPanel
55
import cc.unitmesh.devti.gui.chat.ChatCodingService
66
import cc.unitmesh.devti.settings.LanguageChangedCallback.componentStateChanged
7+
import com.intellij.openapi.Disposable
78
import com.intellij.openapi.actionSystem.ex.ActionUtil
89
import com.intellij.openapi.application.ApplicationManager
910
import com.intellij.openapi.project.DumbAware
@@ -20,6 +21,8 @@ class AutoDevToolWindowFactory : ToolWindowFactory, DumbAware {
2021
}
2122

2223
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
24+
cc.unitmesh.devti.inline.ShireInlineChatProvider.addListener(project)
25+
2326
val chatCodingService = ChatCodingService(ChatActionType.CHAT, project)
2427
val contentPanel = ChatCodingPanel(chatCodingService, toolWindow.disposable)
2528
val content = ContentFactory.getInstance().createContent(contentPanel, "", false).apply {
@@ -35,6 +38,7 @@ class AutoDevToolWindowFactory : ToolWindowFactory, DumbAware {
3538
toolWindow.setTitleActions(listOfNotNull(ActionUtil.getActionGroup("AutoDev.ToolWindow.Chat.TitleActions")))
3639
}
3740

41+
3842
companion object {
3943
fun getToolWindow(project: Project): ToolWindow? {
4044
return ToolWindowManager.getInstance(project).getToolWindow(Util.id)

core/src/main/kotlin/cc/unitmesh/devti/gui/chat/ChatActionType.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ enum class ChatActionType(var context: ChatTemplateContext) {
3434
CUSTOM_COMPLETE(ChatTemplateContext()),
3535
CUSTOM_ACTION(ChatTemplateContext()),
3636
CUSTOM_AGENT(ChatTemplateContext()),
37-
CODE_REVIEW(ChatTemplateContext())
37+
CODE_REVIEW(ChatTemplateContext()),
38+
INLINE_CHAT(ChatTemplateContext())
3839
;
3940

4041
fun instruction(lang: String = "", project: Project?): TextTemplatePrompt {
@@ -86,6 +87,14 @@ enum class ChatActionType(var context: ChatTemplateContext) {
8687

8788
TextTemplatePrompt(displayText, template, templateRender, this.context)
8889
}
90+
91+
INLINE_CHAT -> {
92+
val displayText = AutoDevBundle.message("prompts.autodev.inlineChat", lang)
93+
val templateRender = TemplateRender(GENIUS_CODE)
94+
val template = templateRender.getTemplate("inline-chat.vm")
95+
96+
TextTemplatePrompt(displayText, template, templateRender, this.context)
97+
}
8998
}
9099
}
91100
}

core/src/main/kotlin/cc/unitmesh/devti/gui/chat/ChatCodingService.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package cc.unitmesh.devti.gui.chat
22

33
import cc.unitmesh.cf.core.llms.LlmMsg
44
import cc.unitmesh.devti.AutoDevBundle
5-
import cc.unitmesh.devti.util.LLMCoroutineScope
5+
import cc.unitmesh.devti.util.AutoDevCoroutineScope
66
import cc.unitmesh.devti.agent.CustomAgentChatProcessor
77
import cc.unitmesh.devti.agent.configurable.customAgentSetting
88
import cc.unitmesh.devti.agent.model.CustomAgentState
@@ -82,7 +82,7 @@ class ChatCodingService(var actionType: ChatActionType, val project: Project) {
8282

8383
ApplicationManager.getApplication().executeOnPooledThread {
8484
val response = this.makeChatBotRequest(requestPrompt, keepHistory, chatHistory)
85-
currentJob = LLMCoroutineScope.scope(project).launch {
85+
currentJob = AutoDevCoroutineScope.scope(project).launch {
8686
when {
8787
actionType === ChatActionType.REFACTOR -> ui.updateReplaceableContent(response) {
8888
context?.postAction?.invoke(it)
@@ -107,7 +107,7 @@ class ChatCodingService(var actionType: ChatActionType, val project: Project) {
107107
ApplicationManager.getApplication().executeOnPooledThread {
108108
val response = llmProvider.stream(requestPrompt, systemPrompt)
109109

110-
currentJob = LLMCoroutineScope.scope(project).launch {
110+
currentJob = AutoDevCoroutineScope.scope(project).launch {
111111
ui.updateMessage(response)
112112
}
113113
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package com.phodal.shire.inline
2+
3+
import cc.unitmesh.devti.inline.AutoDevGutterIconRenderer
4+
import cc.unitmesh.devti.inline.AutoDevInlineChatService
5+
import com.intellij.openapi.Disposable
6+
import com.intellij.openapi.components.Service
7+
import com.intellij.openapi.editor.Editor
8+
import com.intellij.openapi.editor.EditorFactory
9+
import com.intellij.openapi.editor.event.EditorFactoryEvent
10+
import com.intellij.openapi.editor.event.EditorFactoryListener
11+
import com.intellij.openapi.editor.event.SelectionEvent
12+
import com.intellij.openapi.editor.event.SelectionListener
13+
import com.intellij.openapi.editor.markup.RangeHighlighter
14+
import com.intellij.openapi.fileEditor.FileDocumentManager
15+
import com.intellij.openapi.fileEditor.FileEditorManager
16+
import com.intellij.openapi.fileEditor.TextEditor
17+
import com.intellij.openapi.project.Project
18+
import com.intellij.openapi.util.Disposer
19+
import io.ktor.util.collections.*
20+
21+
data class GutterIconData(
22+
val line: Int,
23+
val highlighter: RangeHighlighter,
24+
)
25+
26+
@Service(Service.Level.PROJECT)
27+
class AutoDevGutterHandler(private val project: Project) : Disposable {
28+
private val gutterIcons: ConcurrentMap<Editor, GutterIconData?> = ConcurrentMap()
29+
30+
private var disposable: Disposable? = null
31+
32+
fun listen() {
33+
addEditorFactoryListener()
34+
}
35+
36+
private fun addEditorFactoryListener() {
37+
if (disposable != null) {
38+
return
39+
}
40+
disposable = Disposer.newDisposable().apply {
41+
EditorFactory.getInstance().addEditorFactoryListener(object : EditorFactoryListener {
42+
override fun editorCreated(event: EditorFactoryEvent) {
43+
onEditorCreated(event.editor, this@apply)
44+
}
45+
}, this)
46+
47+
FileEditorManager.getInstance(project).allEditors.mapNotNull { it as? TextEditor }.forEach {
48+
updateGutterIconWithSelection(it.editor)
49+
onEditorCreated(it.editor, this@apply)
50+
}
51+
}
52+
}
53+
54+
fun onEditorCreated(editor: Editor, disposable: Disposable) {
55+
editor.selectionModel.addSelectionListener(object : SelectionListener {
56+
override fun selectionChanged(e: SelectionEvent) {
57+
if (e.editor.project != project) return
58+
updateGutterIconWithSelection(editor)
59+
}
60+
}, disposable)
61+
}
62+
63+
fun updateGutterIconWithSelection(editor: Editor) {
64+
if (!editor.selectionModel.hasSelection()) {
65+
gutterIcons[editor]?.let {
66+
removeGutterIcon(editor, it.highlighter)
67+
}
68+
69+
return
70+
}
71+
72+
val selectionStart = editor.document.getLineNumber(editor.selectionModel.selectionStart)
73+
if (selectionStart >= 0 && selectionStart < editor.document.lineCount) {
74+
val gutterIconInfo = gutterIcons[editor]
75+
if (gutterIconInfo?.line != selectionStart) {
76+
addGutterIcon(editor, selectionStart)
77+
}
78+
}
79+
}
80+
81+
private fun addGutterIcon(editor: Editor, line: Int) {
82+
val iconData: GutterIconData? = gutterIcons[editor]
83+
if (iconData != null) {
84+
removeGutterIcon(editor, iconData.highlighter)
85+
}
86+
87+
FileDocumentManager.getInstance().getFile(editor.document) ?: return
88+
89+
val highlighter = editor.markupModel.addLineHighlighter(null, line, 0)
90+
highlighter.gutterIconRenderer = AutoDevGutterIconRenderer(line, onClick = {
91+
AutoDevInlineChatService.getInstance().showInlineChat(editor)
92+
})
93+
94+
gutterIcons[editor] = GutterIconData(line, highlighter)
95+
}
96+
97+
fun removeGutterIcon(editor: Editor, highlighter: RangeHighlighter? = null) {
98+
if (highlighter != null) editor.markupModel.removeHighlighter(highlighter)
99+
gutterIcons.remove(editor)
100+
}
101+
102+
override fun dispose() {
103+
gutterIcons.forEach {
104+
removeGutterIcon(it.key, it.value?.highlighter)
105+
}
106+
disposable?.let { Disposer.dispose(it) }
107+
disposable = null
108+
}
109+
110+
companion object {
111+
112+
fun getInstance(project: Project): AutoDevGutterHandler {
113+
return project.getService(AutoDevGutterHandler::class.java)
114+
}
115+
}
116+
}
117+
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package cc.unitmesh.devti.inline
2+
3+
import cc.unitmesh.devti.AutoDevIcons
4+
import com.intellij.openapi.actionSystem.AnAction
5+
import com.intellij.openapi.actionSystem.AnActionEvent
6+
import com.intellij.openapi.editor.markup.GutterIconRenderer
7+
import javax.swing.Icon
8+
9+
class AutoDevGutterIconRenderer(
10+
val line: Int, val onClick: () -> Unit,
11+
) : GutterIconRenderer() {
12+
override fun getClickAction(): AnAction {
13+
return object : AnAction() {
14+
override fun actionPerformed(e: AnActionEvent) {
15+
onClick()
16+
}
17+
}
18+
}
19+
20+
override fun getIcon(): Icon = AutoDevIcons.AI_COPILOT
21+
override fun equals(other: Any?): Boolean {
22+
if (this === other) return true
23+
if (javaClass != other?.javaClass) return false
24+
25+
other as AutoDevGutterIconRenderer
26+
27+
if (line != other.line) return false
28+
if (onClick != other.onClick) return false
29+
30+
return true
31+
}
32+
33+
override fun hashCode(): Int {
34+
var result = line
35+
result = 31 * result + onClick.hashCode()
36+
return result
37+
}
38+
39+
}

0 commit comments

Comments
 (0)