From 6a86d0e233ae2f5dfa0eec31a38ebec697dca5a5 Mon Sep 17 00:00:00 2001 From: Blarc Date: Sat, 30 Aug 2025 17:34:01 +0200 Subject: [PATCH 1/2] save --- .../plugin/settings/prompts/PromptTable.kt | 26 ++-- .../plugin/settings/prompts/PromptTextArea.kt | 140 ++++++++++++++++++ .../messages/AiCommitsBundle.properties | 1 - 3 files changed, 154 insertions(+), 13 deletions(-) create mode 100644 src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/prompts/PromptTextArea.kt diff --git a/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/prompts/PromptTable.kt b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/prompts/PromptTable.kt index 51fc26d..3df1c65 100644 --- a/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/prompts/PromptTable.kt +++ b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/prompts/PromptTable.kt @@ -19,10 +19,7 @@ import com.intellij.openapi.ui.DialogWrapper import com.intellij.openapi.vcs.changes.ChangeListManager import com.intellij.ui.components.JBTextArea import com.intellij.ui.components.JBTextField -import com.intellij.ui.dsl.builder.Align -import com.intellij.ui.dsl.builder.bindText -import com.intellij.ui.dsl.builder.panel -import com.intellij.ui.dsl.builder.text +import com.intellij.ui.dsl.builder.* import com.intellij.ui.table.TableView import com.intellij.util.ui.ListTableModel import kotlinx.coroutines.CoroutineScope @@ -115,7 +112,7 @@ class PromptTable(private val cs: CoroutineScope) { val promptNameTextField = JBTextField() val promptDescriptionTextField = JBTextField() val promptHintTextField = JBTextField() - val promptContentTextArea = JBTextArea() + val promptContentTextArea = PromptTextArea(prompt.content) val promptPreviewTextArea = JBTextArea() var branch: String? = null lateinit var diff: String @@ -127,17 +124,13 @@ class PromptTable(private val cs: CoroutineScope) { title = newPrompt?.let { message("settings.prompt.edit.title") } ?: message("settings.prompt.add.title") setOKButtonText(newPrompt?.let { message("actions.update") } ?: message("actions.add")) - promptContentTextArea.wrapStyleWord = true - promptContentTextArea.lineWrap = true - promptContentTextArea.rows = 5 - promptContentTextArea.autoscrolls = false - if (!prompt.canBeChanged) { isOKActionEnabled = false promptNameTextField.isEditable = false promptDescriptionTextField.isEditable = false promptContentTextArea.isEditable = false } + promptContentTextArea.addOnChangeListener { text -> setPreview(text, promptHintTextField.text) } promptPreviewTextArea.wrapStyleWord = true promptPreviewTextArea.lineWrap = true @@ -181,9 +174,18 @@ class PromptTable(private val cs: CoroutineScope) { } row { scrollCell(promptContentTextArea) - .bindText(prompt::content) + .bind( + componentGet = { it.text }, + componentSet = { component, value -> component.text = value }, + prop = prompt::content.toMutableProperty() + ) + .validationRequestor { validationCallback -> + // The validationCallback is a function that triggers validation + // We need to call it whenever validation should happen (e.g., on text change) + // because this is a custom component + promptContentTextArea.addOnChangeListener { validationCallback() } + } .validationOnApply { notBlank(it.text) } - .onChanged { setPreview(it.text, promptHintTextField.text) } .align(Align.FILL) }.resizableRow() row { diff --git a/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/prompts/PromptTextArea.kt b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/prompts/PromptTextArea.kt new file mode 100644 index 0000000..4e88afb --- /dev/null +++ b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/prompts/PromptTextArea.kt @@ -0,0 +1,140 @@ +package com.github.blarc.ai.commits.intellij.plugin.settings.prompts + +import com.intellij.ui.components.JBScrollPane +import com.intellij.ui.components.JBTextArea +import com.intellij.util.ui.JBUI +import java.awt.* +import java.awt.event.MouseAdapter +import java.awt.event.MouseEvent +import javax.swing.JLabel +import javax.swing.JPanel +import javax.swing.JScrollPane +import javax.swing.event.DocumentEvent +import javax.swing.event.DocumentListener + +class PromptTextArea(initialText: String = "") : JPanel(BorderLayout()) { + + companion object { + val AVAILABLE_VARIABLES = listOf( + "{branch}", + "{diff}", + "{locale}", + "{hint}", + "{taskId}", + "{taskSummary}", + "{taskDescription}", + "{taskTimeSpent}" + ) + } + + val textArea = JBTextArea().apply { + lineWrap = true + wrapStyleWord = true + rows = 5 + autoscrolls = false + text = initialText + } + + init { + // Create the main text area in a scroll pane + val scrollPane = JBScrollPane(textArea).apply { + verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED + horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_NEVER + border = null + } + + // Create the variables panel at the bottom + val variablesPanel = createVariablesPanel() + + add(scrollPane, BorderLayout.CENTER) + add(variablesPanel, BorderLayout.SOUTH) + } + + fun addOnChangeListener(onTextChanged: ((String) -> Unit)) { + textArea.document.addDocumentListener(object : DocumentListener { + override fun insertUpdate(e: DocumentEvent?) = onTextChanged(textArea.text) + override fun removeUpdate(e: DocumentEvent?) = onTextChanged(textArea.text) + override fun changedUpdate(e: DocumentEvent?) = onTextChanged(textArea.text) + }) + } + + private fun createVariablesPanel(): JPanel { + val panel = JPanel(FlowLayout(FlowLayout.LEFT, 5, 5)).apply { + background = textArea.background + } + panel.add(JLabel("Add a variable:")).apply { + // Makes it look like a comment + foreground = JBUI.CurrentTheme.ContextHelp.FOREGROUND + } + AVAILABLE_VARIABLES.forEach { variable -> + panel.add(createVariableLabel(variable)) + } + + return panel + } + + private fun createVariableLabel(variable: String): JLabel { + return object : JLabel(variable) { + override fun paintComponent(g: Graphics) { + val g2 = g.create() as Graphics2D + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) + + // Draw a rounded background + g2.color = background + g2.fillRoundRect(0, 0, width, height, 8, 8) // 8px corner radius + + // Draw the text + super.paintComponent(g2) + g2.dispose() + } + }.apply { + foreground = JBUI.CurrentTheme.Label.foreground() + cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR) + border = JBUI.Borders.empty(4, 8) + background = JBUI.CurrentTheme.ActionButton.hoverBackground() + isOpaque = false // Set to false since we're custom painting the background + + addMouseListener(object : MouseAdapter() { + override fun mouseClicked(e: MouseEvent) { + insertVariableAtCursor(variable) + } + + override fun mouseEntered(e: MouseEvent) { + background = JBUI.CurrentTheme.ActionButton.pressedBackground() + repaint() + } + + override fun mouseExited(e: MouseEvent) { + background = JBUI.CurrentTheme.ActionButton.hoverBackground() + repaint() + } + }) + } + } + + private fun insertVariableAtCursor(variable: String) { + val caretPosition = textArea.caretPosition + val currentText = textArea.text + + val newText = currentText.take(caretPosition) + + variable + + currentText.substring(caretPosition) + + textArea.text = newText + textArea.caretPosition = caretPosition + variable.length + textArea.requestFocus() + } + + // Delegate methods to make it easy to use like a regular text area + var text: String + get() = textArea.text + set(value) { + textArea.text = value + } + + var isEditable: Boolean + get() = textArea.isEditable + set(value) { + textArea.isEditable = value + } +} diff --git a/src/main/resources/messages/AiCommitsBundle.properties b/src/main/resources/messages/AiCommitsBundle.properties index aa58b9e..86b2a40 100644 --- a/src/main/resources/messages/AiCommitsBundle.properties +++ b/src/main/resources/messages/AiCommitsBundle.properties @@ -49,7 +49,6 @@ validation.float=Value should be a float. validation.double=Value should be double. validation.temperature=The temperature should be between 0 and 2. settings.prompt.comment= From f272c1294cad47fe4f281725c9696f471e807eff Mon Sep 17 00:00:00 2001 From: Blarc Date: Sat, 30 Aug 2025 18:19:46 +0200 Subject: [PATCH 2/2] wat --- .../plugin/settings/prompts/PromptTable.kt | 6 +- .../plugin/settings/prompts/PromptTextArea.kt | 80 ++++++++++++++----- 2 files changed, 66 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/prompts/PromptTable.kt b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/prompts/PromptTable.kt index 3df1c65..a5edae0 100644 --- a/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/prompts/PromptTable.kt +++ b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/prompts/PromptTable.kt @@ -185,7 +185,11 @@ class PromptTable(private val cs: CoroutineScope) { // because this is a custom component promptContentTextArea.addOnChangeListener { validationCallback() } } - .validationOnApply { notBlank(it.text) } + .validationOnApply { + val validationInfo = notBlank(it.text) + promptContentTextArea.updateBorder(validationInfo != null) + validationInfo + } .align(Align.FILL) }.resizableRow() row { diff --git a/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/prompts/PromptTextArea.kt b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/prompts/PromptTextArea.kt index 4e88afb..d42d268 100644 --- a/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/prompts/PromptTextArea.kt +++ b/src/main/kotlin/com/github/blarc/ai/commits/intellij/plugin/settings/prompts/PromptTextArea.kt @@ -4,6 +4,8 @@ import com.intellij.ui.components.JBScrollPane import com.intellij.ui.components.JBTextArea import com.intellij.util.ui.JBUI import java.awt.* +import java.awt.event.FocusEvent +import java.awt.event.FocusListener import java.awt.event.MouseAdapter import java.awt.event.MouseEvent import javax.swing.JLabel @@ -35,19 +37,47 @@ class PromptTextArea(initialText: String = "") : JPanel(BorderLayout()) { text = initialText } - init { - // Create the main text area in a scroll pane - val scrollPane = JBScrollPane(textArea).apply { - verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED - horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_NEVER - border = null + var text: String + get() = textArea.text + set(value) { + textArea.text = value + } + + var isEditable: Boolean + get() = textArea.isEditable + set(value) { + textArea.isEditable = value } + private val scrollPane = JBScrollPane(textArea).apply { + verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED + horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_NEVER + border = null + } + + private var isFocused = false + + init { // Create the variables panel at the bottom val variablesPanel = createVariablesPanel() add(scrollPane, BorderLayout.CENTER) add(variablesPanel, BorderLayout.SOUTH) + + addOnChangeListener { _ -> updateBorder() } + + textArea.addFocusListener(object : FocusListener { + override fun focusGained(e: FocusEvent?) { + isFocused = true + updateBorder() + } + + override fun focusLost(e: FocusEvent?) { + isFocused = false + updateBorder() + } + }) + } fun addOnChangeListener(onTextChanged: ((String) -> Unit)) { @@ -58,6 +88,31 @@ class PromptTextArea(initialText: String = "") : JPanel(BorderLayout()) { }) } + fun updateBorder(isError: Boolean = false) { + when { + isError && isFocused -> { + // Redder/thicker border when error and focused + scrollPane.border = JBUI.Borders.customLine( + JBUI.CurrentTheme.Validator.errorBorderColor().darker(), + 3 + ) + } + isError && !isFocused -> { + // Normal error border when error but not focused + scrollPane.border = JBUI.Borders.customLine( + JBUI.CurrentTheme.Validator.errorBorderColor(), + 3 + ) + } + else -> { + // Clear border when no error + scrollPane.border = null + } + } + repaint() + } + + private fun createVariablesPanel(): JPanel { val panel = JPanel(FlowLayout(FlowLayout.LEFT, 5, 5)).apply { background = textArea.background @@ -124,17 +179,4 @@ class PromptTextArea(initialText: String = "") : JPanel(BorderLayout()) { textArea.caretPosition = caretPosition + variable.length textArea.requestFocus() } - - // Delegate methods to make it easy to use like a regular text area - var text: String - get() = textArea.text - set(value) { - textArea.text = value - } - - var isEditable: Boolean - get() = textArea.isEditable - set(value) { - textArea.isEditable = value - } }