1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2024-11-25 16:42:55 +01:00

Compare commits

..

15 Commits

Author SHA1 Message Date
976791ac95
Set plugin version to chylex-21 2023-11-17 12:56:02 +01:00
9b30831b2f
Work around Enter action running N^2 times for number of carets 2023-11-17 12:55:25 +01:00
0c3544c1fe
Set plugin version to chylex-20 2023-11-17 07:56:06 +01:00
29813f12fb
Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2023-11-17 07:56:06 +01:00
0bb5739adc
Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2023-11-17 07:56:06 +01:00
4e5e94cd98
Fix(VIM-3166): Workaround to fix broken filtering of visual lines 2023-11-17 07:56:06 +01:00
8b6925e5e4
Add support for count for visual and line motion surround 2023-11-17 07:56:05 +01:00
e451ebf361
Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2023-11-17 07:56:05 +01:00
056d704297
[VIM-696] Restore visual mode after undo/redo, and disable incompatible actions 2023-11-17 07:56:05 +01:00
dbb0f79113
Respect count with <Action> mappings 2023-11-17 07:56:05 +01:00
1bc6dfac1c
Prevent IdeaVIM from stealing key binding that confirms in-place refactoring 2023-11-17 07:17:31 +01:00
de449adcb9
Change matchit plugin to use HTML patterns in unrecognized files 2023-11-17 07:17:31 +01:00
2ef9742b71
Reset insert mode when switching active editor 2023-11-17 07:17:30 +01:00
a2833aa088
Remove update checker 2023-11-17 07:17:30 +01:00
13ebac83c6
Set custom plugin version 2023-11-17 06:50:20 +01:00
69 changed files with 523 additions and 1605 deletions

View File

@ -5,14 +5,13 @@ object Constants {
const val EAP_CHANNEL = "eap"
const val DEV_CHANNEL = "Dev"
// TODO it should be 2023.3 as soon as it releases
const val GITHUB_TESTS = "LATEST-EAP-SNAPSHOT"
const val NVIM_TESTS = "LATEST-EAP-SNAPSHOT"
const val PROPERTY_TESTS = "LATEST-EAP-SNAPSHOT"
const val LONG_RUNNING_TESTS = "LATEST-EAP-SNAPSHOT"
const val QODANA_TESTS = "LATEST-EAP-SNAPSHOT"
const val RELEASE = "LATEST-EAP-SNAPSHOT"
const val GITHUB_TESTS = "2023.1.2"
const val NVIM_TESTS = "2023.1.2"
const val PROPERTY_TESTS = "2023.1.2"
const val LONG_RUNNING_TESTS = "2023.1.2"
const val QODANA_TESTS = "2023.1.2"
const val RELEASE = "2023.1.2"
const val RELEASE_DEV = "LATEST-EAP-SNAPSHOT"
const val RELEASE_EAP = "LATEST-EAP-SNAPSHOT"
const val RELEASE_DEV = "2023.1.2"
const val RELEASE_EAP = "2023.1.2"
}

View File

@ -23,6 +23,8 @@ object Project : Project({
vcsRoot(GitHubPullRequest)
// Active tests
buildType(TestingBuildType("2023.2", "<default>", version = "2023.2.3"))
buildType(TestingBuildType("2023.1", "<default>", version = "2023.1.5"))
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))

View File

@ -20,6 +20,4 @@ object OldTests : Project({
buildType(TestingBuildType("IC-2021.2.2", "203-212", javaVersion = "1.8", javaPlugin = false))
buildType(TestingBuildType("IC-2021.3.2", "213-221", javaVersion = "1.8", javaPlugin = false))
buildType(TestingBuildType("IC-2022.2.3", branch = "222", javaPlugin = false))
buildType(TestingBuildType("IC-2023.1", "231-232", javaPlugin = false))
buildType(TestingBuildType("IC-2023.2", "231-232", javaPlugin = false))
})

View File

