实现或修改 IntelliJ `AnAction`,并在 `plugin.xml` 中注册。将显示名称和说明写入消息资源文件,按需设置图标,并通过事件协程作用域启动挂起任务,避免同步处理阻塞 IDE。
- 为 IDE 菜单添加操作
- 修改操作注册或图标
- 整理操作名称和说明
使用 Kotlin UI DSL v2 构建 IntelliJ 对话框、设置页和工具窗口表单。学习布局、属性绑定、输入校验,以及按条件显示或启用组件。
Write IntelliJ dialogs and settings with Kotlin UI DSL v2 panels.
实测评分 未实测
查看安装方法本指南介绍如何使用 Kotlin UI DSL v2 构建 IntelliJ 对话框、设置页和表单式工具窗口内容。以 panel { } 为中心,说明如何排列行和单元格、分组、创建多列,以及调整宽度和间距。指南还介绍文本框、复选框和选项列表等常见组件工厂,以及如何将组件绑定到属性,从而处理应用、重置和变更判断。输入校验部分涵盖输入过程中和应用设置时的检查,也说明如何根据条件显示或启用组件。文中提供 UI DSL Showcase 和 UI Sandbox 的示例参考,并介绍从消息包获取界面文案的方法。
为 IntelliJ 插件创建设置页或输入对话框时可以使用。需要统一排列组件、绑定属性、显示输入错误,或根据条件控制界面显示和启用状态时也适用。
本指南针对 Kotlin UI DSL v2,v1 已被移除。请在 EDT 上构建 UI DSL,并避免在 panel { } 内调用服务或执行 I/O。
本介绍由 AI(OpenAI Codex)根据公开的 SKILL.md 撰写。准确的规格请查看下方原文。
AI 根据 SKILL.md 的内容检查了需要注意的操作和使用前提。评分只依据实际运行的结果(实测对比)。
不计入评分。 由 AI(OpenAI Codex)于 2026年10月28日 评估。仅供参考,安装前请同时查看原文。
安装前可以查看交给 AI 代理的指令内容。
Use Kotlin UI DSL (com.intellij.ui.dsl.builder, "version 2") for new IntelliJ forms: dialogs, settings pages, and form-like tool-window content. panel { } returns a DialogPanel with layout, data binding, and validation wired together.
Version 1 (the com.intellij.ui.layout builder: LayoutBuilder, CellBuilder, PropertyBinding, noteRow, titledRow, ...) was removed in July 2026 — never write against it and do not copy old snippets that import it. ComponentPredicate and ValidationInfoBuilder still live in the com.intellij.ui.layout package, but they are current v2 support code, not leftovers.
For component architecture (state flow, EDT, lifecycle) apply the Swing component architecture skill; for accessibility review apply the UI accessibility skill.
community/platform/platform-api/src/com/intellij/ui/dsl/builder/ — Panel.kt, Row.kt, Cell.kt, plus per-component extensions (textField.kt, button.kt, comboBox.kt, spinner.kt, ...). Implementations: community/platform/platform-impl/src/com/intellij/ui/dsl/builder/impl/.community/plugins/devkit/intellij.devkit.uiDsl/src/showcase/Demo*.kt (Basics, RowLayout, ComponentLabels, Comments, Components, Gaps, Groups, Availability, Validation, Binding, Examples). Run via Tools | Internal Actions | UI | Kotlin UI DSL | UI DSL Showcase.community/plugins/devkit/intellij.devkit.uiDsl/src/sandbox/. Run via Tools | Internal Actions | UI | UI Sandbox.Before inventing a pattern, check whether a Demo*.kt or sandbox panel already demonstrates it.
A panel is a grid built row by row:
val panel: DialogPanel = panel {
row(MyBundle.message("label.host")) { // "Host:" — sets labelFor + mnemonics + accessible context
textField()
.align(AlignX.FILL)
.bindText(model::host)
}
row(MyBundle.message("label.port")) {
intTextField(range = 0..65535)
.bindIntText(model::port)
}
row {
checkBox(MyBundle.message("checkbox.use.auth"))
.bindSelected(model::useAuth)
}
}
row("Label:") { ... } — labeled row. Always attach labels of modifiable components via row(label) or Cell.label(...), never a bare label(...) cell next to them: these two methods set correct spacing, mnemonics, labelFor, and accessible context. Label text ends with a colon; use row("") for a row that must align with labeled siblings.Cell.label(text, LabelPosition.TOP) puts the label above the component.Row.layout(RowLayout) controls grid participation:
INDEPENDENT — row has its own grid (default for rows without a label).LABEL_ALIGNED — label sits in the parent grid, the rest is independent (default for row("Label:")). Note: without a label the first cell (often a checkbox) is treated as the label.PARENT_GRID — every cell participates in the parent grid (use for column-aligned forms)..layout(RowLayout.INDEPENDENT).Row.resizableRow() — the row takes the free vertical space (several resizable rows share it). Combine with align(Align.FILL) on the cell (text areas, trees, tables).Row.rowComment("...") — gray comment under the whole row; follows the row's visible/enabled state.Row.topGap(TopGap.SMALL|MEDIUM) / bottomGap(...) — vertical gaps; attach them to the related row so hiding it doesn't leave a stray gap. Default vertical gap is none; group adds its own.Always prefer a factory over cell(JBTextField()) — factories apply platform defaults (widths, gaps, group membership):
checkBox(text), threeStateCheckBox(text), radioButton(text, value = null)button(text) { }, button(text, anAction), actionButton(anAction) / actionsButton(...) (extensions from platform-impl extensions.kt), segmentedButton(items) { text = ... }textField(), passwordField(), expandableTextField(), extendableTextField(), intTextField(range = null, keyboardStep = null), textArea(), textFieldWithBrowseButton(fileChooserDescriptor, project)comboBox(items or model, renderer = null), spinner(IntRange, step), spinner(ClosedRange<Double>, step), slider(min, max, minorTick, majorTick)label(text), text(text) (wrapping HTML text, supports links), comment(text), icon(icon), contextHelp(description, title = null) (the (?) icon), link(text) { }, browserLink(text, url), dropDownLink(item, items)cell(component) (custom component), scrollCell(component) (wraps in JBScrollPane), cell() (reserve an empty grid cell), placeholder() (content assigned later), panel { } (sub-panel cell)Tune components via .applyToComponent { ... }. Text-field width: .columns(COLUMNS_TINY|COLUMNS_SHORT|COLUMNS_MEDIUM|COLUMNS_LARGE) (6/18/25/36).
panel {
group(MyBundle.message("group.server")) { ... } // titled block, own grid, vertical gaps around
groupRowsRange(title) { ... } // titled block sharing the parent grid
collapsibleGroup(title) { ... } // collapsible; title is focusable, supports mnemonics
rowsRange { ... } // invisible range for batch visibleIf/enabledIf, parent grid
indent { ... } // standard left indent
separator() // plain line; for a titled line use group/groupRowsRange
panel { ... } // sub-panel: full width, own grid
twoColumnsRow({ checkBox(...) }, { checkBox(...) }) // and threeColumnsRow(...)
}
buttonsGroup(title = null) { ... } is required around radioButtons (never use raw javax.swing.ButtonGroup) and around checkboxes grouped under a title. Bind a value per radio button:
buttonsGroup(MyBundle.message("group.color")) {
for (value in Color.entries) {
row { radioButton(value.displayName, value) }
}
}.bind(model::color) // radioButton value arguments must match the property type
Multi-column blocks: put several panel { } cells in one row, aligning each with .align(AlignY.TOP); Row.panel { } builds the nested grid inline.
Cell.align(AlignX.LEFT|CENTER|RIGHT|FILL), AlignY.TOP|CENTER|BOTTOM|FILL, combined: align(AlignX.RIGHT + AlignY.TOP), Align.FILL, Align.CENTER. Default is AlignX.LEFT + AlignY.CENTER.Cell.resizableColumn() — this column takes the extra horizontal space; several share it. Setting it once per column is enough (other rows inherit). AlignX.FILL stretches the component inside the column; resizableColumn() grows the column itself — full-width fields usually need both.Cell.widthGroup("name") — equal widths within the group (e.g. buttons); do not combine with AlignX.FILL..gap(RightGap.SMALL) binds tightly related neighbors: checkbox-as-label before a field, field before its unit label (textField().gap(RightGap.SMALL); label("pixels")). RightGap.COLUMNS separates independent columns.JPanels or Borders to fix spacing. Use Cell.customize(UnscaledGaps(...)) / Row.customize(UnscaledGapsY(...)) for exceptional cases, and customizeSpacingConfiguration(...) to change spacing wholesale.Bindings connect a component to a property; the property is written only on DialogPanel.apply(), while reset() and isModified() come for free:
| Component | Binding |
|---|---|
checkBox, any AbstractButton | bindSelected(model::flag) |
textField & other text components | bindText(model::text) / bindText(getter, setter) |
intTextField | bindIntText(model::count) |
comboBox | bindItem(model::item.toNullableProperty()) |
spinner | bindIntValue(model::value) / bindValue(model::double) |
slider | bindValue(model::value) |
textFieldWithBrowseButton | bindText(model::path) |
buttonsGroup | .bind(model::choice) — radio buttons carry values |
segmentedButton | .bind(property) |
custom cell(c) | .bind(componentGet, componentSet, prop) |
KMutableProperty0 (model::field), getter/setter lambdas, MutableProperty(getter, setter); converters toMutableProperty(), toNullableProperty(), toNonNullableProperty(default). Binding an ObservableMutableProperty additionally updates immediately and auto-registers a validation requestor.bindItems: pass combo items at construction (comboBox(items, renderer)).Panel.onApply/onReset/onIsModified { } and Cell.onApply/onReset/onIsModified { }.DialogWrapper applies the panel on OK; BoundConfigurable delegates apply/reset/isModified. Call panel.apply()/reset()/isModified() manually only in custom hosts.Two rule styles on Cell:
// Stable API: ValidationInfoBuilder receiver — error(...) / warning(...)
textField()
.validationOnInput { if (it.text.toIntOrNull() == null) error(MyBundle.message("error.not.a.number")) else null }
.validationOnApply { if (it.text.isBlank()) error(MyBundle.message("error.specify.value")) else null }
// Newer API (experimental, used across the platform; will eventually be renamed `validation`)
textField().cellValidation {
addInputRule(MyBundle.message("error.contains.digits"), level = Level.WARNING) { it.text.any(Char::isDigit) }
addApplyRule(MyBundle.message("error.must.not.be.empty")) { it.text.isNullOrEmpty() } // condition true = problem
enabledIf(someCheckBox.selected)
}
validationOnInput runs on every change — keep it lightweight and only reject impossible input (bad characters, out-of-range). validationOnApply (and addApplyRule) runs on OK — put emptiness and expensive checks there. Never flag empty required fields on input or focus loss.warning(...) / Level.WARNING doesn't block OK; error(...) disables OK by default (.withOKEnabled() to keep it enabled in complex forms).DialogWrapper: return the DialogPanel from createCenterPanel() — validators register automatically on the dialog's disposable, doValidateAll() includes panel.validateAll(), OK applies the panel.BoundConfigurable / DslConfigurableBase: automatic as well.panel.registerValidators(parentDisposable) yourself; run panel.validateAll() before applying.cell(custom).validationRequestor { ... } (or the prebuilt WHEN_TEXT_CHANGED etc. from com.intellij.openapi.ui.validation) tells the panel when to re-validate.ComponentValidator(disposable).withValidator { ... }.installOn(field) per field and revalidate() all of them from each field's onChanged (see sandbox/dsl/validation/CrossValidationPanel.kt).visibleIf(predicate) / enabledIf(predicate) exist on Cell, Row, Panel, RowsRange and accept a ComponentPredicate or ObservableProperty<Boolean>. Built-ins: cellOrButton.selected, comboBox.selectedValueIs(v) / selectedValueMatches { }, textComponent.enteredTextSatisfies { }; combine with and / or / not.
lateinit var enableAll: Cell<JBCheckBox>
row { enableAll = checkBox(MyBundle.message("checkbox.enable")) }
indent {
row { checkBox(MyBundle.message("checkbox.option1")) }
}.enabledIf(enableAll.selected)
placeholder() reserves a cell whose component you assign or clear later; a nested panel { } put there keeps its own bindings and validation.
Change listeners: Cell.onChanged { component -> } / onChangedContext { component, context -> } (supported for buttons, text components, combo boxes, sliders; JSpinner is not supported and throws UiDslException). Experimental typed helpers take a disposable: whenTextChangedFromUi, whenStateChangedFromUi, whenItemSelectedFromUi.
Never hand-write ListCellRenderers for combo boxes and JBList — use the DSL from com.intellij.ui.dsl.listCellRenderer, which handles selection shapes (old/new UI), disabled colors, scaling, accessibility, and speed search:
comboBox(items, textListCellRenderer { it?.displayName }) // simple text
comboBox(items, listCellRenderer { // rich rows
icon(value.icon)
text(value.name)
text(value.detail) { foreground = greyForeground }
separator { text = "Group" } // section separator
})
BoundConfigurable(displayName) or BoundSearchableConfigurable(displayName, helpTopic) and build the UI in createPanel(): DialogPanel; use the inherited disposable for UI that needs one. Apply/reset/isModified/validation are handled by the base class.UiDslUnnamedConfigurable.Simple and override Panel.createContent(); root your content in group { } or panel { } so it doesn't interfere with the parent grid.MyBundle.message("key")); @NlsContexts annotations on the factory parameters enforce this. Sentence capitalization, no ending period on checkbox/radio labels, labels end with :.javax.swing.ButtonGroup; use buttonsGroup { }.JPanel/GridBagLayout wrappers inside DSL forms; nest panel { } / use rowsRange / customize(UnscaledGaps) instead.putClientProperty(DslComponentProperty.VISUAL_PADDINGS, UnscaledGaps.EMPTY) (see also DslComponentProperty.INTERACTIVE_COMPONENT for composite components so labelFor, validation, and onChanged target the right child).DialogPanel construction is EDT work like any Swing; do not run services or I/O inside the panel { } block.还没有评价。欢迎分享你的使用感受。
无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。
还没有评论。来写第一条吧。
实现或修改 IntelliJ `AnAction`,并在 `plugin.xml` 中注册。将显示名称和说明写入消息资源文件,按需设置图标,并通过事件协程作用域启动挂起任务,避免同步处理阻塞 IDE。
将 IntelliJ JPS 测试模块迁移到 Bazel,并排查仅在 Bazel 中出现的失败。根据症状检查依赖、插件、测试数据和 runfiles,重新生成并验证目标。
按 IntelliJ 项目规范编写或审查 Kotlin 和 Java 代码,涵盖语言选择、格式、密封类型、线程注解及控制流异常处理。
本指南帮助您按 IntelliJ 项目规则拆分更改并撰写提交信息:有工单时使用编号和变更说明,无工单时使用标签与子系统名,并在需要时说明原因和决定。
使用多个 Python 类型检查器比较代码片段或文件的类型推断和报错判断,并汇总各工具的结论、耗时与诊断信息,可保存为 Markdown 报告。
整理 Compose Desktop 界面中的状态和副作用,让可组合函数负责绘制,由展示器或协调器处理应用决策。了解状态归属、Effect、可复用组件接口、延迟列表键、动画和性能,适用于单项界面修改。