1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2024-11-24 22:42:53 +01:00

Compare commits

..

13 Commits

6 changed files with 113 additions and 126 deletions

View File

@ -14,7 +14,7 @@ ideaVersion=2024.1
ideaType=IC ideaType=IC
downloadIdeaSources=true downloadIdeaSources=true
instrumentPluginCode=true instrumentPluginCode=true
version=chylex-33 version=chylex-34
javaVersion=17 javaVersion=17
remoteRobotVersion=0.11.22 remoteRobotVersion=0.11.22
antlrVersion=4.10.1 antlrVersion=4.10.1

View File

@ -8,21 +8,25 @@
package com.maddyhome.idea.vim.extension.nerdtree package com.maddyhome.idea.vim.extension.nerdtree
import com.intellij.ide.projectView.ProjectView
import com.intellij.ide.projectView.impl.AbstractProjectViewPane
import com.intellij.ide.projectView.impl.ProjectViewImpl
import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.PlatformCoreDataKeys
import com.intellij.openapi.actionSystem.PlatformDataKeys import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.ui.getUserData import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.putUserData import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.util.Key import com.intellij.openapi.startup.ProjectActivity
import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowId import com.intellij.openapi.wm.ToolWindowId
import com.intellij.openapi.wm.ex.ToolWindowManagerEx import com.intellij.openapi.wm.ex.ToolWindowManagerEx
import com.intellij.openapi.wm.ex.ToolWindowManagerListener
import com.intellij.ui.KeyStrokeAdapter import com.intellij.ui.KeyStrokeAdapter
import com.intellij.ui.TreeExpandCollapse import com.intellij.ui.TreeExpandCollapse
import com.intellij.ui.speedSearch.SpeedSearchSupply import com.intellij.ui.speedSearch.SpeedSearchSupply
@ -49,8 +53,6 @@ import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import javax.swing.JComponent
import javax.swing.JTree
import javax.swing.KeyStroke import javax.swing.KeyStroke
import javax.swing.SwingConstants import javax.swing.SwingConstants
@ -130,6 +132,7 @@ internal class NerdTree : VimExtension {
synchronized(Util.monitor) { synchronized(Util.monitor) {
Util.commandsRegistered = true Util.commandsRegistered = true
ProjectManager.getInstance().openProjects.forEach { project -> installDispatcher(project) }
} }
} }
@ -161,8 +164,39 @@ internal class NerdTree : VimExtension {
} }
} }
class ProjectViewListener(private val project: Project) : ToolWindowManagerListener {
override fun toolWindowShown(toolWindow: ToolWindow) {
if (ToolWindowId.PROJECT_VIEW != toolWindow.id) return
val dispatcher = NerdDispatcher.getInstance(project)
if (dispatcher.speedSearchListenerInstalled) return
// I specify nullability explicitly as we've got a lot of exceptions saying this property is null
val currentProjectViewPane: AbstractProjectViewPane? = ProjectView.getInstance(project).currentProjectViewPane
val tree = currentProjectViewPane?.tree ?: return
val supply = SpeedSearchSupply.getSupply(tree, true) ?: return
// NB: Here might be some issues with concurrency, but it's not really bad, I think
dispatcher.speedSearchListenerInstalled = true
supply.addChangeListener {
dispatcher.waitForSearch = false
}
}
}
// TODO I'm not sure is this activity runs at all? Should we use [RunOnceUtil] instead?
class NerdStartupActivity : ProjectActivity {
override suspend fun execute(project: Project) {
synchronized(Util.monitor) {
if (!Util.commandsRegistered) return
installDispatcher(project)
}
}
}
class NerdDispatcher : DumbAwareAction() { class NerdDispatcher : DumbAwareAction() {
internal var waitForSearch = false internal var waitForSearch = false
internal var speedSearchListenerInstalled = false
override fun actionPerformed(e: AnActionEvent) { override fun actionPerformed(e: AnActionEvent) {
var keyStroke = getKeyStroke(e) ?: return var keyStroke = getKeyStroke(e) ?: return
@ -210,6 +244,10 @@ internal class NerdTree : VimExtension {
} }
companion object { companion object {
fun getInstance(project: Project): NerdDispatcher {
return project.getService(NerdDispatcher::class.java)
}
private const val ESCAPE_KEY_CODE = 27 private const val ESCAPE_KEY_CODE = 27
} }
@ -245,14 +283,19 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"NERDTreeMapActivateNode", "NERDTreeMapActivateNode",
"o", "o",
NerdAction.Code { _, dataContext, e -> NerdAction.Code { project, dataContext, _ ->
val tree = getTree(e) ?: return@Code val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
val row = tree.selectionRows?.getOrNull(0) ?: return@Code val array = CommonDataKeys.NAVIGATABLE_ARRAY.getData(dataContext)?.filter { it.canNavigateToSource() }
if (tree.isExpanded(row)) { if (array.isNullOrEmpty()) {
tree.collapseRow(row) val row = tree.selectionRows?.getOrNull(0) ?: return@Code
if (tree.isExpanded(row)) {
tree.collapseRow(row)
} else {
tree.expandRow(row)
}
} else { } else {
tree.expandRow(row) array.forEach { it.navigate(true) }
} }
}, },
) )
@ -331,8 +374,8 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"NERDTreeMapOpenRecursively", "NERDTreeMapOpenRecursively",
"O", "O",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
TreeExpandCollapse.expandAll(tree) TreeExpandCollapse.expandAll(tree)
tree.selectionPath?.let { tree.selectionPath?.let {
TreeUtil.scrollToVisible(tree, it, false) TreeUtil.scrollToVisible(tree, it, false)
@ -342,8 +385,8 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"NERDTreeMapCloseChildren", "NERDTreeMapCloseChildren",
"X", "X",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
TreeExpandCollapse.collapse(tree) TreeExpandCollapse.collapse(tree)
tree.selectionPath?.let { tree.selectionPath?.let {
TreeUtil.scrollToVisible(tree, it, false) TreeUtil.scrollToVisible(tree, it, false)
@ -353,8 +396,8 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"NERDTreeMapCloseDir", "NERDTreeMapCloseDir",
"x", "x",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
val currentPath = tree.selectionPath ?: return@Code val currentPath = tree.selectionPath ?: return@Code
if (tree.isExpanded(currentPath)) { if (tree.isExpanded(currentPath)) {
tree.collapsePath(currentPath) tree.collapsePath(currentPath)
@ -372,8 +415,8 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"NERDTreeMapJumpParent", "NERDTreeMapJumpParent",
"p", "p",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
val currentPath = tree.selectionPath ?: return@Code val currentPath = tree.selectionPath ?: return@Code
val parentPath = currentPath.parentPath ?: return@Code val parentPath = currentPath.parentPath ?: return@Code
if (parentPath.parentPath != null) { if (parentPath.parentPath != null) {
@ -386,8 +429,8 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"NERDTreeMapJumpFirstChild", "NERDTreeMapJumpFirstChild",
"K", "K",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
val currentPath = tree.selectionPath ?: return@Code val currentPath = tree.selectionPath ?: return@Code
val parent = currentPath.parentPath ?: return@Code val parent = currentPath.parentPath ?: return@Code
val row = tree.getRowForPath(parent) val row = tree.getRowForPath(parent)
@ -399,8 +442,8 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"NERDTreeMapJumpLastChild", "NERDTreeMapJumpLastChild",
"J", "J",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
val currentPath = tree.selectionPath ?: return@Code val currentPath = tree.selectionPath ?: return@Code
val currentPathCount = currentPath.pathCount val currentPathCount = currentPath.pathCount
@ -421,12 +464,12 @@ internal class NerdTree : VimExtension {
) )
registerCommand( registerCommand(
"NERDTreeMapJumpNextSibling", "NERDTreeMapJumpNextSibling",
"<A-J>", "<C-J>",
NerdAction.ToIj("Tree-selectNextSibling"), NerdAction.ToIj("Tree-selectNextSibling"),
) )
registerCommand( registerCommand(
"NERDTreeMapJumpPrevSibling", "NERDTreeMapJumpPrevSibling",
"<A-K>", "<C-K>",
NerdAction.ToIj("Tree-selectPreviousSibling"), NerdAction.ToIj("Tree-selectPreviousSibling"),
) )
registerCommand( registerCommand(
@ -445,26 +488,20 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"/", "/",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code NerdDispatcher.getInstance(project).waitForSearch = true
tree.getUserData(KEY)?.waitForSearch = true
}, },
) )
registerCommand( registerCommand(
"<ESC>", "<ESC>",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code val instance = NerdDispatcher.getInstance(project)
tree.getUserData(KEY)?.waitForSearch = false if (instance.waitForSearch) {
instance.waitForSearch = false
}
}, },
) )
for (c in ('a'..'z') + ('A'..'Z')) {
val ks = KeyStroke.getKeyStroke(c)
if (ks !in actionsRoot) {
registerCommand(c.toString(), NerdAction.Code { _, _, _ -> })
}
}
} }
object Util { object Util {
@ -489,21 +526,6 @@ internal class NerdTree : VimExtension {
companion object { companion object {
const val pluginName = "NERDTree" const val pluginName = "NERDTree"
private val LOG = logger<NerdTree>() private val LOG = logger<NerdTree>()
private val KEY = Key.create<NerdDispatcher>("IdeaVim-NerdTree-Dispatcher")
fun installDispatcher(component: JComponent) {
if (component.getUserData(KEY) != null) return
val dispatcher = NerdDispatcher()
component.putUserData(KEY, dispatcher)
val shortcuts = collectShortcuts(actionsRoot).map { RequiredShortcut(it, MappingOwner.Plugin.get(pluginName)) }
dispatcher.registerCustomShortcutSet(KeyGroup.toShortcutSet(shortcuts), component)
SpeedSearchSupply.getSupply(component, true)?.addChangeListener {
dispatcher.waitForSearch = false
}
}
} }
} }
@ -538,6 +560,12 @@ private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> {
} }
} }
private fun getTree(e: AnActionEvent): JTree? { private fun installDispatcher(project: Project) {
return e.dataContext.getData(PlatformCoreDataKeys.CONTEXT_COMPONENT) as? JTree val dispatcher = NerdTree.NerdDispatcher.getInstance(project)
val shortcuts =
collectShortcuts(actionsRoot).map { RequiredShortcut(it, MappingOwner.Plugin.get(NerdTree.pluginName)) }
dispatcher.registerCustomShortcutSet(
KeyGroup.toShortcutSet(shortcuts),
(ProjectView.getInstance(project) as ProjectViewImpl).component,
)
} }

