1
0
Fork 0

Compare commits

...

8 Commits

Author SHA1 Message Date
chylex 35003b0bab
Improve tag order for non-QWERTY layouts 2021-11-21 05:29:43 +01:00
breandan dc137e4d23 add hop 2021-11-20 15:54:52 -05:00
breandan 6858b745e6 fixes #356 2021-11-14 21:22:06 -05:00
breandan 1505e98d66 update changelog, migrate deprecated and fix compiler error 2021-11-14 17:01:11 -05:00
breandan 2b7015474e
fix markdown 2021-11-14 16:32:44 -05:00
breandan 8d62b0d130
Merge pull request #384 from chylex/dpi
Workaround tag misalignment on high DPI screens
2021-11-14 16:25:55 -05:00
chylex 828940a53a
Update attribution in README 2021-11-14 22:24:03 +01:00
chylex 41071dbaf9
Workaround tag misalignment on high DPI screens
Fixes #362.
2021-11-14 21:07:26 +01:00
22 changed files with 318 additions and 85 deletions

View File

@ -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

View File

@ -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.

View File

@ -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"

View File

@ -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

View File

@ -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 &lt 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 &lt 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))

View File

@ -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)
}

View 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()
}
}

View File

@ -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
}
}

View File

@ -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) {

View File

@ -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)
}
}

View File

@ -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] }
)

View File

@ -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
/**

View File

@ -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())
}
}

View File

@ -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 }

View File

@ -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.
*/

View File

@ -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)
}
}
/**

View File

@ -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)

View File

@ -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>

View File

@ -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")
}

View File

@ -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)

View File

@ -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()
)
}

View File

@ -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()