mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
fix(jetbrains): handle prompt bulk updates and context apply
Skip prompt editor height and highlight recalculation while the editor document is in bulk update, then schedule one refresh after bulk mode exits so undo/redo cannot trigger UnexpectedBulkUpdateStateException. Also include the local Auto-Include Editor Context setting in the Context page draft state so toggling it marks the configurable modified and Apply persists it without sending a CLI config patch.
This commit is contained in:
+29
@@ -42,6 +42,7 @@ import com.intellij.openapi.actionSystem.ex.ActionUtil
|
||||
import com.intellij.openapi.actionSystem.IdeActions
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.editor.DefaultLanguageHighlighterColors
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.editor.SpellCheckingEditorCustomizationProvider
|
||||
import com.intellij.openapi.editor.colors.CodeInsightColors
|
||||
import com.intellij.openapi.editor.colors.TextAttributesKey
|
||||
@@ -256,6 +257,7 @@ class PromptPanel(
|
||||
private var ready = false
|
||||
private var enhancing = false
|
||||
private var request = 0L
|
||||
private var deferred = false
|
||||
|
||||
override val isSendEnabled: Boolean
|
||||
get() = ready && !submitting && (text().isNotEmpty() || attachments.isNotEmpty())
|
||||
@@ -270,12 +272,22 @@ class PromptPanel(
|
||||
editor.addDocumentListener(object : DocumentListener {
|
||||
override fun documentChanged(e: DocumentEvent) {
|
||||
invalidateEnhancement()
|
||||
if (e.document.isInBulkUpdate) {
|
||||
deferEditorSync()
|
||||
syncButton()
|
||||
onChange()
|
||||
return
|
||||
}
|
||||
syncEditorHeight()
|
||||
triggerCompletion(e)
|
||||
syncHighlights()
|
||||
syncButton()
|
||||
onChange()
|
||||
}
|
||||
|
||||
override fun bulkUpdateFinished(document: Document) {
|
||||
deferEditorSync()
|
||||
}
|
||||
})
|
||||
shell.add(strip, BorderLayout.NORTH)
|
||||
shell.add(editor, BorderLayout.CENTER)
|
||||
@@ -937,6 +949,10 @@ class PromptPanel(
|
||||
|
||||
@RequiresEdt
|
||||
private fun syncEditorHeight() {
|
||||
if (editor.document.isInBulkUpdate) {
|
||||
deferEditorSync()
|
||||
return
|
||||
}
|
||||
val before = editor.preferredSize.height
|
||||
val lower = editor.minimumSize.height
|
||||
editor.setPreferredSize(null)
|
||||
@@ -968,6 +984,19 @@ class PromptPanel(
|
||||
repaint()
|
||||
}
|
||||
|
||||
@RequiresEdt
|
||||
private fun deferEditorSync() {
|
||||
if (deferred) return
|
||||
deferred = true
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
deferred = false
|
||||
if (project.isDisposed || editor.document.isInBulkUpdate) return@invokeLater
|
||||
syncEditorHeight()
|
||||
syncHighlights()
|
||||
syncButton()
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresEdt
|
||||
private fun syncEditorScroll(ed: EditorEx?, overflow: Boolean) {
|
||||
// AS_NEEDED keeps the standard auto-hiding editor scrollbar (appears on
|
||||
|
||||
+6
@@ -1,5 +1,6 @@
|
||||
package ai.kilocode.client.settings.context
|
||||
|
||||
import ai.kilocode.client.plugin.KiloPluginSettings
|
||||
import ai.kilocode.rpc.dto.CompactionPatchDto
|
||||
import ai.kilocode.rpc.dto.ConfigDto
|
||||
import ai.kilocode.rpc.dto.ConfigPatchDto
|
||||
@@ -9,6 +10,7 @@ internal data class ContextDraft(
|
||||
val auto: Boolean = false,
|
||||
val threshold: String = "",
|
||||
val prune: Boolean = false,
|
||||
val editor: Boolean = KiloPluginSettings.getAutoEditorContext(),
|
||||
val ignore: List<String> = emptyList(),
|
||||
)
|
||||
|
||||
@@ -21,6 +23,7 @@ internal fun contextDraft(config: ConfigDto?): ContextDraft = ContextDraft(
|
||||
auto = config?.compaction?.auto ?: false,
|
||||
threshold = config?.compaction?.threshold_percent?.let(::formatThreshold).orEmpty(),
|
||||
prune = config?.compaction?.prune ?: false,
|
||||
editor = KiloPluginSettings.getAutoEditorContext(),
|
||||
ignore = config?.watcher?.ignore ?: emptyList(),
|
||||
)
|
||||
|
||||
@@ -38,8 +41,11 @@ internal fun savedMatches(base: ContextDraft, draft: ContextDraft): Boolean =
|
||||
base.auto == draft.auto &&
|
||||
normalizeThreshold(base.threshold) == normalizeThreshold(draft.threshold) &&
|
||||
base.prune == draft.prune &&
|
||||
base.editor == draft.editor &&
|
||||
base.ignore == draft.ignore
|
||||
|
||||
internal fun localChanged(base: ContextDraft, draft: ContextDraft): Boolean = base.editor != draft.editor
|
||||
|
||||
internal fun thresholdStatus(value: String): ThresholdStatus {
|
||||
val text = value.trim()
|
||||
if (text.isBlank()) return ThresholdStatus.VALID
|
||||
|
||||
+21
-10
@@ -63,10 +63,23 @@ internal class ContextSettingsUi(
|
||||
startSettings(ContextSettingsContent { updateDraft(it) })
|
||||
}
|
||||
|
||||
override fun change(from: ContextDraft, to: ContextDraft): ConfigPatchDto? = patch(from, to)?.takeIf(::changed)
|
||||
override fun change(from: ContextDraft, to: ContextDraft): ConfigPatchDto? {
|
||||
val patch = patch(from, to) ?: return null
|
||||
if (changed(patch)) return patch
|
||||
return ConfigPatchDto().takeIf { localChanged(from, to) }
|
||||
}
|
||||
|
||||
override fun save(change: ConfigPatchDto, done: (KiloAppStateDto?) -> Unit) {
|
||||
app.updateConfigAsync(change, done)
|
||||
val value = draft.editor
|
||||
if (!changed(change)) {
|
||||
KiloPluginSettings.setAutoEditorContext(value)
|
||||
done(appState)
|
||||
return
|
||||
}
|
||||
app.updateConfigAsync(change) { result ->
|
||||
if (result != null) KiloPluginSettings.setAutoEditorContext(value)
|
||||
done(result)
|
||||
}
|
||||
}
|
||||
|
||||
override fun base(result: KiloAppStateDto): ContextDraft = contextDraft(result.config)
|
||||
@@ -136,11 +149,9 @@ internal class ContextSettingsContent(
|
||||
) : BaseContentPanel() {
|
||||
private val auto = SettingsToggle { value -> update { copy(auto = value) } }
|
||||
// Editor-context auto-include is a local per-IDE preference in PropertiesComponent (like
|
||||
// autoApprove), applied immediately on toggle rather than through the CLI-backed draft/apply/
|
||||
// reset flow used by the other rows. It stays interactive even when the backend isn't READY.
|
||||
private val editor = SettingsToggle(KiloPluginSettings.getAutoEditorContext()) { value ->
|
||||
KiloPluginSettings.setAutoEditorContext(value)
|
||||
}
|
||||
// autoApprove). It participates in this page's draft/apply/reset state so the Configurable
|
||||
// Apply button reflects unsaved local changes, but it is never sent as CLI config.
|
||||
private val editor = SettingsToggle { value -> update { copy(editor = value) } }
|
||||
private val prune = SettingsToggle { value -> update { copy(prune = value) } }
|
||||
private val threshold = ThresholdField(
|
||||
KiloBundle.message("settings.context.compaction.threshold.placeholder"),
|
||||
@@ -186,9 +197,9 @@ internal class ContextSettingsContent(
|
||||
@RequiresEdt
|
||||
fun sync(draft: ContextDraft, enabled: Boolean) {
|
||||
auto.isSelected = draft.auto
|
||||
// Local preference: reflects PropertiesComponent and stays enabled regardless of the
|
||||
// CLI-backed [enabled] gating that applies to the draft-driven rows below.
|
||||
editor.isSelected = KiloPluginSettings.getAutoEditorContext()
|
||||
// Local preference: draft-driven, but still enabled regardless of the CLI-backed [enabled]
|
||||
// gating that applies to the remote config rows below.
|
||||
editor.isSelected = draft.editor
|
||||
editor.isEnabled = true
|
||||
prune.isSelected = draft.prune
|
||||
threshold.sync(draft.threshold)
|
||||
|
||||
+17
@@ -70,6 +70,7 @@ import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.util.Producer
|
||||
import com.intellij.util.ui.EmptyIcon
|
||||
import com.intellij.ui.scale.JBUIScale
|
||||
import com.intellij.util.DocumentUtil
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
@@ -501,6 +502,22 @@ class PromptPanelTest : BasePlatformTestCase() {
|
||||
assertEquals("hello", editor.document.text)
|
||||
}
|
||||
|
||||
fun `test prompt editor height sync skips bulk document updates`() {
|
||||
val panel = PromptPanel(project = project, onSend = { _, _ -> }, onAbort = {}, onEnhance = { _, _ -> }, completion = completion())
|
||||
val field = panel.defaultFocusedComponent as EditorTextField
|
||||
|
||||
realize(panel, 260, 400)
|
||||
val editor = field.getEditor(false)!!
|
||||
WriteCommandAction.runWriteCommandAction(project) {
|
||||
DocumentUtil.executeInBulk(editor.document, true) {
|
||||
editor.document.insertString(0, "hello")
|
||||
}
|
||||
}
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
|
||||
assertEquals("hello", editor.document.text)
|
||||
}
|
||||
|
||||
fun `test prompt editor highlights missing mention as wrong reference`() {
|
||||
val panel = PromptPanel(project = project, onSend = { _, _ -> }, onAbort = {}, onEnhance = { _, _ -> }, completion = completion())
|
||||
val field = panel.defaultFocusedComponent as EditorTextField
|
||||
|
||||
+5
-1
@@ -274,7 +274,7 @@ class ContextSettingsUiTest : BasePlatformTestCase() {
|
||||
flushUntil { rpc.configPatches.isNotEmpty() }
|
||||
}
|
||||
|
||||
fun `test editor context toggle persists immediately without a config patch`() {
|
||||
fun `test editor context toggle marks modified and applies without a config patch`() {
|
||||
val panel = requireUi()
|
||||
assertTrue(KiloPluginSettings.getAutoEditorContext())
|
||||
|
||||
@@ -282,10 +282,14 @@ class ContextSettingsUiTest : BasePlatformTestCase() {
|
||||
val editor = editorToggle(panel)
|
||||
assertTrue(editor.isEnabled)
|
||||
editor.doClick()
|
||||
assertTrue(panel.modified())
|
||||
}
|
||||
|
||||
assertTrue(KiloPluginSettings.getAutoEditorContext())
|
||||
edt { panel.applyDraft() }
|
||||
assertFalse(KiloPluginSettings.getAutoEditorContext())
|
||||
edt { UIUtil.dispatchAllInvocationEvents() }
|
||||
assertFalse(edt { panel.modified() })
|
||||
assertTrue(rpc.configPatches.isEmpty())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user