1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2024-10-19 04:42:46 +02:00

Compare commits

...

29 Commits

Author SHA1 Message Date
a7def05aa8
Set plugin version to chylex-28 2024-02-18 18:49:31 +01:00
51e13a5f20
Disable speed search in Project tool window when NERDTree is enabled 2024-02-18 18:49:16 +01:00
9b67260d5a
Work around VIM-3306 (make paragraph-motion plugin go to the end of the line) 2024-02-18 18:49:16 +01:00
ac37432db6
Apply scrolloff after executing native IDEA actions 2024-02-18 18:49:16 +01:00
4c946568e4
Stay on same line after reindenting 2024-02-18 18:49:16 +01:00
7e70eed1ab
Implement motions to go to next/previous misspelled word 2024-02-18 18:49:16 +01:00
43ae90044b
Update search register when using f/t 2024-02-18 18:49:16 +01:00
d79f7c23c5
Automatically add unambiguous imports after running a macro 2024-02-18 18:49:15 +01:00
6033450158
Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2024-02-18 18:49:15 +01:00
ea86d7132c
Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2024-02-18 18:49:15 +01:00
711d1f0329
Fix(VIM-3166): Workaround to fix broken filtering of visual lines 2024-02-18 18:49:15 +01:00
338e137347
Add support for count for visual and line motion surround 2024-02-18 18:49:15 +01:00
2b0e9bfec5
Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2024-02-18 18:49:15 +01:00
b42346b9e1
Fix(VIM-696) Restore visual mode after undo/redo, and disable incompatible actions 2024-02-18 18:49:15 +01:00
d58d3ca8b0
Revert(VIM-2884): Fix moving lines to cursor 2024-02-18 18:49:15 +01:00
22b2ca2352
Respect count with <Action> mappings 2024-02-18 18:49:15 +01:00
1c98daa180
Add Matchit support for Java statements 2024-02-18 18:49:15 +01:00
de906bcbac
Change matchit plugin to use HTML patterns in unrecognized files 2024-02-18 18:49:14 +01:00
05fac8bf00
Reset insert mode when switching active editor 2024-02-18 18:49:14 +01:00
a985d260f7
Remove update checker 2024-02-18 18:49:14 +01:00
9f4c679d77
Set custom plugin version 2024-02-18 18:49:14 +01:00
IdeaVim Bot
f4cf06a50e Update changelog. Action id - 7940923443 2024-02-17 10:05:47 +00:00
Alex Plate
86bf8dcc60
Fix the compatibility with platform 2024-02-17 08:56:17 +02:00
Alex Plate
d37898b6d3
Fix(VIM-3234): The space character won't mix in the tab chars after >> and << commands
Because of some reason, the visual position function from the platform starts to return an incorrect column for offsets with tabs. Maybe this is a correct behaviour for the platform, but for IdeaVim it breaks the calculation of the current caret position.

The visual position for calculating the shift was used since 2003, but there is no specific reason to use it and not the buffer (logical) position. So, since it started to cause issues, it's replaced with the buffer position.
2024-02-17 08:54:42 +02:00
Alex Plate
1edd6a9002
Fix the compatibility with the new version of the platform 2024-02-17 08:15:11 +02:00
Alex Plate
f7fa0dcbd1
Update YouTrack query for updating the release status after the release 2024-02-17 08:15:11 +02:00
Alex Plate
4f0a95a803
Bring back setCompletionPhase as this incompatibility was fixed in the latest EAP of the IntelliJ platform 2024-02-17 08:15:11 +02:00
IdeaVim Bot
e443cb0d3c Update changelog. Action id - 7928973613 2024-02-16 10:06:24 +00:00
Alex Plate
6fa228ee08
Fix(VIM-3291): Remove sync of editor selection between different opened editors
This is an old feature implemented by Rick Maddy in 2004, taken from Vim.
 c294063223

 If several buffers for the same file are opened, the selection is synchronized between buffers.
This doesn't happen in IJ natively and I don't see a reason to keep it like that.
This behaviour is removed because it causes issues now, but if we'll figure out the usage, we can bring it back.
2024-02-15 20:34:57 +02:00
51 changed files with 582 additions and 220 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

View File

@ -6,6 +6,7 @@
<option name="CONTINUATION_INDENT_SIZE" value="4" /> <option name="CONTINUATION_INDENT_SIZE" value="4" />
</value> </value>
</option> </option>
<option name="LINE_SEPARATOR" value="&#10;" />
<JavaCodeStyleSettings> <JavaCodeStyleSettings>
<option name="FIELD_NAME_PREFIX" value="my" /> <option name="FIELD_NAME_PREFIX" value="my" />
<option name="STATIC_FIELD_NAME_PREFIX" value="our" /> <option name="STATIC_FIELD_NAME_PREFIX" value="our" />

View File

