mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-29 03:44:06 +08:00
fix(jetbrains): address recovery review feedback
This commit is contained in:
@@ -2,4 +2,4 @@
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Show resolved JetBrains config file paths and open the same global config directory used by the CLI.
|
||||
Show resolved JetBrains config file paths, float connection status above the prompt, and offer retry, restart, and reinstall recovery actions from connection errors. JetBrains now opens the same global config directory used by the CLI; macOS and Windows users who previously created global config from JetBrains may need to move files from the old platform-specific location to `~/.config/kilo`.
|
||||
|
||||
+6
-3
@@ -9,6 +9,7 @@ import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory
|
||||
import kotlinx.coroutines.Job
|
||||
|
||||
/**
|
||||
* Gear icon action placed in the Kilo tool window title bar.
|
||||
@@ -26,9 +27,11 @@ class KiloSettingsAction : AnAction() {
|
||||
return ActionGroupUtil.forceRecursiveUpdateInBackground(group)
|
||||
}
|
||||
|
||||
internal fun refreshConfigTargets(e: AnActionEvent, service: KiloWorkspaceService) {
|
||||
e.workspaceDirectory()?.let { service.refreshLocalConfigTarget(it) }
|
||||
service.refreshGlobalConfigTarget()
|
||||
internal fun refreshConfigTargets(e: AnActionEvent, service: KiloWorkspaceService): List<Job> {
|
||||
return listOfNotNull(
|
||||
e.workspaceDirectory()?.let { service.refreshLocalConfigTarget(it) },
|
||||
service.refreshGlobalConfigTarget(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-6
@@ -17,6 +17,7 @@ import com.intellij.platform.project.ProjectId
|
||||
import fleet.rpc.client.durable
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.flow
|
||||
@@ -189,10 +190,10 @@ class KiloWorkspaceService internal constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshLocalConfigTarget(directory: String) {
|
||||
if (!pendingLocal.add(directory)) return
|
||||
fun refreshLocalConfigTarget(directory: String): Job? {
|
||||
if (!pendingLocal.add(directory)) return null
|
||||
|
||||
cs.launch {
|
||||
return cs.launch {
|
||||
try {
|
||||
localConfigTarget(directory)
|
||||
} finally {
|
||||
@@ -202,10 +203,10 @@ class KiloWorkspaceService internal constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshGlobalConfigTarget() {
|
||||
if (!pendingGlobal.compareAndSet(false, true)) return
|
||||
fun refreshGlobalConfigTarget(): Job? {
|
||||
if (!pendingGlobal.compareAndSet(false, true)) return null
|
||||
|
||||
cs.launch {
|
||||
return cs.launch {
|
||||
try {
|
||||
globalConfigTarget()
|
||||
} finally {
|
||||
|
||||
+19
-2
@@ -13,7 +13,10 @@ import com.intellij.ide.DataManager
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.actionSystem.ActionGroup
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.DefaultActionGroup
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.ui.components.ActionLink
|
||||
@@ -209,11 +212,10 @@ class ConnectionPanel(
|
||||
}
|
||||
|
||||
private fun showRecoveryPopup() {
|
||||
val group = ActionManager.getInstance().getAction(CLI_GROUP_ID) as? ActionGroup ?: return
|
||||
JBPopupFactory.getInstance()
|
||||
.createActionGroupPopup(
|
||||
null,
|
||||
group,
|
||||
recoveryGroup(),
|
||||
DataManager.getInstance().getDataContext(retry),
|
||||
JBPopupFactory.ActionSelectionAid.SPEEDSEARCH,
|
||||
true,
|
||||
@@ -222,6 +224,19 @@ class ConnectionPanel(
|
||||
.showUnderneathOf(retry)
|
||||
}
|
||||
|
||||
private fun recoveryGroup(): ActionGroup {
|
||||
val group = DefaultActionGroup()
|
||||
group.add(object : DumbAwareAction(KiloBundle.message("session.connection.retry")) {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
controller.retryConnection()
|
||||
}
|
||||
})
|
||||
group.addSeparator()
|
||||
ActionManager.getInstance().getAction("Kilo.Restart")?.let { group.add(it) }
|
||||
ActionManager.getInstance().getAction("Kilo.Reinstall")?.let { group.add(it) }
|
||||
return group
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
// no-op
|
||||
}
|
||||
@@ -284,6 +299,8 @@ class ConnectionPanel(
|
||||
|
||||
internal fun retryFocusable() = retry.isFocusable
|
||||
|
||||
internal fun recoveryActionTexts() = recoveryGroup().getChildren(null).mapNotNull { it.templatePresentation.text }
|
||||
|
||||
internal fun hasSeparator() = border != null
|
||||
|
||||
internal fun maxExpandedHeight() =
|
||||
|
||||
+48
-24
@@ -18,12 +18,10 @@ import com.intellij.openapi.actionSystem.ex.ActionUtil
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.testFramework.replaceService
|
||||
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withTimeout
|
||||
@@ -136,6 +134,12 @@ class KiloRecoveryActionsTest : BasePlatformTestCase() {
|
||||
rpc.localConfigPath = "/test/.kilo/kilo.jsonc"
|
||||
rpc.localConfigDisplayPath = "/test/.kilo/kilo.jsonc"
|
||||
rpc.localConfigExists = true
|
||||
val call = CompletableDeferred<Unit>()
|
||||
val gate = CompletableDeferred<Unit>()
|
||||
rpc.beforeLocalConfigTarget = {
|
||||
call.complete(Unit)
|
||||
gate.await()
|
||||
}
|
||||
val action = OpenLocalConfigAction()
|
||||
val event = event(action, workspace = workspace("/test"))
|
||||
|
||||
@@ -143,28 +147,34 @@ class KiloRecoveryActionsTest : BasePlatformTestCase() {
|
||||
|
||||
assertTrue(event.presentation.isEnabled)
|
||||
assertEquals("Open: local ...", event.presentation.text)
|
||||
waitFor { rpc.localConfigPathCalls == 1 && service().localConfig["/test"] != null }
|
||||
await(call)
|
||||
assertEquals(1, rpc.localConfigPathCalls)
|
||||
|
||||
gate.complete(Unit)
|
||||
service().localConfig["/test"] = ConfigTargetDto("/test/.kilo/kilo.jsonc", "/test/.kilo/kilo.jsonc", true)
|
||||
|
||||
val next = event(action, workspace = workspace("/test"))
|
||||
update(action, next)
|
||||
|
||||
assertEquals("Open: local /test/.kilo/kilo.jsonc", next.presentation.text)
|
||||
assertEquals(1, rpc.localConfigPathCalls)
|
||||
}
|
||||
|
||||
fun `test local config action dedupes in flight refresh`() {
|
||||
val gate = CompletableDeferred<Unit>()
|
||||
rpc.beforeLocalConfigTarget = { gate.await() }
|
||||
val call = CompletableDeferred<Unit>()
|
||||
val action = OpenLocalConfigAction()
|
||||
rpc.beforeLocalConfigTarget = {
|
||||
call.complete(Unit)
|
||||
gate.await()
|
||||
}
|
||||
|
||||
update(action, event(action, workspace = workspace("/test")))
|
||||
waitFor { rpc.localConfigPathCalls == 1 }
|
||||
await(call)
|
||||
update(action, event(action, workspace = workspace("/test")))
|
||||
|
||||
assertEquals(1, rpc.localConfigPathCalls)
|
||||
|
||||
gate.complete(Unit)
|
||||
waitFor { service().localConfig["/test"] != null }
|
||||
}
|
||||
|
||||
fun `test global config action says open when target exists`() {
|
||||
@@ -199,34 +209,46 @@ class KiloRecoveryActionsTest : BasePlatformTestCase() {
|
||||
rpc.globalConfigPath = "/config/kilo.jsonc"
|
||||
rpc.globalConfigDisplayPath = "/config/kilo.jsonc"
|
||||
rpc.globalConfigExists = true
|
||||
val call = CompletableDeferred<Unit>()
|
||||
val gate = CompletableDeferred<Unit>()
|
||||
rpc.beforeGlobalConfigTarget = {
|
||||
call.complete(Unit)
|
||||
gate.await()
|
||||
}
|
||||
val action = OpenGlobalConfigAction()
|
||||
val event = event(action)
|
||||
|
||||
update(action, event)
|
||||
|
||||
assertEquals("Open: global ...", event.presentation.text)
|
||||
waitFor { rpc.globalConfigPathCalls == 1 && service().globalConfig != null }
|
||||
await(call)
|
||||
assertEquals(1, rpc.globalConfigPathCalls)
|
||||
|
||||
gate.complete(Unit)
|
||||
cacheGlobal(ConfigTargetDto("/config/kilo.jsonc", "/config/kilo.jsonc", true))
|
||||
|
||||
val next = event(action)
|
||||
update(action, next)
|
||||
|
||||
assertEquals("Open: global /config/kilo.jsonc", next.presentation.text)
|
||||
assertEquals(1, rpc.globalConfigPathCalls)
|
||||
}
|
||||
|
||||
fun `test global config action dedupes in flight refresh`() {
|
||||
val gate = CompletableDeferred<Unit>()
|
||||
rpc.beforeGlobalConfigTarget = { gate.await() }
|
||||
val call = CompletableDeferred<Unit>()
|
||||
rpc.beforeGlobalConfigTarget = {
|
||||
call.complete(Unit)
|
||||
gate.await()
|
||||
}
|
||||
val action = OpenGlobalConfigAction()
|
||||
|
||||
update(action, event(action))
|
||||
waitFor { rpc.globalConfigPathCalls == 1 }
|
||||
await(call)
|
||||
update(action, event(action))
|
||||
|
||||
assertEquals(1, rpc.globalConfigPathCalls)
|
||||
|
||||
gate.complete(Unit)
|
||||
waitFor { service().globalConfig != null }
|
||||
}
|
||||
|
||||
fun `test local config action disables without directory`() {
|
||||
@@ -249,16 +271,24 @@ class KiloRecoveryActionsTest : BasePlatformTestCase() {
|
||||
fun `test settings action prewarms config targets`() {
|
||||
val action = KiloSettingsAction()
|
||||
|
||||
KiloSettingsAction.refreshConfigTargets(event(action, workspace = workspace("/test")), service())
|
||||
runBlocking {
|
||||
KiloSettingsAction.refreshConfigTargets(event(action, workspace = workspace("/test")), service()).forEach { it.join() }
|
||||
}
|
||||
|
||||
waitFor { rpc.localConfigPathCalls == 1 && rpc.globalConfigPathCalls == 1 }
|
||||
assertEquals(1, rpc.localConfigPathCalls)
|
||||
assertEquals(1, rpc.globalConfigPathCalls)
|
||||
}
|
||||
|
||||
fun `test workspace creation prewarms config targets`() {
|
||||
val local = CompletableDeferred<Unit>()
|
||||
val global = CompletableDeferred<Unit>()
|
||||
rpc.beforeLocalConfigTarget = { local.complete(Unit) }
|
||||
rpc.beforeGlobalConfigTarget = { global.complete(Unit) }
|
||||
|
||||
service().workspace("/test")
|
||||
|
||||
waitFor { rpc.localConfigPathCalls == 1 && rpc.globalConfigPathCalls == 1 }
|
||||
|
||||
await(local)
|
||||
await(global)
|
||||
assertEquals(1, rpc.localConfigPathCalls)
|
||||
assertEquals(1, rpc.globalConfigPathCalls)
|
||||
}
|
||||
@@ -275,14 +305,8 @@ class KiloRecoveryActionsTest : BasePlatformTestCase() {
|
||||
}.get()
|
||||
}
|
||||
|
||||
private fun waitFor(done: () -> Boolean) = runBlocking {
|
||||
withTimeout(5_000) {
|
||||
while (!done()) {
|
||||
delay(25)
|
||||
ApplicationManager.getApplication().invokeAndWait { UIUtil.dispatchAllInvocationEvents() }
|
||||
}
|
||||
}
|
||||
ApplicationManager.getApplication().invokeAndWait { UIUtil.dispatchAllInvocationEvents() }
|
||||
private fun await(signal: CompletableDeferred<Unit>) = runBlocking {
|
||||
withTimeout(5_000) { signal.await() }
|
||||
}
|
||||
|
||||
private fun service(): KiloWorkspaceService = ApplicationManager.getApplication().getService(KiloWorkspaceService::class.java)
|
||||
|
||||
+2
-1
@@ -80,7 +80,7 @@ class ConnectionPanelTest : SessionControllerTestBase() {
|
||||
assertEquals("Try again", panel.retryText())
|
||||
}
|
||||
|
||||
fun `test retry popup group uses cli recovery actions`() {
|
||||
fun `test retry popup group keeps lightweight retry and cli recovery actions`() {
|
||||
edt {
|
||||
panel.onEvent(SessionControllerEvent.ConnectionChanged.ShowError("CLI startup failed", null))
|
||||
}
|
||||
@@ -89,6 +89,7 @@ class ConnectionPanelTest : SessionControllerTestBase() {
|
||||
.use { it.readText() }
|
||||
|
||||
assertTrue(panel.retryVisible())
|
||||
assertTrue(panel.recoveryActionTexts().contains("Try again"))
|
||||
assertEquals("Kilo.CliGroup", ConnectionPanel.CLI_GROUP_ID)
|
||||
assertTrue(xml.contains("<group id=\"Kilo.CliGroup\" text=\"CLI\" popup=\"true\">"))
|
||||
assertTrue(xml.contains("<reference ref=\"Kilo.Restart\"/>"))
|
||||
|
||||
Reference in New Issue
Block a user