1
0
mirror of https://github.com/chylex/IntelliJ-Keyboard-Master.git synced 2025-09-15 17:32:10 +02:00

Compare commits

..

18 Commits

Author SHA1 Message Date
7596b8727a Release version 0.6.2 2024-09-19 05:36:42 +02:00
38c80a7b27 Change vim-style navigation binding for 'X' to close all tree nodes 2024-09-18 20:00:34 +02:00
efe13712ad Add new vim-style bindings for navigating tree hierarchy 2024-09-18 20:00:29 +02:00
5aea7947ec Disable automatic tree auto-expansion in vim-style navigation 2024-09-18 08:52:51 +02:00
f814ec04bd Release version 0.6.1 2024-08-11 11:22:06 +02:00
c7229a6d8e Fix leaking editor instances 2024-08-11 11:21:49 +02:00
d711e60da8 Release version 0.6.0 2024-08-10 15:35:08 +02:00
356826f6ae Update uses of obsolete APIs for IDEA 2024.2 2024-08-10 15:35:08 +02:00
f3a3685084 Release version 0.5.8 2024-08-05 03:55:32 +02:00
5e654762fc Fix vim-style navigation not scrolling after collapsing a tree node 2024-08-05 03:55:14 +02:00
723b8af939 Release version 0.5.7 2024-07-15 13:08:10 +02:00
72158689ff Add vim-style binding to expand tree to the next level 2024-07-15 13:06:44 +02:00
d76e259c25 Release version 0.5.6 2024-06-18 12:40:58 +02:00
6ba2a0863e Add vim-style bindings for Ctrl/Cmd+Enter to immediately execute Enter action during search 2024-06-18 12:17:12 +02:00
48a9159c87 Release version 0.5.5 2024-05-19 15:58:49 +02:00
844738794b Make vim-style navigation Enter handling during search in popups consistent with other components 2024-05-19 15:58:25 +02:00
0e2928a737 Fix vim-style navigation preventing default Enter action in components with key listeners 2024-05-19 15:54:30 +02:00
b131413c8d Fix vim-style navigation being active in popups even when disabled 2024-05-19 15:13:59 +02:00
11 changed files with 240 additions and 38 deletions

View File

