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

15 Commits

23 changed files with 448 additions and 371 deletions

View File

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

View File

@@ -40,6 +40,10 @@ 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,12 +5,10 @@ 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
@@ -19,11 +17,15 @@ 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
@@ -50,6 +52,18 @@ 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) {
@@ -77,26 +91,50 @@ 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 BaseWordAction : BaseJumpAction() { abstract class BasePerCaretWriteAction(private val selector: AceTagAction) : AceTagAction() {
final override fun getCaretOffset(editor: Editor, searchProcessor: SearchProcessor, offset: Int): Int { final override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) {
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 BaseCaretRestoringAction : AceTagAction() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) {
val oldCarets = editor.caretModel.caretsAndSelections val oldCarets = editor.caretModel.caretsAndSelections
doInvoke(editor, searchProcessor, offset, shiftMode) selector(editor, searchProcessor, offset, shiftMode = false)
val range = editor.selectionModel.let { TextRange(it.selectionStart, it.selectionEnd) }
editor.caretModel.caretsAndSelections = oldCarets editor.caretModel.caretsAndSelections = oldCarets
invoke(editor, range, shiftMode)
} }
protected abstract fun doInvoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) protected abstract operator fun invoke(editor: Editor, range: TextRange, shiftMode: Boolean)
protected fun insertAtCarets(editor: Editor, text: String) {
val document = editor.document
editor.caretModel.runForEachCaret {
if (it.hasSelection()) {
document.replaceString(it.selectionStart, it.selectionEnd, text)
fixIndents(editor, it.selectionStart, it.selectionEnd)
}
else {
document.insertString(it.offset, text)
fixIndents(editor, it.offset, it.offset + text.length)
}
}
}
private fun fixIndents(editor: Editor, startOffset: Int, endOffset: Int) {
val project = editor.project ?: return
val document = editor.document
val documentManager = PsiDocumentManager.getInstance(project)
documentManager.commitAllDocuments()
val file = documentManager.getPsiFile(document) ?: return
val text = document.charsSequence
if (startOffset > 0 && endOffset > startOffset + 1 && text[endOffset - 1] == '\n' && text[startOffset - 1] == '\n') {
CodeStyleManager.getInstance(project).adjustLineIndent(file, TextRange(startOffset, endOffset - 1))
}
else {
CodeStyleManager.getInstance(project).adjustLineIndent(file, TextRange(startOffset, endOffset))
}
}
} }
private companion object { private companion object {
@@ -174,7 +212,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 JumpToWordStartTag : BaseWordAction() { object JumpToWordStart : 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)
@@ -190,7 +228,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 JumpToWordEndTag : BaseWordAction() { object JumpToWordEnd : 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
@@ -199,6 +237,22 @@ 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)
@@ -213,7 +267,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
* first character of the search query, then the caret is placed after the last character of the search query, and all text between the * last character of the search query, then the caret is placed after the last character of the search query, and all text between the
* start and end of the search query is selected. * 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.
@@ -226,8 +280,8 @@ sealed class AceTagAction {
if (chars[queryEndOffset].isWordPart) { if (chars[queryEndOffset].isWordPart) {
recordCaretPosition(editor) recordCaretPosition(editor)
val startOffset = JumpToWordStartTag.getCaretOffset(editor, offset, queryEndOffset, isInsideWord = true) val startOffset = JumpToWordStart.getCaretOffset(editor, offset, queryEndOffset, isInsideWord = true)
val endOffset = JumpToWordEndTag.getCaretOffset(editor, offset, queryEndOffset, isInsideWord = true) val endOffset = JumpToWordEnd.getCaretOffset(editor, offset, queryEndOffset, isInsideWord = true)
selectRange(editor, startOffset, endOffset) selectRange(editor, startOffset, endOffset)
} }
@@ -237,6 +291,12 @@ 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
@@ -256,13 +316,59 @@ sealed class AceTagAction {
} }
} }
object SelectLine : BaseSelectAction() { /**
* 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) { override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) {
JumpToSearchEnd(editor, searchProcessor, offset, shiftMode = false) SelectWord(editor, searchProcessor, offset, shiftMode = false)
editor.selectionModel.selectLineAtCaret() 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() {
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int) {
JumpToSearchEnd(editor, searchProcessor, offset, shiftMode = false)
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)
@@ -275,6 +381,10 @@ 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
@@ -294,6 +404,10 @@ 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)
@@ -301,80 +415,59 @@ sealed class AceTagAction {
} }
} }
class Cut(private val selector: AceTagAction) : BaseCaretRestoringAction() { /**
override fun doInvoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) { * On default action, selects text based on the provided [selector] action, and deletes it without moving the existing carets.
selector(editor, searchProcessor, offset, shiftMode = false) * On shift action, moves caret to the position where deletion occurred.
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() { /**
override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) { * Selects text based on the provided [selector] action and clones it at every existing caret, selecting the cloned text. If a caret
val document = editor.document * has a selection, the selected text will be replaced.
val oldCarets = editor.caretModel.caretsAndSelections */
class CloneToCaret(selector: AceTagAction) : BasePerCaretWriteAction(selector) {
selector(editor, searchProcessor, offset, shiftMode = false) override fun invoke(editor: Editor, range: TextRange, shiftMode: Boolean) {
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) {
companion object { editor.selectionModel.removeSelection(true)
fun insertAtCarets(editor: Editor, text: String) { editor.caretModel.moveToOffset(range.startOffset + difference)
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)
} }
} }
} }
@@ -382,11 +475,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 end of the search query. * Always places the caret at the start of the word.
*/ */
object GoToDeclaration : BaseCaretRestoringAction() { object GoToDeclaration : AceTagAction() {
override fun doInvoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) { override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) {
JumpToSearchEnd(editor, searchProcessor, offset, shiftMode = false) JumpToWordStart(editor, searchProcessor, offset, shiftMode = false)
performAction(if (shiftMode) GotoTypeDeclarationAction() else GotoDeclarationAction()) performAction(if (shiftMode) GotoTypeDeclarationAction() else GotoDeclarationAction())
} }
} }
@@ -394,29 +487,40 @@ 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 end of the search query. * Always places the caret at the start of the word.
*/ */
object ShowUsages : BaseCaretRestoringAction() { object ShowUsages : AceTagAction() {
override fun doInvoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) { override fun invoke(editor: Editor, searchProcessor: SearchProcessor, offset: Int, shiftMode: Boolean) {
JumpToSearchEnd(editor, searchProcessor, offset, shiftMode = false) JumpToWordStart(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) {
JumpToWordStartTag(editor, searchProcessor, offset, shiftMode = false) JumpToWordStart(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) {
JumpToWordStartTag(editor, searchProcessor, offset, shiftMode = false) JumpToWordStart(editor, searchProcessor, offset, shiftMode = false)
performAction(RefactoringQuickListPopupAction()) if (shiftMode) {
ApplicationManager.getApplication().invokeLater { performAction(RenameElementAction()) }
}
else {
performAction(RefactoringQuickListPopupAction())
}
} }
} }
} }

