diff --git a/.gitignore b/.gitignore index 85c6f93d2..d0a300559 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,7 @@ .teamcity/*.iml # Generated by gradle task "generateGrammarSource" -src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated +vim-engine/src/main/java/com/maddyhome/idea/vim/parser/generated vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated # Generated JSONs for lazy classloading /vim-engine/src/main/resources/ksp-generated diff --git a/build.gradle.kts b/build.gradle.kts index e1f417642..09c271df7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -51,7 +51,7 @@ buildscript { classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r") // This is needed for jgit to connect to ssh - classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.9.0.202403050737-r") + classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.10.0.202406032230-r") classpath("org.kohsuke:github-api:1.305") classpath("io.ktor:ktor-client-core:2.3.11") diff --git a/doc/IdeaVim Plugins.md b/doc/IdeaVim Plugins.md index 8e1d6b546..d3293fa85 100644 --- a/doc/IdeaVim Plugins.md +++ b/doc/IdeaVim Plugins.md @@ -129,8 +129,26 @@ Original plugin: [vim-multiple-cursors](https://github.com/terryma/vim-multiple- </details> ### Instructions - -https://github.com/terryma/vim-multiple-cursors/blob/master/doc/multiple_cursors.txt + +At the moment, the default key binds for this plugin do not get mapped correctly in IdeaVim (see [VIM-2178](https://youtrack.jetbrains.com/issue/VIM-2178)). To enable the default key binds, add the following to your `.ideavimrc` file... + +``` +" Remap multiple-cursors shortcuts to match terryma/vim-multiple-cursors +nmap <C-n> <Plug>NextWholeOccurrence +xmap <C-n> <Plug>NextWholeOccurrence +nmap g<C-n> <Plug>NextOccurrence +xmap g<C-n> <Plug>NextOccurrence +xmap <C-x> <Plug>SkipOccurrence +xmap <C-p> <Plug>RemoveOccurrence + +" Note that the default <A-n> and g<A-n> shortcuts don't work on Mac due to dead keys. +" <A-n> is used to enter accented text e.g. ñ +" Feel free to pick your own mappings that are not affected. I like to use <leader> +nmap <leader><C-n> <Plug>AllWholeOccurrences +xmap <leader><C-n> <Plug>AllWholeOccurrences +nmap <leader>g<C-n> <Plug>AllOccurrences +xmap <leader>g<C-n> <Plug>AllOccurrences +``` </details> diff --git a/scripts/build.gradle.kts b/scripts/build.gradle.kts index 5c877bbb3..9f5c77036 100644 --- a/scripts/build.gradle.kts +++ b/scripts/build.gradle.kts @@ -30,7 +30,7 @@ dependencies { implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r") // This is needed for jgit to connect to ssh - implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.9.0.202403050737-r") + implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.10.0.202406032230-r") implementation("com.vdurmont:semver4j:3.1.0") } diff --git a/src/main/java/com/maddyhome/idea/vim/group/FileGroup.java b/src/main/java/com/maddyhome/idea/vim/group/FileGroup.java index ed2adf223..ee6bf00b5 100644 --- a/src/main/java/com/maddyhome/idea/vim/group/FileGroup.java +++ b/src/main/java/com/maddyhome/idea/vim/group/FileGroup.java @@ -470,7 +470,7 @@ public class FileGroup extends VimFileBase { @NotNull @Override public String getProjectId(@NotNull Object project) { - if (!(project instanceof Project)) throw new IllegalArgumentException(); - return ((Project) project).getName(); + if (!(project instanceof Project ijProject)) throw new IllegalArgumentException(); + return ijProject.getName() + "-" + ijProject.getLocationHash(); } } diff --git a/src/main/java/com/maddyhome/idea/vim/group/VimJumpServiceImpl.kt b/src/main/java/com/maddyhome/idea/vim/group/VimJumpServiceImpl.kt index d04e47407..b44da8c7e 100644 --- a/src/main/java/com/maddyhome/idea/vim/group/VimJumpServiceImpl.kt +++ b/src/main/java/com/maddyhome/idea/vim/group/VimJumpServiceImpl.kt @@ -94,7 +94,7 @@ internal class JumpsListener(val project: Project) : RecentPlacesListener { if (changePlace.timeStamp < jumpService.lastJumpTimeStamp) return // this listener is notified asynchronously, and // we do not want jumps that were processed before val jump = buildJump(changePlace) ?: return - jumpService.addJump(project.basePath ?: IjVimEditor.DEFAULT_PROJECT_ID, jump, true) + jumpService.addJump(injector.file.getProjectId(project), jump, true) } } @@ -106,7 +106,7 @@ internal class JumpsListener(val project: Project) : RecentPlacesListener { if (changePlace.timeStamp < jumpService.lastJumpTimeStamp) return // this listener is notified asynchronously, and // we do not want jumps that were processed before val jump = buildJump(changePlace) ?: return - jumpService.removeJump(project.basePath ?: IjVimEditor.DEFAULT_PROJECT_ID, jump) + jumpService.removeJump(injector.file.getProjectId(project), jump) } } diff --git a/src/main/java/com/maddyhome/idea/vim/listener/VimListenerManager.kt b/src/main/java/com/maddyhome/idea/vim/listener/VimListenerManager.kt index 625d5a301..fd8a8f385 100644 --- a/src/main/java/com/maddyhome/idea/vim/listener/VimListenerManager.kt +++ b/src/main/java/com/maddyhome/idea/vim/listener/VimListenerManager.kt @@ -91,6 +91,7 @@ import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset import com.maddyhome.idea.vim.helper.resetVimLastColumn import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes import com.maddyhome.idea.vim.helper.vimDisabled +import com.maddyhome.idea.vim.helper.vimInitialised import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.InsertTimeRecorder import com.maddyhome.idea.vim.newapi.ij @@ -276,6 +277,11 @@ internal object VimListenerManager { // editor's disposable individually. val disposable = editor.project?.vimDisposable ?: return + // Protect against double initialisation + if (editor.getUserData(editorListenersDisposableKey) != null) { + return + } + val listenersDisposable = Disposer.newDisposable(disposable) editor.putUserData(editorListenersDisposableKey, listenersDisposable) @@ -468,6 +474,9 @@ internal object VimListenerManager { (it.fileEditor as? TextEditor)?.editor?.let { editor -> if (vimDisabled(editor)) return@let + // Protect against double initialisation, in case the editor was already initialised in editorCreated + if (editor.vimInitialised) return@let + val openingEditor = editor.removeUserData(openingEditorKey) val owningEditorWindow = getOwningEditorWindow(editor) val isInSameSplit = owningEditorWindow == openingEditor?.owningEditorWindow diff --git a/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/commands/ClearJumpsCommandTest.kt b/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/commands/ClearJumpsCommandTest.kt new file mode 100644 index 000000000..06ad5ee9a --- /dev/null +++ b/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/commands/ClearJumpsCommandTest.kt @@ -0,0 +1,90 @@ +/* + * 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.ex.implementation.commands + +import org.jetbrains.plugins.ideavim.VimTestCase +import org.junit.jupiter.api.Test + +class ClearJumpsCommandTest : VimTestCase() { + @Test + fun `test clear jumps`() { + configureByText( + """I found ${c}it in a legendary land + |all rocks and lavender and tufted grass, + |where it was settled on some sodden sand + |hard by the torrent of a mountain pass. + | + |The features it combines mark it as new + |to science: shape and shade -- the special tinge, + |akin to moonlight, tempering its blue, + |the dingy underside, the checquered fringe. + """.trimMargin(), + ) + + enterSearch("sodden") + enterSearch("shape") + enterSearch("rocks", false) + enterSearch("underside") + + enterCommand("jumps") + assertExOutput( + """ jump line col file/text + | 4 1 8 I found it in a legendary land + | 3 3 29 where it was settled on some sodden sand + | 2 7 12 to science: shape and shade -- the special tinge, + | 1 2 4 all rocks and lavender and tufted grass, + |> + | + """.trimMargin(), + ) + + enterCommand("clearjumps") + + enterCommand("jumps") + assertExOutput(" jump line col file/text\n>\n") + } + + @Test + fun `test clear jumps abbreviate`() { + configureByText( + """I found ${c}it in a legendary land + |all rocks and lavender and tufted grass, + |where it was settled on some sodden sand + |hard by the torrent of a mountain pass. + | + |The features it combines mark it as new + |to science: shape and shade -- the special tinge, + |akin to moonlight, tempering its blue, + |the dingy underside, the checquered fringe. + """.trimMargin(), + ) + + enterSearch("sodden") + enterSearch("shape") + enterSearch("rocks", false) + enterSearch("underside") + + enterCommand("jumps") + assertExOutput( + """ jump line col file/text + | 4 1 8 I found it in a legendary land + | 3 3 29 where it was settled on some sodden sand + | 2 7 12 to science: shape and shade -- the special tinge, + | 1 2 4 all rocks and lavender and tufted grass, + |> + | + """.trimMargin(), + ) + + enterCommand("clearju") + + enterCommand("jumps") + assertExOutput(" jump line col file/text\n>\n") + } +} diff --git a/vim-engine/src/main/antlr/Vimscript.g4 b/vim-engine/src/main/antlr/Vimscript.g4 index 5fed13cc9..0b1fa8469 100644 --- a/vim-engine/src/main/antlr/Vimscript.g4 +++ b/vim-engine/src/main/antlr/Vimscript.g4 @@ -119,6 +119,7 @@ command: | PROMPT_REPLACE | P_LOWERCASE | P_UPPERCASE | PRINT | PREVIOUS_TAB | N_UPPERCASE | PREVIOUS_FILE | PLUG | ONLY | NO_HL_SEARCH | NEXT_TAB | N_LOWERCASE | NEXT_FILE | M_LOWERCASE | MOVE_TEXT | MARKS | K_LOWERCASE | MARK_COMMAND | JUMPS | J_LOWERCASE | JOIN_LINES | HISTORY | GO_TO_CHAR | SYMBOL | FIND | CLASS | F_LOWERCASE + | CLEARJUMPS | FILE | EXIT | E_LOWERCASE | EDIT_FILE | DUMP_LINE | DIGRAPH | DEL_MARKS | D_LOWERCASE | DEL_LINES | DELCMD | T_LOWERCASE | COPY | CMD_CLEAR | BUFFER_LIST | BUFFER_CLOSE | B_LOWERCASE | BUFFER | ASCII | ACTIONLIST | ACTION | LOCKVAR | UNLOCKVAR | PACKADD | TABMOVE @@ -618,6 +619,7 @@ BUFFER_CLOSE: 'bd' | 'bde' | 'bdel' | 'bdele' | 'bdelet' | 'bdelete'; BUFFER_LIST: 'buffers' | 'ls' | 'files'; CALL: 'cal' | 'call'; CLASS: 'cla' | 'clas' | 'class'; +CLEARJUMPS: 'cle' | 'clea' | 'clear' | 'clearj' | 'clearju' | 'clearjum' | 'clearjump' | 'clearjumps'; CMD: 'com' | 'comm' | 'comma' | 'comman' | 'command'; CMD_CLEAR: 'comc' | 'comcl' | 'comcle' | 'comclea' | 'comclear'; COPY: 'co' | 'cop' | 'copy'; diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimJumpService.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimJumpService.kt index 43ba550aa..09d8e5877 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimJumpService.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimJumpService.kt @@ -34,6 +34,7 @@ public interface VimJumpService { public fun removeJump(projectId: String, jump: Jump) public fun dropLastJump(projectId: String) + public fun clearJumps(projectId: String) public fun updateJumpsFromInsert(projectId: String, startOffset: Int, length: Int) public fun updateJumpsFromDelete(projectId: String, startOffset: Int, length: Int) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimJumpServiceBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimJumpServiceBase.kt index 9dc6c3575..fb8bbd365 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimJumpServiceBase.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimJumpServiceBase.kt @@ -58,6 +58,11 @@ public abstract class VimJumpServiceBase : VimJumpService { projectToJumps[projectId]?.removeLastOrNull() } + override fun clearJumps(projectId: String) { + projectToJumps.remove(projectId) + projectToJumpSpot.remove(projectId) + } + override fun updateJumpsFromInsert(projectId: String, startOffset: Int, length: Int) { TODO("Not yet implemented") } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/ClearJumpsCommand.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/ClearJumpsCommand.kt new file mode 100644 index 000000000..ca60fc854 --- /dev/null +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/ClearJumpsCommand.kt @@ -0,0 +1,35 @@ +/* + * 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 com.maddyhome.idea.vim.vimscript.model.commands + +import com.intellij.vim.annotations.ExCommand +import com.maddyhome.idea.vim.api.ExecutionContext +import com.maddyhome.idea.vim.api.VimEditor +import com.maddyhome.idea.vim.api.injector +import com.maddyhome.idea.vim.command.OperatorArguments +import com.maddyhome.idea.vim.ex.ranges.Range +import com.maddyhome.idea.vim.vimscript.model.ExecutionResult + +@ExCommand(command = "cle[arjumps]") +public data class ClearJumpsCommand(val range: Range, val argument: String) : Command.SingleExecution(range, argument) { + override val argFlags: CommandHandlerFlags = flags( + RangeFlag.RANGE_FORBIDDEN, + ArgumentFlag.ARGUMENT_FORBIDDEN, + Access.READ_ONLY + ) + + override fun processCommand( + editor: VimEditor, + context: ExecutionContext, + operatorArguments: OperatorArguments, + ): ExecutionResult { + injector.jumpService.clearJumps(editor.projectId) + return ExecutionResult.Success + } +}