diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java index c57dcd33..487ad6e8 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java @@ -34,10 +34,10 @@ import ee.carlrobert.codegpt.toolwindow.ui.ResponseMessagePanel; import ee.carlrobert.codegpt.toolwindow.ui.UserMessagePanel; import ee.carlrobert.codegpt.ui.OverlayUtil; import ee.carlrobert.codegpt.ui.textarea.UserInputPanel; -import ee.carlrobert.codegpt.ui.textarea.header.FileTagDetails; -import ee.carlrobert.codegpt.ui.textarea.header.GitCommitTagDetails; -import ee.carlrobert.codegpt.ui.textarea.header.HeaderTagDetails; -import ee.carlrobert.codegpt.ui.textarea.header.PersonaTagDetails; +import ee.carlrobert.codegpt.ui.textarea.header.tag.FileTagDetails; +import ee.carlrobert.codegpt.ui.textarea.header.tag.GitCommitTagDetails; +import ee.carlrobert.codegpt.ui.textarea.header.tag.PersonaTagDetails; +import ee.carlrobert.codegpt.ui.textarea.header.tag.TagDetails; import ee.carlrobert.codegpt.util.EditorUtil; import ee.carlrobert.codegpt.util.file.FileUtil; import git4idea.GitCommit; @@ -128,7 +128,7 @@ public class ChatToolWindowTabPanel implements Disposable { userInputPanel.addCommitReferences(gitCommits); } - public List getSelectedTags() { + public List getSelectedTags() { return userInputPanel.getSelectedTags(); } @@ -155,15 +155,15 @@ public class ChatToolWindowTabPanel implements Disposable { return getReferencedFiles(userInputPanel.getSelectedTags()); } - private List getReferencedFiles(List tags) { + private List getReferencedFiles(List tags) { return tags.stream() .filter(FileTagDetails.class::isInstance) .map(it -> ReferencedFile.from(((FileTagDetails) it).getVirtualFile())) .toList(); } - private Optional findTagOfType( - List tags, + private Optional findTagOfType( + List tags, Class tagClass) { return tags.stream() .filter(tagClass::isInstance) @@ -307,7 +307,7 @@ public class ChatToolWindowTabPanel implements Disposable { .executeOnPooledThread(() -> requestHandler.call(callParameters)); } - private Unit handleSubmit(String text, List appliedTags) { + private Unit handleSubmit(String text, List appliedTags) { var messageBuilder = new MessageBuilder(project, text).withInlays(appliedTags); List referencedFiles = getReferencedFiles(); diff --git a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/MessageBuilder.kt b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/MessageBuilder.kt index 7f317114..32f5782f 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/MessageBuilder.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/chat/MessageBuilder.kt @@ -4,13 +4,13 @@ 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.ui.textarea.header.tag.TagDetails class MessageBuilder(private val project: Project, private val text: String) { private val message = Message("") private var inlayContent: String = "" - fun withInlays(appliedTags: List): MessageBuilder { + fun withInlays(appliedTags: List): MessageBuilder { if (appliedTags.isNotEmpty()) { inlayContent = processTags(message, appliedTags) } @@ -42,7 +42,7 @@ class MessageBuilder(private val project: Project, private val text: String) { private fun processTags( message: Message, - tags: List + tags: List ): String = buildString { tags.forEach { TagProcessorFactory.getProcessor(project, it).process(message, it, this) diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/WrapLayout.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/WrapLayout.kt new file mode 100644 index 00000000..88649e3b --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/WrapLayout.kt @@ -0,0 +1,65 @@ +package ee.carlrobert.codegpt.ui + +import java.awt.Container +import java.awt.Dimension +import java.awt.FlowLayout + +class WrapLayout(align: Int, hgap: Int, vgap: Int) : FlowLayout(align, hgap, vgap) { + + override fun preferredLayoutSize(target: Container): Dimension { + return layoutSize(target, true) + } + + override fun minimumLayoutSize(target: Container): Dimension { + return layoutSize(target, false) + } + + private fun layoutSize(target: Container, preferred: Boolean): Dimension { + synchronized(target.treeLock) { + val targetWidth = target.width + var width = targetWidth + if (targetWidth == 0) { + width = Int.MAX_VALUE + } + + val insets = target.insets + val horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2) + val maxWidth = width - horizontalInsetsAndGap + + val dim = Dimension(0, 0) + var rowWidth = 0 + var rowHeight = 0 + + for (i in 0 until target.componentCount) { + val m = target.getComponent(i) + if (m.isVisible) { + val d = if (preferred) m.preferredSize else m.minimumSize + if (rowWidth + d.width > maxWidth) { + addRow(dim, rowWidth, rowHeight) + rowWidth = 0 + rowHeight = 0 + } + if (rowWidth != 0) { + rowWidth += hgap + } + rowWidth += d.width + rowHeight = maxOf(rowHeight, d.height) + } + } + addRow(dim, rowWidth, rowHeight) + + dim.width += horizontalInsetsAndGap + dim.height += insets.top + insets.bottom + vgap * 2 + + return dim + } + } + + private fun addRow(dim: Dimension, rowWidth: Int, rowHeight: Int) { + dim.width = maxOf(dim.width, rowWidth) + if (dim.height > 0) { + dim.height += vgap + } + dim.height += rowHeight + } +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/TagProcessor.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/TagProcessor.kt index 695a6c2b..045d1cf7 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/TagProcessor.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/TagProcessor.kt @@ -1,8 +1,8 @@ package ee.carlrobert.codegpt.ui.textarea import ee.carlrobert.codegpt.conversations.message.Message -import ee.carlrobert.codegpt.ui.textarea.header.HeaderTagDetails +import ee.carlrobert.codegpt.ui.textarea.header.tag.TagDetails interface TagProcessor { - fun process(message: Message, tagDetails: HeaderTagDetails, promptBuilder: StringBuilder) + fun process(message: Message, tagDetails: TagDetails, promptBuilder: StringBuilder) } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/TagProcessorFactory.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/TagProcessorFactory.kt index ce1b1429..e24fedb2 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/TagProcessorFactory.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/TagProcessorFactory.kt @@ -6,13 +6,13 @@ import com.intellij.openapi.project.Project 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.ui.textarea.header.tag.* import ee.carlrobert.codegpt.util.GitUtil import git4idea.GitCommit object TagProcessorFactory { - fun getProcessor(project: Project, tagDetails: HeaderTagDetails): TagProcessor { + fun getProcessor(project: Project, tagDetails: TagDetails): TagProcessor { return when (tagDetails) { is FileTagDetails -> FileTagProcessor() is SelectionTagDetails -> SelectionTagProcessor() @@ -30,7 +30,7 @@ object TagProcessorFactory { class FileTagProcessor : TagProcessor { override fun process( message: Message, - tagDetails: HeaderTagDetails, + tagDetails: TagDetails, promptBuilder: StringBuilder ) { if (tagDetails !is FileTagDetails) { @@ -46,7 +46,7 @@ class FileTagProcessor : TagProcessor { class SelectionTagProcessor : TagProcessor { override fun process( message: Message, - tagDetails: HeaderTagDetails, + tagDetails: TagDetails, promptBuilder: StringBuilder ) { val selectionModel = (tagDetails as? SelectionTagDetails)?.selectionModel ?: return @@ -68,7 +68,7 @@ class SelectionTagProcessor : TagProcessor { class DocumentationTagProcessor : TagProcessor { override fun process( message: Message, - tagDetails: HeaderTagDetails, + tagDetails: TagDetails, promptBuilder: StringBuilder ) { if (tagDetails !is DocumentationTagDetails) { @@ -81,7 +81,7 @@ class DocumentationTagProcessor : TagProcessor { class PersonaTagProcessor : TagProcessor { override fun process( message: Message, - tagDetails: HeaderTagDetails, + tagDetails: TagDetails, promptBuilder: StringBuilder ) { if (tagDetails !is PersonaTagDetails) { @@ -94,7 +94,7 @@ class PersonaTagProcessor : TagProcessor { class FolderTagProcessor : TagProcessor { override fun process( message: Message, - tagDetails: HeaderTagDetails, + tagDetails: TagDetails, promptBuilder: StringBuilder ) { if (tagDetails !is FolderTagDetails) { @@ -121,7 +121,7 @@ class FolderTagProcessor : TagProcessor { class WebTagProcessor : TagProcessor { override fun process( message: Message, - tagDetails: HeaderTagDetails, + tagDetails: TagDetails, promptBuilder: StringBuilder ) { if (tagDetails !is WebTagDetails) { @@ -134,7 +134,7 @@ class WebTagProcessor : TagProcessor { class GitCommitTagProcessor(private val project: Project) : TagProcessor { override fun process( message: Message, - tagDetails: HeaderTagDetails, + tagDetails: TagDetails, promptBuilder: StringBuilder ) { if (tagDetails !is GitCommitTagDetails) { @@ -168,7 +168,7 @@ class GitCommitTagProcessor(private val project: Project) : TagProcessor { class CurrentGitChangesTagProcessor(private val project: Project) : TagProcessor { override fun process( message: Message, - tagDetails: HeaderTagDetails, + tagDetails: TagDetails, promptBuilder: StringBuilder ) { if (tagDetails !is CurrentGitChangesTagDetails) { diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/UserInputPanel.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/UserInputPanel.kt index 26d817b5..59fb74d4 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/UserInputPanel.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/UserInputPanel.kt @@ -33,10 +33,10 @@ import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowContentManager import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.ModelComboBoxAction import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.TotalTokensPanel import ee.carlrobert.codegpt.ui.IconActionButton -import ee.carlrobert.codegpt.ui.textarea.header.GitCommitTagDetails -import ee.carlrobert.codegpt.ui.textarea.header.HeaderTagDetails -import ee.carlrobert.codegpt.ui.textarea.header.SelectionTagDetails import ee.carlrobert.codegpt.ui.textarea.header.UserInputHeaderPanel +import ee.carlrobert.codegpt.ui.textarea.header.tag.GitCommitTagDetails +import ee.carlrobert.codegpt.ui.textarea.header.tag.TagDetails +import ee.carlrobert.codegpt.ui.textarea.header.tag.SelectionTagDetails import ee.carlrobert.codegpt.ui.textarea.suggestion.SuggestionsPopupManager import ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionModel import git4idea.GitCommit @@ -51,7 +51,7 @@ class UserInputPanel( private val conversation: Conversation, private val totalTokensPanel: TotalTokensPanel, parentDisposable: Disposable, - private val onSubmit: (String, List) -> Unit, + private val onSubmit: (String, List) -> Unit, private val onStop: () -> Unit ) : JPanel(BorderLayout()) { @@ -60,10 +60,7 @@ class UserInputPanel( } private val suggestionsPopupManager = SuggestionsPopupManager(project, this) - private val userInputHeaderPanel = UserInputHeaderPanel( - project, - suggestionsPopupManager - ) + private val userInputHeaderPanel = UserInputHeaderPanel(project, suggestionsPopupManager) private val promptTextField = PromptTextField(project, suggestionsPopupManager, ::updateUserTokens) { handleSubmit(it, userInputHeaderPanel.getSelectedTags()) @@ -106,7 +103,7 @@ class UserInputPanel( Disposer.register(parentDisposable, promptTextField) } - fun getSelectedTags(): List { + fun getSelectedTags(): List { return userInputHeaderPanel.getSelectedTags() } @@ -139,7 +136,7 @@ class UserInputPanel( } } - fun addTag(tagDetails: HeaderTagDetails) { + fun addTag(tagDetails: TagDetails) { userInputHeaderPanel.addTag(tagDetails) val text = promptTextField.text if (text.isNotEmpty() && text.last() == '@') { @@ -190,7 +187,7 @@ class UserInputPanel( override fun getInsets(): Insets = JBUI.insets(4) - private fun handleSubmit(text: String, appliedTags: List = emptyList()) { + private fun handleSubmit(text: String, appliedTags: List = emptyList()) { if (text.isNotEmpty() && submitButton.isEnabled) { onSubmit(text, appliedTags) promptTextField.clear() @@ -271,64 +268,4 @@ class UserInputPanel( else -> false } } -} - -class WrapLayout(align: Int, hgap: Int, vgap: Int) : FlowLayout(align, hgap, vgap) { - - override fun preferredLayoutSize(target: Container): Dimension { - return layoutSize(target, true) - } - - override fun minimumLayoutSize(target: Container): Dimension { - return layoutSize(target, false) - } - - private fun layoutSize(target: Container, preferred: Boolean): Dimension { - synchronized(target.treeLock) { - val targetWidth = target.width - var width = targetWidth - if (targetWidth == 0) { - width = Int.MAX_VALUE - } - - val insets = target.insets - val horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2) - val maxWidth = width - horizontalInsetsAndGap - - val dim = Dimension(0, 0) - var rowWidth = 0 - var rowHeight = 0 - - for (i in 0 until target.componentCount) { - val m = target.getComponent(i) - if (m.isVisible) { - val d = if (preferred) m.preferredSize else m.minimumSize - if (rowWidth + d.width > maxWidth) { - addRow(dim, rowWidth, rowHeight) - rowWidth = 0 - rowHeight = 0 - } - if (rowWidth != 0) { - rowWidth += hgap - } - rowWidth += d.width - rowHeight = maxOf(rowHeight, d.height) - } - } - addRow(dim, rowWidth, rowHeight) - - dim.width += horizontalInsetsAndGap - dim.height += insets.top + insets.bottom + vgap * 2 - - return dim - } - } - - private fun addRow(dim: Dimension, rowWidth: Int, rowHeight: Int) { - dim.width = maxOf(dim.width, rowWidth) - if (dim.height > 0) { - dim.height += vgap - } - dim.height += rowHeight - } } \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/AddButton.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/AddButton.kt deleted file mode 100644 index a1526d62..00000000 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/AddButton.kt +++ /dev/null @@ -1,31 +0,0 @@ -package ee.carlrobert.codegpt.ui.textarea.header - -import com.intellij.icons.AllIcons -import com.intellij.util.IconUtil -import java.awt.Cursor -import java.awt.Dimension -import java.awt.Graphics -import javax.swing.JButton - -class AddButton(onAdd: () -> Unit) : JButton() { - init { - addActionListener { - onAdd() - } - - cursor = Cursor(Cursor.HAND_CURSOR) - preferredSize = Dimension(20, 20) - isContentAreaFilled = false - isOpaque = false - border = null - toolTipText = "Add Context" - icon = IconUtil.scale(AllIcons.General.InlineAdd, null, 0.75f) - rolloverIcon = IconUtil.scale(AllIcons.General.InlineAddHover, null, 0.75f) - pressedIcon = IconUtil.scale(AllIcons.General.InlineAddHover, null, 0.75f) - } - - override fun paintComponent(g: Graphics) { - PaintUtil.drawRoundedBackground(g, this, true) - super.paintComponent(g) - } -} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/CloseButton.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/CloseButton.kt deleted file mode 100644 index e10b2cb0..00000000 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/CloseButton.kt +++ /dev/null @@ -1,21 +0,0 @@ -package ee.carlrobert.codegpt.ui.textarea.header - -import com.intellij.icons.AllIcons -import com.intellij.icons.AllIcons.Actions.Close -import com.intellij.util.ui.JBUI -import java.awt.Dimension -import javax.swing.JButton - -class CloseButton(onClose: () -> Unit) : JButton(Close) { - init { - addActionListener { - onClose() - } - - preferredSize = Dimension(Close.iconWidth, Close.iconHeight) - border = JBUI.Borders.emptyLeft(4) - isContentAreaFilled = false - toolTipText = "Remove" - rolloverIcon = AllIcons.Actions.CloseHovered - } -} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/CustomFlowPanel.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/CustomFlowPanel.kt new file mode 100644 index 00000000..a1330d7f --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/CustomFlowPanel.kt @@ -0,0 +1,62 @@ +package ee.carlrobert.codegpt.ui.textarea.header + +import com.intellij.util.ui.JBUI +import java.awt.Container +import java.awt.Dimension +import java.awt.FlowLayout +import javax.swing.JPanel + +class CustomFlowLayout(private val spacing: Int = 4) : FlowLayout(LEFT, 0, 0) { + override fun layoutContainer(target: Container) { + synchronized(target.treeLock) { + val insets = target.insets + var x = insets.left + val y = insets.top + + val visibleComponents = target.components.filter { it.isVisible } + visibleComponents.forEachIndexed { index, component -> + val dim = component.preferredSize + component.setBounds(x, y, dim.width, dim.height) + x += dim.width + + if (index < visibleComponents.size - 1) { + x += spacing + } + } + } + } +} + +const val DEFAULT_SPACING = 4 + +class CustomFlowPanel : JPanel(CustomFlowLayout(DEFAULT_SPACING)) { + + init { + isOpaque = false + border = JBUI.Borders.empty() + } + + override fun getPreferredSize(): Dimension { + var width = 0 + var height = 0 + var visibleCount = 0 + + for (component in components) { + if (component.isVisible) { + width += component.preferredSize.width + height = maxOf(height, component.preferredSize.height) + visibleCount++ + } + } + + if (visibleCount > 1) { + // gaps between multiple visible components + width += DEFAULT_SPACING * (visibleCount - 1) + } + + return Dimension(width, height) + } + + override fun getMinimumSize(): Dimension = preferredSize + override fun getMaximumSize(): Dimension = preferredSize +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/UserInputHeaderPanel.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/UserInputHeaderPanel.kt index d1c47c15..e0d5aaae 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/UserInputHeaderPanel.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/UserInputHeaderPanel.kt @@ -1,5 +1,7 @@ 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.EditorKind @@ -10,36 +12,39 @@ import com.intellij.openapi.fileEditor.FileEditorManagerListener import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.ui.components.JBLabel +import com.intellij.util.IconUtil import com.intellij.util.ui.JBUI -import com.jetbrains.rd.util.UUID import ee.carlrobert.codegpt.EditorNotifier import ee.carlrobert.codegpt.actions.IncludeFilesInContextNotifier -import ee.carlrobert.codegpt.ui.textarea.WrapLayout +import ee.carlrobert.codegpt.ui.WrapLayout +import ee.carlrobert.codegpt.ui.textarea.header.tag.* 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.Cursor +import java.awt.Dimension import java.awt.FlowLayout +import java.awt.Graphics +import javax.swing.JButton import javax.swing.JPanel -import kotlin.math.max +import kotlin.math.min class UserInputHeaderPanel( private val project: Project, suggestionsPopupManager: SuggestionsPopupManager -) : JPanel(WrapLayout(FlowLayout.LEFT, 4, 4)) { +) : JPanel(WrapLayout(FlowLayout.LEFT, 4, 4)), TagManagerListener { companion object { - private const val MAX_VISIBLE_TAGS = 3 private const val INITIAL_VISIBLE_FILES = 2 - private const val TAG_INSERTION_OFFSET = 4 + private const val TAG_INSERTION_OFFSET = 1 } - private val tags = mutableSetOf() - private val selectedFileTags = mutableSetOf() - private val selectedFileHeaderTag = object : SelectedFileHeaderTag(project) { + private val tagManager = TagManager() + private val selectedFileTagPanel = object : SelectedFileTagPanel(project) { override fun onClose() { this.isVisible = false - if (tags.isEmpty()) { + if (tagManager.getTags().isEmpty()) { emptyText.isVisible = true } } @@ -47,168 +52,11 @@ class UserInputHeaderPanel( private val emptyText = JBLabel("No context included").apply { foreground = JBUI.CurrentTheme.Label.disabledForeground() font = JBUI.Fonts.smallFont() - border = JBUI.Borders.emptyLeft(2) + border = JBUI.Borders.empty(4, 4, 4, 0) isVisible = getSelectedEditor(project) == null } - private val selectionHeaderTag = SelectionHeaderTag(project) - - init { - initializeUI(suggestionsPopupManager) - initializeEventListeners() - } - - fun getSelectedTags(): List { - val selectedTags: MutableList = - tags.filter { it.selected }.toMutableList() - - val selectedFile = (selectedFileHeaderTag.tagDetails as? FileTagDetails)?.virtualFile - if (selectedFileHeaderTag.isVisible && selectedFileHeaderTag.tagDetails.selected && selectedFile != null) { - selectedTags.add(FileTagDetails(selectedFile)) - } - - (selectionHeaderTag.tagDetails as? SelectionTagDetails)?.let { - selectedTags.add(it) - } - - return selectedTags - } - - fun addTag(tagDetails: HeaderTagDetails) { - if (selectedFileHeaderTag.isVisible - && tagDetails is FileTagDetails - && (selectedFileHeaderTag.tagDetails as? FileTagDetails)?.virtualFile == tagDetails.virtualFile - ) { - return - } - - if (tags.add(tagDetails)) { - emptyText.isVisible = false - - if (tagDetails is FileTagDetails) { - selectedFileTags.add(tagDetails) - } - - val tag = createTag(tagDetails) - val lastSelectionTagIndex = getLastSelectedTagIndex() - if (lastSelectionTagIndex != -1) { - add(tag, lastSelectionTagIndex + TAG_INSERTION_OFFSET + 1) - } else { - add(tag, TAG_INSERTION_OFFSET) - } - - val unselectedTags = components - .filter { it !is SelectedFileHeaderTag && it !is SelectionHeaderTag } - .filterIsInstance() - .filter { !it.tagDetails.selected } - if (unselectedTags.size > 2) { - removeTag(unselectedTags.last().tagDetails.id) - } - - revalidate() - repaint() - } - } - - 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().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() - if (lastSelectionTagIndex != -1) { - add(tag, lastSelectionTagIndex + TAG_INSERTION_OFFSET + 1) - } else { - add(tag, max(getFirstUnselectedTagIndex(), TAG_INSERTION_OFFSET)) - } - repaint() - revalidate() - } - - private fun getFilteredHeaderTags(): List = components - .filter { it !is SelectedFileHeaderTag && it !is SelectionHeaderTag } - .filterIsInstance() - - private fun getLastSelectedTagIndex(): Int = - getFilteredHeaderTags().indexOfLast { it.tagDetails.selected } - - private fun getFirstUnselectedTagIndex(): Int = - getFilteredHeaderTags().indexOfFirst { !it.tagDetails.selected } - - private fun getSelectedFileTag(file: VirtualFile): FileTagDetails? = - selectedFileTags.find { it.virtualFile == file } - - private fun getFileTag(file: VirtualFile): FileTagDetails? = - tags.filterIsInstance().find { it.virtualFile == file } - - private fun isFileTagExists(file: VirtualFile): Boolean { - return getFileTag(file) != null || selectedFileHeaderTag.virtualFile == file - } - - private fun getSortedOpenFiles(project: Project): MutableList = - EditorUtil.getOpenLocalFiles(project) - .filterNot { isFileTagExists(it) } - .map { FileTagDetails(it) } - .toMutableList() - - private fun addNextOpenFile() { - EditorUtil.getOpenLocalFiles(project) - .firstOrNull { !isFileTagExists(it) } - ?.let { - addTag(FileTagDetails(it, false)) - } - } - - private fun removeFileTag(virtualFile: VirtualFile) { - getFileTag(virtualFile)?.let { - removeTag(it.id) - } - } - - private fun removeTag(id: UUID) { - val tagToRemove = - tags.find { it.id == id } ?: throw IllegalArgumentException("Tag with id $id not found") - if (tags.removeIf { it.id == tagToRemove.id }) { - val componentToRemove = components.find { it is HeaderTag && it.id == id } ?: return - remove(componentToRemove) - - if (tags.isEmpty() && !selectedFileHeaderTag.isVisible) { - emptyText.isVisible = true - } - - revalidate() - repaint() - } - } - - private fun initializeUI(suggestionsPopupManager: SuggestionsPopupManager) { - isOpaque = false - border = JBUI.Borders.empty() - + private val selectionTagPanel = SelectionTagPanel(project) + private val defaultHeaderTagsPanel = CustomFlowPanel().apply { add(AddButton { if (suggestionsPopupManager.isPopupVisible()) { suggestionsPopupManager.hidePopup() @@ -217,17 +65,145 @@ class UserInputHeaderPanel( } }) add(emptyText) - add(selectionHeaderTag) - add(selectedFileHeaderTag) + add(selectionTagPanel) + add(selectedFileTagPanel) + } - if (tags.size <= 2) { - val selectedFile = getSelectedEditor(project)?.virtualFile - getSortedOpenFiles(project) - .take(INITIAL_VISIBLE_FILES) - .forEach { - addTag(FileTagDetails(it.virtualFile, selectedFile == it.virtualFile)) - } + init { + tagManager.addListener(this) + initializeUI() + initializeEventListeners() + } + + fun getSelectedTags(): List { + val selectedTags = tagManager.getSelectedTags().toMutableList() + + val selectedFile = getSelectedFile() + if (selectedFileTagPanel.isVisible && selectedFileTagPanel.tagDetails.selected && selectedFile != null) { + selectedTags.add(FileTagDetails(selectedFile)) } + + (selectionTagPanel.tagDetails as? SelectionTagDetails)?.let { + selectedTags.add(it) + } + + return selectedTags + } + + fun addTag(tagDetails: TagDetails) { + tagManager.addTag(tagDetails) + } + + override fun onTagAdded(tag: TagDetails) { + emptyText.isVisible = false + add(createTag(tag), getNextInsertionIndex()) + revalidate() + repaint() + } + + private fun getNextInsertionIndex(): Int { + val lastSelectionTagIndex = getLastSelectedTagIndex() + return if (lastSelectionTagIndex != -1) { + min(lastSelectionTagIndex + TAG_INSERTION_OFFSET + 1, components.size) + } else { + TAG_INSERTION_OFFSET + } + } + + override fun onTagRemoved(tag: TagDetails) { + val componentToRemove = + components.find { it is TagPanel && it.id == tag.id } ?: return + remove(componentToRemove) + + if (getSelectedEditorFile(project) == null) { + selectedFileTagPanel.isVisible = false + } + + if (tagManager.getTags().isEmpty() && !selectedFileTagPanel.isVisible) { + emptyText.isVisible = true + } + + revalidate() + repaint() + } + + override fun onTagSelectionChanged(tag: TagDetails) { + val existingTagComponent = + components.filterIsInstance().find { it.id == tag.id } + ?: return + existingTagComponent.update(tag.name, tag.icon) + updateTagPosition(existingTagComponent) + } + + private fun createTag(tagDetails: TagDetails) = + object : TagPanel(tagDetails, tagDetails is FileTagDetails) { + override fun onSelect(tagDetails: TagDetails) { + if (tagDetails is FileTagDetails) { + if (tagDetails.selected) { + project.service().openFile(tagDetails.virtualFile) + return + } + + tagDetails.selected = true + + val canAddNewTag = tagManager.getTags() + .filterIsInstance() + .count { !it.selected } <= 2 + if (canAddNewTag) { + addNextOpenFile() + } + update(tagDetails.name, tagDetails.icon) + } + } + + override fun onClose() { + tagManager.removeTag(tagDetails.id) + } + } + + private fun updateTagPosition(tag: TagPanel) { + remove(tag) + add(tag, getNextInsertionIndex()) + revalidate() + repaint() + } + + private fun getLastSelectedTagIndex(): Int = + components + .filter { it !is SelectedFileTagPanel && it !is SelectionTagPanel } + .filterIsInstance() + .indexOfLast { it.tagDetails.selected } + + private fun getSortedOpenFileTags(): MutableList = + EditorUtil.getOpenLocalFiles(project) + .filterNot { tagManager.isFileTagExists(it) } + .map { FileTagDetails(it) } + .toMutableList() + + private fun addNextOpenFile() { + getSortedOpenFileTags() + .firstOrNull { it.virtualFile != getSelectedFile() } + ?.let { + tagManager.addTag(it.apply { selected = false }) + } + } + + private fun initializeUI() { + isOpaque = false + border = JBUI.Borders.empty() + + add(defaultHeaderTagsPanel) + addInitialTags() + } + + private fun addInitialTags() { + val selectedFile = getSelectedEditor(project)?.virtualFile + getSortedOpenFileTags() + .filterNot { it.virtualFile == selectedFile } + .take(INITIAL_VISIBLE_FILES) + .forEach { + tagManager.addTag(it.apply { selected = false }) + } } private fun initializeEventListeners() { @@ -243,6 +219,29 @@ class UserInputHeaderPanel( } } + private class AddButton(onAdd: () -> Unit) : JButton() { + init { + addActionListener { + onAdd() + } + + cursor = Cursor(Cursor.HAND_CURSOR) + preferredSize = Dimension(20, 20) + isContentAreaFilled = false + isOpaque = false + border = null + toolTipText = "Add Context" + icon = IconUtil.scale(AllIcons.General.InlineAdd, null, 0.75f) + rolloverIcon = IconUtil.scale(AllIcons.General.InlineAddHover, null, 0.75f) + pressedIcon = IconUtil.scale(AllIcons.General.InlineAddHover, null, 0.75f) + } + + override fun paintComponent(g: Graphics) { + PaintUtil.drawRoundedBackground(g, this, true) + super.paintComponent(g) + } + } + private inner class EditorSelectionChangeListener : EditorNotifier.SelectionChange { override fun selectionChanged(selectionModel: SelectionModel, virtualFile: VirtualFile) { handleSelectionChange(selectionModel, virtualFile) @@ -252,18 +251,18 @@ class UserInputHeaderPanel( selectionModel: SelectionModel, virtualFile: VirtualFile ) { - selectionHeaderTag.update(virtualFile, selectionModel) + selectionTagPanel.update(virtualFile, selectionModel) } } private inner class EditorCreatedListener : EditorNotifier.Created { override fun editorCreated(editor: Editor) { editor.virtualFile?.let { editorFile -> - if (selectedFileHeaderTag.isVisible && (selectedFileHeaderTag.tagDetails as? FileTagDetails)?.virtualFile == editorFile) { + if (selectedFileTagPanel.isVisible && getSelectedFile() == editorFile) { return } - addTag(FileTagDetails(editorFile, false)) + tagManager.addTag(FileTagDetails(editorFile, false)) } } } @@ -271,30 +270,32 @@ class UserInputHeaderPanel( private inner class EditorReleasedListener : EditorNotifier.Released { override fun editorReleased(editor: Editor) { if (editor.editorKind == EditorKind.MAIN_EDITOR && !editor.isDisposed && editor.virtualFile != null) { - removeFileTag(editor.virtualFile) - - if (tags.isEmpty() && getSelectedEditorFile(project) == null) { - selectedFileHeaderTag.isVisible = false - emptyText.isVisible = true - } + tagManager.removeFileTag(editor.virtualFile) } } } + private fun getSelectedFile(): VirtualFile? { + return (selectedFileTagPanel.tagDetails as? FileTagDetails)?.virtualFile + } + private inner class FileSelectionListener : FileEditorManagerListener { - override fun selectionChanged(event: FileEditorManagerEvent) { - val fileTags = tags.filterIsInstance() + val currentTagDetails = (selectedFileTagPanel.tagDetails as? FileTagDetails) ?: return event.newFile?.let { newFile -> - if (fileTags.any { it.virtualFile == newFile }) { - removeFileTag(newFile) + if (event.oldFile == currentTagDetails.virtualFile) { + tagManager.removeFileTag(newFile) + tagManager.addTag(currentTagDetails) } - selectedFileHeaderTag.update(newFile) - emptyText.isVisible = false - event.oldFile?.let { oldFile -> - val prevSelectedTag = getSelectedFileTag(oldFile) - addTag(prevSelectedTag ?: FileTagDetails(oldFile, false)) + val existingTag = tagManager.getFileTag(newFile) + runInEdt { + if (existingTag == null) { + selectedFileTagPanel.update(FileTagDetails(newFile)) + } else { + selectedFileTagPanel.update(existingTag) + } + emptyText.isVisible = false } } } @@ -303,9 +304,9 @@ class UserInputHeaderPanel( private inner class IncludedFilesListener : IncludeFilesInContextNotifier { override fun filesIncluded(includedFiles: MutableList) { includedFiles - .filterNot { isFileTagExists(it) } + .filterNot { tagManager.isFileTagExists(it) } .forEach { - addTag(FileTagDetails(it)) + tagManager.addTag(FileTagDetails(it)) } } } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/tag/TagDetails.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/tag/TagDetails.kt new file mode 100644 index 00000000..10f76250 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/tag/TagDetails.kt @@ -0,0 +1,60 @@ +package ee.carlrobert.codegpt.ui.textarea.header.tag + +import com.intellij.icons.AllIcons +import com.intellij.openapi.editor.SelectionModel +import com.intellij.openapi.vfs.VirtualFile +import ee.carlrobert.codegpt.Icons +import ee.carlrobert.codegpt.settings.prompts.PersonaDetails +import ee.carlrobert.codegpt.ui.DocumentationDetails +import git4idea.GitCommit +import java.util.* +import javax.swing.Icon + +open class TagDetails( + open val name: String, + val icon: Icon? = null, + open var selected: Boolean = true +) { + val id: UUID = UUID.randomUUID() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is TagDetails) return false + return id == other.id + } + + override fun hashCode(): Int { + return id.hashCode() + } +} + +data class FileTagDetails(var virtualFile: VirtualFile, override var selected: Boolean = true) : + TagDetails(virtualFile.name, virtualFile.fileType.icon) + +data class SelectionTagDetails( + var virtualFile: VirtualFile?, + var selectionModel: SelectionModel?, +) : + TagDetails( + "${virtualFile?.name} (${selectionModel?.selectionStartPosition?.line}:${selectionModel?.selectionEndPosition?.line})", + Icons.InSelection + ) + +data class DocumentationTagDetails(var documentationDetails: DocumentationDetails) : + TagDetails(documentationDetails.name, AllIcons.Toolwindows.Documentation) + +data class PersonaTagDetails(var personaDetails: PersonaDetails) : + TagDetails(personaDetails.name, AllIcons.General.User) + +data class GitCommitTagDetails(var gitCommit: GitCommit) : + TagDetails(gitCommit.id.asString().take(6), AllIcons.Vcs.CommitNode) + +class CurrentGitChangesTagDetails : + TagDetails("Current Git Changes", AllIcons.Vcs.CommitNode) + +data class FolderTagDetails(var folder: VirtualFile) : + TagDetails(folder.name, AllIcons.Nodes.Folder) + +class WebTagDetails : TagDetails("Web", AllIcons.General.Web) + +class EmptyTagDetails : TagDetails("") \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/tag/TagManager.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/tag/TagManager.kt new file mode 100644 index 00000000..c464d816 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/tag/TagManager.kt @@ -0,0 +1,52 @@ +package ee.carlrobert.codegpt.ui.textarea.header.tag + +import com.intellij.openapi.vfs.VirtualFile +import java.util.* + +class TagManager { + + private val tags = mutableSetOf() + private val listeners = mutableListOf() + + fun addListener(listener: TagManagerListener) { + listeners.add(listener) + } + + fun getTags(): Set = tags.toSet() + + fun getSelectedTags(): List = tags.filter { it.selected } + + fun addTag(tagDetails: TagDetails) { + if (tags.add(tagDetails)) { + listeners.forEach { it.onTagAdded(tagDetails) } + } + } + + fun removeFileTag(virtualFile: VirtualFile) { + getFileTag(virtualFile)?.let { + removeTag(it.id) + } + } + + fun removeTag(id: UUID) { + tags.find { it.id == id } + ?.let { tag -> + if (tags.removeIf { it.id == tag.id }) { + listeners.forEach { it.onTagRemoved(tag) } + } + } + } + + fun getFileTag(file: VirtualFile): FileTagDetails? = + tags.filterIsInstance().find { it.virtualFile == file } + + fun isFileTagExists(file: VirtualFile): Boolean = getFileTag(file) != null + + fun clear() { + val tagsToRemove = tags.toList() + tags.clear() + tagsToRemove.forEach { tag -> + listeners.forEach { it.onTagRemoved(tag) } + } + } +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/tag/TagManagerListener.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/tag/TagManagerListener.kt new file mode 100644 index 00000000..fad9e342 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/tag/TagManagerListener.kt @@ -0,0 +1,7 @@ +package ee.carlrobert.codegpt.ui.textarea.header.tag + +interface TagManagerListener { + fun onTagAdded(tag: TagDetails) + fun onTagRemoved(tag: TagDetails) + fun onTagSelectionChanged(tag: TagDetails) +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/HeaderTag.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/tag/TagPanel.kt similarity index 50% rename from src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/HeaderTag.kt rename to src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/tag/TagPanel.kt index 46c45e95..60f4b867 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/HeaderTag.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/tag/TagPanel.kt @@ -1,83 +1,34 @@ -package ee.carlrobert.codegpt.ui.textarea.header +package ee.carlrobert.codegpt.ui.textarea.header.tag import com.intellij.icons.AllIcons -import com.intellij.openapi.application.runInEdt +import com.intellij.icons.AllIcons.Actions.Close import com.intellij.openapi.components.service import com.intellij.openapi.editor.SelectionModel import com.intellij.openapi.editor.colors.EditorColorsManager -import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.ui.components.JBLabel import com.intellij.util.IconUtil import com.intellij.util.ui.JBUI 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.ui.textarea.header.PaintUtil import ee.carlrobert.codegpt.util.EditorUtil.getSelectedEditor import ee.carlrobert.codegpt.util.EditorUtil.getSelectedEditorFile import java.awt.Cursor +import java.awt.Dimension import java.awt.FlowLayout import java.awt.Graphics import java.awt.event.MouseAdapter import java.awt.event.MouseEvent import javax.swing.Icon +import javax.swing.JButton import javax.swing.JPanel import javax.swing.SwingUtilities -import git4idea.GitCommit as Git4IdeaGitCommit -open class HeaderTagDetails( - open val name: String, - val icon: Icon? = null, - open var selected: Boolean = true -) { - val id: UUID = UUID.randomUUID() - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is HeaderTagDetails) return false - return id == other.id - } - - override fun hashCode(): Int { - return id.hashCode() - } -} - -data class FileTagDetails(var virtualFile: VirtualFile, override var selected: Boolean = true) : - HeaderTagDetails(virtualFile.name, virtualFile.fileType.icon) - -data class SelectionTagDetails( - var virtualFile: VirtualFile?, - var selectionModel: SelectionModel?, -) : - HeaderTagDetails( - "${virtualFile?.name} (${selectionModel?.selectionStartPosition?.line}:${selectionModel?.selectionEndPosition?.line})", - Icons.InSelection - ) - -data class DocumentationTagDetails(var documentationDetails: DocumentationDetails) : - HeaderTagDetails(documentationDetails.name, AllIcons.Toolwindows.Documentation) - -data class PersonaTagDetails(var personaDetails: PersonaDetails) : - HeaderTagDetails(personaDetails.name, AllIcons.General.User) - -data class GitCommitTagDetails(var gitCommit: Git4IdeaGitCommit) : - HeaderTagDetails(gitCommit.id.asString().take(6), AllIcons.Vcs.CommitNode) - -class CurrentGitChangesTagDetails : - HeaderTagDetails("Current Git Changes", AllIcons.Vcs.CommitNode) - -data class FolderTagDetails(var folder: VirtualFile) : - HeaderTagDetails(folder.name, AllIcons.Nodes.Folder) - -class WebTagDetails : HeaderTagDetails("Web", AllIcons.General.Web) - -class EmptyTagDetails : HeaderTagDetails("") - -abstract class HeaderTag(var tagDetails: HeaderTagDetails, private val selectable: Boolean = true) : - JPanel() { +abstract class TagPanel( + var tagDetails: TagDetails, + private val selectable: Boolean = true +) : JPanel(FlowLayout(FlowLayout.LEFT, 0, 0)) { val id: UUID = tagDetails.id @@ -93,7 +44,7 @@ abstract class HeaderTag(var tagDetails: HeaderTagDetails, private val selectabl setupUI() } - abstract fun onSelect(tagDetails: HeaderTagDetails) + abstract fun onSelect(tagDetails: TagDetails) abstract fun onClose() @@ -108,7 +59,6 @@ abstract class HeaderTag(var tagDetails: HeaderTagDetails, private val selectabl } else { JBUI.CurrentTheme.Label.disabledForeground(false) } - repaint() } override fun paintComponent(g: Graphics) { @@ -116,11 +66,15 @@ abstract class HeaderTag(var tagDetails: HeaderTagDetails, private val selectabl PaintUtil.drawRoundedBackground(g, this, tagDetails.selected) } - private fun createLabel(tagDetails: HeaderTagDetails): JBLabel { + private fun createLabel(tagDetails: TagDetails): JBLabel { return (if (tagDetails.icon == null) { JBLabel(tagDetails.name) } else { - JBLabel(tagDetails.name, tagDetails.icon.scale(), SwingUtilities.LEADING) + JBLabel( + tagDetails.name, + IconUtil.scale(tagDetails.icon, null, 0.65f), + SwingUtilities.LEADING + ) }).apply { foreground = if (tagDetails.selected) { service().globalScheme.defaultForeground @@ -133,8 +87,7 @@ abstract class HeaderTag(var tagDetails: HeaderTagDetails, private val selectabl private fun setupUI() { isOpaque = false - layout = FlowLayout(FlowLayout.LEFT, 0, 2) - border = JBUI.Borders.empty(0, 6, 0, 4) + border = JBUI.Borders.empty(2, 8) cursor = if (selectable) Cursor(Cursor.HAND_CURSOR) else Cursor(Cursor.DEFAULT_CURSOR) add(label) @@ -149,13 +102,27 @@ abstract class HeaderTag(var tagDetails: HeaderTagDetails, private val selectabl } }) } + + private class CloseButton(onClose: () -> Unit) : JButton(Close) { + init { + addActionListener { + onClose() + } + + preferredSize = Dimension(Close.iconWidth, Close.iconHeight) + border = JBUI.Borders.emptyLeft(4) + isContentAreaFilled = false + toolTipText = "Remove" + rolloverIcon = AllIcons.Actions.CloseHovered + } + } } -abstract class SelectedFileHeaderTag( +abstract class SelectedFileTagPanel( private val project: Project, - var virtualFile: VirtualFile? = getSelectedEditorFile(project) -) : HeaderTag( - if (virtualFile == null) HeaderTagDetails("") + virtualFile: VirtualFile? = getSelectedEditorFile(project) +) : TagPanel( + if (virtualFile == null) TagDetails("") else FileTagDetails(virtualFile) ) { @@ -163,47 +130,42 @@ abstract class SelectedFileHeaderTag( isVisible = getSelectedEditorFile(project) != null } - override fun onSelect(tagDetails: HeaderTagDetails) { + override fun onSelect(tagDetails: TagDetails) { if (tagDetails is FileTagDetails) { - if (tagDetails.selected) { - project.service().openFile(tagDetails.virtualFile) - } tagDetails.selected = !tagDetails.selected - update(tagDetails.virtualFile.name, tagDetails.virtualFile.fileType.icon) } } - fun update(virtualFile: VirtualFile) { - tagDetails = FileTagDetails(virtualFile) - - runInEdt { - isVisible = true - update(virtualFile.name, virtualFile.fileType.icon) - } + fun update(tagDetails: FileTagDetails) { + this.tagDetails = tagDetails + isVisible = true + update(tagDetails.name, tagDetails.virtualFile.fileType.icon) } } -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( +class SelectionTagPanel(project: Project) : TagPanel( getDefaultSelectionTagDetails(project), false ) { + companion object { + fun getDefaultSelectionTagDetails(project: Project): TagDetails { + val editor = getSelectedEditor(project) + val selectionModel = editor?.selectionModel + return if (selectionModel?.hasSelection() == true) { + SelectionTagDetails(editor.virtualFile, selectionModel) + } else { + EmptyTagDetails() + } + } + } + init { isVisible = tagDetails !is EmptyTagDetails } - override fun onSelect(tagDetails: HeaderTagDetails) { + override fun onSelect(tagDetails: TagDetails) { } override fun onClose() { @@ -218,6 +180,4 @@ class SelectionHeaderTag(project: Project) : HeaderTag( virtualFile.fileType.icon ) } -} - -fun Icon.scale() = IconUtil.scale(this, null, 0.65f) \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/TagUtil.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/tag/TagUtil.kt similarity index 83% rename from src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/TagUtil.kt rename to src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/tag/TagUtil.kt index 917be1bc..8b1481c4 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/TagUtil.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/header/tag/TagUtil.kt @@ -1,11 +1,11 @@ -package ee.carlrobert.codegpt.ui.textarea.header +package ee.carlrobert.codegpt.ui.textarea.header.tag import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowContentManager object TagUtil { - fun isTagTypePresent( + fun isTagTypePresent( project: Project, tagClass: Class ): Boolean { @@ -16,7 +16,7 @@ object TagUtil { .any { tagClass.isInstance(it) } } - fun getExistingTags( + fun getExistingTags( project: Project, tagClass: Class ): List { diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/suggestion/item/SuggestionActionItems.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/suggestion/item/SuggestionActionItems.kt index 7ad250c5..3bb7227c 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/suggestion/item/SuggestionActionItems.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/suggestion/item/SuggestionActionItems.kt @@ -17,7 +17,7 @@ import ee.carlrobert.codegpt.settings.service.ServiceType import ee.carlrobert.codegpt.ui.AddDocumentationDialog import ee.carlrobert.codegpt.ui.DocumentationDetails import ee.carlrobert.codegpt.ui.textarea.UserInputPanel -import ee.carlrobert.codegpt.ui.textarea.header.* +import ee.carlrobert.codegpt.ui.textarea.header.tag.* import git4idea.GitCommit import javax.swing.Icon diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/suggestion/item/SuggestionGroupItems.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/suggestion/item/SuggestionGroupItems.kt index d1e5f793..91d8e64b 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/suggestion/item/SuggestionGroupItems.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/suggestion/item/SuggestionGroupItems.kt @@ -15,10 +15,10 @@ import ee.carlrobert.codegpt.settings.prompts.PersonaDetails import ee.carlrobert.codegpt.settings.prompts.PromptsSettings import ee.carlrobert.codegpt.settings.service.ServiceType import ee.carlrobert.codegpt.ui.DocumentationDetails -import ee.carlrobert.codegpt.ui.textarea.header.DocumentationTagDetails -import ee.carlrobert.codegpt.ui.textarea.header.FileTagDetails -import ee.carlrobert.codegpt.ui.textarea.header.PersonaTagDetails -import ee.carlrobert.codegpt.ui.textarea.header.TagUtil +import ee.carlrobert.codegpt.ui.textarea.header.tag.DocumentationTagDetails +import ee.carlrobert.codegpt.ui.textarea.header.tag.FileTagDetails +import ee.carlrobert.codegpt.ui.textarea.header.tag.PersonaTagDetails +import ee.carlrobert.codegpt.ui.textarea.header.tag.TagUtil import ee.carlrobert.codegpt.util.GitUtil import ee.carlrobert.codegpt.util.file.FileUtil import kotlinx.coroutines.Dispatchers diff --git a/src/main/kotlin/ee/carlrobert/codegpt/util/file/FileUtil.kt b/src/main/kotlin/ee/carlrobert/codegpt/util/file/FileUtil.kt index 58704ef8..e8c8c640 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/util/file/FileUtil.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/util/file/FileUtil.kt @@ -12,8 +12,6 @@ import com.intellij.openapi.util.io.FileUtil.createDirectory import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFileFilter import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings.getLlamaModelsPath -import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowContentManager -import ee.carlrobert.codegpt.ui.textarea.header.FileTagDetails import java.io.File import java.io.FileOutputStream import java.io.IOException