Compare commits
8 Commits
cb10ff7789
...
35003b0bab
Author | SHA1 | Date |
---|---|---|
chylex | 35003b0bab | |
breandan | dc137e4d23 | |
breandan | 6858b745e6 | |
breandan | 1505e98d66 | |
breandan | 2b7015474e | |
breandan | 8d62b0d130 | |
chylex | 828940a53a | |
chylex | 41071dbaf9 |
|
@ -2,10 +2,14 @@
|
|||
|
||||
## Unreleased
|
||||
|
||||
## 3.8.5
|
||||
|
||||
- Restores <kbd>Tab</kbd>/<kbd>Shift</kbd>+<kbd>Tab</kbd> functionality, [#356](https://github.com/acejump/AceJump/issues/356)
|
||||
|
||||
## 3.8.4
|
||||
|
||||
- Fixes Declaration Mode in Rider, [#379](https://github.com/acejump/AceJump/issues/379)
|
||||
- Thanks to @igor-akhmetov for the help diagnosing!
|
||||
- Fixes Declaration Mode in Rider, [#379](https://github.com/acejump/AceJump/issues/379), thanks to @igor-akhmetov for helping diagnose!
|
||||
- Fixes highlight offset on high-DPI screens, [#362](https://github.com/acejump/AceJump/issues/362), thanks to @chylex for [the PR](https://github.com/acejump/AceJump/pull/384)!
|
||||
|
||||
## 3.8.3
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ AceJump search is [smart case](http://ideavim.sourceforge.net/vim/usr_27.html#vi
|
|||
|
||||
## Tips
|
||||
|
||||
- Press <kbd>Tab</kbd> when searching to jump to the next group of matches in the editor. *This feature is supported in `3.6.3` and currently unavailable in `3.7.0`.*
|
||||
- Press <kbd>Tab</kbd> when searching to jump to the next group of matches in the editor. *This feature is unavailable in `3.6.4-3.8.4`.*
|
||||
|
||||
- If you make a mistake searching, just press <kbd>Backspace</kbd> to restart from scratch.
|
||||
|
||||
|
@ -178,6 +178,7 @@ The following plugins have a similar UI for navigating text and web browsing:
|
|||
| [ace-jump-mode](https://github.com/winterTTr/ace-jump-mode) | [⬇](https://melpa.org/#/ace-jump-mode) | [emacs](https://www.gnu.org/software/emacs/) | :x: | [Emacs Lisp](https://www.gnu.org/software/emacs/manual/eintr.html) |
|
||||
| [avy](https://github.com/abo-abo/avy) | [⬇](https://melpa.org/#/avy) | [emacs](https://www.gnu.org/software/emacs/) | :heavy_check_mark: | [Emacs Lisp](https://www.gnu.org/software/emacs/manual/eintr.html) |
|
||||
| [EasyMotion](https://github.com/easymotion/vim-easymotion) | [⬇](https://vimawesome.com/plugin/easymotion) | [Vim](http://www.vim.org/) | :x: | [Vimscript](http://learnvimscriptthehardway.stevelosh.com/) |
|
||||
| [Hop](https://github.com/phaazon/hop.nvim) | [⬇](https://github.com/phaazon/hop.nvim#installation) | [NeoVim](https://neovim.io/) | :heavy_check_mark: | [Lua](https://www.lua.org/) |
|
||||
| [Sublime EasyMotion](https://github.com/tednaleid/sublime-EasyMotion) | [⬇](https://packagecontrol.io/packages/EasyMotion) | [Sublime](https://www.sublimetext.com/) | :x: | [Python](https://www.python.org/) |
|
||||
| [AceJump](https://github.com/ice9js/ace-jump-sublime) | [⬇](https://packagecontrol.io/packages/AceJump) | [Sublime](https://www.sublimetext.com/) | :heavy_check_mark: | [Python](https://www.python.org/) |
|
||||
| [Jumpy](https://github.com/DavidLGoldberg/jumpy) | [⬇](https://atom.io/packages/jumpy) | [Atom](https://atom.io/) | :heavy_check_mark: | [CoffeeScript](http://coffeescript.org/) |
|
||||
|
@ -205,7 +206,7 @@ The following individuals have significantly improved AceJump through their cont
|
|||
|
||||
* [John Lindquist](https://github.com/johnlindquist) for creating AceJump and supporting it for many years.
|
||||
* [Breandan Considine](https://github.com/breandan) for maintaining the project and adding some new features.
|
||||
* [Daniel Chýlek](https://github.com/chylex) for numerous [performance optimizations](https://github.com/acejump/AceJump/pulls?q=is%3Apr+author%3Achylex), [bug fixes](https://github.com/acejump/AceJump/issues/348#issuecomment-739454920) and [refactoring](https://github.com/acejump/AceJump/pull/353).
|
||||
* [chylex](https://github.com/chylex) for numerous [performance optimizations](https://github.com/acejump/AceJump/pulls?q=is%3Apr+author%3Achylex), [bug fixes](https://github.com/acejump/AceJump/issues/348#issuecomment-739454920) and [refactoring](https://github.com/acejump/AceJump/pull/353).
|
||||
* [Alex Plate](https://github.com/AlexPl292) for submitting [several PRs](https://github.com/acejump/AceJump/pulls?q=is%3Apr+author%3AAlexPl292).
|
||||
* [Sven Speckmaier](https://github.com/svensp) for [improving](https://github.com/acejump/AceJump/pull/214) search latency.
|
||||
* [Stefan Monnier](https://www.iro.umontreal.ca/~monnier/) for algorithmic advice and maintaining Emacs for several years.
|
||||
|
|
|
@ -4,9 +4,9 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
|||
|
||||
plugins {
|
||||
idea apply true
|
||||
kotlin("jvm") version "1.5.30"
|
||||
id("org.jetbrains.intellij") version "1.1.6"
|
||||
id("org.jetbrains.changelog") version "1.3.0"
|
||||
kotlin("jvm") version "1.6.0-RC2"
|
||||
id("org.jetbrains.intellij") version "1.2.1"
|
||||
id("org.jetbrains.changelog") version "1.3.1"
|
||||
id("com.github.ben-manes.versions") version "0.39.0"
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ tasks {
|
|||
}
|
||||
|
||||
changelog {
|
||||
version.set("3.8.4")
|
||||
version.set("3.8.5")
|
||||
path.set("${project.projectDir}/CHANGES.md")
|
||||
header.set(provider { "[${project.version}] - ${date()}" })
|
||||
itemPrefix.set("-")
|
||||
|
@ -70,4 +70,4 @@ intellij {
|
|||
}
|
||||
|
||||
group = "org.acejump"
|
||||
version = "3.8.4"
|
||||
version = "3.8.5"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package org.acejump
|
||||
|
||||
import com.anyascii.AnyAscii
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.diff.util.DiffUtil.getLineCount
|
||||
import com.intellij.openapi.editor.*
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList
|
||||
import org.acejump.config.AceConfig
|
||||
import kotlin.math.*
|
||||
|
||||
/**
|
||||
* This annotation is a marker which means that the annotated function is
|
||||
|
@ -117,3 +119,137 @@ fun MutableMap<Editor, IntArrayList>.clone(): MutableMap<Editor, IntArrayList> {
|
|||
|
||||
return clone
|
||||
}
|
||||
|
||||
fun Editor.offsetCenter(first: Int, second: Int): LogicalPosition {
|
||||
val firstIndexLine = offsetToLogicalPosition(first).line
|
||||
val lastIndexLine = offsetToLogicalPosition(second).line
|
||||
val center = (firstIndexLine + lastIndexLine) / 2
|
||||
return offsetToLogicalPosition(getLineStartOffset(center))
|
||||
}
|
||||
|
||||
fun Editor.getView(): IntRange {
|
||||
val firstVisibleLine = max(0, getVisualLineAtTopOfScreen() - 1)
|
||||
val firstLine = visualLineToLogicalLine(firstVisibleLine)
|
||||
val startOffset = getLineStartOffset(firstLine)
|
||||
|
||||
val height = getScreenHeight() + 2
|
||||
val lastLine = visualLineToLogicalLine(firstVisibleLine + height)
|
||||
var endOffset = getLineEndOffset(lastLine, true)
|
||||
endOffset = normalizeOffset(lastLine, endOffset)
|
||||
endOffset = min(max(0, document.textLength - 1), endOffset + 1)
|
||||
|
||||
return startOffset..endOffset
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the offset of the start of the requested line.
|
||||
*
|
||||
* @param line The logical line to get the start offset for.
|
||||
*
|
||||
* @return 0 if line is < 0, file size of line is bigger than file, else the
|
||||
* start offset for the line
|
||||
*/
|
||||
|
||||
fun Editor.getLineStartOffset(line: Int) =
|
||||
when {
|
||||
line < 0 -> 0
|
||||
line >= getLineCount(document) -> getFileSize()
|
||||
else -> document.getLineStartOffset(line)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the offset of the end of the requested line.
|
||||
*
|
||||
* @param line The logical line to get the end offset for
|
||||
*
|
||||
* @param allowEnd True include newline
|
||||
*
|
||||
* @return 0 if line is < 0, file size of line is bigger than file, else the
|
||||
* end offset for the line
|
||||
*/
|
||||
|
||||
fun Editor.getLineEndOffset(line: Int, allowEnd: Boolean = true) =
|
||||
when {
|
||||
line < 0 -> 0
|
||||
line >= getLineCount(document) -> getFileSize(allowEnd)
|
||||
else -> document.getLineEndOffset(line) - if (allowEnd) 0 else 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of lines than can be displayed on the screen at one time.
|
||||
* This is rounded down to the nearest whole line if there is a partial line
|
||||
* visible at the bottom of the screen.
|
||||
*
|
||||
* @return The number of screen lines
|
||||
*/
|
||||
|
||||
fun Editor.getScreenHeight() =
|
||||
(scrollingModel.visibleArea.y + scrollingModel.visibleArea.height -
|
||||
getVisualLineAtTopOfScreen() * lineHeight) / lineHeight
|
||||
|
||||
|
||||
/**
|
||||
* This is a set of helper methods for working with editors.
|
||||
* All line and column values are zero based.
|
||||
*/
|
||||
|
||||
fun Editor.getVisualLineAtTopOfScreen() =
|
||||
(scrollingModel.verticalScrollOffset + lineHeight - 1) / lineHeight
|
||||
|
||||
/**
|
||||
* Gets the actual number of characters in the file
|
||||
*
|
||||
* @param countNewLines True include newline
|
||||
*
|
||||
* @return The file's character count
|
||||
*/
|
||||
|
||||
fun Editor.getFileSize(countNewLines: Boolean = false): Int {
|
||||
val len = document.textLength
|
||||
val doc = document.charsSequence
|
||||
return if (countNewLines || len == 0 || doc[len - 1] != '\n') len else len - 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the supplied logical line is within the range 0 (incl) and the
|
||||
* number of logical lines in the file (excl).
|
||||
*
|
||||
* @param line The logical line number to normalize
|
||||
*
|
||||
* @return The normalized logical line number
|
||||
*/
|
||||
|
||||
fun Editor.normalizeLine(line: Int) = max(0, min(line, getLineCount(document) - 1))
|
||||
|
||||
/**
|
||||
* Converts a visual line number to a logical line number.
|
||||
*
|
||||
* @param line The visual line number to convert
|
||||
*
|
||||
* @return The logical line number
|
||||
*/
|
||||
|
||||
fun Editor.visualLineToLogicalLine(line: Int) =
|
||||
normalizeLine(visualToLogicalPosition(
|
||||
VisualPosition(line.coerceAtLeast(0), 0)).line)
|
||||
|
||||
|
||||
/**
|
||||
* Ensures that the supplied offset for the given logical line is within the
|
||||
* range for the line. If allowEnd is true, the range will allow for the offset
|
||||
* to be one past the last character on the line.
|
||||
*
|
||||
* @param line The logical line number
|
||||
*
|
||||
* @param offset The offset to normalize
|
||||
*
|
||||
* @param allowEnd true if the offset can be one past the last character on the
|
||||
* line, false if not
|
||||
*
|
||||
* @return The normalized column number
|
||||
*/
|
||||
|
||||
fun Editor.normalizeOffset(line: Int, offset: Int, allowEnd: Boolean = true) =
|
||||
if (getFileSize(allowEnd) == 0) 0 else
|
||||
max(min(offset, getLineEndOffset(line, allowEnd)), getLineStartOffset(line))
|
||||
|
||||
|
|
|
@ -44,6 +44,14 @@ sealed class AceEditorAction(private val originalHandler: EditorActionHandler):
|
|||
override fun run(session: Session) = session.visitNextTag()
|
||||
}
|
||||
|
||||
class ScrollToNextScreenful(originalHandler: EditorActionHandler): AceEditorAction(originalHandler) {
|
||||
override fun run(session: Session) { session.scrollToNextScreenful() }
|
||||
}
|
||||
|
||||
class ScrollToPreviousScreenful(originalHandler: EditorActionHandler): AceEditorAction(originalHandler) {
|
||||
override fun run(session: Session) { session.scrollToPreviousScreenful() }
|
||||
}
|
||||
|
||||
class SearchLineStarts(originalHandler: EditorActionHandler): AceEditorAction(originalHandler) {
|
||||
override fun run(session: Session) = session.startRegexSearch(LINE_STARTS, WHOLE_FILE)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
package org.acejump.action
|
||||
|
||||
import com.intellij.openapi.editor.*
|
||||
import org.acejump.*
|
||||
import org.acejump.search.SearchProcessor
|
||||
|
||||
internal class TagScroller(private val editor: Editor, private val searchProcessor: SearchProcessor) {
|
||||
fun scroll(forward: Boolean = true): Boolean {
|
||||
val position = if (forward) findNextPosition() else findPreviousPosition()
|
||||
return if (position != null) true.also { scrollTo(position) } else false
|
||||
}
|
||||
|
||||
private fun scrollTo(position: LogicalPosition) = editor.run {
|
||||
scrollingModel.disableAnimation()
|
||||
scrollingModel.scrollTo(position, ScrollType.CENTER)
|
||||
|
||||
val firstInView = textMatches.first { it in editor.getView() }
|
||||
val horizontalOffset = offsetToLogicalPosition(firstInView).column
|
||||
if (horizontalOffset > scrollingModel.visibleArea.width)
|
||||
scrollingModel.scrollHorizontally(horizontalOffset)
|
||||
}
|
||||
|
||||
val textMatches by lazy { searchProcessor.results[editor]!! }
|
||||
|
||||
private fun findPreviousPosition(): LogicalPosition? {
|
||||
val prevIndex = textMatches.toList().dropLastWhile { it > editor.getView().first }
|
||||
.lastOrNull() ?: textMatches.lastOrNull() ?: return null
|
||||
|
||||
val prevLineNum = editor.offsetToLogicalPosition(prevIndex).line
|
||||
|
||||
// Try to capture as many previous results as will fit in a screenful
|
||||
fun maximizeCoverageOfPreviousOccurrence(): LogicalPosition {
|
||||
val minVisibleLine = prevLineNum - editor.getScreenHeight()
|
||||
val firstVisibleIndex = editor.getLineStartOffset(minVisibleLine)
|
||||
val firstIndex = textMatches.dropWhile { it < firstVisibleIndex }.first()
|
||||
return editor.offsetCenter(firstIndex, prevIndex)
|
||||
}
|
||||
|
||||
return maximizeCoverageOfPreviousOccurrence()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the center of the next set of results that will fit in the editor.
|
||||
* [textMatches] must be sorted prior to using Scroller. If [textMatches] have
|
||||
* not previously been sorted, the result of calling this method is undefined.
|
||||
*/
|
||||
|
||||
private fun findNextPosition(): LogicalPosition? {
|
||||
val nextIndex = textMatches.dropWhile { it <= editor.getView().last }
|
||||
.firstOrNull() ?: textMatches.firstOrNull() ?: return null
|
||||
|
||||
val nextLineNum = editor.offsetToLogicalPosition(nextIndex).line
|
||||
|
||||
// Try to capture as many subsequent results as will fit in a screenful
|
||||
fun maximizeCoverageOfNextOccurrence(): LogicalPosition {
|
||||
val maxVisibleLine = nextLineNum + editor.getScreenHeight()
|
||||
val lastVisibleIndex = editor.getLineEndOffset(maxVisibleLine, true)
|
||||
val lastIndex = textMatches.toList().dropLastWhile { it > lastVisibleIndex }.last()
|
||||
return editor.offsetCenter(nextIndex, lastIndex)
|
||||
}
|
||||
|
||||
return maximizeCoverageOfNextOccurrence()
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
package org.acejump.action
|
||||
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.ScrollType.RELATIVE
|
||||
import com.intellij.openapi.editor.SelectionModel
|
||||
import com.intellij.openapi.editor.*
|
||||
import com.intellij.openapi.editor.ScrollType.*
|
||||
import org.acejump.*
|
||||
import org.acejump.search.SearchProcessor
|
||||
import org.acejump.search.Tag
|
||||
import kotlin.math.abs
|
||||
|
@ -40,9 +40,7 @@ internal class TagVisitor(private val editor: Editor, private val searchProcesso
|
|||
val targetOffset = listOfNotNull(
|
||||
results.getOrNull(index - 1),
|
||||
results.getOrNull(index)
|
||||
).minBy {
|
||||
abs(it - caret)
|
||||
}
|
||||
).minByOrNull { abs(it - caret) }
|
||||
|
||||
if (targetOffset != null)
|
||||
editor.scrollingModel.scrollTo(editor.offsetToLogicalPosition(targetOffset), RELATIVE)
|
||||
|
@ -68,4 +66,5 @@ internal class TagVisitor(private val editor: Editor, private val searchProcesso
|
|||
editor.scrollingModel.scrollToCaret(RELATIVE)
|
||||
return onlyResult
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -144,7 +144,7 @@ internal class AceSettingsPanel {
|
|||
|
||||
// Removal pending support for https://youtrack.jetbrains.com/issue/KT-8575
|
||||
|
||||
private operator fun JTextComponent.getValue(a: AceSettingsPanel, p: KProperty<*>) = text.toLowerCase()
|
||||
private operator fun JTextComponent.getValue(a: AceSettingsPanel, p: KProperty<*>) = text.lowercase()
|
||||
private operator fun JTextComponent.setValue(a: AceSettingsPanel, p: KProperty<*>, s: String) = setText(s)
|
||||
|
||||
private operator fun ColorPanel.getValue(a: AceSettingsPanel, p: KProperty<*>) = selectedColor
|
||||
|
@ -153,7 +153,7 @@ internal class AceSettingsPanel {
|
|||
private operator fun JCheckBox.getValue(a: AceSettingsPanel, p: KProperty<*>) = isSelected
|
||||
private operator fun JCheckBox.setValue(a: AceSettingsPanel, p: KProperty<*>, selected: Boolean) = setSelected(selected)
|
||||
|
||||
private operator fun <T> ComboBox<T>.getValue(a: AceSettingsPanel, p: KProperty<*>) = selectedItem as T
|
||||
private inline operator fun <reified T> ComboBox<T>.getValue(a: AceSettingsPanel, p: KProperty<*>) = selectedItem as T
|
||||
private operator fun <T> ComboBox<T>.setValue(a: AceSettingsPanel, p: KProperty<*>, item: T) = setSelectedItem(item)
|
||||
|
||||
private inline fun <reified T: Enum<T>> ComboBox<T>.setupEnumItems(crossinline onChanged: (T) -> Unit) {
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
package org.acejump.input
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
|
||||
import java.awt.geom.Point2D
|
||||
import kotlin.math.floor
|
||||
|
||||
/**
|
||||
* Defines common keyboard layouts. Each layout has a key priority order,
|
||||
* based on each key's distance from the home row and how ergonomically
|
||||
* based on each key's distance from the home row and how ergonomically
|
||||
* difficult they are to press.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
|
@ -19,5 +24,38 @@ enum class KeyLayout(internal val rows: Array<String>, priority: String) {
|
|||
internal val allChars = rows.joinToString("").toCharArray().apply(CharArray::sort).joinToString("")
|
||||
internal val allPriorities = priority.mapIndexed { index, char -> char to index }.toMap()
|
||||
|
||||
internal inline fun priority(crossinline tagToChar: (String) -> Char): (String) -> Int? = { allPriorities[tagToChar(it)] }
|
||||
private val keyDistances: Map<Char, Object2IntMap<Char>> by lazy {
|
||||
val keyDistanceMap = mutableMapOf<Char, Object2IntMap<Char>>()
|
||||
val keyLocations = mutableMapOf<Char, Point2D>()
|
||||
|
||||
for ((rowIndex, rowChars) in rows.withIndex()) {
|
||||
val keyY = rowIndex * 1.2F // Slightly increase cost of traveling between rows.
|
||||
|
||||
for ((columnIndex, char) in rowChars.withIndex()) {
|
||||
val keyX = columnIndex + (0.25F * rowIndex) // Assume a 1/4-key uniform stagger.
|
||||
keyLocations[char] = Point2D.Float(keyX, keyY)
|
||||
}
|
||||
}
|
||||
|
||||
for (fromChar in allChars) {
|
||||
val distances = Object2IntOpenHashMap<Char>()
|
||||
val fromLocation = keyLocations.getValue(fromChar)
|
||||
|
||||
for (toChar in allChars) {
|
||||
distances[toChar] = floor(2F * fromLocation.distanceSq(keyLocations.getValue(toChar))).toInt()
|
||||
}
|
||||
|
||||
keyDistanceMap[fromChar] = distances
|
||||
}
|
||||
|
||||
keyDistanceMap
|
||||
}
|
||||
|
||||
internal inline fun priority(crossinline tagToChar: (String) -> Char): (String) -> Int? {
|
||||
return { allPriorities[tagToChar(it)] }
|
||||
}
|
||||
|
||||
internal fun distanceBetweenKeys(char1: Char, char2: Char): Int {
|
||||
return keyDistances.getValue(char1).getValue(char2)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,48 +7,6 @@ import org.acejump.config.AceSettings
|
|||
* with repeated keys (ex. FF, JJ) or adjacent keys (ex. GH, UJ).
|
||||
*/
|
||||
internal object KeyLayoutCache {
|
||||
/**
|
||||
* Stores keys ordered by proximity to other keys for the QWERTY layout.
|
||||
* TODO: Support more layouts, perhaps generate automatically.
|
||||
*/
|
||||
private val qwertyCharacterDistances = mapOf(
|
||||
'j' to "jikmnhuolbgypvftcdrxsezawq8796054321",
|
||||
'f' to "ftgvcdryhbxseujnzawqikmolp5463728190",
|
||||
'k' to "kolmjipnhubgyvftcdrxsezawq9807654321",
|
||||
'd' to "drfcxsetgvzawyhbqujnikmolp4352617890",
|
||||
'l' to "lkopmjinhubgyvftcdrxsezawq0987654321",
|
||||
's' to "sedxzawrfcqtgvyhbujnikmolp3241567890",
|
||||
'a' to "aqwszedxrfctgvyhbujnikmolp1234567890",
|
||||
'h' to "hujnbgyikmvftolcdrpxsezawq6758493021",
|
||||
'g' to "gyhbvftujncdrikmxseolzawpq5647382910",
|
||||
'y' to "yuhgtijnbvfrokmcdeplxswzaq6758493021",
|
||||
't' to "tygfruhbvcdeijnxswokmzaqpl5647382910",
|
||||
'u' to "uijhyokmnbgtplvfrcdexswzaq7869504321",
|
||||
'r' to "rtfdeygvcxswuhbzaqijnokmpl4536271890",
|
||||
'n' to "nbhjmvgyuiklocftpxdrzseawq7685940321",
|
||||
'v' to "vcfgbxdrtyhnzseujmawikqolp5463728190",
|
||||
'm' to "mnjkbhuilvgyopcftxdrzseawq8970654321",
|
||||
'c' to "cxdfvzsertgbawyhnqujmikolp4352617890",
|
||||
'b' to "bvghncftyujmxdrikzseolawqp6574839201",
|
||||
'i' to "iokjuplmnhybgtvfrcdexswzaq8970654321",
|
||||
'e' to "erdswtfcxzaqygvuhbijnokmpl3425167890",
|
||||
'x' to "xzsdcawerfvqtgbyhnujmikolp3241567890",
|
||||
'z' to "zasxqwedcrfvtgbyhnujmikolp1234567890",
|
||||
'o' to "oplkimjunhybgtvfrcdexswzaq9087654321",
|
||||
'w' to "wesaqrdxztfcygvuhbijnokmpl2314567890",
|
||||
'p' to "plokimjunhybgtvfrcdexswzaq0987654321",
|
||||
'q' to "qwaeszrdxtfcygvuhbijnokmpl1234567890",
|
||||
'1' to "1234567890qawzsexdrcftvgybhunjimkolp",
|
||||
'2' to "2134567890qwasezxdrcftvgybhunjimkolp",
|
||||
'3' to "3241567890weqasdrzxcftvgybhunjimkolp",
|
||||
'4' to "4352617890erwsdftqazxcvgybhunjimkolp",
|
||||
'5' to "5463728190rtedfgywsxcvbhuqaznjimkolp",
|
||||
'6' to "6574839201tyrfghuedcvbnjiwsxmkoqazlp",
|
||||
'7' to "7685940321yutghjirfvbnmkoedclpwsxqaz",
|
||||
'8' to "8796054321uiyhjkotgbnmlprfvedcwsxqaz",
|
||||
'9' to "9807654321ioujklpyhnmtgbrfvedcwsxqaz",
|
||||
'0' to "0987654321opiklujmyhntgbrfvedcwsxqaz").mapValues { (_, v) -> v.mapIndexed { index, char -> char to index }.toMap() }
|
||||
|
||||
/**
|
||||
* Sorts tags according to current keyboard layout settings, and some predefined rules that force tags with digits, and tags with two
|
||||
* keys far apart, to be sorted after other (easier to type) tags.
|
||||
|
@ -74,7 +32,7 @@ internal object KeyLayoutCache {
|
|||
fun reset(settings: AceSettings) {
|
||||
tagOrder = compareBy(
|
||||
{ it[0].isDigit() || it[1].isDigit() },
|
||||
{ qwertyCharacterDistances.getValue(it[0]).getValue(it[1]) },
|
||||
{ settings.layout.distanceBetweenKeys(it[0], it[1]) },
|
||||
settings.layout.priority { it[0] }
|
||||
)
|
||||
|
||||
|
|
|
@ -55,10 +55,10 @@ internal class SearchProcessor private constructor(
|
|||
}
|
||||
}
|
||||
|
||||
var query = query
|
||||
var query: SearchQuery = query
|
||||
private set
|
||||
|
||||
var results = results
|
||||
var results: MutableMap<Editor, IntArrayList> = results
|
||||
private set
|
||||
|
||||
/**
|
||||
|
|
|
@ -73,7 +73,7 @@ internal class Solver private constructor(
|
|||
private val newTagIndices = newResults.keys.associateWith { IntOpenHashSet() }
|
||||
|
||||
private var allWordFragments =
|
||||
HashSet<String>(allResults.values.sumBy(IntList::size)).apply {
|
||||
HashSet<String>(allResults.values.sumOf(IntList::size)).apply {
|
||||
for ((editor, offsets) in allResults) {
|
||||
val iter = offsets.iterator()
|
||||
while (iter.hasNext()) forEachWordFragment(editor, iter.nextInt()) { add(it) }
|
||||
|
@ -119,7 +119,7 @@ internal class Solver private constructor(
|
|||
}
|
||||
|
||||
var totalAssigned = 0
|
||||
val totalResults = newResults.values.sumBy(IntList::size)
|
||||
val totalResults = newResults.values.sumOf(IntList::size)
|
||||
|
||||
for (tag in sortedTags) {
|
||||
if (totalAssigned == totalResults) {
|
||||
|
@ -191,7 +191,7 @@ internal class Solver private constructor(
|
|||
val builder = StringBuilder(1 + right - left)
|
||||
|
||||
for (i in left..right) {
|
||||
builder.append(chars[i].toLowerCase())
|
||||
builder.append(chars[i].lowercase())
|
||||
callback(builder.toString())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ internal class Tagger(private val editors: List<Editor>) {
|
|||
*/
|
||||
fun markOrJump(query: SearchQuery, results: Map<Editor, IntList>): TaggingResult {
|
||||
val isRegex = query is SearchQuery.RegularExpression
|
||||
val queryText = if (isRegex) " ${query.rawText}" else query.rawText[0] + query.rawText.drop(1).toLowerCase()
|
||||
val queryText = if (isRegex) " ${query.rawText}" else query.rawText[0] + query.rawText.drop(1).lowercase()
|
||||
|
||||
val availableTags = allPossibleTags.filter { !queryText.endsWith(it[0]) && it !in tagMap }
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import com.intellij.openapi.editor.colors.EditorColors.CARET_COLOR
|
|||
import com.intellij.util.containers.ContainerUtil
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList
|
||||
import org.acejump.*
|
||||
import org.acejump.action.TagScroller
|
||||
import org.acejump.action.TagJumper
|
||||
import org.acejump.action.TagVisitor
|
||||
import org.acejump.boundaries.Boundaries
|
||||
|
@ -65,7 +66,10 @@ class Session(private val mainEditor: Editor, private val jumpEditors: List<Edit
|
|||
|
||||
private val tagVisitor
|
||||
get() = searchProcessor?.let { TagVisitor(mainEditor, it, tagJumper) }
|
||||
|
||||
|
||||
private val tagScroller
|
||||
get() = searchProcessor?.let { TagScroller(mainEditor, it) }
|
||||
|
||||
private val textHighlighter = TextHighlighter()
|
||||
private val tagCanvases = jumpEditors.associateWith(::TagCanvas)
|
||||
|
||||
|
@ -236,6 +240,16 @@ class Session(private val mainEditor: Editor, private val jumpEditors: List<Edit
|
|||
fun visitNextTag() =
|
||||
if (tagVisitor?.visitNext() == true) end() else Unit
|
||||
|
||||
/**
|
||||
* See [TagVisitor.visitPrevious]. If there are no tags, nothing happens.
|
||||
*/
|
||||
fun scrollToNextScreenful() = tagScroller?.scroll(true)
|
||||
|
||||
/**
|
||||
* See [TagVisitor.visitNext]. If there are no tags, nothing happens.
|
||||
*/
|
||||
fun scrollToPreviousScreenful() = tagScroller?.scroll(false)
|
||||
|
||||
/**
|
||||
* Ends this session.
|
||||
*/
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.acejump.view
|
|||
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.ui.ColorUtil
|
||||
import com.intellij.ui.JreHiDpiUtil
|
||||
import com.intellij.ui.scale.JBUIScale
|
||||
import org.acejump.boundaries.EditorOffsetCache
|
||||
import org.acejump.boundaries.StandardBoundaries.VISIBLE_ON_SCREEN
|
||||
|
@ -39,9 +40,9 @@ class TagMarker(
|
|||
val hasSpaceRight = offset + 1 >= chars.length || chars[offset + 1].isWhitespace()
|
||||
|
||||
val displayedTag = if (literalQueryText != null && literalQueryText.last().equals(tag.first(), ignoreCase = true))
|
||||
tag.drop(1).toUpperCase()
|
||||
tag.drop(1).uppercase()
|
||||
else
|
||||
tag.toUpperCase()
|
||||
tag.uppercase()
|
||||
|
||||
return TagMarker(displayedTag, offset, offset + max(0, matching - 1), tag.length - displayedTag.length, hasSpaceRight)
|
||||
}
|
||||
|
@ -51,7 +52,16 @@ class TagMarker(
|
|||
*/
|
||||
private fun drawHighlight(g: Graphics2D, rect: Rectangle, color: Color) {
|
||||
g.color = color
|
||||
g.fillRoundRect(rect.x, rect.y, rect.width, rect.height + 1, ARC, ARC)
|
||||
|
||||
// Workaround for misalignment on high DPI screens.
|
||||
if (JreHiDpiUtil.isJreHiDPI(g)) {
|
||||
g.translate(0.0, -0.5)
|
||||
g.fillRoundRect(rect.x, rect.y, rect.width, rect.height + 1, ARC, ARC)
|
||||
g.translate(0.0, 0.5)
|
||||
}
|
||||
else {
|
||||
g.fillRoundRect(rect.x, rect.y, rect.width, rect.height + 1, ARC, ARC)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -105,7 +105,7 @@ internal class TextHighlighter {
|
|||
|
||||
val queryText = " " +
|
||||
if (query is SearchQuery.RegularExpression) query.toRegex().toString()
|
||||
else query.rawText[0] + query.rawText.drop(1).toLowerCase()
|
||||
else query.rawText[0] + query.rawText.drop(1).lowercase()
|
||||
val label2 = NotificationLabel(queryText)
|
||||
|
||||
val label3 = NotificationLabel(
|
||||
|
@ -164,7 +164,7 @@ internal class TextHighlighter {
|
|||
val end = EditorOffsetCache.Uncached.offsetToXY(editor, endOffset)
|
||||
|
||||
g.color = AceConfig.textHighlightColor
|
||||
g.fillRect(start.x, start.y + 1, end.x - start.x, editor.lineHeight - 1)
|
||||
g.fillRect(start.x, start.y, end.x - start.x, editor.lineHeight)
|
||||
|
||||
g.color = AceConfig.tagBackgroundColor
|
||||
g.drawRect(start.x, start.y, end.x - start.x, editor.lineHeight)
|
||||
|
@ -213,7 +213,7 @@ internal class TextHighlighter {
|
|||
val lastCharWidth = editor.component.getFontMetrics(font).charWidth(char)
|
||||
|
||||
g.color = AceConfig.textHighlightColor
|
||||
g.fillRect(pos.x, pos.y + 1, lastCharWidth, editor.lineHeight - 1)
|
||||
g.fillRect(pos.x, pos.y, lastCharWidth, editor.lineHeight)
|
||||
|
||||
g.color = AceConfig.tagBackgroundColor
|
||||
g.drawRect(pos.x, pos.y, lastCharWidth, editor.lineHeight)
|
||||
|
|
|
@ -27,6 +27,10 @@
|
|||
implementationClass="org.acejump.action.AceEditorAction$SelectBackward"/>
|
||||
<editorActionHandler action="EditorEnter" order="first"
|
||||
implementationClass="org.acejump.action.AceEditorAction$SelectForward"/>
|
||||
<editorActionHandler action="EditorTab" order="first"
|
||||
implementationClass="org.acejump.action.AceEditorAction$ScrollToNextScreenful"/>
|
||||
<editorActionHandler action="EditorUnindentSelection" order="first"
|
||||
implementationClass="org.acejump.action.AceEditorAction$ScrollToPreviousScreenful"/>
|
||||
<editorActionHandler action="EditorUp" order="first"
|
||||
implementationClass="org.acejump.action.AceEditorAction$SearchLineStarts"/>
|
||||
<editorActionHandler action="EditorLeft" order="first"
|
||||
|
@ -37,6 +41,7 @@
|
|||
implementationClass="org.acejump.action.AceEditorAction$SearchLineEnds"/>
|
||||
<editorActionHandler action="EditorLineEnd" order="first"
|
||||
implementationClass="org.acejump.action.AceEditorAction$SearchLineEnds"/>
|
||||
|
||||
</extensions>
|
||||
|
||||
<actions>
|
||||
|
|
|
@ -65,7 +65,7 @@ class AceTest : BaseTest() {
|
|||
fun `test shift selection`() {
|
||||
"<caret>testing 1234".search("4")
|
||||
|
||||
typeAndWaitForResults(session.tags[0].key.toUpperCase())
|
||||
typeAndWaitForResults(session.tags[0].key.uppercase())
|
||||
|
||||
myFixture.checkResult("<selection>testing 123<caret></selection>4")
|
||||
}
|
||||
|
|
|
@ -22,8 +22,7 @@ class ExternalUsageTest: BaseTest() {
|
|||
fun `test externally tagged results and listener notification`() {
|
||||
makeEditor("test externally tagged results")
|
||||
|
||||
SessionManager.start(myFixture.editor)
|
||||
.markResults(sortedSetOf(4, 10, 15))
|
||||
SessionManager.start(myFixture.editor).markResults(sortedSetOf(4, 10, 15))
|
||||
|
||||
TestCase.assertEquals(3, session.tags.size)
|
||||
|
||||
|
|
|
@ -38,8 +38,6 @@ class LatencyTest: BaseTest() {
|
|||
)
|
||||
|
||||
fun `test lorem ipsum latency`() = `test tag latency`(
|
||||
File(
|
||||
javaClass.classLoader.getResource("lipsum.txt")!!.file
|
||||
).readText()
|
||||
File(javaClass.classLoader.getResource("lipsum.txt")!!.file).readText()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -19,8 +19,7 @@ abstract class BaseTest: FileEditorManagerTestCase() {
|
|||
}
|
||||
}
|
||||
|
||||
protected val session
|
||||
get() = SessionManager[myFixture.editor]!!
|
||||
protected val session get() = SessionManager[myFixture.editor]!!
|
||||
|
||||
override fun tearDown() {
|
||||
resetEditor()
|
||||
|
|
Loading…
Reference in New Issue