mirror of
https://github.com/chylex/IntelliJ-AceJump.git
synced 2025-04-09 17:15:43 +02:00
Replaces the staggering hack that was SearchBox with the slightly less messy, but still hacky KeyboardHandler
This commit is contained in:
parent
7847ee351d
commit
6e4a096c5f
@ -20,7 +20,7 @@ apply plugin: 'org.jetbrains.intellij'
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
intellij {
|
||||
version '162.1121.32'
|
||||
version '163.7743.44'
|
||||
pluginName 'AceJump'
|
||||
updateSinceUntilBuild false
|
||||
|
||||
|
26
src/main/kotlin/com/johnlindquist/acejump/AceJump.kt
Normal file
26
src/main/kotlin/com/johnlindquist/acejump/AceJump.kt
Normal file
@ -0,0 +1,26 @@
|
||||
package com.johnlindquist.acejump
|
||||
|
||||
import com.intellij.find.FindManager
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.components.ApplicationComponent
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
|
||||
class AceJumpPlugin : ApplicationComponent {
|
||||
val COMPONENT_NAME = "AceJump"
|
||||
val project = ProjectManager.getInstance().defaultProject
|
||||
val findManager = FindManager.getInstance(project)
|
||||
var searchBox: KeyboardHandler? = null
|
||||
val action = ActionManager.getInstance().getAction("AceJumpKeyAction")
|
||||
override fun initComponent() {
|
||||
}
|
||||
|
||||
|
||||
override fun disposeComponent() {
|
||||
|
||||
}
|
||||
|
||||
override fun getComponentName(): String {
|
||||
return COMPONENT_NAME
|
||||
}
|
||||
}
|
||||
|
@ -4,19 +4,19 @@ import com.intellij.find.FindManager
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys.EDITOR
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys.PROJECT
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.editor.impl.EditorImpl
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.wm.IdeFocusManager
|
||||
import com.johnlindquist.acejump.keycommands.ShowLineMarkers
|
||||
import com.johnlindquist.acejump.search.AceFinder
|
||||
import com.johnlindquist.acejump.ui.SearchBox
|
||||
import com.sun.glass.events.KeyEvent.VK_BACKSPACE
|
||||
import java.awt.event.KeyEvent
|
||||
import java.awt.event.KeyEvent.VK_ESCAPE
|
||||
|
||||
private var aceFinder: AceFinder? = null
|
||||
private var searchBox: KeyboardHandler? = null
|
||||
|
||||
open class AceJumpAction() : DumbAwareAction() {
|
||||
var aceFinder: AceFinder? = null
|
||||
var searchBox: SearchBox? = null
|
||||
|
||||
override fun update(e: AnActionEvent?) {
|
||||
e?.presentation?.isEnabled = (e?.getData(EDITOR)) != null
|
||||
}
|
||||
@ -27,17 +27,25 @@ open class AceJumpAction() : DumbAwareAction() {
|
||||
val editor = actionEvent.getData(EDITOR) as EditorImpl
|
||||
val findManager = FindManager.getInstance(project)!!
|
||||
aceFinder = AceFinder(findManager, editor)
|
||||
searchBox = SearchBox(aceFinder!!, editor)
|
||||
|
||||
ApplicationManager.getApplication().invokeLater({
|
||||
IdeFocusManager.getInstance(project).requestFocus(searchBox!!, false)
|
||||
})
|
||||
searchBox = KeyboardHandler(aceFinder!!, editor)
|
||||
}
|
||||
}
|
||||
|
||||
class AceJumpLineAction : AceJumpAction() {
|
||||
override fun actionPerformed(actionEvent: AnActionEvent) {
|
||||
super.actionPerformed(actionEvent)
|
||||
searchBox!!.processRegexCommand(ShowLineMarkers(aceFinder!!))
|
||||
super.actionPerformed(actionEvent)
|
||||
}
|
||||
}
|
||||
|
||||
class AceJumpKeyAction : AceJumpAction() {
|
||||
override fun actionPerformed(actionEvent: AnActionEvent) {
|
||||
val inputEvent = actionEvent.inputEvent as? KeyEvent ?: return
|
||||
|
||||
when (inputEvent.keyCode) {
|
||||
VK_ESCAPE -> searchBox!!.exit()
|
||||
VK_BACKSPACE -> searchBox!!.processBackspaceCommand()
|
||||
else -> searchBox!!.processRegexCommand(inputEvent.keyCode)
|
||||
}
|
||||
}
|
||||
}
|
143
src/main/kotlin/com/johnlindquist/acejump/KeyboardHandler.kt
Executable file
143
src/main/kotlin/com/johnlindquist/acejump/KeyboardHandler.kt
Executable file
@ -0,0 +1,143 @@
|
||||
package com.johnlindquist.acejump
|
||||
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.actionSystem.EditorActionManager
|
||||
import com.intellij.openapi.editor.colors.EditorColors.CARET_COLOR
|
||||
import com.intellij.openapi.editor.impl.EditorImpl
|
||||
import com.intellij.ui.popup.AbstractPopup
|
||||
import com.johnlindquist.acejump.keycommands.*
|
||||
import com.johnlindquist.acejump.search.AceFinder
|
||||
import com.johnlindquist.acejump.search.Pattern.Companion.REGEX_PREFIX
|
||||
import com.johnlindquist.acejump.ui.AceCanvas
|
||||
import com.sun.glass.events.KeyEvent.VK_BACKSPACE
|
||||
import java.awt.Color.WHITE
|
||||
import java.awt.event.KeyEvent.*
|
||||
import javax.swing.JRootPane
|
||||
import javax.swing.SwingUtilities
|
||||
import javax.swing.event.ChangeListener
|
||||
|
||||
class KeyboardHandler(val finder: AceFinder, var editor: EditorImpl) {
|
||||
var text = ""
|
||||
var aceCanvas = AceCanvas(editor)
|
||||
var keyMap: Map<Int, AceKeyCommand> = hashMapOf()
|
||||
var popupContainer: AbstractPopup? = null
|
||||
var defaultKeyCommand = DefaultKeyCommand(finder)
|
||||
var naturalColor = WHITE
|
||||
var keyHandler = EditorActionManager.getInstance().typedAction.rawHandler
|
||||
val specials = intArrayOf(VK_BACKSPACE, VK_LEFT, VK_RIGHT, VK_UP, VK_ESCAPE)
|
||||
|
||||
init {
|
||||
configureKeyMap()
|
||||
configureEditor()
|
||||
EditorActionManager.getInstance().typedAction.setupRawHandler {
|
||||
editor: Editor, c: Char, dataContext: DataContext ->
|
||||
text += c
|
||||
defaultKeyCommand.execute(c, text)
|
||||
}
|
||||
|
||||
finder.eventDispatcher.addListener(ChangeListener {
|
||||
aceCanvas.jumpLocations = finder.jumpLocations
|
||||
if (finder.hasJumped) {
|
||||
finder.hasJumped = false
|
||||
popupContainer?.cancel()
|
||||
exit()
|
||||
}
|
||||
|
||||
aceCanvas.repaint()
|
||||
})
|
||||
//
|
||||
// val search = "dispatch"
|
||||
// (' '..'~').forEach { inputMap.put(getKeyStroke(it), search) }
|
||||
// actionMap.put(search, object : AbstractAction() {
|
||||
// override fun actionPerformed(e: ActionEvent) {
|
||||
// if (e.modifiers == 0) {
|
||||
// text += e.actionCommand
|
||||
// defaultKeyCommand.execute(e.actionCommand[0], text)
|
||||
// } else if (e.modifiers == SHIFT_MASK) {
|
||||
// text += e.actionCommand
|
||||
// defaultKeyCommand.execute(e.actionCommand[0].toUpperCase(), text)
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
//
|
||||
//
|
||||
// val aja = "AceJumpAction"
|
||||
// ActionManager.getInstance().getAction(aja).shortcutSet?.shortcuts?.forEach {
|
||||
// if (it.isKeyboard) {
|
||||
// val kbs = it as KeyboardShortcut
|
||||
// inputMap.put(kbs.firstKeyStroke, aja)
|
||||
// actionMap.put(aja, object : AbstractAction() {
|
||||
// override fun actionPerformed(e: ActionEvent) {
|
||||
// if (finder.toggleTargetMode())
|
||||
// editor.colorsScheme.setColor(CARET_COLOR, RED)
|
||||
// else
|
||||
// editor.colorsScheme.setColor(CARET_COLOR, naturalColor)
|
||||
//
|
||||
// aceCanvas.repaint()
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
fun processRegexCommand(aceKeyCommand: AceKeyCommand) {
|
||||
text = REGEX_PREFIX.toString()
|
||||
aceKeyCommand.execute()
|
||||
}
|
||||
|
||||
fun processRegexCommand(keyCode: Int) {
|
||||
text = REGEX_PREFIX.toString()
|
||||
keyMap[keyCode]?.execute()
|
||||
}
|
||||
|
||||
fun processBackspaceCommand() {
|
||||
text = ""
|
||||
defaultKeyCommand.execute(0.toChar())
|
||||
}
|
||||
|
||||
private fun configureEditor() {
|
||||
addAceCanvas()
|
||||
editor.scrollingModel.addVisibleAreaListener { exit() }
|
||||
naturalColor = editor.colorsScheme.getColor(CARET_COLOR)
|
||||
}
|
||||
|
||||
private fun configureKeyMap() {
|
||||
specials.forEach {
|
||||
ActionManager.getInstance().getAction("AceJumpKeyAction")
|
||||
.registerCustomShortcutSet(it, 0, editor.component)
|
||||
}
|
||||
|
||||
val showBeginningOfLines = ShowStartOfLines(finder)
|
||||
val showEndOfLines = ShowEndOfLines(finder)
|
||||
keyMap = mapOf(VK_HOME to showBeginningOfLines,
|
||||
VK_LEFT to showBeginningOfLines,
|
||||
VK_RIGHT to showEndOfLines,
|
||||
VK_END to showEndOfLines,
|
||||
VK_UP to ShowFirstLetters(finder),
|
||||
VK_SPACE to ShowWhiteSpace(finder))
|
||||
}
|
||||
|
||||
fun addAceCanvas() {
|
||||
editor.contentComponent.add(aceCanvas)
|
||||
val viewport = editor.scrollPane.viewport
|
||||
aceCanvas.setBounds(0, 0, viewport.width + 1000, viewport.height + 1000)
|
||||
val root: JRootPane = editor.component.rootPane
|
||||
val loc = SwingUtilities.convertPoint(aceCanvas, aceCanvas.location, root)
|
||||
aceCanvas.setLocation(-loc.x, -loc.y)
|
||||
}
|
||||
|
||||
fun exit() {
|
||||
val contentComponent = editor.contentComponent
|
||||
contentComponent.remove(aceCanvas)
|
||||
contentComponent.repaint()
|
||||
EditorActionManager.getInstance().typedAction.setupRawHandler(keyHandler)
|
||||
specials.forEach {
|
||||
ActionManager.getInstance().getAction("AceJumpKeyAction")
|
||||
.unregisterCustomShortcutSet(editor.component)
|
||||
}
|
||||
popupContainer?.dispose()
|
||||
finder.reset()
|
||||
}
|
||||
}
|
@ -94,7 +94,8 @@ class AceFinder(val findManager: FindManager, var editor: EditorImpl) {
|
||||
jumpTo(JumpInfo(last2, query, tagMap[last2]!!, this))
|
||||
} else if (tagMap.containsKey(last1)) {
|
||||
val index = tagMap[last1]!!
|
||||
if (document[index + query.length - 1].toLowerCase() != last1[0])
|
||||
val charIndex = index + query.length - 1
|
||||
if (charIndex > document.length || document[charIndex] != last1[0])
|
||||
jumpTo(JumpInfo(last1, query, index, this))
|
||||
}
|
||||
}
|
||||
@ -167,7 +168,7 @@ class AceFinder(val findManager: FindManager, var editor: EditorImpl) {
|
||||
var startingFrom = if (oldResults.hasNext()) oldResults.next() else viewTop
|
||||
|
||||
var result = findManager.findString(fullText, startingFrom, findModel)
|
||||
while (result!!.isStringFound && result.startOffset < viewBottom) {
|
||||
while (result!!.isStringFound && result.startOffset <= viewBottom) {
|
||||
if (!editor.foldingModel.isOffsetCollapsed(result.startOffset))
|
||||
indicesToCheck.add(result.startOffset)
|
||||
|
||||
@ -347,10 +348,13 @@ class AceFinder(val findManager: FindManager, var editor: EditorImpl) {
|
||||
|
||||
val remainingSites = digraphs.asMap().entries.filter {
|
||||
it.key.first().isLetterOrDigit() || query.isNotEmpty()
|
||||
}.sortedBy { it.value.size }.flatMap { it.value }.sortedBy {
|
||||
}.sortedBy { it.value.size }.flatMap { it.value }.sortedWith(compareBy(
|
||||
// Ensure that the first letter of a word is prioritized for tagging
|
||||
document[Math.max(0, it - 1)].isLetterOrDigit()
|
||||
}.iterator()
|
||||
{ document[Math.max(0, it - 1)].isLetterOrDigit() },
|
||||
// Longer characters should come first
|
||||
{ val bounds = getWordBounds(it); -document.substring(bounds.first,
|
||||
bounds.second).toCharArray().distinct().size}
|
||||
)).iterator()
|
||||
|
||||
if (!findModel.isRegularExpressions || newTagMap.isEmpty())
|
||||
while (remainingSites.hasNext() && tags.isNotEmpty())
|
||||
|
@ -152,7 +152,7 @@ class JumpInfo(private val tag: String, var query: String, val index: Int,
|
||||
|
||||
fun surroundTargetWord() {
|
||||
val (wordStart, wordEnd) = aceFinder.getWordBounds(index)
|
||||
g2d.color = blue
|
||||
g2d.color = red
|
||||
|
||||
val startPoint = editor.offsetToVisualPosition(wordStart)
|
||||
val startPointO = getPointFromVisualPosition(editor, startPoint)
|
||||
@ -170,4 +170,4 @@ class JumpInfo(private val tag: String, var query: String, val index: Int,
|
||||
surroundTargetWord()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,174 +0,0 @@
|
||||
package com.johnlindquist.acejump.ui
|
||||
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.KeyboardShortcut
|
||||
import com.intellij.openapi.editor.impl.EditorImpl
|
||||
import com.intellij.openapi.ui.popup.ComponentPopupBuilder
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory
|
||||
import com.intellij.ui.popup.AbstractPopup
|
||||
import com.johnlindquist.acejump.keycommands.*
|
||||
import com.johnlindquist.acejump.search.AceFinder
|
||||
import com.johnlindquist.acejump.search.Pattern.Companion.REGEX_PREFIX
|
||||
import com.johnlindquist.acejump.search.guessBestLocation
|
||||
import java.awt.Color.RED
|
||||
import java.awt.Color.WHITE
|
||||
import java.awt.Graphics
|
||||
import java.awt.event.ActionEvent
|
||||
import java.awt.event.FocusEvent
|
||||
import java.awt.event.FocusListener
|
||||
import java.awt.event.KeyEvent
|
||||
import java.awt.event.KeyEvent.*
|
||||
import javax.swing.AbstractAction
|
||||
import javax.swing.JRootPane
|
||||
import javax.swing.JTextField
|
||||
import javax.swing.KeyStroke.getKeyStroke
|
||||
import javax.swing.SwingUtilities
|
||||
import javax.swing.event.ChangeListener
|
||||
|
||||
class SearchBox(val finder: AceFinder, var editor: EditorImpl) : JTextField() {
|
||||
var aceCanvas = AceCanvas(editor)
|
||||
var keyMap: Map<Int, AceKeyCommand> = hashMapOf()
|
||||
var popupContainer: AbstractPopup? = null
|
||||
var defaultKeyCommand = DefaultKeyCommand(finder)
|
||||
var naturalColor = WHITE
|
||||
|
||||
init {
|
||||
configureKeyMap()
|
||||
configurePopup()
|
||||
|
||||
finder.eventDispatcher.addListener(ChangeListener {
|
||||
aceCanvas.jumpLocations = finder.jumpLocations
|
||||
if (finder.hasJumped) {
|
||||
finder.hasJumped = false
|
||||
popupContainer?.cancel()
|
||||
exit()
|
||||
}
|
||||
|
||||
aceCanvas.repaint()
|
||||
})
|
||||
|
||||
val search = "dispatch"
|
||||
(' '..'~').forEach { inputMap.put(getKeyStroke(it), search) }
|
||||
actionMap.put(search, object : AbstractAction() {
|
||||
override fun actionPerformed(e: ActionEvent) {
|
||||
if (e.modifiers == 0) {
|
||||
text += e.actionCommand
|
||||
defaultKeyCommand.execute(e.actionCommand[0], text)
|
||||
} else if (e.modifiers == SHIFT_MASK) {
|
||||
text += e.actionCommand
|
||||
defaultKeyCommand.execute(e.actionCommand[0].toUpperCase(), text)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
(VK_LEFT..VK_RIGHT).forEach {
|
||||
val keyName: String = KeyEvent.getKeyText(it)
|
||||
inputMap.put(getKeyStroke(it, 0), keyName)
|
||||
actionMap.put(keyName, object : AbstractAction() {
|
||||
override fun actionPerformed(e: ActionEvent) = processRegexCommand(it)
|
||||
})
|
||||
}
|
||||
|
||||
val aja = "AceJumpAction"
|
||||
ActionManager.getInstance().getAction(aja).shortcutSet?.shortcuts?.forEach {
|
||||
if (it.isKeyboard) {
|
||||
val kbs = it as KeyboardShortcut
|
||||
inputMap.put(kbs.firstKeyStroke, aja)
|
||||
actionMap.put(aja, object : AbstractAction() {
|
||||
override fun actionPerformed(e: ActionEvent) {
|
||||
background = if (finder.toggleTargetMode()) RED else naturalColor
|
||||
aceCanvas.repaint()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* For some reason, Swing does not like to pass us actions via the inputMap or
|
||||
* actionMap registration technique. Until that works reliably, we need to use
|
||||
* low-level KeyEvents for processing the following keystrokes.
|
||||
*/
|
||||
override fun processKeyEvent(keyEvent: KeyEvent) {
|
||||
if (keyEvent.id == KEY_RELEASED) {
|
||||
when (keyEvent.keyCode) {
|
||||
VK_BACK_SPACE -> processBackspaceCommand()
|
||||
VK_HOME -> processRegexCommand(VK_END)
|
||||
VK_END -> processRegexCommand(VK_END)
|
||||
}
|
||||
}
|
||||
|
||||
super.processKeyEvent(keyEvent)
|
||||
}
|
||||
|
||||
fun processRegexCommand(aceKeyCommand: AceKeyCommand) {
|
||||
text = REGEX_PREFIX.toString()
|
||||
aceKeyCommand.execute()
|
||||
}
|
||||
|
||||
fun processRegexCommand(keyCode: Int) {
|
||||
text = REGEX_PREFIX.toString()
|
||||
keyMap[keyCode]!!.execute()
|
||||
}
|
||||
|
||||
fun processBackspaceCommand() {
|
||||
text = ""
|
||||
defaultKeyCommand.execute(0.toChar())
|
||||
}
|
||||
|
||||
private fun configurePopup() {
|
||||
val pb: ComponentPopupBuilder? =
|
||||
JBPopupFactory.getInstance()?.createComponentPopupBuilder(this, this)
|
||||
pb?.setCancelKeyEnabled(true)
|
||||
val popup = pb?.createPopup() as AbstractPopup?
|
||||
popup?.show(guessBestLocation(editor))
|
||||
popup?.setRequestFocus(true)
|
||||
|
||||
isFocusable = true
|
||||
addFocusListener(object : FocusListener {
|
||||
override fun focusGained(p0: FocusEvent) = addAceCanvas()
|
||||
override fun focusLost(p0: FocusEvent) = exit()
|
||||
})
|
||||
|
||||
editor.scrollingModel.addVisibleAreaListener { exit() }
|
||||
popupContainer = popup
|
||||
naturalColor = background
|
||||
}
|
||||
|
||||
private fun configureKeyMap() {
|
||||
val showBeginningOfLines = ShowStartOfLines(finder)
|
||||
val showEndOfLines = ShowEndOfLines(finder)
|
||||
keyMap = mapOf(VK_HOME to showBeginningOfLines,
|
||||
VK_LEFT to showBeginningOfLines,
|
||||
VK_RIGHT to showEndOfLines,
|
||||
VK_END to showEndOfLines,
|
||||
VK_UP to ShowFirstLetters(finder),
|
||||
VK_SPACE to ShowWhiteSpace(finder))
|
||||
}
|
||||
|
||||
override fun requestFocus() {
|
||||
transferHandler = null
|
||||
super.requestFocus()
|
||||
}
|
||||
|
||||
override fun paintBorder(p0: Graphics?) = Unit
|
||||
|
||||
fun addAceCanvas() {
|
||||
editor.contentComponent.add(aceCanvas)
|
||||
val viewport = editor.scrollPane.viewport
|
||||
aceCanvas.setBounds(0, 0, viewport.width + 1000, viewport.height + 1000)
|
||||
val root: JRootPane = editor.component.rootPane
|
||||
val loc = SwingUtilities.convertPoint(aceCanvas, aceCanvas.location, root)
|
||||
aceCanvas.setLocation(-loc.x, -loc.y)
|
||||
}
|
||||
|
||||
override fun paint(g: Graphics?) {}
|
||||
|
||||
fun exit() {
|
||||
val contentComponent = editor.contentComponent
|
||||
contentComponent.remove(aceCanvas)
|
||||
contentComponent.repaint()
|
||||
popupContainer?.dispose()
|
||||
finder.reset()
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
|
||||
Simply hit "ctrl+;", type a character, then type the matching character to Ace Jump. ]]>
|
||||
</description>
|
||||
<change-notes></change-notes>
|
||||
<change-notes>No longer tags "folded" regions and minor alignment adjustments.</change-notes>
|
||||
|
||||
<depends>com.intellij.modules.platform</depends>
|
||||
|
||||
@ -35,5 +35,8 @@
|
||||
<keyboard-shortcut keymap="Mac OS X 10.5+" first-keystroke="ctrl shift SEMICOLON"/>
|
||||
<keyboard-shortcut keymap="$default" first-keystroke="ctrl shift SEMICOLON"/>
|
||||
</action>
|
||||
<action id="AceJumpKeyAction"
|
||||
class="com.johnlindquist.acejump.AceJumpKeyAction"
|
||||
text="Motion Keys"/>
|
||||
</actions>
|
||||
</idea-plugin>
|
||||
</idea-plugin>
|
Loading…
Reference in New Issue
Block a user