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
+  }
+}