1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2024-11-25 07:42:59 +01: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" />
</value>
</option>
<option name="LINE_SEPARATOR" value="&#10;" />
<JavaCodeStyleSettings>
<option name="FIELD_NAME_PREFIX" value="my" />
<option name="STATIC_FIELD_NAME_PREFIX" value="our" />

View File

@ -27,6 +27,8 @@ usual beta standards.
### Fixes:
* [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:
* [725](https://github.com/JetBrains/ideavim/pull/725) by [Emanuel Gestosa](https://github.com/emanuelgestosa): Regex

View File

@ -70,7 +70,7 @@ plugins {
application
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.kotlinx.kover") version "0.6.1"
@ -305,28 +305,26 @@ tasks {
from(createOpenApiSourceJar) { into("lib/src") }
}
val pluginVersion = version
// Don't forget to update plugin.xml
patchPluginXml {
sinceBuild.set("233.11799.30")
// Get the latest available change notes from the changelog file
changeNotes.set(
provider {
with(changelog) {
val log = try {
getUnreleased()
} catch (e: MissingVersionException) {
getOrNull(pluginVersion.toString()) ?: getLatest()
}
renderItem(
log,
org.jetbrains.changelog.Changelog.OutputType.HTML,
)
}
},
)
}
val pluginVersion = version
// Don't forget to update plugin.xml
patchPluginXml {
// Get the latest available change notes from the changelog file
changeNotes.set(
provider {
with(changelog) {
val log = try {
getUnreleased()
} catch (e: MissingVersionException) {
getOrNull(pluginVersion.toString()) ?: getLatest()
}
renderItem(
log,
org.jetbrains.changelog.Changelog.OutputType.HTML,
)
}
},
)
}
}
// --- Tests
@ -460,7 +458,7 @@ val fixVersionsElementType = "VersionBundleElement"
tasks.register("releaseActions") {
group = "other"
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()) {
println("Updating statuses for tickets: $tickets")
setYoutrackStatus(tickets, "Fixed")

View File

@ -8,16 +8,17 @@
# 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
ideaType=IC
downloadIdeaSources=true
instrumentPluginCode=true
version=SNAPSHOT
version=chylex-28
javaVersion=17
remoteRobotVersion=0.11.22
antlrVersion=4.10.1
kotlin.incremental.useClasspathSnapshot=false
# Please don't forget to update kotlin version in buildscript section
# Also update kotlinxSerializationVersion version

View File

@ -217,6 +217,8 @@ private object FileTypePatterns {
return if (fileTypeName in htmlLikeFileTypes) {
this.htmlPatterns
} else if (fileTypeName == "JAVA" || fileExtension == "java") {
this.javaPatterns
} else if (fileTypeName == "Ruby" || fileExtension == "rb") {
this.rubyPatterns
} else if (fileTypeName == "RHTML" || fileExtension == "erb") {
@ -231,7 +233,7 @@ private object FileTypePatterns {
} else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") {
this.cMakePatterns
} else {
return null
this.htmlPatterns
}
}
@ -242,6 +244,7 @@ private object FileTypePatterns {
)
private val htmlPatterns = createHtmlPatterns()
private val javaPatterns = createJavaPatterns()
private val rubyPatterns = createRubyPatterns()
private val rubyAndHtmlPatterns = rubyPatterns + htmlPatterns
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 {
// 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.

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 {

View File

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

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.api.ExecutionContext
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.endsWithNewLine
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.setOperatorFunction
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.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
@ -80,7 +84,7 @@ internal class VimSurroundExtension : VimExtension {
override val isRepeatable = true
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)
}
}
@ -101,7 +105,7 @@ internal class VimSurroundExtension : VimExtension {
val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset)
if (lastNonWhiteSpaceOffset != null) {
val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1)
performSurround(pair, range, it)
performSurround(pair, range, it, count = operatorArguments.count1)
}
// it.moveToOffset(lineStartOffset)
}
@ -121,15 +125,13 @@ internal class VimSurroundExtension : VimExtension {
private class VSurroundHandler : ExtensionHandler {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val selectionStart = editor.ij.caretModel.primaryCaret.selectionStart
// 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
}
runWriteAction {
// Leave visual mode
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
editor.ij.caretModel.moveToOffset(selectionStart)
}
}
}
@ -150,6 +152,10 @@ internal class VimSurroundExtension : VimExtension {
companion object {
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
val surroundings = editor.sortedCarets()
.map {
@ -257,21 +263,42 @@ internal class VimSurroundExtension : VimExtension {
}
}
private class Operator : OperatorFunction {
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val ijEditor = editor.ij
val c = getChar(ijEditor)
private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction {
override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val editor = vimEditor.ij
val c = getChar(editor)
if (c.code == 0) return true
val pair = getOrInputPair(c, ijEditor) ?: return false
// XXX: Will it work with line-wise or block-wise selections?
val range = getSurroundRange(editor.currentCaret()) ?: return false
performSurround(pair, range, editor.currentCaret(), selectionType == SelectionType.LINE_WISE)
// Jump back to start
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
val pair = getOrInputPair(c, editor) ?: return false
runWriteAction {
val change = VimPlugin.getChange()
if (supportsMultipleCursors) {
editor.runWithEveryCaretAndRestore {
applyOnce(editor, change, pair, count)
}
}
else {
applyOnce(editor, change, pair, count)
// Jump back to start
executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor)
}
}
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? {
val editor = caret.editor
val ijEditor = editor.ij
@ -354,15 +381,15 @@ private fun getChar(editor: Editor): Char {
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 {
val editor = caret.editor
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 hasNewLine = editor.endsWithNewLine()
val rightSurround = if (tagsOnNewLines) {
val rightSurround = (if (tagsOnNewLines) {
if (isEOF && !hasNewLine) {
"\n" + pair.second
} else {
@ -370,7 +397,7 @@ private fun performSurround(pair: Pair<String, String>, range: TextRange, caret:
}
} else {
pair.second
}
}).let { RepeatedCharSequence.of(it, count) }
change.insertText(editor, caret, range.startOffset, leftSurround)
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.function.Consumer
import kotlin.math.max
import kotlin.math.min
/**
* Provides all the insert/replace related functionality
@ -395,6 +394,7 @@ public class ChangeGroup : VimChangeGroupBase() {
context: ExecutionContext,
range: TextRange,
) {
val startPos = editor.offsetToBufferPosition(caret.offset.point)
val startOffset = editor.getLineStartForOffset(range.startOffset)
val endOffset = editor.getLineEndForOffset(range.endOffset)
val ijEditor = (editor as IjVimEditor).editor
@ -419,11 +419,7 @@ public class ChangeGroup : VimChangeGroupBase() {
}
}
val afterAction = {
val firstLine = editor.offsetToBufferPosition(
min(startOffset.toDouble(), endOffset.toDouble()).toInt()
).line
val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine)
caret.moveToOffset(newOffset)
caret.moveToOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, startPos.line))
restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
}
if (project != null) {
@ -535,7 +531,7 @@ public class ChangeGroup : VimChangeGroupBase() {
val soff = editor.getLineStartOffset(l)
val eoff = editor.getLineEndOffset(l, true)
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())
.toInt()
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 unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true))
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 commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, 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
import com.intellij.codeInsight.completion.CompletionPhase
import com.intellij.codeInsight.completion.impl.CompletionServiceImpl
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.logger
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.macro.VimMacroBase
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
/**
* Used to handle playback of macros
@ -76,7 +79,7 @@ internal class MacroGroup : VimMacroBase() {
ProgressManager.getInstance().executeNonCancelableSection {
// Prevent autocompletion during macros.
// See https://github.com/JetBrains/ideavim/pull/772 for details
// CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion)
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion)
getInstance().handleKey(editor, key, context)
}
if (injector.messages.isError()) return@runnable
@ -88,6 +91,9 @@ internal class MacroGroup : VimMacroBase() {
} finally {
keyStack.removeFirst()
}
if (!isInternalMacro) {
MacroAutoImport.run(editor.ij, context.ij)
}
}
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 direction The direction to search
*/
@TestOnly
public void setLastSearchState(@SuppressWarnings("unused") @NotNull Editor editor, @NotNull String pattern,
@Override
public void setLastSearchState(@SuppressWarnings("unused") @NotNull VimEditor editor, @NotNull String pattern,
@NotNull String patternOffset, Direction direction) {
if (globalIjOptions(injector).getUseNewRegex()) {
super.setLastSearchState(pattern, patternOffset, direction);

View File

@ -329,7 +329,7 @@ public class EditorHelper {
final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
@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);
// 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.openapi.editor.Caret
import com.intellij.openapi.editor.CaretState
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ex.util.EditorUtil
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.group.IjOptionConstants
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 javax.swing.JComponent
import javax.swing.JTable
@ -93,3 +96,41 @@ internal val Caret.vimLine: Int
*/
internal val Editor.vimLine: Int
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.options
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.getApproximateScreenWidth
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.scrollVisualLineToTopOfScreen
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.VimStateMachine
import kotlin.math.max
import kotlin.math.min
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.
val topLine = getVisualLineAtTopOfScreen(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
val scrollOffset = injector.options(vimEditor).scrolloff

View File

@ -9,6 +9,7 @@
package com.maddyhome.idea.vim.helper;
import com.google.common.collect.Lists;
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx;
import com.intellij.lang.CodeDocumentationAwareCommenter;
import com.intellij.lang.Commenter;
import com.intellij.lang.Language;
@ -16,22 +17,30 @@ import com.intellij.lang.LanguageCommenters;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.spellchecker.SpellCheckerSeveritiesProvider;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
import com.maddyhome.idea.vim.api.VimEditor;
import com.maddyhome.idea.vim.regexp.*;
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.Direction;
import com.maddyhome.idea.vim.common.TextRange;
import com.maddyhome.idea.vim.newapi.IjVimCaret;
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 org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
@ -1573,6 +1582,42 @@ public class SearchHelper {
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) {
List<String> pairs = options(injector, vimEditor).getMatchpairs();
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.components.Service
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.VimEditor
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.newapi.globalIjOptions
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
/**
@ -39,6 +42,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
if (injector.globalIjOptions().oldundo) {
SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
restoreVisualMode(editor)
} else {
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
editor.runWithChangeTracking {
@ -74,6 +78,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
if (undoManager.isRedoAvailable(fileEditor)) {
if (injector.globalIjOptions().oldundo) {
SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
restoreVisualMode(editor)
} else {
undoManager.redo(fileEditor)
CommandProcessor.getInstance().runUndoTransparentAction {
@ -131,4 +136,21 @@ internal class UndoRedoHelper : UndoRedoBase() {
val hasChanges: Boolean
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.impl.ProxyShortcutSet
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.impl.ScrollingModelImpl
import com.intellij.openapi.project.DumbAwareToggleAction
import com.intellij.openapi.util.TextRange
import com.maddyhome.idea.vim.KeyHandler
@ -56,6 +57,7 @@ internal object IdeaSpecifics {
private val surrounderAction =
"com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction"
private var editor: Editor? = null
private var caretOffset = -1
private var completionPrevDocumentLength: Int? = null
private var completionPrevDocumentOffset: Int? = null
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
@ -64,6 +66,7 @@ internal object IdeaSpecifics {
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
if (hostEditor != null) {
editor = hostEditor
caretOffset = hostEditor.caretModel.offset
}
val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction
@ -95,43 +98,56 @@ internal object IdeaSpecifics {
if (VimPlugin.isNotEnabled()) return
val editor = editor
if (editor != null && action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) {
val prevDocumentLength = completionPrevDocumentLength
val prevDocumentOffset = completionPrevDocumentOffset
if (editor != null) {
if (action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) {
val prevDocumentLength = completionPrevDocumentLength
val prevDocumentOffset = completionPrevDocumentOffset
if (prevDocumentLength != null && prevDocumentOffset != null) {
val register = VimPlugin.getRegister()
val addedTextLength = editor.document.textLength - prevDocumentLength
val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
if (prevDocumentLength != null && prevDocumentOffset != null) {
val register = VimPlugin.getRegister()
val addedTextLength = editor.document.textLength - prevDocumentLength
val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength)))
repeat(caretShift.coerceAtLeast(0)) {
register.recordKeyStroke(leftArrow)
register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength)))
repeat(caretShift.coerceAtLeast(0)) {
register.recordKeyStroke(leftArrow)
}
}
this.completionPrevDocumentLength = null
this.completionPrevDocumentOffset = null
}
this.completionPrevDocumentLength = null
this.completionPrevDocumentOffset = null
}
//region Enter insert mode after surround with if
if (surrounderAction == action.javaClass.name && surrounderItems.any {
action.templatePresentation.text.endsWith(
it,
)
}
) {
editor?.let {
val commandState = it.vim.vimStateMachine
//region Enter insert mode after surround with if
if (surrounderAction == action.javaClass.name && surrounderItems.any {
action.templatePresentation.text.endsWith(
it,
)
}
) {
val commandState = editor.vim.vimStateMachine
commandState.mode = Mode.NORMAL()
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
KeyHandler.getInstance().reset(it.vim)
VimPlugin.getChange().insertBeforeCursor(editor.vim, event.dataContext.vim)
KeyHandler.getInstance().reset(editor.vim)
}
//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)
}
}
//endregion
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.OptionGroup
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.VimVisualTimer
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.keyCheckRequests
import com.maddyhome.idea.vim.helper.GuicursorChangeListener
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.exitVisualMode
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.newapi.IjVimEditor
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.mode
import com.maddyhome.idea.vim.state.mode.selectionType
@ -305,6 +304,16 @@ internal object VimListenerManager {
class VimFileEditorManagerListener : FileEditorManagerListener {
override fun selectionChanged(event: FileEditorManagerEvent) {
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)
FileGroup.fileEditorManagerSelectionChangedCallback(event)
VimPlugin.getSearch().fileEditorManagerSelectionChangedCallback(event)
@ -369,8 +378,6 @@ internal object VimListenerManager {
event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused))
}
VimStandalonePluginUpdateChecker.instance.pluginUsed()
}
override fun editorReleased(event: EditorFactoryEvent) {
@ -433,8 +440,6 @@ internal object VimListenerManager {
}
private object EditorSelectionHandler : SelectionListener {
private var myMakingChanges = false
/**
* This event is executed for each caret using [com.intellij.openapi.editor.CaretModel.runForEachCaret]
*/
@ -483,22 +488,9 @@ internal object VimListenerManager {
IdeaSelectionControl.controlNonVimSelectionChange(editor)
}
if (myMakingChanges || document is DocumentEx && document.isInEventsHandling) {
if (document is DocumentEx && document.isInEventsHandling) {
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 javax.swing.KeyStroke
public open class IjVimSearchGroup : VimSearchGroupBase() {
public abstract class IjVimSearchGroup : VimSearchGroupBase() {
init {
// 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.state.VimStateMachine.Companion.getInstance
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.function.Function
import java.util.regex.Pattern
@ -689,4 +691,26 @@ internal class IjVimSearchHelper : VimSearchHelperBase() {
// End offset exclusive
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}
* |[p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
* |[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
* |[{| {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceOpenAction}
* |]_CTRL-D| TO BE IMPLEMENTED
@ -358,7 +358,7 @@
* |]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.PutTextAfterCursorNoIndentAction}
* |]s| TO BE IMPLEMENTED
* |]s| {@link com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordNextAction}
* |]z| TO BE IMPLEMENTED
* |]}| {@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() {
companion object {
private val GROUP = EventLogGroup("vim.actions", 1)
private val GROUP = EventLogGroup("vim.actions", 1, "FUS")
private val TRACKED_ACTIONS = GROUP.registerEvent(
"tracked",
EventFields.StringValidatedByCustomRule("action_id", ActionRuleValidator::class.java),

View File

@ -48,7 +48,7 @@ internal class OptionsState : ApplicationUsagesCollector() {
}
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 IDEAMARKS = BooleanEventField(IjOptions.ideamarks.name)

View File

@ -31,7 +31,7 @@ internal class PluginState : ApplicationUsagesCollector() {
}
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 enabledExtensions = HashSet<String>()

View File

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

View File

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

View File

@ -1,12 +1,4 @@
<!--
~ 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">
<idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
<name>IdeaVim</name>
<id>IdeaVIM</id>
<description><![CDATA[
@ -21,13 +13,13 @@
<li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li>
</ul>
]]></description>
<version>SNAPSHOT</version>
<version>chylex</version>
<vendor>JetBrains</vendor>
<!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version -->
<!-- Check for [Version Update] tag in YouTrack as well -->
<!-- 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) -->
<depends>com.intellij.modules.platform</depends>
@ -158,5 +150,6 @@
</group>
<action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/>
<action id="VimJumpToSource" class="com.intellij.diff.actions.impl.OpenInEditorAction" />
</actions>
</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.api.injector
import com.maddyhome.idea.vim.state.mode.Mode
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.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
@ -85,7 +86,7 @@ class GnNextTextObjectTest : VimTestCase() {
private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) {
configureByText(before)
VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
typeText(keys)
assertState(after)
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.api.injector
import com.maddyhome.idea.vim.state.mode.Mode
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.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
@ -63,7 +64,7 @@ class GnPreviousTextObjectTest : VimTestCase() {
private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) {
configureByText(before)
VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
typeText(keys)
assertState(after)
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.action.motion.search.SearchWholeWordForwardAction
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.SelectionType
import com.maddyhome.idea.vim.common.Direction
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
@ -57,7 +58,7 @@ class VisualSelectNextSearchTest : VimTestCase() {
@Test
fun testWithoutSpaces() {
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"))
assertOffset(7)
assertSelection("test")

View File

@ -11,9 +11,10 @@ import com.intellij.idea.TestFor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction
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.SelectionType
import com.maddyhome.idea.vim.common.Direction
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
@ -54,7 +55,7 @@ class VisualSelectPreviousSearchTest : VimTestCase() {
@Test
fun testWithoutSpaces() {
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"))
assertOffset(0)
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.common.Direction
import com.maddyhome.idea.vim.newapi.vim
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
@ -167,7 +168,7 @@ class SearchAgainPreviousActionTest : VimTestCase() {
private fun doTestWithSearch(keys: String, before: String, after: String) {
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.handler.VimActionHandler
@CommandOrMotion(keys = ["<C-R>"], modes = [Mode.NORMAL])
@CommandOrMotion(keys = ["U", "<C-R>"], modes = [Mode.NORMAL, Mode.VISUAL])
public class RedoAction : VimActionHandler.SingleExecution() {
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.handler.VimActionHandler
@CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL])
@CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL, Mode.VISUAL])
public class UndoAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED

View File

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

View File

@ -8,7 +8,6 @@
package com.maddyhome.idea.vim.action.change.change
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.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
@ -22,7 +21,7 @@ import com.maddyhome.idea.vim.helper.CharacterHelper
/**
* @author vlan
*/
@CommandOrMotion(keys = ["U"], modes = [Mode.VISUAL])
@CommandOrMotion(keys = [], modes = [])
public class ChangeCaseUpperVisualAction : VisualOperatorActionHandler.ForEachCaret() {
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 fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
injector.markService.setVisualSelectionMarks(editor)
injector.processGroup.startFilterCommand(editor, context, cmd)
editor.exitVisualMode()
return true

View File

@ -82,6 +82,13 @@ public sealed class TillCharacterMotion(
)
}
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()
}
}

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

View File

@ -203,7 +203,7 @@ public abstract class VimChangeGroupBase : VimChangeGroup {
* @param caret The caret to start insertion in
* @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)
val newCaret = caret.moveToInlayAwareOffset(offset + str.length)

View File

@ -25,7 +25,7 @@ public interface VimSearchGroup {
* Last used pattern to perform a substitution.
*/
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?
/**

View File

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

View File

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

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.injector
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.ex.ExException
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.VimMark
import com.maddyhome.idea.vim.put.PutData
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
import kotlin.math.min
@ -49,23 +49,14 @@ public data class MoveTextCommand(val ranges: Ranges, val argument: String) : Co
val caretPosition = caret.getBufferPosition()
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 line = min(editor.fileSize().toInt(), normalizeLine(editor, caret, goToLineCommand, lineRange))
val linesMoved = lineRange.endLine - lineRange.startLine + 1
if (line < -1 || line + linesMoved >= editor.lineCount()) {
caret.moveToBufferPosition(caretPosition)
throw ExException("E16: Invalid range")
}
val range = getTextRange(editor, caret, false)
val shift = line + 1 - editor.offsetToBufferPosition(range.startOffset).line
val text = editor.getText(range)
val localMarks = injector.markService.getAllLocalMarks(caret)
.filter { range.contains(it.offset(editor)) }
.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 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)
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) {
assert(editor.text().last() == '\n')
editor.deleteString(TextRange(editor.text().length - 1, editor.text().length))
}
val textData = PutData.TextData(text, SelectionType.LINE_WISE, emptyList(), null)
val putData = PutData(
textData,
null,
1,
insertTextBeforeCaret = false,
rawIndent = true,
caretAfterInsertedText = false,
putToLine = line
)
injector.put.putTextForCaret(editor, caret, context, putData)
globalMarks.forEach { shiftGlobalMark(editor, it, shift) }
localMarks.forEach { shiftLocalMark(caret, it, shift) }
shiftSelectionInfo(caret, selectionStartOffset, selectionEndOffset, lastSelectionInfo, shift, range)
val newCaretPosition = shiftBufferPosition(caretPosition, shift)
caret.moveToBufferPosition(newCaretPosition)
return ExecutionResult.Success
}