Skip to content

Commit

Permalink
fixes #356
Browse files Browse the repository at this point in the history
  • Loading branch information
breandan committed Nov 15, 2021
1 parent 1505e98 commit 6858b74
Show file tree
Hide file tree
Showing 14 changed files with 246 additions and 18 deletions.
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## 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 helping diagnose!
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
4 changes: 2 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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("-")
Expand All @@ -70,4 +70,4 @@ intellij {
}

group = "org.acejump"
version = "3.8.4"
version = "3.8.5"
138 changes: 137 additions & 1 deletion src/main/kotlin/org/acejump/AceUtil.kt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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))

8 changes: 8 additions & 0 deletions src/main/kotlin/org/acejump/action/AceEditorAction.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
64 changes: 64 additions & 0 deletions src/main/kotlin/org/acejump/action/TagScroller.kt
Original file line number Diff line number Diff line change
@@ -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()
}
}
7 changes: 4 additions & 3 deletions src/main/kotlin/org/acejump/action/TagVisitor.kt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -66,4 +66,5 @@ internal class TagVisitor(private val editor: Editor, private val searchProcesso
editor.scrollingModel.scrollToCaret(RELATIVE)
return onlyResult
}

}
2 changes: 1 addition & 1 deletion src/main/kotlin/org/acejump/config/AceSettingsPanel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/org/acejump/search/SearchProcessor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand Down
16 changes: 15 additions & 1 deletion src/main/kotlin/org/acejump/session/Session.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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.
*/
Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -37,6 +41,7 @@
implementationClass="org.acejump.action.AceEditorAction$SearchLineEnds"/>
<editorActionHandler action="EditorLineEnd" order="first"
implementationClass="org.acejump.action.AceEditorAction$SearchLineEnds"/>

</extensions>

<actions>
Expand Down
3 changes: 1 addition & 2 deletions src/test/kotlin/ExternalUsageTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
4 changes: 1 addition & 3 deletions src/test/kotlin/LatencyTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
)
}
3 changes: 1 addition & 2 deletions src/test/kotlin/org/acejump/test/util/BaseTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down

0 comments on commit 6858b74

Please sign in to comment.