Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -181,9 +174,22 @@ class PromptTable(private val cs: CoroutineScope) {
}
row {
scrollCell(promptContentTextArea)
.bindText(prompt::content)
.validationOnApply { notBlank(it.text) }
.onChanged { setPreview(it.text, promptHintTextField.text) }
.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 {
val validationInfo = notBlank(it.text)
promptContentTextArea.updateBorder(validationInfo != null)
validationInfo
}
.align(Align.FILL)
}.resizableRow()
row {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
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.FocusEvent
import java.awt.event.FocusListener
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
}

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)) {
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)
})
}

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
}
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()
}
}
1 change: 0 additions & 1 deletion src/main/resources/messages/AiCommitsBundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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=<ul>\
<li>Customize your prompt with variables: {locale}, {diff}, {branch}, {hint}, {taskId}, {taskSummary}, {taskDescription} and {taskTimeSpent}.</li>\
<li>Include a hint by using a dollar sign prefix like this: {Here is a hint: $hint}. This adds the hint text inside the curly brackets only if a hint is provided.</li>\
<li>Note: The prompt preview displays only the first 10,000 characters.</li>\
</ul>
Expand Down
Loading