mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-01 15:32:11 +08:00
refactor(jetbrains): page-level auto-approve filter and wildcard labels
This commit is contained in:
+6
-2
@@ -1,15 +1,19 @@
|
||||
package ai.kilocode.client.settings.autoapprove
|
||||
|
||||
import ai.kilocode.client.plugin.KiloBundle
|
||||
import ai.kilocode.client.settings.base.ScrollableDraftReadyConfigurable
|
||||
import ai.kilocode.client.settings.base.DraftReadyConfigurable
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import javax.swing.JComponent
|
||||
|
||||
class AutoApproveConfigurable : ScrollableDraftReadyConfigurable<JComponent>() {
|
||||
class AutoApproveConfigurable : DraftReadyConfigurable<JComponent>() {
|
||||
override fun getId(): String = ID
|
||||
|
||||
override fun getDisplayName(): String = KiloBundle.message("settings.autoApprove.displayName")
|
||||
|
||||
// The page renders its own fixed search field plus a scrollable body, so the shell must not
|
||||
// add another scroll pane around it.
|
||||
override fun scrollReadyShell(): Boolean = false
|
||||
|
||||
override fun create(cs: CoroutineScope): JComponent = AutoApproveSettingsUi(cs)
|
||||
|
||||
companion object {
|
||||
|
||||
+14
-3
@@ -29,7 +29,6 @@ internal class AutoApproveContent(
|
||||
private val granular = GRANULAR_TOOLS.map { (id, kind) -> id to granularSection(id, kind) }
|
||||
private val tools = SettingsInlineList(
|
||||
empty = KiloBundle.message("settings.autoApprove.tools.empty"),
|
||||
search = KiloBundle.message("settings.autoApprove.tools.search"),
|
||||
onSetLevel = { key, level -> update { setListTool(this, key, level) } },
|
||||
onInherit = { key -> update { inheritListTool(this, key) } },
|
||||
picker = picker,
|
||||
@@ -46,12 +45,24 @@ internal class AutoApproveContent(
|
||||
tools.syncRows(toolRows(draft), enabled)
|
||||
}
|
||||
|
||||
/** Filter every list on the page by [query], driven by the shared search field. */
|
||||
@RequiresEdt
|
||||
fun filter(query: String) {
|
||||
for ((_, section) in granular) section.filter(query)
|
||||
tools.filter(query)
|
||||
}
|
||||
|
||||
private fun granularSection(tool: String, kind: ExceptionKind): GranularToolSection {
|
||||
val addKey = if (kind == ExceptionKind.COMMAND) "addCommand" else "addPath"
|
||||
val placeholderKey = if (kind == ExceptionKind.COMMAND) "placeholder.command" else "placeholder.path"
|
||||
val commands = kind == ExceptionKind.COMMAND
|
||||
val wildcardKey = if (commands) "commands" else "paths"
|
||||
val emptyKey = if (commands) "commands" else "paths"
|
||||
val addKey = if (commands) "addCommand" else "addPath"
|
||||
val placeholderKey = if (commands) "placeholder.command" else "placeholder.path"
|
||||
return GranularToolSection(
|
||||
tool,
|
||||
KiloBundle.message("settings.autoApprove.tool.$tool"),
|
||||
KiloBundle.message("settings.autoApprove.wildcardLabel.$wildcardKey"),
|
||||
KiloBundle.message("settings.autoApprove.filters.empty.$emptyKey"),
|
||||
KiloBundle.message("settings.autoApprove.$addKey"),
|
||||
KiloBundle.message("settings.autoApprove.$placeholderKey"),
|
||||
picker,
|
||||
|
||||
+14
-1
@@ -4,13 +4,19 @@ import ai.kilocode.client.app.KiloAppService
|
||||
import ai.kilocode.client.app.KiloWorkspaceService
|
||||
import ai.kilocode.client.plugin.KiloBundle
|
||||
import ai.kilocode.client.settings.base.BaseSettingsUi
|
||||
import ai.kilocode.client.ui.UiStyle
|
||||
import ai.kilocode.log.KiloLog
|
||||
import ai.kilocode.rpc.dto.ConfigPatchDto
|
||||
import ai.kilocode.rpc.dto.KiloAppStateDto
|
||||
import ai.kilocode.rpc.dto.KiloAppStatusDto
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.ui.DocumentAdapter
|
||||
import com.intellij.ui.SearchTextField
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import com.intellij.util.ui.JBUI
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import java.awt.BorderLayout
|
||||
import javax.swing.event.DocumentEvent
|
||||
|
||||
internal class AutoApproveSettingsUi(
|
||||
cs: CoroutineScope,
|
||||
@@ -23,9 +29,16 @@ internal class AutoApproveSettingsUi(
|
||||
app,
|
||||
workspaces,
|
||||
loginBanner = false,
|
||||
scroll = false,
|
||||
) {
|
||||
private val search = SearchTextField(false)
|
||||
|
||||
init {
|
||||
search.textEditor.emptyText.text = KiloBundle.message("settings.autoApprove.filter")
|
||||
search.border = JBUI.Borders.empty(UiStyle.Gap.md(), UiStyle.Gap.pad())
|
||||
search.textEditor.document.addDocumentListener(object : DocumentAdapter() {
|
||||
override fun textChanged(e: DocumentEvent) = form.filter(search.text)
|
||||
})
|
||||
content.add(search, BorderLayout.NORTH)
|
||||
startSettings(AutoApproveContent({ updateDraft(it) }, picker))
|
||||
}
|
||||
|
||||
|
||||
+8
-4
@@ -11,8 +11,10 @@ import javax.swing.ListSelectionModel
|
||||
|
||||
/** One granular permission tool (`external_directory`, `bash`, `read`, `edit`). */
|
||||
internal class GranularToolSection(
|
||||
private val tool: String,
|
||||
private val tool: String,
|
||||
description: String,
|
||||
private val wildcardLabel: String,
|
||||
emptyText: String,
|
||||
addLabel: String,
|
||||
placeholder: String,
|
||||
picker: LevelPicker,
|
||||
@@ -24,8 +26,7 @@ internal class GranularToolSection(
|
||||
) : BaseContentPanel() {
|
||||
private val wildcard = LevelSelect(onWildcardChange) { onWildcardInherit() }
|
||||
private val list = SettingsInlineList(
|
||||
empty = KiloBundle.message("settings.autoApprove.filters.empty"),
|
||||
search = KiloBundle.message("settings.autoApprove.filters.search"),
|
||||
empty = emptyText,
|
||||
addLabel = addLabel,
|
||||
placeholder = placeholder,
|
||||
right = toolbarRight(),
|
||||
@@ -47,7 +48,10 @@ internal class GranularToolSection(
|
||||
list.syncItems(exceptions(rule), enabled)
|
||||
}
|
||||
|
||||
@RequiresEdt
|
||||
fun filter(query: String) = list.filter(query)
|
||||
|
||||
private fun toolbarRight() = Stack.horizontal(UiStyle.Gap.sm())
|
||||
.next(JBLabel(KiloBundle.message("settings.autoApprove.defaultSetting")))
|
||||
.next(JBLabel(wildcardLabel))
|
||||
.next(wildcard)
|
||||
}
|
||||
|
||||
+1
-3
@@ -63,7 +63,6 @@ internal fun levelChoiceLabel(choice: LevelChoice): String = when (choice) {
|
||||
*/
|
||||
internal class SettingsInlineList(
|
||||
private val empty: String,
|
||||
private val search: String,
|
||||
private val addLabel: String? = null,
|
||||
private val placeholder: String = "",
|
||||
private val right: JComponent? = null,
|
||||
@@ -77,6 +76,7 @@ internal class SettingsInlineList(
|
||||
empty,
|
||||
SettingsListConfig.Equal,
|
||||
selectionMode,
|
||||
showSearch = false,
|
||||
) {
|
||||
|
||||
/** Overridable in tests, mirrors `PatternList.input` in ContextSettingsUi.kt. */
|
||||
@@ -123,8 +123,6 @@ internal class SettingsInlineList(
|
||||
|
||||
override fun toolbarRight(): JComponent? = right
|
||||
|
||||
override fun searchPlaceholder(): String = search
|
||||
|
||||
private fun promptAdd() {
|
||||
if (!isEnabled) return
|
||||
val add = onAdd ?: return
|
||||
|
||||
-4
@@ -34,7 +34,3 @@ abstract class DraftReadyConfigurableBase<T : JComponent> : KiloReadyConfigurabl
|
||||
}
|
||||
|
||||
abstract class DraftReadyConfigurable<T : JComponent> : DraftReadyConfigurableBase<T>(), Configurable.NoScroll
|
||||
|
||||
abstract class ScrollableDraftReadyConfigurable<T : JComponent> : DraftReadyConfigurableBase<T>() {
|
||||
override fun scrollReadyShell(): Boolean = false
|
||||
}
|
||||
|
||||
+18
-6
@@ -31,6 +31,7 @@ internal abstract class SettingsInlineListPanel(
|
||||
emptyText: String,
|
||||
cfg: SettingsListConfig = SettingsListConfig.Equal,
|
||||
private val selectionMode: Int = ListSelectionModel.SINGLE_SELECTION,
|
||||
private val showSearch: Boolean = true,
|
||||
) : BaseContentPanel() {
|
||||
private val search = SearchTextField(false)
|
||||
protected val view = SettingsListView(emptyText, cfg) { key, cellId -> onCell(key, cellId) }
|
||||
@@ -39,17 +40,26 @@ internal abstract class SettingsInlineListPanel(
|
||||
@RequiresEdt
|
||||
protected fun start() {
|
||||
checkEdt()
|
||||
search.textEditor.emptyText.text = searchPlaceholder()
|
||||
view.list.selectionMode = selectionMode
|
||||
view.minimumSize = JBUI.size(0, minListHeight())
|
||||
view.list.minimumSize = JBUI.size(0, minListHeight())
|
||||
view.onSelect = { toolbar?.updateActionsImmediately() }
|
||||
next(toolbarRow())
|
||||
gap(UiStyle.Gap.sm())
|
||||
next(search)
|
||||
gap(UiStyle.Gap.sm())
|
||||
if (showSearch) {
|
||||
search.textEditor.emptyText.text = searchPlaceholder()
|
||||
next(search)
|
||||
gap(UiStyle.Gap.sm())
|
||||
wireSearch()
|
||||
}
|
||||
next(view)
|
||||
wireSearch()
|
||||
}
|
||||
|
||||
/** Filter list rows by [query]. Used by an external search field when the list hides its own. */
|
||||
@RequiresEdt
|
||||
fun filter(query: String) {
|
||||
checkEdt()
|
||||
view.filter(query)
|
||||
}
|
||||
|
||||
@RequiresEdt
|
||||
@@ -68,8 +78,10 @@ internal abstract class SettingsInlineListPanel(
|
||||
|
||||
override fun setEnabled(enabled: Boolean) {
|
||||
super.setEnabled(enabled)
|
||||
search.isEnabled = enabled
|
||||
search.textEditor.isEnabled = enabled
|
||||
if (showSearch) {
|
||||
search.isEnabled = enabled
|
||||
search.textEditor.isEnabled = enabled
|
||||
}
|
||||
view.isEnabled = enabled
|
||||
view.setBusy(!enabled)
|
||||
toolbar?.updateActionsImmediately()
|
||||
|
||||
@@ -345,14 +345,15 @@ settings.autoApprove.default=Default ({0})
|
||||
settings.autoApprove.level.allow=Allow
|
||||
settings.autoApprove.level.ask=Ask
|
||||
settings.autoApprove.level.deny=Deny
|
||||
settings.autoApprove.defaultSetting=Default Setting
|
||||
settings.autoApprove.filters.empty=No filters
|
||||
settings.autoApprove.filters.search=Filter filters
|
||||
settings.autoApprove.filter=Filter auto-approve rules
|
||||
settings.autoApprove.wildcardLabel.commands=All commands (*)
|
||||
settings.autoApprove.wildcardLabel.paths=All paths (*)
|
||||
settings.autoApprove.filters.empty.commands=No custom commands
|
||||
settings.autoApprove.filters.empty.paths=No custom paths
|
||||
settings.autoApprove.tools.empty=No tools
|
||||
settings.autoApprove.tools.search=Filter tools
|
||||
settings.autoApprove.add=Add
|
||||
settings.autoApprove.delete=Delete
|
||||
settings.autoApprove.delete.description=Delete selected filters
|
||||
settings.autoApprove.delete.description=Delete selected
|
||||
settings.autoApprove.addCommand=Add command
|
||||
settings.autoApprove.addPath=Add path
|
||||
settings.autoApprove.placeholder.command=e.g. git *
|
||||
|
||||
+4
-2
@@ -40,10 +40,12 @@ class KiloSettingsConfigurableTest : BasePlatformTestCase() {
|
||||
assertEquals("ai.kilocode.jetbrains.settings.agentBehavior", AgentBehaviorConfigurable.ID)
|
||||
}
|
||||
|
||||
fun `test auto approve uses platform configurable scrollpane`() {
|
||||
fun `test auto approve opts out of platform scrollpane`() {
|
||||
// Auto-Approve renders its own fixed search field and scrollable body, so it must not be
|
||||
// wrapped in the platform configurable scrollpane.
|
||||
val auto: Configurable = AutoApproveConfigurable()
|
||||
val context: Configurable = ContextConfigurable()
|
||||
assertFalse(auto is Configurable.NoScroll)
|
||||
assertTrue(auto is Configurable.NoScroll)
|
||||
assertTrue(context is Configurable.NoScroll)
|
||||
}
|
||||
|
||||
|
||||
+2
-6
@@ -31,7 +31,7 @@ class SettingsInlineListTest : BasePlatformTestCase() {
|
||||
list.syncItems(listOf("*.env" to "deny", "*.key" to "deny", "*.pem" to "deny"), true)
|
||||
layout(list)
|
||||
|
||||
search(list).text = "nomatch"
|
||||
list.filter("nomatch")
|
||||
layout(list)
|
||||
|
||||
assertEquals(0, jbList(list).model.size)
|
||||
@@ -113,7 +113,7 @@ class SettingsInlineListTest : BasePlatformTestCase() {
|
||||
}
|
||||
}
|
||||
|
||||
fun `test setEnabled disables search add and list`() {
|
||||
fun `test setEnabled disables add and list`() {
|
||||
edt {
|
||||
val list = list()
|
||||
list.syncItems(listOf("*.env" to "deny"), true)
|
||||
@@ -134,7 +134,6 @@ class SettingsInlineListTest : BasePlatformTestCase() {
|
||||
selection: Int = ListSelectionModel.SINGLE_SELECTION,
|
||||
): SettingsInlineList = SettingsInlineList(
|
||||
empty = "Empty",
|
||||
search = "Search",
|
||||
addLabel = "Add",
|
||||
placeholder = "e.g. *.env",
|
||||
onAdd = onAdd,
|
||||
@@ -162,9 +161,6 @@ class SettingsInlineListTest : BasePlatformTestCase() {
|
||||
|
||||
private fun jbList(list: SettingsInlineList): JBList<*> = components(list).filterIsInstance<JBList<*>>().single()
|
||||
|
||||
private fun search(list: SettingsInlineList): javax.swing.text.JTextComponent =
|
||||
components(list).filterIsInstance<javax.swing.text.JTextComponent>().first()
|
||||
|
||||
private fun layout(root: Container) {
|
||||
root.setSize(400, root.preferredSize.height.coerceAtLeast(50))
|
||||
root.doLayout()
|
||||
|
||||
Reference in New Issue
Block a user