From 238942c4719ff1699fab8940930ea1c8e9bd8bb0 Mon Sep 17 00:00:00 2001 From: chylex <contact@chylex.com> Date: Tue, 17 Oct 2023 03:34:42 +0200 Subject: [PATCH] Add actions to go to next/previous type in file --- build.gradle.kts | 2 + .../gotoType/AbstractGotoTypeInFileAction.kt | 34 +++++++ .../gotoType/GotoNextTypeInFileAction.kt | 9 ++ .../gotoType/GotoPreviousTypeInFileAction.kt | 9 ++ .../action/gotoType/GotoTypeInFileHandler.kt | 97 +++++++++++++++++++ .../META-INF/KeyboardMaster-Java.xml | 15 +++ src/main/resources/META-INF/plugin.xml | 1 + 7 files changed, 167 insertions(+) create mode 100644 src/main/kotlin/com/chylex/intellij/keyboardmaster/feature/action/gotoType/AbstractGotoTypeInFileAction.kt create mode 100644 src/main/kotlin/com/chylex/intellij/keyboardmaster/feature/action/gotoType/GotoNextTypeInFileAction.kt create mode 100644 src/main/kotlin/com/chylex/intellij/keyboardmaster/feature/action/gotoType/GotoPreviousTypeInFileAction.kt create mode 100644 src/main/kotlin/com/chylex/intellij/keyboardmaster/feature/action/gotoType/GotoTypeInFileHandler.kt create mode 100644 src/main/resources/META-INF/KeyboardMaster-Java.xml diff --git a/build.gradle.kts b/build.gradle.kts index f103584..9cbc4d2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,6 +19,8 @@ intellij { version.set("2023.2") updateSinceUntilBuild.set(false) + plugins.add("com.intellij.java") + if (System.getenv("IDEAVIM") == "1") { plugins.add("IdeaVIM:0.66") } diff --git a/src/main/kotlin/com/chylex/intellij/keyboardmaster/feature/action/gotoType/AbstractGotoTypeInFileAction.kt b/src/main/kotlin/com/chylex/intellij/keyboardmaster/feature/action/gotoType/AbstractGotoTypeInFileAction.kt new file mode 100644 index 0000000..7dc9406 --- /dev/null +++ b/src/main/kotlin/com/chylex/intellij/keyboardmaster/feature/action/gotoType/AbstractGotoTypeInFileAction.kt @@ -0,0 +1,34 @@ +package com.chylex.intellij.keyboardmaster.feature.action.gotoType + +import com.intellij.codeInsight.actions.BaseCodeInsightAction +import com.intellij.ide.structureView.TreeBasedStructureViewBuilder +import com.intellij.lang.LanguageStructureViewBuilder +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.DumbAware +import com.intellij.openapi.project.IndexNotReadyException +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFile + +abstract class AbstractGotoTypeInFileAction : BaseCodeInsightAction(), DumbAware { + init { + isEnabledInModalContext = true + } + + final override fun isValidForLookup(): Boolean { + return true + } + + final override fun isValidForFile(project: Project, editor: Editor, file: PsiFile): Boolean { + return checkValidForFile(file) + } + + private companion object { + fun checkValidForFile(file: PsiFile): Boolean { + return try { + LanguageStructureViewBuilder.INSTANCE.getStructureViewBuilder(file) is TreeBasedStructureViewBuilder + } catch (e: IndexNotReadyException) { + false + } + } + } +} diff --git a/src/main/kotlin/com/chylex/intellij/keyboardmaster/feature/action/gotoType/GotoNextTypeInFileAction.kt b/src/main/kotlin/com/chylex/intellij/keyboardmaster/feature/action/gotoType/GotoNextTypeInFileAction.kt new file mode 100644 index 0000000..78732ef --- /dev/null +++ b/src/main/kotlin/com/chylex/intellij/keyboardmaster/feature/action/gotoType/GotoNextTypeInFileAction.kt @@ -0,0 +1,9 @@ +package com.chylex.intellij.keyboardmaster.feature.action.gotoType + +import com.intellij.codeInsight.CodeInsightActionHandler + +class GotoNextTypeInFileAction : AbstractGotoTypeInFileAction() { + override fun getHandler(): CodeInsightActionHandler { + return GotoTypeInFileHandler(forward = true) + } +} diff --git a/src/main/kotlin/com/chylex/intellij/keyboardmaster/feature/action/gotoType/GotoPreviousTypeInFileAction.kt b/src/main/kotlin/com/chylex/intellij/keyboardmaster/feature/action/gotoType/GotoPreviousTypeInFileAction.kt new file mode 100644 index 0000000..795dd35 --- /dev/null +++ b/src/main/kotlin/com/chylex/intellij/keyboardmaster/feature/action/gotoType/GotoPreviousTypeInFileAction.kt @@ -0,0 +1,9 @@ +package com.chylex.intellij.keyboardmaster.feature.action.gotoType + +import com.intellij.codeInsight.CodeInsightActionHandler + +class GotoPreviousTypeInFileAction : AbstractGotoTypeInFileAction() { + override fun getHandler(): CodeInsightActionHandler { + return GotoTypeInFileHandler(forward = false) + } +} diff --git a/src/main/kotlin/com/chylex/intellij/keyboardmaster/feature/action/gotoType/GotoTypeInFileHandler.kt b/src/main/kotlin/com/chylex/intellij/keyboardmaster/feature/action/gotoType/GotoTypeInFileHandler.kt new file mode 100644 index 0000000..fb187dc --- /dev/null +++ b/src/main/kotlin/com/chylex/intellij/keyboardmaster/feature/action/gotoType/GotoTypeInFileHandler.kt @@ -0,0 +1,97 @@ +package com.chylex.intellij.keyboardmaster.feature.action.gotoType + +import com.intellij.codeInsight.CodeInsightActionHandler +import com.intellij.codeInsight.lookup.LookupManager +import com.intellij.ide.structureView.StructureViewTreeElement +import com.intellij.ide.structureView.TreeBasedStructureViewBuilder +import com.intellij.lang.LanguageStructureViewBuilder +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.ScrollType +import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Disposer +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import it.unimi.dsi.fastutil.ints.IntArrayList + +class GotoTypeInFileHandler(private val forward: Boolean) : CodeInsightActionHandler { + override fun invoke(project: Project, editor: Editor, file: PsiFile) { + LookupManager.getInstance(project).hideActiveLookup() + + val caretOffset = editor.caretModel.offset + val caretLine = editor.caretModel.logicalPosition.line + + val searchedOffsetRange = if (forward) + caretOffset + 1..file.textLength + else + 0 until caretOffset + + val navigationOffsets = getNavigationOffsets(file, searchedOffsetRange) + if (!forward) { + navigationOffsets.reverse() + } + + val direction = if (forward) 1 else -1 + for (offset in navigationOffsets) { + val line = editor.offsetToLogicalPosition(offset).line + if (line.compareTo(caretLine) * direction > 0) { + editor.caretModel.removeSecondaryCarets() + editor.caretModel.moveToOffset(offset) + editor.selectionModel.removeSelection() + editor.scrollingModel.scrollToCaret(if (forward) ScrollType.CENTER_DOWN else ScrollType.CENTER_UP) + IdeDocumentHistory.getInstance(project).includeCurrentCommandAsNavigation() + break + } + } + } + + override fun getElementToMakeWritable(currentFile: PsiFile): PsiElement? { + return null + } + + private companion object { + fun getNavigationOffsets(file: PsiFile, searchedOffsetRange: IntRange): IntArray { + val structureViewBuilder = LanguageStructureViewBuilder.INSTANCE.getStructureViewBuilder(file) + if (structureViewBuilder !is TreeBasedStructureViewBuilder) { + return intArrayOf() + } + + val elements = mutableSetOf<PsiElement>() + val model = structureViewBuilder.createStructureViewModel(null) + + try { + addStructureViewElements(elements, model.root, file) + } finally { + Disposer.dispose(model) + } + + return offsetsFromElements(elements, searchedOffsetRange) + } + + private fun addStructureViewElements(result: MutableSet<PsiElement>, parent: StructureViewTreeElement, file: PsiFile) { + for (child in parent.children) { + val value = (child as StructureViewTreeElement).value + if (value is PsiClass && file == value.containingFile) { + result.add(value) + } + + addStructureViewElements(result, child, file) + } + } + + private fun offsetsFromElements(elements: Collection<PsiElement>, searchedOffsetRange: IntRange): IntArray { + val offsets = IntArrayList(elements.size) + + for (element in elements) { + val offset = element.textOffset + if (offset in searchedOffsetRange) { + offsets.add(offset) + } + } + + offsets.sort(null) + return offsets.toIntArray() + } + } +} diff --git a/src/main/resources/META-INF/KeyboardMaster-Java.xml b/src/main/resources/META-INF/KeyboardMaster-Java.xml new file mode 100644 index 0000000..16d55da --- /dev/null +++ b/src/main/resources/META-INF/KeyboardMaster-Java.xml @@ -0,0 +1,15 @@ +<idea-plugin> + <actions> + <!-- Go to Type in File --> + <action id="KM.GotoNextTypeInFile" + text="Next Type" + class="com.chylex.intellij.keyboardmaster.feature.action.gotoType.GotoNextTypeInFileAction"> + <add-to-group group-id="NavigateInFileGroup" anchor="after" relative-to-action="MethodUp" /> + </action> + <action id="KM.GotoPreviousTypeInFile" + text="Previous Type" + class="com.chylex.intellij.keyboardmaster.feature.action.gotoType.GotoPreviousTypeInFileAction"> + <add-to-group group-id="NavigateInFileGroup" anchor="after" relative-to-action="KM.GotoNextTypeInFile" /> + </action> + </actions> +</idea-plugin> diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 8f4af26..0878298 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -12,6 +12,7 @@ ]]></description> <depends>com.intellij.modules.platform</depends> + <depends optional="true" config-file="KeyboardMaster-Java.xml">com.intellij.java</depends> <projectListeners> <listener class="com.chylex.intellij.keyboardmaster.feature.codeCompletion.CodeCompletionPopupListener" topic="com.intellij.codeInsight.lookup.LookupManagerListener" />