@ -27,6 +27,8 @@ usual beta standards.
### Fixes: ### Fixes:
* [VIM-3055](https://youtrack.jetbrains.com/issue/VIM-3055) Fix the issue with double deleting after dot * [VIM-3055](https://youtrack.jetbrains.com/issue/VIM-3055) Fix the issue with double deleting after dot
* [VIM-3291](https://youtrack.jetbrains.com/issue/VIM-3291) Remove sync of editor selection between different opened editors
* [VIM-3234](https://youtrack.jetbrains.com/issue/VIM-3234) The space character won't mix in the tab chars after >> and << commands
### Merged PRs: ### Merged PRs:
* [725](https://github.com/JetBrains/ideavim/pull/725) by [Emanuel Gestosa](https://github.com/emanuelgestosa): Regex * [725](https://github.com/JetBrains/ideavim/pull/725) by [Emanuel Gestosa](https://github.com/emanuelgestosa): Regex

View File

@ -70,7 +70,7 @@ plugins {
application application
id("java-test-fixtures") id("java-test-fixtures")
id("org.jetbrains.intellij") version "1.17.0" id("org.jetbrains.intellij") version "1.17.1"
id("org.jetbrains.changelog") version "2.2.0" id("org.jetbrains.changelog") version "2.2.0"
id("org.jetbrains.kotlinx.kover") version "0.6.1" id("org.jetbrains.kotlinx.kover") version "0.6.1"
@ -308,8 +308,6 @@ tasks {
val pluginVersion = version val pluginVersion = version
// Don't forget to update plugin.xml // Don't forget to update plugin.xml
patchPluginXml { patchPluginXml {
sinceBuild.set("233.11799.30")
// Get the latest available change notes from the changelog file // Get the latest available change notes from the changelog file
changeNotes.set( changeNotes.set(
provider { provider {
@ -460,7 +458,7 @@ val fixVersionsElementType = "VersionBundleElement"
tasks.register("releaseActions") { tasks.register("releaseActions") {
group = "other" group = "other"
doLast { doLast {
val tickets = getYoutrackTicketsByQuery("%23%7BReady+To+Release%7D") val tickets = getYoutrackTicketsByQuery("%23%7BReady+To+Release%7D%20and%20tag:%20%7BIdeaVim%20Released%20In%20EAP%7D%20")
if (tickets.isNotEmpty()) { if (tickets.isNotEmpty()) {
println("Updating statuses for tickets: $tickets") println("Updating statuses for tickets: $tickets")
setYoutrackStatus(tickets, "Fixed") setYoutrackStatus(tickets, "Fixed")

View File

@ -8,16 +8,17 @@
# suppress inspection "UnusedProperty" for whole file # suppress inspection "UnusedProperty" for whole file
ideaVersion=2023.3.2 ideaVersion=2023.3.3
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type # Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
ideaType=IC ideaType=IC
downloadIdeaSources=true downloadIdeaSources=true
instrumentPluginCode=true instrumentPluginCode=true
version=SNAPSHOT version=chylex-28
javaVersion=17 javaVersion=17
remoteRobotVersion=0.11.22 remoteRobotVersion=0.11.22
antlrVersion=4.10.1 antlrVersion=4.10.1
kotlin.incremental.useClasspathSnapshot=false
# Please don't forget to update kotlin version in buildscript section # Please don't forget to update kotlin version in buildscript section
# Also update kotlinxSerializationVersion version # Also update kotlinxSerializationVersion version

View File

@ -217,6 +217,8 @@ private object FileTypePatterns {
return if (fileTypeName in htmlLikeFileTypes) { return if (fileTypeName in htmlLikeFileTypes) {
this.htmlPatterns this.htmlPatterns
} else if (fileTypeName == "JAVA" || fileExtension == "java") {
this.javaPatterns
} else if (fileTypeName == "Ruby" || fileExtension == "rb") { } else if (fileTypeName == "Ruby" || fileExtension == "rb") {
this.rubyPatterns this.rubyPatterns
} else if (fileTypeName == "RHTML" || fileExtension == "erb") { } else if (fileTypeName == "RHTML" || fileExtension == "erb") {
@ -231,7 +233,7 @@ private object FileTypePatterns {
} else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") { } else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") {
this.cMakePatterns this.cMakePatterns
} else { } else {
return null this.htmlPatterns
} }
} }
@ -242,6 +244,7 @@ private object FileTypePatterns {
) )
private val htmlPatterns = createHtmlPatterns() private val htmlPatterns = createHtmlPatterns()
private val javaPatterns = createJavaPatterns()
private val rubyPatterns = createRubyPatterns() private val rubyPatterns = createRubyPatterns()
private val rubyAndHtmlPatterns = rubyPatterns + htmlPatterns private val rubyAndHtmlPatterns = rubyPatterns + htmlPatterns
private val phpPatterns = createPhpPatterns() private val phpPatterns = createPhpPatterns()
@ -271,6 +274,14 @@ private object FileTypePatterns {
) )
} }
private fun createJavaPatterns(): LanguagePatterns {
return (
LanguagePatterns("\\b(?<!else\\s+)if\\b", "\\belse\\s+if\\b", "\\belse(?!\\s+if)\\b") +
LanguagePatterns("\\bdo\\b", "\\bwhile\\b") +
LanguagePatterns("\\btry\\b", "\\bcatch\\b", "\\bfinally\\b")
)
}
private fun createRubyPatterns(): LanguagePatterns { private fun createRubyPatterns(): LanguagePatterns {
// Original patterns: https://github.com/vim/vim/blob/master/runtime/ftplugin/ruby.vim // Original patterns: https://github.com/vim/vim/blob/master/runtime/ftplugin/ruby.vim
// We use non-capturing groups (?:) since we don't need any back refs. The \\b marker takes care of word boundaries. // We use non-capturing groups (?:) since we don't need any back refs. The \\b marker takes care of word boundaries.

View File

@ -502,6 +502,13 @@ internal class NerdTree : VimExtension {
} }
}, },
) )
for (c in ('a'..'z') + ('A'..'Z')) {
val ks = KeyStroke.getKeyStroke(c)
if (ks !in actionsRoot) {
registerCommand(c.toString(), NerdAction.Code { _, _, _ -> })
}
}
} }
companion object { companion object {

View File

@ -11,8 +11,8 @@ package com.maddyhome.idea.vim.extension.paragraphmotion
import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Caret
import com.maddyhome.idea.vim.api.ExecutionContext 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.getLineEndForOffset
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.normalizeOffset
import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.extension.ExtensionHandler import com.maddyhome.idea.vim.extension.ExtensionHandler
@ -45,8 +45,7 @@ internal class ParagraphMotion : VimExtension {
} }
fun moveCaretToNextParagraph(editor: VimEditor, caret: Caret, count: Int): Int? { fun moveCaretToNextParagraph(editor: VimEditor, caret: Caret, count: Int): Int? {
return injector.searchHelper.findNextParagraph(editor, caret.vim, count, true) return injector.searchHelper.findNextParagraph(editor, caret.vim, count, true)?.let(editor::getLineEndForOffset)
?.let { editor.normalizeOffset(it, true) }
} }
} }
} }

View File

@ -0,0 +1,30 @@
package com.maddyhome.idea.vim.extension.surround
import com.intellij.util.text.CharSequenceSubSequence
internal data class RepeatedCharSequence(val text: CharSequence, val count: Int) : CharSequence {
override val length = text.length * count
override fun get(index: Int): Char {
if (index < 0 || index >= length) throw IndexOutOfBoundsException()
return text[index % text.length]
}
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
return CharSequenceSubSequence(this, startIndex, endIndex)
}
override fun toString(): String {
return text.repeat(count)
}
companion object {
fun of(text: CharSequence, count: Int): CharSequence {
return when (count) {
0 -> ""
1 -> text
else -> RepeatedCharSequence(text, count)
}
}
}
}

View File

@ -13,6 +13,7 @@ import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimChangeGroup
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.endsWithNewLine import com.maddyhome.idea.vim.api.endsWithNewLine
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
@ -31,7 +32,10 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMa
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
import com.maddyhome.idea.vim.key.OperatorFunction import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
@ -80,7 +84,7 @@ internal class VimSurroundExtension : VimExtension {
override val isRepeatable = true override val isRepeatable = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
setOperatorFunction(Operator()) setOperatorFunction(Operator(supportsMultipleCursors = false, count = 1)) // TODO
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij) executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
} }
} }
@ -101,7 +105,7 @@ internal class VimSurroundExtension : VimExtension {
val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset) val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset)
if (lastNonWhiteSpaceOffset != null) { if (lastNonWhiteSpaceOffset != null) {
val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1) val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1)
performSurround(pair, range, it) performSurround(pair, range, it, count = operatorArguments.count1)
} }
// it.moveToOffset(lineStartOffset) // it.moveToOffset(lineStartOffset)
} }
@ -121,15 +125,13 @@ internal class VimSurroundExtension : VimExtension {
private class VSurroundHandler : ExtensionHandler { private class VSurroundHandler : ExtensionHandler {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val selectionStart = editor.ij.caretModel.primaryCaret.selectionStart
// NB: Operator ignores SelectionType anyway // NB: Operator ignores SelectionType anyway
if (!Operator().apply(editor, context, editor.mode.selectionType)) { if (!Operator(supportsMultipleCursors = true, count = operatorArguments.count1).apply(editor, context, editor.mode.selectionType)) {
return return
} }
runWriteAction { runWriteAction {
// Leave visual mode // Leave visual mode
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij) executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
editor.ij.caretModel.moveToOffset(selectionStart)
} }
} }
} }
@ -150,6 +152,10 @@ internal class VimSurroundExtension : VimExtension {
companion object { companion object {
fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) { fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
editor.ij.runWithEveryCaretAndRestore { changeAtCaret(editor, context, charFrom, newSurround) }
}
fun changeAtCaret(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
// Save old register values for carets // Save old register values for carets
val surroundings = editor.sortedCarets() val surroundings = editor.sortedCarets()
.map { .map {
@ -257,21 +263,42 @@ internal class VimSurroundExtension : VimExtension {
} }
} }
private class Operator : OperatorFunction { private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction {
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean { override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val ijEditor = editor.ij val editor = vimEditor.ij
val c = getChar(ijEditor) val c = getChar(editor)
if (c.code == 0) return true if (c.code == 0) return true
val pair = getOrInputPair(c, ijEditor) ?: return false val pair = getOrInputPair(c, editor) ?: return false
// XXX: Will it work with line-wise or block-wise selections?
val range = getSurroundRange(editor.currentCaret()) ?: return false runWriteAction {
performSurround(pair, range, editor.currentCaret(), selectionType == SelectionType.LINE_WISE) val change = VimPlugin.getChange()
if (supportsMultipleCursors) {
editor.runWithEveryCaretAndRestore {
applyOnce(editor, change, pair, count)
}
}
else {
applyOnce(editor, change, pair, count)
// Jump back to start // Jump back to start
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor) executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor)
}
}
return true return true
} }
private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: Pair<String, String>, count: Int) {
// XXX: Will it work with line-wise or block-wise selections?
val primaryCaret = editor.caretModel.primaryCaret
val range = getSurroundRange(primaryCaret.vim)
if (range != null) {
val start = RepeatedCharSequence.of(pair.first, count)
val end = RepeatedCharSequence.of(pair.second, count)
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, start)
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.endOffset + start.length, end)
}
}
private fun getSurroundRange(caret: VimCaret): TextRange? { private fun getSurroundRange(caret: VimCaret): TextRange? {
val editor = caret.editor val editor = caret.editor
val ijEditor = editor.ij val ijEditor = editor.ij
@ -354,15 +381,15 @@ private fun getChar(editor: Editor): Char {
return res return res
} }
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, tagsOnNewLines: Boolean = false) { private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
runWriteAction { runWriteAction {
val editor = caret.editor val editor = caret.editor
val change = VimPlugin.getChange() val change = VimPlugin.getChange()
val leftSurround = pair.first + if (tagsOnNewLines) "\n" else "" val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count)
val isEOF = range.endOffset == editor.text().length val isEOF = range.endOffset == editor.text().length
val hasNewLine = editor.endsWithNewLine() val hasNewLine = editor.endsWithNewLine()
val rightSurround = if (tagsOnNewLines) { val rightSurround = (if (tagsOnNewLines) {
if (isEOF && !hasNewLine) { if (isEOF && !hasNewLine) {
"\n" + pair.second "\n" + pair.second
} else { } else {
@ -370,7 +397,7 @@ private fun performSurround(pair: Pair<String, String>, range: TextRange, caret:
} }
} else { } else {
pair.second pair.second
} }).let { RepeatedCharSequence.of(it, count) }
change.insertText(editor, caret, range.startOffset, leftSurround) change.insertText(editor, caret, range.startOffset, leftSurround)
change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround) change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)

View File

@ -78,7 +78,6 @@ import java.math.BigInteger
import java.util.* import java.util.*
import java.util.function.Consumer import java.util.function.Consumer
import kotlin.math.max import kotlin.math.max
import kotlin.math.min
/** /**
* Provides all the insert/replace related functionality * Provides all the insert/replace related functionality
@ -395,6 +394,7 @@ public class ChangeGroup : VimChangeGroupBase() {
context: ExecutionContext, context: ExecutionContext,
range: TextRange, range: TextRange,
) { ) {
val startPos = editor.offsetToBufferPosition(caret.offset.point)
val startOffset = editor.getLineStartForOffset(range.startOffset) val startOffset = editor.getLineStartForOffset(range.startOffset)
val endOffset = editor.getLineEndForOffset(range.endOffset) val endOffset = editor.getLineEndForOffset(range.endOffset)
val ijEditor = (editor as IjVimEditor).editor val ijEditor = (editor as IjVimEditor).editor
@ -419,11 +419,7 @@ public class ChangeGroup : VimChangeGroupBase() {
} }
} }
val afterAction = { val afterAction = {
val firstLine = editor.offsetToBufferPosition( caret.moveToOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, startPos.line))
min(startOffset.toDouble(), endOffset.toDouble()).toInt()
).line
val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine)
caret.moveToOffset(newOffset)
restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line) restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
} }
if (project != null) { if (project != null) {
@ -535,7 +531,7 @@ public class ChangeGroup : VimChangeGroupBase() {
val soff = editor.getLineStartOffset(l) val soff = editor.getLineStartOffset(l)
val eoff = editor.getLineEndOffset(l, true) val eoff = editor.getLineEndOffset(l, true)
val woff = injector.motion.moveCaretToLineStartSkipLeading(editor, l) val woff = injector.motion.moveCaretToLineStartSkipLeading(editor, l)
val col = editor.offsetToVisualPosition(woff).column val col = editor.offsetToBufferPosition(woff).column
val limit = max(0.0, (col + dir * indentConfig.getTotalIndent(count)).toDouble()) val limit = max(0.0, (col + dir * indentConfig.getTotalIndent(count)).toDouble())
.toInt() .toInt()
if (col > 0 || soff != eoff) { if (col > 0 || soff != eoff) {

View File

@ -83,7 +83,7 @@ public object IjOptions {
public val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false)) public val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false))
public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true)) public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true))
public val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100)) public val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100))
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", false, isTemporary = true)) public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isTemporary = true))
public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isTemporary = true)) public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isTemporary = true))
public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isTemporary = true)) public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isTemporary = true))
public val showmodewidget: ToggleOption = addOption(ToggleOption("showmodewidget", GLOBAL, "showmodewidget", false, isTemporary = true)) public val showmodewidget: ToggleOption = addOption(ToggleOption("showmodewidget", GLOBAL, "showmodewidget", false, isTemporary = true))

View File

@ -0,0 +1,68 @@
package com.maddyhome.idea.vim.group
import com.intellij.codeInsight.daemon.ReferenceImporter
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiRecursiveElementWalkingVisitor
import java.util.function.BooleanSupplier
internal object MacroAutoImport {
fun run(editor: Editor, dataContext: DataContext) {
val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return
val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: return
if (!FileDocumentManager.getInstance().requestWriting(editor.document, project)) {
return
}
val importers = ReferenceImporter.EP_NAME.extensionList
if (importers.isEmpty()) {
return
}
ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Auto import", true) {
override fun run(indicator: ProgressIndicator) {
val fixes = ReadAction.nonBlocking<List<BooleanSupplier>> {
val fixes = mutableListOf<BooleanSupplier>()
file.accept(object : PsiRecursiveElementWalkingVisitor() {
override fun visitElement(element: PsiElement) {
for (reference in element.references) {
if (reference.resolve() != null) {
continue
}
for (importer in importers) {
importer.computeAutoImportAtOffset(editor, file, element.textRange.startOffset, true)
?.let(fixes::add)
}
}
super.visitElement(element)
}
})
return@nonBlocking fixes
}.executeSynchronously()
ApplicationManager.getApplication().invokeAndWait {
WriteCommandAction.writeCommandAction(project)
.withName("Auto Import")
.withGroupId("IdeaVimAutoImportAfterMacro")
.shouldRecordActionForActiveDocument(true)
.run<RuntimeException> {
fixes.forEach { it.asBoolean }
}
}
}
})
}
}

View File

@ -7,6 +7,8 @@
*/ */
package com.maddyhome.idea.vim.group package com.maddyhome.idea.vim.group
import com.intellij.codeInsight.completion.CompletionPhase
import com.intellij.codeInsight.completion.impl.CompletionServiceImpl
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProcessCanceledException
@ -19,6 +21,7 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.MessageHelper.message import com.maddyhome.idea.vim.helper.MessageHelper.message
import com.maddyhome.idea.vim.macro.VimMacroBase import com.maddyhome.idea.vim.macro.VimMacroBase
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
/** /**
* Used to handle playback of macros * Used to handle playback of macros
@ -76,7 +79,7 @@ internal class MacroGroup : VimMacroBase() {
ProgressManager.getInstance().executeNonCancelableSection { ProgressManager.getInstance().executeNonCancelableSection {
// Prevent autocompletion during macros. // Prevent autocompletion during macros.
// See https://github.com/JetBrains/ideavim/pull/772 for details // See https://github.com/JetBrains/ideavim/pull/772 for details
// CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion) CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion)
getInstance().handleKey(editor, key, context) getInstance().handleKey(editor, key, context)
} }
if (injector.messages.isError()) return@runnable if (injector.messages.isError()) return@runnable
@ -88,6 +91,9 @@ internal class MacroGroup : VimMacroBase() {
} finally { } finally {
keyStack.removeFirst() keyStack.removeFirst()
} }
if (!isInternalMacro) {
MacroAutoImport.run(editor.ij, context.ij)
}
} }
if (isInternalMacro) { if (isInternalMacro) {

View File

@ -214,8 +214,8 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
* @param patternOffset The pattern offset, e.g. `/{pattern}/{offset}` * @param patternOffset The pattern offset, e.g. `/{pattern}/{offset}`
* @param direction The direction to search * @param direction The direction to search
*/ */
@TestOnly @Override
public void setLastSearchState(@SuppressWarnings("unused") @NotNull Editor editor, @NotNull String pattern, public void setLastSearchState(@SuppressWarnings("unused") @NotNull VimEditor editor, @NotNull String pattern,
@NotNull String patternOffset, Direction direction) { @NotNull String patternOffset, Direction direction) {
if (globalIjOptions(injector).getUseNewRegex()) { if (globalIjOptions(injector).getUseNewRegex()) {
super.setLastSearchState(pattern, patternOffset, direction); super.setLastSearchState(pattern, patternOffset, direction);

View File

@ -329,7 +329,7 @@ public class EditorHelper {
final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight); final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
@NotNull final VimEditor editor1 = new IjVimEditor(editor); @NotNull final VimEditor editor1 = new IjVimEditor(editor);
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1; final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) + editor.getSettings().getAdditionalLinesCount();
final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine); final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine);
// For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen. // For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen.

View File

@ -12,6 +12,7 @@ package com.maddyhome.idea.vim.helper
import com.intellij.codeWithMe.ClientId import com.intellij.codeWithMe.ClientId
import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.CaretState
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ex.util.EditorUtil import com.intellij.openapi.editor.ex.util.EditorUtil
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
@ -19,6 +20,8 @@ import com.intellij.util.ui.table.JBTableRowEditor
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.group.IjOptionConstants import com.maddyhome.idea.vim.group.IjOptionConstants
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.inBlockSelection
import java.awt.Component import java.awt.Component
import javax.swing.JComponent import javax.swing.JComponent
import javax.swing.JTable import javax.swing.JTable
@ -93,3 +96,41 @@ internal val Caret.vimLine: Int
*/ */
internal val Editor.vimLine: Int internal val Editor.vimLine: Int
get() = this.caretModel.currentCaret.vimLine get() = this.caretModel.currentCaret.vimLine
internal inline fun Editor.runWithEveryCaretAndRestore(action: () -> Unit) {
val caretModel = this.caretModel
val carets = if (this.vim.inBlockSelection) null else caretModel.allCarets
if (carets == null || carets.size == 1) {
action()
}
else {
var initialDocumentSize = this.document.textLength
var documentSizeDifference = 0
val caretOffsets = carets.map { it.selectionStart to it.selectionEnd }
val restoredCarets = mutableListOf<CaretState>()
caretModel.removeSecondaryCarets()
for ((selectionStart, selectionEnd) in caretOffsets) {
if (selectionStart == selectionEnd) {
caretModel.primaryCaret.moveToOffset(selectionStart + documentSizeDifference)
}
else {
caretModel.primaryCaret.setSelection(
selectionStart + documentSizeDifference,
selectionEnd + documentSizeDifference
)
}
action()
restoredCarets.add(caretModel.caretsAndSelections.single())
val documentLength = this.document.textLength
documentSizeDifference += documentLength - initialDocumentSize
initialDocumentSize = documentLength
}
caretModel.caretsAndSelections = restoredCarets
}
}

View File

@ -14,7 +14,6 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.normalizeVisualColumn import com.maddyhome.idea.vim.api.normalizeVisualColumn
import com.maddyhome.idea.vim.api.options import com.maddyhome.idea.vim.api.options
import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenHeight import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenHeight
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenWidth import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenWidth
import com.maddyhome.idea.vim.helper.EditorHelper.getNonNormalizedVisualLineAtBottomOfScreen import com.maddyhome.idea.vim.helper.EditorHelper.getNonNormalizedVisualLineAtBottomOfScreen
@ -29,6 +28,7 @@ import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToBottomOfScre
import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToMiddleOfScreen import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToMiddleOfScreen
import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToTopOfScreen import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToTopOfScreen
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.VimStateMachine
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -56,7 +56,7 @@ internal object ScrollViewHelper {
// that this needs to be replaced as a more or less dumb line for line rewrite. // that this needs to be replaced as a more or less dumb line for line rewrite.
val topLine = getVisualLineAtTopOfScreen(editor) val topLine = getVisualLineAtTopOfScreen(editor)
val bottomLine = getVisualLineAtBottomOfScreen(editor) val bottomLine = getVisualLineAtBottomOfScreen(editor)
val lastLine = vimEditor.getVisualLineCount() - 1 val lastLine = vimEditor.getVisualLineCount() + editor.settings.additionalLinesCount
// We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred // We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred
val scrollOffset = injector.options(vimEditor).scrolloff val scrollOffset = injector.options(vimEditor).scrolloff

View File

@ -9,6 +9,7 @@
package com.maddyhome.idea.vim.helper; package com.maddyhome.idea.vim.helper;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx;
import com.intellij.lang.CodeDocumentationAwareCommenter; import com.intellij.lang.CodeDocumentationAwareCommenter;
import com.intellij.lang.Commenter; import com.intellij.lang.Commenter;
import com.intellij.lang.Language; import com.intellij.lang.Language;
@ -16,22 +17,30 @@ import com.intellij.lang.LanguageCommenters;
import com.intellij.openapi.diagnostic.Logger; 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.openapi.project.Project;
import com.intellij.psi.PsiComment; import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.spellchecker.SpellCheckerSeveritiesProvider;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.EngineEditorHelperKt; import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
import com.maddyhome.idea.vim.api.VimEditor; import com.maddyhome.idea.vim.api.VimEditor;
import com.maddyhome.idea.vim.regexp.*; import com.maddyhome.idea.vim.regexp.*;
import com.maddyhome.idea.vim.regexp.match.VimMatchResult; import com.maddyhome.idea.vim.regexp.match.VimMatchResult;
import com.maddyhome.idea.vim.state.mode.Mode;
import com.maddyhome.idea.vim.state.VimStateMachine;
import com.maddyhome.idea.vim.common.CharacterPosition; import com.maddyhome.idea.vim.common.CharacterPosition;
import com.maddyhome.idea.vim.common.Direction; import com.maddyhome.idea.vim.common.Direction;
import com.maddyhome.idea.vim.common.TextRange; import com.maddyhome.idea.vim.common.TextRange;
import com.maddyhome.idea.vim.newapi.IjVimCaret; import com.maddyhome.idea.vim.newapi.IjVimCaret;
import com.maddyhome.idea.vim.newapi.IjVimEditor; import com.maddyhome.idea.vim.newapi.IjVimEditor;
import com.maddyhome.idea.vim.regexp.CharPointer;
import com.maddyhome.idea.vim.regexp.RegExp;
import com.maddyhome.idea.vim.state.VimStateMachine;
import com.maddyhome.idea.vim.state.mode.Mode;
import it.unimi.dsi.fastutil.ints.IntComparator;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntRBTreeSet;
import it.unimi.dsi.fastutil.ints.IntSortedSet;
import kotlin.Pair; import kotlin.Pair;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -1573,6 +1582,42 @@ public class SearchHelper {
return PsiHelper.findMethodEnd(editor, caret.getOffset(), count); return PsiHelper.findMethodEnd(editor, caret.getOffset(), count);
} }
public static int findMisspelledWords(@NotNull Editor editor,
int startOffset,
int endOffset,
int skipCount,
IntComparator offsetOrdering) {
Project project = editor.getProject();
if (project == null) {
return -1;
}
IntSortedSet offsets = new IntRBTreeSet(offsetOrdering);
DaemonCodeAnalyzerEx.processHighlights(editor.getDocument(), project, SpellCheckerSeveritiesProvider.TYPO,
startOffset, endOffset, highlight -> {
if (highlight.getSeverity() == SpellCheckerSeveritiesProvider.TYPO) {
int offset = highlight.getStartOffset();
if (offset >= startOffset && offset <= endOffset) {
offsets.add(offset);
}
}
return true;
});
if (offsets.isEmpty()) {
return -1;
}
if (skipCount >= offsets.size()) {
return offsets.lastInt();
}
else {
IntIterator offsetIterator = offsets.iterator();
offsetIterator.skip(skipCount);
return offsetIterator.nextInt();
}
}
private static @NotNull String parseMatchPairsOption(final VimEditor vimEditor) { private static @NotNull String parseMatchPairsOption(final VimEditor vimEditor) {
List<String> pairs = options(injector, vimEditor).getMatchpairs(); List<String> pairs = options(injector, vimEditor).getMatchpairs();
StringBuilder res = new StringBuilder(); StringBuilder res = new StringBuilder();

View File

@ -14,6 +14,7 @@ import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.undo.UndoManager import com.intellij.openapi.command.undo.UndoManager
import com.intellij.openapi.components.Service import com.intellij.openapi.components.Service
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext 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.injector import com.maddyhome.idea.vim.api.injector
@ -21,6 +22,8 @@ import com.maddyhome.idea.vim.common.ChangesListener
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.inVisualMode
import com.maddyhome.idea.vim.undo.UndoRedoBase import com.maddyhome.idea.vim.undo.UndoRedoBase
/** /**
@ -39,6 +42,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
if (injector.globalIjOptions().oldundo) { if (injector.globalIjOptions().oldundo) {
SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) } SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
restoreVisualMode(editor)
} else { } else {
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
editor.runWithChangeTracking { editor.runWithChangeTracking {
@ -74,6 +78,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
if (undoManager.isRedoAvailable(fileEditor)) { if (undoManager.isRedoAvailable(fileEditor)) {
if (injector.globalIjOptions().oldundo) { if (injector.globalIjOptions().oldundo) {
SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) } SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
restoreVisualMode(editor)
} else { } else {
undoManager.redo(fileEditor) undoManager.redo(fileEditor)
CommandProcessor.getInstance().runUndoTransparentAction { CommandProcessor.getInstance().runUndoTransparentAction {
@ -131,4 +136,21 @@ internal class UndoRedoHelper : UndoRedoBase() {
val hasChanges: Boolean val hasChanges: Boolean
get() = changeListener.hasChanged || initialPath != editor.getPath() get() = changeListener.hasChanged || initialPath != editor.getPath()
} }
private fun restoreVisualMode(editor: VimEditor) {
if (!editor.inVisualMode && editor.getSelectionModel().hasSelection()) {
val detectedMode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor)
// Visual block selection is restored into multiple carets, so multi-carets that form a block are always
// identified as visual block mode, leading to false positives.
// Since I use visual block mode much less often than multi-carets, this is a judgment call to never restore
// visual block mode.
val wantedMode = if (detectedMode == SelectionType.BLOCK_WISE)
SelectionType.CHARACTER_WISE
else
detectedMode
VimPlugin.getVisualMotion().enterVisualMode(editor, wantedMode)
}
}
} }

View File

@ -1,32 +0,0 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.helper
import com.intellij.ide.plugins.StandalonePluginUpdateChecker
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.group.NotificationService
import com.maddyhome.idea.vim.icons.VimIcons
@Service(Service.Level.APP)
internal class VimStandalonePluginUpdateChecker : StandalonePluginUpdateChecker(
VimPlugin.getPluginId(),
updateTimestampProperty = PROPERTY_NAME,
NotificationService.IDEAVIM_STICKY_GROUP,
VimIcons.IDEAVIM,
) {
override fun skipUpdateCheck(): Boolean = VimPlugin.isNotEnabled() || "dev" in VimPlugin.getVersion()
companion object {
private const val PROPERTY_NAME = "ideavim.statistics.timestamp"
val instance: VimStandalonePluginUpdateChecker = service()
}
}

View File

@ -27,6 +27,7 @@ import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.ex.AnActionListener import com.intellij.openapi.actionSystem.ex.AnActionListener
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.impl.ScrollingModelImpl
import com.intellij.openapi.project.DumbAwareToggleAction import com.intellij.openapi.project.DumbAwareToggleAction
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
@ -56,6 +57,7 @@ internal object IdeaSpecifics {
private val surrounderAction = private val surrounderAction =
"com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction" "com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction"
private var editor: Editor? = null private var editor: Editor? = null
private var caretOffset = -1
private var completionPrevDocumentLength: Int? = null private var completionPrevDocumentLength: Int? = null
private var completionPrevDocumentOffset: Int? = null private var completionPrevDocumentOffset: Int? = null
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) { override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
@ -64,6 +66,7 @@ internal object IdeaSpecifics {
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR) val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
if (hostEditor != null) { if (hostEditor != null) {
editor = hostEditor editor = hostEditor
caretOffset = hostEditor.caretModel.offset
} }
val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction
@ -95,7 +98,8 @@ internal object IdeaSpecifics {
if (VimPlugin.isNotEnabled()) return if (VimPlugin.isNotEnabled()) return
val editor = editor val editor = editor
if (editor != null && action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) { if (editor != null) {
if (action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) {
val prevDocumentLength = completionPrevDocumentLength val prevDocumentLength = completionPrevDocumentLength
val prevDocumentOffset = completionPrevDocumentOffset val prevDocumentOffset = completionPrevDocumentOffset
@ -122,16 +126,28 @@ internal object IdeaSpecifics {
) )
} }
) { ) {
editor?.let { val commandState = editor.vim.vimStateMachine
val commandState = it.vim.vimStateMachine
commandState.mode = Mode.NORMAL() commandState.mode = Mode.NORMAL()
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim) VimPlugin.getChange().insertBeforeCursor(editor.vim, event.dataContext.vim)
KeyHandler.getInstance().reset(it.vim) KeyHandler.getInstance().reset(editor.vim)
}
} }
//endregion //endregion
if (caretOffset != -1 && caretOffset != editor.caretModel.offset) {
val scrollModel = editor.scrollingModel as ScrollingModelImpl
if (scrollModel.isScrollingNow) {
val v = scrollModel.verticalScrollOffset
val h = scrollModel.horizontalScrollOffset
scrollModel.finishAnimation()
scrollModel.scroll(h, v)
scrollModel.finishAnimation()
}
injector.scroll.scrollCaretIntoView(editor.vim)
}
}
this.editor = null this.editor = null
this.caretOffset = -1
} }
} }

View File

@ -66,16 +66,13 @@ import com.maddyhome.idea.vim.group.IjOptions
import com.maddyhome.idea.vim.group.MotionGroup import com.maddyhome.idea.vim.group.MotionGroup
import com.maddyhome.idea.vim.group.OptionGroup import com.maddyhome.idea.vim.group.OptionGroup
import com.maddyhome.idea.vim.group.ScrollGroup import com.maddyhome.idea.vim.group.ScrollGroup
import com.maddyhome.idea.vim.group.SearchGroup
import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl
import com.maddyhome.idea.vim.group.visual.VimVisualTimer import com.maddyhome.idea.vim.group.visual.VimVisualTimer
import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
import com.maddyhome.idea.vim.handler.correctorRequester import com.maddyhome.idea.vim.handler.correctorRequester
import com.maddyhome.idea.vim.handler.keyCheckRequests import com.maddyhome.idea.vim.handler.keyCheckRequests
import com.maddyhome.idea.vim.helper.GuicursorChangeListener import com.maddyhome.idea.vim.helper.GuicursorChangeListener
import com.maddyhome.idea.vim.helper.StrictMode import com.maddyhome.idea.vim.helper.StrictMode
import com.maddyhome.idea.vim.helper.VimStandalonePluginUpdateChecker
import com.maddyhome.idea.vim.helper.exitSelectMode import com.maddyhome.idea.vim.helper.exitSelectMode
import com.maddyhome.idea.vim.helper.exitVisualMode import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.forceBarCursor import com.maddyhome.idea.vim.helper.forceBarCursor
@ -92,6 +89,8 @@ import com.maddyhome.idea.vim.listener.MouseEventsDataHolder.skipNDragEvents
import com.maddyhome.idea.vim.listener.VimListenerManager.EditorListeners.add import com.maddyhome.idea.vim.listener.VimListenerManager.EditorListeners.add
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inSelectMode import com.maddyhome.idea.vim.state.mode.inSelectMode
import com.maddyhome.idea.vim.state.mode.mode import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.state.mode.selectionType import com.maddyhome.idea.vim.state.mode.selectionType
@ -305,6 +304,16 @@ internal object VimListenerManager {
class VimFileEditorManagerListener : FileEditorManagerListener { class VimFileEditorManagerListener : FileEditorManagerListener {
override fun selectionChanged(event: FileEditorManagerEvent) { override fun selectionChanged(event: FileEditorManagerEvent) {
if (VimPlugin.isNotEnabled()) return if (VimPlugin.isNotEnabled()) return
val newEditor = event.newEditor
if (newEditor is TextEditor) {
val editor = newEditor.editor
if (editor.isInsertMode) {
VimStateMachine.getInstance(editor).mode = Mode.NORMAL()
KeyHandler.getInstance().reset(editor.vim)
}
}
MotionGroup.fileEditorManagerSelectionChangedCallback(event) MotionGroup.fileEditorManagerSelectionChangedCallback(event)
FileGroup.fileEditorManagerSelectionChangedCallback(event) FileGroup.fileEditorManagerSelectionChangedCallback(event)
VimPlugin.getSearch().fileEditorManagerSelectionChangedCallback(event) VimPlugin.getSearch().fileEditorManagerSelectionChangedCallback(event)
@ -369,8 +378,6 @@ internal object VimListenerManager {
event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused)) event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused))
} }
VimStandalonePluginUpdateChecker.instance.pluginUsed()
} }
override fun editorReleased(event: EditorFactoryEvent) { override fun editorReleased(event: EditorFactoryEvent) {
@ -433,8 +440,6 @@ internal object VimListenerManager {
} }
private object EditorSelectionHandler : SelectionListener { private object EditorSelectionHandler : SelectionListener {
private var myMakingChanges = false
/** /**
* This event is executed for each caret using [com.intellij.openapi.editor.CaretModel.runForEachCaret] * This event is executed for each caret using [com.intellij.openapi.editor.CaretModel.runForEachCaret]
*/ */
@ -483,22 +488,9 @@ internal object VimListenerManager {
IdeaSelectionControl.controlNonVimSelectionChange(editor) IdeaSelectionControl.controlNonVimSelectionChange(editor)
} }
if (myMakingChanges || document is DocumentEx && document.isInEventsHandling) { if (document is DocumentEx && document.isInEventsHandling) {
return return
} }
myMakingChanges = true
try {
// Synchronize selections between editors
val newRange = selectionEvent.newRange
for (e in localEditors(document)) {
if (e != editor) {
e.selectionModel.vimSetSystemSelectionSilently(newRange.startOffset, newRange.endOffset)
}
}
} finally {
myMakingChanges = false
}
} }
} }