View File

@@ -19,6 +19,7 @@ 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
@@ -26,7 +27,6 @@ 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,18 +14,19 @@ 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 }
@@ -33,7 +34,6 @@ 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,27 +8,26 @@ 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.BLUE, var jumpModeColor: Color = Color(0xFFFFFF),
@OptionTag("fromCaretModeRGB", converter = ColorConverter::class) @OptionTag("fromCaretModeRGB", converter = ColorConverter::class)
var fromCaretModeColor: Color = Color.ORANGE, var fromCaretModeColor: Color = Color(0xFFB700),
@OptionTag("betweenPointsModeRGB", converter = ColorConverter::class) @OptionTag("betweenPointsModeRGB", converter = ColorConverter::class)
var betweenPointsModeColor: Color = Color.YELLOW, var betweenPointsModeColor: Color = Color(0x6FC5FF),
@OptionTag("textHighlightRGB", converter = ColorConverter::class) @OptionTag("textHighlightRGB", converter = ColorConverter::class)
var textHighlightColor: Color = Color.GREEN, var textHighlightColor: Color = Color(0x394B58),
@OptionTag("tagForegroundRGB", converter = ColorConverter::class) @OptionTag("tagForegroundRGB", converter = ColorConverter::class)
var tagForegroundColor: Color = Color.BLACK, var tagForegroundColor: Color = Color(0xFFFFFF),
@OptionTag("tagBackgroundRGB", converter = ColorConverter::class) @OptionTag("tagBackgroundRGB", converter = ColorConverter::class)
var tagBackgroundColor: Color = Color.YELLOW, var tagBackgroundColor: Color = Color(0x008299),
@OptionTag("acceptedTagRGB", converter = ColorConverter::class) @OptionTag("acceptedTagRGB", converter = ColorConverter::class)
var acceptedTagColor: Color = Color.CYAN, var acceptedTagColor: Color = Color(0x394B58)
var roundedTagCorners: Boolean = true
) )

