@@ -21,18 +21,19 @@ import java.awt.event.*
21
21
import javax.swing.*
22
22
import com.intellij.ui.components.IconLabelButton
23
23
import com.intellij.openapi.ui.popup.JBPopupFactory
24
+ import com.intellij.openapi.ui.popup.JBPopupListener
24
25
import com.tabbyml.intellijtabby.lsp.ConnectionService
25
26
import com.tabbyml.intellijtabby.lsp.protocol.ChatEditParams
26
27
import kotlinx.coroutines.CoroutineScope
27
28
import kotlinx.coroutines.Dispatchers
28
29
import kotlinx.coroutines.launch
29
30
import org.eclipse.lsp4j.Location
31
+ import org.eclipse.lsp4j.Range
30
32
31
33
class InlineChatIntentionAction : BaseIntentionAction (), DumbAware {
32
34
private var inlay: Inlay <InlineChatInlayRenderer >? = null
33
35
private var inlayRender: InlineChatInlayRenderer ? = null
34
36
private var project: Project ? = null
35
- private var location: Location ? = null
36
37
private var editor: Editor ? = null
37
38
override fun getFamilyName (): String {
38
39
return " Tabby"
@@ -49,8 +50,8 @@ class InlineChatIntentionAction : BaseIntentionAction(), DumbAware {
49
50
this .project = project
50
51
this .editor = editor
51
52
if (editor != null ) {
52
- this .location = getCurrentLocation(editor = editor)
53
- addInputToEditor(editor = editor, offset = editor.caretModel.offset);
53
+ inlineChatService .location = getCurrentLocation(editor = editor)
54
+ addInputToEditor(project, editor, editor.caretModel.offset);
54
55
}
55
56
56
57
project.messageBus.connect().subscribe(LafManagerListener .TOPIC , LafManagerListener {
@@ -67,9 +68,9 @@ class InlineChatIntentionAction : BaseIntentionAction(), DumbAware {
67
68
return IntentionPreviewInfo .EMPTY
68
69
}
69
70
70
- private fun addInputToEditor (editor : Editor , offset : Int ) {
71
+ private fun addInputToEditor (project : Project , editor : Editor , offset : Int ) {
71
72
val inlayModel = editor.inlayModel
72
- inlayRender = InlineChatInlayRenderer (editor, this ::onClose, this ::onInputSubmit)
73
+ inlayRender = InlineChatInlayRenderer (project, editor, this ::onClose, this ::onInputSubmit)
73
74
inlay = inlayModel.addBlockElement(offset, true , true , 0 , inlayRender!! )
74
75
}
75
76
@@ -82,18 +83,17 @@ class InlineChatIntentionAction : BaseIntentionAction(), DumbAware {
82
83
private fun onInputSubmit (value : String ) {
83
84
chatEdit(command = value)
84
85
editor?.selectionModel?.removeSelection()
86
+ project?.serviceOrNull<CommandHistory >()?.addCommand(value)
85
87
}
86
88
87
89
private fun chatEdit (command : String ) {
88
90
val scope = CoroutineScope (Dispatchers .IO )
91
+ val inlineChatService = project?.serviceOrNull<InlineChatService >() ? : return
89
92
scope.launch {
90
93
val server = project?.serviceOrNull<ConnectionService >()?.getServerAsync() ? : return @launch
91
- if (location == null ) {
92
- return @launch
93
- }
94
- println (" chat edit $location " )
94
+ val location = inlineChatService.location ? : return @launch
95
95
val param = ChatEditParams (
96
- location = location!! ,
96
+ location = location,
97
97
command = command
98
98
)
99
99
server.chatFeature.chatEdit(params = param)
@@ -102,12 +102,13 @@ class InlineChatIntentionAction : BaseIntentionAction(), DumbAware {
102
102
}
103
103
104
104
class InlineChatInlayRenderer (
105
+ private val project : Project ,
105
106
private val editor : Editor ,
106
107
private val onClose : () -> Unit ,
107
108
private val onSubmit : (value: String ) -> Unit
108
109
) :
109
110
EditorCustomElementRenderer {
110
- private val inlineChatComponent = InlineChatComponent (onClose = this ::removeComponent, onSubmit = onSubmit)
111
+ private val inlineChatComponent = InlineChatComponent (project, this ::removeComponent, onSubmit)
111
112
private var targetRegion: Rectangle ? = null
112
113
113
114
init {
@@ -169,9 +170,9 @@ class InlineChatInlayRenderer(
169
170
}
170
171
}
171
172
172
- class InlineChatComponent (private val onClose : () -> Unit , private val onSubmit : (value: String ) -> Unit ) : JPanel() {
173
+ class InlineChatComponent (private val project : Project , private val onClose : () -> Unit , private val onSubmit : (value: String ) -> Unit ) : JPanel() {
173
174
private val closeButton = createCloseButton()
174
- private val inlineInput = InlineInputComponent (onSubmit = this ::handleSubmit, onCancel = this ::handleClose)
175
+ private val inlineInput = InlineInputComponent (project, this ::handleSubmit, this ::handleClose)
175
176
176
177
override fun isOpaque (): Boolean {
177
178
return false ;
@@ -223,7 +224,12 @@ class InlineChatComponent(private val onClose: () -> Unit, private val onSubmit:
223
224
}
224
225
}
225
226
226
- class InlineInputComponent (private var onSubmit : (value: String ) -> Unit , private var onCancel : () -> Unit ) : JPanel() {
227
+ data class PickItem (var label : String , var value : String , var icon : Icon , var description : String? , val canDelete : Boolean )
228
+ data class ChatEditFileContext (val referrer : String , val uri : String , val range : Range )
229
+ data class InlineEditCommand (val command : String , val context : List <ChatEditFileContext >? )
230
+
231
+ class InlineInputComponent (private var project : Project , private var onSubmit : (value: String ) -> Unit , private var onCancel : () -> Unit ) : JPanel() {
232
+ private val history: CommandHistory ? = project.serviceOrNull<CommandHistory >()
227
233
private val textArea: JTextArea = createTextArea()
228
234
private val submitButton: JLabel = createSubmitButton()
229
235
private val historyButton: JLabel = createHistoryButton()
@@ -320,9 +326,17 @@ class InlineInputComponent(private var onSubmit: (value: String) -> Unit, privat
320
326
return submitButton
321
327
}
322
328
329
+ private fun createHistoryButton (): JLabel {
330
+ val historyButton = IconLabelButton (AllIcons .Actions .SearchWithHistory ) { handleOpenHistory() }
331
+ historyButton.toolTipText = " Select suggested / history Command"
332
+ historyButton.cursor = Cursor .getPredefinedCursor(Cursor .HAND_CURSOR )
333
+ historyButton.border = BorderFactory .createEmptyBorder(4 , 8 , 4 , 8 )
334
+ return historyButton
335
+ }
336
+
323
337
private fun handleOpenHistory () {
324
- val historyItems = listOf ( " History command 1 " , " History command 2 " , " /doc " )
325
- val popup = JBPopupFactory .getInstance().createPopupChooserBuilder(historyItems )
338
+ val commandItems = getCommandList( )
339
+ val popup = JBPopupFactory .getInstance().createPopupChooserBuilder< PickItem >(commandItems )
326
340
.setRenderer(object : DefaultListCellRenderer () {
327
341
override fun getListCellRendererComponent (
328
342
list : JList <* >? ,
@@ -331,19 +345,31 @@ class InlineInputComponent(private var onSubmit: (value: String) -> Unit, privat
331
345
isSelected : Boolean ,
332
346
cellHasFocus : Boolean
333
347
): Component {
348
+ if (value !is PickItem ) {
349
+ return super .getListCellRendererComponent(list, value, index, isSelected, cellHasFocus)
350
+ }
334
351
val panel = JPanel (BorderLayout ())
335
352
panel.preferredSize = Dimension (730 , 20 )
336
-
337
- val label = JLabel (value.toString() )
353
+ val label = JLabel (value.label)
354
+ val desc = JLabel (value.description )
338
355
label.border = BorderFactory .createEmptyBorder(0 , 10 , 0 , 10 )
339
-
340
- val deleteButton = IconLabelButton (AllIcons .Actions .Close ) {
341
- // Handle delete action here in a real implementation
356
+ panel.add(JLabel (value.icon), BorderLayout .WEST )
357
+ val contentPanel = JPanel (BorderLayout ())
358
+ contentPanel.add(label, BorderLayout .WEST )
359
+ desc.foreground = UIUtil .getContextHelpForeground()
360
+ contentPanel.add(desc, BorderLayout .CENTER )
361
+ contentPanel.isOpaque = false
362
+ panel.add(contentPanel, BorderLayout .CENTER )
363
+ if (value.canDelete) {
364
+ val deleteButton = IconLabelButton (AllIcons .Actions .Close ) {
365
+ history?.deleteCommand(value.value)
366
+ handleOpenHistory()
367
+ }
368
+ deleteButton.toolTipText = " Delete this command from history"
369
+ // deleteButton.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
370
+ deleteButton.border = BorderFactory .createEmptyBorder(0 , 5 , 0 , 5 )
371
+ panel.add(deleteButton, BorderLayout .EAST )
342
372
}
343
- deleteButton.border = BorderFactory .createEmptyBorder(0 , 5 , 0 , 5 )
344
-
345
- panel.add(label, BorderLayout .CENTER )
346
- panel.add(deleteButton, BorderLayout .EAST )
347
373
348
374
if (isSelected) {
349
375
panel.background = UIUtil .getListSelectionBackground(true )
@@ -357,18 +383,25 @@ class InlineInputComponent(private var onSubmit: (value: String) -> Unit, privat
357
383
}
358
384
})
359
385
.setItemChosenCallback { selectedValue ->
360
- textArea.text = selectedValue
386
+ textArea.text = selectedValue.value
361
387
}
362
388
.createPopup()
363
389
364
390
popup.showUnderneathOf(this )
365
391
}
366
392
367
- private fun createHistoryButton (): JLabel {
368
- val historyButton = IconLabelButton (AllIcons .Actions .SearchWithHistory ) { handleOpenHistory() }
369
- historyButton.toolTipText = " Select predefined / history Command"
370
- historyButton.cursor = Cursor .getPredefinedCursor(Cursor .HAND_CURSOR )
371
- historyButton.border = BorderFactory .createEmptyBorder(4 , 8 , 4 , 8 )
372
- return historyButton
393
+ private fun getHistoryCommand (): List <InlineEditCommand > {
394
+ return history?.getHistory()?.map {
395
+ InlineEditCommand (it, null )
396
+ } ? : emptyList()
397
+ }
398
+
399
+ private fun getCommandList (): List <PickItem > {
400
+ val location = project.serviceOrNull<InlineChatService >()?.location ? : return emptyList()
401
+ val suggestedItems = getSuggestedCommands(project, location).get()?.map { PickItem (label = it.label, value = it.command, icon = AllIcons .Debugger .ThreadRunning , description = it.command, canDelete = false ) } ? : emptyList()
402
+ val historyItems = getHistoryCommand().filter {historyCommand -> suggestedItems.find { it.value == historyCommand.command.replace(" \n " , " " ) } == null }.map {
403
+ PickItem (label = it.command.replace(" \n " , " " ), value = it.command.replace(" \n " , " " ), icon = AllIcons .Vcs .History , description = null , canDelete = true )
404
+ }
405
+ return suggestedItems + historyItems
373
406
}
374
407
}
0 commit comments