View File

@ -32,7 +32,7 @@ import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser.parseExpression
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import javax.swing.KeyStroke import javax.swing.KeyStroke
public open class IjVimSearchGroup : VimSearchGroupBase() { public abstract class IjVimSearchGroup : VimSearchGroupBase() {
init { init {
// TODO: Investigate migrating these listeners to use the effective value change listener // TODO: Investigate migrating these listeners to use the effective value change listener

View File

@ -29,6 +29,8 @@ import com.maddyhome.idea.vim.helper.checkInString
import com.maddyhome.idea.vim.helper.fileSize import com.maddyhome.idea.vim.helper.fileSize
import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
import it.unimi.dsi.fastutil.ints.IntComparator
import it.unimi.dsi.fastutil.ints.IntComparators
import java.util.* import java.util.*
import java.util.function.Function import java.util.function.Function
import java.util.regex.Pattern import java.util.regex.Pattern
@ -689,4 +691,26 @@ internal class IjVimSearchHelper : VimSearchHelperBase() {
// End offset exclusive // End offset exclusive
return TextRange(bstart, bend + 1) return TextRange(bstart, bend + 1)
} }
override fun findMisspelledWord(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Int {
val startOffset: Int
val endOffset: Int
val skipCount: Int
val offsetOrdering: IntComparator
if (count < 0) {
startOffset = 0
endOffset = caret.offset.point - 1
skipCount = -count - 1
offsetOrdering = IntComparators.OPPOSITE_COMPARATOR
}
else {
startOffset = caret.offset.point + 1
endOffset = editor.ij.document.textLength
skipCount = count - 1
offsetOrdering = IntComparators.NATURAL_COMPARATOR
}
return SearchHelper.findMisspelledWords(editor.ij, startOffset, endOffset, skipCount, offsetOrdering)
}
} }

View File

@ -333,7 +333,7 @@
* |[m| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodPreviousStartAction} * |[m| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodPreviousStartAction}
* |[p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction} * |[p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
* |[p| {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction} * |[p| {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction}
* |[s| TO BE IMPLEMENTED * |[s| {@link com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordPreviousAction}
* |[z| TO BE IMPLEMENTED * |[z| TO BE IMPLEMENTED
* |[{| {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceOpenAction} * |[{| {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceOpenAction}
* |]_CTRL-D| TO BE IMPLEMENTED * |]_CTRL-D| TO BE IMPLEMENTED
@ -358,7 +358,7 @@
* |]m| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodNextStartAction} * |]m| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodNextStartAction}
* |]p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction} * |]p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
* |]p| {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction} * |]p| {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction}
* |]s| TO BE IMPLEMENTED * |]s| {@link com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordNextAction}
* |]z| TO BE IMPLEMENTED * |]z| TO BE IMPLEMENTED
* |]}| {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceCloseAction} * |]}| {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceCloseAction}
* *

