mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-21 05:52:35 +08:00
feat(kilo-jetbrains): add settings config file actions
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Improve the JetBrains tool window settings menu with config file shortcuts and separate CLI restart and reinstall actions.
|
||||
+61
@@ -6,6 +6,8 @@ import ai.kilocode.backend.app.KiloAppState
|
||||
import ai.kilocode.backend.app.KiloBackendAppService
|
||||
import ai.kilocode.backend.app.LoadError
|
||||
import ai.kilocode.backend.cli.KiloCliDataParser
|
||||
import ai.kilocode.backend.cli.KiloBackendCliManager
|
||||
import ai.kilocode.backend.cli.KiloCliConfigPath
|
||||
import ai.kilocode.backend.workspace.AgentData
|
||||
import ai.kilocode.backend.workspace.AgentInfo
|
||||
import ai.kilocode.backend.workspace.KiloBackendWorkspaceManager
|
||||
@@ -39,6 +41,7 @@ import java.net.URI
|
||||
import java.net.URLDecoder
|
||||
import java.net.URLEncoder
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.InvalidPathException
|
||||
import java.nio.file.Path
|
||||
import kotlin.coroutines.resume
|
||||
@@ -53,6 +56,15 @@ import kotlin.coroutines.resume
|
||||
class KiloWorkspaceRpcApiImpl : KiloWorkspaceRpcApi {
|
||||
companion object {
|
||||
private val LOG = KiloLog.create(KiloWorkspaceRpcApiImpl::class.java)
|
||||
private const val SCHEMA = "https://app.kilo.ai/config.json"
|
||||
private val MODERN = listOf("kilo.jsonc", "kilo.json")
|
||||
private val LEGACY = listOf("opencode.jsonc", "opencode.json")
|
||||
private val GLOBAL = MODERN + LEGACY + "config.json"
|
||||
private val LOCAL_DIRS = listOf(".kilo", ".kilocode", ".opencode")
|
||||
private val CONFIG = """{
|
||||
"${'$'}schema": "$SCHEMA"
|
||||
}
|
||||
"""
|
||||
}
|
||||
|
||||
private val app: KiloBackendAppService get() = service()
|
||||
@@ -171,6 +183,55 @@ class KiloWorkspaceRpcApiImpl : KiloWorkspaceRpcApi {
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun localConfigPath(directory: String): String = withContext(Dispatchers.IO) {
|
||||
localConfig(directory).toString()
|
||||
}
|
||||
|
||||
override suspend fun globalConfigPath(): String = withContext(Dispatchers.IO) {
|
||||
globalConfig().toString()
|
||||
}
|
||||
|
||||
override suspend fun openLocalConfig(directory: String): Boolean = openConfig(withContext(Dispatchers.IO) {
|
||||
localConfig(directory)
|
||||
})
|
||||
|
||||
override suspend fun openGlobalConfig(): Boolean = openConfig(withContext(Dispatchers.IO) {
|
||||
globalConfig()
|
||||
})
|
||||
|
||||
private suspend fun openConfig(path: Path): Boolean {
|
||||
val target = withContext(Dispatchers.IO) {
|
||||
Files.createDirectories(path.parent)
|
||||
if (!Files.exists(path)) Files.writeString(path, CONFIG, StandardCharsets.UTF_8)
|
||||
path
|
||||
}
|
||||
val vf = LocalFileSystem.getInstance().refreshAndFindFileByPath(target.toString()) ?: return false
|
||||
val project = project(target) ?: run {
|
||||
LOG.warn("No project available to open config file: $target")
|
||||
return false
|
||||
}
|
||||
navigate(project, vf)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun localConfig(directory: String): Path {
|
||||
val root = file(clean(directory) ?: directory)?.takeIf { it.isAbsolute } ?: Path.of(directory).normalize()
|
||||
val dirs = LOCAL_DIRS.map { root.resolve(it) } + root
|
||||
val found = dirs.asSequence()
|
||||
.flatMap { dir -> (MODERN + LEGACY).asSequence().map { name -> dir.resolve(name) } }
|
||||
.firstOrNull { Files.exists(it) }
|
||||
return found ?: root.resolve(".kilo").resolve("kilo.jsonc")
|
||||
}
|
||||
|
||||
private fun globalConfig(): Path {
|
||||
val env = KiloBackendCliManager().buildEnv("config")
|
||||
val root = KiloCliConfigPath.resolve(env).toPath().normalize()
|
||||
return GLOBAL.asSequence()
|
||||
.map { root.resolve(it) }
|
||||
.firstOrNull { Files.exists(it) }
|
||||
?: root.resolve("kilo.jsonc")
|
||||
}
|
||||
|
||||
private fun clean(path: String): String? {
|
||||
val raw = path.trim().takeIf { it.isNotBlank() } ?: return null
|
||||
return try {
|
||||
|
||||
-1
@@ -73,7 +73,6 @@ class KiloToolWindowFactory : ToolWindowFactory, DumbAware {
|
||||
val actions = listOfNotNull(
|
||||
ActionManager.getInstance().getAction("Kilo.NewSession"),
|
||||
ActionManager.getInstance().getAction("Kilo.History"),
|
||||
ActionManager.getInstance().getAction("Kilo.ShowProfile"),
|
||||
ActionManager.getInstance().getAction("Kilo.Settings"),
|
||||
)
|
||||
toolWindow.setTitleActions(actions)
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package ai.kilocode.client.actions
|
||||
|
||||
import ai.kilocode.client.KiloNotifications
|
||||
import ai.kilocode.client.app.KiloWorkspaceService
|
||||
import ai.kilocode.client.plugin.KiloBundle
|
||||
import ai.kilocode.client.session.SessionManager
|
||||
import ai.kilocode.client.telemetry.Telemetry
|
||||
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.intellij.openapi.project.DumbAware
|
||||
|
||||
abstract class ConfigAction(
|
||||
private val label: String,
|
||||
text: String,
|
||||
description: String,
|
||||
) : AnAction(text, description, null), DumbAware {
|
||||
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
|
||||
|
||||
protected fun text(path: String?) = KiloBundle.message(label, path ?: "...")
|
||||
|
||||
protected fun failed() {
|
||||
KiloNotifications.error(KiloBundle.message("action.Kilo.OpenConfig.failed"))
|
||||
}
|
||||
}
|
||||
|
||||
class OpenLocalConfigAction : ConfigAction(
|
||||
label = "action.Kilo.OpenLocalConfig.text",
|
||||
text = KiloBundle.message("action.Kilo.OpenLocalConfig.text", "..."),
|
||||
description = KiloBundle.message("action.Kilo.OpenLocalConfig.description"),
|
||||
) {
|
||||
override fun update(e: AnActionEvent) {
|
||||
val dir = directory(e)
|
||||
e.presentation.isEnabled = dir != null
|
||||
e.presentation.text = text(dir?.let { service<KiloWorkspaceService>().localConfigPath(it) })
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val dir = directory(e) ?: return
|
||||
Telemetry.send("Config Opened", mapOf("surface" to "tool_window", "scope" to "local"))
|
||||
service<KiloWorkspaceService>().openLocalConfig(dir) { ok ->
|
||||
if (!ok) failed()
|
||||
}
|
||||
}
|
||||
|
||||
private fun directory(e: AnActionEvent): String? {
|
||||
return e.getData(SessionManager.WORKSPACE_KEY)?.directory ?: e.project?.basePath
|
||||
}
|
||||
}
|
||||
|
||||
class OpenGlobalConfigAction : ConfigAction(
|
||||
label = "action.Kilo.OpenGlobalConfig.text",
|
||||
text = KiloBundle.message("action.Kilo.OpenGlobalConfig.text", "..."),
|
||||
description = KiloBundle.message("action.Kilo.OpenGlobalConfig.description"),
|
||||
) {
|
||||
override fun update(e: AnActionEvent) {
|
||||
e.presentation.text = text(service<KiloWorkspaceService>().globalConfigPath())
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
Telemetry.send("Config Opened", mapOf("surface" to "tool_window", "scope" to "global"))
|
||||
service<KiloWorkspaceService>().openGlobalConfig { ok ->
|
||||
if (!ok) failed()
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -3,7 +3,6 @@ package ai.kilocode.client.actions
|
||||
import ai.kilocode.client.plugin.KiloBundle
|
||||
import ai.kilocode.client.settings.KiloSettingsConfigurable
|
||||
import ai.kilocode.client.telemetry.Telemetry
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.options.Configurable
|
||||
@@ -15,7 +14,7 @@ import java.util.function.Predicate
|
||||
class OpenSettingsAction : DumbAwareAction(
|
||||
KiloBundle.message("action.Kilo.OpenSettings.text"),
|
||||
KiloBundle.message("action.Kilo.OpenSettings.description"),
|
||||
AllIcons.General.GearPlain,
|
||||
null,
|
||||
) {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
Telemetry.send("Settings Opened", mapOf("surface" to "tool_window"))
|
||||
|
||||
+50
@@ -41,6 +41,10 @@ class KiloWorkspaceService internal constructor(
|
||||
}
|
||||
|
||||
private val workspaces = ConcurrentHashMap<String, Workspace>()
|
||||
private val localConfig = ConcurrentHashMap<String, String>()
|
||||
|
||||
@Volatile
|
||||
private var globalConfig: String? = null
|
||||
|
||||
// ------ RPC helpers ------
|
||||
|
||||
@@ -129,4 +133,50 @@ class KiloWorkspaceService internal constructor(
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun localConfigPath(directory: String): String? {
|
||||
cs.launch {
|
||||
try {
|
||||
localConfig[directory] = call { this.localConfigPath(directory) }
|
||||
} catch (e: Exception) {
|
||||
LOG.warn("local config lookup failed for directory=$directory", e)
|
||||
}
|
||||
}
|
||||
return localConfig[directory]
|
||||
}
|
||||
|
||||
fun globalConfigPath(): String? {
|
||||
cs.launch {
|
||||
try {
|
||||
globalConfig = call { this.globalConfigPath() }
|
||||
} catch (e: Exception) {
|
||||
LOG.warn("global config lookup failed", e)
|
||||
}
|
||||
}
|
||||
return globalConfig
|
||||
}
|
||||
|
||||
fun openLocalConfig(directory: String, done: (Boolean) -> Unit) {
|
||||
cs.launch {
|
||||
val ok = try {
|
||||
call { this.openLocalConfig(directory) }
|
||||
} catch (e: Exception) {
|
||||
LOG.warn("local config open failed for directory=$directory", e)
|
||||
false
|
||||
}
|
||||
done(ok)
|
||||
}
|
||||
}
|
||||
|
||||
fun openGlobalConfig(done: (Boolean) -> Unit) {
|
||||
cs.launch {
|
||||
val ok = try {
|
||||
call { this.openGlobalConfig() }
|
||||
} catch (e: Exception) {
|
||||
LOG.warn("global config open failed", e)
|
||||
false
|
||||
}
|
||||
done(ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -1,11 +1,13 @@
|
||||
package ai.kilocode.client.session
|
||||
|
||||
import ai.kilocode.client.app.Workspace
|
||||
import ai.kilocode.rpc.dto.SessionDto
|
||||
import com.intellij.openapi.actionSystem.DataKey
|
||||
|
||||
interface SessionManager {
|
||||
companion object {
|
||||
val KEY = DataKey.create<SessionManager>("ai.kilocode.client.session.SessionManager")
|
||||
val WORKSPACE_KEY = DataKey.create<Workspace>("ai.kilocode.client.session.Workspace")
|
||||
}
|
||||
|
||||
fun newSession()
|
||||
|
||||
+1
@@ -34,6 +34,7 @@ class SessionSidePanelManager(
|
||||
val component: JPanel = object : JPanel(BorderLayout()), DataProvider {
|
||||
override fun getData(dataId: String): Any? {
|
||||
if (SessionManager.KEY.`is`(dataId)) return this@SessionSidePanelManager
|
||||
if (SessionManager.WORKSPACE_KEY.`is`(dataId)) return root
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,14 +70,27 @@
|
||||
<action id="Kilo.OpenSettings"
|
||||
class="ai.kilocode.client.actions.OpenSettingsAction"/>
|
||||
|
||||
<action id="Kilo.OpenLocalConfig"
|
||||
class="ai.kilocode.client.actions.OpenLocalConfigAction"/>
|
||||
|
||||
<action id="Kilo.OpenGlobalConfig"
|
||||
class="ai.kilocode.client.actions.OpenGlobalConfigAction"/>
|
||||
|
||||
<group id="Kilo.CliGroup" text="CLI" popup="true">
|
||||
<reference ref="Kilo.Restart"/>
|
||||
<reference ref="Kilo.Reinstall"/>
|
||||
</group>
|
||||
|
||||
<group id="Kilo.OpenConfigGroup" text="Config Files" popup="true">
|
||||
<reference ref="Kilo.OpenLocalConfig"/>
|
||||
<reference ref="Kilo.OpenGlobalConfig"/>
|
||||
</group>
|
||||
|
||||
<group id="Kilo.SettingsGroup">
|
||||
<reference ref="Kilo.OpenSettings"/>
|
||||
<separator/>
|
||||
<reference ref="Kilo.OpenConfigGroup"/>
|
||||
<separator/>
|
||||
<reference ref="Kilo.CliGroup"/>
|
||||
</group>
|
||||
|
||||
@@ -91,14 +104,10 @@
|
||||
<action id="Kilo.History"
|
||||
class="ai.kilocode.client.actions.HistoryAction"/>
|
||||
|
||||
<action id="Kilo.ShowProfile"
|
||||
class="ai.kilocode.client.actions.ShowProfileAction"/>
|
||||
|
||||
<!-- Declarative toolbar group for the Kilo tool window title bar -->
|
||||
<group id="Kilo.ToolWindowToolbar">
|
||||
<reference ref="Kilo.NewSession"/>
|
||||
<reference ref="Kilo.History"/>
|
||||
<reference ref="Kilo.ShowProfile"/>
|
||||
<reference ref="Kilo.Settings"/>
|
||||
</group>
|
||||
|
||||
|
||||
@@ -259,8 +259,15 @@ action.Kilo.StopSession.text=Stop Session
|
||||
action.Kilo.StopSession.description=Stop the current Kilo session
|
||||
action.Kilo.SettingsGroup.text=Settings
|
||||
action.Kilo.SettingsGroup.description=Kilo Code settings
|
||||
action.Kilo.OpenSettings.text=Open Settings
|
||||
action.Kilo.OpenSettings.description=Open Kilo Code settings
|
||||
action.Kilo.OpenSettings.text=Open Settings...
|
||||
action.Kilo.OpenSettings.description=Open Kilo Code settings dialog
|
||||
action.Kilo.OpenConfigGroup.text=Config Files
|
||||
action.Kilo.OpenConfigGroup.description=Open Kilo config files
|
||||
action.Kilo.OpenLocalConfig.text=Local {0}
|
||||
action.Kilo.OpenLocalConfig.description=Open or create the local Kilo config file
|
||||
action.Kilo.OpenGlobalConfig.text=Global {0}
|
||||
action.Kilo.OpenGlobalConfig.description=Open or create the global Kilo config file
|
||||
action.Kilo.OpenConfig.failed=Failed to open Kilo config file
|
||||
action.Kilo.CliGroup.text=CLI
|
||||
action.Kilo.CliGroup.description=Kilo CLI actions
|
||||
action.Kilo.Restart.text=Restart Kilo
|
||||
|
||||
+6
@@ -32,6 +32,12 @@ class KiloRecoveryActionsTest : BasePlatformTestCase() {
|
||||
.use { it.readText() }
|
||||
|
||||
assertTrue(xml.contains("<group id=\"Kilo.CliGroup\" text=\"CLI\" popup=\"true\">"))
|
||||
assertTrue(xml.contains("<reference ref=\"Kilo.Restart\"/>"))
|
||||
assertTrue(xml.contains("<reference ref=\"Kilo.Reinstall\"/>"))
|
||||
assertTrue(xml.contains("<group id=\"Kilo.OpenConfigGroup\" text=\"Config Files\" popup=\"true\">"))
|
||||
assertTrue(xml.contains("<reference ref=\"Kilo.OpenConfigGroup\"/>"))
|
||||
assertFalse(xml.contains("<action id=\"Kilo.ShowProfile\""))
|
||||
assertFalse(xml.contains("<reference ref=\"Kilo.ShowProfile\"/>"))
|
||||
}
|
||||
|
||||
private fun event(action: AnAction): AnActionEvent {
|
||||
|
||||
+26
@@ -27,8 +27,12 @@ class FakeWorkspaceRpcApi : KiloWorkspaceRpcApi {
|
||||
var modelsGate: CompletableDeferred<Unit>? = null
|
||||
var fileMatches = emptyList<WorkspaceFileDto>()
|
||||
var openResult = true
|
||||
var localConfigPath = "/test/.kilo/kilo.jsonc"
|
||||
var globalConfigPath = "/config/kilo.jsonc"
|
||||
val fileCalls = mutableListOf<Pair<String, String>>()
|
||||
val opened = mutableListOf<String>()
|
||||
val localConfigs = mutableListOf<String>()
|
||||
var globalConfigs = 0
|
||||
|
||||
override suspend fun resolveProjectDirectory(hint: String): String {
|
||||
assertNotEdt("resolveProjectDirectory")
|
||||
@@ -62,4 +66,26 @@ class FakeWorkspaceRpcApi : KiloWorkspaceRpcApi {
|
||||
opened.add(path)
|
||||
return openResult
|
||||
}
|
||||
|
||||
override suspend fun localConfigPath(directory: String): String {
|
||||
assertNotEdt("localConfigPath")
|
||||
return localConfigPath
|
||||
}
|
||||
|
||||
override suspend fun globalConfigPath(): String {
|
||||
assertNotEdt("globalConfigPath")
|
||||
return globalConfigPath
|
||||
}
|
||||
|
||||
override suspend fun openLocalConfig(directory: String): Boolean {
|
||||
assertNotEdt("openLocalConfig")
|
||||
localConfigs.add(directory)
|
||||
return openResult
|
||||
}
|
||||
|
||||
override suspend fun openGlobalConfig(): Boolean {
|
||||
assertNotEdt("openGlobalConfig")
|
||||
globalConfigs += 1
|
||||
return openResult
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,4 +47,16 @@ interface KiloWorkspaceRpcApi : RemoteApi<Unit> {
|
||||
|
||||
/** Open an absolute backend file path in the IDE. */
|
||||
suspend fun openFile(path: String): Boolean
|
||||
|
||||
/** Resolve the editable local config target path. */
|
||||
suspend fun localConfigPath(directory: String): String
|
||||
|
||||
/** Resolve the editable global config target path. */
|
||||
suspend fun globalConfigPath(): String
|
||||
|
||||
/** Open or create the local config file in the IDE. */
|
||||
suspend fun openLocalConfig(directory: String): Boolean
|
||||
|
||||
/** Open or create the global config file in the IDE. */
|
||||
suspend fun openGlobalConfig(): Boolean
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user