1
0
mirror of https://github.com/chylex/IntelliJ-AceJump.git synced 2025-09-15 22:32:11 +02:00

14 Commits

32 changed files with 346 additions and 1167 deletions

24
.gitignore vendored
View File

@@ -1,21 +1,5 @@
### JetBrains template /.idea/*
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio !/.idea/runConfigurations
## Directory-based project format: /.gradle/
.idea /build/
*.iml
## Plugin-specific files:
# IntelliJ
/out/
### Gradle template
.gradle
build/
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
gradle.properties
# Mac OS
.DS_Store

View File

@@ -1,32 +1,43 @@
@file:Suppress("ConvertLambdaToReference")
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
idea apply true kotlin("jvm") version "1.8.0"
kotlin("jvm") version "1.5.0" id("org.jetbrains.intellij") version "1.15.0"
id("org.jetbrains.intellij") version "0.7.2"
}
tasks {
withType<KotlinCompile> {
kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
dependencies {
compileOnly(kotlin("stdlib-jdk8"))
}
repositories {
mavenCentral()
jcenter()
}
intellij {
version = "2021.1"
pluginName = "AceJump"
updateSinceUntilBuild = false
setPlugins("java", "IdeaVIM:0.66")
} }
group = "org.acejump" group = "org.acejump"
version = "chylex-8" version = "chylex-13"
repositories {
mavenCentral()
}
kotlin {
jvmToolchain(17)
}
intellij {
version.set("2023.2")
updateSinceUntilBuild.set(false)
plugins.add("IdeaVIM:chylex-16")
pluginsRepositories {
custom("https://intellij.chylex.com")
}
}
tasks.patchPluginXml {
sinceBuild.set("231")
}
tasks.buildSearchableOptions {
enabled = false
}
tasks.withType<KotlinCompile> {
kotlinOptions.freeCompilerArgs = listOf(
"-Xjvm-default=all"
)
}

1
gradle.properties Normal file
View File

@@ -0,0 +1 @@
kotlin.stdlib.default.dependency=false

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -1,7 +1,10 @@
package org.acejump package org.acejump
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.actions.EditorActionUtil import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.project.Project
import com.intellij.util.IncorrectOperationException
import it.unimi.dsi.fastutil.ints.IntArrayList import it.unimi.dsi.fastutil.ints.IntArrayList
annotation class ExternalUsage annotation class ExternalUsage
@@ -12,6 +15,21 @@ annotation class ExternalUsage
val Editor.immutableText val Editor.immutableText
get() = this.document.immutableCharSequence get() = this.document.immutableCharSequence
/**
* Returns all open editors in the project.
*/
val Project.openEditors: List<Editor>
get() {
return try {
FileEditorManagerEx.getInstanceEx(this)
.splitters
.getSelectedEditors()
.mapNotNull { (it as? TextEditor)?.editor }
} catch (e: IncorrectOperationException) {
emptyList()
}
}
/** /**
* Returns true if [this] contains [otherText] at the specified offset. * Returns true if [this] contains [otherText] at the specified offset.
*/ */
@@ -67,32 +85,6 @@ inline fun CharSequence.wordEnd(pos: Int, isPartOfWord: (Char) -> Boolean = Char
return end return end
} }
/**
* Finds index of the previous "camelHumps" hump in a word.
*/
inline fun CharSequence.humpStart(pos: Int, isPartOfWord: (Char) -> Boolean = Char::isWordPart): Int {
var start = pos
while (start > 0 && isPartOfWord(this[start - 1]) && !EditorActionUtil.isHumpBound(this, start, true)) {
--start
}
return start
}
/**
* Finds index of the next "camelHumps" hump in a word.
*/
inline fun CharSequence.humpEnd(pos: Int, isPartOfWord: (Char) -> Boolean = Char::isWordPart): Int {
var end = pos
while (end < length - 1 && isPartOfWord(this[end + 1]) && !EditorActionUtil.isHumpBound(this, end + 1, false)) {
++end
}
return end
}
/** /**
* Finds index of the first word character following a sequence of non-word characters following the end of a word. * Finds index of the first word character following a sequence of non-word characters following the end of a word.
*/ */

View File

@@ -4,7 +4,6 @@ import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.actionSystem.EditorActionHandler import com.intellij.openapi.editor.actionSystem.EditorActionHandler
import org.acejump.search.Pattern
import org.acejump.session.Session import org.acejump.session.Session
import org.acejump.session.SessionManager import org.acejump.session.SessionManager
@@ -42,16 +41,4 @@ abstract class AceEditorAction(private val originalHandler: EditorActionHandler)
class TagImmediately(originalHandler: EditorActionHandler) : AceEditorAction(originalHandler) { class TagImmediately(originalHandler: EditorActionHandler) : AceEditorAction(originalHandler) {
override fun run(session: Session) = session.tagImmediately() override fun run(session: Session) = session.tagImmediately()
} }
class SearchLineStarts(originalHandler: EditorActionHandler) : AceEditorAction(originalHandler) {
override fun run(session: Session) = session.startRegexSearch(Pattern.LINE_STARTS)
}
class SearchLineEnds(originalHandler: EditorActionHandler) : AceEditorAction(originalHandler) {
override fun run(session: Session) = session.startRegexSearch(Pattern.LINE_ENDS)
}
class SearchLineIndents(originalHandler: EditorActionHandler) : AceEditorAction(originalHandler) {
override fun run(session: Session) = session.startRegexSearch(Pattern.LINE_INDENTS)
}
} }

View File

@@ -1,81 +0,0 @@
package org.acejump.action
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys.EDITOR
import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.util.IncorrectOperationException
import org.acejump.boundaries.Boundaries
import org.acejump.boundaries.StandardBoundaries.*
import org.acejump.search.Pattern
import org.acejump.session.Session
import org.acejump.session.SessionManager
/**
* Base class for keyboard-activated actions that create or update an AceJump [Session].
*/
abstract class AceKeyboardAction : DumbAwareAction() {
final override fun update(action: AnActionEvent) {
action.presentation.isEnabled = action.getData(EDITOR) != null
}
final override fun actionPerformed(e: AnActionEvent) {
val editor = e.getData(EDITOR) ?: return
val project = e.project
if (project != null) {
try {
val openEditors = FileEditorManagerEx.getInstanceEx(project)
.splitters
.selectedEditors
.mapNotNull { (it as? TextEditor)?.editor }
.sortedBy { if (it === editor) 0 else 1 }
invoke(SessionManager.start(editor, openEditors))
} catch (e: IncorrectOperationException) {
invoke(SessionManager.start(editor))
}
}
else {
invoke(SessionManager.start(editor))
}
}
abstract operator fun invoke(session: Session)
/**
* Generic action type that starts a regex search.
*/
abstract class BaseRegexSearchAction(private val pattern: Pattern, private val boundaries: Boundaries) : AceKeyboardAction() {
override fun invoke(session: Session) {
session.defaultBoundary = boundaries
session.startRegexSearch(pattern)
}
}
/**
* Starts or ends an AceJump session in quick jump mode.
*/
object ActivateAceJump : AceKeyboardAction() {
override fun invoke(session: Session) = session.startJumpMode()
}
/**
* Starts or cycles main AceJump modes.
*/
object ActivateAceJumpSpecial : AceKeyboardAction() {
override fun invoke(session: Session) = session.startOrCycleSpecialModes()
}
// @formatter:off
object StartAllWordsMode : BaseRegexSearchAction(Pattern.ALL_WORDS, VISIBLE_ON_SCREEN)
object StartAllWordsBackwardsMode : BaseRegexSearchAction(Pattern.ALL_WORDS, BEFORE_CARET.intersection(VISIBLE_ON_SCREEN))
object StartAllWordsForwardMode : BaseRegexSearchAction(Pattern.ALL_WORDS, AFTER_CARET.intersection(VISIBLE_ON_SCREEN))
object StartAllLineStartsMode : BaseRegexSearchAction(Pattern.LINE_STARTS, VISIBLE_ON_SCREEN)
object StartAllLineEndsMode : BaseRegexSearchAction(Pattern.LINE_ENDS, VISIBLE_ON_SCREEN)
object StartAllLineIndentsMode : BaseRegexSearchAction(Pattern.LINE_INDENTS, VISIBLE_ON_SCREEN)
object StartAllLineMarksMode : BaseRegexSearchAction(Pattern.LINE_ALL_MARKS, VISIBLE_ON_SCREEN)
// @formatter:on
}

View File