View File

@ -1,25 +0,0 @@
package com.maddyhome.idea.vim.extension.nerdtree
import com.intellij.ide.ApplicationInitializedListener
import com.intellij.openapi.application.ApplicationManager
import com.intellij.util.ui.StartupUiUtil
import kotlinx.coroutines.CoroutineScope
import java.awt.AWTEvent
import java.awt.event.FocusEvent
import javax.swing.JTree
@Suppress("UnstableApiUsage")
internal class NerdTreeApplicationListener : ApplicationInitializedListener {
override suspend fun execute(asyncScope: CoroutineScope) {
StartupUiUtil.addAwtListener(::handleEvent, AWTEvent.FOCUS_EVENT_MASK, ApplicationManager.getApplication().getService(NerdTreeDisposableService::class.java))
}
private fun handleEvent(event: AWTEvent) {
if (event is FocusEvent && event.id == FocusEvent.FOCUS_GAINED) {
val source = event.source
if (source is JTree) {
NerdTree.installDispatcher(source)
}
}
}
}

View File

@ -1,9 +0,0 @@
package com.maddyhome.idea.vim.extension.nerdtree
import com.intellij.openapi.Disposable
import com.intellij.openapi.components.Service
@Service
internal class NerdTreeDisposableService : Disposable {
override fun dispose() {}
}

