feat: add default docs and other minor improvements

This commit is contained in:
Carl-Robert Linnupuu 2024-08-13 22:33:53 +03:00
parent a2d71efd78
commit 2a0747f923
10 changed files with 114 additions and 34 deletions

View file

@ -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>(DocumentationSettingsState())
SimplePersistentStateComponent<DocumentationSettingsState>(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<DocumentationDetailsState>()
init {
documentations.addAll(DEFAULT_DOCUMENTATIONS)
}
}
class DocumentationDetailsState : BaseState() {
var name by string("CodeGPT Docs")
var url by string("https://docs.codegpt.ee")
}
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
}
}

View file

@ -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
}
}
}

View file

@ -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
}
}
}

View file

@ -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)

View file

@ -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<SuggestionItem> = 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)

View file

@ -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<DocumentationSettings>().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<DocumentationSettings>()
.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")
}
}

View file

@ -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<SuggestionActionItem> =
service<DocumentationSettings>().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
}
}

View file

@ -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
}

View file

@ -44,14 +44,14 @@
instance="ee.carlrobert.codegpt.settings.service.LlamaServiceConfigurable"/>
<applicationConfigurable id="settings.codegpt.services.ollama" parentId="settings.codegpt.services" displayName="Ollama (Local)"
instance="ee.carlrobert.codegpt.settings.service.ollama.OllamaSettingsConfigurable"/>
<applicationConfigurable id="settings.codegpt.configuration" parentId="settings.codegpt" displayName="Configuration"
instance="ee.carlrobert.codegpt.settings.configuration.ConfigurationConfigurable"/>
<applicationConfigurable id="settings.codegpt.advanced" parentId="settings.codegpt" displayName="Advanced Settings"
instance="ee.carlrobert.codegpt.settings.advanced.AdvancedSettingsConfigurable"/>
<applicationConfigurable id="settings.codegpt.personas" parentId="settings.codegpt" displayName="Personas"
instance="ee.carlrobert.codegpt.settings.persona.PersonasConfigurable"/>
<applicationConfigurable id="settings.codegpt.dcoumentations" parentId="settings.codegpt" displayName="Documentations"
instance="ee.carlrobert.codegpt.settings.documentation.DocumentationsConfigurable"/>
<applicationConfigurable id="settings.codegpt.configuration" parentId="settings.codegpt" displayName="Configuration"
instance="ee.carlrobert.codegpt.settings.configuration.ConfigurationConfigurable"/>
<applicationConfigurable id="settings.codegpt.advanced" parentId="settings.codegpt" displayName="Advanced Settings"
instance="ee.carlrobert.codegpt.settings.advanced.AdvancedSettingsConfigurable"/>
<applicationConfigurable
parentId="settings.codegpt"
instance="ee.carlrobert.codegpt.telemetry.ui.preferences.TelemetryConfigurable"

View file

@ -260,6 +260,6 @@ suggestionGroupItem.folders.displayName=Folders
suggestionGroupItem.personas.displayName=Personas
suggestionGroupItem.docs.displayName=Docs
suggestionActionItem.webSearch.displayName=Web
suggestionActionItem.viewDocumentations.displayName=View all
suggestionActionItem.viewDocumentations.displayName=View all docs
suggestionActionItem.createPersona.displayName=Create new persona
suggestionActionItem.createDocumentation.displayName=Create new documentation