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

25 Commits

Author SHA1 Message Date
49dd8ddaf6 [WIP] Simplify tags by only tagging if the search query has at 2 or more alnum characters (or other symbols) 2021-01-16 01:19:05 +01:00
752e661bb7 [WIP] Make most actions always restore carets & tweak intention/refactor actions 2021-01-16 00:24:16 +01:00
a4eff1f5a6 [WIP] Add 'Clone/Move to Caret' actions and quick selection modes to 'Between Points' mode 2021-01-16 00:18:23 +01:00
0e4caafbba [WIP] Implement shift mode for 'From Caret' and 'Between Points' modes 2021-01-09 18:08:36 +01:00
bf2cb0b0a1 [WIP] Fix selection overlaps in shift mode (again) 2021-01-09 18:08:29 +01:00
af10e87811 [WIP] Add 'Between Points' cycle mode 2021-01-09 17:01:52 +01:00
8859498066 [WIP] Move 'Select to Caret' modes into an old style cycle-able AceJump mode 2021-01-09 17:01:51 +01:00
76b751274b [WIP] Remove tag visiting functionality 2021-01-09 14:38:51 +01:00
197ae268dc [WIP] Remove whole file search 2021-01-09 09:52:41 +01:00
ffbb3747b4 [WIP] Remove currently broken multi-caret mode 2021-01-04 22:56:13 +01:00
30a9ebb471 [WIP] Fix issue with caret/selection append modes 2021-01-04 22:46:28 +01:00
31cfbcc940 [WIP] Replace 'Caret to...' sub-menu with Shift modifier for selection-based modes 2021-01-04 22:24:38 +01:00
8891fd562a Fix occasional conflicts between tags and search query when assigning vacant results 2020-12-23 03:28:30 +01:00
d46cc80ccf [WIP] Make Shift mode add caret/selection to existing ones 2020-12-23 02:57:13 +01:00
3507aebed0 [WIP] Refactor new modes 2020-12-23 02:18:10 +01:00
2ef2d32f15 [WIP] Fix selection mode tooltip 2020-12-22 04:26:20 +01:00
5400910c7d [WIP] Add caret selection variants to all selection-based modes 2020-12-22 04:21:07 +01:00
f08286cd8d [WIP] Add cut/copy/paste modes 2020-12-22 04:19:22 +01:00
1ccab7cd32 [WIP] Add copy/paste modes 2020-12-21 20:14:31 +01:00
53e882e752 Remove unnecessary method 2020-12-21 19:41:47 +01:00
658c2062a7 [WIP] Add interactive modes for selection and deletion 2020-12-21 06:04:01 +01:00
402517c412 Add 'Refactor' action 2020-12-18 13:59:00 +01:00
5e1a6f47a1 Make selection jumps expand current selection 2020-12-14 13:32:46 +01:00
42232fa0f2 Add multi-caret session mode 2020-12-14 12:57:51 +01:00
56010a7128 Replace 'jump modes' with various actions activated by letters after typing a tag 2020-12-14 12:32:10 +01:00
23 changed files with 368 additions and 445 deletions

View File

@@ -43,4 +43,4 @@ intellij {
} }
group = "org.acejump" group = "org.acejump"
version = "chylex" version = "4.0"

View File

@@ -40,10 +40,6 @@ sealed class AceEditorAction(private val originalHandler: EditorActionHandler) :
override fun run(session: Session) = session.restart() override fun run(session: Session) = session.restart()
} }
class TagImmediately(originalHandler: EditorActionHandler) : AceEditorAction(originalHandler) {
override fun run(session: Session) = session.tagImmediately()
}
class SearchLineStarts(originalHandler: EditorActionHandler) : AceEditorAction(originalHandler) { class SearchLineStarts(originalHandler: EditorActionHandler) : AceEditorAction(originalHandler) {
override fun run(session: Session) = session.startRegexSearch(Pattern.LINE_STARTS, StandardBoundaries.VISIBLE_ON_SCREEN) override fun run(session: Session) = session.startRegexSearch(Pattern.LINE_STARTS, StandardBoundaries.VISIBLE_ON_SCREEN)
} }

View File