View File

@ -61,18 +61,15 @@ internal class UndoRedoHelper : UndoRedoBase() {
undoManager: UndoManager, undoManager: UndoManager,
fileEditor: TextEditor, fileEditor: TextEditor,
editor: VimEditor, editor: VimEditor,
) {if (injector.globalIjOptions().oldundo) { ) {
SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) } if (injector.globalIjOptions().oldundo) {
SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
restoreVisualMode(editor)
} else {
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
editor.runWithChangeTracking {
undoManager.undo(fileEditor)
restoreVisualMode(editor) restoreVisualMode(editor)
} else {
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
editor.runWithChangeTracking {
undoManager.undo(fileEditor)
// We execute undo one more time if the previous one just restored selection
if (!hasChanges && hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
undoManager.undo(fileEditor)
}
} }
CommandProcessor.getInstance().runUndoTransparentAction { CommandProcessor.getInstance().runUndoTransparentAction {
@ -91,15 +88,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
editor.runWithChangeTracking { editor.runWithChangeTracking {
undoManager.undo(fileEditor) undoManager.undo(fileEditor)
restoreVisualMode(editor)
// We execute undo one more time if the previous one just restored selection
if (!hasChanges && hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
undoManager.undo(fileEditor)
}
}
CommandProcessor.getInstance().runUndoTransparentAction {
removeSelections(editor)
} }
} else { } else {
runWithBooleanRegistryOption("ide.undo.transparent.caret.movement", true) { runWithBooleanRegistryOption("ide.undo.transparent.caret.movement", true) {
@ -115,7 +104,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
private fun hasSelection(editor: VimEditor): Boolean { private fun hasSelection(editor: VimEditor): Boolean {
return editor.primaryCaret().ij.hasSelection() return editor.primaryCaret().ij.hasSelection()
} }
override fun redo(editor: VimEditor, context: ExecutionContext): Boolean { override fun redo(editor: VimEditor, context: ExecutionContext): Boolean {
val ijContext = context.context as DataContext val ijContext = context.context as DataContext
val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
@ -138,17 +127,17 @@ internal class UndoRedoHelper : UndoRedoBase() {
undoManager: UndoManager, undoManager: UndoManager,
fileEditor: TextEditor, fileEditor: TextEditor,
editor: VimEditor, editor: VimEditor,
) {if (injector.globalIjOptions().oldundo) { ) {
SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) } if (injector.globalIjOptions().oldundo) {
restoreVisualMode(editor) SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
} else { } else {
undoManager.redo(fileEditor)
CommandProcessor.getInstance().runUndoTransparentAction {
editor.carets().forEach { it.ij.removeSelection() }
}
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
editor.runWithChangeTracking {
undoManager.redo(fileEditor) undoManager.redo(fileEditor)
CommandProcessor.getInstance().runUndoTransparentAction {
editor.carets().forEach { it.ij.removeSelection() }
}
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
editor.runWithChangeTracking {
undoManager.redo(fileEditor)
// We execute undo one more time if the previous one just restored selection // We execute undo one more time if the previous one just restored selection
if (!hasChanges && hasSelection(editor) && undoManager.isRedoAvailable(fileEditor)) { if (!hasChanges && hasSelection(editor) && undoManager.isRedoAvailable(fileEditor)) {
@ -244,7 +233,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
private fun restoreVisualMode(editor: VimEditor) { private fun restoreVisualMode(editor: VimEditor) {
if (!editor.inVisualMode && editor.getSelectionModel().hasSelection()) { if (!editor.inVisualMode && editor.getSelectionModel().hasSelection()) {
val detectedMode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor) val detectedMode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor)
// Visual block selection is restored into multiple carets, so multi-carets that form a block are always // Visual block selection is restored into multiple carets, so multi-carets that form a block are always
// identified as visual block mode, leading to false positives. // identified as visual block mode, leading to false positives.
// Since I use visual block mode much less often than multi-carets, this is a judgment call to never restore // Since I use visual block mode much less often than multi-carets, this is a judgment call to never restore
@ -253,7 +242,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
SelectionType.CHARACTER_WISE SelectionType.CHARACTER_WISE
else else
detectedMode detectedMode
VimPlugin.getVisualMotion().enterVisualMode(editor, wantedMode) VimPlugin.getVisualMotion().enterVisualMode(editor, wantedMode)
} }
} }

View File

@ -136,7 +136,11 @@
<!-- IdeaVim extensions--> <!-- IdeaVim extensions-->
<extensions defaultExtensionNs="com.intellij"> <extensions defaultExtensionNs="com.intellij">
<applicationService serviceImplementation="com.maddyhome.idea.vim.extension.nerdtree.NerdTree$NerdDispatcher"/> <projectService serviceImplementation="com.maddyhome.idea.vim.extension.nerdtree.NerdTree$NerdDispatcher"/>
<applicationInitializedListener implementation="com.maddyhome.idea.vim.extension.nerdtree.NerdTreeApplicationListener"/> <postStartupActivity implementation="com.maddyhome.idea.vim.extension.nerdtree.NerdTree$NerdStartupActivity"/>
</extensions> </extensions>
<projectListeners>
<listener class="com.maddyhome.idea.vim.extension.nerdtree.NerdTree$ProjectViewListener"
topic="com.intellij.openapi.wm.ex.ToolWindowManagerListener"/>
</projectListeners>
</idea-plugin> </idea-plugin>