IntelliJプラグインのAnActionと登録設定を実装・変更し、表示文言やアイコンの定義、非同期処理の起動方法を整えます。
- IDEに新しいアクションを追加したいとき
- アクションの登録やアイコンを変更したいとき
- 表示名と説明文の定義を整理したいとき
IntelliJのダイアログや設定画面をKotlin UI DSL v2で組み立てる方法を案内し、レイアウト、データ連携、入力検証の実装を支援します。
Write IntelliJ dialogs and settings with Kotlin UI DSL v2 panels.
実行スコア 未検証
インストール方法を見るIntelliJのダイアログ、設定ページ、フォーム形式のツールウィンドウをKotlin UI DSL v2で作るための実装ガイドです。panel { }を中心に、行やセルの配置、グループ化、複数列、幅や余白の調整などを説明します。テキスト欄やチェックボックス、選択リストなどの部品を使い、プロパティと結び付けることで、適用・リセット・変更判定を扱えます。入力中と適用時の検証、条件に応じた表示・有効化、設定画面での利用方法も確認できます。既存のShowcaseやSandboxを参照する案内があり、画面文言をメッセージバンドルから取得することや、DSL内でI/Oを行わないことなどの注意も示しています。旧バージョンのUI DSLは削除済みとして扱い、v2のパターンを使うよう案内します。
IntelliJプラグインの設定画面や入力ダイアログを新しく作るときに向いています。項目の整列、プロパティとの連携、入力エラーの表示、条件付きUIを一貫した方法で実装したい場合に役立ちます。
Kotlin UI DSL v2を対象とします。UI DSLの構築はEDT上で行い、panel { }内でサービス呼び出しやI/Oを行わないよう案内されています。
この紹介文は、公開されている SKILL.md をもとに AI(OpenAI Codex)が作成しました。正確な仕様は下の原文を確認してください。
インストールする前に、エージェントに与えられる指示の中身を確認できます。
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と登録設定を実装・変更し、表示文言やアイコンの定義、非同期処理の起動方法を整えます。
IntelliJのJPSテストモジュールをBazelへ移行し、Bazelだけで起きる失敗を依存関係や実行環境から切り分ける手順を案内します。
IntelliJのKotlin・Javaコードを新規作成やレビューする際に、言語の選び方、書式、型の扱い、例外処理などのプロジェクト規約に沿って整えるスキルです。
IntelliJ向けの変更を適切な単位に分け、チケットの有無に応じた形式で、理由や判断も伝わるコミットメッセージを作成・修正するスキルです。
Pythonコードの型推論や型エラーを複数の型チェッカーで比較し、各ツールの判定と診断内容をまとめたレポートを作成します。
Compose Desktopの画面で状態や副作用をどこに持たせるか整理し、表示と判断の境界、再利用部品やリストの設計を整えます。