forked from acejump/AceJump
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
See acejump#348 for information on what's changed and what more needs to be done.
- Loading branch information
Showing
58 changed files
with
2,504 additions
and
3,014 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
[*] | ||
charset=utf-8 | ||
end_of_line=lf | ||
insert_final_newline=false | ||
indent_style=space | ||
indent_size=2 | ||
max_line_length=80 | ||
charset = utf-8 | ||
end_of_line = lf | ||
insert_final_newline = true | ||
indent_style = space | ||
indent_size = 2 | ||
max_line_length = 140 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package org.acejump | ||
|
||
import com.intellij.openapi.editor.Editor | ||
|
||
/** | ||
* Returns an immutable version of the currently edited document. | ||
*/ | ||
val Editor.immutableText | ||
get() = this.document.immutableCharSequence | ||
|
||
/** | ||
* Returns true if [this] contains [otherText] at the specified offset. | ||
*/ | ||
fun CharSequence.matchesAt(selfOffset: Int, otherText: String, ignoreCase: Boolean): Boolean { | ||
return this.regionMatches(selfOffset, otherText, 0, otherText.length, ignoreCase) | ||
} | ||
|
||
/** | ||
* Calculates the length of a common prefix in [this] starting at index [selfOffset], and [otherText] starting at index 0. | ||
*/ | ||
fun CharSequence.countMatchingCharacters(selfOffset: Int, otherText: String): Int { | ||
var i = 0 | ||
var o = selfOffset + i | ||
|
||
while (i < otherText.length && o < this.length && otherText[i].equals(this[o], ignoreCase = true)) { | ||
i++ | ||
o++ | ||
} | ||
|
||
return i | ||
} | ||
|
||
/** | ||
* Determines which characters form a "word" for the purposes of functions below. | ||
*/ | ||
val Char.isWordPart | ||
get() = this.isJavaIdentifierPart() | ||
|
||
/** | ||
* Finds index of the first character in a word. | ||
*/ | ||
inline fun CharSequence.wordStart(pos: Int, isPartOfWord: (Char) -> Boolean = Char::isWordPart): Int { | ||
var start = pos | ||
|
||
while (start > 0 && isPartOfWord(this[start - 1])) { | ||
--start | ||
} | ||
|
||
return start | ||
} | ||
|
||
/** | ||
* Finds index of the last character in a word. | ||
*/ | ||
inline fun CharSequence.wordEnd(pos: Int, isPartOfWord: (Char) -> Boolean = Char::isWordPart): Int { | ||
var end = pos | ||
|
||
while (end < length - 1 && isPartOfWord(this[end + 1])) { | ||
++end | ||
} | ||
|
||
return end | ||
} | ||
|
||
/** | ||
* Finds index of the first word character following a sequence of non-word characters following the end of a word. | ||
*/ | ||
inline fun CharSequence.wordEndPlus(pos: Int, isPartOfWord: (Char) -> Boolean = Char::isWordPart): Int { | ||
var end = this.wordEnd(pos, isPartOfWord) | ||
|
||
while (end < length - 1 && !isPartOfWord(this[end + 1])) { | ||
++end | ||
} | ||
|
||
if (end < length - 1 && isPartOfWord(this[end + 1])) { | ||
++end | ||
} | ||
|
||
return end | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package org.acejump.action | ||
|
||
import com.intellij.openapi.actionSystem.AnActionEvent | ||
import com.intellij.openapi.actionSystem.CommonDataKeys.EDITOR | ||
import com.intellij.openapi.project.DumbAwareAction | ||
import org.acejump.boundaries.Boundaries | ||
import org.acejump.boundaries.StandardBoundaries | ||
import org.acejump.input.JumpMode | ||
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]. | ||
*/ | ||
sealed class AceAction : DumbAwareAction() { | ||
final override fun update(action: AnActionEvent) { | ||
action.presentation.isEnabled = action.getData(EDITOR) != null | ||
} | ||
|
||
final override fun actionPerformed(e: AnActionEvent) { | ||
invoke(SessionManager.start(e.getData(EDITOR) ?: return)) | ||
} | ||
|
||
abstract operator fun invoke(session: Session) | ||
|
||
/** | ||
* Generic action type that toggles a specific [JumpMode]. | ||
*/ | ||
abstract class BaseToggleJumpModeAction(private val mode: JumpMode) : AceAction() { | ||
final override fun invoke(session: Session) = session.toggleJumpMode(mode) | ||
} | ||
|
||
/** | ||
* Generic action type that starts a regex search. | ||
*/ | ||
abstract class BaseRegexSearchAction(private val pattern: Pattern, private val boundaries: Boundaries) : AceAction() { | ||
override fun invoke(session: Session) = session.startRegexSearch(pattern, boundaries) | ||
} | ||
|
||
/** | ||
* Initiates an AceJump session in the first [JumpMode], or cycles to the next [JumpMode] as defined in configuration. | ||
*/ | ||
object ActivateOrCycleMode : AceAction() { | ||
override fun invoke(session: Session) = session.cycleJumpMode() | ||
} | ||
|
||
// @formatter:off | ||
|
||
object ToggleJumpMode : BaseToggleJumpModeAction(JumpMode.JUMP) | ||
object ToggleJumpEndMode : BaseToggleJumpModeAction(JumpMode.JUMP_END) | ||
object ToggleTargetMode : BaseToggleJumpModeAction(JumpMode.TARGET) | ||
object ToggleDeclarationMode : BaseToggleJumpModeAction(JumpMode.DEFINE) | ||
|
||
object ToggleAllLinesMode : BaseRegexSearchAction(Pattern.LINE_MARK, StandardBoundaries.WHOLE_FILE) | ||
object ToggleAllWordsMode : BaseRegexSearchAction(Pattern.ALL_WORDS, StandardBoundaries.VISIBLE_ON_SCREEN) | ||
object ToggleAllWordsForwardMode : BaseRegexSearchAction(Pattern.ALL_WORDS, StandardBoundaries.VISIBLE_ON_SCREEN_AFTER_CARET) | ||
object ToggleAllWordsBackwardsMode : BaseRegexSearchAction(Pattern.ALL_WORDS, StandardBoundaries.VISIBLE_ON_SCREEN_BEFORE_CARET) | ||
|
||
// @formatter:on | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package org.acejump.action | ||
|
||
import com.intellij.openapi.actionSystem.DataContext | ||
import com.intellij.openapi.editor.Caret | ||
import com.intellij.openapi.editor.Editor | ||
import com.intellij.openapi.editor.actionSystem.EditorActionHandler | ||
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 overrides of existing editor actions, that have a different meaning during an AceJump [Session]. | ||
*/ | ||
sealed class AceEditorAction(private val originalHandler: EditorActionHandler) : EditorActionHandler() { | ||
final override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean { | ||
return SessionManager[editor] != null || originalHandler.isEnabled(editor, caret, dataContext) | ||
} | ||
|
||
final override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) { | ||
val session = SessionManager[editor] | ||
|
||
if (session != null) { | ||
run(session) | ||
} | ||
else if (originalHandler.isEnabled(editor, caret, dataContext)) { | ||
originalHandler.execute(editor, caret, dataContext) | ||
} | ||
} | ||
|
||
protected abstract fun run(session: Session) | ||
|
||
// Actions | ||
|
||
class Reset(originalHandler: EditorActionHandler) : AceEditorAction(originalHandler) { | ||
override fun run(session: Session) = session.end() | ||
} | ||
|
||
class ClearSearch(originalHandler: EditorActionHandler) : AceEditorAction(originalHandler) { | ||
override fun run(session: Session) = session.restart() | ||
} | ||
|
||
class SelectBackward(originalHandler: EditorActionHandler) : AceEditorAction(originalHandler) { | ||
override fun run(session: Session) = session.visitPreviousTag() | ||
} | ||
|
||
class SelectForward(originalHandler: EditorActionHandler) : AceEditorAction(originalHandler) { | ||
override fun run(session: Session) = session.visitNextTag() | ||
} | ||
|
||
class SearchCodeIndents(originalHandler: EditorActionHandler) : AceEditorAction(originalHandler) { | ||
override fun run(session: Session) = session.startRegexSearch(Pattern.CODE_INDENTS, StandardBoundaries.WHOLE_FILE) | ||
} | ||
|
||
class SearchLineStarts(originalHandler: EditorActionHandler) : AceEditorAction(originalHandler) { | ||
override fun run(session: Session) = session.startRegexSearch(Pattern.START_OF_LINE, StandardBoundaries.WHOLE_FILE) | ||
} | ||
|
||
class SearchLineEnds(originalHandler: EditorActionHandler) : AceEditorAction(originalHandler) { | ||
override fun run(session: Session) = session.startRegexSearch(Pattern.END_OF_LINE, StandardBoundaries.WHOLE_FILE) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package org.acejump.action | ||
|
||
import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction | ||
import com.intellij.codeInsight.navigation.actions.GotoTypeDeclarationAction | ||
import com.intellij.openapi.actionSystem.ActionManager | ||
import com.intellij.openapi.actionSystem.AnAction | ||
import com.intellij.openapi.command.CommandProcessor | ||
import com.intellij.openapi.command.UndoConfirmationPolicy | ||
import com.intellij.openapi.editor.Document | ||
import com.intellij.openapi.editor.Editor | ||
import com.intellij.openapi.editor.actionSystem.DocCommandGroupId | ||
import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory | ||
import com.intellij.openapi.project.Project | ||
import com.intellij.openapi.ui.playback.commands.ActionCommand | ||
import org.acejump.* | ||
import org.acejump.input.JumpMode | ||
import org.acejump.input.JumpMode.* | ||
import org.acejump.search.SearchProcessor | ||
|
||
/** | ||
* Performs [JumpMode] navigation and actions. | ||
*/ | ||
internal class TagJumper(private val editor: Editor, private val mode: JumpMode, private val searchProcessor: SearchProcessor?) { | ||
/** | ||
* Moves caret to a specific offset in the editor according to the positioning and selection rules of the current [JumpMode]. | ||
*/ | ||
fun visit(offset: Int) { | ||
if (mode === JUMP_END || mode === TARGET) { | ||
val chars = editor.immutableText | ||
val matchingChars = searchProcessor?.let { chars.countMatchingCharacters(offset, it.query.rawText) } ?: 0 | ||
val targetOffset = offset + matchingChars | ||
val isInsideWord = matchingChars > 0 && chars[targetOffset - 1].isWordPart && chars[targetOffset].isWordPart | ||
val finalTargetOffset = if (isInsideWord) chars.wordEnd(targetOffset) + 1 else targetOffset | ||
|
||
if (mode === JUMP_END) { | ||
moveCaretTo(editor, finalTargetOffset) | ||
} | ||
else if (mode === TARGET) { | ||
if (isInsideWord) { | ||
selectRange(editor, chars.wordStart(targetOffset), finalTargetOffset) | ||
} | ||
else { | ||
selectRange(editor, offset, finalTargetOffset) | ||
} | ||
} | ||
} | ||
else { | ||
moveCaretTo(editor, offset) | ||
} | ||
} | ||
|
||
/** | ||
* Updates caret and selection by [visit]ing a specific offset in the editor, and applying session-finalizing [JumpMode] actions such as | ||
* using the Go To Declaration action, or selecting text between caret and target offset/word if Shift was held during the jump. | ||
*/ | ||
fun jump(offset: Int, shiftMode: Boolean) { | ||
val oldOffset = editor.caretModel.offset | ||
|
||
visit(offset) | ||
|
||
if (mode === DEFINE) { | ||
performAction(if (shiftMode) GotoTypeDeclarationAction() else GotoDeclarationAction()) | ||
return | ||
} | ||
|
||
if (shiftMode) { | ||
val newOffset = editor.caretModel.offset | ||
|
||
if (mode === TARGET) { | ||
selectRange(editor, oldOffset, when { | ||
newOffset < oldOffset -> editor.selectionModel.selectionStart | ||
else -> editor.selectionModel.selectionEnd | ||
}) | ||
} | ||
else { | ||
selectRange(editor, oldOffset, newOffset) | ||
} | ||
} | ||
} | ||
|
||
private companion object { | ||
private fun moveCaretTo(editor: Editor, offset: Int) = with(editor) { | ||
project?.let { addCurrentPositionToHistory(it, document) } | ||
selectionModel.removeSelection(true) | ||
caretModel.moveToOffset(offset) | ||
} | ||
|
||
private fun selectRange(editor: Editor, fromOffset: Int, toOffset: Int) = with(editor) { | ||
selectionModel.removeSelection(true) | ||
selectionModel.setSelection(fromOffset, toOffset) | ||
caretModel.moveToOffset(toOffset) | ||
} | ||
|
||
private fun addCurrentPositionToHistory(project: Project, document: Document) { | ||
CommandProcessor.getInstance().executeCommand(project, { | ||
with(IdeDocumentHistory.getInstance(project)) { | ||
setCurrentCommandHasMoves() | ||
includeCurrentCommandAsNavigation() | ||
includeCurrentPlaceAsChangePlace() | ||
} | ||
}, "AceJumpHistoryAppender", DocCommandGroupId.noneGroupId(document), UndoConfirmationPolicy.DO_NOT_REQUEST_CONFIRMATION, document) | ||
} | ||
|
||
private fun performAction(action: AnAction) { | ||
ActionManager.getInstance().tryToExecute(action, ActionCommand.getInputEvent(null), null, null, true) | ||
} | ||
} | ||
} |
Oops, something went wrong.