@ -31,21 +31,6 @@ usual beta standards.
* [VIM-3165](https://youtrack.jetbrains.com/issue/VIM-3165) Do not process enter key as IdeaVim shortcut if it's not an actual keypress
* [VIM-3159](https://youtrack.jetbrains.com/issue/VIM-3159) Shift-enter now works in normal mode again
* [VIM-3157](https://youtrack.jetbrains.com/issue/VIM-3157) Do not invoke enter in invokeLater for python console
* [VIM-3195](https://youtrack.jetbrains.com/issue/VIM-3195) Fix escape in injected editor
* [VIM-3190](https://youtrack.jetbrains.com/issue/VIM-3190) Do not use octopus handler if the enter key is used with modifiers like shift or control
* [VIM-3203](https://youtrack.jetbrains.com/issue/VIM-3203) Split action not works in normal mode
* [VIM-3184](https://youtrack.jetbrains.com/issue/VIM-3184) Revert "VIM-3184: Temporally disable new handlers for the thin client"
* [VIM-3186](https://youtrack.jetbrains.com/issue/VIM-3186) Do not multiply the enter action by the amount of carets
* [VIM-3177](https://youtrack.jetbrains.com/issue/VIM-3177) Formatting of commit message works again
* [VIM-1611](https://youtrack.jetbrains.com/issue/VIM-1611) actions related to resolving conflicts doesn't seem to work
* [VIM-3204](https://youtrack.jetbrains.com/issue/VIM-3204) Add checker that verifies the configuratin of the keymap
* [VIM-3084](https://youtrack.jetbrains.com/issue/VIM-3084) Double update for the status bar icon
* [VIM-3176](https://youtrack.jetbrains.com/issue/VIM-3176) Reselecting visual selection after pasting above it select wrong lines
* [VIM-3206](https://youtrack.jetbrains.com/issue/VIM-3206) Disable both copilot suggestion and insert mode on a single escape
* [VIM-3090](https://youtrack.jetbrains.com/issue/VIM-3090) Cmd line mode saves the visual mode
### Merged PRs:
* [763](https://github.com/JetBrains/ideavim/pull/763) by [Sam Ng](https://github.com/samabcde): Fix(VIM-3176) add test for restore selection after pasting in/below s…
## 2.7.0, 2023-11-07

View File

@ -324,7 +324,7 @@ IdeaVim tips and tricks
- Use the power of IJ and Vim:
- `set ideajoin` to enable join via the IDE. See the [examples](https://jb.gg/f9zji9).
- Make sure `ideaput` is enabled for `clipboard` to enable native IJ insertion in Vim.
- Sync IJ bookmarks and IdeaVim global marks: `set ideamarks` (works for marks with capital letters only)
- Sync IJ bookmarks and Vim marks: `set ideamarks`
- Check out more [ex commands](https://github.com/JetBrains/ideavim/wiki/%22set%22-commands).
- Use your vim settings with IdeaVim. Put `source ~/.vimrc` in `~/.ideavimrc`.

View File

@ -11,8 +11,6 @@ plugins {
kotlin("plugin.serialization") version "1.8.21"
}
val kotlinxSerializationVersion: String by project
group = "com.intellij"
version = "SNAPSHOT"
@ -21,10 +19,6 @@ repositories {
}
dependencies {
compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.21-1.0.15")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion") {
// kotlin stdlib is provided by IJ, so there is no need to include it into the distribution
exclude("org.jetbrains.kotlin", "kotlin-stdlib")
exclude("org.jetbrains.kotlin", "kotlin-stdlib-common")
}
compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.20-1.0.14")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.6.0")
}

View File

@ -44,7 +44,7 @@ enum class Mode(val abbrev: Char) {
OP_PENDING('O'),
/**
* Indicates this key mapping applies to Insert or Replace modes
* Indicates this key mapping applies to Insert mode
*/
INSERT('I'),

View File

@ -53,7 +53,7 @@ buildscript {
classpath("org.kohsuke:github-api:1.305")
classpath("io.ktor:ktor-client-core:2.3.6")
classpath("io.ktor:ktor-client-cio:2.3.6")
classpath("io.ktor:ktor-client-cio:2.3.5")
classpath("io.ktor:ktor-client-auth:2.3.6")
classpath("io.ktor:ktor-client-content-negotiation:2.3.6")
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
@ -116,7 +116,7 @@ repositories {
dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
compileOnly("org.jetbrains:annotations:24.1.0")
compileOnly("org.jetbrains:annotations:24.0.1")
// https://mvnrepository.com/artifact/com.ensarsarajcic.neovim.java/neovim-api
testImplementation("com.ensarsarajcic.neovim.java:neovim-api:0.2.3")
@ -141,7 +141,7 @@ dependencies {
testApi("com.squareup.okhttp3:okhttp:4.12.0")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.1")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.1")
}
@ -344,6 +344,8 @@ tasks {
val pluginVersion = version
// Don't forget to update plugin.xml
patchPluginXml {
sinceBuild.set("231.7515.13")
// Get the latest available change notes from the changelog file
changeNotes.set(
provider {
@ -522,12 +524,10 @@ tasks.register("releaseActions") {
if (tickets.isNotEmpty()) {
println("Updating statuses for tickets: $tickets")
setYoutrackStatus(tickets, "Fixed")
println("Checking if version $version exists...")
val versionId = getVersionIdByName(version.toString())
if (versionId == null) {
if (getVersionIdByName(version.toString()) != null) {
addReleaseToYoutrack(version.toString())
} else {
println("Version $version already exists in YouTrack. Version id: $versionId")
println("Version $version is already exists in YouTrack")
}
setYoutrackFixVersion(tickets, version.toString())
} else {

View File

@ -3,11 +3,6 @@ Put `set ideajoin` to your `~/.ideavimrc` to enable this functionality.
Now, you can press `J` (`shift+j`) on a line or a selected block of text to join the lines together.
:warning: This feature is language-specific. This means that the IDE should implement this feature for a particular
language in order for the IDE to work as described below. If any of the examples provided below don't match your case,
please file an issue in the project related to your IDE: https://youtrack.jetbrains.com/.
Here is a list of known requests: https://youtrack.jetbrains.com/issues?q=links:VIM-3214.
* Automatic join concatenated lines:
```

View File

@ -11,7 +11,7 @@
ideaVersion=2023.2
downloadIdeaSources=true
instrumentPluginCode=true
version=chylex-22
version=chylex-21
javaVersion=17
remoteRobotVersion=0.11.17
antlrVersion=4.10.1
@ -19,15 +19,10 @@ antlrVersion=4.10.1
kotlin.incremental.useClasspathSnapshot=false
# Please don't forget to update kotlin version in buildscript section
# Also update kotlinxSerializationVersion version
kotlinVersion=1.8.21
publishToken=token
publishChannels=eap
# Kotlinx serialization also uses some version of kotlin stdlib under the hood. However,
# we exclude this version from the dependency and use our own version of kotlin that is specified above
kotlinxSerializationVersion=1.5.1
slackUrl=
youtrackToken=

View File

@ -20,10 +20,10 @@ repositories {
}
dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.21")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.20")
implementation("io.ktor:ktor-client-core:2.3.6")
implementation("io.ktor:ktor-client-cio:2.3.6")
implementation("io.ktor:ktor-client-cio:2.3.5")
implementation("io.ktor:ktor-client-content-negotiation:2.3.6")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
implementation("io.ktor:ktor-client-auth:2.3.6")

View File

@ -12,7 +12,7 @@ fun main(args: Array<String>) {
println("HI!")
val projectDir = args[0]
println("Working directory: $projectDir")
val (lastVersion, objectId) = getVersion(projectDir, ReleaseType.STABLE_NO_PATCH)
val (lastVersion, objectId) = getVersion(projectDir, onlyStable = true)
println("Last version: $lastVersion, hash: ${objectId.name}")
val branch = withRepo(projectDir) { it.branch }

View File

@ -12,7 +12,7 @@ fun main(args: Array<String>) {
println("HI!")
val projectDir = args[0]
println("Working directory: $projectDir")
val (lastVersion, _) = getVersion(projectDir, ReleaseType.ANY)
val (lastVersion, _) = getVersion(projectDir, onlyStable = false)
val nextVersion = if (lastVersion.suffixTokens.isEmpty()) {
lastVersion.nextMinor().withSuffix("eap.1").value

View File

@ -14,7 +14,7 @@ fun main(args: Array<String>) {
val releaseType = args[1]
println("Working directory: $projectDir")
println("Release type: $releaseType")
val (lastVersion, _) = getVersion(projectDir, ReleaseType.ONLY_STABLE)
val (lastVersion, _) = getVersion(projectDir, onlyStable = true)
val nextVersion = when (releaseType) {
"major" -> lastVersion.nextMajor()

View File

@ -58,13 +58,7 @@ internal fun checkBranch(rootDir: String, releaseType: String) {
}
}
enum class ReleaseType {
ANY,
ONLY_STABLE,
STABLE_NO_PATCH, // Version that ends on 0. Like 2.5.0
}
internal fun getVersion(projectDir: String, releaseType: ReleaseType): Pair<Semver, ObjectId> {
internal fun getVersion(projectDir: String, onlyStable: Boolean): Pair<Semver, ObjectId> {
val repository = RepositoryBuilder().setGitDir(File("$projectDir/.git")).build()
val git = Git(repository)
println(git.log().call().first())
@ -81,10 +75,10 @@ internal fun getVersion(projectDir: String, releaseType: ReleaseType): Pair<Semv
}
.sortedBy { it.first }
val version = when (releaseType) {
ReleaseType.ANY -> versions.last()
ReleaseType.ONLY_STABLE -> versions.last { it.first.isStable }
ReleaseType.STABLE_NO_PATCH -> versions.last { it.first.isStable && it.first.patch == 0 }
val version = if (onlyStable) {
versions.last { it.first.isStable }
} else {
versions.last()
}
return version

View File

@ -11,7 +11,7 @@ package com.maddyhome.idea.vim
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManagerListener
import com.intellij.openapi.startup.ProjectActivity
import com.intellij.openapi.startup.StartupActivity
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.localEditors
@ -20,11 +20,16 @@ import com.maddyhome.idea.vim.newapi.globalIjOptions
/**
* @author Alex Plate
*/
internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
// This service should be migrated to ProjectActivity. But we should cariful because simple replacement
// leads to deadlock in tests. I'm not sure about the exact reasons, but "invokeAndWait" inside "initialize" function
// causes this deadlock. Good new: it's easy reproducible in tests.
// Previous migration: fc7efd5484a13b40ba9bf86a1d5429e215d973f3
// Revert: 24dd84b31cffb99eb6114524859a46d02717d33f
internal class PluginStartup : StartupActivity.DumbAware/*, LightEditCompatible*/ {
private var firstInitializationOccurred = false
override suspend fun execute(project: Project) {
override fun runActivity(project: Project) {
if (firstInitializationOccurred) return
firstInitializationOccurred = true

View File

@ -232,7 +232,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
getInstance().turnOnPlugin();
}
StatusBarIconFactory.Util.INSTANCE.updateIcon();
StatusBarIconFactory.Companion.updateIcon();
}
public static String getMessage() {
@ -264,8 +264,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
if (enabled) {
Application application = ApplicationManager.getApplication();
if (application.isUnitTestMode()) {
turnOnPlugin();
//application.invokeAndWait(this::turnOnPlugin);
application.invokeAndWait(this::turnOnPlugin);
}
else {
application.invokeLater(this::turnOnPlugin);

View File

@ -0,0 +1,74 @@
/*
* 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
import com.intellij.codeInsight.hint.HintManagerImpl
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.PerformWithDocumentsCommitted
import com.intellij.openapi.actionSystem.PopupAction
import com.intellij.openapi.actionSystem.impl.ActionConfigurationCustomizer
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorMouseHoverPopupManager
import com.intellij.openapi.editor.event.EditorMouseEvent
import com.intellij.openapi.editor.event.EditorMouseEventArea
import com.intellij.openapi.project.DumbAware
import java.awt.event.MouseEvent
// [VERSION UPDATE] 233+ Remove class
// The ShowHoverInfo action is built into the platform (using a nicer EditorMouseHoverPopupManager API)
public class VimActionConfigurationCustomizer : ActionConfigurationCustomizer {
public override fun customize(actionManager: ActionManager) {
// If the ShowHoverInfo action doesn't exist in the platform, add our own implementation
if (actionManager.getAction("ShowHoverInfo") == null) {
actionManager.registerAction("ShowHoverInfo", VimShowHoverInfoAction())
}
}
private class VimShowHoverInfoAction : AnAction(), HintManagerImpl.ActionToIgnore, PopupAction, DumbAware,
PerformWithDocumentsCommitted {
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
override fun update(e: AnActionEvent) {
val dataContext = e.dataContext
val editor = CommonDataKeys.EDITOR.getData(dataContext)
if (editor == null) {
e.presentation.isEnabledAndVisible = false
}
}
override fun actionPerformed(e: AnActionEvent) {
val editor = CommonDataKeys.EDITOR.getData(e.dataContext) ?: return
val editorMouseEvent = createFakeEditorMouseEvent(editor)
EditorMouseHoverPopupManager.getInstance().showInfoTooltip(editorMouseEvent)
}
private fun createFakeEditorMouseEvent(editor: Editor): EditorMouseEvent {
val xy = editor.offsetToXY(editor.caretModel.offset)
val mouseEvent =
MouseEvent(editor.component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, xy.x, xy.y, 0, false)
val editorMouseEvent = EditorMouseEvent(
editor,
mouseEvent,
EditorMouseEventArea.EDITING_AREA,
editor.caretModel.offset,
editor.caretModel.logicalPosition,
editor.caretModel.visualPosition,
true,
null,
null,
null
)
return editorMouseEvent
}
}
}

View File

@ -60,12 +60,7 @@ import javax.swing.KeyStroke
* These keys are not passed to [com.maddyhome.idea.vim.VimTypedActionHandler] and should be handled by actions.
*/
internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
private val traceTime: Boolean
get() {
// Make sure the injector is initialized
VimPlugin.getInstance()
return injector.globalOptions().ideatracetime
}
private val traceTime = injector.globalOptions().ideatracetime
override fun actionPerformed(e: AnActionEvent) {
LOG.trace("Executing shortcut key action")

View File

@ -27,7 +27,7 @@ public class CommandState(private val machine: VimStateMachine) {
get() {
val myMode = machine.mode
return when (myMode) {
is com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
com.maddyhome.idea.vim.state.mode.Mode.INSERT -> CommandState.Mode.INSERT
is com.maddyhome.idea.vim.state.mode.Mode.NORMAL -> CommandState.Mode.COMMAND
is com.maddyhome.idea.vim.state.mode.Mode.OP_PENDING -> CommandState.Mode.OP_PENDING

View File

@ -275,8 +275,8 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
private void registerRequiredShortcut(@NotNull List<KeyStroke> keys, MappingOwner owner) {
for (KeyStroke key : keys) {
if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED &&
!(key.getKeyCode() == KeyEvent.VK_ESCAPE && key.getModifiers() == 0) &&
!(key.getKeyCode() == KeyEvent.VK_ENTER && key.getModifiers() == 0)) {
key.getKeyCode() != KeyEvent.VK_ESCAPE &&
key.getKeyCode() != KeyEvent.VK_ENTER) {
getRequiredShortcutKeys().add(new RequiredShortcut(key, owner));
}
}

View File

@ -1,68 +0,0 @@
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,8 +7,6 @@
*/
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
@ -21,7 +19,6 @@ 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
@ -74,18 +71,12 @@ internal class MacroGroup : VimMacroBase() {
} catch (e: ProcessCanceledException) {
return@runnable
}
ProgressManager.getInstance().executeNonCancelableSection {
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion)
getInstance().handleKey(editor, key, context)
}
ProgressManager.getInstance().executeNonCancelableSection { getInstance().handleKey(editor, key, context) }
if (injector.messages.isError()) return@runnable
}
keyStack.resetFirst()
}
keyStack.removeFirst()
if (!isInternalMacro) {
MacroAutoImport.run(editor.ij, context.ij)
}
}
if (isInternalMacro) {

View File

@ -21,11 +21,8 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.KeyboardShortcut
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.ide.CopyPasteManager
import com.intellij.openapi.keymap.KeymapUtil
import com.intellij.openapi.keymap.ex.KeymapManagerEx
import com.intellij.openapi.keymap.impl.ui.KeymapPanel
import com.intellij.openapi.options.ShowSettingsUtil
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.project.Project
@ -35,7 +32,6 @@ import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.handler.KeyMapIssue
import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.key.ShortcutOwner
import com.maddyhome.idea.vim.key.ShortcutOwnerInfo
@ -184,77 +180,6 @@ internal class NotificationService(private val project: Project?) {
ActionIdNotifier.notifyActionId(id, project)
}
fun notifyKeymapIssues(issues: ArrayList<KeyMapIssue>) {
val keymapManager = KeymapManagerEx.getInstanceEx()
val keymap = keymapManager.activeKeymap
val message = buildString {
appendLine("Current IDE keymap (${keymap.name}) has issues:<br/>")
issues.forEach {
when (it) {
is KeyMapIssue.AddShortcut -> {
appendLine("- ${it.key} key is not assigned to the ${it.action} action.<br/>")
}
is KeyMapIssue.RemoveShortcut -> {
appendLine("- ${it.shortcut} key is incorrectly assigned to the ${it.action} action.<br/>")
}
}
}
}
val notification = IDEAVIM_STICKY_GROUP.createNotification(
IDEAVIM_NOTIFICATION_TITLE,
message,
NotificationType.ERROR,
)
notification.subtitle = "IDE keymap misconfigured"
notification.addAction(object : DumbAwareAction("Fix Keymap") {
override fun actionPerformed(e: AnActionEvent) {
issues.forEach {
when (it) {
is KeyMapIssue.AddShortcut -> {
keymap.addShortcut(it.actionId, KeyboardShortcut(it.keyStroke, null))
}
is KeyMapIssue.RemoveShortcut -> {
keymap.removeShortcut(it.actionId, it.shortcut)
}
}
}
LOG.info("Shortcuts updated $issues")
notification.expire()
requiredShortcutsAssigned()
}
})
notification.addAction(object : DumbAwareAction("Open Keymap Settings") {
override fun actionPerformed(e: AnActionEvent) {
ShowSettingsUtil.getInstance().showSettingsDialog(e.project, KeymapPanel::class.java)
notification.hideBalloon()
}
})
notification.addAction(object : DumbAwareAction("Ignore") {
override fun actionPerformed(e: AnActionEvent) {
LOG.info("Ignored to update shortcuts $issues")
notification.hideBalloon()
}
})
notification.notify(project)
}
private fun requiredShortcutsAssigned() {
val notification = Notification(
IDEAVIM_NOTIFICATION_ID,
IDEAVIM_NOTIFICATION_TITLE,
"Keymap fixed",
NotificationType.INFORMATION,
)
notification.addAction(object : DumbAwareAction("Open Keymap Settings") {
override fun actionPerformed(e: AnActionEvent) {
ShowSettingsUtil.getInstance().showSettingsDialog(e.project, KeymapPanel::class.java)
notification.hideBalloon()
}
})
notification.notify(project)
}
object ActionIdNotifier {
private var notification: Notification? = null
private const val NO_ID = "<i>Cannot detect action id</i>"
@ -389,8 +314,6 @@ internal class NotificationService(private val project: Project?) {
const val IDEAVIM_NOTIFICATION_TITLE = "IdeaVim"
const val ideajoinExamplesUrl = "https://jb.gg/f9zji9"
private val LOG = logger<NotificationService>()
private fun createIdeaVimRcManually(message: String, project: Project?) {
val notification =
Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, message, NotificationType.WARNING)

View File

@ -0,0 +1,285 @@
/*
* 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.group;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.CapturingProcessHandler;
import com.intellij.execution.process.ProcessAdapter;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.execution.process.ProcessOutput;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.util.execution.ParametersListUtil;
import com.intellij.util.text.CharSequenceReader;
import com.maddyhome.idea.vim.KeyHandler;
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.VimInjectorKt;
import com.maddyhome.idea.vim.api.VimProcessGroupBase;
import com.maddyhome.idea.vim.command.Command;
import com.maddyhome.idea.vim.state.mode.Mode;
import com.maddyhome.idea.vim.state.VimStateMachine;
import com.maddyhome.idea.vim.ex.ExException;
import com.maddyhome.idea.vim.ex.InvalidCommandException;
import com.maddyhome.idea.vim.helper.UiHelper;
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext;
import com.maddyhome.idea.vim.newapi.IjVimEditor;
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.io.*;
import java.util.ArrayList;
import static com.maddyhome.idea.vim.api.VimInjectorKt.globalOptions;
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
public class ProcessGroup extends VimProcessGroupBase {
public String getLastCommand() {
return lastCommand;
}
@Override
public void startSearchCommand(@NotNull VimEditor editor, ExecutionContext context, int count, char leader) {
if (((IjVimEditor)editor).getEditor().isOneLineMode()) // Don't allow searching in one line editors
{
return;
}
String initText = "";
String label = String.valueOf(leader);
ExEntryPanel panel = ExEntryPanel.getInstance();
panel.activate(((IjVimEditor)editor).getEditor(), ((DataContext)context.getContext()), label, initText, count);
}
@Override
public @NotNull String endSearchCommand() {
ExEntryPanel panel = ExEntryPanel.getInstance();
panel.deactivate(true);
return panel.getText();
}
public void startExCommand(@NotNull VimEditor editor, ExecutionContext context, @NotNull Command cmd) {
// Don't allow ex commands in one line editors
if (editor.isOneLineMode()) return;
String initText = getRange(((IjVimEditor) editor).getEditor(), cmd);
injector.getMarkService().setVisualSelectionMarks(editor);
VimStateMachine.Companion.getInstance(editor).setMode(Mode.CMD_LINE.INSTANCE);
ExEntryPanel panel = ExEntryPanel.getInstance();
panel.activate(((IjVimEditor) editor).getEditor(), ((IjEditorExecutionContext) context).getContext(), ":", initText, 1);
}
@Override
public boolean processExKey(@NotNull VimEditor editor, @NotNull KeyStroke stroke) {
// This will only get called if somehow the key focus ended up in the editor while the ex entry window
// is open. So I'll put focus back in the editor and process the key.
ExEntryPanel panel = ExEntryPanel.getInstance();
if (panel.isActive()) {
UiHelper.requestFocus(panel.getEntry());
panel.handleKey(stroke);
return true;
}
else {
VimStateMachine.Companion.getInstance(editor).setMode(new Mode.NORMAL());
KeyHandler.getInstance().reset(editor);
return false;
}
}
public boolean processExEntry(final @NotNull VimEditor editor, final @NotNull ExecutionContext context) {
ExEntryPanel panel = ExEntryPanel.getInstance();
panel.deactivate(true);
boolean res = true;
try {
VimStateMachine.Companion.getInstance(editor).setMode(new Mode.NORMAL());
logger.debug("processing command");
final String text = panel.getText();
if (!panel.getLabel().equals(":")) {
// Search is handled via Argument.Type.EX_STRING. Although ProcessExEntryAction is registered as the handler for
// <CR> in both command and search modes, it's only invoked for command mode (see KeyHandler.handleCommandNode).
// We should never be invoked for anything other than an actual ex command.
throw new InvalidCommandException("Expected ':' command. Got '" + panel.getLabel() + "'", text);
}
if (logger.isDebugEnabled()) logger.debug("swing=" + SwingUtilities.isEventDispatchThread());
VimInjectorKt.getInjector().getVimscriptExecutor().execute(text, editor, context, skipHistory(editor), true, CommandLineVimLContext.INSTANCE);
}
catch (ExException e) {
VimPlugin.showMessage(e.getMessage());
VimPlugin.indicateError();
res = false;
}
catch (Exception bad) {
ProcessGroup.logger.error(bad);
VimPlugin.indicateError();
res = false;
}
return res;
}
// commands executed from map command / macro should not be added to history
private boolean skipHistory(VimEditor editor) {
return VimStateMachine.Companion.getInstance(editor).getMappingState().isExecutingMap() || injector.getMacro().isExecutingMacro();
}
public void cancelExEntry(final @NotNull VimEditor editor, boolean resetCaret) {
VimStateMachine.Companion.getInstance(editor).setMode(new Mode.NORMAL());
KeyHandler.getInstance().reset(editor);
ExEntryPanel panel = ExEntryPanel.getInstance();
panel.deactivate(true, resetCaret);
}
@Override
public void startFilterCommand(@NotNull VimEditor editor, ExecutionContext context, @NotNull Command cmd) {
String initText = getRange(((IjVimEditor) editor).getEditor(), cmd) + "!";
VimStateMachine.Companion.getInstance(editor).setMode(Mode.CMD_LINE.INSTANCE);
ExEntryPanel panel = ExEntryPanel.getInstance();
panel.activate(((IjVimEditor) editor).getEditor(), ((IjEditorExecutionContext) context).getContext(), ":", initText, 1);
}
private @NotNull String getRange(Editor editor, @NotNull Command cmd) {
String initText = "";
if (VimStateMachine.Companion.getInstance(new IjVimEditor(editor)).getMode() instanceof Mode.VISUAL) {
initText = "'<,'>";
}
else if (cmd.getRawCount() > 0) {
if (cmd.getCount() == 1) {
initText = ".";
}
else {
initText = ".,.+" + (cmd.getCount() - 1);
}
}
return initText;
}
public @Nullable String executeCommand(@NotNull VimEditor editor, @NotNull String command, @Nullable CharSequence input, @Nullable String currentDirectoryPath)
throws ExecutionException, ProcessCanceledException {
// This is a much simplified version of how Vim does this. We're using stdin/stdout directly, while Vim will
// redirect to temp files ('shellredir' and 'shelltemp') or use pipes. We don't support 'shellquote', because we're
// not handling redirection, but we do use 'shellxquote' and 'shellxescape', because these have defaults that work
// better with Windows. We also don't bother using ShellExecute for Windows commands beginning with `start`.
// Finally, we're also not bothering with the crazy space and backslash handling of the 'shell' options content.
return ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
final String shell = globalOptions(injector).getShell();
final String shellcmdflag = globalOptions(injector).getShellcmdflag();
final String shellxescape = globalOptions(injector).getShellxescape();
final String shellxquote = globalOptions(injector).getShellxquote();
// For Win32. See :help 'shellxescape'
final String escapedCommand = shellxquote.equals("(")
? doEscape(command, shellxescape, "^")
: command;
// Required for Win32+cmd.exe, defaults to "(". See :help 'shellxquote'
final String quotedCommand = shellxquote.equals("(")
? "(" + escapedCommand + ")"
: (shellxquote.equals("\"(")
? "\"(" + escapedCommand + ")\""
: shellxquote + escapedCommand + shellxquote);
final ArrayList<String> commands = new ArrayList<>();
commands.add(shell);
if (!shellcmdflag.isEmpty()) {
// Note that Vim also does a simple whitespace split for multiple parameters
commands.addAll(ParametersListUtil.parse(shellcmdflag));
}
commands.add(quotedCommand);
if (logger.isDebugEnabled()) {
logger.debug(String.format("shell=%s shellcmdflag=%s command=%s", shell, shellcmdflag, quotedCommand));
}
final GeneralCommandLine commandLine = new GeneralCommandLine(commands);
if (currentDirectoryPath != null) {
commandLine.setWorkDirectory(currentDirectoryPath);
}
final CapturingProcessHandler handler = new CapturingProcessHandler(commandLine);
if (input != null) {
handler.addProcessListener(new ProcessAdapter() {
@Override
public void startNotified(@NotNull ProcessEvent event) {
try {
final CharSequenceReader charSequenceReader = new CharSequenceReader(input);
final BufferedWriter outputStreamWriter = new BufferedWriter(new OutputStreamWriter(handler.getProcessInput()));
copy(charSequenceReader, outputStreamWriter);
outputStreamWriter.close();
}
catch (IOException e) {
logger.error(e);
}
}
});
}
final ProgressIndicator progressIndicator = ProgressIndicatorProvider.getInstance().getProgressIndicator();
final ProcessOutput output = handler.runProcessWithProgressIndicator(progressIndicator);
lastCommand = command;
if (output.isCancelled()) {
// TODO: Vim will use whatever text has already been written to stdout
// For whatever reason, we're not getting any here, so just throw an exception
throw new ProcessCanceledException();
}
final Integer exitCode = handler.getExitCode();
if (exitCode != null && exitCode != 0) {
VimPlugin.showMessage("shell returned " + exitCode);
VimPlugin.indicateError();
}
// Get stderr; stdout and strip colors, which are not handles properly.
return (output.getStderr() + output.getStdout()).replaceAll("\u001B\\[[;\\d]*m", "");
}, "IdeaVim - !" + command, true, ((IjVimEditor) editor).getEditor().getProject());
}
private String doEscape(String original, String charsToEscape, String escapeChar) {
String result = original;
for (char c : charsToEscape.toCharArray()) {
result = result.replace("" + c, escapeChar + c);
}
return result;
}
// TODO: Java 10 has a transferTo method we could use instead
private void copy(@NotNull Reader from, @NotNull Writer to) throws IOException {
char[] buf = new char[2048];
int cnt;
while ((cnt = from.read(buf)) != -1) {
to.write(buf, 0, cnt);
}
}
private String lastCommand;
private static final Logger logger = Logger.getInstance(ProcessGroup.class.getName());
}

View File

@ -1,281 +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.group
import com.intellij.execution.ExecutionException
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.process.CapturingProcessHandler
import com.intellij.execution.process.ProcessAdapter
import com.intellij.execution.process.ProcessEvent
import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.progress.ProgressIndicatorProvider
import com.intellij.openapi.progress.ProgressManager
import com.intellij.util.execution.ParametersListUtil
import com.intellij.util.text.CharSequenceReader
import com.maddyhome.idea.vim.KeyHandler.Companion.getInstance
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.VimProcessGroupBase
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.ex.InvalidCommandException
import com.maddyhome.idea.vim.helper.requestFocus
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.Mode.NORMAL
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
import java.io.BufferedWriter
import java.io.IOException
import java.io.OutputStreamWriter
import java.io.Reader
import java.io.Writer
import javax.swing.KeyStroke
import javax.swing.SwingUtilities
public class ProcessGroup : VimProcessGroupBase() {
override var lastCommand: String? = null
private set
public override fun startSearchCommand(editor: VimEditor, context: ExecutionContext, count: Int, leader: Char) {
// Don't allow searching in one line editors
if (editor.isOneLineMode()) return
val initText = ""
val label = leader.toString()
val panel = ExEntryPanel.getInstance()
panel.activate(editor.ij, context.ij, label, initText, count)
}
public override fun endSearchCommand(): String {
val panel = ExEntryPanel.getInstance()
panel.deactivate(true)
return panel.text
}
public override fun startExCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
// Don't allow ex commands in one line editors
if (editor.isOneLineMode()) return
val currentMode = editor.vimStateMachine.mode
check(currentMode is ReturnableFromCmd) {
"Cannot enable cmd mode from current mode $currentMode"
}
val initText = getRange(editor, cmd)
injector.markService.setVisualSelectionMarks(editor)
editor.vimStateMachine.mode = Mode.CMD_LINE(currentMode)
val panel = ExEntryPanel.getInstance()
panel.activate(editor.ij, context.ij, ":", initText, 1)
}
public override fun processExKey(editor: VimEditor, stroke: KeyStroke): Boolean {
// This will only get called if somehow the key focus ended up in the editor while the ex entry window
// is open. So I'll put focus back in the editor and process the key.
val panel = ExEntryPanel.getInstance()
if (panel.isActive) {
requestFocus(panel.entry)
panel.handleKey(stroke)
return true
} else {
getInstance(editor).mode = NORMAL()
getInstance().reset(editor)
return false
}
}
public override fun processExEntry(editor: VimEditor, context: ExecutionContext): Boolean {
val panel = ExEntryPanel.getInstance()
panel.deactivate(true)
var res = true
try {
getInstance(editor).mode = NORMAL()
logger.debug("processing command")
val text = panel.text
if (panel.label != ":") {
// Search is handled via Argument.Type.EX_STRING. Although ProcessExEntryAction is registered as the handler for
// <CR> in both command and search modes, it's only invoked for command mode (see KeyHandler.handleCommandNode).
// We should never be invoked for anything other than an actual ex command.
throw InvalidCommandException("Expected ':' command. Got '" + panel.label + "'", text)
}
logger.debug {
"swing=" + SwingUtilities.isEventDispatchThread()
}
injector.vimscriptExecutor.execute(text, editor, context, skipHistory(editor), true, CommandLineVimLContext)
} catch (e: ExException) {
VimPlugin.showMessage(e.message)
VimPlugin.indicateError()
res = false
} catch (bad: Exception) {
logger.error(bad)
VimPlugin.indicateError()
res = false
}
return res
}
// commands executed from map command / macro should not be added to history
private fun skipHistory(editor: VimEditor): Boolean {
return getInstance(editor).mappingState.isExecutingMap() || injector.macro.isExecutingMacro
}
public override fun cancelExEntry(editor: VimEditor, resetCaret: Boolean) {
editor.vimStateMachine.mode = NORMAL()
getInstance().reset(editor)
val panel = ExEntryPanel.getInstance()
panel.deactivate(true, resetCaret)
}
public override fun startFilterCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
val initText = getRange(editor, cmd) + "!"
val currentMode = editor.mode
check(currentMode is ReturnableFromCmd) { "Cannot enable cmd mode from $currentMode" }
editor.vimStateMachine.mode = Mode.CMD_LINE(currentMode)
val panel = ExEntryPanel.getInstance()
panel.activate(editor.ij, context.ij, ":", initText, 1)
}
private fun getRange(editor: VimEditor, cmd: Command): String {
var initText = ""
if (editor.vimStateMachine.mode is VISUAL) {
initText = "'<,'>"
} else if (cmd.rawCount > 0) {
initText = if (cmd.count == 1) {
"."
} else {
".,.+" + (cmd.count - 1)
}
}
return initText
}
@Throws(ExecutionException::class, ProcessCanceledException::class)
public override fun executeCommand(
editor: VimEditor,
command: String,
input: CharSequence?,
currentDirectoryPath: String?
): String? {
// This is a much simplified version of how Vim does this. We're using stdin/stdout directly, while Vim will
// redirect to temp files ('shellredir' and 'shelltemp') or use pipes. We don't support 'shellquote', because we're
// not handling redirection, but we do use 'shellxquote' and 'shellxescape', because these have defaults that work
// better with Windows. We also don't bother using ShellExecute for Windows commands beginning with `start`.
// Finally, we're also not bothering with the crazy space and backslash handling of the 'shell' options content.
return ProgressManager.getInstance().runProcessWithProgressSynchronously<String, ExecutionException>(
{
val shell = injector.globalOptions().shell
val shellcmdflag = injector.globalOptions().shellcmdflag
val shellxescape = injector.globalOptions().shellxescape
val shellxquote = injector.globalOptions().shellxquote
// For Win32. See :help 'shellxescape'
val escapedCommand = if (shellxquote == "(") doEscape(command, shellxescape, "^")
else command
// Required for Win32+cmd.exe, defaults to "(". See :help 'shellxquote'
val quotedCommand = if (shellxquote == "(") "($escapedCommand)"
else (if (shellxquote == "\"(") "\"($escapedCommand)\""
else shellxquote + escapedCommand + shellxquote)
val commands = ArrayList<String>()
commands.add(shell)
if (shellcmdflag.isNotEmpty()) {
// Note that Vim also does a simple whitespace split for multiple parameters
commands.addAll(ParametersListUtil.parse(shellcmdflag))
}
commands.add(quotedCommand)
if (logger.isDebugEnabled) {
logger.debug(String.format("shell=%s shellcmdflag=%s command=%s", shell, shellcmdflag, quotedCommand))
}
val commandLine = GeneralCommandLine(commands)
if (currentDirectoryPath != null) {
commandLine.setWorkDirectory(currentDirectoryPath)
}
val handler = CapturingProcessHandler(commandLine)
if (input != null) {
handler.addProcessListener(object : ProcessAdapter() {
override fun startNotified(event: ProcessEvent) {
try {
val charSequenceReader = CharSequenceReader(input)
val outputStreamWriter = BufferedWriter(OutputStreamWriter(handler.processInput))
copy(charSequenceReader, outputStreamWriter)
outputStreamWriter.close()
} catch (e: IOException) {
logger.error(e)
}
}
})
}
val progressIndicator = ProgressIndicatorProvider.getInstance().progressIndicator
val output = handler.runProcessWithProgressIndicator(progressIndicator)
lastCommand = command
if (output.isCancelled) {
// TODO: Vim will use whatever text has already been written to stdout
// For whatever reason, we're not getting any here, so just throw an exception
throw ProcessCanceledException()
}
val exitCode = handler.exitCode
if (exitCode != null && exitCode != 0) {
VimPlugin.showMessage("shell returned $exitCode")
VimPlugin.indicateError()
}
(output.stderr + output.stdout).replace("\u001B\\[[;\\d]*m".toRegex(), "")
}, "IdeaVim - !$command", true, editor.ij.project
)
}
@Suppress("SameParameterValue")
private fun doEscape(original: String, charsToEscape: String, escapeChar: String): String {
var result = original
for (c in charsToEscape.toCharArray()) {
result = result.replace("" + c, escapeChar + c)
}
return result
}
// TODO: Java 10 has a transferTo method we could use instead
@Throws(IOException::class)
private fun copy(from: Reader, to: Writer) {
val buf = CharArray(2048)
var cnt: Int
while ((from.read(buf).also { cnt = it }) != -1) {
to.write(buf, 0, cnt)
}
}
public companion object {
private val logger = logger<ProcessGroup>()
}
}

View File

@ -1,91 +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.handler
import com.intellij.openapi.actionSystem.KeyboardShortcut
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.keymap.Keymap
import com.intellij.openapi.keymap.KeymapManagerListener
import com.intellij.openapi.keymap.ex.KeymapManagerEx
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.StartupActivity
import com.intellij.util.SingleAlarm
import com.jetbrains.rd.util.ConcurrentHashMap
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.key
// We use alarm with delay to avoid many actions in case many events are fired at the same time
// [VERSION UPDATE] 2023.3+ Replace SingleAlarm with coroutine flows https://youtrack.jetbrains.com/articles/IJPL-A-8/Alarm-Alternative
internal val correctorRequester = SingleAlarm({ correctCopilotKeymap() }, 1_000)
private val LOG = logger<CopilotKeymapCorrector>()
internal class CopilotKeymapCorrector : StartupActivity {
override fun runActivity(project: Project) {
correctorRequester.request()
}
}
internal class IdeaVimCorrectorKeymapChangedListener : KeymapManagerListener {
override fun activeKeymapChanged(keymap: Keymap?) {
correctorRequester.request()
}
override fun shortcutChanged(keymap: Keymap, actionId: String) {
correctorRequester.request()
}
override fun shortcutChanged(keymap: Keymap, actionId: String, fromSettings: Boolean) {
correctorRequester.request()
}
}
private val copilotHideActionMap = ConcurrentHashMap<String, Unit>()
/**
* See VIM-3206
* The user expected to both copilot suggestion and the insert mode to be exited on a single esc.
* However, for the moment, the first esc hides copilot suggestion and the second one exits insert mode.
* To fix this, we remove the esc shortcut from the copilot action if the IdeaVim is active.
*
* This workaround is not the best solution, however, I don't see the better way with the current architecture of
* actions and EditorHandlers. Firstly, I wanted to suggest to copilot to migrate to EditorActionHandler as well,
* but this doesn't seem correct for me because in this case the user will lose an ability to change the shorcut for
* it. It seems like copilot has a similar problem as we do - we don't want to make a handler for "Editor enter action",
* but a handler for the esc key press. And, moreover, be able to communicate with other plugins about the ordering.
* Before this feature is implemented, hiding the copilot suggestion on esc looks like a good workaround.
*/
private fun correctCopilotKeymap() {
// This is needed to initialize the injector in case this verification is called to fast
VimPlugin.getInstance()
if (injector.enabler.isEnabled()) {
val keymap = KeymapManagerEx.getInstanceEx().activeKeymap
val res = keymap.getShortcuts("copilot.disposeInlays")
if (res.isEmpty()) return
val escapeShortcut = res.find { it.toString() == "[pressed ESCAPE]" } ?: return
keymap.removeShortcut("copilot.disposeInlays", escapeShortcut)
copilotHideActionMap[keymap.name] = Unit
LOG.info("Remove copilot escape shortcut from keymap ${keymap.name}")
}
else {
copilotHideActionMap.forEach { (name, _) ->
val keymap = KeymapManagerEx.getInstanceEx().getKeymap(name) ?: return@forEach
val currentShortcuts = keymap.getShortcuts("copilot.disposeInlays")
if ("[pressed ESCAPE]" !in currentShortcuts.map { it.toString() }) {
keymap.addShortcut("copilot.disposeInlays", KeyboardShortcut(key("<esc>"), null))
}
LOG.info("Restore copilot escape shortcut in keymap ${keymap.name}")
}
}
}

View File

@ -8,14 +8,11 @@
package com.maddyhome.idea.vim.handler
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.actionSystem.EditorActionHandlerBean
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.keymap.ex.KeymapManagerEx
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.ProjectActivity
import com.maddyhome.idea.vim.api.key
import com.intellij.openapi.startup.StartupActivity
/**
* Logs the chain of handlers for esc and enter
@ -29,11 +26,11 @@ import com.maddyhome.idea.vim.api.key
* Strictly speaking, such access to the extension point is not allowed by the platform. But we can't do this thing
* otherwise, so let's use it as long as we can.
*/
internal class EditorHandlersChainLogger : ProjectActivity {
internal class EditorHandlersChainLogger : StartupActivity {
@Suppress("UnresolvedPluginConfigReference")
private val editorHandlers = ExtensionPointName<EditorActionHandlerBean>("com.intellij.editorActionHandler")
override suspend fun execute(project: Project) {
override fun runActivity(project: Project) {
val escHandlers = editorHandlers.extensionList
.filter { it.action == "EditorEscape" }
.joinToString("\n") { it.implementationClass }
@ -43,22 +40,6 @@ internal class EditorHandlersChainLogger : ProjectActivity {
LOG.info("Esc handlers chain:\n$escHandlers")
LOG.info("Enter handlers chain:\n$enterHandlers")
val keymapManager = KeymapManagerEx.getInstanceEx()
val keymap = keymapManager.activeKeymap
val keymapShortcutsForEsc = keymap.getShortcuts(IdeActions.ACTION_EDITOR_ESCAPE).joinToString()
val keymapShortcutsForEnter = keymap.getShortcuts(IdeActions.ACTION_EDITOR_ENTER).joinToString()
LOG.info("Active keymap (${keymap.name}) shortcuts for esc: $keymapShortcutsForEsc, Shortcuts for enter: $keymapShortcutsForEnter")
val actionsForEsc = keymap.getActionIds(key("<esc>")).joinToString("\n")
val actionsForEnter = keymap.getActionIds(key("<enter>")).joinToString("\n")
LOG.info(
"Also keymap (${keymap.name}) has " +
"the following actions assigned to esc:\n$actionsForEsc " +
"\nand following actions assigned to enter:\n$actionsForEnter"
)
}
companion object {

View File

@ -1,128 +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.handler
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.actionSystem.KeyboardShortcut
import com.intellij.openapi.actionSystem.Shortcut
import com.intellij.openapi.keymap.Keymap
import com.intellij.openapi.keymap.KeymapManagerListener
import com.intellij.openapi.keymap.ex.KeymapManagerEx
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.StartupActivity
import com.intellij.util.SingleAlarm
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.key
import javax.swing.KeyStroke
// We use alarm with delay to avoid many notifications in case many events are fired at the same time
// [VERSION UPDATE] 2023.3+ Replace SingleAlarm with coroutine flows https://youtrack.jetbrains.com/articles/IJPL-A-8/Alarm-Alternative
internal val keymapCheckRequester = SingleAlarm({ verifyKeymap() }, 5_000)
/**
* This checker verifies that the keymap has a correct configuration that is required for IdeaVim plugin
*/
internal class KeymapChecker : StartupActivity {
override fun runActivity(project: Project) {
keymapCheckRequester.request()
}
}
internal class IdeaVimKeymapChangedListener : KeymapManagerListener {
override fun activeKeymapChanged(keymap: Keymap?) {
keymapCheckRequester.request()
}
override fun shortcutChanged(keymap: Keymap, actionId: String) {
keymapCheckRequester.request()
}
override fun shortcutChanged(keymap: Keymap, actionId: String, fromSettings: Boolean) {
keymapCheckRequester.request()
}
}
/**
* After migration to the editor action handlers, we have to make sure that the keymap has a correct configuration.
* For example, that esc key is assigned to esc editor action
*
* Usually this is not a problem because this is a standard mapping, but the problem may appear in a misconfiguration
* like it was in VIM-3204
*/
private fun verifyKeymap() {
// This is needed to initialize the injector in case this verification is called to fast
VimPlugin.getInstance()
if (!injector.enabler.isEnabled()) return
val keymap = KeymapManagerEx.getInstanceEx().activeKeymap
val keymapShortcutsForEsc = keymap.getShortcuts(IdeActions.ACTION_EDITOR_ESCAPE)
val keymapShortcutsForEnter = keymap.getShortcuts(IdeActions.ACTION_EDITOR_ENTER)
val issues = ArrayList<KeyMapIssue>()
val correctShortcutMissing = keymapShortcutsForEsc
.filterIsInstance<KeyboardShortcut>()
.none { it.firstKeyStroke.toString() == "pressed ESCAPE" && it.secondKeyStroke == null }
// We also check if there are any shortcuts starting from esc and with a second key. This should also be removed.
// For example, VIM-3162 has a case when two escapes were assigned to editor escape action
val shortcutsStartingFromEsc = keymapShortcutsForEsc
.filterIsInstance<KeyboardShortcut>()
.filter { it.firstKeyStroke.toString() == "pressed ESCAPE" && it.secondKeyStroke != null }
if (correctShortcutMissing) {
issues += KeyMapIssue.AddShortcut(
"esc",
"editor escape",
IdeActions.ACTION_EDITOR_ESCAPE,
key("<esc>")
)
}
shortcutsStartingFromEsc.forEach {
issues += KeyMapIssue.RemoveShortcut("editor escape", IdeActions.ACTION_EDITOR_ESCAPE, it)
}
val correctEnterShortcutMissing = keymapShortcutsForEnter
.filterIsInstance<KeyboardShortcut>()
.none { it.firstKeyStroke.toString() == "pressed ENTER" && it.secondKeyStroke == null }
val shortcutsStartingFromEnter = keymapShortcutsForEnter
.filterIsInstance<KeyboardShortcut>()
.filter { it.firstKeyStroke.toString() == "pressed ENTER" && it.secondKeyStroke != null }
if (correctEnterShortcutMissing) {
issues += KeyMapIssue.AddShortcut(
"enter",
"editor enter",
IdeActions.ACTION_EDITOR_ENTER,
key("<enter>")
)
}
shortcutsStartingFromEnter.forEach {
issues += KeyMapIssue.RemoveShortcut("editor enter", IdeActions.ACTION_EDITOR_ENTER, it)
}
if (issues.isNotEmpty()) {
VimPlugin.getNotifications(null).notifyKeymapIssues(issues)
}
}
internal sealed interface KeyMapIssue {
data class AddShortcut(
val key: String,
val action: String,
val actionId: String,
val keyStroke: KeyStroke,
) : KeyMapIssue
data class RemoveShortcut(
val action: String,
val actionId: String,
val shortcut: Shortcut,
): KeyMapIssue
}

View File

@ -10,7 +10,6 @@ package com.maddyhome.idea.vim.handler
import com.intellij.codeInsight.editorActions.AutoHardWrapHandler
import com.intellij.codeInsight.lookup.LookupManager
import com.intellij.formatting.LineWrappingUtil
import com.intellij.ide.DataManager
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
@ -19,7 +18,6 @@ import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.actionSystem.EditorActionHandler
import com.intellij.openapi.editor.actions.SplitLineAction
import com.intellij.openapi.editor.impl.CaretModelImpl
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.util.Key
@ -97,15 +95,7 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
// the condition (see VIM-3103 for example).
// Since we can't make sure we don't execute `runForEachCaret`, we have to "escape" out of this function. This is
// done by scheduling the execution of our code later via the invokeLater function.
//
// We run this job only once for a primary caret. In the handler itself, we'll multiply the execution by the
// number of carets. If we run this job for each caret, we may end up in the issue like VIM-3186.
// However, I think that we may do some refactoring to run this job for each caret (if needed).
//
// For the moment, the known case when the caret is null - work in injected editor - VIM-3195
if (caret == null || caret == editor.caretModel.primaryCaret) {
ApplicationManager.getApplication().invokeLater(executionHandler)
}
ApplicationManager.getApplication().invokeLater(executionHandler)
} else {
executionHandler()
}
@ -116,11 +106,7 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
private fun executeInInvokeLater(editor: Editor): Boolean {
// Currently we have a workaround for the PY console VIM-3157
val fileName = FileDocumentManager.getInstance().getFile(editor.document)?.name
if (
fileName == "Python Console.py" || // This is the name in 232+
fileName == "Python Console" // This is the name in 231
) return false
if (FileDocumentManager.getInstance().getFile(editor.document)?.name == "Python Console.py") return false
return (editor.caretModel as? CaretModelImpl)?.isIteratingOverCarets ?: true
}
@ -145,20 +131,7 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
return true
}
// From VIM-3177
val wrapLongLineDuringFormattingInProgress = dataManager
.loadFromDataContext(dataContext, LineWrappingUtil.WRAP_LONG_LINE_DURING_FORMATTING_IN_PROGRESS_KEY)
if (wrapLongLineDuringFormattingInProgress == true) {
return true
}
// From VIM-3203
val splitLineInProgress = dataManager.loadFromDataContext(dataContext, SplitLineAction.SPLIT_LINE_KEY)
if (splitLineInProgress == true) {
return true
}
if (dataManager.loadFromDataContext(dataContext, StartNewLineDetectorBase.Util.key) == true) {
if (dataManager.loadFromDataContext(dataContext, ShiftEnterDetector.Util.key) == true) {
return true
}
}
@ -201,6 +174,11 @@ internal class VimEnterHandler(nextHandler: EditorActionHandler?) : VimKeyHandle
// See VIM-2974 for example where it was broken
return !editor.isOneLineMode
}
override fun executeHandler(editor: Editor, caret: Caret?, dataContext: DataContext?) {
if (caret == null || caret === editor.caretModel.primaryCaret)
super.executeHandler(editor, caret, dataContext)
}
}
/**
@ -271,17 +249,11 @@ internal class VimEscLoggerHandler(private val nextHandler: EditorActionHandler)
}
/**
* Workaround to support "Start New Line" action in normal mode.
* IJ executes enter handler on "Start New Line". This causes an issue that IdeaVim thinks that this is just an enter key.
* Workaround to support shift-enter in normal mode.
* IJ executes enter handler on shift-enter. This causes an issue that IdeaVim thinks that this is just an enter key.
* This thing should be refactored, but for now we'll use this workaround VIM-3159
*
* The Same thing happens with "Start New Line Before Current" action.
*/
internal class StartNewLineDetector(nextHandler: EditorActionHandler) : StartNewLineDetectorBase(nextHandler)
internal class StartNewLineBeforeCurrentDetector(nextHandler: EditorActionHandler) :
StartNewLineDetectorBase(nextHandler)
internal open class StartNewLineDetectorBase(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
internal class ShiftEnterDetector(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
DataManager.getInstance().saveInDataContext(dataContext, Util.key, true)
nextHandler.execute(editor, caret, dataContext)
@ -292,7 +264,7 @@ internal open class StartNewLineDetectorBase(private val nextHandler: EditorActi
}
object Util {
val key = Key.create<Boolean>("vim.is.start.new.line")
val key = Key.create<Boolean>("vim.is.shift.enter")
}
companion object {
@ -344,9 +316,9 @@ internal fun isOctopusEnabled(s: KeyStroke, editor: Editor): Boolean {
// CMD line has a different processing mechanizm: the processing actions are registered
// for the input field component. These keys are not dispatched via the octopus handler.
if (editor.vim.mode is Mode.CMD_LINE) return false
when {
s.keyCode == KeyEvent.VK_ENTER && s.modifiers == 0 -> return true
s.keyCode == KeyEvent.VK_ESCAPE && s.modifiers == 0 -> return true
when (s.keyCode) {
KeyEvent.VK_ENTER -> return true
KeyEvent.VK_ESCAPE -> return true
}
return false
}

View File

@ -36,7 +36,7 @@ public val Editor.mode: CommandState.Mode
get() {
val mode = this.vim.vimStateMachine.mode
return when (mode) {
is Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
Mode.INSERT -> CommandState.Mode.INSERT
is Mode.NORMAL -> CommandState.Mode.COMMAND
is Mode.OP_PENDING -> CommandState.Mode.OP_PENDING

View File

@ -15,12 +15,10 @@ import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.AnActionResult
import com.intellij.openapi.actionSystem.DataContextWrapper
import com.intellij.openapi.actionSystem.EmptyAction
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.actionSystem.ex.ActionManagerEx
import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.UndoConfirmationPolicy
import com.intellij.openapi.components.Service
@ -41,8 +39,6 @@ import com.maddyhome.idea.vim.newapi.IjNativeAction
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.runFromVimKey
import org.jetbrains.annotations.NonNls
import java.awt.Component
import javax.swing.JComponent
import javax.swing.SwingUtilities
@Service
@ -154,44 +150,11 @@ internal class IjActionExecutor : VimActionExecutor {
* @param context The context to run it in
*/
override fun executeAction(name: @NonNls String, context: ExecutionContext): Boolean {
val action = getAction(name, context)
val aMgr = ActionManager.getInstance()
val action = aMgr.getAction(name)
return action != null && executeAction(null, IjNativeAction(action), context)
}
private fun getAction(name: String, context: ExecutionContext): AnAction? {
val actionManager = ActionManager.getInstance()
val action = actionManager.getAction(name)
if (action !is EmptyAction) return action
// But if the action is an instance of EmptyAction, the fun begins
var component: Component? = context.ij.getData(PlatformDataKeys.CONTEXT_COMPONENT) ?: return null
while (component != null) {
if (component !is JComponent) {
component = component.parent
continue
}
val listOfActions = ActionUtil.getActions(component)
if (listOfActions.isEmpty()) {
component = component.getParent()
continue
}
fun AnAction.getId(): String? {
return actionManager.getId(this)
?: (shortcutSet as? ProxyShortcutSet)?.actionId
}
for (action in listOfActions) {
if (action.getId() == name) {
return action
}
}
component = component.getParent()
}
return null
}
override fun executeCommand(
editor: VimEditor?,
runnable: Runnable,

View File

@ -18,7 +18,6 @@ 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
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
@ -45,17 +44,22 @@ internal class UndoRedoHelper : UndoRedoBase() {
restoreVisualMode(editor)
} else {
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
editor.runWithChangeTracking {
undoManager.undo(fileEditor)
// We execute undo one more time if the previous one just restored selection
if (!hasChanges && hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
undoManager.undo(fileEditor)
}
undoManager.undo(fileEditor)
if (hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
undoManager.undo(fileEditor) // execute one more time if the previous undo just restored selection
}
CommandProcessor.getInstance().runUndoTransparentAction {
removeSelections(editor)
// remove selection
editor.carets().forEach {
val ijCaret = it.ij
val hasSelection = ijCaret.hasSelection()
if (hasSelection) {
val selectionStart = ijCaret.selectionStart
CommandProcessor.getInstance().runUndoTransparentAction {
it.ij.removeSelection()
it.ij.moveToOffset(selectionStart)
}
}
}
}
@ -84,59 +88,12 @@ internal class UndoRedoHelper : UndoRedoBase() {
CommandProcessor.getInstance().runUndoTransparentAction {
editor.carets().forEach { it.ij.removeSelection() }
}
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
editor.runWithChangeTracking {
undoManager.redo(fileEditor)
// We execute undo one more time if the previous one just restored selection
if (!hasChanges && hasSelection(editor) && undoManager.isRedoAvailable(fileEditor)) {
undoManager.redo(fileEditor)
}
}
CommandProcessor.getInstance().runUndoTransparentAction {
removeSelections(editor)
}
}
return true
}
return false
}
private fun removeSelections(editor: VimEditor) {
editor.carets().forEach {
val ijCaret = it.ij
if (!ijCaret.hasSelection()) return@forEach
val selectionStart = ijCaret.selectionStart
ijCaret.removeSelection()
ijCaret.moveToOffset(selectionStart)
}
}
private fun VimEditor.runWithChangeTracking(block: ChangeTracker.() -> Unit) {
val tracker = ChangeTracker(this)
tracker.block()
}
private class ChangeTracker(private val editor: VimEditor) {
private val initialPath = editor.getPath()
private val changeListener = object : ChangesListener {
var hasChanged = false
override fun documentChanged(change: ChangesListener.Change) {
hasChanged = true
}
}
init {
editor.document.addChangeListener(changeListener)
}
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)

View File

@ -70,8 +70,6 @@ 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.keymapCheckRequester
import com.maddyhome.idea.vim.helper.GuicursorChangeListener
import com.maddyhome.idea.vim.helper.StrictMode
import com.maddyhome.idea.vim.helper.exitSelectMode
@ -131,14 +129,11 @@ internal object VimListenerManager {
fun turnOn() {
GlobalListeners.enable()
EditorListeners.addAll()
correctorRequester.request()
keymapCheckRequester.request()
}
fun turnOff() {
GlobalListeners.disable()
EditorListeners.removeAll()
correctorRequester.request()
}
object GlobalListeners {

View File

@ -18,7 +18,6 @@ import com.intellij.openapi.editor.CaretStateTransferableData
import com.intellij.openapi.editor.RawText
import com.intellij.openapi.editor.richcopy.view.HtmlTransferableData
import com.intellij.openapi.editor.richcopy.view.RtfTransferableData
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.IndexNotReadyException
import com.intellij.psi.PsiDocumentManager
import com.intellij.util.ui.EmptyClipboardOwner
@ -101,7 +100,8 @@ internal class IjClipboardManager : VimClipboardManager {
// This thing enables alternative context resolve for dumb mode.
// Please read docs for com.intellij.openapi.project.DumbService.isAlternativeResolveEnabled
DumbService.getInstance(project).withAlternativeResolveEnabled {
// [VERSION UPDATE] 2023.2+ Enable alternative context back
// DumbService.getInstance(project).withAlternativeResolveEnabled {
for (processor in CopyPastePostProcessor.EP_NAME.extensionList) {
try {
logger.debug { "Copy paste processor: ${processor.javaClass.name}" }
@ -116,7 +116,7 @@ internal class IjClipboardManager : VimClipboardManager {
} catch (ignore: IndexNotReadyException) {
}
}
}
// }
transferableData.add(CaretStateTransferableData(intArrayOf(0), intArrayOf(text.length)))
// These data provided by {@link com.intellij.openapi.editor.richcopy.TextWithMarkupProcessor} doesn't work with

View File

@ -52,7 +52,6 @@ import java.awt.Point
import java.awt.event.MouseEvent
import javax.swing.Icon
import javax.swing.SwingConstants
import javax.swing.Timer
@NonNls
internal const val STATUS_BAR_ICON_ID = "IdeaVim-Icon"
@ -74,14 +73,6 @@ internal class StatusBarIconFactory : StatusBarWidgetFactory/*, LightEditCompati
override fun createWidget(project: Project): StatusBarWidget {
VimPlugin.getOptionGroup().addGlobalOptionChangeListener(IjOptions.ideastatusicon) { updateAll() }
// Double update the status bar icon with 5-second delay
// There is an issue VIM-3084 that must probably caused by some race between status bar icon initialization
// and .ideavimrc reading. I believe this is a simple fix for it.
val timer = Timer(5_000) { updateAll() }
timer.isRepeats = false
timer.start()
return VimStatusBar()
}
@ -98,10 +89,10 @@ internal class StatusBarIconFactory : StatusBarWidgetFactory/*, LightEditCompati
statusBarWidgetsManager.updateWidget(this)
}
Util.updateIcon()
updateIcon()
}
object Util {
companion object {
fun updateIcon() {
val projectManager = ProjectManager.getInstanceIfCreated() ?: return
for (project in projectManager.openProjects) {

View File

@ -12,10 +12,6 @@
topic="com.intellij.ide.ui.LafManagerListener"/>
<listener class="com.maddyhome.idea.vim.extension.highlightedyank.HighlightColorResetter"
topic="com.intellij.ide.ui.LafManagerListener"/>
<listener class="com.maddyhome.idea.vim.handler.IdeaVimKeymapChangedListener"
topic="com.intellij.openapi.keymap.KeymapManagerListener"/>
<listener class="com.maddyhome.idea.vim.handler.IdeaVimCorrectorKeymapChangedListener"
topic="com.intellij.openapi.keymap.KeymapManagerListener"/>
</applicationListeners>
<projectListeners>
<listener class="com.maddyhome.idea.vim.ui.ExOutputPanel$LafListener"

View File

@ -71,12 +71,11 @@
core platform activities have IDs, so we can't use "before ID". We have to use "first" -->
<postStartupActivity implementation="com.maddyhome.idea.vim.PluginStartup" order="first"/>
<postStartupActivity implementation="com.maddyhome.idea.vim.handler.EditorHandlersChainLogger"/>
<postStartupActivity implementation="com.maddyhome.idea.vim.handler.KeymapChecker"/>
<postStartupActivity implementation="com.maddyhome.idea.vim.handler.CopilotKeymapCorrector"/>
<editorFloatingToolbarProvider implementation="com.maddyhome.idea.vim.ui.ReloadFloatingToolbar"/>
<actionPromoter implementation="com.maddyhome.idea.vim.key.VimActionsPromoter" order="last"/>
<actionConfigurationCustomizer implementation="com.maddyhome.idea.vim.action.VimActionConfigurationCustomizer"/>
<spellchecker.bundledDictionaryProvider implementation="com.maddyhome.idea.vim.VimBundledDictionaryProvider"/>
@ -118,12 +117,8 @@
id="ideavim-enter-logger"
order="first"/>
<editorActionHandler action="EditorStartNewLine"
implementationClass="com.maddyhome.idea.vim.handler.StartNewLineDetector"
id="ideavim-start-new-line-detector"
order="first"/>
<editorActionHandler action="EditorStartNewLineBefore"
implementationClass="com.maddyhome.idea.vim.handler.StartNewLineBeforeCurrentDetector"
id="ideavim-start-new-line-before-current-detector"
implementationClass="com.maddyhome.idea.vim.handler.ShiftEnterDetector"
id="ideavim-shift-enter-detector"
order="first"/>
</extensions>

View File

@ -1,11 +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.
-->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 40 40" fill-opacity="0" stroke="#6E6E6E" stroke-width="3">
<path d="M 28.019 4 L 15.988 24.119 L 15.988 4 L 4 4 L 4 36 L 17.953 36 L 36 4z"/>
</svg>

Before

Width:  |  Height:  |  Size: 446 B

View File

@ -771,11 +771,7 @@ abstract class VimTestCase {
private fun KeyStroke.getChar(editor: Editor): CharType {
if (keyChar != KeyEvent.CHAR_UNDEFINED) return CharType.CharDetected(keyChar)
if (isOctopusEnabled(this, editor)) {
if (keyCode in setOf(KeyEvent.VK_ENTER)) {
if (modifiers == 0) {
return CharType.CharDetected(keyCode.toChar())
}
}
if (keyCode in setOf(KeyEvent.VK_ENTER)) return CharType.CharDetected(keyCode.toChar())
if (keyCode == KeyEvent.VK_ESCAPE) return CharType.EditorAction("EditorEscape")
}
return CharType.UNDEFINED

View File

@ -1,101 +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 org.jetbrains.plugins.ideavim.action
import com.intellij.idea.TestFor
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
class ActionsTest : VimTestCase() {
@Test
@TestFor(issues = ["VIM-3203"])
@TestWithoutNeovim(SkipNeovimReason.NOT_VIM_TESTING)
fun `split line action`() {
configureByText(
"""
Lorem Ipsum
Lorem ipsum dolor sit amet,$c consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
fixture.performEditorAction("EditorSplitLine")
assertState(
"""
Lorem Ipsum
Lorem ipsum dolor sit amet,$c
consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
}
@Test
@TestFor(issues = ["VIM-3159"])
@TestWithoutNeovim(SkipNeovimReason.NOT_VIM_TESTING)
fun `start new line before`() {
configureByText(
"""
Lorem Ipsum
Lorem ipsum dolor sit amet,$c consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
fixture.performEditorAction("EditorStartNewLineBefore")
assertState(
"""
Lorem Ipsum
$c
Lorem ipsum dolor sit amet, consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
}
@Test
@TestFor(issues = ["VIM-3159"])
@TestWithoutNeovim(SkipNeovimReason.NOT_VIM_TESTING)
fun `start new line`() {
configureByText(
"""
Lorem Ipsum
Lorem ipsum dolor sit amet,$c consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
fixture.performEditorAction("EditorStartNewLine")
assertState(
"""
Lorem Ipsum
Lorem ipsum dolor sit amet, consectetur adipiscing elit
$c
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
}
}

View File

@ -1,101 +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 org.jetbrains.plugins.ideavim.action
import com.intellij.idea.TestFor
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
class EscapeTest : VimTestCase() {
@Test
@TestFor(issues = ["VIM-3190"])
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
fun `mapping to control esc`() {
configureByText(
"""
Lorem Ipsum
Lorem ipsum dolor sit amet,$c consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
typeText(commandToKeys("nmap <C-Esc> k"))
typeText("<C-Esc>")
assertState(
"""
Lorem Ipsum
$c
Lorem ipsum dolor sit amet, consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
}
@Test
@TestFor(issues = ["VIM-3190"])
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
fun `mapping to alt esc`() {
configureByText(
"""
Lorem Ipsum
Lorem ipsum dolor sit amet,$c consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
typeText(commandToKeys("nmap <A-Esc> k"))
typeText("<A-Esc>")
assertState(
"""
Lorem Ipsum
$c
Lorem ipsum dolor sit amet, consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
}
@Test
@TestFor(issues = ["VIM-3190"])
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
fun `mapping to shift esc`() {
configureByText(
"""
Lorem Ipsum
Lorem ipsum dolor sit amet,$c consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
typeText(commandToKeys("nmap <S-Esc> k"))
typeText("<S-Esc>")
assertState(
"""
Lorem Ipsum
$c
Lorem ipsum dolor sit amet, consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
}
}

View File

@ -11,8 +11,10 @@ import com.maddyhome.idea.vim.api.injector
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
// [VERSION UPDATE] 232+ enable tests
@Suppress("unused")
class ReformatCodeTest : VimTestCase() {
@Test
@ -21,6 +23,7 @@ class ReformatCodeTest : VimTestCase() {
}
@Test
@Disabled
fun testEmpty() {
configureByJavaText("<caret>")
typeText(injector.parser.parseKeys("gqq"))
@ -29,6 +32,7 @@ class ReformatCodeTest : VimTestCase() {
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
@Test
@Disabled
fun testWithCount() {
configureByJavaText("class C {\n\tint a;\n\tint <caret>b;\n\tint c;\n\tint d;\n}\n")
typeText(injector.parser.parseKeys("2gqq"))
@ -37,6 +41,7 @@ class ReformatCodeTest : VimTestCase() {
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
@Test
@Disabled
fun testWithUpMotion() {
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint b;\n" + "\tint <caret>c;\n" + "\tint d;\n" + "}\n")
typeText(injector.parser.parseKeys("gqk"))
@ -45,6 +50,7 @@ class ReformatCodeTest : VimTestCase() {
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
@Test
@Disabled
fun testWithRightMotion() {
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n")
typeText(injector.parser.parseKeys("gql"))
@ -53,6 +59,7 @@ class ReformatCodeTest : VimTestCase() {
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
@Test
@Disabled
fun testWithTextObject() {
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n")
typeText(injector.parser.parseKeys("gqi{"))
@ -68,6 +75,7 @@ class ReformatCodeTest : VimTestCase() {
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
@Test
@Disabled
fun testWithCountsAndDownMotion() {
configureByJavaText("class C {\n" + "\tint <caret>a;\n" + "\tint b;\n" + "\tint c;\n" + "\tint d;\n" + "}\n")
typeText(injector.parser.parseKeys("2gqj"))
@ -76,6 +84,7 @@ class ReformatCodeTest : VimTestCase() {
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
@Test
@Disabled
fun testVisual() {
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n")
typeText(injector.parser.parseKeys("v" + "l" + "gq"))
@ -84,6 +93,7 @@ class ReformatCodeTest : VimTestCase() {
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
@Test
@Disabled
fun testLinewiseVisual() {
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n")
typeText(injector.parser.parseKeys("V" + "l" + "gq"))
@ -92,6 +102,7 @@ class ReformatCodeTest : VimTestCase() {
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
@Test
@Disabled
fun testVisualMultiline() {
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "\tint d;\n" + "}\n")
typeText(injector.parser.parseKeys("v" + "j" + "gq"))
@ -100,6 +111,7 @@ class ReformatCodeTest : VimTestCase() {
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
@Test
@Disabled
fun testVisualBlock() {
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "\tint d;\n" + "}\n")
typeText(injector.parser.parseKeys("<C-V>" + "j" + "gq"))

View File

@ -10,7 +10,6 @@
package org.jetbrains.plugins.ideavim.action.change.delete
import com.intellij.notification.ActionCenter
import com.intellij.notification.EventLog
import com.intellij.notification.Notification
import com.maddyhome.idea.vim.VimPlugin
@ -21,12 +20,14 @@ import org.jetbrains.plugins.ideavim.VimTestCase
import org.jetbrains.plugins.ideavim.impl.OptionTest
import org.jetbrains.plugins.ideavim.impl.TraceOptions
import org.jetbrains.plugins.ideavim.impl.VimOption
import org.junit.jupiter.api.Disabled
/**
* @author Alex Plate
*/
@TraceOptions(TestIjOptionConstants.ideajoin)
class JoinNotificationTest : VimTestCase() {
@Disabled("[VERSION UPDATE] Enable when min version is 2023.2+")
@OptionTest(VimOption(TestIjOptionConstants.ideajoin, limitedValues = ["false"]))
fun `test notification shown for no ideajoin`() {
val before = "I found${c} it\n in a legendary land"
@ -44,6 +45,7 @@ class JoinNotificationTest : VimTestCase() {
}
}
@Disabled("[VERSION UPDATE] Enable when min version is 2023.2+")
@OptionTest(VimOption(TestIjOptionConstants.ideajoin, limitedValues = ["true"]))
fun `test notification not shown for ideajoin`() {
val before = "I found${c} it\n in a legendary land"
@ -56,7 +58,8 @@ class JoinNotificationTest : VimTestCase() {
}
private fun notifications(): MutableList<Notification> {
return ActionCenter.getNotifications(fixture.project)
TODO()
// return ActionCenter.getNotifications(fixture.project)
}
@OptionTest(VimOption(TestIjOptionConstants.ideajoin, limitedValues = ["false"]))

View File

@ -60,7 +60,7 @@ class InsertEnterActionTest : VimTestCase() {
} else if (repetitionInfo.currentRepetition == 3) {
ExtensionTestUtil.maskExtensions(
ExtensionPointName("com.intellij.editorActionHandler"),
listOf(forEachBean, mainBean),
listOf(singleBean, mainBean),
fixture.testRootDisposable
)
}

View File

@ -12,38 +12,14 @@ import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
class InsertSingleCommandActionTest : SingleCommandActionTest() {
override val command: String = "i"
override val mode: Mode = Mode.INSERT
}
class ReplaceSingleCommandActionTest : SingleCommandActionTest() {
override val command: String = "R"
override val mode: Mode = Mode.REPLACE
}
abstract class SingleCommandActionTest : VimTestCase() {
abstract val command: String
abstract val mode: Mode
@Test
fun `one operation`() {
doTest(
listOf(command, "<C-O>", "l"),
"I found ${c}it in a legendary land",
"I found i${c}t in a legendary land",
mode,
)
}
class InsertSingleCommandActionTest : VimTestCase() {
@Test
fun `test enter visual`() {
doTest(
listOf(command, "<C-O>", "vlll", "<Esc>"),
listOf("i", "<C-O>", "vlll", "<Esc>"),
"I found ${c}it in a legendary land",
"I found it ${c}in a legendary land",
mode,
Mode.INSERT,
)
}
}

View File

@ -8,7 +8,6 @@
package org.jetbrains.plugins.ideavim.action.copy
import com.intellij.notification.ActionCenter
import com.intellij.notification.EventLog
import com.intellij.notification.Notification
import com.maddyhome.idea.vim.VimPlugin
@ -23,9 +22,11 @@ import org.jetbrains.plugins.ideavim.impl.OptionTest
import org.jetbrains.plugins.ideavim.impl.TraceOptions
import org.jetbrains.plugins.ideavim.impl.VimOption
import org.jetbrains.plugins.ideavim.rangeOf
import org.junit.jupiter.api.Disabled
@TraceOptions(TestOptionConstants.clipboard)
class IdeaPutNotificationsTest : VimTestCase() {
@Disabled("[VERSION UPDATE] Enable when min version is 2023.2+")
@OptionTest(VimOption(TestOptionConstants.clipboard, limitedValues = [""]))
fun `test notification exists if no ideaput`() {
val before = "${c}I found it in a legendary land"
@ -46,6 +47,7 @@ class IdeaPutNotificationsTest : VimTestCase() {
}
}
@Disabled("[VERSION UPDATE] Enable when min version is 2023.2+")
@OptionTest(VimOption(TestOptionConstants.clipboard, limitedValues = [OptionConstants.clipboard_ideaput]))
fun `test no notification on ideaput`() {
val before = "${c}I found it in a legendary land"
@ -61,7 +63,8 @@ class IdeaPutNotificationsTest : VimTestCase() {
}
private fun notifications(): MutableList<Notification> {
return ActionCenter.getNotifications(fixture.project)
TODO()
// return ActionCenter.getNotifications(fixture.project)
}
@OptionTest(VimOption(TestOptionConstants.clipboard, limitedValues = [""]))

View File

@ -1,122 +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 org.jetbrains.plugins.ideavim.action.motion.search
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
class SearchEntryFwdActionTest : VimTestCase() {
@Test
fun `search in visual mode`() {
doTest(
"v/id<CR>",
"""Lorem ipsum dolor sit amet,
|${c}consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
"""Lorem ipsum dolor sit amet,
|${s}consectetur adipiscing elit
|Sed in orci mauris.
|Cras ${c}i${se}d tellus in ex imperdiet egestas.
""".trimMargin(),
Mode.VISUAL(SelectionType.CHARACTER_WISE),
)
}
@Test
fun `search in one time visual mode`() {
doTest(
"i<C-O>v/id<CR>",
"""Lorem ipsum dolor sit amet,
|${c}consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
"""Lorem ipsum dolor sit amet,
|consectetur adipiscing elit
|Sed in orci mauris.
|Cras ${c}id tellus in ex imperdiet egestas.
""".trimMargin(),
Mode.INSERT,
)
}
@Test
fun `search in one time visual mode from replace`() {
doTest(
"R<C-O>v/id<CR>",
"""Lorem ipsum dolor sit amet,
|${c}consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
"""Lorem ipsum dolor sit amet,
|consectetur adipiscing elit
|Sed in orci mauris.
|Cras ${c}id tellus in ex imperdiet egestas.
""".trimMargin(),
Mode.REPLACE,
)
}
@Test
fun `search in op pending`() {
doTest(
"d/id<CR>",
"""Lorem ipsum dolor sit amet,
|${c}consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
"""Lorem ipsum dolor sit amet,
|${c}id tellus in ex imperdiet egestas.
""".trimMargin(),
Mode.NORMAL(),
)
}
@Test
fun `search in op pending from one time mode`() {
doTest(
"i<C-O>d/id<CR>",
"""Lorem ipsum dolor sit amet,
|${c}consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
"""Lorem ipsum dolor sit amet,
|${c}id tellus in ex imperdiet egestas.
""".trimMargin(),
Mode.INSERT,
)
}
@Disabled("Ctrl-o doesn't work yet in select mode")
@Test
fun `search in one time from select mode`() {
doTest(
"gh<C-O>/id<CR>",
"""Lorem ipsum dolor sit amet,
|${c}consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
"""Lorem ipsum dolor sit amet,
|consectetur adipiscing elit
|Sed in orci mauris.
|Cras ${c}id tellus in ex imperdiet egestas.
""".trimMargin(),
Mode.SELECT(SelectionType.CHARACTER_WISE),
)
}
}

View File

@ -1,96 +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 org.jetbrains.plugins.ideavim.action.motion.updown
import com.intellij.idea.TestFor
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
class EnterNormalActionTest : VimTestCase() {
@Test
@TestFor(issues = ["VIM-3190"])
fun `mapping to control enter`() {
configureByText(
"""
Lorem Ipsum
Lorem ipsum dolor sit amet,$c consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
typeText(commandToKeys("nmap <C-Enter> k"))
typeText("<C-Enter>")
assertState(
"""
Lorem Ipsum
$c
Lorem ipsum dolor sit amet, consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
}
@Test
@TestFor(issues = ["VIM-3190"])
fun `mapping to alt enter`() {
configureByText(
"""
Lorem Ipsum
Lorem ipsum dolor sit amet,$c consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
typeText(commandToKeys("nmap <A-Enter> k"))
typeText("<A-Enter>")
assertState(
"""
Lorem Ipsum
$c
Lorem ipsum dolor sit amet, consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
}
@Test
@TestFor(issues = ["VIM-3190"])
fun `mapping to shift enter`() {
configureByText(
"""
Lorem Ipsum
Lorem ipsum dolor sit amet,$c consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
typeText(commandToKeys("nmap <S-Enter> k"))
typeText("<S-Enter>")
assertState(
"""
Lorem Ipsum
$c
Lorem ipsum dolor sit amet, consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
}
}

View File

@ -8,105 +8,15 @@
package org.jetbrains.plugins.ideavim.ex.implementation.commands
import com.intellij.idea.TestFor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.vim
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
class MarksCommandTest : VimTestCase() {
@Test
@TestFor(issues = ["VIM-3176"])
fun `test gv after pasting to the same line`() {
configureByText(
"""${c}I found 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.
""".trimMargin(),
)
typeText(injector.parser.parseKeys("V3j" + "y" + "P" + "gv"))
assertState(
"""I found 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.
|${s}I found it in a legendary land
|all rocks and lavender and tufted grass,
|where it was settled on some sodden sand
|${c}hard by the torrent of a mountain pass.${se}
""".trimMargin(),
)
}
// https://youtrack.jetbrains.com/issue/VIM-2223
@Test
@TestFor(issues = ["VIM-3176"])
fun `test gv after pasting to the same line reversed selection`() {
configureByText(
"""I found it in a legendary land
|all rocks and lavender and tufted grass,
|where it was settled on some sodden sand
|${c}hard by the torrent of a mountain pass.
""".trimMargin(),
)
typeText(injector.parser.parseKeys("V3k" + "y" + "P" + "gv"))
assertState(
"""I found 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.
|${s}${c}I found 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.${se}
""".trimMargin(),
)
}
@Test
@TestFor(issues = ["VIM-3176"])
fun `test gv after pasting inside selection expanded selection`() {
configureByText(
"""
${c}line1
line2
""".trimIndent(),
)
typeText(injector.parser.parseKeys("Vj" + "y" + "j" + "P" + "gv"))
assertState(
"""
${s}line1
line1
line2
line2${se}
""".trimIndent(),
)
}
@Test
@TestFor(issues = ["VIM-3176"])
fun `test gv after pasting below selection not changing selection`() {
configureByText(
"""
${c}line1
line2
not selected
""".trimIndent(),
)
typeText(injector.parser.parseKeys("Vj" + "y" + "j" + "p" + "gv"))
assertState(
"""
${s}line1
line2
${se}line1
line2
not selected
""".trimIndent(),
)
}
@Test
@TestFor(issues = ["VIM-2223"])
fun `test gv after replacing a line`() {
configureByText(
"""I found it in a legendary land
@ -125,8 +35,8 @@ class MarksCommandTest : VimTestCase() {
)
}
// https://youtrack.jetbrains.com/issue/VIM-1684
@Test
@TestFor(issues = ["VIM-1684"])
fun `test reselecting different text length`() {
configureByText(
"""
@ -143,8 +53,8 @@ class MarksCommandTest : VimTestCase() {
)
}
// https://youtrack.jetbrains.com/issue/VIM-2491
@Test
@TestFor(issues = ["VIM-2491"])
fun `test mapping with gv`() {
configureByText("Oh, hi ${c}Andy Tom John")
typeText(commandToKeys("xnoremap p pgvy"))

View File

@ -11,10 +11,10 @@ package org.jetbrains.plugins.ideavim.propertybased
import com.intellij.ide.IdeEventQueue
import com.intellij.openapi.editor.Editor
import com.intellij.testFramework.PlatformTestUtil
import com.maddyhome.idea.vim.action.change.LazyVimCommand
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.key.CommandNode
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import org.jetbrains.jetCheck.Generator
import org.jetbrains.jetCheck.ImperativeCommand
@ -99,7 +99,7 @@ private class AvailableActions(private val editor: Editor) : ImperativeCommand {
val usedKey = env.generateValue(keyGenerator, null)
val node = currentNode[usedKey]
env.logMessage("Use command: ${injector.parser.toKeyNotation(usedKey)}. ${if (node is CommandNode) "Action: ${(node.actionHolder as LazyVimCommand).actionId}" else ""}")
env.logMessage("Use command: ${injector.parser.toKeyNotation(usedKey)}. ${if (node is CommandNode) "Action: ${node.actionHolder.ij.actionId}" else ""}")
VimTestCase.typeText(listOf(usedKey), editor, editor.project)
IdeEventQueue.getInstance().flushQueue()

View File

@ -15,7 +15,6 @@ plugins {
}
val kotlinVersion: String by project
val kotlinxSerializationVersion: String by project
// group 'org.jetbrains.ideavim'
// version 'SNAPSHOT'
@ -36,18 +35,18 @@ afterEvaluate {
}
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.1")
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-test
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
compileOnly("org.jetbrains:annotations:24.1.0")
compileOnly("org.jetbrains:annotations:24.0.1")
ksp(project(":annotation-processors"))
implementation(project(":annotation-processors"))
compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion")
compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.6.0")
}
tasks {

View File

@ -36,7 +36,6 @@ import com.maddyhome.idea.vim.key.Node
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.ReturnTo
import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd
import com.maddyhome.idea.vim.state.mode.returnTo
import java.awt.event.InputEvent
import java.awt.event.KeyEvent
@ -499,7 +498,7 @@ public class KeyHandler {
val text = injector.processGroup.endSearchCommand()
commandBuilder.popCommandPart() // Pop ProcessExEntryAction
commandBuilder.completeCommandPart(Argument(text)) // Set search text on SearchEntry(Fwd|Rev)Action
editorState.mode = editorState.mode.returnTo()
editorState.mode = Mode.NORMAL()
}
}
@ -546,9 +545,7 @@ public class KeyHandler {
// state hits READY. Start the ex input field, push CMD_LINE mode and wait for the argument.
injector.processGroup.startSearchCommand(editor, context, commandBuilder.count, key)
commandBuilder.commandState = CurrentCommandState.NEW_COMMAND
val currentMode = editorState.mode
check(currentMode is ReturnableFromCmd) { "Cannot enable command line mode $currentMode" }
editorState.mode = Mode.CMD_LINE(currentMode)
editorState.mode = Mode.CMD_LINE
}
else -> Unit

View File

@ -17,12 +17,6 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import java.io.InputStream
/**
* An interface defining the contract for providers responsible for reading and parsing JSON files.
* These files contain a list of command beans that are intended to be lazily loaded during runtime.
* The primary functionality of this interface is to transform the JSON data into a collection of
* {@code LazyVimCommand} instances.
*/
public interface CommandProvider {
public val commandListFileName: String
@ -35,7 +29,12 @@ public interface CommandProvider {
.map {
val keys = it.value.map { bean -> injector.parser.parseKeys(bean.keys) }.toSet()
val modes = it.value.first().modes.map { mode -> MappingMode.parseModeChar(mode) }.toSet()
LazyVimCommand(keys, modes, it.key, classLoader)
LazyVimCommand(
keys,
modes,
it.key,
classLoader
)
}
}

View File

@ -35,9 +35,8 @@ import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_START
import com.maddyhome.idea.vim.register.RegisterConstants.LAST_INSERTED_TEXT_REGISTER
import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.ReturnTo
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.state.mode.toReturnTo
import org.jetbrains.annotations.NonNls
import java.awt.event.KeyEvent
import java.util.*
@ -615,7 +614,7 @@ public abstract class VimChangeGroupBase : VimChangeGroup {
* @param editor The editor to put into NORMAL mode for one command
*/
override fun processSingleCommand(editor: VimEditor) {
getInstance(editor).mode = Mode.NORMAL(returnTo = editor.mode.toReturnTo)
getInstance(editor).mode = Mode.NORMAL(returnTo = ReturnTo.INSERT)
clearStrokes(editor)
}

View File

@ -166,10 +166,7 @@ public abstract class VimKeyGroupBase : VimKeyGroup {
private fun registerKeyMapping(fromKeys: List<KeyStroke>, owner: MappingOwner) {
val oldSize = requiredShortcutKeys.size
for (key in fromKeys) {
if (key.keyChar == KeyEvent.CHAR_UNDEFINED &&
!(key.keyCode == KeyEvent.VK_ESCAPE && key.modifiers == 0) &&
!(key.keyCode == KeyEvent.VK_ENTER && key.modifiers == 0)
) {
if (key.keyChar == KeyEvent.CHAR_UNDEFINED && key.keyCode != KeyEvent.VK_ESCAPE && key.keyCode != KeyEvent.VK_ENTER) {
requiredShortcutKeys.add(RequiredShortcut(key, owner))
}
}

View File

@ -334,13 +334,13 @@ public abstract class VimMarkServiceBase : VimMarkService {
val startPosition = selectionInfo.start
var newStartPosition = selectionInfo.start
if (startPosition != null && insStart.line <= startPosition.line) {
if (startPosition != null && insStart.line < startPosition.line) {
newStartPosition = BufferPosition(startPosition.line + lines, startPosition.column, startPosition.leansForward)
}
val endPosition = selectionInfo.end
var newEndPosition = endPosition
if (endPosition != null && insStart.line <= endPosition.line) {
if (endPosition != null && insStart.line < endPosition.line) {
newEndPosition = BufferPosition(endPosition.line + lines, endPosition.column, endPosition.leansForward)
}

View File

@ -13,11 +13,11 @@ import javax.swing.KeyStroke
public interface VimProcessGroup {
public val lastCommand: String?
public fun startSearchCommand(editor: VimEditor, context: ExecutionContext, count: Int, leader: Char)
public fun startSearchCommand(editor: VimEditor, context: ExecutionContext?, count: Int, leader: Char)
public fun endSearchCommand(): String
public fun processExKey(editor: VimEditor, stroke: KeyStroke): Boolean
public fun startFilterCommand(editor: VimEditor, context: ExecutionContext, cmd: Command)
public fun startExCommand(editor: VimEditor, context: ExecutionContext, cmd: Command)
public fun startFilterCommand(editor: VimEditor, context: ExecutionContext?, cmd: Command)
public fun startExCommand(editor: VimEditor, context: ExecutionContext?, cmd: Command)
public fun processExEntry(editor: VimEditor, context: ExecutionContext): Boolean
public fun cancelExEntry(editor: VimEditor, resetCaret: Boolean)

View File

@ -23,7 +23,7 @@ public class VimProcessGroupStub : VimProcessGroupBase() {
override val lastCommand: String
get() = TODO("Not yet implemented")
override fun startSearchCommand(editor: VimEditor, context: ExecutionContext, count: Int, leader: Char) {
override fun startSearchCommand(editor: VimEditor, context: ExecutionContext?, count: Int, leader: Char) {
TODO("Not yet implemented")
}
@ -35,11 +35,11 @@ public class VimProcessGroupStub : VimProcessGroupBase() {
TODO("Not yet implemented")
}
public override fun startFilterCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
override fun startFilterCommand(editor: VimEditor, context: ExecutionContext?, cmd: Command) {
TODO("Not yet implemented")
}
public override fun startExCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
override fun startExCommand(editor: VimEditor, context: ExecutionContext?, cmd: Command) {
TODO("Not yet implemented")
}

View File

@ -11,9 +11,9 @@ package com.maddyhome.idea.vim.helper
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.options
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.options.OptionConstants
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.isSingleModeActive
@ -41,7 +41,7 @@ public val VimEditor.isEndAllowed: Boolean
public fun VimEditor.isEndAllowed(mode: Mode): Boolean {
return when (mode) {
is Mode.INSERT, is Mode.VISUAL, is Mode.SELECT -> true
is Mode.NORMAL, is Mode.CMD_LINE, Mode.REPLACE, is Mode.OP_PENDING -> {
is Mode.NORMAL, Mode.CMD_LINE, Mode.REPLACE, is Mode.OP_PENDING -> {
// One day we'll use a proper insert_normal mode
if (mode.isSingleModeActive) true else usesVirtualSpace
}

View File

@ -8,5 +8,8 @@
package com.maddyhome.idea.vim.helper
@VimNlsSafe
import org.jetbrains.annotations.NonNls
// [VERSION UPDATE] 203+ replace this annotation with @VimNlsSafe
@NonNls
public annotation class VimNlsSafe

View File

@ -237,7 +237,7 @@ public class VimStateMachineImpl(private val editor: VimEditor?) : VimStateMachi
Mode.INSERT, Mode.REPLACE -> MappingMode.INSERT
is Mode.VISUAL -> MappingMode.VISUAL
is Mode.SELECT -> MappingMode.SELECT
is Mode.CMD_LINE -> MappingMode.CMD_LINE
Mode.CMD_LINE -> MappingMode.CMD_LINE
is Mode.OP_PENDING -> MappingMode.OP_PENDING
}
}

View File

@ -251,11 +251,9 @@ public class ToActionMappingInfo(
val editorDataContext = injector.executionContextManager.onEditor(editor, context)
val dataContext = injector.executionContextManager.onCaret(editor.currentCaret(), editorDataContext)
val commandBuilder = editor.vimStateMachine.commandBuilder
for (i in 0 until commandBuilder.count.coerceAtLeast(1)) {
for (i in 0 until editor.vimStateMachine.commandBuilder.count.coerceAtLeast(1)) {
injector.actionExecutor.executeAction(action, dataContext)
}
commandBuilder.resetCount()
}
public companion object {

View File

@ -42,7 +42,6 @@ import javax.swing.KeyStroke
public interface Node<T>
/** Represents a complete command */
// Todo make T LazyVimCommand
public class CommandNode<T>(public val actionHolder: T) : Node<T>
/** Represents a part of the command */

View File

@ -77,7 +77,7 @@ public sealed class ShortcutOwnerInfo {
is Mode.VISUAL -> this.visual
is Mode.SELECT -> this.visual
Mode.INSERT -> this.insert
is Mode.CMD_LINE -> this.normal
Mode.CMD_LINE -> this.normal
is Mode.OP_PENDING -> this.normal
Mode.REPLACE -> this.insert
}

View File

@ -10,9 +10,9 @@ package com.maddyhome.idea.vim.options.helpers
import com.maddyhome.idea.vim.api.Options
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.ex.exExceptionMessage
import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import java.util.*
@ -175,7 +175,7 @@ public enum class GuiCursorMode(public val token: String) {
is Mode.SELECT -> GuiCursorMode.INSERT
is Mode.VISUAL -> GuiCursorMode.VISUAL // TODO: VISUAL_EXCLUSIVE
// This doesn't handle ci and cr, but we don't care - our CMD_LINE will never call this
is Mode.CMD_LINE -> GuiCursorMode.CMD_LINE
Mode.CMD_LINE -> GuiCursorMode.CMD_LINE
}
}
}

View File

@ -27,15 +27,14 @@ import com.maddyhome.idea.vim.state.VimStateMachine
* Also read about how modes work in Vim: https://github.com/JetBrains/ideavim/wiki/how-many-modes-does-vim-have
*/
public sealed interface Mode {
public data class NORMAL(public val returnTo: ReturnTo? = null) : Mode, ReturnableFromCmd
public data class NORMAL(public val returnTo: ReturnTo? = null) : Mode
public data class OP_PENDING(public val returnTo: ReturnTo? = null, public val forcedVisual: SelectionType? = null) :
Mode, ReturnableFromCmd
public data class VISUAL(public val selectionType: SelectionType, public val returnTo: ReturnTo? = null) : Mode,
ReturnableFromCmd
Mode
public data class VISUAL(public val selectionType: SelectionType, public val returnTo: ReturnTo? = null) : Mode
public data class SELECT(public val selectionType: SelectionType, public val returnTo: ReturnTo? = null) : Mode
public object INSERT : Mode
public object REPLACE : Mode
public data class CMD_LINE(public val returnTo: ReturnableFromCmd) : Mode
public object CMD_LINE : Mode
}
public sealed interface ReturnTo {
@ -43,9 +42,6 @@ public sealed interface ReturnTo {
public object REPLACE : ReturnTo
}
// Marks modes that can we return from CMD_LINE mode
public sealed interface ReturnableFromCmd
public enum class SelectionType {
LINE_WISE,
CHARACTER_WISE,

View File

@ -117,55 +117,7 @@ public fun Mode.toVimNotation(): String {
}
Mode.REPLACE -> "R"
is Mode.CMD_LINE -> "c"
Mode.CMD_LINE -> "c"
is Mode.OP_PENDING -> "no"
}
}
public fun Mode.returnTo(): Mode {
return when (this) {
is Mode.CMD_LINE -> {
val returnMode = returnTo as Mode
// We need to understand logic that doesn't exit visual if it's just visual,
// but exits visual if it's one-time visual
if (returnMode.returnTo != null) {
returnMode.returnTo()
} else {
returnMode
}
}
Mode.INSERT -> Mode.NORMAL()
is Mode.NORMAL -> when (returnTo) {
ReturnTo.INSERT -> Mode.INSERT
ReturnTo.REPLACE -> Mode.REPLACE
null -> Mode.NORMAL()
}
is Mode.OP_PENDING -> when (returnTo) {
ReturnTo.INSERT -> Mode.INSERT
ReturnTo.REPLACE -> Mode.REPLACE
null -> Mode.NORMAL()
}
Mode.REPLACE -> Mode.NORMAL()
is Mode.SELECT -> when (returnTo) {
ReturnTo.INSERT -> Mode.INSERT
ReturnTo.REPLACE -> Mode.REPLACE
null -> Mode.NORMAL()
}
is Mode.VISUAL -> when (returnTo) {
ReturnTo.INSERT -> Mode.INSERT
ReturnTo.REPLACE -> Mode.REPLACE
null -> Mode.NORMAL()
}
}
}
public val Mode.toReturnTo: ReturnTo
get() = when (this) {
Mode.INSERT -> ReturnTo.INSERT
Mode.REPLACE -> ReturnTo.REPLACE
else -> error("Cannot get return to from $this")
}

View File

@ -11,13 +11,7 @@ package com.maddyhome.idea.vim.vimscript.model
import java.lang.invoke.MethodHandles
import java.lang.invoke.MethodType
/**
* Abstract class representing a lazily loaded instance of a specified class. The class is dynamically
* loaded and instantiated at runtime, using the provided class name and class loader. This approach is
* useful for deferring the loading and instantiation of a class until it is actually needed, reducing
* initial memory footprint and startup time.
*/
public abstract class LazyInstance<T>(private val className: String, private val classLoader: ClassLoader) {
public open class LazyInstance<T>(private val className: String, private val classLoader: ClassLoader) {
public open val instance: T by lazy {
val aClass = classLoader.loadClass(className)
val lookup = MethodHandles.privateLookupIn(aClass, MethodHandles.lookup())