feat(intellij): update tabby-agent to 0.3.1. (#490)
parent
4ebad71805
commit
52c4ef38d3
|
|
@ -24,7 +24,7 @@ jobs:
|
|||
distribution: zulu
|
||||
java-version: 17
|
||||
- name: Test Build
|
||||
uses: gradle/gradle-build-action@v2
|
||||
uses: gradle/gradle-build-action@v2.4.2
|
||||
with:
|
||||
arguments: buildPlugin
|
||||
build-root-directory: clients/intellij
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ plugins {
|
|||
}
|
||||
|
||||
group = "com.tabbyml"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0-dev"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -8,7 +8,6 @@ import com.intellij.openapi.application.invokeLater
|
|||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.tabbyml.intellijtabby.agent.Agent
|
||||
import com.tabbyml.intellijtabby.agent.AgentService
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
|
@ -89,7 +88,7 @@ class CheckIssueDetail : AnAction() {
|
|||
|
||||
override fun update(e: AnActionEvent) {
|
||||
val agentService = service<AgentService>()
|
||||
e.presentation.isVisible = agentService.status.value == Agent.Status.ISSUES_EXIST
|
||||
e.presentation.isVisible = agentService.currentIssue.value != null
|
||||
}
|
||||
|
||||
override fun getActionUpdateThread(): ActionUpdateThread {
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
package com.tabbyml.intellijtabby.actions
|
||||
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.components.service
|
||||
import com.tabbyml.intellijtabby.settings.ApplicationSettingsState
|
||||
|
||||
class ToggleAutoCompletionEnabled : AnAction() {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val settings = service<ApplicationSettingsState>()
|
||||
settings.isAutoCompletionEnabled = !settings.isAutoCompletionEnabled
|
||||
}
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
val settings = service<ApplicationSettingsState>()
|
||||
if (settings.isAutoCompletionEnabled) {
|
||||
e.presentation.text = "Disable Auto Completion"
|
||||
e.presentation.description = "Tabby does not show completion suggestions automatically, you can still request them on demand."
|
||||
} else {
|
||||
e.presentation.text = "Enable Auto Completion"
|
||||
e.presentation.description = "Tabby shows inline completion suggestions automatically."
|
||||
}
|
||||
}
|
||||
|
||||
override fun getActionUpdateThread(): ActionUpdateThread {
|
||||
return ActionUpdateThread.BGT
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tabbyml.intellijtabby.actions
|
||||
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.components.service
|
||||
import com.tabbyml.intellijtabby.settings.ApplicationSettingsState
|
||||
|
||||
class ToggleInlineCompletionTriggerMode : AnAction() {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val settings = service<ApplicationSettingsState>()
|
||||
settings.completionTriggerMode = when (settings.completionTriggerMode) {
|
||||
ApplicationSettingsState.TriggerMode.AUTOMATIC -> ApplicationSettingsState.TriggerMode.MANUAL
|
||||
ApplicationSettingsState.TriggerMode.MANUAL -> ApplicationSettingsState.TriggerMode.AUTOMATIC
|
||||
}
|
||||
}
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
val settings = service<ApplicationSettingsState>()
|
||||
if (settings.completionTriggerMode == ApplicationSettingsState.TriggerMode.AUTOMATIC) {
|
||||
e.presentation.text = "Switch to Manual Mode"
|
||||
e.presentation.description = "Manual trigger inline completion suggestions by pressing `Alt + \\`."
|
||||
} else {
|
||||
e.presentation.text = "Switch to Automatic Mode"
|
||||
e.presentation.description = "Show inline completion suggestions automatically."
|
||||
}
|
||||
}
|
||||
|
||||
override fun getActionUpdateThread(): ActionUpdateThread {
|
||||
return ActionUpdateThread.BGT
|
||||
}
|
||||
}
|
||||
|
|
@ -5,15 +5,15 @@ import com.intellij.openapi.actionSystem.AnAction
|
|||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||
import com.intellij.openapi.components.service
|
||||
import com.tabbyml.intellijtabby.editor.CompletionScheduler
|
||||
import com.tabbyml.intellijtabby.editor.CompletionProvider
|
||||
|
||||
|
||||
class TriggerCompletion : AnAction() {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val completionScheduler = service<CompletionScheduler>()
|
||||
val completionScheduler = service<CompletionProvider>()
|
||||
val editor = e.getRequiredData(CommonDataKeys.EDITOR)
|
||||
val offset = editor.caretModel.primaryCaret.offset
|
||||
completionScheduler.schedule(editor, offset, manually = true)
|
||||
completionScheduler.provideCompletion(editor, offset, manually = true)
|
||||
}
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ class Agent : ProcessAdapter() {
|
|||
READY,
|
||||
DISCONNECTED,
|
||||
UNAUTHORIZED,
|
||||
ISSUES_EXIST,
|
||||
}
|
||||
|
||||
private val statusFlow = MutableStateFlow(Status.NOT_INITIALIZED)
|
||||
|
|
@ -140,17 +139,30 @@ class Agent : ProcessAdapter() {
|
|||
)
|
||||
}
|
||||
|
||||
suspend fun initialize(config: Config, client: String): Boolean {
|
||||
data class ClientProperties(
|
||||
val user: Map<String, Any>,
|
||||
val session: Map<String, Any>,
|
||||
)
|
||||
|
||||
suspend fun initialize(config: Config, clientProperties: ClientProperties): Boolean {
|
||||
return request(
|
||||
"initialize", listOf(
|
||||
mapOf(
|
||||
"config" to config,
|
||||
"client" to client,
|
||||
"clientProperties" to clientProperties,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun finalize(): Boolean {
|
||||
return request("finalize", listOf())
|
||||
}
|
||||
|
||||
suspend fun updateClientProperties(type: String, key: String, value: Any): Boolean {
|
||||
return request("updateClientProperties", listOf(type, key, value))
|
||||
}
|
||||
|
||||
suspend fun updateConfig(key: String, config: Any): Boolean {
|
||||
return request("updateConfig", listOf(key, config))
|
||||
}
|
||||
|
|
@ -159,14 +171,44 @@ class Agent : ProcessAdapter() {
|
|||
return request("clearConfig", listOf(key))
|
||||
}
|
||||
|
||||
suspend fun getIssues(): List<Map<String, Any>> {
|
||||
suspend fun getConfig(): Config {
|
||||
return request("getConfig", listOf())
|
||||
}
|
||||
|
||||
suspend fun getStatus(): Status {
|
||||
return request("getStatus", listOf())
|
||||
}
|
||||
|
||||
suspend fun getIssues(): List<String> {
|
||||
return request("getIssues", listOf())
|
||||
}
|
||||
|
||||
data class GetIssueDetailOptions(
|
||||
val index: Int? = null,
|
||||
val name: String? = null,
|
||||
)
|
||||
|
||||
suspend fun getIssueDetail(options: GetIssueDetailOptions): Map<String, Any>? {
|
||||
return request("getIssueDetail", listOf(options))
|
||||
}
|
||||
|
||||
suspend fun getServerHealthState(): Map<String, Any>? {
|
||||
return request("getServerHealthState", listOf())
|
||||
}
|
||||
|
||||
data class AuthUrlResponse(
|
||||
val authUrl: String,
|
||||
val code: String,
|
||||
)
|
||||
|
||||
suspend fun requestAuthUrl(): AuthUrlResponse? {
|
||||
return request("requestAuthUrl", listOf(ABORT_SIGNAL_ENABLED))
|
||||
}
|
||||
|
||||
suspend fun waitForAuthToken(code: String) {
|
||||
return request("waitForAuthToken", listOf(code, ABORT_SIGNAL_ENABLED))
|
||||
}
|
||||
|
||||
data class CompletionRequest(
|
||||
val filepath: String,
|
||||
val language: String,
|
||||
|
|
@ -185,14 +227,6 @@ class Agent : ProcessAdapter() {
|
|||
)
|
||||
}
|
||||
|
||||
suspend fun requestAuthUrl(): AuthUrlResponse? {
|
||||
return request("requestAuthUrl", listOf(ABORT_SIGNAL_ENABLED))
|
||||
}
|
||||
|
||||
suspend fun waitForAuthToken(code: String) {
|
||||
return request("waitForAuthToken", listOf(code, ABORT_SIGNAL_ENABLED))
|
||||
}
|
||||
|
||||
suspend fun provideCompletions(request: CompletionRequest): CompletionResponse? {
|
||||
return request("provideCompletions", listOf(request, ABORT_SIGNAL_ENABLED))
|
||||
}
|
||||
|
|
@ -201,6 +235,7 @@ class Agent : ProcessAdapter() {
|
|||
val type: EventType,
|
||||
@SerializedName("completion_id") val completionId: String,
|
||||
@SerializedName("choice_index") val choiceIndex: Int,
|
||||
@SerializedName("select_kind") val selectKind: SelectKind? = null,
|
||||
) {
|
||||
enum class EventType {
|
||||
@SerializedName("view")
|
||||
|
|
@ -209,16 +244,17 @@ class Agent : ProcessAdapter() {
|
|||
@SerializedName("select")
|
||||
SELECT,
|
||||
}
|
||||
|
||||
enum class SelectKind {
|
||||
@SerializedName("line")
|
||||
LINE,
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun postEvent(event: LogEventRequest) {
|
||||
request<Any>("postEvent", listOf(event, ABORT_SIGNAL_ENABLED))
|
||||
}
|
||||
|
||||
data class AuthUrlResponse(
|
||||
val authUrl: String,
|
||||
val code: String,
|
||||
)
|
||||
|
||||
fun close() {
|
||||
try {
|
||||
|
|
@ -304,12 +340,8 @@ class Agent : ProcessAdapter() {
|
|||
"ready" -> Status.READY
|
||||
"disconnected" -> Status.DISCONNECTED
|
||||
"unauthorized" -> Status.UNAUTHORIZED
|
||||
"issuesExist" -> Status.ISSUES_EXIST
|
||||
else -> Status.NOT_INITIALIZED
|
||||
}
|
||||
if (statusFlow.value !== Status.ISSUES_EXIST) {
|
||||
currentIssueFlow.value = null
|
||||
}
|
||||
}
|
||||
|
||||
"configUpdated" -> {
|
||||
|
|
@ -321,9 +353,9 @@ class Agent : ProcessAdapter() {
|
|||
authRequiredEventFlow.tryEmit(Unit)
|
||||
}
|
||||
|
||||
"newIssue" -> {
|
||||
"issuesUpdated" -> {
|
||||
logger.info("Agent notification $event")
|
||||
currentIssueFlow.value = (event["issue"] as Map<*, *>)["name"] as String?
|
||||
currentIssueFlow.value = (event["issues"] as List<*>).firstOrNull() as String?
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
|
|
|||
|
|
@ -26,22 +26,27 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
@Service
|
||||
class AgentService : Disposable {
|
||||
private val logger = Logger.getInstance(AgentService::class.java)
|
||||
private var agent: Agent = Agent()
|
||||
val scope: CoroutineScope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
private var initFailedNotification: Notification? = null
|
||||
var authNotification: Notification? = null
|
||||
private set
|
||||
var issueNotification: Notification? = null
|
||||
private set
|
||||
|
||||
private var completionResponseWarningShown = false
|
||||
|
||||
enum class Status {
|
||||
INITIALIZING,
|
||||
INITIALIZATION_FAILED,
|
||||
}
|
||||
|
||||
private var initResultFlow: MutableStateFlow<Boolean?> = MutableStateFlow(null)
|
||||
val status = initResultFlow.combine(agent.status) { initResult, agentStatus ->
|
||||
if (initResult == null) {
|
||||
|
|
@ -59,14 +64,12 @@ class AgentService : Disposable {
|
|||
val settings = service<ApplicationSettingsState>()
|
||||
val anonymousUsageLogger = service<AnonymousUsageLogger>()
|
||||
scope.launch {
|
||||
val appInfo = ApplicationInfo.getInstance().fullApplicationName
|
||||
val pluginId = "com.tabbyml.intellij-tabby"
|
||||
val pluginVersion = PluginManagerCore.getPlugin(PluginId.getId(pluginId))?.version
|
||||
val client = "$appInfo $pluginId $pluginVersion"
|
||||
|
||||
val config = createAgentConfig(settings.data)
|
||||
val clientProperties = createClientProperties(settings.data)
|
||||
try {
|
||||
agent.open()
|
||||
agent.initialize(createAgentConfig(settings.data), client)
|
||||
agent.initialize(config, clientProperties)
|
||||
initResultFlow.value = true
|
||||
logger.info("Agent init done.")
|
||||
} catch (e: Exception) {
|
||||
|
|
@ -74,7 +77,7 @@ class AgentService : Disposable {
|
|||
logger.warn("Agent init failed: $e")
|
||||
anonymousUsageLogger.event(
|
||||
"IntelliJInitFailed", mapOf(
|
||||
"client" to client, "error" to e.stackTraceToString()
|
||||
"client" to clientProperties.session["client"] as String, "error" to e.stackTraceToString()
|
||||
)
|
||||
)
|
||||
val notification = Notification(
|
||||
|
|
@ -99,6 +102,7 @@ class AgentService : Disposable {
|
|||
} else {
|
||||
clearConfig("server.endpoint")
|
||||
}
|
||||
updateClientProperties("user", "intellij.triggerMode", it.completionTriggerMode)
|
||||
updateConfig("anonymousUsageTracking.disable", it.isAnonymousUsageTrackingDisabled)
|
||||
}
|
||||
}
|
||||
|
|
@ -127,6 +131,10 @@ class AgentService : Disposable {
|
|||
"highCompletionTimeoutRate" -> "Most completion requests timed out"
|
||||
else -> return@collect
|
||||
}
|
||||
if (completionResponseWarningShown) {
|
||||
return@collect
|
||||
}
|
||||
completionResponseWarningShown = true
|
||||
val notification = Notification(
|
||||
"com.tabbyml.intellijtabby.notification.warning",
|
||||
content,
|
||||
|
|
@ -161,10 +169,36 @@ class AgentService : Disposable {
|
|||
)
|
||||
}
|
||||
|
||||
private fun createClientProperties(state: ApplicationSettingsState.State): Agent.ClientProperties {
|
||||
val appInfo = ApplicationInfo.getInstance()
|
||||
val appVersion = appInfo.fullVersion
|
||||
val appName = appInfo.fullApplicationName.replace(appVersion, "").trim()
|
||||
val pluginId = "com.tabbyml.intellij-tabby"
|
||||
val pluginVersion = PluginManagerCore.getPlugin(PluginId.getId(pluginId))?.version
|
||||
val client = "$appName $pluginId $pluginVersion"
|
||||
return Agent.ClientProperties(
|
||||
user = mapOf(
|
||||
"intellij" to mapOf(
|
||||
"triggerMode" to state.completionTriggerMode,
|
||||
),
|
||||
),
|
||||
session = mapOf(
|
||||
"client" to client,
|
||||
"ide" to mapOf("name" to appName, "version" to appVersion),
|
||||
"tabby_plugin" to mapOf("name" to pluginId, "version" to pluginVersion),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun waitForInitialized() {
|
||||
agent.status.first { it != Agent.Status.NOT_INITIALIZED }
|
||||
}
|
||||
|
||||
private suspend fun updateClientProperties(type: String, key: String, config: Any) {
|
||||
waitForInitialized()
|
||||
agent.updateClientProperties(type, key, config)
|
||||
}
|
||||
|
||||
private suspend fun updateConfig(key: String, config: Any) {
|
||||
waitForInitialized()
|
||||
agent.updateConfig(key, config)
|
||||
|
|
@ -235,7 +269,7 @@ class AgentService : Disposable {
|
|||
|
||||
suspend fun getCurrentIssueDetail(): Map<String, Any>? {
|
||||
waitForInitialized()
|
||||
return agent.getIssues().firstOrNull { it["name"] == currentIssue.value }
|
||||
return agent.getIssueDetail(Agent.GetIssueDetailOptions(name = currentIssue.value))
|
||||
}
|
||||
|
||||
suspend fun getServerHealthState(): Map<String, Any>? {
|
||||
|
|
@ -244,6 +278,11 @@ class AgentService : Disposable {
|
|||
}
|
||||
|
||||
override fun dispose() {
|
||||
runBlocking {
|
||||
runCatching {
|
||||
agent.finalize()
|
||||
}
|
||||
}
|
||||
agent.close()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,44 +5,41 @@ import com.intellij.openapi.components.service
|
|||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.tabbyml.intellijtabby.agent.AgentService
|
||||
import com.tabbyml.intellijtabby.settings.ApplicationSettingsState
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Service
|
||||
class CompletionScheduler {
|
||||
private val logger = Logger.getInstance(CompletionScheduler::class.java)
|
||||
class CompletionProvider {
|
||||
private val logger = Logger.getInstance(CompletionProvider::class.java)
|
||||
|
||||
data class CompletionContext(val editor: Editor, val offset: Int, val job: Job)
|
||||
|
||||
var scheduled: CompletionContext? = null
|
||||
private set
|
||||
private val ongoingCompletionFlow: MutableStateFlow<CompletionContext?> = MutableStateFlow(null)
|
||||
val ongoingCompletion = ongoingCompletionFlow.asStateFlow()
|
||||
|
||||
fun schedule(editor: Editor, offset: Int, manually: Boolean = false) {
|
||||
fun provideCompletion(editor: Editor, offset: Int, manually: Boolean = false) {
|
||||
val agentService = service<AgentService>()
|
||||
val inlineCompletionService = service<InlineCompletionService>()
|
||||
val settings = service<ApplicationSettingsState>()
|
||||
clear()
|
||||
val job = agentService.scope.launch {
|
||||
if (!manually && !settings.isAutoCompletionEnabled) {
|
||||
return@launch
|
||||
}
|
||||
|
||||
logger.info("Trigger completion at $offset")
|
||||
agentService.provideCompletion(editor, offset, manually)?.let {
|
||||
logger.info("Show completion at $offset: $it")
|
||||
inlineCompletionService.show(editor, offset, it)
|
||||
ongoingCompletionFlow.value = null
|
||||
}
|
||||
}
|
||||
scheduled = CompletionContext(editor, offset, job)
|
||||
ongoingCompletionFlow.value = CompletionContext(editor, offset, job)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
val inlineCompletionService = service<InlineCompletionService>()
|
||||
inlineCompletionService.dismiss()
|
||||
scheduled?.let {
|
||||
ongoingCompletionFlow.value?.let {
|
||||
it.job.cancel()
|
||||
scheduled = null
|
||||
ongoingCompletionFlow.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import com.intellij.openapi.fileEditor.FileEditorManager
|
|||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
|
||||
import com.intellij.openapi.fileEditor.FileEditorManagerListener
|
||||
import com.intellij.util.messages.MessageBusConnection
|
||||
import com.tabbyml.intellijtabby.settings.ApplicationSettingsState
|
||||
|
||||
class EditorListener : EditorFactoryListener {
|
||||
private val logger = Logger.getInstance(EditorListener::class.java)
|
||||
|
|
@ -16,14 +17,17 @@ class EditorListener : EditorFactoryListener {
|
|||
override fun editorCreated(event: EditorFactoryEvent) {
|
||||
val editor = event.editor
|
||||
val editorManager = editor.project?.let { FileEditorManager.getInstance(it) } ?: return
|
||||
val completionScheduler = service<CompletionScheduler>()
|
||||
val settings = service<ApplicationSettingsState>()
|
||||
val completionProvider = service<CompletionProvider>()
|
||||
|
||||
editor.caretModel.addCaretListener(object : CaretListener {
|
||||
override fun caretPositionChanged(event: CaretEvent) {
|
||||
if (editorManager.selectedTextEditor == editor) {
|
||||
completionScheduler.scheduled?.let {
|
||||
if (it.editor != editor || it.offset != editor.caretModel.primaryCaret.offset) {
|
||||
completionScheduler.clear()
|
||||
completionProvider.ongoingCompletion.value.let {
|
||||
if (it != null && it.editor == editor && it.offset == editor.caretModel.primaryCaret.offset) {
|
||||
// keep ongoing completion
|
||||
} else {
|
||||
completionProvider.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -33,8 +37,10 @@ class EditorListener : EditorFactoryListener {
|
|||
editor.document.addDocumentListener(object : DocumentListener {
|
||||
override fun documentChanged(event: DocumentEvent) {
|
||||
if (editorManager.selectedTextEditor == editor) {
|
||||
val offset = event.offset + event.newFragment.length
|
||||
completionScheduler.schedule(editor, offset)
|
||||
if (settings.completionTriggerMode == ApplicationSettingsState.TriggerMode.AUTOMATIC) {
|
||||
val offset = event.offset + event.newFragment.length
|
||||
completionProvider.provideCompletion(editor, offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -42,10 +48,10 @@ class EditorListener : EditorFactoryListener {
|
|||
editor.project?.messageBus?.connect()?.let {
|
||||
it.subscribe(
|
||||
FileEditorManagerListener.FILE_EDITOR_MANAGER,
|
||||
object: FileEditorManagerListener {
|
||||
object : FileEditorManagerListener {
|
||||
override fun selectionChanged(event: FileEditorManagerEvent) {
|
||||
logger.info("FileEditorManagerListener selectionChanged.")
|
||||
completionScheduler.clear()
|
||||
completionProvider.clear()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -54,9 +60,6 @@ class EditorListener : EditorFactoryListener {
|
|||
}
|
||||
|
||||
override fun editorReleased(event: EditorFactoryEvent) {
|
||||
messagesConnection[event.editor]?.let {
|
||||
it.disconnect()
|
||||
it.dispose()
|
||||
}
|
||||
messagesConnection[event.editor]?.disconnect()
|
||||
}
|
||||
}
|
||||
|
|
@ -18,21 +18,21 @@ class ApplicationConfigurable : Configurable {
|
|||
|
||||
override fun isModified(): Boolean {
|
||||
val settings = service<ApplicationSettingsState>()
|
||||
return settingsPanel.isAutoCompletionEnabled != settings.isAutoCompletionEnabled
|
||||
return settingsPanel.completionTriggerMode != settings.completionTriggerMode
|
||||
|| settingsPanel.serverEndpoint != settings.serverEndpoint
|
||||
|| settingsPanel.isAnonymousUsageTrackingDisabled != settings.isAnonymousUsageTrackingDisabled
|
||||
}
|
||||
|
||||
override fun apply() {
|
||||
val settings = service<ApplicationSettingsState>()
|
||||
settings.isAutoCompletionEnabled = settingsPanel.isAutoCompletionEnabled
|
||||
settings.completionTriggerMode = settingsPanel.completionTriggerMode
|
||||
settings.serverEndpoint = settingsPanel.serverEndpoint
|
||||
settings.isAnonymousUsageTrackingDisabled = settingsPanel.isAnonymousUsageTrackingDisabled
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
val settings = service<ApplicationSettingsState>()
|
||||
settingsPanel.isAutoCompletionEnabled = settings.isAutoCompletionEnabled
|
||||
settingsPanel.completionTriggerMode = settings.completionTriggerMode
|
||||
settingsPanel.serverEndpoint = settings.serverEndpoint
|
||||
settingsPanel.isAnonymousUsageTrackingDisabled = settings.isAnonymousUsageTrackingDisabled
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,62 @@
|
|||
package com.tabbyml.intellijtabby.settings
|
||||
|
||||
import com.intellij.ui.components.JBCheckBox
|
||||
import com.intellij.ui.components.JBRadioButton
|
||||
import com.intellij.ui.components.JBTextField
|
||||
import com.intellij.util.ui.FormBuilder
|
||||
import javax.swing.ButtonGroup
|
||||
import javax.swing.JPanel
|
||||
|
||||
class ApplicationSettingsPanel {
|
||||
private val isAutoCompletionEnabledCheckBox = JBCheckBox("Enable auto completion")
|
||||
private val serverEndpointTextField = JBTextField()
|
||||
private val isAnonymousUsageTrackingDisabledCheckBox = JBCheckBox("Disable anonymous usage tracking")
|
||||
private val serverEndpointPanel = FormBuilder.createFormBuilder()
|
||||
.addComponent(serverEndpointTextField)
|
||||
.addTooltip(
|
||||
"""
|
||||
<html>
|
||||
A http or https URL of Tabby server endpoint.<br/>
|
||||
If leave empty, server endpoint config in <i>~/.tabby-client/agent/config.toml</i> will be used<br/>
|
||||
Default to <i>http://localhost:8080</i>.
|
||||
</html>
|
||||
""".trimIndent()
|
||||
)
|
||||
.panel
|
||||
|
||||
private val completionTriggerModeAutomaticRadioButton = JBRadioButton("Automatic")
|
||||
private val completionTriggerModeManualRadioButton = JBRadioButton("Manual")
|
||||
private val completionTriggerModeRadioGroup = ButtonGroup().apply {
|
||||
add(completionTriggerModeAutomaticRadioButton)
|
||||
add(completionTriggerModeManualRadioButton)
|
||||
}
|
||||
private val completionTriggerModePanel: JPanel = FormBuilder.createFormBuilder()
|
||||
.addComponent(completionTriggerModeAutomaticRadioButton)
|
||||
.addTooltip("Trigger automatically when you stop typing")
|
||||
.addComponent(completionTriggerModeManualRadioButton)
|
||||
.addTooltip("Trigger manually by pressing `Alt + \\`")
|
||||
.panel
|
||||
|
||||
private val isAnonymousUsageTrackingDisabledCheckBox = JBCheckBox("Disable")
|
||||
|
||||
val mainPanel: JPanel = FormBuilder.createFormBuilder()
|
||||
.addLabeledComponent("Server endpoint", serverEndpointTextField, 1, false)
|
||||
.addTooltip("A http or https URL of Tabby server endpoint.")
|
||||
.addTooltip("If leave empty, server endpoint config in `~/.tabby-client/agent/config.toml` will be used")
|
||||
.addTooltip("Default to 'http://localhost:8080'.")
|
||||
.addSeparator()
|
||||
.addComponent(isAutoCompletionEnabledCheckBox, 1)
|
||||
.addComponent(isAnonymousUsageTrackingDisabledCheckBox, 1)
|
||||
.addLabeledComponent("Server endpoint", serverEndpointPanel, 5, false)
|
||||
.addSeparator(5)
|
||||
.addLabeledComponent("Inline completion trigger", completionTriggerModePanel, 5, false)
|
||||
.addSeparator(5)
|
||||
.addLabeledComponent("Anonymous usage tracking", isAnonymousUsageTrackingDisabledCheckBox, 5, false)
|
||||
.addComponentFillVertically(JPanel(), 0)
|
||||
.panel
|
||||
|
||||
var isAutoCompletionEnabled: Boolean
|
||||
get() = isAutoCompletionEnabledCheckBox.isSelected
|
||||
var completionTriggerMode: ApplicationSettingsState.TriggerMode
|
||||
get() = if (completionTriggerModeAutomaticRadioButton.isSelected) {
|
||||
ApplicationSettingsState.TriggerMode.AUTOMATIC
|
||||
} else {
|
||||
ApplicationSettingsState.TriggerMode.MANUAL
|
||||
}
|
||||
set(value) {
|
||||
isAutoCompletionEnabledCheckBox.isSelected = value
|
||||
when (value) {
|
||||
ApplicationSettingsState.TriggerMode.AUTOMATIC -> completionTriggerModeAutomaticRadioButton.isSelected = true
|
||||
ApplicationSettingsState.TriggerMode.MANUAL -> completionTriggerModeManualRadioButton.isSelected = true
|
||||
}
|
||||
}
|
||||
|
||||
var serverEndpoint: String
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tabbyml.intellijtabby.settings
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.intellij.openapi.components.PersistentStateComponent
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.State
|
||||
|
|
@ -14,7 +15,14 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||
storages = [Storage("intellij-tabby.xml")]
|
||||
)
|
||||
class ApplicationSettingsState : PersistentStateComponent<ApplicationSettingsState> {
|
||||
var isAutoCompletionEnabled: Boolean = true
|
||||
enum class TriggerMode {
|
||||
@SerializedName("manual")
|
||||
MANUAL,
|
||||
@SerializedName("automatic")
|
||||
AUTOMATIC,
|
||||
}
|
||||
|
||||
var completionTriggerMode: TriggerMode = TriggerMode.AUTOMATIC
|
||||
set(value) {
|
||||
field = value
|
||||
stateFlow.value = this.data
|
||||
|
|
@ -31,14 +39,14 @@ class ApplicationSettingsState : PersistentStateComponent<ApplicationSettingsSta
|
|||
}
|
||||
|
||||
data class State(
|
||||
val isAutoCompletionEnabled: Boolean,
|
||||
val completionTriggerMode: TriggerMode,
|
||||
val serverEndpoint: String,
|
||||
val isAnonymousUsageTrackingDisabled: Boolean,
|
||||
)
|
||||
|
||||
val data: State
|
||||
get() = State(
|
||||
isAutoCompletionEnabled = isAutoCompletionEnabled,
|
||||
completionTriggerMode = completionTriggerMode,
|
||||
serverEndpoint = serverEndpoint,
|
||||
isAnonymousUsageTrackingDisabled = isAnonymousUsageTrackingDisabled,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,14 +11,17 @@ import com.intellij.openapi.vfs.VirtualFile
|
|||
import com.intellij.openapi.wm.StatusBarWidget
|
||||
import com.intellij.openapi.wm.impl.status.EditorBasedStatusBarPopup
|
||||
import com.intellij.openapi.wm.impl.status.widget.StatusBarEditorBasedWidgetFactory
|
||||
import com.intellij.ui.AnimatedIcon
|
||||
import com.tabbyml.intellijtabby.agent.Agent
|
||||
import com.tabbyml.intellijtabby.agent.AgentService
|
||||
import com.tabbyml.intellijtabby.editor.CompletionProvider
|
||||
import com.tabbyml.intellijtabby.settings.ApplicationSettingsState
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.swing.Icon
|
||||
|
||||
class StatusBarWidgetFactory : StatusBarEditorBasedWidgetFactory() {
|
||||
override fun getId(): String {
|
||||
|
|
@ -30,20 +33,28 @@ class StatusBarWidgetFactory : StatusBarEditorBasedWidgetFactory() {
|
|||
}
|
||||
|
||||
override fun createWidget(project: Project): StatusBarWidget {
|
||||
data class CombinedState(
|
||||
val settings: ApplicationSettingsState.State,
|
||||
val agentStatus: Enum<*>,
|
||||
val currentIssue: String?,
|
||||
val ongoingCompletion: CompletionProvider.CompletionContext?,
|
||||
)
|
||||
|
||||
return object : EditorBasedStatusBarPopup(project, false) {
|
||||
val updateStatusScope: CoroutineScope = CoroutineScope(Dispatchers.Main)
|
||||
val text = "Tabby"
|
||||
var icon = AllIcons.Actions.Refresh
|
||||
var icon: Icon = AnimatedIcon.Default()
|
||||
var tooltip = "Tabby: Initializing"
|
||||
|
||||
init {
|
||||
val settings = service<ApplicationSettingsState>()
|
||||
val agentService = service<AgentService>()
|
||||
val completionProvider = service<CompletionProvider>()
|
||||
updateStatusScope.launch {
|
||||
combine(settings.state, agentService.status, agentService.currentIssue) { settings, agentStatus, currentIssue ->
|
||||
Triple(settings, agentStatus, currentIssue)
|
||||
combine(settings.state, agentService.status, agentService.currentIssue, completionProvider.ongoingCompletion) { settings, agentStatus, currentIssue, ongoingCompletion ->
|
||||
CombinedState(settings, agentStatus, currentIssue, ongoingCompletion)
|
||||
}.collect {
|
||||
updateStatus(it.first, it.second, it.third)
|
||||
updateStatus(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -74,7 +85,7 @@ class StatusBarWidgetFactory : StatusBarEditorBasedWidgetFactory() {
|
|||
return arrayOf(
|
||||
actionManager.getAction("Tabby.OpenAuthPage"),
|
||||
actionManager.getAction("Tabby.CheckIssueDetail"),
|
||||
actionManager.getAction("Tabby.ToggleAutoCompletionEnabled"),
|
||||
actionManager.getAction("Tabby.ToggleInlineCompletionTriggerMode"),
|
||||
actionManager.getAction("Tabby.OpenSettings"),
|
||||
)
|
||||
}
|
||||
|
|
@ -86,41 +97,51 @@ class StatusBarWidgetFactory : StatusBarEditorBasedWidgetFactory() {
|
|||
)
|
||||
}
|
||||
|
||||
private fun updateStatus(settingsState: ApplicationSettingsState.State, agentStatus: Enum<*>, currentIssue: String?) {
|
||||
if (!settingsState.isAutoCompletionEnabled) {
|
||||
icon = AllIcons.Windows.CloseSmall
|
||||
tooltip = "Tabby: Auto completion is disabled"
|
||||
} else {
|
||||
when(agentStatus) {
|
||||
AgentService.Status.INITIALIZING, Agent.Status.NOT_INITIALIZED -> {
|
||||
icon = AllIcons.Actions.Refresh
|
||||
tooltip = "Tabby: Initializing"
|
||||
}
|
||||
AgentService.Status.INITIALIZATION_FAILED -> {
|
||||
icon = AllIcons.General.Error
|
||||
tooltip = "Tabby: Initialization failed"
|
||||
}
|
||||
Agent.Status.READY -> {
|
||||
icon = AllIcons.Actions.Checked
|
||||
tooltip = "Tabby: Ready"
|
||||
}
|
||||
Agent.Status.DISCONNECTED -> {
|
||||
icon = AllIcons.General.Error
|
||||
tooltip = "Tabby: Cannot connect to Server"
|
||||
}
|
||||
Agent.Status.UNAUTHORIZED -> {
|
||||
private fun updateStatus(state: CombinedState) {
|
||||
when(state.agentStatus) {
|
||||
AgentService.Status.INITIALIZING, Agent.Status.NOT_INITIALIZED -> {
|
||||
icon = AnimatedIcon.Default()
|
||||
tooltip = "Tabby: Initializing"
|
||||
}
|
||||
AgentService.Status.INITIALIZATION_FAILED -> {
|
||||
icon = AllIcons.General.Error
|
||||
tooltip = "Tabby: Initialization failed"
|
||||
}
|
||||
Agent.Status.READY -> {
|
||||
if (state.currentIssue != null) {
|
||||
icon = AllIcons.General.Warning
|
||||
tooltip = "Tabby: Requires authorization"
|
||||
}
|
||||
Agent.Status.ISSUES_EXIST -> {
|
||||
icon = AllIcons.General.Warning
|
||||
tooltip = when(currentIssue) {
|
||||
tooltip = when(state.currentIssue) {
|
||||
"slowCompletionResponseTime" -> "Tabby: Completion requests appear to take too much time"
|
||||
"highCompletionTimeoutRate" -> "Tabby: Most completion requests timed out"
|
||||
else -> "Tabby: Issues exist"
|
||||
}
|
||||
} else {
|
||||
when (state.settings.completionTriggerMode) {
|
||||
ApplicationSettingsState.TriggerMode.AUTOMATIC -> {
|
||||
icon = AllIcons.Actions.Checked
|
||||
tooltip = "Tabby: Automatic code completion is enabled"
|
||||
}
|
||||
|
||||
ApplicationSettingsState.TriggerMode.MANUAL -> {
|
||||
if (state.ongoingCompletion == null) {
|
||||
icon = AllIcons.General.ChevronRight
|
||||
tooltip = "Tabby: Standing by, press `Alt + \\` to trigger code completion."
|
||||
} else {
|
||||
icon = AnimatedIcon.Default()
|
||||
tooltip = "Tabby: Generating code completions"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Agent.Status.DISCONNECTED -> {
|
||||
icon = AllIcons.General.Error
|
||||
tooltip = "Tabby: Cannot connect to Server, please check settings"
|
||||
}
|
||||
Agent.Status.UNAUTHORIZED -> {
|
||||
icon = AllIcons.General.Warning
|
||||
tooltip = "Tabby: Authorization required, click to continue"
|
||||
}
|
||||
}
|
||||
invokeLater {
|
||||
update { myStatusBar?.updateWidget(ID()) }
|
||||
|
|
|
|||
|
|
@ -22,17 +22,7 @@
|
|||
<h2 id="demo">Demo</h2>
|
||||
<p>Try our online demo <a href="https://tabby.tabbyml.com/playground/">here</a>.</p>
|
||||
<h2 id="requirements">Requirements</h2>
|
||||
Tabby plugin requires <a href="https://nodejs.org/">Node.js</a> 16.0+ installed and added into <code>PATH</code> enviroment variable. </p>
|
||||
<h2 id="get-started">Get Started</h2>
|
||||
<ol>
|
||||
<li>
|
||||
Set up the Tabby server: you can get a Tabby Cloud hosted server <a href="https://app.tabbyml.com">here</a>, or build your self-hosted Tabby server following <a href="https://tabby.tabbyml.com/docs/installation/">this guide</a>.<br/>
|
||||
<b>Note:</b> Tabby Cloud is currently in beta. Join our <a href="https://join.slack.com/t/tabbycommunity/shared_invite/zt-1xeiddizp-bciR2RtFTaJ37RBxr8VxpA">Slack community</a> and ask in Tabby Cloud channel to get a beta invite.
|
||||
</li>
|
||||
<li>Open the settings page <code>Settings > Editor > Tabby</code>, or click the <code>Tabby</code> status bar item and <code>Open Settings...</code>. Fill in the server endpoint URL to connect the plugin to your Tabby server. The status bar item will show a checked icon if the connection is successful.</li>
|
||||
<li>Once setup is complete, Tabby will provide inline suggestions automatically, and you can accept suggestions by just pressing the <code>Tab</code> key.</li>
|
||||
<li>You can find more actions and hotkey in the IDE tools menu <code>Code > Tabby</code>.</li>
|
||||
</ol>
|
||||
Tabby plugin requires <a href="https://nodejs.org/">Node.js</a> v18+ installed and added into <code>PATH</code> environment variable. </p>
|
||||
]]></description>
|
||||
|
||||
<!-- Product and plugin compatibility requirements.
|
||||
|
|
@ -93,8 +83,8 @@
|
|||
text="Check Issue Detail..."
|
||||
description="Show detail information for current issue.">
|
||||
</action>
|
||||
<action id="Tabby.ToggleAutoCompletionEnabled"
|
||||
class="com.tabbyml.intellijtabby.actions.ToggleAutoCompletionEnabled">
|
||||
<action id="Tabby.ToggleInlineCompletionTriggerMode"
|
||||
class="com.tabbyml.intellijtabby.actions.ToggleInlineCompletionTriggerMode">
|
||||
</action>
|
||||
<action id="Tabby.OpenSettings"
|
||||
class="com.tabbyml.intellijtabby.actions.OpenSettings"
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 107 KiB |
Loading…
Reference in New Issue