diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/documentation/DocumentationSettings.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/documentation/DocumentationSettings.kt index d2041c9f..24072598 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/documentation/DocumentationSettings.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/documentation/DocumentationSettings.kt @@ -1,6 +1,8 @@ package ee.carlrobert.codegpt.settings.documentation import com.intellij.openapi.components.* +import java.time.Instant +import java.time.format.DateTimeFormatter @Service @State( @@ -8,13 +10,50 @@ import com.intellij.openapi.components.* storages = [Storage("CodeGPT_DocumentationSettings.xml")] ) class DocumentationSettings : - SimplePersistentStateComponent(DocumentationSettingsState()) + SimplePersistentStateComponent(DocumentationSettingsState()) { + + fun updateLastUsedDateTime(url: String) { + state.documentations + .find { it.url == url } + ?.run { + lastUsedDateTime = DateTimeFormatter.ISO_INSTANT.format(Instant.now()) + } + } +} class DocumentationSettingsState : BaseState() { var documentations by list() + + init { + documentations.addAll(DEFAULT_DOCUMENTATIONS) + } } class DocumentationDetailsState : BaseState() { var name by string("CodeGPT Docs") var url by string("https://docs.codegpt.ee") -} \ No newline at end of file + var lastUsedDateTime by string() +} + +private val DEFAULT_DOCUMENTATIONS = mutableListOf( + getDocState("Astro Runtime API", "https://docs.astro.build/en/reference/api-reference/"), + getDocState("Flask API", "https://flask.palletsprojects.com/en/3.0.x/api/"), + getDocState("Flutter API", "https://api.flutter.dev/"), + getDocState("IPFS Kubo CLI", "https://docs.ipfs.tech/reference/kubo/cli/#ipfs"), + getDocState("Kotlin Coding Conventions", "https://kotlinlang.org/docs/coding-conventions.html"), + getDocState( + "Next.js Authentication", + "https://nextjs.org/docs/app/building-your-application/authentication" + ), + getDocState("SolidJS Documentation", "https://www.solidjs.com/docs"), + getDocState("SvelteKit Modules", "https://kit.svelte.dev/docs/modules"), + getDocState("SwiftUI Updates", "https://developer.apple.com/documentation/updates/swiftui"), + getDocState("Zapier CLI Documentation", "https://platform.zapier.com/reference/cli-docs"), +) + +private fun getDocState(name: String, url: String): DocumentationDetailsState { + return DocumentationDetailsState().apply { + this.name = name + this.url = url + } +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/settings/documentation/DocumentationsSettingsForm.kt b/src/main/kotlin/ee/carlrobert/codegpt/settings/documentation/DocumentationsSettingsForm.kt index 9d34a8b2..4266e2d0 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/settings/documentation/DocumentationsSettingsForm.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/settings/documentation/DocumentationsSettingsForm.kt @@ -43,9 +43,12 @@ class DocumentationsSettingsForm { .align(Align.FILL) .resizableColumn() .applyToComponent { - preferredSize = Dimension(650, 250) + preferredSize = Dimension(600, 400) } } + row { + text("Documentations can be included in the chat suggestions popup by pressing the @ symbol.") + } } } @@ -132,7 +135,7 @@ class DocumentationsSettingsForm { private fun JBTable.setupTableColumns() { columnModel.apply { getColumn(0).preferredWidth = 200 - getColumn(1).preferredWidth = 450 + getColumn(1).preferredWidth = 400 } } } diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/CustomTextPane.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/CustomTextPane.kt index a7cb70a3..612b4241 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/CustomTextPane.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/CustomTextPane.kt @@ -1,6 +1,7 @@ package ee.carlrobert.codegpt.ui.textarea import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.thisLogger import com.intellij.openapi.editor.colors.EditorColorsManager import com.intellij.openapi.editor.colors.EditorFontType import com.intellij.openapi.editor.ex.util.EditorUtil @@ -27,6 +28,10 @@ class CustomTextPane( private val onSubmit: (String) -> Unit ) : JTextPane() { + companion object { + private val logger = thisLogger() + } + init { isOpaque = false background = JBColor.namedColor("Editor.SearchField.background") @@ -42,15 +47,7 @@ class CustomTextPane( inputMap.put(KeyStroke.getKeyStroke("ENTER"), "text-submit") actionMap.put("text-submit", object : AbstractAction() { override fun actionPerformed(e: ActionEvent) { - var textWithoutActions = text - highlightedTextRanges.forEach { - val (textRange, replacement) = it - if (replacement) { - textWithoutActions = - textWithoutActions.replace(text.substring(textRange.startOffset, textRange.endOffset), "") - } - } - onSubmit(textWithoutActions.trim()) + onSubmit(removeHighlightedText(text)) } }) } @@ -123,4 +120,22 @@ class CustomTextPane( ) } } + + private fun removeHighlightedText(text: String): String { + try { + var result = text + highlightedTextRanges.forEach { (textRange, replacement) -> + if (replacement) { + result = result.replace( + text.substring(textRange.startOffset, textRange.endOffset), + "" + ) + } + } + return result.trim() + } catch (e: Exception) { + logger.error("Error while removing highlighted text", e) + return text + } + } } \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/suggestion/SuggestionsPopupBuilder.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/suggestion/SuggestionsPopupBuilder.kt index 94bb76a1..f84518b8 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/suggestion/SuggestionsPopupBuilder.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/suggestion/SuggestionsPopupBuilder.kt @@ -49,7 +49,7 @@ class SuggestionsPopupBuilder { .createComponentPopupBuilder(popupPanel, preferableFocusComponent) .setMovable(true) .setCancelOnClickOutside(false) - .setCancelOnWindowDeactivation(false) + .setCancelOnWindowDeactivation(true) .setRequestFocus(true) .setMinSize(Dimension(480, 30)) .setCancelCallback(onCancel) diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/suggestion/SuggestionsPopupManager.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/suggestion/SuggestionsPopupManager.kt index 63c8373d..c2b1f269 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/suggestion/SuggestionsPopupManager.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/suggestion/SuggestionsPopupManager.kt @@ -33,19 +33,7 @@ class SuggestionsPopupManager( }) } private val list = SuggestionList(listModel, textPane) { - when (it) { - is SuggestionActionItem -> { - it.execute(project, textPane) - hidePopup() - } - - is SuggestionGroupItem -> { - selectedActionGroup = it - updateSuggestions() - textPane.appendHighlightedText(it.groupPrefix, withWhitespace = false) - textPane.requestFocus() - } - } + handleSuggestionItemSelection(it) } private val defaultActions: MutableList = mutableListOf( FileSuggestionGroupItem(project), @@ -110,6 +98,22 @@ class SuggestionsPopupManager( popup?.content?.repaint() } + private fun handleSuggestionItemSelection(item: SuggestionItem) { + when (item) { + is SuggestionActionItem -> { + hidePopup() + item.execute(project, textPane) + } + + is SuggestionGroupItem -> { + selectedActionGroup = item + updateSuggestions() + textPane.appendHighlightedText(item.groupPrefix, withWhitespace = false) + textPane.requestFocus() + } + } + } + private fun adjustPopupSize() { val maxVisibleRows = 15 val newRowCount = minOf(listModel.size(), maxVisibleRows) 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 1d33b2ea..5500cdef 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 @@ -8,6 +8,7 @@ import com.intellij.openapi.vfs.VirtualFile import ee.carlrobert.codegpt.CodeGPTBundle import ee.carlrobert.codegpt.CodeGPTKeys import ee.carlrobert.codegpt.settings.GeneralSettings +import ee.carlrobert.codegpt.settings.documentation.DocumentationSettings import ee.carlrobert.codegpt.settings.documentation.DocumentationsConfigurable import ee.carlrobert.codegpt.settings.persona.PersonaDetails import ee.carlrobert.codegpt.settings.persona.PersonaSettings @@ -64,6 +65,7 @@ class DocumentationActionItem( override fun execute(project: Project, textPane: CustomTextPane) { CodeGPTKeys.ADDED_DOCUMENTATION.set(project, documentationDetails) + service().updateLastUsedDateTime(documentationDetails.url) textPane.appendHighlightedText(documentationDetails.name, ':') } } @@ -77,6 +79,8 @@ class CreateDocumentationActionItem : SuggestionActionItem { override fun execute(project: Project, textPane: CustomTextPane) { val addDocumentationDialog = AddDocumentationDialog(project) if (addDocumentationDialog.showAndGet()) { + service() + .updateLastUsedDateTime(addDocumentationDialog.documentationDetails.url) textPane.appendHighlightedText( addDocumentationDialog.documentationDetails.name, searchChar = ':', @@ -120,5 +124,6 @@ class WebSearchActionItem(private val onWebSearchIncluded: () -> Unit) : Suggest override fun execute(project: Project, textPane: CustomTextPane) { onWebSearchIncluded() + textPane.appendHighlightedText("web") } } \ No newline at end of file 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 46e8e2aa..ff2b2391 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 @@ -16,6 +16,8 @@ import ee.carlrobert.codegpt.ui.textarea.FileSearchService import ee.carlrobert.codegpt.util.ResourceUtil.getDefaultPersonas import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import java.time.Instant +import java.time.format.DateTimeParseException class FileSuggestionGroupItem(private val project: Project) : SuggestionGroupItem { override val displayName: String = CodeGPTBundle.get("suggestionGroupItem.files.displayName") @@ -102,7 +104,7 @@ class DocumentationSuggestionGroupItem : SuggestionGroupItem { override suspend fun getSuggestions(searchText: String?): List = service().state.documentations - .take(10) + .sortedByDescending { parseDateTime(it.lastUsedDateTime) } .filter { if (searchText.isNullOrEmpty()) { true @@ -110,7 +112,18 @@ class DocumentationSuggestionGroupItem : SuggestionGroupItem { it.name?.contains(searchText, true) ?: false } } + .take(10) .map { DocumentationActionItem(DocumentationDetails(it.name ?: "", it.url ?: "")) } + listOf(CreateDocumentationActionItem(), ViewAllDocumentationsActionItem()) + + private fun parseDateTime(dateTimeString: String?): Instant { + return dateTimeString?.let { + try { + Instant.parse(it) + } catch (e: DateTimeParseException) { + Instant.EPOCH + } + } ?: Instant.EPOCH + } } \ No newline at end of file diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/suggestion/renderer/SuggestionItemRenderer.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/suggestion/renderer/SuggestionItemRenderer.kt index d28fccc7..3cff8861 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/suggestion/renderer/SuggestionItemRenderer.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/suggestion/renderer/SuggestionItemRenderer.kt @@ -36,6 +36,7 @@ abstract class BaseItemRenderer(private val textPane: CustomTextPane) : ItemRend val searchText = textPane.text.searchText() label.apply { this.icon = icon + disabledIcon = icon iconTextGap = 4 text = searchText?.let { title.highlightSearchText(it) } ?: title } @@ -98,7 +99,7 @@ class DefaultItemRenderer(textPane: CustomTextPane) : BaseItemRenderer(textPane) value.icon ?: EMPTY_ICON, getTitle(value), getDescription(value), - if (value.enabled) null else "This action can only be used with CodeGPT provider." + if (value.enabled) null else "This action can only be used with CodeGPT provider" ).apply { isEnabled = value.enabled } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 716ac65c..5f988eea 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -44,14 +44,14 @@ instance="ee.carlrobert.codegpt.settings.service.LlamaServiceConfigurable"/> - - + +