View File

@ -15,7 +15,7 @@ import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesColle
internal class ActionTracker : CounterUsagesCollector() { internal class ActionTracker : CounterUsagesCollector() {
companion object { companion object {
private val GROUP = EventLogGroup("vim.actions", 1) private val GROUP = EventLogGroup("vim.actions", 1, "FUS")
private val TRACKED_ACTIONS = GROUP.registerEvent( private val TRACKED_ACTIONS = GROUP.registerEvent(
"tracked", "tracked",
EventFields.StringValidatedByCustomRule("action_id", ActionRuleValidator::class.java), EventFields.StringValidatedByCustomRule("action_id", ActionRuleValidator::class.java),

View File

@ -48,7 +48,7 @@ internal class OptionsState : ApplicationUsagesCollector() {
} }
companion object { companion object {
private val GROUP = EventLogGroup("vim.options", 1) private val GROUP = EventLogGroup("vim.options", 1, "FUS")
private val IDEAJOIN = BooleanEventField(IjOptions.ideajoin.name) private val IDEAJOIN = BooleanEventField(IjOptions.ideajoin.name)
private val IDEAMARKS = BooleanEventField(IjOptions.ideamarks.name) private val IDEAMARKS = BooleanEventField(IjOptions.ideamarks.name)

View File

@ -31,7 +31,7 @@ internal class PluginState : ApplicationUsagesCollector() {
} }
companion object { companion object {
private val GROUP = EventLogGroup("vim.common", 1) private val GROUP = EventLogGroup("vim.common", 1, "FUS")
val extensionNames = listOf("textobj-entire", "argtextobj", "ReplaceWithRegister", "vim-paragraph-motion", "highlightedyank", "multiple-cursors", "exchange", "NERDTree", "surround", "commentary", "matchit", "textobj-indent") val extensionNames = listOf("textobj-entire", "argtextobj", "ReplaceWithRegister", "vim-paragraph-motion", "highlightedyank", "multiple-cursors", "exchange", "NERDTree", "surround", "commentary", "matchit", "textobj-indent")
val enabledExtensions = HashSet<String>() val enabledExtensions = HashSet<String>()

View File

@ -72,7 +72,7 @@ internal class ShortcutConflictState : ApplicationUsagesCollector() {
} }
companion object { companion object {
private val GROUP = EventLogGroup("vim.handlers", 1) private val GROUP = EventLogGroup("vim.handlers", 1, "FUS")
private val keyStrokes = listOf( private val keyStrokes = listOf(
KeyStroke.getKeyStroke('1'.code, CTRL_DOWN_MASK), KeyStroke.getKeyStroke('1'.code, CTRL_DOWN_MASK),

View File

@ -37,7 +37,7 @@ internal class VimscriptState : ApplicationUsagesCollector() {
} }
companion object { companion object {
private val GROUP = EventLogGroup("vim.vimscript", 1) private val GROUP = EventLogGroup("vim.vimscript", 1, "FUS")
val sourcedFiles = HashSet<String>() val sourcedFiles = HashSet<String>()
val extensionsEnabledWithPlug = HashSet<String>() val extensionsEnabledWithPlug = HashSet<String>()

View File

@ -1,12 +1,4 @@
<!-- <idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
~ Copyright 2003-2023 The IdeaVim authors
~
~ Use of this source code is governed by an MIT-style
~ license that can be found in the LICENSE.txt file or at
~ https://opensource.org/licenses/MIT.
-->
<idea-plugin url="https://plugins.jetbrains.com/plugin/164" xmlns:xi="http://www.w3.org/2001/XInclude">
<name>IdeaVim</name> <name>IdeaVim</name>
<id>IdeaVIM</id> <id>IdeaVIM</id>
<description><![CDATA[ <description><![CDATA[
@ -21,13 +13,13 @@
<li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li> <li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li>
</ul> </ul>
]]></description> ]]></description>
<version>SNAPSHOT</version> <version>chylex</version>
<vendor>JetBrains</vendor> <vendor>JetBrains</vendor>
<!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version --> <!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version -->
<!-- Check for [Version Update] tag in YouTrack as well --> <!-- Check for [Version Update] tag in YouTrack as well -->
<!-- Also, please update the value in build.gradle.kts file--> <!-- Also, please update the value in build.gradle.kts file-->
<idea-version since-build="233.11799.30"/> <idea-version since-build="233"/>
<!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) --> <!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) -->
<depends>com.intellij.modules.platform</depends> <depends>com.intellij.modules.platform</depends>
@ -158,5 +150,6 @@
</group> </group>
<action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/> <action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/>
<action id="VimJumpToSource" class="com.intellij.diff.actions.impl.OpenInEditorAction" />
</actions> </actions>
</idea-plugin> </idea-plugin>

View File

@ -12,8 +12,9 @@ package org.jetbrains.plugins.ideavim.action.motion.gn
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.common.Direction import com.maddyhome.idea.vim.common.Direction
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.VimTestCase
@ -85,7 +86,7 @@ class GnNextTextObjectTest : VimTestCase() {
private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) { private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) {
configureByText(before) configureByText(before)
VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS) VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
typeText(keys) typeText(keys)
assertState(after) assertState(after)
assertState(Mode.NORMAL()) assertState(Mode.NORMAL())

View File

@ -12,8 +12,9 @@ package org.jetbrains.plugins.ideavim.action.motion.gn
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.common.Direction import com.maddyhome.idea.vim.common.Direction
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.VimTestCase
@ -63,7 +64,7 @@ class GnPreviousTextObjectTest : VimTestCase() {
private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) { private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) {
configureByText(before) configureByText(before)
VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS) VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
typeText(keys) typeText(keys)
assertState(after) assertState(after)
assertState(Mode.NORMAL()) assertState(Mode.NORMAL())

View File

@ -11,9 +11,10 @@ import com.intellij.idea.TestFor
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.Direction
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.common.Direction
import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.VimTestCase
@ -57,7 +58,7 @@ class VisualSelectNextSearchTest : VimTestCase() {
@Test @Test
fun testWithoutSpaces() { fun testWithoutSpaces() {
configureByText("test<caret>test") configureByText("test<caret>test")
VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS) VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
typeText(injector.parser.parseKeys("gn")) typeText(injector.parser.parseKeys("gn"))
assertOffset(7) assertOffset(7)
assertSelection("test") assertSelection("test")

View File

@ -11,9 +11,10 @@ import com.intellij.idea.TestFor
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.Direction
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.common.Direction
import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.VimTestCase
@ -54,7 +55,7 @@ class VisualSelectPreviousSearchTest : VimTestCase() {
@Test @Test
fun testWithoutSpaces() { fun testWithoutSpaces() {
configureByText("tes<caret>ttest") configureByText("tes<caret>ttest")
VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS) VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
typeText(injector.parser.parseKeys("gN")) typeText(injector.parser.parseKeys("gN"))
assertOffset(0) assertOffset(0)
assertSelection("test") assertSelection("test")

View File

@ -10,6 +10,7 @@ package org.jetbrains.plugins.ideavim.action.motion.search
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.common.Direction import com.maddyhome.idea.vim.common.Direction
import com.maddyhome.idea.vim.newapi.vim
import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.VimTestCase
@ -167,7 +168,7 @@ class SearchAgainPreviousActionTest : VimTestCase() {
private fun doTestWithSearch(keys: String, before: String, after: String) { private fun doTestWithSearch(keys: String, before: String, after: String) {
doTest(keys, before, after) { doTest(keys, before, after) {
VimPlugin.getSearch().setLastSearchState(it, "all", "", Direction.FORWARDS) VimPlugin.getSearch().setLastSearchState(it.vim, "all", "", Direction.FORWARDS)
} }
} }
} }

View File

@ -0,0 +1,52 @@
/*
* Copyright 2003-2024 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package org.jetbrains.plugins.ideavim.action.change.shift
import com.intellij.application.options.CodeStyle
import com.intellij.ide.highlighter.JavaFileType
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimJavaTestCase
import org.junit.jupiter.api.Test
class ShiftRightLinesActionTest : VimJavaTestCase() {
@TestWithoutNeovim(SkipNeovimReason.NOT_VIM_TESTING)
@Test
fun `add indent for java file`() {
val before = """
|public class C {
|.I${c}nteger a;
|}
""".trimMargin().dotToTab()
val after = """
|public class C {
|.....Integer a;
|}
""".trimMargin().dotToTab()
usingTabs {
configureByJavaText(before)
typeText(">>".repeat(4))
assertState(after)
}
}
private fun usingTabs(action: () -> Unit) {
val testSettings = CodeStyle.createTestSettings()
val javaSettings = testSettings.getIndentOptions(JavaFileType.INSTANCE)
javaSettings.USE_TAB_CHARACTER = true
CodeStyle.setTemporarySettings(fixture.project, testSettings)
try {
action()
} finally {
CodeStyle.dropTemporarySettings(fixture.project)
}
}
}

View File

@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.handler.VimActionHandler
@CommandOrMotion(keys = ["<C-R>"], modes = [Mode.NORMAL]) @CommandOrMotion(keys = ["U", "<C-R>"], modes = [Mode.NORMAL, Mode.VISUAL])
public class RedoAction : VimActionHandler.SingleExecution() { public class RedoAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED

View File

@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.handler.VimActionHandler
@CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL]) @CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL, Mode.VISUAL])
public class UndoAction : VimActionHandler.SingleExecution() { public class UndoAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED

View File

@ -8,7 +8,6 @@
package com.maddyhome.idea.vim.action.change.change package com.maddyhome.idea.vim.action.change.change
import com.intellij.vim.annotations.CommandOrMotion import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
@ -22,7 +21,7 @@ import com.maddyhome.idea.vim.helper.CharacterHelper
/** /**
* @author vlan * @author vlan
*/ */
@CommandOrMotion(keys = ["u"], modes = [Mode.VISUAL]) @CommandOrMotion(keys = [], modes = [])
public class ChangeCaseLowerVisualAction : VisualOperatorActionHandler.ForEachCaret() { public class ChangeCaseLowerVisualAction : VisualOperatorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE override val type: Command.Type = Command.Type.CHANGE

View File

@ -8,7 +8,6 @@
package com.maddyhome.idea.vim.action.change.change package com.maddyhome.idea.vim.action.change.change
import com.intellij.vim.annotations.CommandOrMotion import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
@ -22,7 +21,7 @@ import com.maddyhome.idea.vim.helper.CharacterHelper
/** /**
* @author vlan * @author vlan
*/ */
@CommandOrMotion(keys = ["U"], modes = [Mode.VISUAL]) @CommandOrMotion(keys = [], modes = [])
public class ChangeCaseUpperVisualAction : VisualOperatorActionHandler.ForEachCaret() { public class ChangeCaseUpperVisualAction : VisualOperatorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE override val type: Command.Type = Command.Type.CHANGE

View File

@ -30,6 +30,7 @@ public class FilterVisualLinesAction : VimActionHandler.SingleExecution() {
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MOT_LINEWISE) override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MOT_LINEWISE)
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
injector.markService.setVisualSelectionMarks(editor)
injector.processGroup.startFilterCommand(editor, context, cmd) injector.processGroup.startFilterCommand(editor, context, cmd)
editor.exitVisualMode() editor.exitVisualMode()
return true return true

View File

@ -82,6 +82,13 @@ public sealed class TillCharacterMotion(
) )
} }
injector.motion.setLastFTCmd(tillCharacterMotionType, argument.character) injector.motion.setLastFTCmd(tillCharacterMotionType, argument.character)
val offset = if (!finishBeforeCharacter) ""
else if (direction == Direction.FORWARDS) "s-1"
else "s+1"
injector.searchGroup.setLastSearchState(editor, argument.character.let { if (it == '.') "\\." else it.toString() }, offset, direction)
return res.toMotionOrError() return res.toMotionOrError()
} }
} }

View File

@ -0,0 +1,58 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.action.motion.text
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.MotionType
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.Motion
import com.maddyhome.idea.vim.handler.MotionActionHandler
import com.maddyhome.idea.vim.handler.toMotionOrError
import com.maddyhome.idea.vim.helper.enumSetOf
import java.util.*
@CommandOrMotion(keys = ["]s"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
public class MotionMisspelledWordNextAction : MotionActionHandler.ForEachCaret() {
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_JUMP)
override fun getOffset(
editor: VimEditor,
caret: ImmutableVimCaret,
context: ExecutionContext,
argument: Argument?,
operatorArguments: OperatorArguments,
): Motion {
return injector.searchHelper.findMisspelledWord(editor, caret, operatorArguments.count1).toMotionOrError()
}
override val motionType: MotionType = MotionType.EXCLUSIVE
}
@CommandOrMotion(keys = ["[s"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
public class MotionMisspelledWordPreviousAction : MotionActionHandler.ForEachCaret() {
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_JUMP)
override fun getOffset(
editor: VimEditor,
caret: ImmutableVimCaret,
context: ExecutionContext,
argument: Argument?,
operatorArguments: OperatorArguments,
): Motion {
return injector.searchHelper.findMisspelledWord(editor, caret, -operatorArguments.count1).toMotionOrError()
}
override val motionType: MotionType = MotionType.EXCLUSIVE
}

View File

@ -145,7 +145,7 @@ public interface VimChangeGroup {
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
) )
public fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: String): VimCaret public fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: CharSequence): VimCaret
public fun insertText(editor: VimEditor, caret: VimCaret, str: String): VimCaret public fun insertText(editor: VimEditor, caret: VimCaret, str: String): VimCaret

View File

@ -203,7 +203,7 @@ public abstract class VimChangeGroupBase : VimChangeGroup {
* @param caret The caret to start insertion in * @param caret The caret to start insertion in
* @param str The text to insert * @param str The text to insert
*/ */
override fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: String): VimCaret { override fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: CharSequence): VimCaret {
(editor as MutableVimEditor).insertText(Offset(offset), str) (editor as MutableVimEditor).insertText(Offset(offset), str)
val newCaret = caret.moveToInlayAwareOffset(offset + str.length) val newCaret = caret.moveToInlayAwareOffset(offset + str.length)

View File

@ -25,7 +25,7 @@ public interface VimSearchGroup {
* Last used pattern to perform a substitution. * Last used pattern to perform a substitution.
*/ */
public var lastSubstitutePattern: String? public var lastSubstitutePattern: String?
public fun setLastSearchState(editor: VimEditor, pattern: String, patternOffset: String, direction: Direction?)
public fun searchBackward(editor: VimEditor, offset: Int, count: Int): TextRange? public fun searchBackward(editor: VimEditor, offset: Int, count: Int): TextRange?
/** /**

View File

@ -239,4 +239,10 @@ public interface VimSearchHelper {
count: Int, count: Int,
isOuter: Boolean, isOuter: Boolean,
): TextRange? ): TextRange?
public fun findMisspelledWord(
editor: VimEditor,
caret: ImmutableVimCaret,
count: Int,
): Int
} }

View File

@ -259,8 +259,13 @@ public class ToActionMappingInfo(
LOG.debug("Executing 'ToAction' mapping...") LOG.debug("Executing 'ToAction' mapping...")
val editorDataContext = injector.executionContextManager.onEditor(editor, context) val editorDataContext = injector.executionContextManager.onEditor(editor, context)
val dataContext = injector.executionContextManager.onCaret(editor.currentCaret(), editorDataContext) val dataContext = injector.executionContextManager.onCaret(editor.currentCaret(), editorDataContext)
val commandBuilder = editor.vimStateMachine.commandBuilder
for (i in 0 until commandBuilder.count.coerceAtLeast(1)) {
injector.actionExecutor.executeAction(action, dataContext) injector.actionExecutor.executeAction(action, dataContext)
} }
commandBuilder.resetCount()
}
public companion object { public companion object {
private val LOG = vimLogger<ToActionMappingInfo>() private val LOG = vimLogger<ToActionMappingInfo>()

View File

@ -19,7 +19,6 @@ import com.maddyhome.idea.vim.api.VimMarkService
import com.maddyhome.idea.vim.api.getText import com.maddyhome.idea.vim.api.getText
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ExException import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.ex.InvalidRangeException import com.maddyhome.idea.vim.ex.InvalidRangeException
@ -29,6 +28,7 @@ import com.maddyhome.idea.vim.helper.Msg
import com.maddyhome.idea.vim.mark.Mark import com.maddyhome.idea.vim.mark.Mark
import com.maddyhome.idea.vim.mark.VimMark import com.maddyhome.idea.vim.mark.VimMark
import com.maddyhome.idea.vim.put.PutData import com.maddyhome.idea.vim.put.PutData
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
import kotlin.math.min import kotlin.math.min
@ -49,23 +49,14 @@ public data class MoveTextCommand(val ranges: Ranges, val argument: String) : Co
val caretPosition = caret.getBufferPosition() val caretPosition = caret.getBufferPosition()
val goToLineCommand = injector.vimscriptParser.parseCommand(argument) ?: throw ExException("E16: Invalid range") val goToLineCommand = injector.vimscriptParser.parseCommand(argument) ?: throw ExException("E16: Invalid range")
val range = getTextRange(editor, caret, false)
/*
FIXME: see VIM-2884. It's absolutely not the best way to resolve this bug
*/
caret.moveToOffset(range.startOffset)
val lineRange = getLineRange(editor, caret) val lineRange = getLineRange(editor, caret)
val line = min(editor.fileSize().toInt(), normalizeLine(editor, caret, goToLineCommand, lineRange)) val line = min(editor.fileSize().toInt(), normalizeLine(editor, caret, goToLineCommand, lineRange))
val linesMoved = lineRange.endLine - lineRange.startLine + 1 val range = getTextRange(editor, caret, false)
if (line < -1 || line + linesMoved >= editor.lineCount()) {
caret.moveToBufferPosition(caretPosition)
throw ExException("E16: Invalid range")
}
val shift = line + 1 - editor.offsetToBufferPosition(range.startOffset).line val shift = line + 1 - editor.offsetToBufferPosition(range.startOffset).line
val text = editor.getText(range)
val localMarks = injector.markService.getAllLocalMarks(caret) val localMarks = injector.markService.getAllLocalMarks(caret)
.filter { range.contains(it.offset(editor)) } .filter { range.contains(it.offset(editor)) }
.filter { it.key != VimMarkService.SELECTION_START_MARK && it.key != VimMarkService.SELECTION_END_MARK } .filter { it.key != VimMarkService.SELECTION_START_MARK && it.key != VimMarkService.SELECTION_END_MARK }
@ -78,33 +69,24 @@ public data class MoveTextCommand(val ranges: Ranges, val argument: String) : Co
val selectionStartOffset = lastSelectionInfo.start?.let { editor.bufferPositionToOffset(it) } val selectionStartOffset = lastSelectionInfo.start?.let { editor.bufferPositionToOffset(it) }
val selectionEndOffset = lastSelectionInfo.end?.let { editor.bufferPositionToOffset(it) } val selectionEndOffset = lastSelectionInfo.end?.let { editor.bufferPositionToOffset(it) }
val text = editor.getText(range)
val textData = PutData.TextData(text, SelectionType.LINE_WISE, emptyList(), null)
val dropNewLineInEnd = (line + linesMoved == editor.lineCount() - 1 && text.last() == '\n') ||
(lineRange.endLine == editor.lineCount() - 1)
editor.deleteString(range) editor.deleteString(range)
val putData = if (line == -1) {
caret.moveToOffset(0)
PutData(textData, null, 1, insertTextBeforeCaret = true, rawIndent = true, caretAfterInsertedText = false)
} else {
PutData(textData, null, 1, insertTextBeforeCaret = false, rawIndent = true, caretAfterInsertedText = false, putToLine = line)
}
injector.put.putTextForCaret(editor, caret, context, putData)
if (dropNewLineInEnd) { val textData = PutData.TextData(text, SelectionType.LINE_WISE, emptyList(), null)
assert(editor.text().last() == '\n') val putData = PutData(
editor.deleteString(TextRange(editor.text().length - 1, editor.text().length)) textData,
} null,
1,
insertTextBeforeCaret = false,
rawIndent = true,
caretAfterInsertedText = false,
putToLine = line
)
injector.put.putTextForCaret(editor, caret, context, putData)
globalMarks.forEach { shiftGlobalMark(editor, it, shift) } globalMarks.forEach { shiftGlobalMark(editor, it, shift) }
localMarks.forEach { shiftLocalMark(caret, it, shift) } localMarks.forEach { shiftLocalMark(caret, it, shift) }
shiftSelectionInfo(caret, selectionStartOffset, selectionEndOffset, lastSelectionInfo, shift, range) shiftSelectionInfo(caret, selectionStartOffset, selectionEndOffset, lastSelectionInfo, shift, range)
val newCaretPosition = shiftBufferPosition(caretPosition, shift)
caret.moveToBufferPosition(newCaretPosition)
return ExecutionResult.Success return ExecutionResult.Success
} }