@@ -1,29 +1,15 @@
package org.acejump.action package org.acejump.action
import com.intellij.find.actions.ShowUsagesAction
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.CommandProcessor import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.UndoConfirmationPolicy import com.intellij.openapi.command.UndoConfirmationPolicy
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.editor.CaretState
import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.actionSystem.DocCommandGroupId import com.intellij.openapi.editor.actionSystem.DocCommandGroupId
import com.intellij.openapi.editor.actions.EditorActionUtil
import com.intellij.openapi.fileEditor.TextEditor import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.playback.commands.ActionCommand
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.codeStyle.CodeStyleManager
import org.acejump.*
import org.acejump.search.SearchProcessor import org.acejump.search.SearchProcessor
import kotlin.math.max
/** /**
* Base class for actions available after typing a tag. * Base class for actions available after typing a tag.
@@ -52,96 +38,7 @@ sealed class AceTagAction {
abstract fun getCaretOffset(editor: Editor, searchProcessor: SearchProcessor, offset: Int): Int abstract fun getCaretOffset(editor: Editor, searchProcessor: SearchProcessor, offset: Int): Int
} }
abstract class BaseWordAction : BaseJumpAction() {
final override fun getCaretOffset(editor: Editor, searchProcessor: SearchProcessor, offset: Int): Int {
val matchingChars = countMatchingCharacters(editor, searchProcessor, offset)
val targetOffset = offset + matchingChars
val isInsideWord = matchingChars > 0 && editor.immutableText.let { it[targetOffset - 1].isWordPart && it[targetOffset].isWordPart }
return getCaretOffset(editor, offset, targetOffset, isInsideWord)
}
abstract fun getCaretOffset(editor: Editor, queryStartOffset: Int, queryEndOffset: Int, isInsideWord: Boolean): Int
}
abstract class BaseSelectAction : AceTagAction() {
final override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean, isFinal: Boolean) {
if (shiftMode) {
val caretModel = editor.caretModel
val oldCarets = caretModel.caretsAndSelections
val oldOffsetPosition = caretModel.logicalPosition
invoke(editor, searchProcessor, offset)
if (caretModel.caretsAndSelections.any { isSelectionOverlapping(oldOffsetPosition, it) }) {
oldCarets.removeAll { isSelectionOverlapping(oldOffsetPosition, it) }
}
caretModel.caretsAndSelections = oldCarets + caretModel.caretsAndSelections
}
else {
invoke(editor, searchProcessor, offset)
}
}
private fun isSelectionOverlapping(offset: LogicalPosition, oldCaret: CaretState): Boolean {
return oldCaret.caretPosition == offset || oldCaret.selectionStart == offset || oldCaret.selectionEnd == offset
}
protected abstract operator fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int)
}
abstract class BasePerCaretWriteAction(private val selector: AceTagAction) : AceTagAction() {
final override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean, isFinal: Boolean) {
val oldCarets = editor.caretModel.caretsAndSelections
selector(editor, searchProcessor, offset, shiftMode = false, isFinal = isFinal)
val range = editor.selectionModel.let { TextRange(it.selectionStart, it.selectionEnd) }
editor.caretModel.caretsAndSelections = oldCarets
invoke(editor, range, shiftMode)
}
protected abstract operator fun invoke(editor: Editor, range: TextRange, shiftMode: Boolean)
protected fun insertAtCarets(editor: Editor, text: String) {
val document = editor.document
editor.caretModel.runForEachCaret {
if (it.hasSelection()) {
document.replaceString(it.selectionStart, it.selectionEnd, text)
fixIndents(editor, it.selectionStart, it.selectionEnd)
}
else {
document.insertString(it.offset, text)
fixIndents(editor, it.offset, it.offset + text.length)
}
}
}
private fun fixIndents(editor: Editor, startOffset: Int, endOffset: Int) {
val project = editor.project ?: return
val document = editor.document
val documentManager = PsiDocumentManager.getInstance(project)
documentManager.commitAllDocuments()
val file = documentManager.getPsiFile(document) ?: return
val text = document.charsSequence
if (startOffset > 0 && endOffset > startOffset + 1 && text[endOffset - 1] == '\n' && text[startOffset - 1] == '\n') {
CodeStyleManager.getInstance(project).adjustLineIndent(file, TextRange(startOffset, endOffset - 1))
}
else {
CodeStyleManager.getInstance(project).adjustLineIndent(file, TextRange(startOffset, endOffset))
}
}
}
private companion object { private companion object {
fun countMatchingCharacters(editor: Editor, searchProcessor: SearchProcessor, offset: Int): Int {
return editor.immutableText.countMatchingCharacters(offset, searchProcessor.query.rawText)
}
fun recordCaretPosition(editor: Editor) = with(editor) { fun recordCaretPosition(editor: Editor) = with(editor) {
project?.let { addCurrentPositionToHistory(it, document) } project?.let { addCurrentPositionToHistory(it, document) }
} }
@@ -152,25 +49,11 @@ sealed class AceTagAction {
caretModel.moveToOffset(offset) caretModel.moveToOffset(offset)
} }
fun selectRange(editor: Editor, fromOffset: Int, toOffset: Int, cursorOffset: Int = toOffset) = with(editor) {
selectionModel.removeSelection(true)
selectionModel.setSelection(fromOffset, toOffset)
caretModel.moveToOffset(cursorOffset)
}
fun performAction(actionName: String) {
val actionManager = ActionManager.getInstance()
val action = actionManager.getAction(actionName)
if (action != null) {
actionManager.tryToExecute(action, ActionCommand.getInputEvent(null), null, null, true)
}
}
fun ensureEditorFocused(editor: Editor) { fun ensureEditorFocused(editor: Editor) {
val project = editor.project ?: return val project = editor.project ?: return
val fem = FileEditorManagerEx.getInstanceEx(project) val fem = FileEditorManagerEx.getInstanceEx(project)
val window = fem.windows.firstOrNull { (it.selectedEditor?.selectedWithProvider?.fileEditor as? TextEditor)?.editor === editor } val window = fem.windows.firstOrNull { (it.selectedComposite?.selectedWithProvider?.fileEditor as? TextEditor)?.editor === editor }
if (window != null && window !== fem.currentWindow) { if (window != null && window !== fem.currentWindow) {
fem.currentWindow = window fem.currentWindow = window
} }
@@ -198,287 +81,4 @@ sealed class AceTagAction {
return offset return offset
} }
} }
/**
* On default action, places the caret at the last character of the search query.
* On shift action, adds the new caret to existing carets.
*/
object JumpToSearchEnd : BaseJumpAction() {
override fun getCaretOffset(editor: Editor, searchProcessor: SearchProcessor, offset: Int): Int {
return offset + max(0, countMatchingCharacters(editor, searchProcessor, offset) - 1)
}
}
/**
* On default action, places the caret just past the last character of the search query.
* On shift action, adds the new caret to existing carets.
*/
object JumpPastSearchEnd : BaseJumpAction() {
override fun getCaretOffset(editor: Editor, searchProcessor: SearchProcessor, offset: Int): Int {
return offset + countMatchingCharacters(editor, searchProcessor, offset)
}
}
/**
* On default action, places the caret at the start of a word. Word detection uses [Character.isJavaIdentifierPart] to count some special
* characters, such as underscores, as part of a word. If there is no word at the last character of the search query, then the caret is
* placed at the first character of the search query.
*
* On shift action, adds the new caret to existing carets.
*/
object JumpToWordStart : BaseWordAction() {
override fun getCaretOffset(editor: Editor, queryStartOffset: Int, queryEndOffset: Int, isInsideWord: Boolean): Int {
return if (isInsideWord)
editor.immutableText.wordStart(queryEndOffset)
else
queryStartOffset
}
}
/**
* On default action, places the caret at the end of a word. Word detection uses [Character.isJavaIdentifierPart] to count some special
* characters, such as underscores, as part of a word. If there is no word at the last character of the search query, then the caret is
* placed after the last character of the search query.
*
* On shift action, adds the new caret to existing carets.
*/
object JumpToWordEnd : BaseWordAction() {
override fun getCaretOffset(editor: Editor, queryStartOffset: Int, queryEndOffset: Int, isInsideWord: Boolean): Int {
return if (isInsideWord)
editor.immutableText.wordEnd(queryEndOffset) + 1
else
queryEndOffset
}
}
/**
* On default action, places the caret at the end of the line.
* On shift action, adds the new caret to existing carets.
*/
object JumpToLineEnd : BaseWordAction() {
override fun getCaretOffset(editor: Editor, queryStartOffset: Int, queryEndOffset: Int, isInsideWord: Boolean): Int {
val document = editor.document
val line = document.getLineNumber(queryEndOffset)
return document.getLineEndOffset(line)
}
}
/**
* On default action, selects all characters covered by the search query.
* On shift action, adds the new selection to existing selections.
*/
object SelectQuery : BaseSelectAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) {
recordCaretPosition(editor)
val startOffset = JumpToSearchStart.getCaretOffset(editor, searchProcessor, offset)
val endOffset = JumpPastSearchEnd.getCaretOffset(editor, searchProcessor, offset)
selectRange(editor, startOffset, endOffset)
}
}
/**
* On default action, places the caret at the end of a word, and also selects the entire word. Word detection uses
* [Character.isJavaIdentifierPart] to count some special characters, such as underscores, as part of a word. If there is no word at the
* last character of the search query, then the caret is placed after the last character of the search query, and all text between the
* start and end of the search query is selected.
*
* On shift action, adds the new selection to existing selections.
*/
object SelectWord : BaseSelectAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) {
val chars = editor.immutableText
val queryEndOffset = JumpToSearchEnd.getCaretOffset(editor, searchProcessor, offset)
if (chars[queryEndOffset].isWordPart) {
recordCaretPosition(editor)
val startOffset = JumpToWordStart.getCaretOffset(editor, offset, queryEndOffset, isInsideWord = true)
val endOffset = JumpToWordEnd.getCaretOffset(editor, offset, queryEndOffset, isInsideWord = true)
selectRange(editor, startOffset, endOffset)
}
else {
SelectQuery(editor, searchProcessor, offset, shiftMode = false, isFinal = true)
}
}
}
/**
* On default action, places the caret at the end of a camel hump inside a word, and also selects the hump. If there is no word at the
* last character of the search query, then the search query is selected. See [SelectWord] and [SelectQuery] for details.
*
* On shift action, adds the new selection to existing selections.
*/
object SelectHump : BaseSelectAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) {
val chars = editor.immutableText
val queryEndOffset = JumpToSearchEnd.getCaretOffset(editor, searchProcessor, offset)
if (chars[queryEndOffset].isWordPart) {
recordCaretPosition(editor)
val startOffset = chars.humpStart(queryEndOffset)
val endOffset = chars.humpEnd(queryEndOffset) + 1
selectRange(editor, startOffset, endOffset)
}
else {
SelectQuery(editor, searchProcessor, offset, shiftMode = false, isFinal = true)
}
}
}
/**
* On default action, selects the line at the tag, excluding the indent.
* On shift action, adds the new selection to existing selections.
*/
object SelectLine : BaseSelectAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) {
JumpToSearchEnd(editor, searchProcessor, offset, shiftMode = false, isFinal = true)
val document = editor.document
val line = editor.caretModel.logicalPosition.line
val lineStart = EditorActionUtil.findFirstNonSpaceOffsetOnTheLine(document, line)
val lineEnd = document.getLineEndOffset(line)
selectRange(editor, lineStart, lineEnd)
}
}
/**
* On default action, places the caret at the last character of the search query, and then performs Extend Selection a set amount of
* times.
*
* On shift action, adds the new selection to existing selections.
*/
class SelectExtended(private val extendCount: Int) : BaseSelectAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) {
JumpToSearchEnd(editor, searchProcessor, offset, shiftMode = false, isFinal = true)
repeat(extendCount) {
performAction(IdeActions.ACTION_EDITOR_SELECT_WORD_AT_CARET)
}
}
}
/**
* On default action, selects the range between the caret and a position decided by the provided [BaseJumpAction].
* On shift action, adds the new selection to existing selections.
*/
class SelectToCaret(private val jumper: BaseJumpAction) : BaseSelectAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) {
val caretModel = editor.caretModel
val oldOffset = caretModel.offset
val oldSelection = editor.selectionModel.takeIf { it.hasSelection(false) }?.let { it.selectionStart..it.selectionEnd }
jumper(editor, searchProcessor, offset, shiftMode = false, isFinal = true)
val newOffset = caretModel.offset
if (oldSelection == null) {
selectRange(editor, oldOffset, newOffset)
}
else {
selectRange(editor, minOf(oldOffset, newOffset, oldSelection.first), maxOf(oldOffset, newOffset, oldSelection.last), newOffset)
}
}
}
/**
* On default action, selects the range between [firstOffset] and a position decided by the provided [BaseJumpAction].
* On shift action, adds the new selection to existing selections.
*/
class SelectBetweenPoints(private val firstOffset: Int, private val secondOffsetJumper: BaseJumpAction) : BaseSelectAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) {
secondOffsetJumper(editor, searchProcessor, offset, shiftMode = false, isFinal = true)
selectRange(editor, firstOffset, editor.caretModel.offset)
}
}
/**
* Selects text based on the provided [selector] action and clones it at every existing caret, selecting the cloned text. If a caret
* has a selection, the selected text will be replaced.
*/
class CloneToCaret(selector: AceTagAction) : BasePerCaretWriteAction(selector) {
override fun invoke(editor: Editor, range: TextRange, shiftMode: Boolean) {
WriteCommandAction.writeCommandAction(editor.project).withName("AceJump Clone").run<Throwable> {
insertAtCarets(editor, editor.document.getText(range))
}
}
}
/**
* Selects text based on the provided [selector] action and clones it to every existing caret, selecting the cloned text and deleting
* the original. If a caret has a selection, the selected text will be replaced.
*/
open class MoveToCaret(selector: AceTagAction) : BasePerCaretWriteAction(selector) {
override fun invoke(editor: Editor, range: TextRange, shiftMode: Boolean) {
val difference = if (shiftMode) editor.caretModel.caretsAndSelections.sumBy {
val start = it.selectionStart?.let(editor::logicalPositionToOffset)
val end = it.selectionEnd?.let(editor::logicalPositionToOffset)
if (start == null || end == null || end > range.endOffset) 0 else range.length - (end - start)
} else 0
WriteCommandAction.writeCommandAction(editor.project).withName("AceJump Move").run<Throwable> {
val document = editor.document
val text = document.getText(range)
document.deleteString(range.startOffset, range.endOffset)
insertAtCarets(editor, text)
}
if (shiftMode) {
editor.selectionModel.removeSelection(true)
editor.caretModel.moveToOffset(range.startOffset + difference)
}
}
}
/**
* On default action, performs the Go To Declaration action, available via `Navigate | Declaration or Usages`.
* On shift action, performs the Go To Type Declaration action, available via `Navigate | Type Declaration`.
* Always places the caret at the start of the word.
*/
object GoToDeclaration : AceTagAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean, isFinal: Boolean) {
JumpToWordStart(editor, searchProcessor, offset, shiftMode = false, isFinal = isFinal)
ApplicationManager.getApplication().invokeLater { performAction(if (shiftMode) IdeActions.ACTION_GOTO_TYPE_DECLARATION else IdeActions.ACTION_GOTO_DECLARATION) }
}
}
/**
* On default action, performs the Show Usages action, available via the context menu.
* On shift action, performs the Find Usages action, available via the context menu.
* Always places the caret at the start of the word.
*/
object ShowUsages : AceTagAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean, isFinal: Boolean) {
JumpToWordStart(editor, searchProcessor, offset, shiftMode = false, isFinal = isFinal)
ApplicationManager.getApplication().invokeLater { performAction(if (shiftMode) IdeActions.ACTION_FIND_USAGES else ShowUsagesAction.ID) }
}
}
/**
* Performs the Show Context Actions action, available via the context menu or Alt+Enter.
* Always places the caret at the start of the word.
*/
object ShowIntentions : AceTagAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean, isFinal: Boolean) {
JumpToWordStart(editor, searchProcessor, offset, shiftMode = false, isFinal = isFinal)
ApplicationManager.getApplication().invokeLater { performAction(IdeActions.ACTION_SHOW_INTENTION_ACTIONS) }
}
}
/**
* On default action, performs the Refactor This action, available via the main menu.
* On shift action, performs the Rename... refactoring, available via the main menu.
* Always places the caret at the start of the word.
*/
object Refactor : AceTagAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean, isFinal: Boolean) {
JumpToWordStart(editor, searchProcessor, offset, shiftMode = false, isFinal = isFinal)
ApplicationManager.getApplication().invokeLater { performAction(if (shiftMode) IdeActions.ACTION_RENAME else "Refactorings.QuickListPopupAction") }
}
}
} }

