mirror of
https://github.com/carlrobertoh/ProxyAI.git
synced 2026-05-19 16:28:46 +00:00
feat: copy-paste placeholder for prompt text field
This commit is contained in:
parent
14f16465d3
commit
08804c4c6e
4 changed files with 355 additions and 12 deletions
|
|
@ -6,34 +6,54 @@ import com.intellij.codeInsight.lookup.LookupListener
|
|||
import com.intellij.codeInsight.lookup.impl.LookupImpl
|
||||
import com.intellij.ide.IdeEventQueue
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.actionSystem.IdeActions
|
||||
import com.intellij.openapi.application.runInEdt
|
||||
import com.intellij.openapi.application.runUndoTransparentWriteAction
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.openapi.editor.Caret
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.actionSystem.EditorActionHandler
|
||||
import com.intellij.openapi.editor.actionSystem.EditorActionManager
|
||||
import com.intellij.openapi.editor.colors.EditorColorsManager
|
||||
import com.intellij.openapi.editor.event.DocumentEvent
|
||||
import com.intellij.openapi.editor.event.DocumentListener
|
||||
import com.intellij.openapi.editor.ex.EditorEx
|
||||
import com.intellij.openapi.editor.markup.EffectType
|
||||
import com.intellij.openapi.editor.markup.HighlighterLayer
|
||||
import com.intellij.openapi.editor.markup.HighlighterTargetArea
|
||||
import com.intellij.openapi.editor.markup.RangeHighlighter
|
||||
import com.intellij.openapi.editor.markup.TextAttributes
|
||||
import com.intellij.openapi.fileTypes.FileTypes
|
||||
import com.intellij.openapi.ide.CopyPasteManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.wm.ToolWindowManager
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.wm.ToolWindow
|
||||
import com.intellij.openapi.wm.ToolWindowManager
|
||||
import com.intellij.ui.EditorTextField
|
||||
import com.intellij.ui.JBColor
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import ee.carlrobert.codegpt.CodeGPTBundle
|
||||
import ee.carlrobert.codegpt.CodeGPTKeys.IS_PROMPT_TEXT_FIELD_DOCUMENT
|
||||
import ee.carlrobert.codegpt.settings.service.FeatureType
|
||||
import ee.carlrobert.codegpt.ui.dnd.FileDragAndDrop
|
||||
import ee.carlrobert.codegpt.ui.textarea.header.tag.TagManager
|
||||
import ee.carlrobert.codegpt.ui.textarea.lookup.DynamicLookupGroupItem
|
||||
import ee.carlrobert.codegpt.ui.textarea.lookup.LookupActionItem
|
||||
import ee.carlrobert.codegpt.ui.textarea.lookup.LookupGroupItem
|
||||
import ee.carlrobert.codegpt.ui.dnd.FileDragAndDrop
|
||||
import kotlinx.coroutines.*
|
||||
import java.awt.Cursor
|
||||
import java.awt.Dimension
|
||||
import java.awt.datatransfer.DataFlavor
|
||||
import java.awt.event.MouseAdapter
|
||||
import java.awt.event.MouseEvent
|
||||
import java.awt.event.MouseMotionAdapter
|
||||
import java.util.*
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.TransferHandler
|
||||
|
||||
class PromptTextField(
|
||||
private val project: Project,
|
||||
|
|
@ -43,17 +63,16 @@ class PromptTextField(
|
|||
private val onLookupAdded: (LookupActionItem) -> Unit,
|
||||
private val onSubmit: (String) -> Unit,
|
||||
private val onFilesDropped: (List<VirtualFile>) -> Unit = {},
|
||||
private val featureType: FeatureType? = null
|
||||
featureType: FeatureType? = null
|
||||
) : EditorTextField(project, FileTypes.PLAIN_TEXT), Disposable {
|
||||
|
||||
companion object {
|
||||
private val logger = thisLogger()
|
||||
}
|
||||
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||
private val lookupManager = PromptTextFieldLookupManager(project, onLookupAdded)
|
||||
private val searchManager = SearchManager(project, tagManager, featureType)
|
||||
|
||||
private var mouseClickListener: MouseAdapter? = null
|
||||
private var mouseMotionListener: MouseMotionAdapter? = null
|
||||
|
||||
private var showSuggestionsJob: Job? = null
|
||||
private var searchState = SearchState()
|
||||
private var lastSearchResults: List<LookupActionItem>? = null
|
||||
|
|
@ -64,9 +83,11 @@ class PromptTextField(
|
|||
init {
|
||||
isOneLineMode = false
|
||||
IS_PROMPT_TEXT_FIELD_DOCUMENT.set(document, true)
|
||||
document.putUserData(PROMPT_FIELD_KEY, this)
|
||||
setPlaceholder(CodeGPTBundle.get("toolwindow.chat.textArea.emptyText"))
|
||||
|
||||
|
||||
putClientProperty(UIUtil.HIDE_EDITOR_FROM_DATA_CONTEXT_PROPERTY, true)
|
||||
installPasteHandler()
|
||||
}
|
||||
|
||||
override fun onEditorAdded(editor: Editor) {
|
||||
|
|
@ -77,18 +98,77 @@ class PromptTextField(
|
|||
return@PromptTextFieldEventDispatcher
|
||||
}
|
||||
|
||||
onSubmit(text)
|
||||
onSubmit(getExpandedText())
|
||||
event.consume()
|
||||
},
|
||||
this
|
||||
)
|
||||
val highlightTarget = (this.parent as? JComponent) ?: this
|
||||
val highlightTarget = (parent as? JComponent) ?: this
|
||||
FileDragAndDrop.install(editor.contentComponent, highlightTarget) { onFilesDropped(it) }
|
||||
|
||||
val contentComponent = editor.contentComponent
|
||||
val previousHandler = contentComponent.transferHandler
|
||||
contentComponent.transferHandler = object : TransferHandler() {
|
||||
override fun canImport(support: TransferSupport): Boolean {
|
||||
return support.isDataFlavorSupported(DataFlavor.stringFlavor) ||
|
||||
(previousHandler?.canImport(support) == true)
|
||||
}
|
||||
|
||||
override fun importData(support: TransferSupport): Boolean {
|
||||
if (!support.isDrop && support.isDataFlavorSupported(DataFlavor.stringFlavor)) {
|
||||
val pasted = try {
|
||||
support.transferable.getTransferData(DataFlavor.stringFlavor) as? String
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
if (!pasted.isNullOrEmpty()) {
|
||||
insertPlaceholderFor(pasted)
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
return previousHandler?.importData(support) == true
|
||||
}
|
||||
}
|
||||
|
||||
val clickListener = object : MouseAdapter() {
|
||||
override fun mouseClicked(e: MouseEvent) {
|
||||
val editor = this@PromptTextField.editor as? EditorEx ?: return
|
||||
val offset = editor.logicalPositionToOffset(editor.xyToLogicalPosition(e.point))
|
||||
val placeholder = findPlaceholderAtOffset(offset) ?: return
|
||||
val start = placeholder.highlighter.startOffset
|
||||
val end = placeholder.highlighter.endOffset
|
||||
runUndoTransparentWriteAction {
|
||||
editor.document.replaceString(start, end, placeholder.content)
|
||||
editor.caretModel.moveToOffset(start + placeholder.content.length)
|
||||
}
|
||||
editor.markupModel.removeHighlighter(placeholder.highlighter)
|
||||
placeholders.remove(placeholder)
|
||||
}
|
||||
}
|
||||
mouseClickListener = clickListener
|
||||
editor.contentComponent.addMouseListener(clickListener)
|
||||
|
||||
val motionListener = object : MouseMotionAdapter() {
|
||||
override fun mouseMoved(e: MouseEvent) {
|
||||
val editor = this@PromptTextField.editor as? EditorEx ?: return
|
||||
val offset = editor.logicalPositionToOffset(editor.xyToLogicalPosition(e.point))
|
||||
val inside = findPlaceholderAtOffset(offset) != null
|
||||
val component = editor.contentComponent
|
||||
component.cursor =
|
||||
if (inside) Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
|
||||
else Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)
|
||||
component.toolTipText = if (inside) "Click to edit" else null
|
||||
}
|
||||
}
|
||||
mouseMotionListener = motionListener
|
||||
editor.contentComponent.addMouseMotionListener(motionListener)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
runInEdt {
|
||||
text = ""
|
||||
clearPlaceholders()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -223,6 +303,158 @@ class PromptTextField(
|
|||
override fun dispose() {
|
||||
showSuggestionsJob?.cancel()
|
||||
lastSearchResults = null
|
||||
clearPlaceholders()
|
||||
val ed = this.editor
|
||||
mouseClickListener?.let { l -> ed?.contentComponent?.removeMouseListener(l) }
|
||||
mouseMotionListener?.let { l -> ed?.contentComponent?.removeMouseMotionListener(l) }
|
||||
}
|
||||
|
||||
fun insertPlaceholderFor(pastedText: String) {
|
||||
val editor = editor as? EditorEx ?: return
|
||||
if (pastedText.isEmpty()) return
|
||||
|
||||
if (pastedText.length <= PromptTextFieldConstants.PASTE_PLACEHOLDER_MIN_LENGTH) {
|
||||
runUndoTransparentWriteAction { replaceSelectionOrInsert(editor, pastedText) }
|
||||
return
|
||||
}
|
||||
|
||||
val placeholderLabel = " Pasted Content ${pastedText.length} chars "
|
||||
runUndoTransparentWriteAction {
|
||||
val (start, end) = replaceSelectionOrInsert(editor, placeholderLabel)
|
||||
addPastePlaceholder(editor, start, end, placeholderLabel, pastedText)
|
||||
}
|
||||
}
|
||||
|
||||
private fun replaceSelectionOrInsert(editor: EditorEx, text: String): Pair<Int, Int> {
|
||||
val document = editor.document
|
||||
val caret = editor.caretModel
|
||||
val selectionModel = editor.selectionModel
|
||||
val start: Int
|
||||
if (selectionModel.hasSelection()) {
|
||||
val selectionStart = selectionModel.selectionStart
|
||||
val selectionEnd = selectionModel.selectionEnd
|
||||
document.replaceString(selectionStart, selectionEnd, text)
|
||||
selectionModel.removeSelection()
|
||||
start = selectionStart
|
||||
} else {
|
||||
start = caret.offset
|
||||
document.insertString(start, text)
|
||||
}
|
||||
val end = start + text.length
|
||||
caret.moveToOffset(end)
|
||||
return start to end
|
||||
}
|
||||
|
||||
private fun addPastePlaceholder(
|
||||
editor: EditorEx,
|
||||
start: Int,
|
||||
end: Int,
|
||||
label: String,
|
||||
content: String
|
||||
) {
|
||||
val attrs = TextAttributes().apply {
|
||||
backgroundColor = JBColor(0xF2F4F7, 0x2B2D30)
|
||||
effectType = EffectType.ROUNDED_BOX
|
||||
effectColor = JBColor(0xC4C9D0, 0x44484F)
|
||||
}
|
||||
val highlighter = editor.markupModel.addRangeHighlighter(
|
||||
start,
|
||||
end,
|
||||
HighlighterLayer.ADDITIONAL_SYNTAX,
|
||||
attrs,
|
||||
HighlighterTargetArea.EXACT_RANGE
|
||||
)
|
||||
highlighter.isGreedyToLeft = false
|
||||
highlighter.isGreedyToRight = false
|
||||
placeholders.add(PastePlaceholder(highlighter, label, content))
|
||||
}
|
||||
|
||||
private data class PastePlaceholder(
|
||||
val highlighter: RangeHighlighter,
|
||||
val label: String,
|
||||
var content: String
|
||||
)
|
||||
|
||||
private val placeholders: MutableList<PastePlaceholder> = mutableListOf()
|
||||
|
||||
fun getExpandedText(): String {
|
||||
val text = document.text
|
||||
if (placeholders.isEmpty()) return text
|
||||
val validPlaceholders =
|
||||
placeholders.filter { it.highlighter.isValid }.sortedBy { it.highlighter.startOffset }
|
||||
if (validPlaceholders.isEmpty()) return text
|
||||
val result = StringBuilder()
|
||||
var cursor = 0
|
||||
for (placeholder in validPlaceholders) {
|
||||
val start = placeholder.highlighter.startOffset
|
||||
val end = placeholder.highlighter.endOffset
|
||||
if (start < cursor || start > text.length || end > text.length) continue
|
||||
if (cursor < start) result.append(text, cursor, start)
|
||||
val span = text.substring(start, end)
|
||||
if (span == placeholder.label) result.append(placeholder.content) else result.append(
|
||||
span
|
||||
)
|
||||
cursor = end
|
||||
}
|
||||
if (cursor < text.length) result.append(text.substring(cursor))
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
private fun findPlaceholderAtOffset(offset: Int): PastePlaceholder? {
|
||||
return placeholders.firstOrNull { ph ->
|
||||
ph.highlighter.isValid && offset >= ph.highlighter.startOffset && offset < ph.highlighter.endOffset
|
||||
}
|
||||
}
|
||||
|
||||
private fun findPlaceholdersIntersecting(start: Int, end: Int): List<PastePlaceholder> {
|
||||
return placeholders.filter { ph ->
|
||||
ph.highlighter.isValid && ph.highlighter.startOffset < end && ph.highlighter.endOffset > start
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearPlaceholders() {
|
||||
val ed = this.editor as? EditorEx ?: return
|
||||
placeholders.forEach { ph -> ed.markupModel.removeHighlighter(ph.highlighter) }
|
||||
placeholders.clear()
|
||||
}
|
||||
|
||||
fun handlePlaceholderDelete(isBackspace: Boolean): Boolean {
|
||||
val editor = editor as? EditorEx ?: return false
|
||||
val document = editor.document
|
||||
val caret = editor.caretModel
|
||||
val selectionModel = editor.selectionModel
|
||||
|
||||
if (selectionModel.hasSelection()) {
|
||||
val selStart = selectionModel.selectionStart
|
||||
val selEnd = selectionModel.selectionEnd
|
||||
val intersecting = findPlaceholdersIntersecting(selStart, selEnd)
|
||||
if (intersecting.isNotEmpty()) {
|
||||
val newStart = minOf(selStart, intersecting.minOf { it.highlighter.startOffset })
|
||||
val newEnd = maxOf(selEnd, intersecting.maxOf { it.highlighter.endOffset })
|
||||
runUndoTransparentWriteAction {
|
||||
document.deleteString(newStart, newEnd)
|
||||
caret.moveToOffset(newStart)
|
||||
}
|
||||
intersecting.forEach { ph -> editor.markupModel.removeHighlighter(ph.highlighter) }
|
||||
placeholders.removeAll(intersecting.toSet())
|
||||
selectionModel.removeSelection()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
val offset = caret.offset
|
||||
val target = if (isBackspace) (if (offset > 0) offset - 1 else offset) else offset
|
||||
val placeholder = findPlaceholderAtOffset(target) ?: return false
|
||||
val start = placeholder.highlighter.startOffset
|
||||
val end = placeholder.highlighter.endOffset
|
||||
runUndoTransparentWriteAction {
|
||||
document.deleteString(start, end)
|
||||
caret.moveToOffset(start)
|
||||
}
|
||||
editor.markupModel.removeHighlighter(placeholder.highlighter)
|
||||
placeholders.remove(placeholder)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun setupDocumentListener(editor: EditorEx) {
|
||||
|
|
@ -236,6 +468,7 @@ class PromptTextField(
|
|||
}
|
||||
|
||||
private fun handleDocumentChange(event: DocumentEvent) {
|
||||
prunePlaceholders(event)
|
||||
val text = event.document.text
|
||||
val caretOffset = event.offset + event.newLength
|
||||
|
||||
|
|
@ -245,6 +478,42 @@ class PromptTextField(
|
|||
}
|
||||
}
|
||||
|
||||
private fun prunePlaceholders(event: DocumentEvent) {
|
||||
if (placeholders.isEmpty()) return
|
||||
|
||||
val editor = editor as? EditorEx ?: return
|
||||
val document = event.document
|
||||
val textLength = document.textLength
|
||||
val placeholdersToRemove = mutableListOf<PastePlaceholder>()
|
||||
for (placeholder in placeholders) {
|
||||
val highlighter = placeholder.highlighter
|
||||
if (!highlighter.isValid) {
|
||||
placeholdersToRemove.add(placeholder)
|
||||
continue
|
||||
}
|
||||
val start = highlighter.startOffset
|
||||
val end = highlighter.endOffset
|
||||
if (start < 0 || end > textLength || start >= end) {
|
||||
placeholdersToRemove.add(placeholder)
|
||||
continue
|
||||
}
|
||||
val span = try {
|
||||
document.charsSequence.subSequence(start, end).toString()
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
if (span == null || span != placeholder.label) {
|
||||
placeholdersToRemove.add(placeholder)
|
||||
}
|
||||
}
|
||||
if (placeholdersToRemove.isNotEmpty()) {
|
||||
placeholdersToRemove.forEach { placeholder ->
|
||||
editor.markupModel.removeHighlighter(placeholder.highlighter)
|
||||
}
|
||||
placeholders.removeAll(placeholdersToRemove.toSet())
|
||||
}
|
||||
}
|
||||
|
||||
private fun isAtSymbolTyped(event: DocumentEvent): Boolean {
|
||||
return PromptTextFieldConstants.AT_SYMBOL == event.newFragment.toString()
|
||||
}
|
||||
|
|
@ -385,4 +654,48 @@ class PromptTextField(
|
|||
return toolWindow.component.visibleRect?.height
|
||||
?: PromptTextFieldConstants.DEFAULT_TOOL_WINDOW_HEIGHT
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val logger = thisLogger()
|
||||
private val PROMPT_FIELD_KEY: Key<PromptTextField> =
|
||||
Key.create("codegpt.promptTextField.instance")
|
||||
|
||||
private var pasteHandlerInstalled = false
|
||||
private var originalPasteHandler: EditorActionHandler? = null
|
||||
|
||||
private fun installPasteHandler() {
|
||||
if (pasteHandlerInstalled) return
|
||||
synchronized(PromptTextField::class.java) {
|
||||
if (pasteHandlerInstalled) return
|
||||
val manager = EditorActionManager.getInstance()
|
||||
val existing = manager.getActionHandler(IdeActions.ACTION_EDITOR_PASTE)
|
||||
originalPasteHandler = existing
|
||||
manager.setActionHandler(
|
||||
IdeActions.ACTION_EDITOR_PASTE,
|
||||
object : EditorActionHandler() {
|
||||
override fun doExecute(
|
||||
editor: Editor,
|
||||
caret: Caret?,
|
||||
dataContext: DataContext
|
||||
) {
|
||||
val field = editor.document.getUserData(PROMPT_FIELD_KEY)
|
||||
if (field != null) {
|
||||
val pasted = try {
|
||||
CopyPasteManager.getInstance()
|
||||
.getContents(DataFlavor.stringFlavor) as? String
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
if (!pasted.isNullOrEmpty()) {
|
||||
field.insertPlaceholderFor(pasted)
|
||||
return
|
||||
}
|
||||
}
|
||||
originalPasteHandler?.execute(editor, caret, dataContext)
|
||||
}
|
||||
})
|
||||
pasteHandlerInstalled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ object PromptTextFieldConstants {
|
|||
const val BORDER_PADDING = 4
|
||||
const val BORDER_SIDE_PADDING = 8
|
||||
const val HEIGHT_PADDING = 8
|
||||
const val PASTE_PLACEHOLDER_MIN_LENGTH = 250
|
||||
|
||||
val DEFAULT_GROUP_NAMES = listOf(
|
||||
"files", "file", "f",
|
||||
|
|
@ -29,4 +30,4 @@ object PromptTextFieldConstants {
|
|||
|
||||
const val LIGHT_THEME_COLOR = 0x00627A
|
||||
const val DARK_THEME_COLOR = 0xCC7832
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import java.awt.event.InputEvent
|
|||
import java.awt.event.KeyEvent
|
||||
import java.awt.event.MouseEvent
|
||||
import java.util.*
|
||||
import com.intellij.openapi.ide.CopyPasteManager
|
||||
import java.awt.datatransfer.DataFlavor
|
||||
|
||||
class PromptTextFieldEventDispatcher(
|
||||
private val dispatcherId: UUID,
|
||||
|
|
@ -32,6 +34,13 @@ class PromptTextFieldEventDispatcher(
|
|||
|
||||
KeyEvent.VK_DELETE -> handleDelete(e)
|
||||
KeyEvent.VK_A -> if (e.isControlDown || e.isMetaDown) handleSelectAll(e)
|
||||
KeyEvent.VK_V -> {
|
||||
if (e.isControlDown || e.isMetaDown) {
|
||||
if (handlePaste(e)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyEvent.VK_ENTER -> {
|
||||
if (e.isShiftDown) {
|
||||
handleShiftEnter(e)
|
||||
|
|
@ -94,6 +103,10 @@ class PromptTextFieldEventDispatcher(
|
|||
val parent = findParent()
|
||||
if (parent is PromptTextField) {
|
||||
parent.editor?.let { editor ->
|
||||
if (parent.handlePlaceholderDelete(isBackspace = false)) {
|
||||
e.consume()
|
||||
return
|
||||
}
|
||||
runUndoTransparentWriteAction {
|
||||
val document = editor.document
|
||||
val caretModel = editor.caretModel
|
||||
|
|
@ -116,10 +129,26 @@ class PromptTextFieldEventDispatcher(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handlePaste(e: KeyEvent): Boolean {
|
||||
val parent = findParent()
|
||||
if (parent is PromptTextField) {
|
||||
val clipText: String? = try { CopyPasteManager.getInstance().getContents(DataFlavor.stringFlavor) as? String } catch (_: Exception) { null }
|
||||
if (clipText.isNullOrEmpty()) return false
|
||||
parent.insertPlaceholderFor(clipText)
|
||||
e.consume()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun handleBackspace(e: KeyEvent): Boolean {
|
||||
val parent = findParent()
|
||||
if (parent is PromptTextField) {
|
||||
parent.editor?.let { editor ->
|
||||
if (parent.handlePlaceholderDelete(isBackspace = true)) {
|
||||
e.consume()
|
||||
return true
|
||||
}
|
||||
val selectionModel = editor.selectionModel
|
||||
if (selectionModel.hasSelection()) {
|
||||
runUndoTransparentWriteAction {
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ class UserInputPanel @JvmOverloads constructor(
|
|||
IconUtil.scale(Icons.Send, null, 0.85f)
|
||||
) {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
handleSubmit(promptTextField.text)
|
||||
handleSubmit(promptTextField.getExpandedText())
|
||||
}
|
||||
},
|
||||
"SUBMIT"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue