feat: option to disable open file chat tag and other minor improvements

This commit is contained in:
Carl-Robert Linnupuu 2025-01-23 15:10:23 +00:00
parent 6044f88697
commit f7c49f5f90
7 changed files with 133 additions and 155 deletions

View file

@ -308,9 +308,7 @@ public class ChatToolWindowTabPanel implements Disposable {
}
private Unit handleSubmit(String text, List<? extends HeaderTagDetails> appliedTags) {
var messageBuilder = new MessageBuilder(project, text)
.withSelectedEditorContent()
.withInlays(appliedTags);
var messageBuilder = new MessageBuilder(project, text).withInlays(appliedTags);
List<ReferencedFile> referencedFiles = getReferencedFiles();
if (!referencedFiles.isEmpty()) {

View file

@ -1,25 +1,15 @@
package ee.carlrobert.codegpt.toolwindow.chat
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import ee.carlrobert.codegpt.ReferencedFile
import ee.carlrobert.codegpt.conversations.message.Message
import ee.carlrobert.codegpt.ui.textarea.TagProcessorFactory
import ee.carlrobert.codegpt.ui.textarea.header.HeaderTagDetails
import ee.carlrobert.codegpt.util.EditorUtil.getSelectedEditor
class MessageBuilder(private val project: Project, private val text: String) {
private val message = Message("")
private var editorContent: String = ""
private var inlayContent: String = ""
fun withSelectedEditorContent(): MessageBuilder {
getSelectedEditor(project)?.let { editor ->
editorContent = processEditorSelectedText(editor)
}
return this
}
fun withInlays(appliedTags: List<HeaderTagDetails>): MessageBuilder {
if (appliedTags.isNotEmpty()) {
inlayContent = processTags(message, appliedTags)
@ -42,10 +32,6 @@ class MessageBuilder(private val project: Project, private val text: String) {
fun build(): Message {
message.prompt = buildString {
append(text)
if (editorContent.isNotBlank()) {
append("\n\n")
append(editorContent)
}
if (inlayContent.isNotBlank()) {
append("\n")
append(inlayContent)
@ -54,17 +40,6 @@ class MessageBuilder(private val project: Project, private val text: String) {
return message
}
private fun processEditorSelectedText(editor: Editor): String {
return editor.selectionModel.selectedText?.let { selectedText ->
if (selectedText.isBlank()) return ""
val fileExtension = editor.virtualFile?.name?.substringAfterLast('.', "") ?: ""
editor.selectionModel.removeSelection()
"```$fileExtension\n$selectedText\n```"
} ?: ""
}
private fun processTags(
message: Message,
tags: List<HeaderTagDetails>

View file

@ -69,7 +69,6 @@ class PromptTextField(
}
override fun dispose() {
clear()
suggestionsPopupManager.hidePopup()
}

View file

@ -7,9 +7,7 @@ import com.intellij.openapi.vfs.VirtualFile
import ee.carlrobert.codegpt.EncodingManager
import ee.carlrobert.codegpt.conversations.message.Message
import ee.carlrobert.codegpt.ui.textarea.header.*
import ee.carlrobert.codegpt.util.EditorUtil
import ee.carlrobert.codegpt.util.GitUtil
import ee.carlrobert.codegpt.util.file.FileUtil.getFileExtension
import git4idea.GitCommit
object TagProcessorFactory {
@ -17,7 +15,7 @@ object TagProcessorFactory {
fun getProcessor(project: Project, tagDetails: HeaderTagDetails): TagProcessor {
return when (tagDetails) {
is FileTagDetails -> FileTagProcessor()
is SelectionTagDetails -> SelectionTagProcessor(project)
is SelectionTagDetails -> SelectionTagProcessor()
is DocumentationTagDetails -> DocumentationTagProcessor()
is PersonaTagDetails -> PersonaTagProcessor()
is FolderTagDetails -> FolderTagProcessor()
@ -45,23 +43,25 @@ class FileTagProcessor : TagProcessor {
}
}
class SelectionTagProcessor(private val project: Project) : TagProcessor {
class SelectionTagProcessor : TagProcessor {
override fun process(
message: Message,
tagDetails: HeaderTagDetails,
promptBuilder: StringBuilder
) {
if (tagDetails !is SelectionTagDetails) {
val selectionModel = (tagDetails as? SelectionTagDetails)?.selectionModel ?: return
if (!selectionModel.hasSelection() || tagDetails.virtualFile == null) {
return
}
EditorUtil.getSelectedEditor(project)?.let { selectedEditor ->
val fileExtension = getFileExtension(selectedEditor.virtualFile.name)
promptBuilder
.append("\n```$fileExtension\n")
.append(tagDetails.selectedText)
.append("\n```\n")
}
promptBuilder
.append("\n```${tagDetails.virtualFile?.extension}\n")
.append(selectionModel.selectedText)
.append("\n```\n")
tagDetails.virtualFile = null
tagDetails.selectionModel = null
selectionModel.removeSelection()
}
}

View file

@ -116,7 +116,7 @@ class UserInputPanel(
}
fun addSelection(editorFile: VirtualFile, selectionModel: SelectionModel) {
addTag(SelectionTagDetails(editorFile, selectionModel, selectionModel.selectedText))
addTag(SelectionTagDetails(editorFile, selectionModel))
promptTextField.requestFocusInWindow()
selectionModel.removeSelection()
}

View file

@ -1,8 +1,8 @@
package ee.carlrobert.codegpt.ui.textarea.header
import com.intellij.icons.AllIcons
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.components.service
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.SelectionModel
import com.intellij.openapi.editor.colors.EditorColorsManager
import com.intellij.openapi.fileEditor.FileEditorManager
@ -15,7 +15,8 @@ import com.jetbrains.rd.util.UUID
import ee.carlrobert.codegpt.Icons
import ee.carlrobert.codegpt.settings.prompts.PersonaDetails
import ee.carlrobert.codegpt.ui.DocumentationDetails
import ee.carlrobert.codegpt.util.EditorUtil
import ee.carlrobert.codegpt.util.EditorUtil.getSelectedEditor
import ee.carlrobert.codegpt.util.EditorUtil.getSelectedEditorFile
import java.awt.Cursor
import java.awt.FlowLayout
import java.awt.Graphics
@ -50,7 +51,6 @@ data class FileTagDetails(var virtualFile: VirtualFile, override var selected: B
data class SelectionTagDetails(
var virtualFile: VirtualFile?,
var selectionModel: SelectionModel?,
var selectedText: String?
) :
HeaderTagDetails(
"${virtualFile?.name} (${selectionModel?.selectionStartPosition?.line}:${selectionModel?.selectionEndPosition?.line})",
@ -74,7 +74,9 @@ data class FolderTagDetails(var folder: VirtualFile) :
class WebTagDetails : HeaderTagDetails("Web", AllIcons.General.Web)
abstract class HeaderTag(val tagDetails: HeaderTagDetails, private val selectable: Boolean = true) :
class EmptyTagDetails : HeaderTagDetails("")
abstract class HeaderTag(var tagDetails: HeaderTagDetails, private val selectable: Boolean = true) :
JPanel() {
val id: UUID = tagDetails.id
@ -91,23 +93,22 @@ abstract class HeaderTag(val tagDetails: HeaderTagDetails, private val selectabl
setupUI()
}
abstract fun onClose()
abstract fun onSelect(tagDetails: HeaderTagDetails)
fun updateLabel(text: String, icon: Icon) {
abstract fun onClose()
fun update(text: String, icon: Icon? = null) {
label.text = text
label.icon = IconUtil.scale(icon, null, 0.65f)
}
open fun select() {
onSelect(tagDetails)
if (!tagDetails.selected) {
tagDetails.selected = true
closeButton.isVisible = true
label.foreground = service<EditorColorsManager>().globalScheme.defaultForeground
icon?.let {
label.icon = IconUtil.scale(it, null, 0.65f)
}
closeButton.isVisible = tagDetails.selected
label.foreground = if (tagDetails.selected) {
service<EditorColorsManager>().globalScheme.defaultForeground
} else {
JBUI.CurrentTheme.Label.disabledForeground(false)
}
repaint()
}
override fun paintComponent(g: Graphics) {
@ -138,82 +139,85 @@ abstract class HeaderTag(val tagDetails: HeaderTagDetails, private val selectabl
add(label)
add(closeButton)
if (selectable) {
addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent) {
select()
repaint()
addMouseListener(object : MouseAdapter() {
override fun mousePressed(e: MouseEvent) {
if (!selectable) {
return
}
})
}
onSelect(tagDetails)
}
})
}
}
abstract class SelectedFileHeaderTag(
private val project: Project,
var virtualFile: VirtualFile? = project.getSelectedEditorFile()
var virtualFile: VirtualFile? = getSelectedEditorFile(project)
) : HeaderTag(
HeaderTagDetails(
virtualFile?.name ?: "",
virtualFile?.fileType?.icon
)
if (virtualFile == null) HeaderTagDetails("")
else FileTagDetails(virtualFile)
) {
init {
isVisible = project.getSelectedEditorFile() != null
isVisible = getSelectedEditorFile(project) != null
}
override fun onSelect(tagDetails: HeaderTagDetails) {
if (tagDetails is FileTagDetails) {
project.service<FileEditorManager>().openFile(tagDetails.virtualFile)
if (tagDetails.selected) {
project.service<FileEditorManager>().openFile(tagDetails.virtualFile)
}
tagDetails.selected = !tagDetails.selected
update(tagDetails.virtualFile.name, tagDetails.virtualFile.fileType.icon)
}
}
fun update(virtualFile: VirtualFile) {
this.virtualFile = virtualFile
isVisible = true
updateLabel(virtualFile.name, virtualFile.fileType.icon)
tagDetails = FileTagDetails(virtualFile)
runInEdt {
isVisible = true
update(virtualFile.name, virtualFile.fileType.icon)
}
}
}
class SelectionHeaderTag(
private val project: Project,
var selectedEditor: Editor? = project.getSelectedEditor()
) : HeaderTag(
SelectionTagDetails(
selectedEditor?.virtualFile,
selectedEditor?.selectionModel,
selectedEditor?.selectionModel?.selectedText
),
fun getDefaultSelectionTagDetails(project: Project): HeaderTagDetails {
val editor = getSelectedEditor(project)
val selectionModel = editor?.selectionModel
return if (selectionModel?.hasSelection() == true) {
SelectionTagDetails(editor.virtualFile, selectionModel)
} else {
EmptyTagDetails()
}
}
class SelectionHeaderTag(project: Project) : HeaderTag(
getDefaultSelectionTagDetails(project),
false
) {
init {
isVisible = selectedEditor?.selectionModel?.hasSelection() ?: false
isVisible = tagDetails !is EmptyTagDetails
}
override fun onSelect(tagDetails: HeaderTagDetails) {
}
override fun onClose() {
selectedEditor?.selectionModel?.removeSelection()
(tagDetails as? SelectionTagDetails)?.selectionModel?.removeSelection()
}
fun update(virtualFile: VirtualFile, selectionModel: SelectionModel) {
tagDetails = SelectionTagDetails(virtualFile, selectionModel)
isVisible = selectionModel.hasSelection()
updateLabel(
update(
"${virtualFile.name}:${selectionModel.selectionStart}-${selectionModel.selectionEnd}",
virtualFile.fileType.icon
)
}
}
fun Icon.scale() = IconUtil.scale(this, null, 0.65f)
fun Project.getSelectedEditorFile(): VirtualFile? {
return this.getSelectedEditor()?.virtualFile
}
fun Project.getSelectedEditor(): Editor? {
return EditorUtil.getSelectedEditor(this)
}
fun Icon.scale() = IconUtil.scale(this, null, 0.65f)

View file

@ -1,8 +1,8 @@
package ee.carlrobert.codegpt.ui.textarea.header
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.components.service
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorKind
import com.intellij.openapi.editor.SelectionModel
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
@ -17,6 +17,7 @@ import ee.carlrobert.codegpt.actions.IncludeFilesInContextNotifier
import ee.carlrobert.codegpt.ui.textarea.WrapLayout
import ee.carlrobert.codegpt.ui.textarea.suggestion.SuggestionsPopupManager
import ee.carlrobert.codegpt.util.EditorUtil
import ee.carlrobert.codegpt.util.EditorUtil.getSelectedEditor
import ee.carlrobert.codegpt.util.EditorUtil.getSelectedEditorFile
import java.awt.FlowLayout
import javax.swing.JPanel
@ -47,7 +48,7 @@ class UserInputHeaderPanel(
foreground = JBUI.CurrentTheme.Label.disabledForeground()
font = JBUI.Fonts.smallFont()
border = JBUI.Borders.emptyLeft(2)
isVisible = project.getSelectedEditor() == null
isVisible = getSelectedEditor(project) == null
}
private val selectionHeaderTag = SelectionHeaderTag(project)
@ -60,24 +61,13 @@ class UserInputHeaderPanel(
val selectedTags: MutableList<HeaderTagDetails> =
tags.filter { it.selected }.toMutableList()
val selectedFile = selectedFileHeaderTag.virtualFile
if (selectedFileHeaderTag.isVisible && selectedFile != null) {
val selectedFile = (selectedFileHeaderTag.tagDetails as? FileTagDetails)?.virtualFile
if (selectedFileHeaderTag.isVisible && selectedFileHeaderTag.tagDetails.selected && selectedFile != null) {
selectedTags.add(FileTagDetails(selectedFile))
}
selectionHeaderTag.selectedEditor?.let { editor ->
val selectionFile = editor.virtualFile
if (!editor.isDisposed && selectionHeaderTag.isVisible && selectionFile != null) {
selectedTags.add(
runReadAction {
SelectionTagDetails(
selectionFile,
editor.selectionModel,
editor.selectionModel.selectedText
)
}
)
}
(selectionHeaderTag.tagDetails as? SelectionTagDetails)?.let {
selectedTags.add(it)
}
return selectedTags
@ -86,35 +76,11 @@ class UserInputHeaderPanel(
fun addTag(tagDetails: HeaderTagDetails) {
if (selectedFileHeaderTag.isVisible
&& tagDetails is FileTagDetails
&& selectedFileHeaderTag.virtualFile == tagDetails.virtualFile
&& (selectedFileHeaderTag.tagDetails as? FileTagDetails)?.virtualFile == tagDetails.virtualFile
) {
return
}
val tag = object : HeaderTag(tagDetails, tagDetails is FileTagDetails) {
override fun onSelect(tagDetails: HeaderTagDetails) {
if (tagDetails is FileTagDetails) {
if (tagDetails.selected) {
project.service<FileEditorManager>().openFile(tagDetails.virtualFile)
} else {
selectedFileTags.add(tagDetails)
}
}
}
override fun onClose() {
removeTag(tagDetails.id)
}
override fun select() {
updateTagPosition(this)
super.select()
if (tags.filterIsInstance<FileTagDetails>().filter { !it.selected }.size <= 2) {
addNextOpenFile()
}
}
}
if (tags.add(tagDetails)) {
emptyText.isVisible = false
@ -122,6 +88,7 @@ class UserInputHeaderPanel(
selectedFileTags.add(tagDetails)
}
val tag = createTag(tagDetails)
val lastSelectionTagIndex = getLastSelectedTagIndex()
if (lastSelectionTagIndex != -1) {
add(tag, lastSelectionTagIndex + TAG_INSERTION_OFFSET + 1)
@ -142,6 +109,34 @@ class UserInputHeaderPanel(
}
}
private fun createTag(tagDetails: HeaderTagDetails) =
object : HeaderTag(tagDetails, tagDetails is FileTagDetails) {
override fun onSelect(tagDetails: HeaderTagDetails) {
tagDetails.selected = !tagDetails.selected
update(tagDetails.name, tagDetails.icon)
if (tagDetails is FileTagDetails) {
if (tagDetails.selected) {
selectedFileTags.add(tagDetails)
updateTagPosition(this)
val canAddNewTag = tags
.filter { it is FileTagDetails && !it.selected }.size <= 2
if (canAddNewTag) {
addNextOpenFile()
}
} else {
project.service<FileEditorManager>().openFile(tagDetails.virtualFile)
}
}
}
override fun onClose() {
selectedFileTags.removeIf { it.id == tagDetails.id }
removeTag(tagDetails.id)
}
}
private fun updateTagPosition(tag: HeaderTag) {
remove(tag)
val lastSelectionTagIndex = getLastSelectedTagIndex()
@ -150,6 +145,8 @@ class UserInputHeaderPanel(
} else {
add(tag, max(getFirstUnselectedTagIndex(), TAG_INSERTION_OFFSET))
}
repaint()
revalidate()
}
private fun getFilteredHeaderTags(): List<HeaderTag> = components
@ -168,18 +165,22 @@ class UserInputHeaderPanel(
private fun getFileTag(file: VirtualFile): FileTagDetails? =
tags.filterIsInstance<FileTagDetails>().find { it.virtualFile == file }
private fun isFileTagExists(file: VirtualFile): Boolean {
return getFileTag(file) != null || selectedFileHeaderTag.virtualFile == file
}
private fun getSortedOpenFiles(project: Project): MutableList<FileTagDetails> =
EditorUtil.getOpenLocalFiles(project)
.filterNot { it == selectedFileHeaderTag.virtualFile }
.filterNot { isFileTagExists(it) }
.map { FileTagDetails(it) }
.toMutableList()
private fun addNextOpenFile() {
val file = EditorUtil.getOpenLocalFiles(project).firstOrNull {
!tags.filterIsInstance<FileTagDetails>().any { tag -> tag.virtualFile == it }
&& it != selectedFileHeaderTag.virtualFile
} ?: return
addTag(FileTagDetails(file, false))
EditorUtil.getOpenLocalFiles(project)
.firstOrNull { !isFileTagExists(it) }
?.let {
addTag(FileTagDetails(it, false))
}
}
private fun removeFileTag(virtualFile: VirtualFile) {
@ -220,7 +221,7 @@ class UserInputHeaderPanel(
add(selectedFileHeaderTag)
if (tags.size <= 2) {
val selectedFile = EditorUtil.getSelectedEditor(project)?.virtualFile
val selectedFile = getSelectedEditor(project)?.virtualFile
getSortedOpenFiles(project)
.take(INITIAL_VISIBLE_FILES)
.forEach {
@ -258,7 +259,7 @@ class UserInputHeaderPanel(
private inner class EditorCreatedListener : EditorNotifier.Created {
override fun editorCreated(editor: Editor) {
editor.virtualFile?.let { editorFile ->
if (selectedFileHeaderTag.isVisible && selectedFileHeaderTag.virtualFile == editorFile) {
if (selectedFileHeaderTag.isVisible && (selectedFileHeaderTag.tagDetails as? FileTagDetails)?.virtualFile == editorFile) {
return
}
@ -269,11 +270,13 @@ class UserInputHeaderPanel(
private inner class EditorReleasedListener : EditorNotifier.Released {
override fun editorReleased(editor: Editor) {
removeFileTag(editor.virtualFile)
if (editor.editorKind == EditorKind.MAIN_EDITOR && !editor.isDisposed && editor.virtualFile != null) {
removeFileTag(editor.virtualFile)
if (tags.isEmpty() && project.getSelectedEditorFile() == null) {
selectedFileHeaderTag.isVisible = false
emptyText.isVisible = true
if (tags.isEmpty() && getSelectedEditorFile(project) == null) {
selectedFileHeaderTag.isVisible = false
emptyText.isVisible = true
}
}
}
}
@ -299,12 +302,11 @@ class UserInputHeaderPanel(
private inner class IncludedFilesListener : IncludeFilesInContextNotifier {
override fun filesIncluded(includedFiles: MutableList<VirtualFile>) {
val selectedEditorFile = getSelectedEditorFile(project)
includedFiles.forEach {
if (getFileTag(it) == null && selectedEditorFile != it) {
includedFiles
.filterNot { isFileTagExists(it) }
.forEach {
addTag(FileTagDetails(it))
}
}
}
}
}