View File

@@ -1,148 +1,162 @@
package org.acejump.action package org.acejump.action
import org.acejump.boundaries.Boundaries import com.intellij.openapi.actionSystem.AnActionEvent
import org.acejump.boundaries.StandardBoundaries.AFTER_CARET import com.intellij.openapi.actionSystem.CommonDataKeys
import org.acejump.boundaries.StandardBoundaries.BEFORE_CARET import com.intellij.openapi.application.ApplicationManager
import org.acejump.boundaries.StandardBoundaries.VISIBLE_ON_SCREEN import com.intellij.openapi.application.WriteAction
import org.acejump.modes.ActionMode import com.intellij.openapi.project.DumbAwareAction
import org.acejump.modes.VimJumpMode import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.action.change.change.ChangeVisualAction
import com.maddyhome.idea.vim.action.change.delete.DeleteVisualAction
import com.maddyhome.idea.vim.action.copy.YankVisualAction
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.MappingMode.OP_PENDING
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.group.visual.vimSetSelection
import com.maddyhome.idea.vim.helper.inVisualMode
import com.maddyhome.idea.vim.helper.vimSelectionStart
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.SelectionType
import org.acejump.boundaries.StandardBoundaries.*
import org.acejump.modes.JumpMode
import org.acejump.search.Pattern import org.acejump.search.Pattern
import org.acejump.session.Session import org.acejump.search.Tag
import org.acejump.session.SessionManager
import org.acejump.session.SessionState
sealed class AceVimAction : AceKeyboardAction() { sealed class AceVimAction : DumbAwareAction() {
protected abstract val boundary: Boundaries protected abstract val mode: AceVimMode
final override fun invoke(session: Session) { final override fun actionPerformed(e: AnActionEvent) {
session.defaultBoundary = boundary val editor = e.getData(CommonDataKeys.EDITOR) ?: return
start(session) val context = e.dataContext.vim
val caret = editor.caretModel.currentCaret
val initialOffset = caret.offset
val selectionStart = if (editor.inVisualMode) caret.vimSelectionStart else null
val session = SessionManager.start(editor, mode.getJumpEditors(editor))
session.defaultBoundary = mode.boundaries
session.startJumpMode {
object : JumpMode() {
override fun accept(state: SessionState, acceptedTag: Tag): Boolean {
state.act(AceTagAction.JumpToSearchStart, acceptedTag, wasUpperCase, isFinal = true)
if (selectionStart != null) {
caret.vim.vimSetSelection(selectionStart, caret.offset, moveCaretToSelectionEnd = true)
}
else {
val vim = editor.vim
val commandState = vim.vimStateMachine
if (commandState.isOperatorPending) {
val key = commandState.commandBuilder.keys.singleOrNull()?.keyChar
commandState.reset()
KeyHandler.getInstance().fullReset(vim)
AceVimUtil.enterVisualMode(vim, SelectionType.CHARACTER_WISE)
caret.vim.vimSetSelection(caret.offset, initialOffset, moveCaretToSelectionEnd = true)
val action = when (key) {
'd' -> DeleteVisualAction()
'c' -> ChangeVisualAction()
'y' -> YankVisualAction()
else -> null
} }
protected open fun start(session: Session) { if (action != null) {
session.startJumpMode(::VimJumpMode) ApplicationManager.getApplication().invokeLater {
} WriteAction.run<Nothing> {
commandState.commandBuilder.pushCommandPart(action)
class JumpToChar : AceVimAction() { val cmd = commandState.commandBuilder.buildCommand()
override val boundary = VISIBLE_ON_SCREEN val operatorArguments = OperatorArguments(commandState.mappingState.mappingMode == OP_PENDING, cmd.rawCount, commandState.mode)
}
class JumpToCharAfterCaret : AceVimAction() { commandState.executingCommand = cmd
override val boundary = VISIBLE_ON_SCREEN.intersection(AFTER_CARET) injector.actionExecutor.executeVimAction(vim, action, context, operatorArguments)
// TODO does not update status
}
} }
class JumpToCharBeforeCaret : AceVimAction() {
override val boundary = VISIBLE_ON_SCREEN.intersection(BEFORE_CARET)
} }
class LWordsAfterCaret : AceVimAction() {
override val boundary = VISIBLE_ON_SCREEN.intersection(AFTER_CARET)
override fun start(session: Session) {
super.start(session)
session.startRegexSearch(Pattern.VIM_LWORD)
} }
} }
class UWordsAfterCaret : AceVimAction() { mode.finishSession(editor, session)
override val boundary = VISIBLE_ON_SCREEN.intersection(AFTER_CARET) return true
override fun start(session: Session) { }
super.start(session)
session.startRegexSearch(Pattern.VIM_UWORD)
} }
} }
class LWordsBeforeCaret : AceVimAction() { mode.setupSession(editor, session)
override val boundary = VISIBLE_ON_SCREEN.intersection(BEFORE_CARET)
override fun start(session: Session) {
super.start(session)
session.startRegexSearch(Pattern.VIM_LWORD)
}
} }
class UWordsBeforeCaret : AceVimAction() { class JumpAllEditors : AceVimAction() {
override val boundary = VISIBLE_ON_SCREEN.intersection(BEFORE_CARET) override val mode = AceVimMode.JumpAllEditors
override fun start(session: Session) {
super.start(session)
session.startRegexSearch(Pattern.VIM_UWORD)
}
} }
class LWordEndsAfterCaret : AceVimAction() { class JumpForward : AceVimAction() {
override val boundary = VISIBLE_ON_SCREEN.intersection(AFTER_CARET) override val mode = AceVimMode.Jump(AFTER_CARET.intersection(VISIBLE_ON_SCREEN))
override fun start(session: Session) {
super.start(session)
session.startRegexSearch(Pattern.VIM_LWORD_END)
}
} }
class UWordEndsAfterCaret : AceVimAction() { class JumpBackward : AceVimAction() {
override val boundary = VISIBLE_ON_SCREEN.intersection(AFTER_CARET) override val mode = AceVimMode.Jump(BEFORE_CARET.intersection(VISIBLE_ON_SCREEN))
override fun start(session: Session) {
super.start(session)
session.startRegexSearch(Pattern.VIM_UWORD_END)
}
} }
class LWordEndsBeforeCaret : AceVimAction() { class JumpTillForward : AceVimAction() {
override val boundary = VISIBLE_ON_SCREEN.intersection(BEFORE_CARET) override val mode = AceVimMode.JumpTillForward(AFTER_CARET.intersection(VISIBLE_ON_SCREEN))
override fun start(session: Session) {
super.start(session)
session.startRegexSearch(Pattern.VIM_LWORD_END)
}
} }
class UWordEndsBeforeCaret : AceVimAction() { class JumpTillBackward : AceVimAction() {
override val boundary = VISIBLE_ON_SCREEN.intersection(BEFORE_CARET) override val mode = AceVimMode.JumpTillBackward(BEFORE_CARET.intersection(VISIBLE_ON_SCREEN))
override fun start(session: Session) {
super.start(session)
session.startRegexSearch(Pattern.VIM_UWORD_END)
}
} }
class GoToDeclaration : AceVimAction() { class JumpOnLineForward : AceVimAction() {
override val boundary = VISIBLE_ON_SCREEN override val mode = AceVimMode.Jump(AFTER_CARET.intersection(CARET_LINE))
override fun start(session: Session) {
session.startJumpMode { ActionMode(AceTagAction.GoToDeclaration, shiftMode = false) }
}
} }
class GoToTypeDeclaration : AceVimAction() { class JumpOnLineBackward : AceVimAction() {
override val boundary = VISIBLE_ON_SCREEN override val mode = AceVimMode.Jump(BEFORE_CARET.intersection(CARET_LINE))
override fun start(session: Session) {
session.startJumpMode { ActionMode(AceTagAction.GoToDeclaration, shiftMode = true) }
}
} }
class ShowIntentions : AceVimAction() { class JumpLineIndentsForward : AceVimAction() {
override val boundary = VISIBLE_ON_SCREEN override val mode = AceVimMode.JumpToPattern(Pattern.LINE_INDENTS, AFTER_CARET.intersection(VISIBLE_ON_SCREEN))
override fun start(session: Session) {
session.startJumpMode { ActionMode(AceTagAction.ShowIntentions, shiftMode = false) }
}
} }
class ShowUsages : AceVimAction() { class JumpLineIndentsBackward : AceVimAction() {
override val boundary = VISIBLE_ON_SCREEN override val mode = AceVimMode.JumpToPattern(Pattern.LINE_INDENTS, BEFORE_CARET.intersection(VISIBLE_ON_SCREEN))
override fun start(session: Session) {
session.startJumpMode { ActionMode(AceTagAction.ShowUsages, shiftMode = false) }
}
} }
class FindUsages : AceVimAction() { class JumpLWordForward : AceVimAction() {
override val boundary = VISIBLE_ON_SCREEN override val mode = AceVimMode.JumpToPattern(Pattern.VIM_LWORD, AFTER_CARET.intersection(VISIBLE_ON_SCREEN))
override fun start(session: Session) {
session.startJumpMode { ActionMode(AceTagAction.ShowUsages, shiftMode = true) }
}
} }
class Refactor : AceVimAction() { class JumpUWordForward : AceVimAction() {
override val boundary = VISIBLE_ON_SCREEN override val mode = AceVimMode.JumpToPattern(Pattern.VIM_UWORD, AFTER_CARET.intersection(VISIBLE_ON_SCREEN))
override fun start(session: Session) {
session.startJumpMode { ActionMode(AceTagAction.Refactor, shiftMode = false) }
}
} }
class Rename : AceVimAction() { class JumpLWordBackward : AceVimAction() {
override val boundary = VISIBLE_ON_SCREEN override val mode = AceVimMode.JumpToPattern(Pattern.VIM_LWORD, BEFORE_CARET.intersection(VISIBLE_ON_SCREEN))
override fun start(session: Session) { }
session.startJumpMode { ActionMode(AceTagAction.Refactor, shiftMode = true) }
} class JumpUWordBackward : AceVimAction() {
override val mode = AceVimMode.JumpToPattern(Pattern.VIM_UWORD, BEFORE_CARET.intersection(VISIBLE_ON_SCREEN))
}
class JumpLWordEndForward : AceVimAction() {
override val mode = AceVimMode.JumpToPattern(Pattern.VIM_LWORD_END, AFTER_CARET.intersection(VISIBLE_ON_SCREEN))
}
class JumpUWordEndForward : AceVimAction() {
override val mode = AceVimMode.JumpToPattern(Pattern.VIM_UWORD_END, AFTER_CARET.intersection(VISIBLE_ON_SCREEN))
}
class JumpLWordEndBackward : AceVimAction() {
override val mode = AceVimMode.JumpToPattern(Pattern.VIM_LWORD_END, BEFORE_CARET.intersection(VISIBLE_ON_SCREEN))
}
class JumpUWordEndBackward : AceVimAction() {
override val mode = AceVimMode.JumpToPattern(Pattern.VIM_UWORD_END, BEFORE_CARET.intersection(VISIBLE_ON_SCREEN))
} }
} }

View File

@@ -0,0 +1,64 @@
package org.acejump.action
import com.intellij.openapi.editor.Editor
import org.acejump.boundaries.Boundaries
import org.acejump.boundaries.StandardBoundaries
import org.acejump.openEditors
import org.acejump.search.Pattern
import org.acejump.session.Session
sealed class AceVimMode {
abstract val boundaries: Boundaries
open fun getJumpEditors(mainEditor: Editor): List<Editor> {
return listOf(mainEditor)
}
open fun setupSession(editor: Editor, session: Session) {}
open fun finishSession(editor: Editor, session: Session) {}
class Jump(override val boundaries: Boundaries) : AceVimMode()
object JumpAllEditors : AceVimMode() {
override val boundaries = StandardBoundaries.VISIBLE_ON_SCREEN
override fun getJumpEditors(mainEditor: Editor): List<Editor> {
val project = mainEditor.project ?: return super.getJumpEditors(mainEditor)
return project.openEditors
.sortedBy { if (it === mainEditor) 0 else 1 }
.ifEmpty { listOf(mainEditor) }
}
}
class JumpTillForward(override val boundaries: Boundaries) : AceVimMode() {
override fun finishSession(editor: Editor, session: Session) {
val document = editor.document
for (caret in editor.caretModel.allCarets) {
val offset = caret.offset
if (offset > document.getLineStartOffset(document.getLineNumber(offset))) {
caret.moveToOffset(offset - 1, false)
}
}
}
}
class JumpTillBackward(override val boundaries: Boundaries) : AceVimMode() {
override fun finishSession(editor: Editor, session: Session) {
val document = editor.document
for (caret in editor.caretModel.allCarets) {
val offset = caret.offset
if (offset < document.getLineEndOffset(document.getLineNumber(offset))) {
caret.moveToOffset(offset + 1, false)
}
}
}
}
class JumpToPattern(private val pattern: Pattern, override val boundaries: Boundaries) : AceVimMode() {
override fun setupSession(editor: Editor, session: Session) {
session.startRegexSearch(pattern)
}
}
}

View File

@@ -0,0 +1,13 @@
package org.acejump.action;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.VimEditor;
import com.maddyhome.idea.vim.state.mode.SelectionType;
final class AceVimUtil {
private AceVimUtil() {}
public static void enterVisualMode(final VimEditor vim, final SelectionType mode) {
VimPlugin.getVisualMotion().enterVisualMode(vim, mode);
}
}

View File

@@ -51,5 +51,18 @@ enum class StandardBoundaries : Boundaries {
override fun isOffsetInside(editor: Editor, offset: Int, cache: EditorOffsetCache): Boolean { override fun isOffsetInside(editor: Editor, offset: Int, cache: EditorOffsetCache): Boolean {
return offset > editor.caretModel.offset return offset > editor.caretModel.offset
} }
},
CARET_LINE {
override fun getOffsetRange(editor: Editor, cache: EditorOffsetCache): IntRange {
val document = editor.document
val offset = editor.caretModel.offset
val line = document.getLineNumber(offset)
return document.getLineStartOffset(line)..document.getLineEndOffset(line)
}
override fun isOffsetInside(editor: Editor, offset: Int, cache: EditorOffsetCache): Boolean {
return offset in getOffsetRange(editor, cache)
}
} }
} }

View File