@@ -8,7 +8,7 @@ plugins {
}
group = "com.chylex.intellij.keyboardmaster"
version = "0.5.4"
version = "0.6.2"
repositories {
mavenCentral()
@@ -16,7 +16,7 @@ repositories {
intellij {
type.set("IU")
version.set("2024.1.1")
version.set("2024.2")
updateSinceUntilBuild.set(false)
plugins.add("com.intellij.java")
@@ -35,7 +35,7 @@ dependencies {
}
tasks.patchPluginXml {
sinceBuild.set("241")
sinceBuild.set("242")
}
tasks.test {

View File

@@ -2,9 +2,10 @@ package com.chylex.intellij.keyboardmaster.feature.action.gotoError
import com.intellij.codeInsight.CodeInsightActionHandler
import com.intellij.codeInsight.daemon.impl.actions.GotoNextErrorAction
import com.intellij.openapi.actionSystem.DataContext
class GotoNextErrorInOtherModeAction : GotoNextErrorAction() {
override fun getHandler(): CodeInsightActionHandler {
override fun getHandler(dataContext: DataContext): CodeInsightActionHandler {
return GotoErrorInOtherModeHandler(forward = true)
}
}

View File

@@ -2,9 +2,10 @@ package com.chylex.intellij.keyboardmaster.feature.action.gotoError
import com.intellij.codeInsight.CodeInsightActionHandler
import com.intellij.codeInsight.daemon.impl.actions.GotoPreviousErrorAction
import com.intellij.openapi.actionSystem.DataContext
class GotoPreviousErrorInOtherModeAction : GotoPreviousErrorAction() {
override fun getHandler(): CodeInsightActionHandler {
override fun getHandler(dataContext: DataContext): CodeInsightActionHandler {
return GotoErrorInOtherModeHandler(forward = false)
}
}

View File

@@ -52,7 +52,7 @@ class GotoTypeInFileHandler(private val forward: Boolean) : CodeInsightActionHan
private companion object {
fun getNavigationOffsets(file: PsiFile, searchedOffsetRange: IntRange): IntArray {
val structureViewBuilder = LanguageStructureViewBuilder.INSTANCE.getStructureViewBuilder(file)
val structureViewBuilder = LanguageStructureViewBuilder.getInstance().getStructureViewBuilder(file)
if (structureViewBuilder !is TreeBasedStructureViewBuilder) {
return intArrayOf()
}

View File

@@ -1,13 +1,13 @@
package com.chylex.intellij.keyboardmaster.feature.codeCompletion
import com.intellij.util.containers.IntIntHashMap
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap
object CodeCompletionPopupConfiguration {
const val SHORTCUT_NONE = -1
const val SHORTCUT_NEXT_PAGE = -2
const val SHORTCUT_PREV_PAGE = -3
private val charToShortcutMap = IntIntHashMap(16, SHORTCUT_NONE)
private val charToShortcutMap = Int2IntOpenHashMap(16).also { it.defaultReturnValue(SHORTCUT_NONE) }
private var hintTexts = mutableListOf<String>()
val itemShortcutCount

View File

@@ -59,7 +59,7 @@ internal interface KeyStrokeNode<T> {
override fun performAction(holder: T, actionEvent: AnActionEvent, keyEvent: KeyEvent) {
val action = actionEvent.actionManager.getAction(name) ?: return
val dataContext = CustomizedDataContext.create(actionEvent.dataContext) {
val dataContext = CustomizedDataContext.withProvider(actionEvent.dataContext) {
when {
PlatformDataKeys.CONTEXT_COMPONENT.`is`(it) -> holder.component
else -> null

View File

@@ -8,7 +8,7 @@ import com.intellij.ui.popup.list.ListPopupImpl
internal object PopupInterceptor : PersistentUiInterceptor<AbstractPopup>(AbstractPopup::class.java) {
override fun shouldIntercept(component: AbstractPopup): Boolean {
if (component is ListPopupImpl) {
if (component is ListPopupImpl && VimNavigation.isEnabled) {
VimListNavigation.install(component.list, component)
}

View File

@@ -18,18 +18,21 @@ import javax.swing.KeyStroke
import javax.swing.UIManager
object VimNavigation {
private val isEnabled = AtomicBoolean(false)
private val isEnabledFlag = AtomicBoolean(false)
private var originalPopupBindings: Array<*>? = null
val isEnabled
get() = isEnabledFlag.get()
fun register() {
val disposable = ApplicationManager.getApplication().getService(PluginDisposableService::class.java)
StartupUiUtil.addAwtListener(::handleEvent, AWTEvent.FOCUS_EVENT_MASK, disposable)
StartupUiUtil.addAwtListener(AWTEvent.FOCUS_EVENT_MASK, disposable, ::handleEvent)
UiInterceptors.registerPersistent(disposable, PopupInterceptor)
}
fun setEnabled(enabled: Boolean) {
if (!isEnabled.compareAndSet(!enabled, enabled)) {
if (!isEnabledFlag.compareAndSet(!enabled, enabled)) {
return
}
@@ -61,7 +64,7 @@ object VimNavigation {
}
private fun handleEvent(event: AWTEvent) {
if (event is FocusEvent && event.id == FocusEvent.FOCUS_GAINED && isEnabled.get()) {
if (event is FocusEvent && event.id == FocusEvent.FOCUS_GAINED && isEnabled) {
when (val source = event.source) {
is JList<*> -> VimListNavigation.install(source)
is JTree -> VimTreeNavigation.install(source)

View File

@@ -1,6 +1,7 @@
package com.chylex.intellij.keyboardmaster.feature.vimNavigation
import com.chylex.intellij.keyboardmaster.PluginDisposableService
import com.chylex.intellij.keyboardmaster.feature.vimNavigation.VimNavigationDispatcher.WrappedAction.ForKeyListener
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
@@ -9,6 +10,7 @@ import com.intellij.openapi.actionSystem.KeyboardShortcut
import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.util.SystemInfo
import com.intellij.toolWindow.InternalDecoratorImpl
import com.intellij.ui.SpeedSearchBase
import com.intellij.ui.speedSearch.SpeedSearch
@@ -20,15 +22,19 @@ import java.awt.event.ActionListener
import java.awt.event.KeyEvent
import java.beans.PropertyChangeEvent
import java.util.concurrent.atomic.AtomicBoolean
import javax.swing.Action
import javax.swing.JComponent
import javax.swing.KeyStroke
internal open class VimNavigationDispatcher<T : JComponent>(final override val component: T, private val rootNode: KeyStrokeNode.Parent<VimNavigationDispatcher<T>>) : DumbAwareAction(), ComponentHolder {
internal open class VimNavigationDispatcher<T : JComponent>(final override val component: T, private val rootNode: KeyStrokeNode.Parent<VimNavigationDispatcher<T>>, disposable: Disposable? = null) : DumbAwareAction(), ComponentHolder {
companion object {
private val DISPOSABLE = ApplicationManager.getApplication().getService(PluginDisposableService::class.java)
private val ENTER_KEY = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)
@JvmStatic
protected val ENTER_KEY: KeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)
private fun findOriginalEnterAction(component: JComponent): WrappedAction? {
private val CTRL_ENTER_KEY: KeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.CTRL_DOWN_MASK)
private val META_ENTER_KEY: KeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.META_DOWN_MASK)
private fun findOriginalEnterAction(component: JComponent): WrappedAction {
var originalEnterAction: WrappedAction? = null
for (container in JBIterable.generate<Container>(component) { it.parent }) {
@@ -36,7 +42,7 @@ internal open class VimNavigationDispatcher<T : JComponent>(final override val c
continue
}
container.getActionForKeyStroke(ENTER_KEY)?.let {
container.getActionForKeyStroke(ENTER_KEY)?.takeUnless(::isIgnoredEnterAction)?.let {
originalEnterAction = WrappedAction.ForActionListener(container, it)
}
@@ -47,7 +53,11 @@ internal open class VimNavigationDispatcher<T : JComponent>(final override val c
}
}
return originalEnterAction
return originalEnterAction ?: ForKeyListener(component)
}
private fun isIgnoredEnterAction(action: ActionListener): Boolean {
return action is Action && action.getValue(Action.NAME) == "toggle"
}
@Suppress("UnstableApiUsage")
@@ -61,12 +71,12 @@ internal open class VimNavigationDispatcher<T : JComponent>(final override val c
var isSearching = AtomicBoolean(false)
init {
registerCustomShortcutSet(KeyStrokeNode.getAllShortcuts(getAllKeyStrokes()), component, DISPOSABLE)
registerCustomShortcutSet(KeyStrokeNode.getAllShortcuts(getAllKeyStrokes()), component, disposable)
SpeedSearchSupply.getSupply(component, true)?.addChangeListener(::handleSpeedSearchChange)
}
protected fun getAllKeyStrokes(): Set<KeyStroke> {
return KeyStrokeNode.getAllKeyStrokes(rootNode, setOf(ENTER_KEY))
return KeyStrokeNode.getAllKeyStrokes(rootNode, setOf(ENTER_KEY, CTRL_ENTER_KEY, META_ENTER_KEY))
}
private fun handleSpeedSearchChange(e: PropertyChangeEvent) {
@@ -100,15 +110,27 @@ internal open class VimNavigationDispatcher<T : JComponent>(final override val c
}
private fun handleEnterKeyPress(actionEvent: AnActionEvent, keyEvent: KeyEvent) {
if (isSearching.compareAndSet(true, false)) {
when (val supply = SpeedSearchSupply.getSupply(component)) {
is SpeedSearchBase<*> -> supply.hidePopup()
is SpeedSearch -> supply.reset()
}
handleEnterKeyPress(keyEvent) { originalEnterAction.perform(actionEvent, it) }
}
protected inline fun handleEnterKeyPress(keyEvent: KeyEvent, originalAction: (KeyEvent) -> Unit) {
if (isSearching.compareAndSet(true, false) && !runEnterActionImmediately(keyEvent)) {
stopSpeedSearch()
}
else {
currentNode = rootNode
originalEnterAction?.perform(actionEvent, keyEvent)
originalAction(keyEvent)
}
}
private fun runEnterActionImmediately(keyEvent: KeyEvent): Boolean {
return if (SystemInfo.isMac) keyEvent.isMetaDown else keyEvent.isControlDown
}
protected open fun stopSpeedSearch() {
when (val supply = SpeedSearchSupply.getSupply(component)) {
is SpeedSearchBase<*> -> supply.hidePopup()
is SpeedSearch -> supply.reset()
}
}
@@ -132,7 +154,7 @@ internal open class VimNavigationDispatcher<T : JComponent>(final override val c
private sealed interface WrappedAction {
fun perform(actionEvent: AnActionEvent, keyEvent: KeyEvent)
class ForActionListener(val component: JComponent, val listener: ActionListener) : WrappedAction {
class ForActionListener(private val component: JComponent, private val listener: ActionListener) : WrappedAction {
override fun perform(actionEvent: AnActionEvent, keyEvent: KeyEvent) {
listener.actionPerformed(ActionEvent(component, ActionEvent.ACTION_PERFORMED, "Enter", keyEvent.`when`, keyEvent.modifiersEx))
}
@@ -143,5 +165,12 @@ internal open class VimNavigationDispatcher<T : JComponent>(final override val c
action.actionPerformed(actionEvent)
}
}
class ForKeyListener(private val component: JComponent) : WrappedAction {
override fun perform(actionEvent: AnActionEvent, keyEvent: KeyEvent) {
val unconsumedKeyEvent = KeyEvent(component, keyEvent.id, keyEvent.`when`, keyEvent.modifiersEx, keyEvent.keyCode, keyEvent.keyChar, keyEvent.keyLocation)
component.dispatchEvent(unconsumedKeyEvent)
}
}
}
}

View File

@@ -8,6 +8,7 @@ import com.intellij.openapi.ui.getUserData
import com.intellij.openapi.ui.putUserData
import com.intellij.openapi.util.Key
import com.intellij.ui.popup.WizardPopup
import com.intellij.ui.popup.list.ListPopupImpl
import com.intellij.ui.speedSearch.SpeedSearch
import com.intellij.ui.speedSearch.SpeedSearchSupply
import java.awt.event.ActionEvent
@@ -59,7 +60,7 @@ internal object VimListNavigation {
}
@Suppress("serial")
private class VimPopupListNavigationDispatcher(component: JList<*>, override val popup: WizardPopup) : VimNavigationDispatcher<JList<*>>(component, POPUP_LIST_ROOT_NODE) {
private class VimPopupListNavigationDispatcher(component: JList<*>, override val popup: WizardPopup) : VimNavigationDispatcher<JList<*>>(component, POPUP_LIST_ROOT_NODE, popup.parent) {
init {
val speedSearch = SpeedSearchSupply.getSupply(component, true) as? SpeedSearch
if (speedSearch != null) {
@@ -85,6 +86,24 @@ internal object VimListNavigation {
// WizardPopup only checks key codes against its input map, but key codes may be undefined for some characters.
popup.registerAction("KeyboardMaster-VimListNavigation-PauseSpeedSearch", KeyStroke.getKeyStroke(KeyEvent.CHAR_UNDEFINED, 0), pauseAction)
popup.registerAction("KeyboardMaster-VimListNavigation-PauseSpeedSearch", KeyStroke.getKeyStroke(KeyEvent.CHAR_UNDEFINED, KeyEvent.SHIFT_DOWN_MASK), pauseAction)
if (popup is ListPopupImpl) {
popup.registerAction("KeyboardMaster-VimListNavigation-Enter", ENTER_KEY, object : AbstractAction() {
override fun actionPerformed(e: ActionEvent) {
handleEnterKeyPress(createEnterEvent(e)) { popup.handleSelect(true, it) }
}
private fun createEnterEvent(e: ActionEvent): KeyEvent {
return KeyEvent(component, KeyEvent.KEY_PRESSED, e.`when`, e.modifiers, KeyEvent.VK_ENTER, KeyEvent.CHAR_UNDEFINED)
}
})
}
}
override fun stopSpeedSearch() {
val selectedValue = component.selectedValue
super.stopSpeedSearch()
component.setSelectedValue(selectedValue, true)
}
private class PauseSpeedSearchAction(private val dispatcher: VimNavigationDispatcher<JList<*>>, private val speedSearch: SpeedSearch) : AbstractAction() {

View File

@@ -8,9 +8,12 @@ import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.ui.getUserData
import com.intellij.openapi.ui.putUserData
import com.intellij.openapi.util.Key
import com.intellij.ui.ClientProperty
import com.intellij.ui.tree.ui.DefaultTreeUI
import java.awt.event.KeyEvent
import javax.swing.JTree
import javax.swing.KeyStroke
import javax.swing.tree.TreePath
internal object VimTreeNavigation {
private val KEY = Key.create<VimNavigationDispatcher<JTree>>("KeyboardMaster-VimTreeNavigation")
@@ -22,21 +25,26 @@ internal object VimTreeNavigation {
KeyStroke.getKeyStroke('g') to IdeaAction("Tree-selectFirst"),
KeyStroke.getKeyStroke('j') to SelectLastSibling,
KeyStroke.getKeyStroke('k') to SelectFirstSibling,
KeyStroke.getKeyStroke('o') to ExpandChildrenToNextLevel,
)
),
KeyStroke.getKeyStroke('G') to IdeaAction("Tree-selectLast"),
KeyStroke.getKeyStroke('h') to CollapseSelfOrMoveToParentNode,
KeyStroke.getKeyStroke('H') to CollapseUntilRootNode,
KeyStroke.getKeyStroke('j') to IdeaAction("Tree-selectNext"),
KeyStroke.getKeyStroke('j', KeyEvent.ALT_DOWN_MASK) to IdeaAction("Tree-selectNextSibling"),
KeyStroke.getKeyStroke('J') to IdeaAction("Tree-selectNextExtendSelection"),
KeyStroke.getKeyStroke('k') to IdeaAction("Tree-selectPrevious"),
KeyStroke.getKeyStroke('k', KeyEvent.ALT_DOWN_MASK) to IdeaAction("Tree-selectPreviousSibling"),
KeyStroke.getKeyStroke('K') to IdeaAction("Tree-selectPreviousExtendSelection"),
KeyStroke.getKeyStroke('l') to ExpandSelfOrMoveToFirstChildNode,
KeyStroke.getKeyStroke('L') to ExpandUntilFirstLeafNode,
KeyStroke.getKeyStroke('o') to ExpandOrCollapseTreeNode,
KeyStroke.getKeyStroke('O') to IdeaAction("FullyExpandTreeNode"),
KeyStroke.getKeyStroke('p') to IdeaAction("Tree-selectParentNoCollapse"),
KeyStroke.getKeyStroke('P') to IdeaAction("Tree-selectFirst"),
KeyStroke.getKeyStroke('x') to CollapseSelfOrParentNode,
KeyStroke.getKeyStroke('X') to IdeaAction("CollapseTreeNode"),
KeyStroke.getKeyStroke('X') to CollapseAll,
)
)
@@ -55,7 +63,51 @@ internal object VimTreeNavigation {
tree.collapsePath(path)
}
else {
tree.expandPath(path)
runWithoutAutoExpand(tree) { tree.expandPath(path) }
}
}
}
private data object ExpandSelfOrMoveToFirstChildNode : ActionNode<VimNavigationDispatcher<JTree>> {
override fun performAction(holder: VimNavigationDispatcher<JTree>, actionEvent: AnActionEvent, keyEvent: KeyEvent) {
val tree = holder.component
val path = tree.selectionPath?.takeUnless { isLeaf(tree, it) } ?: return
if (tree.isExpanded(path)) {
selectRow(tree, getFirstChild(tree, path))
}
else {
runWithoutAutoExpand(tree) { tree.expandPath(path) }
}
}
}
private data object ExpandUntilFirstLeafNode : ActionNode<VimNavigationDispatcher<JTree>> {
override fun performAction(holder: VimNavigationDispatcher<JTree>, actionEvent: AnActionEvent, keyEvent: KeyEvent) {
val tree = holder.component
val path = tree.selectionPath ?: return
var firstChildPath = path
while (!isLeaf(tree, firstChildPath)) {
tree.expandPath(firstChildPath)
firstChildPath = getFirstChild(tree, firstChildPath)
}
selectRow(tree, firstChildPath)
}
}
private data object CollapseSelfOrMoveToParentNode : ActionNode<VimNavigationDispatcher<JTree>> {
override fun performAction(holder: VimNavigationDispatcher<JTree>, actionEvent: AnActionEvent, keyEvent: KeyEvent) {
val tree = holder.component
val path = tree.selectionPath ?: return
if (tree.isExpanded(path)) {
collapseAndScroll(tree, path)
}
else {
withParentPath(tree, path) { selectRow(tree, it) }
}
}
}
@@ -66,17 +118,76 @@ internal object VimTreeNavigation {
val path = tree.selectionPath ?: return
if (tree.isExpanded(path)) {
tree.collapsePath(path)
collapseAndScroll(tree, path)
}
else {
val parentPath = path.parentPath
if (parentPath.parentPath != null || tree.isRootVisible) {
tree.collapsePath(parentPath)
}
withParentPath(tree, path) { collapseAndScroll(tree, it) }
}
}
}
private data object CollapseUntilRootNode : ActionNode<VimNavigationDispatcher<JTree>> {
override fun performAction(holder: VimNavigationDispatcher<JTree>, actionEvent: AnActionEvent, keyEvent: KeyEvent) {
val tree = holder.component
val path = tree.selectionPath ?: return
var parentPath = path
while (true) {
parentPath = parentPath.parentPath.takeUnless { isInvisibleRoot(tree, it) } ?: break
}
collapseAndScroll(tree, parentPath)
}
}
private data object CollapseAll : ActionNode<VimNavigationDispatcher<JTree>> {
override fun performAction(holder: VimNavigationDispatcher<JTree>, actionEvent: AnActionEvent, keyEvent: KeyEvent) {
val tree = holder.component
CollapseUntilRootNode.performAction(holder, actionEvent, keyEvent)
var row = 0
while (row < tree.rowCount) {
tree.collapseRow(row)
row++
}
}
}
private data object ExpandChildrenToNextLevel : ActionNode<VimNavigationDispatcher<JTree>> {
override fun performAction(holder: VimNavigationDispatcher<JTree>, actionEvent: AnActionEvent, keyEvent: KeyEvent) {
val tree = holder.component
val model = tree.model
val path = tree.selectionPath?.takeUnless { isLeaf(tree, it) } ?: return
var pathsToExpand = mutableListOf(path)
do {
if (pathsToExpand.any(tree::isCollapsed)) {
runWithoutAutoExpand(tree) { pathsToExpand.forEach(tree::expandPath) }
break
}
val nextPathsToExpand = mutableListOf<TreePath>()
for (parentPath in pathsToExpand) {
val lastPathComponent = parentPath.lastPathComponent
for (i in 0 until model.getChildCount(lastPathComponent)) {
val child = model.getChild(lastPathComponent, i)
if (!model.isLeaf(child)) {
nextPathsToExpand.add(parentPath.pathByAddingChild(child))
}
}
}
pathsToExpand = nextPathsToExpand
} while (pathsToExpand.isNotEmpty())
}
}
private data object SelectFirstSibling : ActionNode<VimNavigationDispatcher<JTree>> {
override fun performAction(holder: VimNavigationDispatcher<JTree>, actionEvent: AnActionEvent, keyEvent: KeyEvent) {
val tree = holder.component
@@ -115,8 +226,46 @@ internal object VimTreeNavigation {
}
}
private inline fun runWithoutAutoExpand(tree: JTree, action: () -> Unit) {
val previousAutoExpandValue = ClientProperty.get(tree, DefaultTreeUI.AUTO_EXPAND_ALLOWED)
ClientProperty.put(tree, DefaultTreeUI.AUTO_EXPAND_ALLOWED, false)
try {
action()
} finally {
ClientProperty.put(tree, DefaultTreeUI.AUTO_EXPAND_ALLOWED, previousAutoExpandValue)
}
}
private fun selectRow(tree: JTree, row: Int) {
tree.setSelectionRow(row)
tree.scrollRowToVisible(row)
}
private fun selectRow(tree: JTree, path: TreePath) {
selectRow(tree, tree.getRowForPath(path))
}
private fun collapseAndScroll(tree: JTree, path: TreePath) {
tree.collapsePath(path)
tree.scrollRowToVisible(tree.getRowForPath(path))
}
private inline fun withParentPath(tree: JTree, path: TreePath, action: (TreePath) -> Unit) {
val parentPath = path.parentPath
if (!isInvisibleRoot(tree, parentPath)) {
action(parentPath)
}
}
private fun isInvisibleRoot(tree: JTree, parentPath: TreePath): Boolean {
return parentPath.parentPath == null && !tree.isRootVisible
}
private fun getFirstChild(tree: JTree, path: TreePath): TreePath {
return path.pathByAddingChild(tree.model.getChild(path.lastPathComponent, 0))
}
private fun isLeaf(tree: JTree, firstChildPath: TreePath): Boolean {
return tree.model.isLeaf(firstChildPath.lastPathComponent)
}
}