mirror of
https://github.com/carlrobertoh/ProxyAI.git
synced 2026-05-19 16:28:46 +00:00
refactor: code clean up and minor UI improvements
This commit is contained in:
parent
f7c49f5f90
commit
89a3b669c5
18 changed files with 541 additions and 451 deletions
|
|
@ -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<HeaderTagDetails> getSelectedTags() {
|
||||
public List<TagDetails> getSelectedTags() {
|
||||
return userInputPanel.getSelectedTags();
|
||||
}
|
||||
|
||||
|
|
@ -155,15 +155,15 @@ public class ChatToolWindowTabPanel implements Disposable {
|
|||
return getReferencedFiles(userInputPanel.getSelectedTags());
|
||||
}
|
||||
|
||||
private List<ReferencedFile> getReferencedFiles(List<? extends HeaderTagDetails> tags) {
|
||||
private List<ReferencedFile> getReferencedFiles(List<? extends TagDetails> tags) {
|
||||
return tags.stream()
|
||||
.filter(FileTagDetails.class::isInstance)
|
||||
.map(it -> ReferencedFile.from(((FileTagDetails) it).getVirtualFile()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private <T extends HeaderTagDetails> Optional<T> findTagOfType(
|
||||
List<? extends HeaderTagDetails> tags,
|
||||
private <T extends TagDetails> Optional<T> findTagOfType(
|
||||
List<? extends TagDetails> tags,
|
||||
Class<T> 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<? extends HeaderTagDetails> appliedTags) {
|
||||
private Unit handleSubmit(String text, List<? extends TagDetails> appliedTags) {
|
||||
var messageBuilder = new MessageBuilder(project, text).withInlays(appliedTags);
|
||||
|
||||
List<ReferencedFile> referencedFiles = getReferencedFiles();
|
||||
|
|
|
|||
|
|
@ -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<HeaderTagDetails>): MessageBuilder {
|
||||
fun withInlays(appliedTags: List<TagDetails>): 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<HeaderTagDetails>
|
||||
tags: List<TagDetails>
|
||||
): String = buildString {
|
||||
tags.forEach {
|
||||
TagProcessorFactory.getProcessor(project, it).process(message, it, this)
|
||||
|
|
|
|||
65
src/main/kotlin/ee/carlrobert/codegpt/ui/WrapLayout.kt
Normal file
65
src/main/kotlin/ee/carlrobert/codegpt/ui/WrapLayout.kt
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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<HeaderTagDetails>) -> Unit,
|
||||
private val onSubmit: (String, List<TagDetails>) -> 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<HeaderTagDetails> {
|
||||
fun getSelectedTags(): List<TagDetails> {
|
||||
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<HeaderTagDetails> = emptyList()) {
|
||||
private fun handleSubmit(text: String, appliedTags: List<TagDetails> = 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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<HeaderTagDetails>()
|
||||
private val selectedFileTags = mutableSetOf<FileTagDetails>()
|
||||
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<HeaderTagDetails> {
|
||||
val selectedTags: MutableList<HeaderTagDetails> =
|
||||
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<HeaderTag>()
|
||||
.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<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()
|
||||
if (lastSelectionTagIndex != -1) {
|
||||
add(tag, lastSelectionTagIndex + TAG_INSERTION_OFFSET + 1)
|
||||
} else {
|
||||
add(tag, max(getFirstUnselectedTagIndex(), TAG_INSERTION_OFFSET))
|
||||
}
|
||||
repaint()
|
||||
revalidate()
|
||||
}
|
||||
|
||||
private fun getFilteredHeaderTags(): List<HeaderTag> = components
|
||||
.filter { it !is SelectedFileHeaderTag && it !is SelectionHeaderTag }
|
||||
.filterIsInstance<HeaderTag>()
|
||||
|
||||
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<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 { 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<TagDetails> {
|
||||
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<TagPanel>().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<FileEditorManager>().openFile(tagDetails.virtualFile)
|
||||
return
|
||||
}
|
||||
|
||||
tagDetails.selected = true
|
||||
|
||||
val canAddNewTag = tagManager.getTags()
|
||||
.filterIsInstance<FileTagDetails>()
|
||||
.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<TagPanel>()
|
||||
.indexOfLast { it.tagDetails.selected }
|
||||
|
||||
private fun getSortedOpenFileTags(): MutableList<FileTagDetails> =
|
||||
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<FileTagDetails>()
|
||||
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<VirtualFile>) {
|
||||
includedFiles
|
||||
.filterNot { isFileTagExists(it) }
|
||||
.filterNot { tagManager.isFileTagExists(it) }
|
||||
.forEach {
|
||||
addTag(FileTagDetails(it))
|
||||
tagManager.addTag(FileTagDetails(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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("")
|
||||
|
|
@ -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<TagDetails>()
|
||||
private val listeners = mutableListOf<TagManagerListener>()
|
||||
|
||||
fun addListener(listener: TagManagerListener) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
fun getTags(): Set<TagDetails> = tags.toSet()
|
||||
|
||||
fun getSelectedTags(): List<TagDetails> = 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<FileTagDetails>().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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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<EditorColorsManager>().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<FileEditorManager>().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)
|
||||
}
|
||||
|
|
@ -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 <T : HeaderTagDetails> isTagTypePresent(
|
||||
fun <T : TagDetails> isTagTypePresent(
|
||||
project: Project,
|
||||
tagClass: Class<T>
|
||||
): Boolean {
|
||||
|
|
@ -16,7 +16,7 @@ object TagUtil {
|
|||
.any { tagClass.isInstance(it) }
|
||||
}
|
||||
|
||||
fun <T : HeaderTagDetails> getExistingTags(
|
||||
fun <T : TagDetails> getExistingTags(
|
||||
project: Project,
|
||||
tagClass: Class<T>
|
||||
): List<T> {
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue