diff --git a/src/main/kotlin/org/acejump/modes/JumpMode.kt b/src/main/kotlin/org/acejump/modes/JumpMode.kt index 84c5c87..a9476ae 100644 --- a/src/main/kotlin/org/acejump/modes/JumpMode.kt +++ b/src/main/kotlin/org/acejump/modes/JumpMode.kt @@ -39,9 +39,12 @@ class JumpMode : SessionMode { private val ALL_HINTS = arrayOf( *JUMP_HINT, *SELECT_HINT, + "Select <f>[P]</f>rogressively...", "<f>[D]</f>eclaration / <f>[U]</f>sages", "<f>[I]</f>ntentions / <f>[R]</f>efactor" ) + + private const val ACTION_SELECT_PROGRESSIVELY = 'P' private val ALL_ACTION_MAP = mapOf( *JUMP_ACTION_MAP.map { it.key to it.value }.toTypedArray(), @@ -66,6 +69,10 @@ class JumpMode : SessionMode { state.act(action, acceptedTag, charTyped.isUpperCase()) return TypeResult.EndSession } + else if (charTyped.equals(ACTION_SELECT_PROGRESSIVELY, ignoreCase = true)) { + state.act(AceTagAction.SelectQuery, acceptedTag, charTyped.isUpperCase()) + return TypeResult.ChangeMode(ProgressiveSelectionMode()) + } return TypeResult.Nothing } diff --git a/src/main/kotlin/org/acejump/modes/ProgressiveSelectionMode.kt b/src/main/kotlin/org/acejump/modes/ProgressiveSelectionMode.kt new file mode 100644 index 0000000..0a5655c --- /dev/null +++ b/src/main/kotlin/org/acejump/modes/ProgressiveSelectionMode.kt @@ -0,0 +1,142 @@ +package org.acejump.modes + +import com.intellij.openapi.editor.Caret +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.ScrollType +import org.acejump.action.AceTagAction +import org.acejump.config.AceConfig +import org.acejump.immutableText +import org.acejump.isWordPart +import org.acejump.session.SessionState +import org.acejump.session.TypeResult + +class ProgressiveSelectionMode : SessionMode { + private companion object { + private val EXPANSION_HINT = arrayOf( + "<f>[W]</f>ord / <f>[C]</f>har / <f>[L]</f>ine / <f>[S]</f>pace" + ) + + private val EXPANSION_MODES = mapOf( + 'W' to SelectionMode.Word, + 'C' to SelectionMode.Char, + 'L' to SelectionMode.Line, + 'S' to SelectionMode.Space + ) + } + + override val caretColor + get() = AceConfig.jumpModeColor + + override fun type(state: SessionState, charTyped: Char, acceptedTag: Int?): TypeResult { + val editor = state.editor + val mode = EXPANSION_MODES[charTyped.toUpperCase()] + + if (mode != null) { + val hintOffset = if (charTyped.isUpperCase()) { + editor.caretModel.runForEachCaret { mode.extendLeft(editor, it) } + editor.caretModel.allCarets.first().selectionStart + } + else { + editor.caretModel.runForEachCaret { mode.extendRight(editor, it); it.moveToOffset(it.selectionEnd) } + editor.caretModel.allCarets.last().selectionEnd + } + + editor.scrollingModel.scrollTo(editor.offsetToLogicalPosition(hintOffset), ScrollType.RELATIVE) + return TypeResult.MoveHint(hintOffset) + } + + return TypeResult.Nothing + } + + override fun getHint(acceptedTag: Int?, hasQuery: Boolean): Array<String> { + return EXPANSION_HINT + } + + private sealed class SelectionMode { + abstract fun extendLeft(editor: Editor, caret: Caret) + abstract fun extendRight(editor: Editor, caret: Caret) + + object Word : SelectionMode() { + override fun extendLeft(editor: Editor, caret: Caret) { + val text = editor.immutableText + val wordPart = when { + caret.selectionStart == 0 -> caret.selectionStart + text[caret.selectionStart - 1].isWordPart -> caret.selectionStart - 1 + else -> (caret.selectionStart - 1 downTo 0).find { text[it].isWordPart } ?: return + } + + caret.setSelection(caret.selectionEnd, AceTagAction.JumpToWordStart.getCaretOffset(editor, wordPart, wordPart, isInsideWord = true)) + } + + override fun extendRight(editor: Editor, caret: Caret) { + val text = editor.immutableText + val wordPart = when { + text[caret.selectionEnd].isWordPart -> caret.selectionEnd + else -> (caret.selectionEnd until text.length).find { text[it].isWordPart } ?: return + } + + caret.setSelection(caret.selectionStart, AceTagAction.JumpToWordEnd.getCaretOffset(editor, wordPart, wordPart, isInsideWord = true)) + } + } + + object Char : SelectionMode() { + override fun extendLeft(editor: Editor, caret: Caret) { + caret.setSelection((caret.selectionStart - 1).coerceAtLeast(0), caret.selectionEnd) + } + + override fun extendRight(editor: Editor, caret: Caret) { + caret.setSelection(caret.selectionStart, (caret.selectionEnd + 1).coerceAtMost(editor.immutableText.length)) + } + } + + object Line : SelectionMode() { + override fun extendLeft(editor: Editor, caret: Caret) { + val document = editor.document + val line = document.getLineNumber(caret.selectionStart) + val lineOffset = document.getLineStartOffset(line) + + if (caret.selectionStart > lineOffset) { + caret.setSelection(lineOffset, caret.selectionEnd) + } + else if (line - 1 >= 0) { + caret.setSelection(document.getLineStartOffset(line - 1), caret.selectionEnd) + } + } + + override fun extendRight(editor: Editor, caret: Caret) { + val document = editor.document + val line = document.getLineNumber(caret.selectionEnd) + val lineOffset = document.getLineEndOffset(line) + + if (caret.selectionEnd < lineOffset) { + caret.setSelection(caret.selectionStart, lineOffset) + } + else if (line + 1 < document.lineCount) { + caret.setSelection(caret.selectionStart, document.getLineEndOffset(line + 1)) + } + } + } + + object Space : SelectionMode() { + override fun extendLeft(editor: Editor, caret: Caret) { + var offset = caret.selectionStart + + while (offset > 0 && editor.immutableText[offset - 1].isWhitespace()) { + --offset + } + + caret.setSelection(offset, caret.selectionEnd) + } + + override fun extendRight(editor: Editor, caret: Caret) { + var offset = caret.selectionEnd + + while (offset < editor.immutableText.length && editor.immutableText[offset].isWhitespace()) { + ++offset + } + + caret.setSelection(caret.selectionStart, offset) + } + } + } +} diff --git a/src/main/kotlin/org/acejump/session/Session.kt b/src/main/kotlin/org/acejump/session/Session.kt index 2d280af..1f10e90 100644 --- a/src/main/kotlin/org/acejump/session/Session.kt +++ b/src/main/kotlin/org/acejump/session/Session.kt @@ -66,6 +66,7 @@ class Session(private val editor: Editor) { TypeResult.Nothing -> updateHint() TypeResult.RestartSearch -> restart().also { this@Session.state = SessionStateImpl(editor, tagger); updateHint() } is TypeResult.UpdateResults -> updateSearch(result.processor, markImmediately = hadTags) + is TypeResult.MoveHint -> { textHighlighter.reset(); acceptedTag = result.offset; updateHint() } is TypeResult.ChangeMode -> setMode(result.mode) TypeResult.EndSession -> end() } diff --git a/src/main/kotlin/org/acejump/session/TypeResult.kt b/src/main/kotlin/org/acejump/session/TypeResult.kt index a5beef9..57565bc 100644 --- a/src/main/kotlin/org/acejump/session/TypeResult.kt +++ b/src/main/kotlin/org/acejump/session/TypeResult.kt @@ -7,6 +7,7 @@ sealed class TypeResult { object Nothing : TypeResult() object RestartSearch : TypeResult() class UpdateResults(val processor: SearchProcessor) : TypeResult() + class MoveHint(val offset: Int) : TypeResult() class ChangeMode(val mode: SessionMode) : TypeResult() object EndSession : TypeResult() }