Skip to content

Commit

Permalink
feat(inline-chat): add inline chat service and integrate with gutter …
Browse files Browse the repository at this point in the history
…icon #157

Introduce `ShireInlineChatService` to handle inline chat functionality within the editor. Integrate the service with the gutter icon click
  • Loading branch information
phodal committed Dec 31, 2024
1 parent bf85b08 commit 933e8d7
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package com.phodal.shire.marketplace
package com.phodal.shire.inline

import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorFactory
import com.intellij.openapi.editor.colors.TextAttributesKey
import com.intellij.openapi.editor.event.EditorFactoryEvent
import com.intellij.openapi.editor.event.EditorFactoryListener
import com.intellij.openapi.editor.event.SelectionEvent
Expand Down Expand Up @@ -59,17 +58,16 @@ class EditorGutterHandler {
}

fun addGutterIcon(editor: Editor, line: Int) {
val it: GutterIconData? = gutterIcons.get(editor)
if (it != null) {
INSTANCE.removeGutterIcon(editor, it.highlighter)
val iconData: GutterIconData? = gutterIcons[editor]
if (iconData != null) {
INSTANCE.removeGutterIcon(editor, iconData.highlighter)
}


FileDocumentManager.getInstance().getFile(editor.document) ?: return

val highlighter = editor.markupModel.addLineHighlighter(null, line, 0)
highlighter.gutterIconRenderer = ShireGutterIconRenderer(line, onClick = {
ShirelangNotifications.info(editor.project!!, "Line $line clicked")
ShireInlineChatService.getInstance().showInlineChat(editor)
})

gutterIcons[editor] = GutterIconData(line, highlighter)
Expand Down
130 changes: 130 additions & 0 deletions src/main/kotlin/com/phodal/shire/inline/ShireInlineChatService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package com.phodal.shire.inline

import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.components.Service
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorCustomElementRenderer
import com.intellij.openapi.editor.Inlay
import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.wm.IdeFocusManager
import com.intellij.ui.components.JBTextArea
import com.intellij.util.ui.JBUI
import java.awt.*
import java.awt.geom.Rectangle2D
import javax.swing.BorderFactory
import javax.swing.JPanel
import javax.swing.LayoutFocusTraversalPolicy

@Service(Service.Level.APP)
class ShireInlineChatService : Disposable {
fun showInlineChat(editor: Editor) {
runInEdt {
if (editor.component !is ShireInlineChatPanel) {
val panel = ShireInlineChatPanel(editor)
editor.contentComponent.add(panel)
panel.setInlineContainer(editor.contentComponent)
val offset = if (editor.selectionModel.hasSelection()) {
editor.selectionModel.selectionStart
} else {
editor.caretModel.primaryCaret.offset
}

panel.createInlay(offset)
IdeFocusManager.getInstance(editor.project).requestFocus(panel.inputPanel.getInputComponent(), true)
}
}
}

override fun dispose() {
///
}

companion object {
fun getInstance(): ShireInlineChatService {
return ApplicationManager.getApplication().getService(ShireInlineChatService::class.java)
}
}
}

class ShireInlineChatPanel(private val editor: Editor) : JPanel(GridBagLayout()), EditorCustomElementRenderer,
Disposable {
private var inlay: Inlay<*>? = null
val inputPanel = ShireInlineChatInputPanel()
private var centerPanel: JPanel = JPanel(BorderLayout())
private var container: Container? = null

init {
border = JBUI.Borders.empty()
isOpaque = false
cursor = Cursor.getPredefinedCursor(0)

val jPanel = JPanel(BorderLayout())
jPanel.addMouseListener(object : java.awt.event.MouseAdapter() {
override fun mouseClicked(e: java.awt.event.MouseEvent) {
IdeFocusManager.getInstance(editor.project).requestFocus(inputPanel.getInputComponent(), true)
}
})
this.centerPanel = jPanel

val c = GridBagConstraints()
c.gridx = 0
c.gridy = 1
c.weightx = 1.0
c.anchor = GridBagConstraints.NORTHWEST
c.fill = GridBagConstraints.HORIZONTAL
add(this.centerPanel, c)

c.gridy = 0
c.weighty = 0.0
c.fill = GridBagConstraints.HORIZONTAL
add(inputPanel, c)

isFocusCycleRoot = true
focusTraversalPolicy = LayoutFocusTraversalPolicy()
}

override fun calcWidthInPixels(p0: Inlay<*>): Int = size.width

override fun calcHeightInPixels(p0: Inlay<*>): Int = size.height

fun createInlay(offset: Int) {
inlay = editor.inlayModel.addBlockElement(offset, false, true, 1, this)
}

fun setInlineContainer(container: Container) {
this.container = container
}

override fun paint(inlay: Inlay<*>, g: Graphics2D, targetRegion: Rectangle2D, textAttributes: TextAttributes) {
bounds = inlay.bounds ?: return
revalidate()
repaint()
}

override fun dispose() {
inlay?.dispose()
}
}

class ShireInlineChatInputPanel : JPanel(GridBagLayout()) {
private val textArea: JBTextArea

init {
layout = BorderLayout()
textArea = JBTextArea().apply {
isOpaque = false
isFocusable = true
lineWrap = true
wrapStyleWord = true
border = BorderFactory.createEmptyBorder(8, 5, 8, 5)
}

add(textArea)
}

fun getInputComponent(): JBTextArea {
return textArea
}
}
1 change: 1 addition & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
</content>

<extensions defaultExtensionNs="com.intellij">

<applicationConfigurable parentId="tools" instance="com.phodal.shire.settings.ShireLlmSettingsConfigurable"
id="shireLlmSettingsConfigurable"
displayName="Shire"/>
Expand Down

0 comments on commit 933e8d7

Please sign in to comment.