diff --git a/src/main/kotlin/com/chylex/intellij/keyboardmaster/feature/vimNavigation/components/VimTreeNavigation.kt b/src/main/kotlin/com/chylex/intellij/keyboardmaster/feature/vimNavigation/components/VimTreeNavigation.kt index 2f8e304..6585b4a 100644 --- a/src/main/kotlin/com/chylex/intellij/keyboardmaster/feature/vimNavigation/components/VimTreeNavigation.kt +++ b/src/main/kotlin/com/chylex/intellij/keyboardmaster/feature/vimNavigation/components/VimTreeNavigation.kt @@ -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,6 +25,7 @@ internal object VimTreeNavigation { KeyStroke.getKeyStroke('g') to IdeaAction("Tree-selectFirst"), KeyStroke.getKeyStroke('j') to SelectLastSibling, KeyStroke.getKeyStroke('k') to SelectFirstSibling, + KeyStroke.getKeyStroke('o') to ExpandTreeNodeChildrenToNextLevel, ) ), KeyStroke.getKeyStroke('G') to IdeaAction("Tree-selectLast"), @@ -77,6 +81,48 @@ internal object VimTreeNavigation { } } + private data object ExpandTreeNodeChildrenToNextLevel : 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 { model.isLeaf(it.lastPathComponent) } ?: 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 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 data object SelectFirstSibling : ActionNode<VimNavigationDispatcher<JTree>> { override fun performAction(holder: VimNavigationDispatcher<JTree>, actionEvent: AnActionEvent, keyEvent: KeyEvent) { val tree = holder.component