mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-05-25 18:34:08 +02:00
Rewrite displayLocationInfo to avoid findNextWord
Simplifies and fixes counting words, especially when selection intersects words. Also moves implementation to engine and adds more tests.
This commit is contained in:
parent
8eef802ac7
commit
b5937e885d
src
main/java/com/maddyhome/idea/vim
test/java/org/jetbrains/plugins/ideavim/action
vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api
@ -32,17 +32,13 @@ import com.maddyhome.idea.vim.api.ExecutionContext
|
|||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.VimFileBase
|
import com.maddyhome.idea.vim.api.VimFileBase
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.common.TextRange
|
|
||||||
import com.maddyhome.idea.vim.group.LastTabService.Companion.getInstance
|
import com.maddyhome.idea.vim.group.LastTabService.Companion.getInstance
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||||
import com.maddyhome.idea.vim.helper.MessageHelper.message
|
import com.maddyhome.idea.vim.helper.MessageHelper.message
|
||||||
import com.maddyhome.idea.vim.helper.countWords
|
|
||||||
import com.maddyhome.idea.vim.helper.fileSize
|
|
||||||
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
|
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||||
import com.maddyhome.idea.vim.newapi.execute
|
import com.maddyhome.idea.vim.newapi.execute
|
||||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -265,92 +261,6 @@ class FileGroup : VimFileBase() {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun displayLocationInfo(vimEditor: VimEditor) {
|
|
||||||
val editor = (vimEditor as IjVimEditor).editor
|
|
||||||
val msg = StringBuilder()
|
|
||||||
val doc = editor.document
|
|
||||||
|
|
||||||
if (injector.vimState.mode !is VISUAL) {
|
|
||||||
val lp = editor.caretModel.logicalPosition
|
|
||||||
val col = editor.caretModel.offset - doc.getLineStartOffset(lp.line)
|
|
||||||
var endoff = doc.getLineEndOffset(lp.line)
|
|
||||||
if (endoff < editor.fileSize && doc.charsSequence[endoff] == '\n') {
|
|
||||||
endoff--
|
|
||||||
}
|
|
||||||
val ecol = endoff - doc.getLineStartOffset(lp.line)
|
|
||||||
val elp = editor.offsetToLogicalPosition(endoff)
|
|
||||||
|
|
||||||
msg.append("Col ").append(col + 1)
|
|
||||||
if (col != lp.column) {
|
|
||||||
msg.append("-").append(lp.column + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.append(" of ").append(ecol + 1)
|
|
||||||
if (ecol != elp.column) {
|
|
||||||
msg.append("-").append(elp.column + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
val lline = editor.caretModel.logicalPosition.line
|
|
||||||
val total = IjVimEditor(editor).lineCount()
|
|
||||||
|
|
||||||
msg.append("; Line ").append(lline + 1).append(" of ").append(total)
|
|
||||||
|
|
||||||
val cp = countWords(vimEditor)
|
|
||||||
|
|
||||||
msg.append("; Word ").append(cp.position).append(" of ").append(cp.count)
|
|
||||||
|
|
||||||
val offset = editor.caretModel.offset
|
|
||||||
val size = editor.fileSize
|
|
||||||
|
|
||||||
msg.append("; Character ").append(offset + 1).append(" of ").append(size)
|
|
||||||
} else {
|
|
||||||
msg.append("Selected ")
|
|
||||||
|
|
||||||
val vr = TextRange(
|
|
||||||
editor.selectionModel.blockSelectionStarts,
|
|
||||||
editor.selectionModel.blockSelectionEnds
|
|
||||||
)
|
|
||||||
vr.normalize()
|
|
||||||
|
|
||||||
val lines: Int
|
|
||||||
var cp = countWords(vimEditor)
|
|
||||||
val words = cp.count
|
|
||||||
var word = 0
|
|
||||||
if (vr.isMultiple) {
|
|
||||||
lines = vr.size()
|
|
||||||
val cols = vr.maxLength
|
|
||||||
|
|
||||||
msg.append(cols).append(" Cols; ")
|
|
||||||
|
|
||||||
for (i in 0 until vr.size()) {
|
|
||||||
cp = countWords(vimEditor, vr.startOffsets[i], (vr.endOffsets[i] - 1).toLong())
|
|
||||||
word += cp.count
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val slp = editor.offsetToLogicalPosition(vr.startOffset)
|
|
||||||
val elp = editor.offsetToLogicalPosition(vr.endOffset)
|
|
||||||
|
|
||||||
lines = elp.line - slp.line + 1
|
|
||||||
|
|
||||||
cp = countWords(vimEditor, vr.startOffset, (vr.endOffset - 1).toLong())
|
|
||||||
word = cp.count
|
|
||||||
}
|
|
||||||
|
|
||||||
val total = IjVimEditor(editor).lineCount()
|
|
||||||
|
|
||||||
msg.append(lines).append(" of ").append(total).append(" Lines")
|
|
||||||
|
|
||||||
msg.append("; ").append(word).append(" of ").append(words).append(" Words")
|
|
||||||
|
|
||||||
val chars = vr.selectionCount
|
|
||||||
val size = editor.fileSize
|
|
||||||
|
|
||||||
msg.append("; ").append(chars).append(" of ").append(size).append(" Characters")
|
|
||||||
}
|
|
||||||
|
|
||||||
VimPlugin.showMessage(msg.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun displayFileInfo(vimEditor: VimEditor, fullPath: Boolean) {
|
override fun displayFileInfo(vimEditor: VimEditor, fullPath: Boolean) {
|
||||||
val editor = (vimEditor as IjVimEditor).editor
|
val editor = (vimEditor as IjVimEditor).editor
|
||||||
val msg = StringBuilder()
|
val msg = StringBuilder()
|
||||||
|
@ -10,11 +10,9 @@ package com.maddyhome.idea.vim.helper
|
|||||||
|
|
||||||
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx
|
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx
|
||||||
import com.intellij.codeInsight.daemon.impl.HighlightInfo
|
import com.intellij.codeInsight.daemon.impl.HighlightInfo
|
||||||
import com.intellij.openapi.diagnostic.logger
|
|
||||||
import com.intellij.openapi.editor.Caret
|
import com.intellij.openapi.editor.Caret
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.spellchecker.SpellCheckerSeveritiesProvider
|
import com.intellij.spellchecker.SpellCheckerSeveritiesProvider
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
|
||||||
import com.maddyhome.idea.vim.api.getLineEndOffset
|
import com.maddyhome.idea.vim.api.getLineEndOffset
|
||||||
import com.maddyhome.idea.vim.api.globalOptions
|
import com.maddyhome.idea.vim.api.globalOptions
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
@ -52,48 +50,6 @@ private fun containsUpperCase(pattern: String): Boolean {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This counts all the words in the file.
|
|
||||||
*/
|
|
||||||
fun countWords(
|
|
||||||
vimEditor: VimEditor,
|
|
||||||
start: Int = 0,
|
|
||||||
end: Long = vimEditor.fileSize(),
|
|
||||||
): CountPosition {
|
|
||||||
val offset = vimEditor.currentCaret().offset
|
|
||||||
|
|
||||||
var count = 1
|
|
||||||
var position = 0
|
|
||||||
var last = -1
|
|
||||||
var res = start
|
|
||||||
while (true) {
|
|
||||||
res = injector.searchHelper.findNextWord(vimEditor, res, 1, true, false)
|
|
||||||
if (res == start || res == 0 || res > end || res == last) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
count++
|
|
||||||
|
|
||||||
if (res == offset) {
|
|
||||||
position = count
|
|
||||||
} else if (last < offset && res >= offset) {
|
|
||||||
position = if (count == 2) {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
count - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
last = res
|
|
||||||
}
|
|
||||||
|
|
||||||
if (position == 0 && res == offset) {
|
|
||||||
position = count
|
|
||||||
}
|
|
||||||
|
|
||||||
return CountPosition(count, position)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the word under the cursor or the next word to the right of the cursor on the current line.
|
* Find the word under the cursor or the next word to the right of the cursor on the current line.
|
||||||
*
|
*
|
||||||
@ -193,9 +149,3 @@ private fun skip(iterator: IntIterator, n: Int) {
|
|||||||
var i = n
|
var i = n
|
||||||
while (i-- != 0 && iterator.hasNext()) iterator.nextInt()
|
while (i-- != 0 && iterator.hasNext()) iterator.nextInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
class CountPosition(val count: Int, val position: Int)
|
|
||||||
|
|
||||||
private val logger = logger<SearchLogger>()
|
|
||||||
|
|
||||||
private class SearchLogger
|
|
@ -9,80 +9,231 @@
|
|||||||
package org.jetbrains.plugins.ideavim.action
|
package org.jetbrains.plugins.ideavim.action
|
||||||
|
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.injector
|
|
||||||
import org.jetbrains.plugins.ideavim.VimBehaviorDiffers
|
|
||||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
// For all of these tests, note that Vim might show a different total byte count - off by one. This is ok, and not worth
|
||||||
|
// adding a VimBehaviorDiffers annotation for.
|
||||||
|
// It's because Vim requires each line to end with a linefeed character (otherwise it's not a line!) and adds one to
|
||||||
|
// the last line. If the last line ends with a linefeed, that's just the end of the line. In this case, Vim does not
|
||||||
|
// draw an empty line after the last line (because there isn't one!). If we hit enter at the end of the last line, Vim
|
||||||
|
// adds a second linefeed, and there's now a new (empty) line at the end of the file, and the file ends with two
|
||||||
|
// linefeed characters.
|
||||||
|
// IntelliJ treats a linefeed at the end of the last line as a line feed, and draws an empty line. When we initialise
|
||||||
|
// a test with a trailing empty line, IntelliJ only creates one linefeed char, instead of the two that Vim creates.
|
||||||
|
// Maybe we should ensure that each file ends with a linefeed when initialising tests?
|
||||||
|
@Suppress("SpellCheckingInspection")
|
||||||
class FileGetLocationInfoActionTest : VimTestCase() {
|
class FileGetLocationInfoActionTest : VimTestCase() {
|
||||||
@VimBehaviorDiffers(originalVimAfter = "Col 1 of 11; Line 1 of 6; Word 1 of 32; Byte 1 of 166")
|
|
||||||
@Test
|
@Test
|
||||||
fun `test get file info`() {
|
fun `test get file info`() {
|
||||||
val keys = injector.parser.parseKeys("g<C-G>")
|
|
||||||
val before = """
|
val before = """
|
||||||
${c}Lorem Ipsum
|
|${c}Lorem Ipsum
|
||||||
|
|
|
||||||
Lorem ipsum dolor sit amet,
|
|Lorem ipsum dolor sit amet,
|
||||||
consectetur adipiscing elit
|
|consectetur adipiscing elit
|
||||||
Sed in orci mauris.
|
|Sed in orci mauris.
|
||||||
Cras id tellus in ex imperdiet egestas.
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
""".trimIndent()
|
""".trimMargin()
|
||||||
configureByText(before)
|
configureByText(before)
|
||||||
typeText(keys)
|
typeText("g<C-G>")
|
||||||
kotlin.test.assertEquals("Col 1 of 11; Line 1 of 6; Word 1 of 23; Character 1 of 128", VimPlugin.getMessage())
|
assertEquals("Col 1 of 11; Line 1 of 6; Word 1 of 21; Byte 1 of 128", VimPlugin.getMessage())
|
||||||
}
|
}
|
||||||
|
|
||||||
@VimBehaviorDiffers(originalVimAfter = "Col 1 of 11; Line 1 of 7; Word 1 of 32; Byte 1 of 167")
|
|
||||||
@Test
|
@Test
|
||||||
fun `test get file info with empty line`() {
|
fun `test get file info with single word`() {
|
||||||
val keys = injector.parser.parseKeys("g<C-G>")
|
configureByText("Lorem")
|
||||||
val before = """
|
typeText("g<C-G>")
|
||||||
${c}Lorem Ipsum
|
assertEquals("Col 1 of 5; Line 1 of 1; Word 1 of 1; Byte 1 of 5", VimPlugin.getMessage())
|
||||||
|
}
|
||||||
Lorem ipsum dolor sit amet,
|
|
||||||
consectetur adipiscing elit
|
@Test
|
||||||
Sed in orci mauris.
|
fun `test get file info of two separate words`() {
|
||||||
Cras id tellus in ex imperdiet egestas.
|
configureByText("Lorem ipsum")
|
||||||
|
typeText("g<C-G>")
|
||||||
""".trimIndent()
|
assertEquals("Col 1 of 11; Line 1 of 1; Word 1 of 2; Byte 1 of 11", VimPlugin.getMessage())
|
||||||
configureByText(before)
|
}
|
||||||
typeText(keys)
|
|
||||||
kotlin.test.assertEquals("Col 1 of 11; Line 1 of 7; Word 1 of 24; Character 1 of 129", VimPlugin.getMessage())
|
@Test
|
||||||
|
fun `test get file info of one WORD containing non-word characters`() {
|
||||||
|
configureByText("Lorem,,,,,ipsum")
|
||||||
|
typeText("g<C-G>")
|
||||||
|
assertEquals("Col 1 of 15; Line 1 of 1; Word 1 of 1; Byte 1 of 15", VimPlugin.getMessage())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test get file info of words on multiple lines`() {
|
||||||
|
val before = """
|
||||||
|
|Lorem ipsum dolor sit amet
|
||||||
|
|cons${c}ectetur adipiscing elit
|
||||||
|
""".trimMargin()
|
||||||
|
configureByText(before)
|
||||||
|
typeText("g<C-G>")
|
||||||
|
assertEquals("Col 5 of 27; Line 2 of 2; Word 6 of 8; Byte 32 of 54", VimPlugin.getMessage())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test get file info of words with trailing punctuation on multiple lines`() {
|
||||||
|
val before = """
|
||||||
|
|Lorem ipsum dolor sit amet,
|
||||||
|
|cons${c}ectetur adipiscing elit
|
||||||
|
""".trimMargin()
|
||||||
|
configureByText(before)
|
||||||
|
typeText("g<C-G>")
|
||||||
|
assertEquals("Col 5 of 27; Line 2 of 2; Word 6 of 8; Byte 33 of 55", VimPlugin.getMessage())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test get file info of words with empty lines`() {
|
||||||
|
val before = """
|
||||||
|
|Lorem ipsum dolor sit amet,
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|${c}
|
||||||
|
|consectetur adipiscing elit
|
||||||
|
""".trimMargin()
|
||||||
|
configureByText(before)
|
||||||
|
typeText("g<C-G>")
|
||||||
|
assertEquals("Col 1 of 0; Line 4 of 5; Word 5 of 8; Byte 31 of 58", VimPlugin.getMessage())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test get file info on empty line shows zero columns`() {
|
||||||
|
val before = """
|
||||||
|
|Lorem Ipsum
|
||||||
|
|${c}
|
||||||
|
|Lorem ipsum dolor sit amet,
|
||||||
|
|consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
|
|
||||||
|
""".trimMargin()
|
||||||
|
configureByText(before)
|
||||||
|
typeText("g<C-G>")
|
||||||
|
assertEquals("Col 1 of 0; Line 2 of 7; Word 2 of 21; Byte 13 of 129", VimPlugin.getMessage())
|
||||||
}
|
}
|
||||||
|
|
||||||
@VimBehaviorDiffers(originalVimAfter = "Col 1 of 40; Line 4 of 7; Word 12 of 32; Byte 55 of 167")
|
|
||||||
@Test
|
@Test
|
||||||
fun `test get file info in the middle`() {
|
fun `test get file info in the middle`() {
|
||||||
val keys = injector.parser.parseKeys("g<C-G>")
|
|
||||||
val before = """
|
val before = """
|
||||||
Lorem Ipsum
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
Lorem ipsum dolor sit amet,
|
|Lorem ipsum dolor sit amet,
|
||||||
all rocks ${c}and lavender and tufted grass,
|
|consectetur ${c}adipiscing elit
|
||||||
Sed in orci mauris.
|
|Sed in orci mauris.
|
||||||
Cras id tellus in ex imperdiet egestas.
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
|
|
||||||
""".trimIndent()
|
""".trimMargin()
|
||||||
configureByText(before)
|
configureByText(before)
|
||||||
typeText(keys)
|
typeText("g<C-G>")
|
||||||
kotlin.test.assertEquals("Col 11 of 40; Line 4 of 7; Word 11 of 28; Character 52 of 142", VimPlugin.getMessage())
|
assertEquals("Col 13 of 27; Line 4 of 7; Word 9 of 21; Byte 54 of 129", VimPlugin.getMessage())
|
||||||
}
|
}
|
||||||
|
|
||||||
@VimBehaviorDiffers(originalVimAfter = "Col 1 of 0; Line 7 of 7; Word 32 of 32; Byte 167 of 167")
|
|
||||||
@Test
|
@Test
|
||||||
fun `test get file info on the last line`() {
|
fun `test get file info on the last line`() {
|
||||||
val keys = injector.parser.parseKeys("g<C-G>")
|
|
||||||
val before = """
|
val before = """
|
||||||
Lorem Ipsum
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
Lorem ipsum dolor sit amet,
|
|Lorem ipsum dolor sit amet,
|
||||||
consectetur adipiscing elit
|
|consectetur adipiscing elit
|
||||||
Sed in orci mauris.
|
|Sed in orci mauris.
|
||||||
Cras id tellus in ex imperdiet egestas.
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
$c
|
|$c
|
||||||
""".trimIndent()
|
""".trimMargin()
|
||||||
configureByText(before)
|
configureByText(before)
|
||||||
typeText(keys)
|
typeText("g<C-G>")
|
||||||
kotlin.test.assertEquals("Col 1 of 1; Line 7 of 7; Word 24 of 24; Character 130 of 129", VimPlugin.getMessage())
|
assertEquals("Col 1 of 0; Line 7 of 7; Word 21 of 21; Byte 130 of 129", VimPlugin.getMessage())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test get file info with single word selected`() {
|
||||||
|
val before = """
|
||||||
|
|Lorem ${c}ipsum dolor sit amet
|
||||||
|
|consectetur adipiscing elit
|
||||||
|
""".trimMargin()
|
||||||
|
configureByText(before)
|
||||||
|
typeText("ve", "g<C-G>")
|
||||||
|
assertEquals("Selected 1 of 2 Lines; 1 of 8 Words; 5 of 54 Bytes", VimPlugin.getMessage())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test get file info with multiple words selected`() {
|
||||||
|
val before = """
|
||||||
|
|Lorem ${c}ipsum dolor sit amet
|
||||||
|
|consectetur adipiscing elit
|
||||||
|
""".trimMargin()
|
||||||
|
configureByText(before)
|
||||||
|
typeText("v2e", "g<C-G>")
|
||||||
|
assertEquals("Selected 1 of 2 Lines; 2 of 8 Words; 11 of 54 Bytes", VimPlugin.getMessage())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test get file info with single WORD selected`() {
|
||||||
|
val before = """
|
||||||
|
|Lorem ${c}ipsum,,,,dolor sit amet
|
||||||
|
|consectetur adipiscing elit
|
||||||
|
""".trimMargin()
|
||||||
|
configureByText(before)
|
||||||
|
typeText("vE", "g<C-G>")
|
||||||
|
assertEquals("Selected 1 of 2 Lines; 1 of 7 Words; 14 of 57 Bytes", VimPlugin.getMessage())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test get file info across multiple selected lines`() {
|
||||||
|
val before = """
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ${c}ipsum dolor sit amet,
|
||||||
|
|consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin()
|
||||||
|
configureByText(before)
|
||||||
|
typeText("v", "jj", "g<C-G>")
|
||||||
|
assertEquals("Selected 3 of 6 Lines; 9 of 21 Words; 57 of 128 Bytes", VimPlugin.getMessage())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test get file info with empty lines selected`() {
|
||||||
|
val before = """
|
||||||
|
|Lorem ${c}ipsum dolor sit amet,
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|consectetur adipiscing elit
|
||||||
|
""".trimMargin()
|
||||||
|
configureByText(before)
|
||||||
|
typeText("vG", "g<C-G>")
|
||||||
|
assertEquals("Selected 5 of 5 Lines; 5 of 8 Words; 26 of 58 Bytes", VimPlugin.getMessage())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test get file info with linewise selection`() {
|
||||||
|
val before = """
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ${c}ipsum dolor sit amet,
|
||||||
|
|consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin()
|
||||||
|
configureByText(before)
|
||||||
|
typeText("V", "jj", "g<C-G>")
|
||||||
|
assertEquals("Selected 3 of 6 Lines; 12 of 21 Words; 76 of 128 Bytes", VimPlugin.getMessage())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test get file info with blockwise selection`() {
|
||||||
|
val before = """
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ${c}ipsum dolor sit amet,
|
||||||
|
|consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin()
|
||||||
|
configureByText(before)
|
||||||
|
typeText("<C-V>", "jjj", "llll", "g<C-G>")
|
||||||
|
assertEquals("Selected 5 Cols; 4 of 6 Lines; 5 of 21 Words; 20 of 128 Bytes", VimPlugin.getMessage())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ package com.maddyhome.idea.vim.api
|
|||||||
interface VimFile {
|
interface VimFile {
|
||||||
fun displayFileInfo(vimEditor: VimEditor, fullPath: Boolean)
|
fun displayFileInfo(vimEditor: VimEditor, fullPath: Boolean)
|
||||||
fun displayHexInfo(editor: VimEditor)
|
fun displayHexInfo(editor: VimEditor)
|
||||||
fun displayLocationInfo(vimEditor: VimEditor)
|
fun displayLocationInfo(editor: VimEditor)
|
||||||
fun selectPreviousTab(context: ExecutionContext)
|
fun selectPreviousTab(context: ExecutionContext)
|
||||||
fun saveFile(editor: VimEditor, context: ExecutionContext)
|
fun saveFile(editor: VimEditor, context: ExecutionContext)
|
||||||
fun saveFiles(editor: VimEditor, context: ExecutionContext)
|
fun saveFiles(editor: VimEditor, context: ExecutionContext)
|
||||||
|
@ -8,6 +8,13 @@
|
|||||||
|
|
||||||
package com.maddyhome.idea.vim.api
|
package com.maddyhome.idea.vim.api
|
||||||
|
|
||||||
|
import com.maddyhome.idea.vim.group.visual.VimSelection
|
||||||
|
import com.maddyhome.idea.vim.helper.CharacterHelper
|
||||||
|
import com.maddyhome.idea.vim.helper.CharacterHelper.charType
|
||||||
|
import com.maddyhome.idea.vim.helper.endOffsetInclusive
|
||||||
|
import com.maddyhome.idea.vim.state.mode.SelectionType.CHARACTER_WISE
|
||||||
|
import com.maddyhome.idea.vim.state.mode.inVisualMode
|
||||||
|
import com.maddyhome.idea.vim.state.mode.selectionType
|
||||||
import java.lang.Long.toHexString
|
import java.lang.Long.toHexString
|
||||||
|
|
||||||
abstract class VimFileBase : VimFile {
|
abstract class VimFileBase : VimFile {
|
||||||
@ -17,4 +24,106 @@ abstract class VimFileBase : VimFile {
|
|||||||
|
|
||||||
injector.messages.showStatusBarMessage(editor, toHexString(ch.code.toLong()))
|
injector.messages.showStatusBarMessage(editor, toHexString(ch.code.toLong()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun displayLocationInfo(editor: VimEditor) {
|
||||||
|
val msg = buildString {
|
||||||
|
|
||||||
|
val caret = editor.currentCaret()
|
||||||
|
val offset = editor.currentCaret().offset
|
||||||
|
val totalByteCount = editor.fileSize()
|
||||||
|
val totalWordCount = countBigWords(editor, offset)
|
||||||
|
|
||||||
|
if (!editor.inVisualMode) {
|
||||||
|
val pos = caret.getBufferPosition()
|
||||||
|
val line = pos.line + 1
|
||||||
|
val col = pos.column + 1
|
||||||
|
val lineEndOffset = editor.getLineEndOffset(pos.line)
|
||||||
|
val lineEndCol = editor.offsetToBufferPosition(lineEndOffset).column
|
||||||
|
|
||||||
|
// Note that Vim can have different screen columns to buffer columns, and displays these in the form "Col 1-3".
|
||||||
|
// Vim uses screen columns to insert inlay text and symbols such as word wrap indicators, but has a single line
|
||||||
|
// even when wrapped, unlike IntelliJ, which has a virtual line per screen line. Because of this, it's not clear
|
||||||
|
// how IdeaVim could represent this, or even if it would be worthwhile to do so.
|
||||||
|
append("Col ").append(col).append(" of ").append(lineEndCol)
|
||||||
|
append("; Line ").append(line).append(" of ").append(editor.lineCount())
|
||||||
|
append("; Word ").append(totalWordCount.currentWord).append(" of ").append(totalWordCount.count)
|
||||||
|
append("; Byte ").append(offset + 1).append(" of ").append(totalByteCount)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
append("Selected ")
|
||||||
|
|
||||||
|
val selection = VimSelection.create(
|
||||||
|
caret.vimSelectionStart,
|
||||||
|
caret.offset,
|
||||||
|
editor.mode.selectionType ?: CHARACTER_WISE,
|
||||||
|
editor
|
||||||
|
).toVimTextRange()
|
||||||
|
|
||||||
|
val selectedLineCount: Int
|
||||||
|
val selectedWordCount: Int
|
||||||
|
if (selection.isMultiple) {
|
||||||
|
selectedLineCount = selection.size()
|
||||||
|
|
||||||
|
var count = 0
|
||||||
|
for (i in 0 until selection.size()) {
|
||||||
|
val wordCount = countBigWords(editor, offset, selection.startOffsets[i], selection.endOffsets[i])
|
||||||
|
count += wordCount.count
|
||||||
|
}
|
||||||
|
selectedWordCount = count
|
||||||
|
|
||||||
|
append(selection.maxLength).append(" Cols; ")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val startPos = editor.offsetToBufferPosition(selection.startOffset)
|
||||||
|
val endPos = editor.offsetToBufferPosition(selection.endOffsetInclusive)
|
||||||
|
|
||||||
|
selectedLineCount = endPos.line - startPos.line + 1
|
||||||
|
|
||||||
|
val wordCount = countBigWords(editor, offset, selection.startOffset, selection.endOffset)
|
||||||
|
selectedWordCount = wordCount.count
|
||||||
|
}
|
||||||
|
|
||||||
|
append(selectedLineCount).append(" of ").append(editor.lineCount()).append(" Lines; ")
|
||||||
|
append(selectedWordCount).append(" of ").append(totalWordCount.count).append(" Words; ")
|
||||||
|
append(selection.selectionCount).append(" of ").append(totalByteCount).append(" Bytes")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
injector.messages.showStatusBarMessage(editor, msg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count the number of WORDs that intersect the given range, or exist in the whole file
|
||||||
|
*/
|
||||||
|
private fun countBigWords(
|
||||||
|
editor: VimEditor,
|
||||||
|
caretOffset: Int,
|
||||||
|
start: Int = 0,
|
||||||
|
endExclusive: Int = editor.text().length,
|
||||||
|
): WordCount {
|
||||||
|
|
||||||
|
val chars = editor.text()
|
||||||
|
var wordCount = 0
|
||||||
|
var currentWord = 0
|
||||||
|
|
||||||
|
var lastCharacterType: CharacterHelper.CharacterType? = null
|
||||||
|
for (pos in start until endExclusive) {
|
||||||
|
val characterType = charType(editor, chars[pos], punctuationAsLetters = true)
|
||||||
|
if (characterType != lastCharacterType && characterType != CharacterHelper.CharacterType.WHITESPACE) {
|
||||||
|
wordCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
// The current word is the last counted word
|
||||||
|
if (pos <= caretOffset) {
|
||||||
|
currentWord = wordCount
|
||||||
|
}
|
||||||
|
|
||||||
|
lastCharacterType = characterType
|
||||||
|
}
|
||||||
|
|
||||||
|
return WordCount(wordCount, currentWord)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class WordCount(val count: Int, val currentWord: Int)
|
||||||
|
Loading…
Reference in New Issue
Block a user