@@ -5,10 +5,12 @@ import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction
import com.intellij.codeInsight.navigation.actions.GotoTypeDeclarationAction import com.intellij.codeInsight.navigation.actions.GotoTypeDeclarationAction
import com.intellij.find.actions.FindUsagesAction import com.intellij.find.actions.FindUsagesAction
import com.intellij.find.actions.ShowUsagesAction import com.intellij.find.actions.ShowUsagesAction
import com.intellij.ide.actions.CopyAction
import com.intellij.ide.actions.CutAction
import com.intellij.ide.actions.PasteAction
import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.IdeActions 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.command.WriteCommandAction
@@ -17,15 +19,11 @@ 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.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.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.ui.playback.commands.ActionCommand
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.refactoring.actions.RefactoringQuickListPopupAction import com.intellij.refactoring.actions.RefactoringQuickListPopupAction
import com.intellij.refactoring.actions.RenameElementAction
import org.acejump.* import org.acejump.*
import org.acejump.search.SearchProcessor import org.acejump.search.SearchProcessor
import kotlin.math.max import kotlin.math.max
@@ -52,18 +50,6 @@ 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() { abstract class BaseSelectAction : AceTagAction() {
final override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) { final override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) {
if (shiftMode) { if (shiftMode) {
@@ -91,50 +77,26 @@ sealed class AceTagAction {
protected abstract operator fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) protected abstract operator fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int)
} }
abstract class BasePerCaretWriteAction(private val selector: AceTagAction) : AceTagAction() { abstract class BaseWordAction : BaseJumpAction() {
final override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) { final override fun getCaretOffset(editor: Editor, searchProcessor: SearchProcessor, offset: Int): Int {
val oldCarets = editor.caretModel.caretsAndSelections val matchingChars = countMatchingCharacters(editor, searchProcessor, offset)
selector(editor, searchProcessor, offset, shiftMode = false) val targetOffset = offset + matchingChars
val range = editor.selectionModel.let { TextRange(it.selectionStart, it.selectionEnd) } val isInsideWord = matchingChars > 0 && editor.immutableText.let { it[targetOffset - 1].isWordPart && it[targetOffset].isWordPart }
editor.caretModel.caretsAndSelections = oldCarets return getCaretOffset(editor, offset, targetOffset, isInsideWord)
invoke(editor, range, shiftMode)
} }
protected abstract operator fun invoke(editor: Editor, range: TextRange, shiftMode: Boolean) abstract fun getCaretOffset(editor: Editor, queryStartOffset: Int, queryEndOffset: Int, isInsideWord: Boolean): Int
}
protected fun insertAtCarets(editor: Editor, text: String) { abstract class BaseCaretRestoringAction : AceTagAction() {
val document = editor.document override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) {
val oldCarets = editor.caretModel.caretsAndSelections
editor.caretModel.runForEachCaret { doInvoke(editor, searchProcessor, offset, shiftMode)
if (it.hasSelection()) { editor.caretModel.caretsAndSelections = oldCarets
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))
}
} }
protected abstract fun doInvoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean)
} }
private companion object { private companion object {
@@ -212,7 +174,7 @@ sealed class AceTagAction {
* *
* On shift action, adds the new caret to existing carets. * On shift action, adds the new caret to existing carets.
*/ */
object JumpToWordStart : BaseWordAction() { object JumpToWordStartTag : BaseWordAction() {
override fun getCaretOffset(editor: Editor, queryStartOffset: Int, queryEndOffset: Int, isInsideWord: Boolean): Int { override fun getCaretOffset(editor: Editor, queryStartOffset: Int, queryEndOffset: Int, isInsideWord: Boolean): Int {
return if (isInsideWord) return if (isInsideWord)
editor.immutableText.wordStart(queryEndOffset) editor.immutableText.wordStart(queryEndOffset)
@@ -228,7 +190,7 @@ sealed class AceTagAction {
* *
* On shift action, adds the new caret to existing carets. * On shift action, adds the new caret to existing carets.
*/ */
object JumpToWordEnd : BaseWordAction() { object JumpToWordEndTag : BaseWordAction() {
override fun getCaretOffset(editor: Editor, queryStartOffset: Int, queryEndOffset: Int, isInsideWord: Boolean): Int { override fun getCaretOffset(editor: Editor, queryStartOffset: Int, queryEndOffset: Int, isInsideWord: Boolean): Int {
return if (isInsideWord) return if (isInsideWord)
editor.immutableText.wordEnd(queryEndOffset) + 1 editor.immutableText.wordEnd(queryEndOffset) + 1
@@ -237,22 +199,6 @@ sealed class AceTagAction {
} }
} }
/**
* 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() { object SelectQuery : BaseSelectAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) { override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) {
recordCaretPosition(editor) recordCaretPosition(editor)
@@ -267,7 +213,7 @@ sealed class AceTagAction {
/** /**
* On default action, places the caret at the end of a word, and also selects the entire word. Word detection uses * 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 * [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 * first 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. * start and end of the search query is selected.
* *
* On shift action, adds the new selection to existing selections. * On shift action, adds the new selection to existing selections.
@@ -280,8 +226,8 @@ sealed class AceTagAction {
if (chars[queryEndOffset].isWordPart) { if (chars[queryEndOffset].isWordPart) {
recordCaretPosition(editor) recordCaretPosition(editor)
val startOffset = JumpToWordStart.getCaretOffset(editor, offset, queryEndOffset, isInsideWord = true) val startOffset = JumpToWordStartTag.getCaretOffset(editor, offset, queryEndOffset, isInsideWord = true)
val endOffset = JumpToWordEnd.getCaretOffset(editor, offset, queryEndOffset, isInsideWord = true) val endOffset = JumpToWordEndTag.getCaretOffset(editor, offset, queryEndOffset, isInsideWord = true)
selectRange(editor, startOffset, endOffset) selectRange(editor, startOffset, endOffset)
} }
@@ -291,12 +237,6 @@ sealed class AceTagAction {
} }
} }
/**
* 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() { object SelectHump : BaseSelectAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) { override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) {
val chars = editor.immutableText val chars = editor.immutableText
@@ -316,59 +256,13 @@ sealed class AceTagAction {
} }
} }
/**
* On default action, selects a word according to [SelectWord], and then extends the selection in either or both directions based
* on the characters around the word. TODO
*
* On shift action, adds the new selection to existing selections.
*/
object SelectAroundWord : BaseSelectAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) {
SelectWord(editor, searchProcessor, offset, shiftMode = false)
val text = editor.immutableText
var selectionStart = editor.selectionModel.selectionStart
var selectionEnd = editor.selectionModel.selectionEnd
val indentStart = EditorActionUtil.findFirstNonSpaceOffsetOnTheLine(editor.document, editor.caretModel.logicalPosition.line)
while (selectionStart > 0 && selectionStart > indentStart && text[selectionStart - 1].let { it == ' ' || it == ',' }) {
--selectionStart
}
while (selectionEnd < text.length && text[selectionEnd].let { it == ' ' || it == ',' }) {
++selectionEnd
}
if (selectionStart > 0 && text[selectionStart - 1] == '!') {
--selectionStart
}
selectRange(editor, selectionStart, selectionEnd)
}
}
/**
* 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() { object SelectLine : BaseSelectAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) { override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) {
JumpToSearchEnd(editor, searchProcessor, offset, shiftMode = false) JumpToSearchEnd(editor, searchProcessor, offset, shiftMode = false)
editor.selectionModel.selectLineAtCaret()
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() { class SelectExtended(private val extendCount: Int) : BaseSelectAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) { override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) {
JumpToSearchEnd(editor, searchProcessor, offset, shiftMode = false) JumpToSearchEnd(editor, searchProcessor, offset, shiftMode = false)
@@ -381,10 +275,6 @@ sealed class AceTagAction {
} }
} }
/**
* 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() { class SelectToCaret(private val jumper: BaseJumpAction) : BaseSelectAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) { override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) {
val caretModel = editor.caretModel val caretModel = editor.caretModel
@@ -404,10 +294,6 @@ sealed class AceTagAction {
} }
} }
/**
* 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() { class SelectBetweenPoints(private val firstOffset: Int, private val secondOffsetJumper: BaseJumpAction) : BaseSelectAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) { override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) {
secondOffsetJumper(editor, searchProcessor, offset, shiftMode = false) secondOffsetJumper(editor, searchProcessor, offset, shiftMode = false)
@@ -415,59 +301,80 @@ sealed class AceTagAction {
} }
} }
/** class Cut(private val selector: AceTagAction) : BaseCaretRestoringAction() {
* On default action, selects text based on the provided [selector] action, and deletes it without moving the existing carets. override fun doInvoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) {
* On shift action, moves caret to the position where deletion occurred. selector(editor, searchProcessor, offset, shiftMode = false)
*/ performAction(CutAction())
class Delete(private val selector: AceTagAction) : AceTagAction() { }
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) { }
val oldCarets = editor.caretModel.caretsAndSelections
class Copy(private val selector: AceTagAction) : BaseCaretRestoringAction() {
override fun doInvoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) {
selector(editor, searchProcessor, offset, shiftMode = false)
performAction(CopyAction())
}
}
class Paste(private val selector: AceTagAction) : BaseCaretRestoringAction() {
override fun doInvoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) {
selector(editor, searchProcessor, offset, shiftMode = false)
performAction(PasteAction())
}
}
class Delete(private val selector: AceTagAction) : BaseCaretRestoringAction() {
override fun doInvoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) {
selector(editor, searchProcessor, offset, shiftMode = false) selector(editor, searchProcessor, offset, shiftMode = false)
WriteCommandAction.writeCommandAction(editor.project).withName("AceJump Delete").run<Throwable> { WriteCommandAction.writeCommandAction(editor.project).withName("AceJump Delete").run<Throwable> {
editor.selectionModel.let { editor.document.deleteString(it.selectionStart, it.selectionEnd) } editor.selectionModel.let { editor.document.deleteString(it.selectionStart, it.selectionEnd) }
} }
if (!shiftMode) {
editor.caretModel.caretsAndSelections = oldCarets
}
} }
} }
/** class CloneToCaret(private val selector: AceTagAction) : AceTagAction() {
* Selects text based on the provided [selector] action and clones it at every existing caret, selecting the cloned text. If a caret override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) {
* has a selection, the selected text will be replaced. val document = editor.document
*/ val oldCarets = editor.caretModel.caretsAndSelections
class CloneToCaret(selector: AceTagAction) : BasePerCaretWriteAction(selector) {
override fun invoke(editor: Editor, range: TextRange, shiftMode: Boolean) { selector(editor, searchProcessor, offset, shiftMode = false)
val text = document.getText(editor.selectionModel.let { TextRange(it.selectionStart, it.selectionEnd) })
editor.caretModel.caretsAndSelections = oldCarets
WriteCommandAction.writeCommandAction(editor.project).withName("AceJump Clone").run<Throwable> { 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) insertAtCarets(editor, text)
} }
}
if (shiftMode) {
editor.selectionModel.removeSelection(true) companion object {
editor.caretModel.moveToOffset(range.startOffset + difference) fun insertAtCarets(editor: Editor, text: String) {
val document = editor.document
editor.caretModel.runForEachCaret {
if (it.hasSelection()) {
document.replaceString(it.selectionStart, it.selectionEnd, text)
}
else {
document.insertString(it.offset, text)
}
}
}
}
}
class MoveToCaret(private val selector: AceTagAction) : AceTagAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) {
val document = editor.document
val oldCarets = editor.caretModel.caretsAndSelections
selector(editor, searchProcessor, offset, shiftMode = false)
val start = editor.selectionModel.selectionStart
val end = editor.selectionModel.selectionEnd
val text = document.getText(TextRange(start, end))
editor.caretModel.caretsAndSelections = oldCarets
WriteCommandAction.writeCommandAction(editor.project).withName("AceJump Move").run<Throwable> {
document.deleteString(start, end)
CloneToCaret.insertAtCarets(editor, text)
} }
} }
} }
@@ -475,11 +382,11 @@ sealed class AceTagAction {
/** /**
* On default action, performs the Go To Declaration action, available via `Navigate | Declaration or Usages`. * 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`. * 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. * Always places the caret at the end of the search query.
*/ */
object GoToDeclaration : AceTagAction() { object GoToDeclaration : BaseCaretRestoringAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) { override fun doInvoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) {
JumpToWordStart(editor, searchProcessor, offset, shiftMode = false) JumpToSearchEnd(editor, searchProcessor, offset, shiftMode = false)
performAction(if (shiftMode) GotoTypeDeclarationAction() else GotoDeclarationAction()) performAction(if (shiftMode) GotoTypeDeclarationAction() else GotoDeclarationAction())
} }
} }
@@ -487,40 +394,29 @@ sealed class AceTagAction {
/** /**
* On default action, performs the Show Usages action, available via the context menu. * 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. * On shift action, performs the Find Usages action, available via the context menu.
* Always places the caret at the start of the word. * Always places the caret at the end of the search query.
*/ */
object ShowUsages : AceTagAction() { object ShowUsages : BaseCaretRestoringAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) { override fun doInvoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) {
JumpToWordStart(editor, searchProcessor, offset, shiftMode = false) JumpToSearchEnd(editor, searchProcessor, offset, shiftMode = false)
performAction(if (shiftMode) FindUsagesAction() else ShowUsagesAction()) performAction(if (shiftMode) FindUsagesAction() else ShowUsagesAction())
} }
} }
/** /**
* Performs the Show Context Actions action, available via the context menu or Alt+Enter. * 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() { object ShowIntentions : AceTagAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) { override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) {
JumpToWordStart(editor, searchProcessor, offset, shiftMode = false) JumpToWordStartTag(editor, searchProcessor, offset, shiftMode = false)
performAction(ShowIntentionActionsAction()) performAction(ShowIntentionActionsAction())
} }
} }
/**
* 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() { object Refactor : AceTagAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) { override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) {
JumpToWordStart(editor, searchProcessor, offset, shiftMode = false) JumpToWordStartTag(editor, searchProcessor, offset, shiftMode = false)
if (shiftMode) { performAction(RefactoringQuickListPopupAction())
ApplicationManager.getApplication().invokeLater { performAction(RenameElementAction()) }
}
else {
performAction(RefactoringQuickListPopupAction())
}
} }
} }
} }

View File

@@ -19,7 +19,6 @@ class AceConfig : PersistentStateComponent<AceSettings> {
get() = ServiceManager.getService(AceConfig::class.java).aceSettings get() = ServiceManager.getService(AceConfig::class.java).aceSettings
val layout get() = settings.layout val layout get() = settings.layout
val minQueryLength get() = settings.minQueryLength
val jumpModeColor get() = settings.jumpModeColor val jumpModeColor get() = settings.jumpModeColor
val fromCaretModeColor get() = settings.fromCaretModeColor val fromCaretModeColor get() = settings.fromCaretModeColor
val betweenPointsModeColor get() = settings.betweenPointsModeColor val betweenPointsModeColor get() = settings.betweenPointsModeColor
@@ -27,6 +26,7 @@ class AceConfig : PersistentStateComponent<AceSettings> {
val tagForegroundColor get() = settings.tagForegroundColor val tagForegroundColor get() = settings.tagForegroundColor
val tagBackgroundColor get() = settings.tagBackgroundColor val tagBackgroundColor get() = settings.tagBackgroundColor
val acceptedTagColor get() = settings.acceptedTagColor val acceptedTagColor get() = settings.acceptedTagColor
val roundedTagCorners get() = settings.roundedTagCorners
} }
override fun getState(): AceSettings { override fun getState(): AceSettings {

View File

@@ -14,19 +14,18 @@ class AceConfigurable : Configurable {
override fun isModified() = override fun isModified() =
panel.allowedChars != settings.allowedChars || panel.allowedChars != settings.allowedChars ||
panel.keyboardLayout != settings.layout || panel.keyboardLayout != settings.layout ||
panel.minQueryLengthInt != settings.minQueryLength ||
panel.jumpModeColor != settings.jumpModeColor || panel.jumpModeColor != settings.jumpModeColor ||
panel.fromCaretModeColor != settings.fromCaretModeColor || panel.fromCaretModeColor != settings.fromCaretModeColor ||
panel.betweenPointsModeColor != settings.betweenPointsModeColor || 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 ||
panel.acceptedTagColor != settings.acceptedTagColor panel.acceptedTagColor != settings.acceptedTagColor ||
panel.roundedTagCorners != settings.roundedTagCorners
override fun apply() { override fun apply() {
settings.allowedChars = panel.allowedChars settings.allowedChars = panel.allowedChars
settings.layout = panel.keyboardLayout settings.layout = panel.keyboardLayout
settings.minQueryLength = panel.minQueryLengthInt ?: settings.minQueryLength
panel.jumpModeColor?.let { settings.jumpModeColor = it } panel.jumpModeColor?.let { settings.jumpModeColor = it }
panel.fromCaretModeColor?.let { settings.fromCaretModeColor = it } panel.fromCaretModeColor?.let { settings.fromCaretModeColor = it }
panel.betweenPointsModeColor?.let { settings.betweenPointsModeColor = it } panel.betweenPointsModeColor?.let { settings.betweenPointsModeColor = it }
@@ -34,6 +33,7 @@ class AceConfigurable : Configurable {
panel.tagForegroundColor?.let { settings.tagForegroundColor = it } panel.tagForegroundColor?.let { settings.tagForegroundColor = it }
panel.tagBackgroundColor?.let { settings.tagBackgroundColor = it } panel.tagBackgroundColor?.let { settings.tagBackgroundColor = it }
panel.acceptedTagColor?.let { settings.acceptedTagColor = it } panel.acceptedTagColor?.let { settings.acceptedTagColor = it }
settings.roundedTagCorners = panel.roundedTagCorners
KeyLayoutCache.reset(settings) KeyLayoutCache.reset(settings)
} }

View File

@@ -8,26 +8,27 @@ import java.awt.Color
data class AceSettings( data class AceSettings(
var layout: KeyLayout = QWERTY, var layout: KeyLayout = QWERTY,
var allowedChars: String = layout.allChars, var allowedChars: String = layout.allChars,
var minQueryLength: Int = 1,
@OptionTag("jumpModeRGB", converter = ColorConverter::class) @OptionTag("jumpModeRGB", converter = ColorConverter::class)
var jumpModeColor: Color = Color(0xFFFFFF), var jumpModeColor: Color = Color.BLUE,
@OptionTag("fromCaretModeRGB", converter = ColorConverter::class) @OptionTag("fromCaretModeRGB", converter = ColorConverter::class)
var fromCaretModeColor: Color = Color(0xFFB700), var fromCaretModeColor: Color = Color.ORANGE,
@OptionTag("betweenPointsModeRGB", converter = ColorConverter::class) @OptionTag("betweenPointsModeRGB", converter = ColorConverter::class)
var betweenPointsModeColor: Color = Color(0x6FC5FF), var betweenPointsModeColor: Color = Color.YELLOW,
@OptionTag("textHighlightRGB", converter = ColorConverter::class) @OptionTag("textHighlightRGB", converter = ColorConverter::class)
var textHighlightColor: Color = Color(0x394B58), var textHighlightColor: Color = Color.GREEN,
@OptionTag("tagForegroundRGB", converter = ColorConverter::class) @OptionTag("tagForegroundRGB", converter = ColorConverter::class)
var tagForegroundColor: Color = Color(0xFFFFFF), var tagForegroundColor: Color = Color.BLACK,
@OptionTag("tagBackgroundRGB", converter = ColorConverter::class) @OptionTag("tagBackgroundRGB", converter = ColorConverter::class)
var tagBackgroundColor: Color = Color(0x008299), var tagBackgroundColor: Color = Color.YELLOW,
@OptionTag("acceptedTagRGB", converter = ColorConverter::class) @OptionTag("acceptedTagRGB", converter = ColorConverter::class)
var acceptedTagColor: Color = Color(0x394B58) var acceptedTagColor: Color = Color.CYAN,
var roundedTagCorners: Boolean = true
) )

View File

@@ -2,6 +2,7 @@ package org.acejump.config
import com.intellij.openapi.ui.ComboBox import com.intellij.openapi.ui.ComboBox
import com.intellij.ui.ColorPanel import com.intellij.ui.ColorPanel
import com.intellij.ui.components.JBCheckBox
import com.intellij.ui.components.JBTextArea import com.intellij.ui.components.JBTextArea
import com.intellij.ui.components.JBTextField import com.intellij.ui.components.JBTextField
import com.intellij.ui.layout.Cell import com.intellij.ui.layout.Cell
@@ -25,7 +26,6 @@ internal class AceSettingsPanel {
private val tagCharsField = JBTextField() private val tagCharsField = JBTextField()
private val keyboardLayoutCombo = ComboBox<KeyLayout>() private val keyboardLayoutCombo = ComboBox<KeyLayout>()
private val keyboardLayoutArea = JBTextArea().apply { isEditable = false } private val keyboardLayoutArea = JBTextArea().apply { isEditable = false }
private val minQueryLengthField = JBTextField()
private val jumpModeColorWheel = ColorPanel() private val jumpModeColorWheel = ColorPanel()
private val fromCaretModeColorWheel = ColorPanel() private val fromCaretModeColorWheel = ColorPanel()
private val betweenPointsModeColorWheel = ColorPanel() private val betweenPointsModeColorWheel = ColorPanel()
@@ -33,6 +33,7 @@ internal class AceSettingsPanel {
private val tagForegroundColorWheel = ColorPanel() private val tagForegroundColorWheel = ColorPanel()
private val tagBackgroundColorWheel = ColorPanel() private val tagBackgroundColorWheel = ColorPanel()
private val acceptedTagColorWheel = ColorPanel() private val acceptedTagColorWheel = ColorPanel()
private val roundedTagCornersCheckBox = JBCheckBox()
init { init {
tagCharsField.apply { font = Font("monospaced", font.style, font.size) } tagCharsField.apply { font = Font("monospaced", font.style, font.size) }
@@ -50,10 +51,6 @@ internal class AceSettingsPanel {
row("Keyboard design:") { short(keyboardLayoutArea) } row("Keyboard design:") { short(keyboardLayoutArea) }
} }
titledRow("Behavior") {
row("Minimum typed characters (1-10):") { short(minQueryLengthField) }
}
titledRow("Colors") { titledRow("Colors") {
row("Jump mode caret background:") { short(jumpModeColorWheel) } row("Jump mode caret background:") { short(jumpModeColorWheel) }
row("From Caret mode caret background:") { short(fromCaretModeColorWheel) } row("From Caret mode caret background:") { short(fromCaretModeColorWheel) }
@@ -63,13 +60,16 @@ internal class AceSettingsPanel {
row("Tag background:") { short(tagBackgroundColorWheel) } row("Tag background:") { short(tagBackgroundColorWheel) }
row("Accepted tag position background:") { short(acceptedTagColorWheel) } row("Accepted tag position background:") { short(acceptedTagColorWheel) }
} }
titledRow("Appearance") {
row { short(roundedTagCornersCheckBox.apply { text = "Rounded tag corners" }) }
}
} }
// Property-to-property delegation: https://stackoverflow.com/q/45074596/1772342 // Property-to-property delegation: https://stackoverflow.com/q/45074596/1772342
internal var allowedChars by tagCharsField internal var allowedChars by tagCharsField
internal var keyboardLayout by keyboardLayoutCombo internal var keyboardLayout by keyboardLayoutCombo
internal var keyChars by keyboardLayoutArea internal var keyChars by keyboardLayoutArea
internal var minQueryLength by minQueryLengthField
internal var jumpModeColor by jumpModeColorWheel internal var jumpModeColor by jumpModeColorWheel
internal var fromCaretModeColor by fromCaretModeColorWheel internal var fromCaretModeColor by fromCaretModeColorWheel
internal var betweenPointsModeColor by betweenPointsModeColorWheel internal var betweenPointsModeColor by betweenPointsModeColorWheel
@@ -77,15 +77,11 @@ internal class AceSettingsPanel {
internal var tagForegroundColor by tagForegroundColorWheel internal var tagForegroundColor by tagForegroundColorWheel
internal var tagBackgroundColor by tagBackgroundColorWheel internal var tagBackgroundColor by tagBackgroundColorWheel
internal var acceptedTagColor by acceptedTagColorWheel internal var acceptedTagColor by acceptedTagColorWheel
internal var roundedTagCorners by roundedTagCornersCheckBox
internal var minQueryLengthInt
get() = minQueryLength.toIntOrNull()?.coerceIn(1, 10)
set(value) { minQueryLength = value.toString() }
fun reset(settings: AceSettings) { fun reset(settings: AceSettings) {
allowedChars = settings.allowedChars allowedChars = settings.allowedChars
keyboardLayout = settings.layout keyboardLayout = settings.layout
minQueryLength = settings.minQueryLength.toString()
jumpModeColor = settings.jumpModeColor jumpModeColor = settings.jumpModeColor
fromCaretModeColor = settings.fromCaretModeColor fromCaretModeColor = settings.fromCaretModeColor
betweenPointsModeColor = settings.betweenPointsModeColor betweenPointsModeColor = settings.betweenPointsModeColor
@@ -93,6 +89,7 @@ internal class AceSettingsPanel {
tagForegroundColor = settings.tagForegroundColor tagForegroundColor = settings.tagForegroundColor
tagBackgroundColor = settings.tagBackgroundColor tagBackgroundColor = settings.tagBackgroundColor
acceptedTagColor = settings.acceptedTagColor acceptedTagColor = settings.acceptedTagColor
roundedTagCorners = settings.roundedTagCorners
} }
// Removal pending support for https://youtrack.jetbrains.com/issue/KT-8575 // Removal pending support for https://youtrack.jetbrains.com/issue/KT-8575

View File

@@ -3,23 +3,31 @@ package org.acejump.modes
import com.intellij.openapi.editor.CaretState import com.intellij.openapi.editor.CaretState
import org.acejump.action.AceTagAction import org.acejump.action.AceTagAction
import org.acejump.config.AceConfig import org.acejump.config.AceConfig
import org.acejump.session.SessionMode
import org.acejump.session.SessionState import org.acejump.session.SessionState
import org.acejump.session.TypeResult import org.acejump.session.TypeResult
class BetweenPointsMode : SessionMode { class BetweenPointsMode : SessionMode {
private companion object { private companion object {
private val TYPE_TAG_HINT = arrayOf( private val HINT_TYPE_TAG = arrayOf(
"<b>Type to Search...</b>" "<b>Type to Search...</b>"
) )
private val ACTION_MODE_HINT = arrayOf( private val HINT_ACTION_MODE = arrayOf(
"<f>[S]</f>elect... / <f>[F]</f>rom Caret...", "<h>Between Points Mode</h>",
"<f>[D]</f>elete...", "<f>[S]</f>elect... / <f>[D]</f>elete...",
"<f>[C]</f>lone to Caret...", "<f>[C]</f>lone to Caret...",
"<f>[M]</f>ove to Caret..." "<f>[M]</f>ove to Caret..."
) )
private const val ACTION_MODE_FROM_CARET = 'F' private val HINT_JUMP_MODE = arrayOf(
"<f>[J]</f> at Tag / <f>[L]</f> past Query",
"Word <f>[S]</f>tart / Word <f>[E]</f>nd"
)
private val HINT_JUMP_OR_SELECT_MODE = HINT_JUMP_MODE + arrayOf(
"Select <f>[W]</f>ord / <f>[H]</f>ump / <f>[Q]</f>uery / <f>[1-9]</f> Expansion"
)
private val ACTION_MODE_MAP = mapOf( private val ACTION_MODE_MAP = mapOf(
'S' to ({ action: AceTagAction.BaseSelectAction -> action }), 'S' to ({ action: AceTagAction.BaseSelectAction -> action }),
@@ -27,6 +35,20 @@ class BetweenPointsMode : SessionMode {
'C' to (AceTagAction::CloneToCaret), 'C' to (AceTagAction::CloneToCaret),
'M' to (AceTagAction::MoveToCaret) 'M' to (AceTagAction::MoveToCaret)
) )
private val JUMP_MODE_MAP = mapOf(
'J' to AceTagAction.JumpToSearchStart,
'L' to AceTagAction.JumpPastSearchEnd,
'S' to AceTagAction.JumpToWordStartTag,
'E' to AceTagAction.JumpToWordEndTag
)
private val SELECTION_MODE_MAP = mapOf(
'W' to AceTagAction.SelectWord,
'H' to AceTagAction.SelectHump,
'Q' to AceTagAction.SelectQuery,
*('1'..'9').mapIndexed { index, char -> char to AceTagAction.SelectExtended(index + 1) }.toTypedArray()
)
} }
override val caretColor override val caretColor
@@ -39,10 +61,6 @@ class BetweenPointsMode : SessionMode {
override fun type(state: SessionState, charTyped: Char, acceptedTag: Int?): TypeResult { override fun type(state: SessionState, charTyped: Char, acceptedTag: Int?): TypeResult {
val actionMode = actionMode val actionMode = actionMode
if (actionMode == null) { if (actionMode == null) {
if (charTyped.equals(ACTION_MODE_FROM_CARET, ignoreCase = true)) {
return TypeResult.ChangeMode(SelectFromCaretMode())
}
this.actionMode = ACTION_MODE_MAP[charTyped.toUpperCase()] this.actionMode = ACTION_MODE_MAP[charTyped.toUpperCase()]
return TypeResult.Nothing return TypeResult.Nothing
} }
@@ -52,18 +70,18 @@ class BetweenPointsMode : SessionMode {
} }
if (firstOffset == null) { if (firstOffset == null) {
val selectAction = JumpMode.SELECT_ACTION_MAP[charTyped.toUpperCase()] val selectAction = SELECTION_MODE_MAP[charTyped.toUpperCase()]
if (selectAction != null) { if (selectAction != null) {
state.act(actionMode(selectAction), acceptedTag, shiftMode = charTyped.isUpperCase()) state.act(actionMode(selectAction), acceptedTag, shiftMode = charTyped.isUpperCase())
return TypeResult.EndSession return TypeResult.EndSession
} }
} }
val jumpAction = JumpMode.JUMP_ACTION_MAP[charTyped.toUpperCase()] val jumpAction = JUMP_MODE_MAP[charTyped.toUpperCase()]
if (jumpAction == null) { if (jumpAction == null) {
return TypeResult.Nothing return TypeResult.Nothing
} }
val firstOffset = firstOffset val firstOffset = firstOffset
if (firstOffset == null) { if (firstOffset == null) {
val caretModel = state.editor.caretModel val caretModel = state.editor.caretModel
@@ -80,12 +98,12 @@ class BetweenPointsMode : SessionMode {
return TypeResult.EndSession return TypeResult.EndSession
} }
override fun getHint(acceptedTag: Int?, hasQuery: Boolean): Array<String>? { override fun getHint(acceptedTag: Int?): Array<String> {
return when { return when {
actionMode == null -> ACTION_MODE_HINT actionMode == null -> HINT_ACTION_MODE
acceptedTag == null -> TYPE_TAG_HINT.takeUnless { hasQuery } acceptedTag == null -> HINT_TYPE_TAG
firstOffset == null -> JumpMode.JUMP_ALT_HINT + JumpMode.SELECT_HINT firstOffset == null -> HINT_JUMP_OR_SELECT_MODE
else -> JumpMode.JUMP_ALT_HINT else -> HINT_JUMP_MODE
} }
} }
} }

View File

@@ -0,0 +1,74 @@
package org.acejump.modes
import org.acejump.action.AceTagAction
import org.acejump.config.AceConfig
import org.acejump.session.SessionMode
import org.acejump.session.SessionState
import org.acejump.session.TypeResult
class FromCaretMode : SessionMode {
private companion object {
private val HINT_TYPE_TAG = arrayOf(
"<b>Type to Search...</b>"
)
private val HINT_ACTION_MODE = arrayOf(
"<h>From Caret Mode</h>",
"<f>[S]</f>elect... / <f>[D]</f>elete...",
"<f>[X]</f> Cut... / <f>[C]</f>opy... / <f>[P]</f>aste..."
)
private val HINT_JUMP_MODE = arrayOf(
"<f>[J]</f> at Tag / <f>[L]</f> past Query",
"Word <f>[S]</f>tart / Word <f>[E]</f>nd"
)
private val ACTION_MODE_MAP = mapOf(
'S' to ({ action: AceTagAction.SelectToCaret -> action }),
'D' to (AceTagAction::Delete),
'X' to (AceTagAction::Cut),
'C' to (AceTagAction::Copy),
'P' to (AceTagAction::Paste)
)
private val JUMP_MODE_MAP = mapOf(
'J' to AceTagAction.JumpToSearchStart,
'L' to AceTagAction.JumpPastSearchEnd,
'S' to AceTagAction.JumpToWordStartTag,
'E' to AceTagAction.JumpToWordEndTag
)
}
override val caretColor
get() = AceConfig.fromCaretModeColor
private var actionMode: ((AceTagAction.SelectToCaret) -> AceTagAction)? = null
override fun type(state: SessionState, charTyped: Char, acceptedTag: Int?): TypeResult {
val actionMode = actionMode
if (actionMode == null) {
this.actionMode = ACTION_MODE_MAP[charTyped.toUpperCase()]
return TypeResult.Nothing
}
if (acceptedTag == null) {
return state.type(charTyped)
}
val jumpAction = JUMP_MODE_MAP[charTyped.toUpperCase()]
if (jumpAction == null) {
return TypeResult.Nothing
}
state.act(actionMode(AceTagAction.SelectToCaret(jumpAction)), acceptedTag, shiftMode = charTyped.isUpperCase())
return TypeResult.EndSession
}
override fun getHint(acceptedTag: Int?): Array<String>? {
return when {
actionMode == null -> HINT_ACTION_MODE
acceptedTag == null -> HINT_TYPE_TAG
else -> HINT_JUMP_MODE
}
}
}

View File

@@ -2,65 +2,46 @@ package org.acejump.modes
import org.acejump.action.AceTagAction import org.acejump.action.AceTagAction
import org.acejump.config.AceConfig import org.acejump.config.AceConfig
import org.acejump.session.SessionMode
import org.acejump.session.SessionState import org.acejump.session.SessionState
import org.acejump.session.TypeResult import org.acejump.session.TypeResult
class JumpMode : SessionMode { class JumpMode : SessionMode {
companion object { private companion object {
private val JUMP_HINT = arrayOf( private val HINT_ACTIONS = arrayOf(
"<f>[J]</f>ump / <f>[L]</f> past Query", "<f>[J]</f>ump to Tag / <f>[L]</f> past Query",
"<f>[E]</f> Word End / <f>[M]</f> Line End" "Word <f>[S]</f>tart / <f>[E]</f>nd",
) "Select <f>[W]</f>ord / <f>[H]</f>ump / <f>[Q]</f>uery / <f>[1-9]</f> Expansion",
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 / <f>[A]</f>round",
"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,
'A' to AceTagAction.SelectAroundWord,
'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,
"<f>[D]</f>eclaration / <f>[U]</f>sages", "<f>[D]</f>eclaration / <f>[U]</f>sages",
"<f>[I]</f>ntentions / <f>[R]</f>efactor" "<f>[I]</f>ntentions / <f>[R]</f>efactor"
) )
private val ALL_ACTION_MAP = mapOf( private val ACTION_MAP = mapOf(
*JUMP_ACTION_MAP.map { it.key to it.value }.toTypedArray(), 'J' to AceTagAction.JumpToSearchStart,
*SELECT_ACTION_MAP.map { it.key to it.value }.toTypedArray(), 'L' to AceTagAction.JumpPastSearchEnd,
'S' to AceTagAction.JumpToWordStartTag,
'E' to AceTagAction.JumpToWordEndTag,
'W' to AceTagAction.SelectWord,
'H' to AceTagAction.SelectHump,
'Q' to AceTagAction.SelectQuery,
'D' to AceTagAction.GoToDeclaration, 'D' to AceTagAction.GoToDeclaration,
'U' to AceTagAction.ShowUsages, 'U' to AceTagAction.ShowUsages,
'I' to AceTagAction.ShowIntentions, 'I' to AceTagAction.ShowIntentions,
'R' to AceTagAction.Refactor 'R' to AceTagAction.Refactor,
*('1'..'9').mapIndexed { index, char -> char to AceTagAction.SelectExtended(index + 1) }.toTypedArray()
) )
} }
override val caretColor override val caretColor
get() = AceConfig.jumpModeColor get() = AceConfig.jumpModeColor
override fun type(state: SessionState, charTyped: Char, acceptedTag: Int?): TypeResult { override fun type(state: SessionState, charTyped: Char, acceptedTag: Int?): TypeResult {
if (acceptedTag == null) { if (acceptedTag == null) {
return state.type(charTyped) return state.type(charTyped)
} }
val action = ALL_ACTION_MAP[charTyped.toUpperCase()] val action = ACTION_MAP[charTyped.toUpperCase()]
if (action != null) { if (action != null) {
state.act(action, acceptedTag, charTyped.isUpperCase()) state.act(action, acceptedTag, charTyped.isUpperCase())
return TypeResult.EndSession return TypeResult.EndSession
@@ -69,7 +50,7 @@ class JumpMode : SessionMode {
return TypeResult.Nothing return TypeResult.Nothing
} }
override fun getHint(acceptedTag: Int?, hasQuery: Boolean): Array<String>? { override fun getHint(acceptedTag: Int?): Array<String>? {
return ALL_HINTS.takeIf { acceptedTag != null } return HINT_ACTIONS.takeIf { acceptedTag != null }
} }
} }

View File

@@ -1,29 +0,0 @@
package org.acejump.modes
import org.acejump.action.AceTagAction
import org.acejump.config.AceConfig
import org.acejump.session.SessionState
import org.acejump.session.TypeResult
class SelectFromCaretMode : SessionMode {
override val caretColor
get() = AceConfig.fromCaretModeColor
override fun type(state: SessionState, charTyped: Char, acceptedTag: Int?): TypeResult {
if (acceptedTag == null) {
return state.type(charTyped)
}
val jumpAction = JumpMode.JUMP_ACTION_MAP[charTyped.toUpperCase()]
if (jumpAction == null) {
return TypeResult.Nothing
}
state.act(AceTagAction.SelectToCaret(jumpAction), acceptedTag, shiftMode = charTyped.isUpperCase())
return TypeResult.EndSession
}
override fun getHint(acceptedTag: Int?, hasQuery: Boolean): Array<String>? {
return JumpMode.JUMP_ALT_HINT.takeIf { acceptedTag != null }
}
}

View File

@@ -1,12 +0,0 @@
package org.acejump.modes
import org.acejump.session.SessionState
import org.acejump.session.TypeResult
import java.awt.Color
interface SessionMode {
val caretColor: Color
fun type(state: SessionState, charTyped: Char, acceptedTag: Int?): TypeResult
fun getHint(acceptedTag: Int?, hasQuery: Boolean): Array<String>?
}

View File

@@ -30,14 +30,13 @@ class SearchProcessor private constructor(private val editor: Editor, query: Sea
while (result != null) { while (result != null) {
val index = result.range.first // For some reason regex matches can be out of bounds, but boundary check prevents an exception. val index = result.range.first // For some reason regex matches can be out of bounds, but boundary check prevents an exception.
val highlightEnd = index + query.getHighlightLength("", index)
if (highlightEnd > offsetRange.last) { if (boundaries.isOffsetInside(editor, index)) {
break
}
else if (boundaries.isOffsetInside(editor, index)) {
results.add(index) results.add(index)
} }
else if (index > offsetRange.last) {
break
}
result = result.next() result = result.next()
} }

View File

@@ -13,12 +13,11 @@ 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.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.BetweenPointsMode import org.acejump.modes.BetweenPointsMode
import org.acejump.modes.FromCaretMode
import org.acejump.modes.JumpMode import org.acejump.modes.JumpMode
import org.acejump.modes.SessionMode
import org.acejump.search.* import org.acejump.search.*
import org.acejump.view.TagCanvas import org.acejump.view.TagCanvas
import org.acejump.view.TextHighlighter import org.acejump.view.TextHighlighter
@@ -30,7 +29,7 @@ class Session(private val editor: Editor) {
private val editorSettings = EditorSettings.setup(editor) private val editorSettings = EditorSettings.setup(editor)
private lateinit var mode: SessionMode private lateinit var mode: SessionMode
private var state: SessionStateImpl? = null private var state: SessionState? = null
private var tagger = Tagger(editor) private var tagger = Tagger(editor)
private var acceptedTag: Int? = null private var acceptedTag: Int? = null
@@ -64,7 +63,7 @@ class Session(private val editor: Editor) {
when (result) { when (result) {
TypeResult.Nothing -> updateHint() TypeResult.Nothing -> updateHint()
TypeResult.RestartSearch -> restart().also { this@Session.state = SessionStateImpl(editor, tagger); updateHint() } TypeResult.RestartSearch -> restart().also { this@Session.state = SessionState(editor, tagger); updateHint() }
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.EndSession -> end() TypeResult.EndSession -> end()
@@ -75,12 +74,13 @@ class Session(private val editor: Editor) {
/** /**
* Updates text highlights and tag markers according to the current search state. Dispatches jumps if the search query matches a tag. * Updates text highlights and tag markers according to the current search state. Dispatches jumps if the search query matches a tag.
* If all tags are outside view, scrolls to the closest one.
*/ */
private fun updateSearch(processor: SearchProcessor, markImmediately: Boolean) { private fun updateSearch(processor: SearchProcessor, markImmediately: Boolean) {
val query = processor.query val query = processor.query
val results = processor.results val results = processor.results
if (!markImmediately && query.rawText.let { it.length < AceConfig.minQueryLength && it.all(Char::isLetterOrDigit) }) { if (query is SearchQuery.Literal && !markImmediately && query.rawText.let { it.length < 2 && it.all(Char::isLetterOrDigit) }) {
textHighlighter.renderOccurrences(results, query) textHighlighter.renderOccurrences(results, query)
return return
} }
@@ -92,9 +92,9 @@ class Session(private val editor: Editor) {
textHighlighter.renderFinal(offset, processor.query) textHighlighter.renderFinal(offset, processor.query)
} }
is TaggingResult.Mark -> { is TaggingResult.Mark -> {
val tags = result.tags val tags = result.tags
tagCanvas.setMarkers(tags) tagCanvas.setMarkers(tags, isRegex = query is SearchQuery.RegularExpression)
textHighlighter.renderOccurrences(results, query) textHighlighter.renderOccurrences(results, query)
} }
} }
@@ -109,11 +109,13 @@ class Session(private val editor: Editor) {
} }
private fun updateHint() { private fun updateHint() {
val hintArray = mode.getHint(acceptedTag, state?.currentProcessor.let { it != null && it.query.rawText.isNotEmpty() }) ?: return val hintArray = mode.getHint(acceptedTag) ?: return
val hintText = hintArray val hintText = hintArray
.joinToString("\n") .joinToString("\n")
.replace("<f>", "<span style=\"font-family:'${editor.colorsScheme.editorFontName}';font-weight:bold\">") .replace("<f>", "<span style=\"font-family:'${editor.colorsScheme.editorFontName}';font-weight:bold\">")
.replace("</f>", "</span>") .replace("</f>", "</span>")
.replace("<h>", "<b><u>")
.replace("</h>", "</u></b>")
val hint = LightweightHint(HintUtil.createInformationLabel(hintText)) val hint = LightweightHint(HintUtil.createInformationLabel(hintText))
val pos = acceptedTag?.let(editor::offsetToLogicalPosition) ?: editor.caretModel.logicalPosition val pos = acceptedTag?.let(editor::offsetToLogicalPosition) ?: editor.caretModel.logicalPosition
@@ -126,31 +128,34 @@ class Session(private val editor: Editor) {
fun cycleMode() { fun cycleMode() {
if (!this::mode.isInitialized) { if (!this::mode.isInitialized) {
setMode(JumpMode()) setMode(JumpMode())
state = SessionStateImpl(editor, tagger) state = SessionState(editor, tagger)
return return
} }
restart() restart()
setMode(when (mode) { setMode(when (mode) {
is JumpMode -> BetweenPointsMode() is JumpMode -> FromCaretMode()
else -> JumpMode() is FromCaretMode -> BetweenPointsMode()
else -> JumpMode()
}) })
state = SessionStateImpl(editor, tagger) state = SessionState(editor, tagger)
} }
/** /**
* 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.
*/ */
fun startRegexSearch(pattern: String, boundaries: Boundaries) { fun startRegexSearch(pattern: String, boundaries: Boundaries) {
if (!this::mode.isInitialized) { if (this::mode.isInitialized) {
setMode(JumpMode()) end()
return
} }
setMode(JumpMode())
tagger = Tagger(editor) tagger = Tagger(editor)
tagCanvas.setMarkers(emptyList()) tagCanvas.setMarkers(emptyList(), isRegex = true)
val processor = SearchProcessor.fromRegex(editor, pattern, boundaries).also { state = SessionStateImpl(editor, tagger, it) } val processor = SearchProcessor.fromRegex(editor, pattern, boundaries).also { state = SessionState(editor, tagger, it) }
updateSearch(processor, markImmediately = true) updateSearch(processor, markImmediately = true)
} }
@@ -161,24 +166,6 @@ class Session(private val editor: Editor) {
startRegexSearch(pattern.regex, boundaries) startRegexSearch(pattern.regex, boundaries)
} }
fun tagImmediately() {
val state = state ?: return
val processor = state.currentProcessor
if (processor != null) {
updateSearch(processor, markImmediately = true)
}
else if (mode is JumpMode) {
val offset = editor.caretModel.offset
val result = editor.immutableText.getOrNull(offset)?.let(state::type)
if (result is TypeResult.UpdateResults) {
acceptedTag = offset
textHighlighter.renderFinal(offset, result.processor.query)
updateHint()
}
}
}
/** /**
* Ends this session. * Ends this session.
*/ */

View File

@@ -0,0 +1,10 @@
package org.acejump.session
import java.awt.Color
interface SessionMode {
val caretColor: Color
fun type(state: SessionState, charTyped: Char, acceptedTag: Int?): TypeResult
fun getHint(acceptedTag: Int?): Array<String>?
}

View File

@@ -2,9 +2,29 @@ package org.acejump.session
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import org.acejump.action.AceTagAction import org.acejump.action.AceTagAction
import org.acejump.boundaries.StandardBoundaries
import org.acejump.search.SearchProcessor
import org.acejump.search.Tagger
interface SessionState { class SessionState(val editor: Editor, private val tagger: Tagger, processor: SearchProcessor? = null) {
val editor: Editor private var currentProcessor: SearchProcessor? = processor
fun type(char: Char): TypeResult
fun act(action: AceTagAction, offset: Int, shiftMode: Boolean) fun type(char: Char): TypeResult {
val processor = currentProcessor
if (processor == null) {
val newProcessor = SearchProcessor.fromChar(editor, char, StandardBoundaries.VISIBLE_ON_SCREEN)
return TypeResult.UpdateResults(newProcessor.also { currentProcessor = it })
}
if (processor.type(char, tagger)) {
return TypeResult.UpdateResults(processor)
}
return TypeResult.Nothing
}
fun act(action: AceTagAction, offset: Int, shiftMode: Boolean) {
currentProcessor?.let { action(editor, it, offset, shiftMode) }
}
} }

View File

@@ -1,30 +0,0 @@
package org.acejump.session
import com.intellij.openapi.editor.Editor
import org.acejump.action.AceTagAction
import org.acejump.boundaries.StandardBoundaries
import org.acejump.search.SearchProcessor
import org.acejump.search.Tagger
internal class SessionStateImpl(override val editor: Editor, private val tagger: Tagger, processor: SearchProcessor? = null) : SessionState {
internal var currentProcessor: SearchProcessor? = processor
override fun type(char: Char): TypeResult {
val processor = currentProcessor
if (processor == null) {
val newProcessor = SearchProcessor.fromChar(editor, char, StandardBoundaries.VISIBLE_ON_SCREEN)
return TypeResult.UpdateResults(newProcessor.also { currentProcessor = it })
}
if (processor.type(char, tagger)) {
return TypeResult.UpdateResults(processor)
}
return TypeResult.Nothing
}
override fun act(action: AceTagAction, offset: Int, shiftMode: Boolean) {
currentProcessor?.let { action(editor, it, offset, shiftMode) }
}
}

View File

@@ -1,6 +1,5 @@
package org.acejump.session package org.acejump.session
import org.acejump.modes.SessionMode
import org.acejump.search.SearchProcessor import org.acejump.search.SearchProcessor
sealed class TypeResult { sealed class TypeResult {

View File

@@ -27,8 +27,6 @@ internal class Tag(
private val length = tag.length private val length = tag.length
companion object { companion object {
private const val ARC = 1
/** /**
* Creates a new tag, precomputing some information about the nearby characters to reduce rendering overhead. If the last typed * Creates a new tag, precomputing some information about the nearby characters to reduce rendering overhead. If the last typed
* character ([literalQueryText]) matches the first [tag] character, only the second [tag] character is displayed. * character ([literalQueryText]) matches the first [tag] character, only the second [tag] character is displayed.
@@ -49,27 +47,18 @@ internal class Tag(
/** /**
* Renders the tag background. * Renders the tag background.
*/ */
private fun drawHighlight(g: Graphics2D, rect: Rectangle, color: Color) { private fun drawHighlight(g: Graphics2D, rect: Rectangle, color: Color, arc: Int) {
g.color = color g.color = color
g.fillRoundRect(rect.x, rect.y, rect.width, rect.height + 1, ARC, ARC) g.fillRoundRect(rect.x, rect.y + 1, rect.width, rect.height - 1, arc, arc)
} }
/** /**
* Renders the tag text. * Renders the tag text.
*/ */
private fun drawForeground(g: Graphics2D, font: TagFont, point: Point, text: String) { private fun drawForeground(g: Graphics2D, font: TagFont, point: Point, text: String) {
val x = point.x + 2
val y = point.y + font.baselineDistance
g.font = font.tagFont g.font = font.tagFont
if (!ColorUtil.isDark(AceConfig.tagForegroundColor)) {
g.color = Color(0F, 0F, 0F, 0.35F)
g.drawString(text, x + 1, y + 1)
}
g.color = AceConfig.tagForegroundColor g.color = AceConfig.tagForegroundColor
g.drawString(text, x, y) g.drawString(text, point.x, point.y + font.baselineDistance)
} }
} }
@@ -81,36 +70,56 @@ internal class Tag(
return offsetL in range return offsetL in range
} }
/**
* Determines on which side of the target character the tag is positioned.
*/
enum class TagAlignment {
LEFT,
RIGHT
}
/** /**
* Paints the tag, taking into consideration visual space around characters in the editor, as well as all other previously painted tags. * Paints the tag, taking into consideration visual space around characters in the editor, as well as all other previously painted tags.
* Returns a rectangle indicating the area where the tag was rendered, or null if the tag could not be rendered due to overlap. * Returns a rectangle indicating the area where the tag was rendered, or null if the tag could not be rendered due to overlap.
*/ */
fun paint(g: Graphics2D, editor: Editor, cache: EditorOffsetCache, font: TagFont, occupied: MutableList<Rectangle>): Rectangle? { fun paint(
val rect = alignTag(editor, cache, font, occupied) ?: return null g: Graphics2D, editor: Editor, cache: EditorOffsetCache, font: TagFont, occupied: MutableList<Rectangle>, isRegex: Boolean
): Rectangle? {
val (rect, alignment) = alignTag(editor, cache, font, occupied) ?: return null
drawHighlight(g, rect, AceConfig.tagBackgroundColor) val highlightColor = when {
alignment != TagAlignment.RIGHT || hasSpaceRight || isRegex -> AceConfig.tagBackgroundColor
else -> ColorUtil.darker(AceConfig.tagBackgroundColor, 3)
}
drawHighlight(g, rect, highlightColor, font.tagCornerArc)
drawForeground(g, font, rect.location, tag) drawForeground(g, font, rect.location, tag)
occupied.add(JBUIScale.scale(2).let { Rectangle(rect.x - it, rect.y, rect.width + (2 * it), rect.height) }) occupied.add(JBUIScale.scale(2).let { Rectangle(rect.x - it, rect.y, rect.width + (2 * it), rect.height) })
return rect return rect
} }
private fun alignTag(editor: Editor, cache: EditorOffsetCache, font: TagFont, occupied: List<Rectangle>): Rectangle? { private fun alignTag(editor: Editor, cache: EditorOffsetCache, font: TagFont, occupied: List<Rectangle>): Pair<Rectangle, TagAlignment>? {
val boundaries = StandardBoundaries.VISIBLE_ON_SCREEN val boundaries = StandardBoundaries.VISIBLE_ON_SCREEN
if (hasSpaceRight || offsetL == 0 || editor.immutableText[offsetL - 1].let { it == '\n' || it == '\r' }) { if (hasSpaceRight || offsetL == 0 || editor.immutableText[offsetL - 1].let { it == '\n' || it == '\r' }) {
val rectR = createRightAlignedTagRect(editor, cache, font) val rectR = createRightAlignedTagRect(editor, cache, font)
return rectR.takeIf { boundaries.isOffsetInside(editor, offsetR, cache) && occupied.none(rectR::intersects) }
return (rectR to TagAlignment.RIGHT).takeIf {
boundaries.isOffsetInside(editor, offsetR, cache) && occupied.none(rectR::intersects)
}
} }
val rectL = createLeftAlignedTagRect(editor, cache, font) val rectL = createLeftAlignedTagRect(editor, cache, font)
if (occupied.none(rectL::intersects)) { if (occupied.none(rectL::intersects)) {
return rectL.takeIf { boundaries.isOffsetInside(editor, offsetL, cache) } return (rectL to TagAlignment.LEFT).takeIf { boundaries.isOffsetInside(editor, offsetL, cache) }
} }
val rectR = createRightAlignedTagRect(editor, cache, font) val rectR = createRightAlignedTagRect(editor, cache, font)
if (occupied.none(rectR::intersects)) { if (occupied.none(rectR::intersects)) {
return rectR.takeIf { boundaries.isOffsetInside(editor, offsetR, cache) } return (rectR to TagAlignment.RIGHT).takeIf { boundaries.isOffsetInside(editor, offsetR, cache) }
} }
return null return null
@@ -119,12 +128,12 @@ internal class Tag(
private fun createRightAlignedTagRect(editor: Editor, cache: EditorOffsetCache, font: TagFont): Rectangle { private fun createRightAlignedTagRect(editor: Editor, cache: EditorOffsetCache, font: TagFont): Rectangle {
val pos = cache.offsetToXY(editor, offsetR) val pos = cache.offsetToXY(editor, offsetR)
val shift = font.editorFontMetrics.charWidth(editor.immutableText[offsetR]) + (font.tagCharWidth * shiftR) val shift = font.editorFontMetrics.charWidth(editor.immutableText[offsetR]) + (font.tagCharWidth * shiftR)
return Rectangle(pos.x + shift, pos.y, (font.tagCharWidth * length) + 4, font.lineHeight) return Rectangle(pos.x + shift, pos.y, font.tagCharWidth * length, font.lineHeight)
} }
private fun createLeftAlignedTagRect(editor: Editor, cache: EditorOffsetCache, font: TagFont): Rectangle { private fun createLeftAlignedTagRect(editor: Editor, cache: EditorOffsetCache, font: TagFont): Rectangle {
val pos = cache.offsetToXY(editor, offsetL) val pos = cache.offsetToXY(editor, offsetL)
val shift = -(font.tagCharWidth * length) val shift = -(font.tagCharWidth * length)
return Rectangle(pos.x + shift - 4, pos.y, (font.tagCharWidth * length) + 4, font.lineHeight) return Rectangle(pos.x + shift, pos.y, font.tagCharWidth * length, font.lineHeight)
} }
} }

View File

@@ -4,8 +4,10 @@ import com.intellij.openapi.application.ApplicationInfo
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.event.CaretEvent import com.intellij.openapi.editor.event.CaretEvent
import com.intellij.openapi.editor.event.CaretListener import com.intellij.openapi.editor.event.CaretListener
import com.intellij.ui.ColorUtil
import org.acejump.boundaries.EditorOffsetCache import org.acejump.boundaries.EditorOffsetCache
import org.acejump.boundaries.StandardBoundaries import org.acejump.boundaries.StandardBoundaries
import org.acejump.config.AceConfig
import java.awt.Graphics import java.awt.Graphics
import java.awt.Graphics2D import java.awt.Graphics2D
import java.awt.Rectangle import java.awt.Rectangle
@@ -18,6 +20,7 @@ import javax.swing.SwingUtilities
*/ */
internal class TagCanvas(private val editor: Editor) : JComponent(), CaretListener { internal class TagCanvas(private val editor: Editor) : JComponent(), CaretListener {
private var markers: List<Tag>? = null private var markers: List<Tag>? = null
private var isRegex = false
init { init {
val contentComponent = editor.contentComponent val contentComponent = editor.contentComponent
@@ -45,8 +48,9 @@ internal class TagCanvas(private val editor: Editor) : JComponent(), CaretListen
repaint() repaint()
} }
fun setMarkers(markers: List<Tag>) { fun setMarkers(markers: List<Tag>, isRegex: Boolean) {
this.markers = markers this.markers = markers
this.isRegex = isRegex
repaint() repaint()
} }
@@ -80,12 +84,18 @@ internal class TagCanvas(private val editor: Editor) : JComponent(), CaretListen
val caretOffset = editor.caretModel.offset val caretOffset = editor.caretModel.offset
val caretMarker = markers.find { it.offsetL == caretOffset || it.offsetR == caretOffset } val caretMarker = markers.find { it.offsetL == caretOffset || it.offsetR == caretOffset }
caretMarker?.paint(g, editor, cache, font, occupied) val caretRect = caretMarker?.paint(g, editor, cache, font, occupied, isRegex)
for (marker in markers) { for (marker in markers) {
if (marker.isOffsetInRange(viewRange) && marker !== caretMarker) { if (marker.isOffsetInRange(viewRange) && marker !== caretMarker) {
marker.paint(g, editor, cache, font, occupied) marker.paint(g, editor, cache, font, occupied, isRegex)
} }
} }
if (caretRect != null) {
g.color = ColorUtil.brighter(AceConfig.tagBackgroundColor, 10)
// Only adding 1 to width because it seems the right side of the tag highlight is slightly off.
g.drawRoundRect(caretRect.x - 1, caretRect.y, caretRect.width + 1, caretRect.height, font.tagCornerArc, font.tagCornerArc)
}
} }
} }

View File

@@ -1,7 +1,8 @@
package org.acejump.view package org.acejump.view;
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.colors.EditorFontType import com.intellij.openapi.editor.colors.EditorFontType
import org.acejump.config.AceConfig
import java.awt.Font import java.awt.Font
import java.awt.FontMetrics import java.awt.FontMetrics
@@ -10,7 +11,8 @@ import java.awt.FontMetrics
*/ */
internal class TagFont(editor: Editor) { internal class TagFont(editor: Editor) {
val tagFont: Font = editor.colorsScheme.getFont(EditorFontType.BOLD) val tagFont: Font = editor.colorsScheme.getFont(EditorFontType.BOLD)
val tagCharWidth = editor.component.getFontMetrics(tagFont).charWidth('W') val tagCharWidth = editor.component.getFontMetrics(tagFont).charWidth('w')
val tagCornerArc = if (AceConfig.roundedTagCorners) editor.colorsScheme.editorFontSize - 3 else 1
val editorFontMetrics: FontMetrics = editor.component.getFontMetrics(editor.colorsScheme.getFont(EditorFontType.PLAIN)) val editorFontMetrics: FontMetrics = editor.component.getFontMetrics(editor.colorsScheme.getFont(EditorFontType.PLAIN))
val lineHeight = editor.lineHeight val lineHeight = editor.lineHeight

View File

@@ -42,6 +42,8 @@ internal class TextHighlighter(private val editor: Editor) {
val markup = editor.markupModel val markup = editor.markupModel
val chars = editor.immutableText val chars = editor.immutableText
ARC = TagFont(editor).tagCornerArc
val modifications = (previousHighlights?.size ?: 0) + offsets.size val modifications = (previousHighlights?.size ?: 0) + offsets.size
val enableBulkEditing = modifications > 1000 val enableBulkEditing = modifications > 1000
@@ -102,16 +104,14 @@ internal class TextHighlighter(private val editor: Editor) {
private companion object { private companion object {
private const val LAYER = HighlighterLayer.LAST + 1 private const val LAYER = HighlighterLayer.LAST + 1
private var ARC = 0
private fun drawFilled(g: Graphics, editor: Editor, startOffset: Int, endOffset: Int, color: Color) { private fun drawFilled(g: Graphics, editor: Editor, startOffset: Int, endOffset: Int, color: Color) {
val start = EditorOffsetCache.Uncached.offsetToXY(editor, startOffset) val start = EditorOffsetCache.Uncached.offsetToXY(editor, startOffset)
val end = EditorOffsetCache.Uncached.offsetToXY(editor, endOffset) val end = EditorOffsetCache.Uncached.offsetToXY(editor, endOffset)
g.color = color g.color = color
g.fillRect(start.x, start.y + 1, end.x - start.x, editor.lineHeight - 1) g.fillRoundRect(start.x, start.y + 1, end.x - start.x, editor.lineHeight - 1, ARC, ARC)
g.color = AceConfig.tagBackgroundColor
g.drawRect(start.x, start.y, end.x - start.x, editor.lineHeight)
} }
private fun drawSingle(g: Graphics, editor: Editor, offset: Int, color: Color) { private fun drawSingle(g: Graphics, editor: Editor, offset: Int, color: Color) {
@@ -121,10 +121,7 @@ internal class TextHighlighter(private val editor: Editor) {
val lastCharWidth = editor.component.getFontMetrics(font).charWidth(char) val lastCharWidth = editor.component.getFontMetrics(font).charWidth(char)
g.color = color g.color = color
g.fillRect(pos.x, pos.y + 1, lastCharWidth, editor.lineHeight - 1) g.fillRoundRect(pos.x, pos.y + 1, lastCharWidth, editor.lineHeight - 1, ARC, ARC)
g.color = AceConfig.tagBackgroundColor
g.drawRect(pos.x, pos.y, lastCharWidth, editor.lineHeight)
} }
} }
} }

View File

@@ -23,14 +23,12 @@
implementationClass="org.acejump.action.AceEditorAction$Reset"/> implementationClass="org.acejump.action.AceEditorAction$Reset"/>
<editorActionHandler action="EditorBackSpace" order="first" <editorActionHandler action="EditorBackSpace" order="first"
implementationClass="org.acejump.action.AceEditorAction$ClearSearch"/> implementationClass="org.acejump.action.AceEditorAction$ClearSearch"/>
<editorActionHandler action="EditorEnter" order="first"
implementationClass="org.acejump.action.AceEditorAction$TagImmediately"/>
<editorActionHandler action="EditorUp" order="first" <editorActionHandler action="EditorUp" order="first"
implementationClass="org.acejump.action.AceEditorAction$SearchLineStarts"/> implementationClass="org.acejump.action.AceEditorAction$SearchLineIndents"/>
<editorActionHandler action="EditorLeft" order="first" <editorActionHandler action="EditorLeft" order="first"
implementationClass="org.acejump.action.AceEditorAction$SearchLineIndents"/> implementationClass="org.acejump.action.AceEditorAction$SearchLineStarts"/>
<editorActionHandler action="EditorLineStart" order="first" <editorActionHandler action="EditorLineStart" order="first"
implementationClass="org.acejump.action.AceEditorAction$SearchLineIndents"/> implementationClass="org.acejump.action.AceEditorAction$SearchLineStarts"/>
<editorActionHandler action="EditorRight" order="first" <editorActionHandler action="EditorRight" order="first"
implementationClass="org.acejump.action.AceEditorAction$SearchLineEnds"/> implementationClass="org.acejump.action.AceEditorAction$SearchLineEnds"/>
<editorActionHandler action="EditorLineEnd" order="first" <editorActionHandler action="EditorLineEnd" order="first"