@@ -21,8 +21,6 @@ class AceConfig : PersistentStateComponent<AceSettings> {
val layout get() = settings.layout val layout get() = settings.layout
val minQueryLength get() = settings.minQueryLength val minQueryLength get() = settings.minQueryLength
val jumpModeColor get() = settings.jumpModeColor val jumpModeColor get() = settings.jumpModeColor
val advancedModeColor get() = settings.advancedModeColor
val betweenPointsModeColor get() = settings.betweenPointsModeColor
val textHighlightColor get() = settings.textHighlightColor val textHighlightColor get() = settings.textHighlightColor
val tagForegroundColor get() = settings.tagForegroundColor val tagForegroundColor get() = settings.tagForegroundColor
val tagBackgroundColor get() = settings.tagBackgroundColor val tagBackgroundColor get() = settings.tagBackgroundColor

View File

@@ -16,8 +16,6 @@ class AceConfigurable : Configurable {
panel.keyboardLayout != settings.layout || panel.keyboardLayout != settings.layout ||
panel.minQueryLengthInt != settings.minQueryLength || panel.minQueryLengthInt != settings.minQueryLength ||
panel.jumpModeColor != settings.jumpModeColor || panel.jumpModeColor != settings.jumpModeColor ||
panel.advancedModeColor != settings.advancedModeColor ||
panel.betweenPointsModeColor != settings.betweenPointsModeColor ||
panel.textHighlightColor != settings.textHighlightColor || panel.textHighlightColor != settings.textHighlightColor ||
panel.tagForegroundColor != settings.tagForegroundColor || panel.tagForegroundColor != settings.tagForegroundColor ||
panel.tagBackgroundColor != settings.tagBackgroundColor || panel.tagBackgroundColor != settings.tagBackgroundColor ||
@@ -28,8 +26,6 @@ class AceConfigurable : Configurable {
settings.layout = panel.keyboardLayout settings.layout = panel.keyboardLayout
settings.minQueryLength = panel.minQueryLengthInt ?: settings.minQueryLength settings.minQueryLength = panel.minQueryLengthInt ?: settings.minQueryLength
panel.jumpModeColor?.let { settings.jumpModeColor = it } panel.jumpModeColor?.let { settings.jumpModeColor = it }
panel.advancedModeColor?.let { settings.advancedModeColor = it }
panel.betweenPointsModeColor?.let { settings.betweenPointsModeColor = it }
panel.textHighlightColor?.let { settings.textHighlightColor = it } panel.textHighlightColor?.let { settings.textHighlightColor = it }
panel.tagForegroundColor?.let { settings.tagForegroundColor = it } panel.tagForegroundColor?.let { settings.tagForegroundColor = it }
panel.tagBackgroundColor?.let { settings.tagBackgroundColor = it } panel.tagBackgroundColor?.let { settings.tagBackgroundColor = it }

View File

@@ -13,12 +13,6 @@ data class AceSettings(
@OptionTag("jumpModeRGB", converter = ColorConverter::class) @OptionTag("jumpModeRGB", converter = ColorConverter::class)
var jumpModeColor: Color = Color(0xFFFFFF), var jumpModeColor: Color = Color(0xFFFFFF),
@OptionTag("advancedModeRGB", converter = ColorConverter::class)
var advancedModeColor: Color = Color(0xFFB700),
@OptionTag("betweenPointsModeRGB", converter = ColorConverter::class)
var betweenPointsModeColor: Color = Color(0x6FC5FF),
@OptionTag("textHighlightRGB", converter = ColorConverter::class) @OptionTag("textHighlightRGB", converter = ColorConverter::class)
var textHighlightColor: Color = Color(0x394B58), var textHighlightColor: Color = Color(0x394B58),

View File

@@ -27,8 +27,6 @@ internal class AceSettingsPanel {
private val keyboardLayoutArea = JBTextArea().apply { isEditable = false } private val keyboardLayoutArea = JBTextArea().apply { isEditable = false }
private val minQueryLengthField = JBTextField() private val minQueryLengthField = JBTextField()
private val jumpModeColorWheel = ColorPanel() private val jumpModeColorWheel = ColorPanel()
private val advancedModeColorWheel = ColorPanel()
private val betweenPointsModeColorWheel = ColorPanel()
private val textHighlightColorWheel = ColorPanel() private val textHighlightColorWheel = ColorPanel()
private val tagForegroundColorWheel = ColorPanel() private val tagForegroundColorWheel = ColorPanel()
private val tagBackgroundColorWheel = ColorPanel() private val tagBackgroundColorWheel = ColorPanel()
@@ -55,9 +53,7 @@ internal class AceSettingsPanel {
} }
titledRow("Colors") { titledRow("Colors") {
row("Jump mode caret background:") { short(jumpModeColorWheel) } row("Caret background:") { short(jumpModeColorWheel) }
row("Advanced mode caret background:") { short(advancedModeColorWheel) }
row("Between Points mode caret background:") { short(betweenPointsModeColorWheel) }
row("Searched text background:") { short(textHighlightColorWheel) } row("Searched text background:") { short(textHighlightColorWheel) }
row("Tag foreground:") { short(tagForegroundColorWheel) } row("Tag foreground:") { short(tagForegroundColorWheel) }
row("Tag background:") { short(tagBackgroundColorWheel) } row("Tag background:") { short(tagBackgroundColorWheel) }
@@ -71,8 +67,6 @@ internal class AceSettingsPanel {
internal var keyChars by keyboardLayoutArea internal var keyChars by keyboardLayoutArea
internal var minQueryLength by minQueryLengthField internal var minQueryLength by minQueryLengthField
internal var jumpModeColor by jumpModeColorWheel internal var jumpModeColor by jumpModeColorWheel
internal var advancedModeColor by advancedModeColorWheel
internal var betweenPointsModeColor by betweenPointsModeColorWheel
internal var textHighlightColor by textHighlightColorWheel internal var textHighlightColor by textHighlightColorWheel
internal var tagForegroundColor by tagForegroundColorWheel internal var tagForegroundColor by tagForegroundColorWheel
internal var tagBackgroundColor by tagBackgroundColorWheel internal var tagBackgroundColor by tagBackgroundColorWheel
@@ -87,8 +81,6 @@ internal class AceSettingsPanel {
keyboardLayout = settings.layout keyboardLayout = settings.layout
minQueryLength = settings.minQueryLength.toString() minQueryLength = settings.minQueryLength.toString()
jumpModeColor = settings.jumpModeColor jumpModeColor = settings.jumpModeColor
advancedModeColor = settings.advancedModeColor
betweenPointsModeColor = settings.betweenPointsModeColor
textHighlightColor = settings.textHighlightColor textHighlightColor = settings.textHighlightColor
tagForegroundColor = settings.tagForegroundColor tagForegroundColor = settings.tagForegroundColor
tagBackgroundColor = settings.tagBackgroundColor tagBackgroundColor = settings.tagBackgroundColor

View File

@@ -1,5 +1,10 @@
package org.acejump.input package org.acejump.input
import it.unimi.dsi.fastutil.objects.Object2IntMap
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
import java.awt.geom.Point2D
import kotlin.math.floor
/** /**
* Defines common keyboard layouts. Each layout has a key priority order, based on each key's distance from the home row and how * Defines common keyboard layouts. Each layout has a key priority order, based on each key's distance from the home row and how
* ergonomically difficult they are to press. * ergonomically difficult they are to press.
@@ -18,7 +23,38 @@ enum class KeyLayout(internal val rows: Array<String>, priority: String) {
internal val allChars = rows.joinToString("").toCharArray().apply(CharArray::sort).joinToString("") internal val allChars = rows.joinToString("").toCharArray().apply(CharArray::sort).joinToString("")
internal val allPriorities = priority.mapIndexed { index, char -> char to index }.toMap() internal val allPriorities = priority.mapIndexed { index, char -> char to index }.toMap()
private val keyDistances: Map<Char, Object2IntMap<Char>> by lazy {
val keyDistanceMap = mutableMapOf<Char, Object2IntMap<Char>>()
val keyLocations = mutableMapOf<Char, Point2D>()
for ((rowIndex, rowChars) in rows.withIndex()) {
val keyY = rowIndex * 1.2F // Slightly increase cost of traveling between rows.
for ((columnIndex, char) in rowChars.withIndex()) {
val keyX = columnIndex + (0.25F * rowIndex) // Assume a 1/4-key uniform stagger.
keyLocations[char] = Point2D.Float(keyX, keyY)
}
}
for (fromChar in allChars) {
val distances = Object2IntOpenHashMap<Char>()
val fromLocation = keyLocations.getValue(fromChar)
for (toChar in allChars) {
distances[toChar] = floor(2F * fromLocation.distanceSq(keyLocations.getValue(toChar))).toInt()
}
keyDistanceMap[fromChar] = distances
}
keyDistanceMap
}
internal inline fun priority(crossinline tagToChar: (String) -> Char): (String) -> Int? { internal inline fun priority(crossinline tagToChar: (String) -> Char): (String) -> Int? {
return { allPriorities[tagToChar(it)] } return { allPriorities[tagToChar(it)] }
} }
internal fun distanceBetweenKeys(char1: Char, char2: Char): Int {
return keyDistances.getValue(char1).getValue(char2)
}
} }

View File

@@ -7,48 +7,6 @@ import org.acejump.config.AceSettings
* with repeated keys (ex. FF, JJ) or adjacent keys (ex. GH, UJ). * with repeated keys (ex. FF, JJ) or adjacent keys (ex. GH, UJ).
*/ */
internal object KeyLayoutCache { internal object KeyLayoutCache {
/**
* Stores keys ordered by proximity to other keys for the QWERTY layout.
* TODO: Support more layouts, perhaps generate automatically.
*/
private val qwertyCharacterDistances = mapOf(
'j' to "jikmnhuolbgypvftcdrxsezawq8796054321",
'f' to "ftgvcdryhbxseujnzawqikmolp5463728190",
'k' to "kolmjipnhubgyvftcdrxsezawq9807654321",
'd' to "drfcxsetgvzawyhbqujnikmolp4352617890",
'l' to "lkopmjinhubgyvftcdrxsezawq0987654321",
's' to "sedxzawrfcqtgvyhbujnikmolp3241567890",
'a' to "aqwszedxrfctgvyhbujnikmolp1234567890",
'h' to "hujnbgyikmvftolcdrpxsezawq6758493021",
'g' to "gyhbvftujncdrikmxseolzawpq5647382910",
'y' to "yuhgtijnbvfrokmcdeplxswzaq6758493021",
't' to "tygfruhbvcdeijnxswokmzaqpl5647382910",
'u' to "uijhyokmnbgtplvfrcdexswzaq7869504321",
'r' to "rtfdeygvcxswuhbzaqijnokmpl4536271890",
'n' to "nbhjmvgyuiklocftpxdrzseawq7685940321",
'v' to "vcfgbxdrtyhnzseujmawikqolp5463728190",
'm' to "mnjkbhuilvgyopcftxdrzseawq8970654321",
'c' to "cxdfvzsertgbawyhnqujmikolp4352617890",
'b' to "bvghncftyujmxdrikzseolawqp6574839201",
'i' to "iokjuplmnhybgtvfrcdexswzaq8970654321",
'e' to "erdswtfcxzaqygvuhbijnokmpl3425167890",
'x' to "xzsdcawerfvqtgbyhnujmikolp3241567890",
'z' to "zasxqwedcrfvtgbyhnujmikolp1234567890",
'o' to "oplkimjunhybgtvfrcdexswzaq9087654321",
'w' to "wesaqrdxztfcygvuhbijnokmpl2314567890",
'p' to "plokimjunhybgtvfrcdexswzaq0987654321",
'q' to "qwaeszrdxtfcygvuhbijnokmpl1234567890",
'1' to "1234567890qawzsexdrcftvgybhunjimkolp",
'2' to "2134567890qwasezxdrcftvgybhunjimkolp",
'3' to "3241567890weqasdrzxcftvgybhunjimkolp",
'4' to "4352617890erwsdftqazxcvgybhunjimkolp",
'5' to "5463728190rtedfgywsxcvbhuqaznjimkolp",
'6' to "6574839201tyrfghuedcvbnjiwsxmkoqazlp",
'7' to "7685940321yutghjirfvbnmkoedclpwsxqaz",
'8' to "8796054321uiyhjkotgbnmlprfvedcwsxqaz",
'9' to "9807654321ioujklpyhnmtgbrfvedcwsxqaz",
'0' to "0987654321opiklujmyhntgbrfvedcwsxqaz").mapValues { (_, v) -> v.mapIndexed { index, char -> char to index }.toMap() }
/** /**
* Sorts tags according to current keyboard layout settings, and some predefined rules that force tags with digits, and tags with two * Sorts tags according to current keyboard layout settings, and some predefined rules that force tags with digits, and tags with two
* keys far apart, to be sorted after other (easier to type) tags. * keys far apart, to be sorted after other (easier to type) tags.
@@ -77,7 +35,7 @@ internal object KeyLayoutCache {
fun reset(settings: AceSettings) { fun reset(settings: AceSettings) {
tagOrder = compareBy( tagOrder = compareBy(
{ it[0].isDigit() || it[1].isDigit() }, { it[0].isDigit() || it[1].isDigit() },
{ qwertyCharacterDistances.getValue(it[0]).getValue(it[1]) }, { settings.layout.distanceBetweenKeys(it[0], it[1]) },
settings.layout.priority { it[0] } settings.layout.priority { it[0] }
) )

View File

@@ -1,12 +0,0 @@
package org.acejump.modes
import org.acejump.action.AceTagAction
import org.acejump.search.Tag
import org.acejump.session.SessionState
class ActionMode(private val action: AceTagAction, private val shiftMode: Boolean) : JumpMode() {
override fun accept(state: SessionState, acceptedTag: Tag): Boolean {
state.act(action, acceptedTag, shiftMode, isFinal = true)
return true
}
}

View File

@@ -1,73 +0,0 @@
package org.acejump.modes
import org.acejump.action.AceTagAction
import org.acejump.config.AceConfig
import org.acejump.search.Tag
import org.acejump.session.SessionState
import org.acejump.session.TypeResult
class AdvancedMode : SessionMode {
companion object {
private val JUMP_HINT = arrayOf(
"<f>[J]</f>ump / <f>[L]</f> past Query",
"<f>[E]</f> Word End / <f>[M]</f> Line End"
)
val JUMP_ALT_HINT = JUMP_HINT.map { it.replace("<f>[J]</f>ump ", "<f>[J]</f> at Tag ") }.toTypedArray()
val JUMP_ACTION_MAP = mapOf(
'J' to AceTagAction.JumpToSearchStart,
'L' to AceTagAction.JumpPastSearchEnd,
'E' to AceTagAction.JumpToWordEnd,
'M' to AceTagAction.JumpToLineEnd
)
val SELECT_HINT = arrayOf(
"Select <f>[W]</f>ord / <f>[H]</f>ump",
"Select <f>[Q]</f>uery / <f>[N]</f> Line / <f>[1-9]</f> Expansion"
)
val SELECT_ACTION_MAP = mapOf(
'W' to AceTagAction.SelectWord,
'H' to AceTagAction.SelectHump,
'Q' to AceTagAction.SelectQuery,
'N' to AceTagAction.SelectLine,
*('1'..'9').mapIndexed { index, char -> char to AceTagAction.SelectExtended(index + 1) }.toTypedArray()
)
private val ALL_HINTS = arrayOf(
*JUMP_HINT,
*SELECT_HINT,
)
private val ALL_ACTION_MAP = mapOf(
*JUMP_ACTION_MAP.map { it.key to it.value }.toTypedArray(),
*SELECT_ACTION_MAP.map { it.key to it.value }.toTypedArray(),
)
}
override val caretColor
get() = AceConfig.advancedModeColor
override fun type(state: SessionState, charTyped: Char, acceptedTag: Tag?): TypeResult {
if (acceptedTag == null) {
return state.type(charTyped)
}
val action = ALL_ACTION_MAP[charTyped.toUpperCase()]
if (action != null) {
state.act(action, acceptedTag, shiftMode = charTyped.isUpperCase(), isFinal = true)
return TypeResult.EndSession
}
return TypeResult.Nothing
}
override fun accept(state: SessionState, acceptedTag: Tag): Boolean {
return false
}
override fun getHint(acceptedTag: Int?, hasQuery: Boolean): Array<String>? {
return ALL_HINTS.takeIf { acceptedTag != null }
}
}

View File

@@ -1,92 +0,0 @@
package org.acejump.modes
import com.intellij.openapi.editor.CaretState
import org.acejump.action.AceTagAction
import org.acejump.config.AceConfig
import org.acejump.search.Tag
import org.acejump.session.SessionState
import org.acejump.session.TypeResult
class BetweenPointsMode : SessionMode {
private companion object {
private val TYPE_TAG_HINT = arrayOf(
"<b>Type to Search...</b>"
)
private val ACTION_MODE_HINT = arrayOf(
"<f>[C]</f>lone to Caret...",
"<f>[M]</f>ove to Caret..."
)
private val ACTION_MODE_MAP = mapOf(
'C' to (AceTagAction::CloneToCaret),
'M' to (AceTagAction::MoveToCaret)
)
}
override val caretColor
get() = AceConfig.betweenPointsModeColor
private var actionMode: ((AceTagAction.BaseSelectAction) -> AceTagAction)? = null
private var originalCarets: List<CaretState>? = null
private var firstOffset: Int? = null
override fun type(state: SessionState, charTyped: Char, acceptedTag: Tag?): TypeResult {
val actionMode = actionMode
if (actionMode == null) {
this.actionMode = ACTION_MODE_MAP[charTyped.toUpperCase()]
return TypeResult.Nothing
}
if (acceptedTag == null) {
return state.type(charTyped)
}
if (firstOffset == null) {
val selectAction = AdvancedMode.SELECT_ACTION_MAP[charTyped.toUpperCase()]
if (selectAction != null) {
state.act(actionMode(selectAction), acceptedTag, shiftMode = charTyped.isUpperCase(), isFinal = true)
return TypeResult.EndSession
}
}
val jumpAction = AdvancedMode.JUMP_ACTION_MAP[charTyped.toUpperCase()]
if (jumpAction == null) {
return TypeResult.Nothing
}
val firstOffset = firstOffset
if (firstOffset == null) {
val caretModel = acceptedTag.editor.caretModel
this.originalCarets = caretModel.caretsAndSelections
state.act(jumpAction, acceptedTag, shiftMode = false, isFinal = false)
this.firstOffset = caretModel.offset
return TypeResult.RestartSearch
}
originalCarets?.let { acceptedTag.editor.caretModel.caretsAndSelections = it }
state.act(
actionMode(AceTagAction.SelectBetweenPoints(firstOffset, jumpAction)),
acceptedTag,
shiftMode = charTyped.isUpperCase(),
isFinal = true
)
return TypeResult.EndSession
}
override fun accept(state: SessionState, acceptedTag: Tag): Boolean {
return false
}
override fun getHint(acceptedTag: Int?, hasQuery: Boolean): Array<String>? {
return when {
actionMode == null -> ACTION_MODE_HINT
acceptedTag == null -> TYPE_TAG_HINT.takeUnless { hasQuery }
firstOffset == null -> AdvancedMode.JUMP_ALT_HINT + AdvancedMode.SELECT_HINT
else -> AdvancedMode.JUMP_ALT_HINT
}
}
}

View File

@@ -22,8 +22,4 @@ open class JumpMode : SessionMode {
state.act(AceTagAction.JumpToSearchStart, acceptedTag, wasUpperCase, isFinal = true) state.act(AceTagAction.JumpToSearchStart, acceptedTag, wasUpperCase, isFinal = true)
return true return true
} }
override fun getHint(acceptedTag: Int?, hasQuery: Boolean): Array<String>? {
return null
}
} }

View File

@@ -10,5 +10,4 @@ interface SessionMode {
fun type(state: SessionState, charTyped: Char, acceptedTag: Tag?): TypeResult fun type(state: SessionState, charTyped: Char, acceptedTag: Tag?): TypeResult
fun accept(state: SessionState, acceptedTag: Tag): Boolean fun accept(state: SessionState, acceptedTag: Tag): Boolean
fun getHint(acceptedTag: Int?, hasQuery: Boolean): Array<String>?
} }

View File

@@ -1,17 +0,0 @@
package org.acejump.modes
import org.acejump.action.AceTagAction
import org.acejump.search.Tag
import org.acejump.session.SessionState
class VimJumpMode : JumpMode() {
override fun accept(state: SessionState, acceptedTag: Tag): Boolean {
val action = if (acceptedTag.editor.selectionModel.hasSelection())
AceTagAction.SelectToCaret(AceTagAction.JumpToSearchStart)
else
AceTagAction.JumpToSearchStart
state.act(action, acceptedTag, wasUpperCase, isFinal = true)
return true
}
}

View File

@@ -4,7 +4,6 @@ enum class Pattern(val regex: String) {
LINE_STARTS("^.|^\\n"), LINE_STARTS("^.|^\\n"),
LINE_ENDS("\\n|\\Z"), LINE_ENDS("\\n|\\Z"),
LINE_INDENTS("[^\\s].*|^\\n"), LINE_INDENTS("[^\\s].*|^\\n"),
LINE_ALL_MARKS(LINE_ENDS.regex + "|" + LINE_STARTS.regex + "|" + LINE_INDENTS.regex),
ALL_WORDS("(?<=[^a-zA-Z0-9_]|\\A)[a-zA-Z0-9_]"), ALL_WORDS("(?<=[^a-zA-Z0-9_]|\\A)[a-zA-Z0-9_]"),
VIM_LWORD("(?<=[^a-zA-Z0-9_]|\\A)[a-zA-Z0-9_]"), VIM_LWORD("(?<=[^a-zA-Z0-9_]|\\A)[a-zA-Z0-9_]"),
VIM_UWORD("(?<=\\s|\\A)[^\\s]"), VIM_UWORD("(?<=\\s|\\A)[^\\s]"),

View File

@@ -3,7 +3,6 @@ package org.acejump.search
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import it.unimi.dsi.fastutil.ints.IntArrayList import it.unimi.dsi.fastutil.ints.IntArrayList
import org.acejump.boundaries.Boundaries import org.acejump.boundaries.Boundaries
import org.acejump.clone
import org.acejump.immutableText import org.acejump.immutableText
import org.acejump.isWordPart import org.acejump.isWordPart
import org.acejump.matchesAt import org.acejump.matchesAt
@@ -11,9 +10,7 @@ import org.acejump.matchesAt
/** /**
* Searches editor text for matches of a [SearchQuery], and updates previous results when the user [type]s a character. * Searches editor text for matches of a [SearchQuery], and updates previous results when the user [type]s a character.
*/ */
class SearchProcessor private constructor( class SearchProcessor private constructor(query: SearchQuery, results: MutableMap<Editor, IntArrayList>) {
private val editors: List<Editor>, query: SearchQuery, results: MutableMap<Editor, IntArrayList>
) {
companion object { companion object {
fun fromChar(editors: List<Editor>, char: Char, boundaries: Boundaries): SearchProcessor { fun fromChar(editors: List<Editor>, char: Char, boundaries: Boundaries): SearchProcessor {
return SearchProcessor(editors, SearchQuery.Literal(char.toString()), boundaries) return SearchProcessor(editors, SearchQuery.Literal(char.toString()), boundaries)
@@ -24,7 +21,7 @@ class SearchProcessor private constructor(
} }
} }
private constructor(editors: List<Editor>, query: SearchQuery, boundaries: Boundaries) : this(editors, query, mutableMapOf()) { private constructor(editors: List<Editor>, query: SearchQuery, boundaries: Boundaries) : this(query, mutableMapOf()) {
val regex = query.toRegex() val regex = query.toRegex()
if (regex != null) { if (regex != null) {
@@ -149,8 +146,4 @@ class SearchProcessor private constructor(
results[editor] = remaining results[editor] = remaining
} }
} }
fun clone(): SearchProcessor {
return SearchProcessor(editors, query, results.clone())
}
} }

View File

@@ -74,10 +74,6 @@ class Tagger(private val editors: List<Editor>) {
return TaggingResult.Mark(createTagMarkers(resultTags, query.rawText.ifEmpty { null })) return TaggingResult.Mark(createTagMarkers(resultTags, query.rawText.ifEmpty { null }))
} }
fun clone(): Tagger {
return Tagger(editors).also { it.tagMap.putAll(tagMap) }
}
/** /**
* Assigns as many unassigned tags as possible, and merges them with the existing compatible tags. * Assigns as many unassigned tags as possible, and merges them with the existing compatible tags.
*/ */

View File

@@ -1,25 +1,19 @@
package org.acejump.session package org.acejump.session
import com.intellij.codeInsight.hint.HintManager
import com.intellij.codeInsight.hint.HintManagerImpl import com.intellij.codeInsight.hint.HintManagerImpl
import com.intellij.codeInsight.hint.HintUtil
import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ScrollType import com.intellij.openapi.editor.ScrollType
import com.intellij.openapi.editor.actionSystem.TypedActionHandler import com.intellij.openapi.editor.actionSystem.TypedActionHandler
import com.intellij.openapi.editor.colors.EditorColors import com.intellij.openapi.editor.colors.EditorColors
import com.intellij.openapi.editor.colors.impl.AbstractColorsScheme import com.intellij.openapi.editor.colors.impl.AbstractColorsScheme
import com.intellij.ui.LightweightHint
import org.acejump.ExternalUsage import org.acejump.ExternalUsage
import org.acejump.boundaries.Boundaries import org.acejump.boundaries.Boundaries
import org.acejump.boundaries.StandardBoundaries import org.acejump.boundaries.StandardBoundaries
import org.acejump.clone import org.acejump.clone
import org.acejump.config.AceConfig import org.acejump.config.AceConfig
import org.acejump.immutableText
import org.acejump.input.EditorKeyListener import org.acejump.input.EditorKeyListener
import org.acejump.input.KeyLayoutCache import org.acejump.input.KeyLayoutCache
import org.acejump.modes.AdvancedMode
import org.acejump.modes.BetweenPointsMode
import org.acejump.modes.JumpMode import org.acejump.modes.JumpMode
import org.acejump.modes.SessionMode import org.acejump.modes.SessionMode
import org.acejump.search.* import org.acejump.search.*
@@ -68,13 +62,12 @@ class Session(private val mainEditor: Editor, private val jumpEditors: List<Edit
editorSettings.stopEditing(editor) editorSettings.stopEditing(editor)
when (result) { when (result) {
TypeResult.Nothing -> updateHint() TypeResult.Nothing -> return;
is TypeResult.UpdateResults -> updateSearch(result.processor, markImmediately = hadTags) is TypeResult.UpdateResults -> updateSearch(result.processor, markImmediately = hadTags)
is TypeResult.ChangeMode -> setMode(result.mode) is TypeResult.ChangeMode -> setMode(result.mode)
TypeResult.RestartSearch -> restart().also { TypeResult.RestartSearch -> restart().also {
this@Session.state = SessionStateImpl(jumpEditors, tagger, defaultBoundary) this@Session.state = SessionStateImpl(jumpEditors, tagger, defaultBoundary)
updateHint()
} }
TypeResult.EndSession -> end() TypeResult.EndSession -> end()
@@ -114,37 +107,11 @@ class Session(private val mainEditor: Editor, private val jumpEditors: List<Edit
textHighlighter.renderOccurrences(results, query) textHighlighter.renderOccurrences(results, query)
} }
} }
updateHint()
} }
private fun setMode(mode: SessionMode) { private fun setMode(mode: SessionMode) {
this.mode = mode this.mode = mode
mainEditor.colorsScheme.setColor(EditorColors.CARET_COLOR, mode.caretColor) mainEditor.colorsScheme.setColor(EditorColors.CARET_COLOR, mode.caretColor)
updateHint()
}
private fun updateHint() {
val acceptedTag = acceptedTag
val editor = mainEditor
val offset = when {
acceptedTag == null -> null
acceptedTag.editor === editor -> acceptedTag.offset
else -> mainEditor.caretModel.offset
}
val hintArray = mode.getHint(offset, state?.currentProcessor.let { it != null && it.query.rawText.isNotEmpty() }) ?: return
val hintText = hintArray
.joinToString("\n")
.replace("<f>", "<span style=\"font-family:'${editor.colorsScheme.editorFontName}';font-weight:bold\">")
.replace("</f>", "</span>")
val hint = LightweightHint(HintUtil.createInformationLabel(hintText))
val pos = offset?.let(editor::offsetToLogicalPosition) ?: editor.caretModel.logicalPosition
val point = HintManagerImpl.getHintPosition(hint, editor, pos, HintManager.ABOVE)
val info = HintManagerImpl.createHintHint(editor, point, hint, HintManager.ABOVE).setShowImmediately(true)
val flags = HintManager.UPDATE_BY_SCROLLING or HintManager.HIDE_BY_ESCAPE
HintManagerImpl.getInstanceImpl().showEditorHint(hint, editor, point, flags, 0, true, info)
} }
fun startJumpMode() { fun startJumpMode() {
@@ -165,22 +132,6 @@ class Session(private val mainEditor: Editor, private val jumpEditors: List<Edit
state = SessionStateImpl(jumpEditors, tagger, defaultBoundary) state = SessionStateImpl(jumpEditors, tagger, defaultBoundary)
} }
fun startOrCycleSpecialModes() {
if (!this::mode.isInitialized) {
setMode(AdvancedMode())
state = SessionStateImpl(jumpEditors, tagger, defaultBoundary)
return
}
restart()
setMode(when (mode) {
is AdvancedMode -> BetweenPointsMode()
else -> AdvancedMode()
})
state = SessionStateImpl(jumpEditors, tagger, defaultBoundary)
}
/** /**
* Starts a regular expression search. If a search was already active, it will be reset alongside its tags and highlights. * Starts a regular expression search. If a search was already active, it will be reset alongside its tags and highlights.
*/ */
@@ -192,9 +143,8 @@ class Session(private val mainEditor: Editor, private val jumpEditors: List<Edit
tagger = Tagger(jumpEditors) tagger = Tagger(jumpEditors)
tagCanvases.values.forEach { it.setMarkers(emptyList()) } tagCanvases.values.forEach { it.setMarkers(emptyList()) }
val processor = SearchProcessor.fromRegex(jumpEditors, pattern.regex, defaultBoundary).also { val processor = SearchProcessor.fromRegex(jumpEditors, pattern.regex, defaultBoundary)
state = SessionStateImpl(jumpEditors, tagger, defaultBoundary, it) state = SessionStateImpl(jumpEditors, tagger, defaultBoundary, processor)
}
updateSearch(processor, markImmediately = true) updateSearch(processor, markImmediately = true)
} }
@@ -206,15 +156,6 @@ class Session(private val mainEditor: Editor, private val jumpEditors: List<Edit
if (processor != null) { if (processor != null) {
updateSearch(processor, markImmediately = true) updateSearch(processor, markImmediately = true)
} }
else if (mode is AdvancedMode) {
val offset = mainEditor.caretModel.offset
val result = mainEditor.immutableText.getOrNull(offset)?.let(state::type)
if (result is TypeResult.UpdateResults) {
val tag = Tag(mainEditor, offset).also { acceptedTag = it }
textHighlighter.renderFinal(tag, result.processor.query)
updateHint()
}
}
} }
/** /**

View File

@@ -9,6 +9,7 @@
</description> </description>
<depends>com.intellij.modules.platform</depends> <depends>com.intellij.modules.platform</depends>
<depends>IdeaVIM</depends>
<category>Navigation</category> <category>Navigation</category>
<vendor url="https://github.com/acejump/AceJump">AceJump</vendor> <vendor url="https://github.com/acejump/AceJump">AceJump</vendor>
@@ -25,111 +26,25 @@
implementationClass="org.acejump.action.AceEditorAction$ClearSearch"/> implementationClass="org.acejump.action.AceEditorAction$ClearSearch"/>
<editorActionHandler action="EditorEnter" order="first" <editorActionHandler action="EditorEnter" order="first"
implementationClass="org.acejump.action.AceEditorAction$TagImmediately"/> implementationClass="org.acejump.action.AceEditorAction$TagImmediately"/>
<editorActionHandler action="EditorUp" order="first"
implementationClass="org.acejump.action.AceEditorAction$SearchLineStarts"/>
<editorActionHandler action="EditorLeft" order="first"
implementationClass="org.acejump.action.AceEditorAction$SearchLineIndents"/>
<editorActionHandler action="EditorLineStart" order="first"
implementationClass="org.acejump.action.AceEditorAction$SearchLineIndents"/>
<editorActionHandler action="EditorRight" order="first"
implementationClass="org.acejump.action.AceEditorAction$SearchLineEnds"/>
<editorActionHandler action="EditorLineEnd" order="first"
implementationClass="org.acejump.action.AceEditorAction$SearchLineEnds"/>
</extensions> </extensions>
<actions> <actions>
<action id="AceAction" <action id="AceVimAction_JumpAllEditors" class="org.acejump.action.AceVimAction$JumpAllEditors" text="AceJump Vim - Jump All Editors" />
class="org.acejump.action.AceKeyboardAction$ActivateAceJump" <action id="AceVimAction_JumpForward" class="org.acejump.action.AceVimAction$JumpForward" text="AceJump Vim - Jump Forward" />
text="Activate AceJump"> <action id="AceVimAction_JumpBackward" class="org.acejump.action.AceVimAction$JumpBackward" text="AceJump Vim - Jump Backward" />
<keyboard-shortcut keymap="Mac OS X" first-keystroke="ctrl SEMICOLON"/> <action id="AceVimAction_JumpTillForward" class="org.acejump.action.AceVimAction$JumpTillForward" text="AceJump Vim - Jump Till Forward" />
<keyboard-shortcut keymap="Mac OS X 10.5+" first-keystroke="ctrl SEMICOLON"/> <action id="AceVimAction_JumpTillBackward" class="org.acejump.action.AceVimAction$JumpTillBackward" text="AceJump Vim - Jump Till Backward" />
<keyboard-shortcut keymap="$default" first-keystroke="ctrl SEMICOLON"/> <action id="AceVimAction_JumpOnLineForward" class="org.acejump.action.AceVimAction$JumpOnLineForward" text="AceJump Vim - Jump On Line Forward" />
</action> <action id="AceVimAction_JumpOnLineBackward" class="org.acejump.action.AceVimAction$JumpOnLineBackward" text="AceJump Vim - Jump On Line Backward" />
<action id="AceSpecialAction" <action id="AceVimAction_JumpLineIndentsForward" class="org.acejump.action.AceVimAction$JumpLineIndentsForward" text="AceJump Vim - Jump Line Indents Forward" />
class="org.acejump.action.AceKeyboardAction$ActivateAceJumpSpecial" <action id="AceVimAction_JumpLineIndentsBackward" class="org.acejump.action.AceVimAction$JumpLineIndentsBackward" text="AceJump Vim - Jump Line Indents Backward" />
text="Activate / Cycle AceJump Special Modes"> <action id="AceVimAction_JumpLWordForward" class="org.acejump.action.AceVimAction$JumpLWordForward" text="AceJump Vim - Jump LWord Forward" />
<keyboard-shortcut keymap="Mac OS X" first-keystroke="ctrl alt SEMICOLON"/> <action id="AceVimAction_JumpUWordForward" class="org.acejump.action.AceVimAction$JumpUWordForward" text="AceJump Vim - Jump UWord Forward" />
<keyboard-shortcut keymap="Mac OS X 10.5+" first-keystroke="ctrl alt SEMICOLON"/> <action id="AceVimAction_JumpLWordBackward" class="org.acejump.action.AceVimAction$JumpLWordBackward" text="AceJump Vim - Jump LWord Backward" />
<keyboard-shortcut keymap="$default" first-keystroke="ctrl alt SEMICOLON"/> <action id="AceVimAction_JumpUWordBackward" class="org.acejump.action.AceVimAction$JumpUWordBackward" text="AceJump Vim - Jump UWord Backward" />
</action> <action id="AceVimAction_JumpLWordEndForward" class="org.acejump.action.AceVimAction$JumpLWordEndForward" text="AceJump Vim - Jump LWord End Forward" />
<action id="AceLineAction" <action id="AceVimAction_JumpUWordEndForward" class="org.acejump.action.AceVimAction$JumpUWordEndForward" text="AceJump Vim - Jump UWord End Forward" />
class="org.acejump.action.AceKeyboardAction$StartAllLineMarksMode" <action id="AceVimAction_JumpLWordEndBackward" class="org.acejump.action.AceVimAction$JumpLWordEndBackward" text="AceJump Vim - Jump LWord End Backward" />
text="Start AceJump in All Line Marks Mode"> <action id="AceVimAction_JumpUWordEndBackward" class="org.acejump.action.AceVimAction$JumpUWordEndBackward" text="AceJump Vim - Jump UWord End Backward" />
<keyboard-shortcut keymap="Mac OS X" first-keystroke="ctrl shift SEMICOLON"/>
<keyboard-shortcut keymap="Mac OS X 10.5+" first-keystroke="ctrl shift SEMICOLON"/>
<keyboard-shortcut keymap="$default" first-keystroke="ctrl shift SEMICOLON"/>
</action>
<action id="AceLineStartsAction"
class="org.acejump.action.AceKeyboardAction$StartAllLineStartsMode"
text="Start AceJump in All Line Starts Mode"/>
<action id="AceLineEndsAction"
class="org.acejump.action.AceKeyboardAction$StartAllLineEndsMode"
text="Start AceJump in All Line Ends Mode"/>
<action id="AceLineIndentsAction"
class="org.acejump.action.AceKeyboardAction$StartAllLineIndentsMode"
text="Start AceJump in All Line Indents Mode"/>
<action id="AceWordAction"
class="org.acejump.action.AceKeyboardAction$StartAllWordsMode"
text="Start AceJump in All Words Mode"/>
<action id="AceWordForwardAction"
class="org.acejump.action.AceKeyboardAction$StartAllWordsForwardMode"
text="Start AceJump in All Words After Caret Mode"/>
<action id="AceWordBackwardsAction"
class="org.acejump.action.AceKeyboardAction$StartAllWordsBackwardsMode"
text="Start AceJump in All Words Before Caret Mode"/>
<action id="AceVimAction_JumpToChar"
class="org.acejump.action.AceVimAction$JumpToChar"
text="AceJump Vim - JumpToChar"/>
<action id="AceVimAction_JumpToCharBeforeCaret"
class="org.acejump.action.AceVimAction$JumpToCharBeforeCaret"
text="AceJump Vim - JumpToCharBeforeCaret"/>
<action id="AceVimAction_JumpToCharAfterCaret"
class="org.acejump.action.AceVimAction$JumpToCharAfterCaret"
text="AceJump Vim - JumpToCharAfterCaret"/>
<action id="AceVimAction_LWordsAfterCaret"
class="org.acejump.action.AceVimAction$LWordsAfterCaret"
text="AceJump Vim - LWordsAfterCaret"/>
<action id="AceVimAction_UWordsAfterCaret"
class="org.acejump.action.AceVimAction$UWordsAfterCaret"
text="AceJump Vim - UWordsAfterCaret"/>
<action id="AceVimAction_LWordsBeforeCaret"
class="org.acejump.action.AceVimAction$LWordsBeforeCaret"
text="AceJump Vim - LWordsBeforeCaret"/>
<action id="AceVimAction_UWordsBeforeCaret"
class="org.acejump.action.AceVimAction$UWordsBeforeCaret"
text="AceJump Vim - UWordsBeforeCaret"/>
<action id="AceVimAction_LWordEndsAfterCaret"
class="org.acejump.action.AceVimAction$LWordEndsAfterCaret"
text="AceJump Vim - LWordEndsAfterCaret"/>
<action id="AceVimAction_UWordEndsAfterCaret"
class="org.acejump.action.AceVimAction$UWordEndsAfterCaret"
text="AceJump Vim - UWordEndsAfterCaret"/>
<action id="AceVimAction_LWordEndsBeforeCaret"
class="org.acejump.action.AceVimAction$LWordEndsBeforeCaret"
text="AceJump Vim - LWordEndsBeforeCaret"/>
<action id="AceVimAction_UWordEndsBeforeCaret"
class="org.acejump.action.AceVimAction$UWordEndsBeforeCaret"
text="AceJump Vim - UWordEndsBeforeCaret"/>
<action id="AceVimAction_GoToDeclaration"
class="org.acejump.action.AceVimAction$GoToDeclaration"
text="AceJump Vim - GoToDeclaration"/>
<action id="AceVimAction_GoToTypeDeclaration"
class="org.acejump.action.AceVimAction$GoToTypeDeclaration"
text="AceJump Vim - GoToTypeDeclaration"/>
<action id="AceVimAction_ShowIntentions"
class="org.acejump.action.AceVimAction$ShowIntentions"
text="AceJump Vim - ShowIntentions"/>
<action id="AceVimAction_ShowUsages"
class="org.acejump.action.AceVimAction$ShowUsages"
text="AceJump Vim - ShowUsages"/>
<action id="AceVimAction_FindUsages"
class="org.acejump.action.AceVimAction$FindUsages"
text="AceJump Vim - FindUsages"/>
<action id="AceVimAction_Refactor"
class="org.acejump.action.AceVimAction$Refactor"
text="AceJump Vim - Refactor"/>
<action id="AceVimAction_Rename"
class="org.acejump.action.AceVimAction$Rename"
text="AceJump Vim - Rename"/>
</actions> </actions>
</idea-plugin> </idea-plugin>

View File

@@ -1,5 +1,4 @@
import org.acejump.action.AceKeyboardAction
import org.acejump.test.util.BaseTest import org.acejump.test.util.BaseTest
/** /**
@@ -34,40 +33,4 @@ class AceTest : BaseTest() {
myFixture.checkResult("testin<caret>g 1234") myFixture.checkResult("testin<caret>g 1234")
} }
fun `test words before caret action`() {
makeEditor("test words <caret> before caret is two")
takeAction(AceKeyboardAction.StartAllWordsBackwardsMode)
assertEquals(2, session.tags.size)
}
fun `test words after caret action`() {
makeEditor("test words <caret> after caret is four")
takeAction(AceKeyboardAction.StartAllWordsForwardMode)
assertEquals(4, session.tags.size)
}
fun `test word mode`() {
makeEditor("test word action")
takeAction(AceKeyboardAction.StartAllWordsMode)
assertEquals(3, session.tags.size)
typeAndWaitForResults(session.tags[1].key)
myFixture.checkResult("test <caret>word action")
}
fun `test line mode`() {
makeEditor(" test\n three\n lines\n")
takeAction(AceKeyboardAction.StartAllLineMarksMode)
assertEquals(9, session.tags.size)
}
} }

View File

@@ -1,11 +1,10 @@
import org.acejump.action.AceKeyboardAction
import org.acejump.action.AceVimAction
import org.acejump.test.util.BaseTest import org.acejump.test.util.BaseTest
import org.junit.Ignore
import java.io.File import java.io.File
import kotlin.random.Random import kotlin.random.Random
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
@Ignore
class LatencyTest : BaseTest() { class LatencyTest : BaseTest() {
private fun `test tag latency`(editorText: String) { private fun `test tag latency`(editorText: String) {
@@ -15,7 +14,7 @@ class LatencyTest : BaseTest() {
for (query in chars) { for (query in chars) {
makeEditor(editorText) makeEditor(editorText)
myFixture.testAction(AceKeyboardAction.ActivateAceJumpSpecial) myFixture.testAction(AceVimAction.JumpAllEditors())
time += measureTimeMillis { typeAndWaitForResults("$query") } time += measureTimeMillis { typeAndWaitForResults("$query") }
// TODO assert(Tagger.markers.isNotEmpty()) { "Should be tagged: $query" } // TODO assert(Tagger.markers.isNotEmpty()) { "Should be tagged: $query" }
resetEditor() resetEditor()

View File

@@ -7,7 +7,7 @@ import com.intellij.openapi.fileTypes.PlainTextFileType
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.intellij.testFramework.fixtures.BasePlatformTestCase
import com.intellij.util.ui.UIUtil import com.intellij.util.ui.UIUtil
import org.acejump.action.AceKeyboardAction import org.acejump.action.AceVimAction
import org.acejump.session.SessionManager import org.acejump.session.SessionManager
abstract class BaseTest : BasePlatformTestCase() { abstract class BaseTest : BasePlatformTestCase() {
@@ -58,7 +58,7 @@ abstract class BaseTest : BasePlatformTestCase() {
private fun String.executeQuery(query: String) { private fun String.executeQuery(query: String) {
myFixture.run { myFixture.run {
makeEditor(this@executeQuery) makeEditor(this@executeQuery)
testAction(AceKeyboardAction.ActivateAceJump) testAction(AceVimAction.JumpAllEditors())
typeAndWaitForResults(query) typeAndWaitForResults(query)
} }
} }