View File

@@ -2,7 +2,6 @@ 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
@@ -26,6 +25,7 @@ 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,7 +33,6 @@ 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) }
@@ -51,6 +50,10 @@ 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) }
@@ -60,16 +63,13 @@ 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,11 +77,15 @@ 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
@@ -89,7 +93,6 @@ 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,31 +3,23 @@ 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 HINT_TYPE_TAG = arrayOf( private val TYPE_TAG_HINT = arrayOf(
"<b>Type to Search...</b>" "<b>Type to Search...</b>"
) )
private val HINT_ACTION_MODE = arrayOf( private val ACTION_MODE_HINT = arrayOf(
"<h>Between Points Mode</h>", "<f>[S]</f>elect... / <f>[F]</f>rom Caret...",
"<f>[S]</f>elect... / <f>[D]</f>elete...", "<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 val HINT_JUMP_MODE = arrayOf( private const val ACTION_MODE_FROM_CARET = 'F'
"<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 }),
@@ -35,20 +27,6 @@ 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
@@ -61,6 +39,10 @@ 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
} }
@@ -70,18 +52,18 @@ class BetweenPointsMode : SessionMode {
} }
if (firstOffset == null) { if (firstOffset == null) {
val selectAction = SELECTION_MODE_MAP[charTyped.toUpperCase()] val selectAction = JumpMode.SELECT_ACTION_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 = JUMP_MODE_MAP[charTyped.toUpperCase()] val jumpAction = JumpMode.JUMP_ACTION_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
@@ -98,12 +80,12 @@ class BetweenPointsMode : SessionMode {
return TypeResult.EndSession return TypeResult.EndSession
} }
override fun getHint(acceptedTag: Int?): Array<String> { override fun getHint(acceptedTag: Int?, hasQuery: Boolean): Array<String>? {
return when { return when {
actionMode == null -> HINT_ACTION_MODE actionMode == null -> ACTION_MODE_HINT
acceptedTag == null -> HINT_TYPE_TAG acceptedTag == null -> TYPE_TAG_HINT.takeUnless { hasQuery }
firstOffset == null -> HINT_JUMP_OR_SELECT_MODE firstOffset == null -> JumpMode.JUMP_ALT_HINT + JumpMode.SELECT_HINT
else -> HINT_JUMP_MODE else -> JumpMode.JUMP_ALT_HINT
} }
} }
} }

View File

@@ -1,74 +0,0 @@
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,46 +2,65 @@ 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 {
private companion object { companion object {
private val HINT_ACTIONS = arrayOf( private val JUMP_HINT = arrayOf(
"<f>[J]</f>ump to Tag / <f>[L]</f> past Query", "<f>[J]</f>ump / <f>[L]</f> past Query",
"Word <f>[S]</f>tart / <f>[E]</f>nd", "<f>[E]</f> Word End / <f>[M]</f> Line End"
"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 ACTION_MAP = mapOf( private val ALL_ACTION_MAP = mapOf(
'J' to AceTagAction.JumpToSearchStart, *JUMP_ACTION_MAP.map { it.key to it.value }.toTypedArray(),
'L' to AceTagAction.JumpPastSearchEnd, *SELECT_ACTION_MAP.map { it.key to it.value }.toTypedArray(),
'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 = ACTION_MAP[charTyped.toUpperCase()] val action = ALL_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
@@ -50,7 +69,7 @@ class JumpMode : SessionMode {
return TypeResult.Nothing return TypeResult.Nothing
} }
override fun getHint(acceptedTag: Int?): Array<String>? { override fun getHint(acceptedTag: Int?, hasQuery: Boolean): Array<String>? {
return HINT_ACTIONS.takeIf { acceptedTag != null } return ALL_HINTS.takeIf { acceptedTag != null }
} }
} }

View File

@@ -0,0 +1,29 @@
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

@@ -0,0 +1,12 @@
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,13 +30,14 @@ 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 (boundaries.isOffsetInside(editor, index)) { if (highlightEnd > offsetRange.last) {
results.add(index)
}
else if (index > offsetRange.last) {
break break
} }
else if (boundaries.isOffsetInside(editor, index)) {
results.add(index)
}
result = result.next() result = result.next()
} }

View File

@@ -13,11 +13,12 @@ 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
@@ -29,7 +30,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: SessionState? = null private var state: SessionStateImpl? = null
private var tagger = Tagger(editor) private var tagger = Tagger(editor)
private var acceptedTag: Int? = null private var acceptedTag: Int? = null
@@ -63,7 +64,7 @@ class Session(private val editor: Editor) {
when (result) { when (result) {
TypeResult.Nothing -> updateHint() TypeResult.Nothing -> updateHint()
TypeResult.RestartSearch -> restart().also { this@Session.state = SessionState(editor, tagger); updateHint() } TypeResult.RestartSearch -> restart().also { this@Session.state = SessionStateImpl(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()
@@ -74,13 +75,12 @@ 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 (query is SearchQuery.Literal && !markImmediately && query.rawText.let { it.length < 2 && it.all(Char::isLetterOrDigit) }) { if (!markImmediately && query.rawText.let { it.length < AceConfig.minQueryLength && 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, isRegex = query is SearchQuery.RegularExpression) tagCanvas.setMarkers(tags)
textHighlighter.renderOccurrences(results, query) textHighlighter.renderOccurrences(results, query)
} }
} }
@@ -109,13 +109,11 @@ class Session(private val editor: Editor) {
} }
private fun updateHint() { private fun updateHint() {
val hintArray = mode.getHint(acceptedTag) ?: return val hintArray = mode.getHint(acceptedTag, state?.currentProcessor.let { it != null && it.query.rawText.isNotEmpty() }) ?: 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
@@ -128,34 +126,31 @@ class Session(private val editor: Editor) {
fun cycleMode() { fun cycleMode() {
if (!this::mode.isInitialized) { if (!this::mode.isInitialized) {
setMode(JumpMode()) setMode(JumpMode())
state = SessionState(editor, tagger) state = SessionStateImpl(editor, tagger)
return return
} }
restart() restart()
setMode(when (mode) { setMode(when (mode) {
is JumpMode -> FromCaretMode() is JumpMode -> BetweenPointsMode()
is FromCaretMode -> BetweenPointsMode() else -> JumpMode()
else -> JumpMode()
}) })
state = SessionState(editor, tagger) state = SessionStateImpl(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) {
end() setMode(JumpMode())
return
} }
setMode(JumpMode())
tagger = Tagger(editor) tagger = Tagger(editor)
tagCanvas.setMarkers(emptyList(), isRegex = true) tagCanvas.setMarkers(emptyList())
val processor = SearchProcessor.fromRegex(editor, pattern, boundaries).also { state = SessionState(editor, tagger, it) } val processor = SearchProcessor.fromRegex(editor, pattern, boundaries).also { state = SessionStateImpl(editor, tagger, it) }
updateSearch(processor, markImmediately = true) updateSearch(processor, markImmediately = true)
} }
@@ -166,6 +161,24 @@ 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

@@ -1,10 +0,0 @@
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,29 +2,9 @@ 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
class SessionState(val editor: Editor, private val tagger: Tagger, processor: SearchProcessor? = null) { interface SessionState {
private var currentProcessor: SearchProcessor? = processor val editor: Editor
fun type(char: Char): TypeResult
fun type(char: Char): TypeResult { fun act(action: AceTagAction, offset: Int, shiftMode: Boolean)
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

@@ -0,0 +1,30 @@
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,5 +1,6 @@
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,6 +27,8 @@ 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.
@@ -47,18 +49,27 @@ internal class Tag(
/** /**
* Renders the tag background. * Renders the tag background.
*/ */
private fun drawHighlight(g: Graphics2D, rect: Rectangle, color: Color, arc: Int) { private fun drawHighlight(g: Graphics2D, rect: Rectangle, color: Color) {
g.color = color g.color = color
g.fillRoundRect(rect.x, rect.y + 1, rect.width, rect.height - 1, arc, arc) g.fillRoundRect(rect.x, rect.y, 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, point.x, point.y + font.baselineDistance) g.drawString(text, x, y)
} }
} }
@@ -70,56 +81,36 @@ 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( fun paint(g: Graphics2D, editor: Editor, cache: EditorOffsetCache, font: TagFont, occupied: MutableList<Rectangle>): Rectangle? {
g: Graphics2D, editor: Editor, cache: EditorOffsetCache, font: TagFont, occupied: MutableList<Rectangle>, isRegex: Boolean val rect = alignTag(editor, cache, font, occupied) ?: return null
): Rectangle? {
val (rect, alignment) = alignTag(editor, cache, font, occupied) ?: return null
val highlightColor = when { drawHighlight(g, rect, AceConfig.tagBackgroundColor)
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>): Pair<Rectangle, TagAlignment>? { private fun alignTag(editor: Editor, cache: EditorOffsetCache, font: TagFont, occupied: List<Rectangle>): Rectangle? {
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 to TagAlignment.LEFT).takeIf { boundaries.isOffsetInside(editor, offsetL, cache) } return rectL.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 to TagAlignment.RIGHT).takeIf { boundaries.isOffsetInside(editor, offsetR, cache) } return rectR.takeIf { boundaries.isOffsetInside(editor, offsetR, cache) }
} }
return null return null
@@ -128,12 +119,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, font.lineHeight) return Rectangle(pos.x + shift, pos.y, (font.tagCharWidth * length) + 4, 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, pos.y, font.tagCharWidth * length, font.lineHeight) return Rectangle(pos.x + shift - 4, pos.y, (font.tagCharWidth * length) + 4, font.lineHeight)
} }
} }

View File

@@ -4,10 +4,8 @@ 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
@@ -20,7 +18,6 @@ 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
@@ -48,9 +45,8 @@ internal class TagCanvas(private val editor: Editor) : JComponent(), CaretListen
repaint() repaint()
} }
fun setMarkers(markers: List<Tag>, isRegex: Boolean) { fun setMarkers(markers: List<Tag>) {
this.markers = markers this.markers = markers
this.isRegex = isRegex
repaint() repaint()
} }
@@ -84,18 +80,12 @@ 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 }
val caretRect = caretMarker?.paint(g, editor, cache, font, occupied, isRegex) caretMarker?.paint(g, editor, cache, font, occupied)
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, isRegex) marker.paint(g, editor, cache, font, occupied)
} }
} }
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,8 +1,7 @@
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
@@ -11,8 +10,7 @@ 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,8 +42,6 @@ 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
@@ -104,14 +102,16 @@ 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.fillRoundRect(start.x, start.y + 1, end.x - start.x, editor.lineHeight - 1, ARC, ARC) g.fillRect(start.x, start.y + 1, end.x - start.x, editor.lineHeight - 1)
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,7 +121,10 @@ 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.fillRoundRect(pos.x, pos.y + 1, lastCharWidth, editor.lineHeight - 1, ARC, ARC) g.fillRect(pos.x, pos.y + 1, lastCharWidth, editor.lineHeight - 1)
g.color = AceConfig.tagBackgroundColor
g.drawRect(pos.x, pos.y, lastCharWidth, editor.lineHeight)
} }
} }
} }

View File

@@ -23,12 +23,14 @@
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$SearchLineIndents"/> implementationClass="org.acejump.action.AceEditorAction$SearchLineStarts"/>
<editorActionHandler action="EditorLeft" order="first" <editorActionHandler action="EditorLeft" order="first"
implementationClass="org.acejump.action.AceEditorAction$SearchLineStarts"/> implementationClass="org.acejump.action.AceEditorAction$SearchLineIndents"/>
<editorActionHandler action="EditorLineStart" order="first" <editorActionHandler action="EditorLineStart" order="first"
implementationClass="org.acejump.action.AceEditorAction$SearchLineStarts"/> implementationClass="org.acejump.action.AceEditorAction$SearchLineIndents"/>
<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"