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

Compare commits

..

15 Commits

Author SHA1 Message Date
fd1a706e4a
Set plugin version to chylex-22 2023-12-03 09:31:23 +01:00
5b2f3e1f12
Automatically add unambiguous imports after running a macro 2023-12-03 09:31:23 +01:00
ec704fc9f9
Prevent code completion popup from appearing after running a macro 2023-12-03 07:27:33 +01:00
ef8955e9d1
Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2023-12-03 04:12:13 +01:00
806f6f8eaa
Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2023-12-03 04:12:12 +01:00
6dcf0f9458
Fix(VIM-3166): Workaround to fix broken filtering of visual lines 2023-12-03 04:12:12 +01:00
8eb201a941
Add support for count for visual and line motion surround 2023-12-03 04:12:12 +01:00
a288feca2a
Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2023-12-03 04:12:12 +01:00
ffe337b145
[VIM-696] Restore visual mode after undo/redo, and disable incompatible actions 2023-12-03 04:12:12 +01:00
ee178e58d0
Respect count with <Action> mappings 2023-12-03 04:12:12 +01:00
12ac424ae0
Prevent IdeaVIM from stealing key binding that confirms in-place refactoring 2023-12-03 04:09:40 +01:00
c96ca2e405
Change matchit plugin to use HTML patterns in unrecognized files 2023-12-03 04:09:40 +01:00
0265432ce5
Reset insert mode when switching active editor 2023-12-03 04:09:39 +01:00
f7e5f15ed2
Remove update checker 2023-12-03 04:09:39 +01:00
68f21ad7c9
Set custom plugin version 2023-12-03 04:09:39 +01:00
145 changed files with 1137 additions and 3083 deletions

View File

@ -8,20 +8,18 @@ jobs:
if: github.repository == 'JetBrains/ideavim'
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- name: Setup Java
uses: actions/setup-java@v4
uses: actions/setup-java@v2.1.0
with:
distribution: zulu
java-version: 11
- name: Setup FFmpeg
uses: FedericoCarboni/setup-ffmpeg@v3
uses: FedericoCarboni/setup-ffmpeg@v1
with:
# Not strictly necessary, but it may prevent rate limit
# errors especially on GitHub-hosted macos machines.
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Gradle
uses: gradle/gradle-build-action@v2.4.2
token: ${{ secrets.GITHUB_TOKEN }}
- name: Build Plugin
run: gradle :buildPlugin
- name: Run Idea
@ -29,7 +27,7 @@ jobs:
mkdir -p build/reports
gradle :runIdeForUiTests > build/reports/idea.log &
- name: Wait for Idea started
uses: jtalk/url-health-check-action@v3
uses: jtalk/url-health-check-action@1.5
with:
url: http://127.0.0.1:8082
max-attempts: 20
@ -37,19 +35,15 @@ jobs:
- name: Tests
run: gradle :testUi
- name: Move video
if: always()
if: ${{ failure() }}
run: mv video build/reports
- name: Move sandbox logs
if: always()
run: mv build/idea-sandbox/system/log sandbox-idea-log
- name: Save report
if: always()
uses: actions/upload-artifact@v4
- name: Save fails report
if: ${{ failure() }}
uses: actions/upload-artifact@v2
with:
name: ui-test-fails-report-mac
path: |
build/reports
sandbox-idea-log
# build-for-ui-test-linux:
# runs-on: ubuntu-latest
# steps:

View File

@ -1,6 +1,6 @@
<component name="CopyrightManager">
<copyright>
<option name="notice" value="Copyright 2003-&amp;#36;today.year The IdeaVim authors&#10;&#10;Use of this source code is governed by an MIT-style&#10;license that can be found in the LICENSE.txt file or at&#10;https://opensource.org/licenses/MIT." />
<option name="notice" value="Copyright 2003-2023 The IdeaVim authors&#10;&#10;Use of this source code is governed by an MIT-style&#10;license that can be found in the LICENSE.txt file or at&#10;https://opensource.org/licenses/MIT." />
<option name="myName" value="IdeaVim" />
</copyright>
</component>

View File

@ -5,13 +5,14 @@ object Constants {
const val EAP_CHANNEL = "eap"
const val DEV_CHANNEL = "Dev"
const val GITHUB_TESTS = "2023.3.2"
const val NVIM_TESTS = "2023.3.2"
const val PROPERTY_TESTS = "2023.3.2"
const val LONG_RUNNING_TESTS = "2023.3.2"
const val QODANA_TESTS = "2023.3.2"
const val RELEASE = "2023.3.2"
// 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 RELEASE_DEV = "2023.3.2"
const val RELEASE_EAP = "2023.3.2"
const val RELEASE_DEV = "LATEST-EAP-SNAPSHOT"
const val RELEASE_EAP = "LATEST-EAP-SNAPSHOT"
}

View File

@ -24,7 +24,6 @@ object Project : Project({
// Active tests
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(TestingBuildType("2023.3", "<default>", version = "2023.3"))
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(PropertyBased)
@ -39,11 +38,6 @@ object Project : Project({
// Common build type for all configurations
abstract class IdeaVimBuildType(init: BuildType.() -> Unit) : BuildType({
artifactRules = """
+:build/reports => build/reports
+:/mnt/agent/temp/buildTmp/ => /mnt/agent/temp/buildTmp/
""".trimIndent()
init()
requirements {

View File

@ -1,29 +0,0 @@
package patches.buildTypes
import jetbrains.buildServer.configs.kotlin.v2019_2.RelativeId
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.GradleBuildStep
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.changeBuildType
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.expectSteps
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.update
/*
This patch script was generated by TeamCity on settings change in UI.
To apply the patch, change the buildType with id = 'IdeaVimTests_Latest_EAP'
accordingly, and delete the patch script.
*/
changeBuildType(RelativeId("IdeaVimTests_Latest_EAP")) {
expectSteps {
gradle {
tasks = "clean test"
buildFile = ""
enableStacktrace = true
}
}
steps {
update<GradleBuildStep>(0) {
clearConditions()
jdkHome = "/usr/lib/jvm/java-17-amazon-corretto"
}
}
}

View File

@ -1,17 +0,0 @@
package patches.projects
import jetbrains.buildServer.configs.kotlin.v2019_2.*
import jetbrains.buildServer.configs.kotlin.v2019_2.Project
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.*
/*
This patch script was generated by TeamCity on settings change in UI.
To apply the patch, change the root project
accordingly, and delete the patch script.
*/
changeProject(DslContext.projectId) {
check(description == "Vim engine for IDEs based on the IntelliJ platform") {
"Unexpected description: '$description'"
}
description = "Vim engine for JetBrains IDEs"
}

View File

@ -487,10 +487,6 @@ Contributors:
[![icon][github]](https://github.com/pWydmuch)
&nbsp;
pWydmuch
* [![icon][mail]](mailto:leonid989@gmail.com)
[![icon][github]](https://github.com/Infonautica)
&nbsp;
Leonid Danilov
Previous contributors:

View File

@ -43,14 +43,9 @@ usual beta standards.
* [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
* [VIM-3085](https://youtrack.jetbrains.com/issue/VIM-3085) Open access to VimTypedActionHandler and VimShortcutKeyAction
### 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…
* [772](https://github.com/JetBrains/ideavim/pull/772) by [chylex](https://github.com/chylex): Prevent code completion popup from appearing after running a macro
* [787](https://github.com/JetBrains/ideavim/pull/787) by [Leonid Danilov](https://github.com/Infonautica): Added "Which-Key" to Plugins
* [778](https://github.com/JetBrains/ideavim/pull/778) by [lippfi](https://github.com/lippfi): Showmode
* [788](https://github.com/JetBrains/ideavim/pull/788) by [Matt Ellis](https://github.com/citizenmatt): Refactor VimOptionGroupBase
## 2.7.0, 2023-11-07

View File

@ -21,7 +21,7 @@ repositories {
}
dependencies {
compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.22-1.0.16")
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")

View File

@ -49,14 +49,14 @@ buildscript {
classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.8.0.202311291450-r")
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.7.0.202309050840-r")
classpath("org.kohsuke:github-api:1.305")
classpath("io.ktor:ktor-client-core:2.3.7")
classpath("io.ktor:ktor-client-cio:2.3.7")
classpath("io.ktor:ktor-client-auth:2.3.7")
classpath("io.ktor:ktor-client-content-negotiation:2.3.7")
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
classpath("io.ktor:ktor-client-core:2.3.6")
classpath("io.ktor:ktor-client-cio:2.3.6")
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")
// This comes from the changelog plugin
// classpath("org.jetbrains:markdown:0.3.1")
@ -69,7 +69,7 @@ plugins {
kotlin("jvm") version "1.8.21"
application
id("org.jetbrains.intellij") version "1.16.1"
id("org.jetbrains.intellij") version "1.16.0"
id("org.jetbrains.changelog") version "2.2.0"
// ktlint linter - read more: https://github.com/JLLeitschuh/ktlint-gradle
@ -126,11 +126,11 @@ dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.1.0")
testImplementation("com.intellij.remoterobot:remote-robot:$remoteRobotVersion")
testImplementation("com.intellij.remoterobot:remote-fixtures:$remoteRobotVersion")
testImplementation("com.automation-remarks:video-recorder-junit5:2.0")
testImplementation("com.automation-remarks:video-recorder-junit:2.0")
runtimeOnly("org.antlr:antlr4-runtime:$antlrVersion")
antlr("org.antlr:antlr4:$antlrVersion")
@ -184,14 +184,6 @@ tasks {
include("**/*test.class")
include("**/*Tests.class")
exclude("**/ParserTest.class")
// Set teamcity env variable locally to run additional tests for leaks.
// By default, this test runs on TC only, but this test doesn't take a lot of time,
// so we can turn it on for local development
if (environment["TEAMCITY_VERSION"] == null) {
println("Set env TEAMCITY_VERSION to X")
environment("TEAMCITY_VERSION" to "X")
}
}
val testWithNeovim by getting(Test::class) {
@ -302,7 +294,6 @@ tasks {
systemProperty("ide.mac.message.dialogs.as.sheets", "false")
systemProperty("jb.privacy.policy.text", "<!--999.999-->")
systemProperty("jb.consents.confirmation.enabled", "false")
systemProperty("ide.show.tips.on.startup.default.value", "false")
}
runPluginVerifier {

View File

@ -396,19 +396,3 @@ Original plugin: [quick-scope](https://github.com/unblevable/quick-scope).
https://plugins.jetbrains.com/plugin/19417-ideavim-quickscope
</details>
<details>
<summary><h2>Which-Key</h2></summary>
Original plugin: [vim-which-key](https://github.com/liuchengxu/vim-which-key).
### Setup:
- Install [Which-Key](https://plugins.jetbrains.com/plugin/15976-which-key) plugin.
- Add the following command to `~/.ideavimrc`: `set which-key`
### Instructions
https://github.com/TheBlob42/idea-which-key?tab=readme-ov-file#installation
</details>

View File

@ -8,12 +8,12 @@
# suppress inspection "UnusedProperty" for whole file
ideaVersion=2023.3.2
ideaVersion=2023.2
downloadIdeaSources=true
instrumentPluginCode=true
version=chylex-23
version=chylex-22
javaVersion=17
remoteRobotVersion=0.11.21
remoteRobotVersion=0.11.17
antlrVersion=4.10.1
kotlin.incremental.useClasspathSnapshot=false

View File

@ -20,17 +20,17 @@ repositories {
}
dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.22")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.21")
implementation("io.ktor:ktor-client-core:2.3.7")
implementation("io.ktor:ktor-client-cio:2.3.7")
implementation("io.ktor:ktor-client-content-negotiation:2.3.7")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
implementation("io.ktor:ktor-client-auth:2.3.7")
implementation("io.ktor:ktor-client-core:2.3.6")
implementation("io.ktor:ktor-client-cio:2.3.6")
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")
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh
implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.8.0.202311291450-r")
implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.7.0.202309050840-r")
implementation("com.vdurmont:semver4j:3.1.0")
}

View File

@ -219,10 +219,6 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
return getInstance().enabled;
}
public static boolean isNotEnabled() {
return !isEnabled();
}
public static void setEnabled(final boolean enabled) {
if (isEnabled() == enabled) return;
@ -236,12 +232,6 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
getInstance().turnOnPlugin();
}
if (enabled) {
VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOn();
} else {
VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOff();
}
StatusBarIconFactory.Util.INSTANCE.updateIcon();
}

View File

@ -28,11 +28,8 @@ import javax.swing.KeyStroke
* Accepts all regular keystrokes and passes them on to the Vim key handler.
*
* IDE shortcut keys used by Vim commands are handled by [com.maddyhome.idea.vim.action.VimShortcutKeyAction].
*
* This class is used in Which-Key plugin, so don't make it internal. Generally, we should provide a proper
* way to get ideavim keys for this plugin. See VIM-3085
*/
public class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandlerEx {
internal class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandlerEx {
private val handler = KeyHandler.getInstance()
private val traceTime = injector.globalOptions().ideatracetime
@ -89,7 +86,7 @@ public class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActio
}
}
internal companion object {
companion object {
private val LOG = logger<VimTypedActionHandler>()
}
}

View File

@ -14,10 +14,14 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.AnActionWrapper
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.actionSystem.KeyboardShortcut
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.application.invokeLater
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.actionSystem.EditorActionManager
import com.intellij.openapi.keymap.KeymapManager
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.util.Key
@ -54,11 +58,8 @@ import javax.swing.KeyStroke
*
*
* These keys are not passed to [com.maddyhome.idea.vim.VimTypedActionHandler] and should be handled by actions.
*
* This class is used in Which-Key plugin, so don't make it internal. Generally, we should provide a proper
* way to get ideavim keys for this plugin. See VIM-3085
*/
public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
private val traceTime: Boolean
get() {
// Make sure the injector is initialized
@ -98,7 +99,7 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
// There is a chance that we can use BGT, but we call for isCell inside the update.
// Not sure if can can use BGT with this call. Let's use EDT for now.
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT
override fun getActionUpdateThread() = ActionUpdateThread.EDT
override fun update(e: AnActionEvent) {
val start = if (traceTime) System.currentTimeMillis() else null
@ -113,7 +114,7 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
}
private fun isEnabled(e: AnActionEvent, keyStroke: KeyStroke?): ActionEnableStatus {
if (VimPlugin.isNotEnabled()) return ActionEnableStatus.no("IdeaVim is disabled", LogLevel.DEBUG)
if (!VimPlugin.isEnabled()) return ActionEnableStatus.no("IdeaVim is disabled", LogLevel.DEBUG)
val editor = getEditor(e)
if (editor != null && keyStroke != null) {
if (isOctopusEnabled(keyStroke, editor)) {
@ -167,6 +168,14 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
return ActionEnableStatus.no("App code template is active", LogLevel.INFO)
}
val nextTemplateVariableShortcuts = KeymapManager.getInstance().activeKeymap.getShortcuts(IdeActions.ACTION_EDITOR_NEXT_TEMPLATE_VARIABLE)
if (nextTemplateVariableShortcuts.any { it is KeyboardShortcut && it.firstKeyStroke == keyStroke }) {
val handler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_NEXT_TEMPLATE_VARIABLE)
if (handler.isEnabled(editor, null, e.dataContext)) {
return ActionEnableStatus.no("Next template variable or finish in-place refactoring", LogLevel.INFO)
}
}
if (editor.inInsertMode) {
if (keyCode == KeyEvent.VK_TAB) {
// TODO: This stops VimEditorTab seeing <Tab> in insert mode and correctly scrolling the view
@ -232,9 +241,9 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
/**
* getDefaultKeyStroke is needed for NEO layout keyboard VIM-987
* but we should cache the value because on the second call (isEnabled -> actionPerformed)
* the event is already consumed and getDefaultKeyStroke returns null
* the event is already consumed
*/
private var keyStrokeCache: Pair<Long?, KeyStroke?> = null to null
private var keyStrokeCache: Pair<KeyEvent?, KeyStroke?> = null to null
private fun getKeyStroke(e: AnActionEvent): KeyStroke? {
val inputEvent = e.inputEvent
@ -242,9 +251,9 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
val defaultKeyStroke = KeyStrokeAdapter.getDefaultKeyStroke(inputEvent)
val strokeCache = keyStrokeCache
if (defaultKeyStroke != null) {
keyStrokeCache = inputEvent.`when` to defaultKeyStroke
keyStrokeCache = inputEvent to defaultKeyStroke
return defaultKeyStroke
} else if (strokeCache.first == inputEvent.`when`) {
} else if (strokeCache.first === inputEvent) {
keyStrokeCache = null to null
return strokeCache.second
}
@ -277,7 +286,7 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
.toSet()
}
internal companion object {
companion object {
@JvmField
val VIM_ONLY_EDITOR_KEYS: Set<KeyStroke> =
ImmutableSet.builder<KeyStroke>().addAll(getKeyStrokes(KeyEvent.VK_ENTER, 0))

View File

@ -18,7 +18,9 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.setChangeMarks
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.common.argumentCaptured
import com.maddyhome.idea.vim.group.MotionGroup
@ -26,9 +28,10 @@ import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.SelectionType
import java.util.*
// todo make it multicaret
private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, selectionType: SelectionType): Boolean {
@ -101,6 +104,8 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
internal class VisualOperatorAction : VisualOperatorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
override fun executeAction(
editor: VimEditor,
caret: VimCaret,

View File

@ -14,10 +14,13 @@ import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.newapi.ijOptions
import java.util.*
/**
* @author vlan
@ -26,6 +29,8 @@ import com.maddyhome.idea.vim.newapi.ijOptions
public class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.DELETE
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
override fun executeForAllCarets(
editor: VimEditor,
context: ExecutionContext,

View File

@ -14,10 +14,13 @@ import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.newapi.ijOptions
import java.util.*
/**
* @author vlan
@ -26,6 +29,8 @@ import com.maddyhome.idea.vim.newapi.ijOptions
public class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.DELETE
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
override fun executeForAllCarets(
editor: VimEditor,
context: ExecutionContext,

View File

@ -9,7 +9,6 @@ package com.maddyhome.idea.vim.extension
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
@ -18,6 +17,7 @@ import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.common.CommandAlias
import com.maddyhome.idea.vim.common.CommandAliasHandler
import com.maddyhome.idea.vim.helper.CommandLineHelper
@ -26,7 +26,6 @@ import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.key.MappingOwner
import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.ui.ModalEntry
import java.awt.event.KeyEvent
import javax.swing.KeyStroke
@ -39,9 +38,6 @@ import javax.swing.KeyStroke
* @author vlan
*/
public object VimExtensionFacade {
private val LOG = logger<VimExtensionFacade>()
/** The 'map' command for mapping keys to handlers defined in extensions. */
@JvmStatic
public fun putExtensionHandlerMapping(
@ -144,12 +140,10 @@ public object VimExtensionFacade {
public fun inputKeyStroke(editor: Editor): KeyStroke {
if (editor.vim.vimStateMachine.isDotRepeatInProgress) {
val input = Extension.consumeKeystroke()
LOG.trace("inputKeyStroke: dot repeat in progress. Input: $input")
return input ?: error("Not enough keystrokes saved: ${Extension.lastExtensionHandler}")
}
val key: KeyStroke? = if (ApplicationManager.getApplication().isUnitTestMode) {
LOG.trace("Unit test mode is active")
val mappingStack = KeyHandler.getInstance().keyStack
mappingStack.feedSomeStroke() ?: TestInputModel.getInstance(editor).nextKeyStroke()?.also {
if (editor.vim.vimStateMachine.isRecording) {
@ -157,13 +151,11 @@ public object VimExtensionFacade {
}
}
} else {
LOG.trace("Getting char from the modal entry...")
var ref: KeyStroke? = null
ModalEntry.activate(editor.vim) { stroke: KeyStroke? ->
ref = stroke
false
}
LOG.trace("Got char $ref")
ref
}
val result = key ?: KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE.toChar())

View File

@ -156,6 +156,11 @@ internal class CommentaryExtension : VimExtension {
private class CommentaryOperatorHandler : OperatorFunction, ExtensionHandler {
override val isRepeatable = true
// In this operator we process selection by ourselves. This is necessary for rider, VIM-1758
override fun postProcessSelection(): Boolean {
return false
}
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
setOperatorFunction(this)
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)

View File

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

View File

@ -8,7 +8,6 @@
package com.maddyhome.idea.vim.extension.surround
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
@ -256,7 +255,6 @@ internal class VimSurroundExtension : VimExtension {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
// Deleting surround is just changing the surrounding to "nothing"
val charFrom = getChar(editor.ij)
LOG.debug("DSurroundHandler: charFrom = $charFrom")
if (charFrom.code == 0) return
runWriteAction { CSurroundHandler.change(editor, context, charFrom, null) }
@ -309,101 +307,96 @@ internal class VimSurroundExtension : VimExtension {
}
}
}
}
private val LOG = logger<VimSurroundExtension>()
companion object {
private const val REGISTER = '"'
private const val REGISTER = '"'
private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
private val SURROUND_PAIRS = mapOf(
'b' to ("(" to ")"),
'(' to ("( " to " )"),
')' to ("(" to ")"),
'B' to ("{" to "}"),
'{' to ("{ " to " }"),
'}' to ("{" to "}"),
'r' to ("[" to "]"),
'[' to ("[ " to " ]"),
']' to ("[" to "]"),
'a' to ("<" to ">"),
'>' to ("<" to ">"),
's' to (" " to ""),
)
private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_PAIRS) {
SURROUND_PAIRS[c]
} else if (!c.isLetter()) {
val s = c.toString()
s to s
} else {
null
}
private fun inputTagPair(editor: Editor): Pair<String, String>? {
val tagInput = inputString(editor, "<", '>')
val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
return if (matcher.find()) {
val tagName = matcher.group(1)
val tagAttributes = matcher.group(2)
"<$tagName$tagAttributes>" to "</$tagName>"
} else {
null
}
}
private fun inputFunctionName(
editor: Editor,
withInternalSpaces: Boolean,
): Pair<String, String>? {
val functionNameInput = inputString(editor, "function: ", null)
if (functionNameInput.isEmpty()) return null
return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
}
private fun getOrInputPair(c: Char, editor: Editor): Pair<String, String>? = when (c) {
'<', 't' -> inputTagPair(editor)
'f' -> inputFunctionName(editor, false)
'F' -> inputFunctionName(editor, true)
else -> getSurroundPair(c)
}
private fun getChar(editor: Editor): Char {
val key = inputKeyStroke(editor)
val keyChar = key.keyChar
val res = if (keyChar == KeyEvent.CHAR_UNDEFINED || keyChar.code == KeyEvent.VK_ESCAPE) {
0.toChar()
} else {
keyChar
}
LOG.trace("getChar: $res")
return res
}
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
runWriteAction {
val editor = caret.editor
val change = VimPlugin.getChange()
val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count)
val isEOF = range.endOffset == editor.text().length
val hasNewLine = editor.endsWithNewLine()
val rightSurround = (if (tagsOnNewLines) {
if (isEOF && !hasNewLine) {
"\n" + pair.second
} else {
pair.second + "\n"
}
} else {
pair.second
}).let { RepeatedCharSequence.of(it, count) }
change.insertText(editor, caret, range.startOffset, leftSurround)
change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)
injector.markService.setChangeMarks(
caret,
TextRange(range.startOffset, range.endOffset + leftSurround.length + rightSurround.length)
private val SURROUND_PAIRS = mapOf(
'b' to ("(" to ")"),
'(' to ("( " to " )"),
')' to ("(" to ")"),
'B' to ("{" to "}"),
'{' to ("{ " to " }"),
'}' to ("{" to "}"),
'r' to ("[" to "]"),
'[' to ("[ " to " ]"),
']' to ("[" to "]"),
'a' to ("<" to ">"),
'>' to ("<" to ">"),
's' to (" " to ""),
)
private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_PAIRS) {
SURROUND_PAIRS[c]
} else if (!c.isLetter()) {
val s = c.toString()
s to s
} else {
null
}
private fun inputTagPair(editor: Editor): Pair<String, String>? {
val tagInput = inputString(editor, "<", '>')
val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
return if (matcher.find()) {
val tagName = matcher.group(1)
val tagAttributes = matcher.group(2)
"<$tagName$tagAttributes>" to "</$tagName>"
} else {
null
}
}
private fun inputFunctionName(
editor: Editor,
withInternalSpaces: Boolean,
): Pair<String, String>? {
val functionNameInput = inputString(editor, "function: ", null)
if (functionNameInput.isEmpty()) return null
return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
}
private fun getOrInputPair(c: Char, editor: Editor): Pair<String, String>? = when (c) {
'<', 't' -> inputTagPair(editor)
'f' -> inputFunctionName(editor, false)
'F' -> inputFunctionName(editor, true)
else -> getSurroundPair(c)
}
private fun getChar(editor: Editor): Char {
val key = inputKeyStroke(editor)
val keyChar = key.keyChar
return if (keyChar == KeyEvent.CHAR_UNDEFINED || keyChar.code == KeyEvent.VK_ESCAPE) {
0.toChar()
} else {
keyChar
}
}
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
runWriteAction {
val editor = caret.editor
val change = VimPlugin.getChange()
val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count)
val isEOF = range.endOffset == editor.text().length
val hasNewLine = editor.endsWithNewLine()
val rightSurround = (if (tagsOnNewLines) {
if (isEOF && !hasNewLine) {
"\n" + pair.second
} else {
pair.second + "\n"
}
} else {
pair.second
}).let { RepeatedCharSequence.of(it, count) }
change.insertText(editor, caret, range.startOffset, leftSurround)
change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)
injector.markService.setChangeMarks(caret, TextRange(range.startOffset, range.endOffset + leftSurround.length + rightSurround.length))
}
}
}
}

View File

@ -20,6 +20,9 @@ import com.intellij.openapi.editor.actions.EnterAction
import com.intellij.openapi.editor.event.EditorMouseEvent
import com.intellij.openapi.editor.event.EditorMouseListener
import com.intellij.openapi.editor.impl.TextRangeInterval
import com.intellij.openapi.ui.MessageType
import com.intellij.openapi.ui.popup.Balloon
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.codeStyle.CodeStyleManager
@ -62,6 +65,7 @@ import com.maddyhome.idea.vim.helper.endOffsetInclusive
import com.maddyhome.idea.vim.helper.inInsertMode
import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
import com.maddyhome.idea.vim.icons.VimIcons
import com.maddyhome.idea.vim.key.KeyHandlerKeeper.Companion.getInstance
import com.maddyhome.idea.vim.listener.VimInsertListener
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
@ -85,6 +89,7 @@ import kotlin.math.min
*/
public class ChangeGroup : VimChangeGroupBase() {
private val insertListeners = ContainerUtil.createLockFreeCopyOnWriteList<VimInsertListener>()
private var lastShownTime = 0L
private val listener: EditorMouseListener = object : EditorMouseListener {
override fun mouseClicked(event: EditorMouseEvent) {
val editor = event.editor
@ -98,6 +103,10 @@ public class ChangeGroup : VimChangeGroupBase() {
EventFacade.getInstance().addEditorMouseListener(editor!!, listener, disposable)
}
public fun editorReleased(editor: Editor?) {
EventFacade.getInstance().removeEditorMouseListener(editor!!, listener)
}
override fun type(vimEditor: VimEditor, context: ExecutionContext, key: Char) {
val editor = (vimEditor as IjVimEditor).editor
val ijContext = context.ij
@ -636,6 +645,25 @@ public class ChangeGroup : VimChangeGroupBase() {
avalanche: Boolean,
): Boolean {
// Just an easter egg
if (avalanche) {
val currentTime = System.currentTimeMillis()
if (currentTime - lastShownTime > 60000) {
lastShownTime = currentTime
ApplicationManager.getApplication().invokeLater {
val balloon = JBPopupFactory.getInstance()
.createHtmlTextBalloonBuilder(
"Wow, nice vim skills!", VimIcons.IDEAVIM,
MessageType.INFO.titleForeground, MessageType.INFO.popupBackground,
null
).createBalloon()
balloon.show(
JBPopupFactory.getInstance().guessBestPopupLocation((editor as IjVimEditor).editor),
Balloon.Position.below
)
}
}
}
val nf: List<String> = injector.options(editor).nrformats
val alpha = nf.contains("alpha")
val hex = nf.contains("hex")

View File

@ -29,8 +29,6 @@ public open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesB
public val lookupkeys: StringListOptionValue by optionProperty(IjOptions.lookupkeys)
public var trackactionids: Boolean by optionProperty(IjOptions.trackactionids)
public var visualdelay: Int by optionProperty(IjOptions.visualdelay)
public var showmodewidget: Boolean by optionProperty(IjOptions.showmodewidget)
public var colorfulmodewidget: Boolean by optionProperty(IjOptions.colorfulmodewidget)
// Temporary options to control work-in-progress behaviour
public var oldundo: Boolean by optionProperty(IjOptions.oldundo)

View File

@ -86,8 +86,6 @@ public object IjOptions {
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isTemporary = true))
public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isTemporary = true))
public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isTemporary = true))
public val showmodewidget: ToggleOption = addOption(ToggleOption("showmodewidget", GLOBAL, "showmodewidget", false, isTemporary = true))
public val colorfulmodewidget: ToggleOption = addOption(ToggleOption("colorfulmodewidget", GLOBAL, "colorfulmodewidget", false, isTemporary = true))
// This needs to be Option<out VimDataType> so that it can work with derived option types, such as NumberOption, which
// derives from Option<VimInt>

View File

@ -64,33 +64,25 @@ internal class MacroGroup : VimMacroBase() {
try {
myPotemkinProgress.text2 = if (isInternalMacro) "Executing internal macro" else ""
val runnable = runnable@{
try {
// Handle one keystroke then queue up the next key
for (i in 0 until total) {
// Handle one keystroke then queue up the next key
for (i in 0 until total) {
myPotemkinProgress.fraction = (i + 1).toDouble() / total
while (keyStack.hasStroke()) {
val key = keyStack.feedStroke()
try {
myPotemkinProgress.fraction = (i + 1).toDouble() / total
while (keyStack.hasStroke()) {
val key = keyStack.feedStroke()
try {
myPotemkinProgress.checkCanceled()
} catch (e: ProcessCanceledException) {
return@runnable
}
ProgressManager.getInstance().executeNonCancelableSection {
// Prevent autocompletion during macros.
// See https://github.com/JetBrains/ideavim/pull/772 for details
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion)
getInstance().handleKey(editor, key, context)
}
if (injector.messages.isError()) return@runnable
}
} finally {
keyStack.resetFirst()
myPotemkinProgress.checkCanceled()
} catch (e: ProcessCanceledException) {
return@runnable
}
ProgressManager.getInstance().executeNonCancelableSection {
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion)
getInstance().handleKey(editor, key, context)
}
if (injector.messages.isError()) return@runnable
}
} finally {
keyStack.removeFirst()
keyStack.resetFirst()
}
keyStack.removeFirst()
if (!isInternalMacro) {
MacroAutoImport.run(editor.ij, context.ij)
}

View File

@ -48,7 +48,9 @@ import com.maddyhome.idea.vim.api.options
import com.maddyhome.idea.vim.api.visualLineToBufferLine
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.MotionType
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.handler.Motion
@ -72,8 +74,6 @@ import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
import org.jetbrains.annotations.Range
import java.io.File
@ -461,13 +461,11 @@ internal class MotionGroup : VimMotionGroupBase() {
val fileEditor = event.oldEditor
if (fileEditor is TextEditor) {
val editor = fileEditor.editor
if (!editor.isDisposed) {
ExOutputModel.getInstance(editor).clear()
editor.vim.let { vimEditor ->
if (VimStateMachine.getInstance(vimEditor).mode is Mode.VISUAL) {
vimEditor.exitVisualMode()
KeyHandler.getInstance().reset(vimEditor)
}
ExOutputModel.getInstance(editor).clear()
editor.vim.let { vimEditor ->
if (VimStateMachine.getInstance(vimEditor).mode is Mode.VISUAL) {
vimEditor.exitVisualMode()
KeyHandler.getInstance().reset(vimEditor)
}
}
}

View File

@ -40,6 +40,10 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
override fun getGlobalIjOptions() = GlobalIjOptions(OptionAccessScope.GLOBAL(null))
override fun getEffectiveIjOptions(editor: VimEditor) = EffectiveIjOptions(OptionAccessScope.EFFECTIVE(editor))
private fun updateFallbackWindow(fallbackWindow: VimEditor, targetEditor: VimEditor) {
copyPerWindowGlobalValues(fallbackWindow, targetEditor)
}
companion object {
fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
// Vim only has one window, and it's not possible to close it. This means that editing a new file will always
@ -54,8 +58,6 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
// Unfortunately, we can't reliably know if a closing editor is the selected editor. Instead, we rely on selection
// change events. If an editor is losing selection and there is no new selection, we can assume this means that
// the last editor has been closed, and use the closed editor to update the fallback window
//
// XXX: event.oldEditor will must probably return a disposed editor. So, it should be treated with care
if (event.newEditor == null) {
(event.oldEditor as? TextEditor)?.editor?.let {
(VimPlugin.getOptionGroup() as OptionGroup).updateFallbackWindow(injector.fallbackWindow, it.vim)
@ -66,7 +68,7 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
}
internal class IjOptionConstants {
@Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate", "ConstPropertyName")
@Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate")
companion object {
const val idearefactormode_keep = "keep"

View File

@ -192,8 +192,8 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
* @param patternOffset The pattern offset, e.g. `/{pattern}/{offset}`
* @param direction The direction to search
*/
@Override
public void setLastSearchState(@SuppressWarnings("unused") @NotNull VimEditor editor, @NotNull String pattern,
@TestOnly
public void setLastSearchState(@SuppressWarnings("unused") @NotNull Editor editor, @NotNull String pattern,
@NotNull String patternOffset, Direction direction) {
setLastUsedPattern(pattern, RE_SEARCH, true);
lastIgnoreSmartCase = false;

View File

@ -205,7 +205,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
* @param event The change event
*/
override fun beforeDocumentChange(event: DocumentEvent) {
if (VimPlugin.isNotEnabled()) return
if (!VimPlugin.isEnabled()) return
if (logger.isDebugEnabled) logger.debug("MarkUpdater before, event = $event")
if (event.oldLength == 0) return
val doc = event.document
@ -221,7 +221,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
* @param event The change event
*/
override fun documentChanged(event: DocumentEvent) {
if (VimPlugin.isNotEnabled()) return
if (!VimPlugin.isEnabled()) return
if (logger.isDebugEnabled) logger.debug("MarkUpdater after, event = $event")
if (event.newLength == 0 || event.newLength == 1 && event.newFragment[0] != '\n') return
val doc = event.document
@ -242,7 +242,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
class VimBookmarksListener(private val myProject: Project) : BookmarksListener {
override fun bookmarkAdded(group: BookmarkGroup, bookmark: Bookmark) {
if (VimPlugin.isNotEnabled()) return
if (!VimPlugin.isEnabled()) return
if (!injector.globalIjOptions().ideamarks) {
return
}
@ -255,7 +255,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
}
override fun bookmarkRemoved(group: BookmarkGroup, bookmark: Bookmark) {
if (VimPlugin.isNotEnabled()) return
if (!VimPlugin.isEnabled()) return
if (!injector.globalIjOptions().ideamarks) {
return
}

View File

@ -27,12 +27,15 @@ import com.maddyhome.idea.vim.api.getLineEndOffset
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.setChangeMarks
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.isBlock
import com.maddyhome.idea.vim.state.mode.isChar
import com.maddyhome.idea.vim.state.mode.isLine
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.diagnostic.debug
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.RWLockLabel
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
import com.maddyhome.idea.vim.ide.isClionNova
import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_POS
import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimEditor
@ -45,10 +48,6 @@ import com.maddyhome.idea.vim.put.PutData
import com.maddyhome.idea.vim.put.VimPasteProvider
import com.maddyhome.idea.vim.put.VimPutBase
import com.maddyhome.idea.vim.register.RegisterConstants
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.isBlock
import com.maddyhome.idea.vim.state.mode.isChar
import com.maddyhome.idea.vim.state.mode.isLine
import java.awt.datatransfer.DataFlavor
internal class PutGroup : VimPutBase() {
@ -190,7 +189,7 @@ internal class PutGroup : VimPutBase() {
endOffset: Int,
): Int {
// Temp fix for VIM-2808. Should be removed after rider will fix it's issues
if (PlatformUtils.isRider() || isClionNova()) return endOffset
if (PlatformUtils.isRider()) return endOffset
val startLine = editor.offsetToBufferPosition(startOffset).line
val endLine = editor.offsetToBufferPosition(endOffset - 1).line

View File

@ -40,15 +40,9 @@ internal object IdeaSelectionControl {
* This method should be in sync with [predictMode]
*
* Control unexpected (non vim) selection change and adjust a mode to it. The new mode is not enabled immediately,
* but with some delay (using [VimVisualTimer]). The delay is used because some platform functionality
* makes features by using selection. E.g. PyCharm unindent firstly select the indenting then applies delete action.
* Such "quick" selection breaks IdeaVim behaviour.
* but with some delay (using [VimVisualTimer])
*
* See [VimVisualTimer] to more info.
*
* XXX: This method can be split into "change calculation" and "change apply". In this way, we would be able
* to calculate if we need to make a change or not and reduce the number of these calls.
* If this refactoring ever is applied, please add `assertNull(VimVisualTimer.timer)` to `tearDown` of VimTestCase.
*/
fun controlNonVimSelectionChange(
editor: Editor,
@ -56,7 +50,6 @@ internal object IdeaSelectionControl {
) {
VimVisualTimer.singleTask(editor.vim.mode) { initialMode ->
if (VimPlugin.isNotEnabled()) return@singleTask
if (editor.isIdeaVimDisabledHere) return@singleTask
logger.debug("Adjust non-vim selection. Source: $selectionSource, initialMode: $initialMode")
@ -128,9 +121,8 @@ internal object IdeaSelectionControl {
}
}
private fun dontChangeMode(editor: Editor): Boolean {
return editor.isTemplateActive() && (editor.vim.isIdeaRefactorModeKeep || editor.vim.mode.hasVisualSelection)
}
private fun dontChangeMode(editor: Editor): Boolean =
editor.isTemplateActive() && (editor.vim.isIdeaRefactorModeKeep || editor.vim.mode.hasVisualSelection)
private fun chooseNonSelectionMode(editor: Editor): Mode {
val templateActive = editor.isTemplateActive()

View File

@ -9,10 +9,10 @@
package com.maddyhome.idea.vim.group.visual
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.group.visual.VimVisualTimer.mode
import com.maddyhome.idea.vim.group.visual.VimVisualTimer.singleTask
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.state.mode.Mode
import java.awt.event.ActionEvent
import javax.swing.Timer
@ -79,11 +79,6 @@ internal object VimVisualTimer {
}
}
fun drop() {
swingTimer?.stop()
swingTimer = null
}
inline fun timerAction(task: (initialMode: Mode?) -> Unit) {
task(mode)
swingTimer = null

View File

@ -11,68 +11,41 @@ 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.components.Service
import com.intellij.openapi.components.service
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.ProjectActivity
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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.launch
import javax.swing.KeyStroke
// We use alarm with delay to avoid many notifications in case many events are fired at the same time
internal val keyCheckRequests = MutableSharedFlow<Unit>(replay=1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
// [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 : ProjectActivity {
override suspend fun execute(project: Project) {
project.service<KeymapCheckerService>().start()
keyCheckRequests.emit(Unit)
}
}
/**
* At the moment of release 2023.3 there is a problem that starting a coroutine like this
* right in the project activity will block this project activity in tests.
* To avoid that, there is an intermediate service that will allow to avoid this issue.
*
* However, in general we should start this coroutine right in the [KeymapChecker]
*/
@OptIn(FlowPreview::class)
@Service(Service.Level.PROJECT)
internal class KeymapCheckerService(private val cs: CoroutineScope) {
fun start() {
cs.launch {
keyCheckRequests
.debounce(5_000)
.collectLatest { verifyKeymap() }
}
internal class KeymapChecker : StartupActivity {
override fun runActivity(project: Project) {
keymapCheckRequester.request()
}
}
internal class IdeaVimKeymapChangedListener : KeymapManagerListener {
override fun activeKeymapChanged(keymap: Keymap?) {
check(keyCheckRequests.tryEmit(Unit))
keymapCheckRequester.request()
}
override fun shortcutChanged(keymap: Keymap, actionId: String) {
check(keyCheckRequests.tryEmit(Unit))
keymapCheckRequester.request()
}
override fun shortcutChanged(keymap: Keymap, actionId: String, fromSettings: Boolean) {
check(keyCheckRequests.tryEmit(Unit))
keymapCheckRequester.request()
}
}

View File

@ -125,7 +125,7 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
}
private fun isThisHandlerEnabled(editor: Editor, caret: Caret?, dataContext: DataContext?): Boolean {
if (VimPlugin.isNotEnabled()) return false
if (!VimPlugin.isEnabled()) return false
if (!isHandlerEnabled(editor, dataContext)) return false
if (isNotActualKeyPress(dataContext)) return false
return true
@ -229,7 +229,7 @@ internal class VimEscHandler(nextHandler: EditorActionHandler) : VimKeyHandler(n
}
/**
* Rider (and CLion Nova) uses a separate handler for esc to close the completion. IdeaOnlyEscapeHandlerAction is especially
* Rider uses a separate handler for esc to close the completion. IdeaOnlyEscapeHandlerAction is especially
* designer to get all the esc presses, and if there is a completion close it and do not pass the execution further.
* This doesn't work the same as in IJ.
* In IdeaVim, we'd like to exit insert mode on closing completion. This is a requirement as the change of this

View File

@ -81,7 +81,7 @@ private fun Editor.guicursorMode(): GuiCursorMode {
private fun isBlockCursorOverride() = EditorSettingsExternalizable.getInstance().isBlockCursor
private fun Editor.updatePrimaryCaretVisualAttributes() {
if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
if (!VimPlugin.isEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
caretModel.primaryCaret.visualAttributes = AttributesCache.getCaretVisualAttributes(this)
// Make sure the caret is visible as soon as it's set. It might be invisible while blinking
@ -89,7 +89,7 @@ private fun Editor.updatePrimaryCaretVisualAttributes() {
}
private fun Editor.updateSecondaryCaretsVisualAttributes() {
if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
if (!VimPlugin.isEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
// IntelliJ simulates visual block with multiple carets with selections. Do our best to hide them
val attributes = if (this.vim.inBlockSelection) HIDDEN else AttributesCache.getCaretVisualAttributes(this)
this.caretModel.allCarets.forEach {

View File

@ -110,7 +110,7 @@ internal fun Editor.isTemplateActive(): Boolean {
}
private fun vimEnabled(editor: Editor?): Boolean {
if (VimPlugin.isNotEnabled()) return false
if (!VimPlugin.isEnabled()) return false
if (editor != null && editor.isIdeaVimDisabledHere) return false
return true
}

View File

@ -143,7 +143,7 @@ internal class IjActionExecutor : VimActionExecutor {
manager.fireAfterActionPerformed(action, event, result!!)
}
if (indexError != null) {
ActionUtil.showDumbModeWarning(project, action, event)
ActionUtil.showDumbModeWarning(project, event)
}
}

View File

@ -124,6 +124,10 @@ internal var Editor.vimMorePanel: ExOutputPanel? by userData()
internal var Editor.vimExOutput: ExOutputModel? by userData()
internal var Editor.vimTestInputModel: TestInputModel? by userData()
/**
* Checks whether a keeping visual mode visual operator action is performed on editor.
*/
internal var Editor.vimKeepingVisualOperatorAction: Boolean by userDataOr { false }
internal var Editor.vimChangeActionSwitchMode: Mode? by userData()
/**

View File

@ -1,25 +0,0 @@
/*
* Copyright 2003-2024 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.ide
import com.intellij.openapi.extensions.ExtensionPointName
internal val clionEP = ExtensionPointName.create<ClionNovaProvider>("IdeaVIM.clionNovaProvider")
internal interface ClionNovaProvider {
fun isClionNova(): Boolean
}
internal class ClionNovaProviderImpl : ClionNovaProvider {
override fun isClionNova(): Boolean = true
}
internal fun isClionNova(): Boolean {
return clionEP.extensions.any { it.isClionNova() }
}

View File

@ -40,7 +40,7 @@ internal object AppCodeTemplates {
private var editor: Editor? = null
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
if (VimPlugin.isNotEnabled()) return
if (!VimPlugin.isEnabled()) return
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
if (hostEditor != null) {
@ -49,7 +49,7 @@ internal object AppCodeTemplates {
}
override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
if (VimPlugin.isNotEnabled()) return
if (!VimPlugin.isEnabled()) return
if (ActionManager.getInstance().getId(action) == IdeActions.ACTION_CHOOSE_LOOKUP_ITEM) {
val myEditor = editor

View File

@ -59,7 +59,7 @@ internal object IdeaSpecifics {
private var completionPrevDocumentLength: Int? = null
private var completionPrevDocumentOffset: Int? = null
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
if (VimPlugin.isNotEnabled()) return
if (!VimPlugin.isEnabled()) return
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
if (hostEditor != null) {
@ -92,7 +92,7 @@ internal object IdeaSpecifics {
}
override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
if (VimPlugin.isNotEnabled()) return
if (!VimPlugin.isEnabled()) return
val editor = editor
if (editor != null && action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) {
@ -138,7 +138,7 @@ internal object IdeaSpecifics {
//region Enter insert mode for surround templates without selection
class VimTemplateManagerListener : TemplateManagerListener {
override fun templateStarted(state: TemplateState) {
if (VimPlugin.isNotEnabled()) return
if (!VimPlugin.isEnabled()) return
val editor = state.editor ?: return
state.addTemplateStateListener(object : TemplateEditingAdapter() {
@ -176,7 +176,7 @@ internal object IdeaSpecifics {
//region Register shortcuts for lookup and perform partial reset
class LookupTopicListener : LookupManagerListener {
override fun activeLookupChanged(oldLookup: Lookup?, newLookup: Lookup?) {
if (VimPlugin.isNotEnabled()) return
if (!VimPlugin.isEnabled()) return
// Lookup opened
if (oldLookup == null && newLookup is LookupImpl) {
@ -199,7 +199,7 @@ internal object IdeaSpecifics {
//region Hide Vim search highlights when showing IntelliJ search results
class VimFindModelListener : FindModelListener {
override fun findNextModelChanged() {
if (VimPlugin.isNotEnabled()) return
if (!VimPlugin.isEnabled()) return
VimPlugin.getSearch().clearSearchHighlight()
}
}

View File

@ -27,7 +27,7 @@ internal class RiderActionListener : AnActionListener {
private var editor: Editor? = null
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
if (VimPlugin.isNotEnabled()) return
if (!VimPlugin.isEnabled()) return
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
if (hostEditor != null) {
@ -36,7 +36,7 @@ internal class RiderActionListener : AnActionListener {
}
override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
if (VimPlugin.isNotEnabled()) return
if (!VimPlugin.isEnabled()) return
//region Extend Selection for Rider
when (ActionManager.getInstance().getId(action)) {

View File

@ -9,7 +9,6 @@
package com.maddyhome.idea.vim.listener
import com.intellij.ide.ui.UISettings
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.trace
@ -29,9 +28,8 @@ import com.intellij.openapi.editor.event.EditorMouseMotionListener
import com.intellij.openapi.editor.event.SelectionEvent
import com.intellij.openapi.editor.event.SelectionListener
import com.intellij.openapi.editor.ex.DocumentEx
import com.intellij.openapi.editor.ex.EditorEventMulticasterEx
import com.intellij.openapi.editor.ex.FocusChangeListener
import com.intellij.openapi.editor.impl.EditorComponentImpl
import com.intellij.openapi.editor.impl.EditorImpl
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
import com.intellij.openapi.fileEditor.FileEditorManagerListener
@ -42,11 +40,14 @@ import com.intellij.openapi.fileEditor.ex.FileEditorWithProvider
import com.intellij.openapi.fileEditor.impl.EditorComposite
import com.intellij.openapi.fileEditor.impl.EditorWindow
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.rd.createLifetime
import com.intellij.openapi.rd.createNestedDisposable
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.removeUserData
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.ExceptionUtil
import com.jetbrains.rd.util.lifetime.Lifetime
import com.maddyhome.idea.vim.EventFacade
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimKeyListener
@ -61,7 +62,6 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.group.EditorGroup
import com.maddyhome.idea.vim.group.FileGroup
import com.maddyhome.idea.vim.group.IjOptions
import com.maddyhome.idea.vim.group.MotionGroup
import com.maddyhome.idea.vim.group.OptionGroup
import com.maddyhome.idea.vim.group.ScrollGroup
@ -71,7 +71,7 @@ import com.maddyhome.idea.vim.group.visual.VimVisualTimer
import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
import com.maddyhome.idea.vim.handler.correctorRequester
import com.maddyhome.idea.vim.handler.keyCheckRequests
import com.maddyhome.idea.vim.handler.keymapCheckRequester
import com.maddyhome.idea.vim.helper.GuicursorChangeListener
import com.maddyhome.idea.vim.helper.StrictMode
import com.maddyhome.idea.vim.helper.exitSelectMode
@ -97,9 +97,6 @@ import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.state.mode.selectionType
import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
import com.maddyhome.idea.vim.ui.widgets.macro.macroWidgetOptionListener
import com.maddyhome.idea.vim.ui.widgets.mode.modeWidgetOptionListener
import com.maddyhome.idea.vim.vimDisposable
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import javax.swing.SwingUtilities
@ -135,7 +132,7 @@ internal object VimListenerManager {
GlobalListeners.enable()
EditorListeners.addAll()
correctorRequester.request()
check(keyCheckRequests.tryEmit(Unit))
keymapCheckRequester.request()
}
fun turnOff() {
@ -159,13 +156,6 @@ internal object VimListenerManager {
optionGroup.addEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
optionGroup.addEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
optionGroup.addGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
// This code is executed after ideavimrc execution, so we trigger onGlobalOptionChanged just in case
optionGroup.addGlobalOptionChangeListener(IjOptions.showmodewidget, modeWidgetOptionListener)
optionGroup.addGlobalOptionChangeListener(IjOptions.showmodewidget, macroWidgetOptionListener)
modeWidgetOptionListener.onGlobalOptionChanged()
macroWidgetOptionListener.onGlobalOptionChanged()
optionGroup.addEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
EventFacade.getInstance().addEditorFactoryListener(VimEditorFactoryListener, VimPlugin.getInstance().onOffDisposable)
@ -173,8 +163,6 @@ internal object VimListenerManager {
busConnection.subscribe(FileOpenedSyncListener.TOPIC, VimEditorFactoryListener)
EditorFactory.getInstance().eventMulticaster.addCaretListener(VimCaretListener, VimPlugin.getInstance().onOffDisposable)
val eventMulticaster = EditorFactory.getInstance().eventMulticaster as? EditorEventMulticasterEx
eventMulticaster?.addFocusChangeListener(VimFocusListener, VimPlugin.getInstance().onOffDisposable)
}
fun disable() {
@ -185,8 +173,6 @@ internal object VimListenerManager {
optionGroup.removeEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
optionGroup.removeEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
optionGroup.removeGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
optionGroup.removeGlobalOptionChangeListener(IjOptions.showmodewidget, modeWidgetOptionListener)
optionGroup.removeGlobalOptionChangeListener(IjOptions.showmodewidget, macroWidgetOptionListener)
optionGroup.removeEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
}
}
@ -229,67 +215,49 @@ internal object VimListenerManager {
}
fun add(editor: Editor, openingEditor: VimEditor, scenario: LocalOptionInitialisationScenario) {
// As I understand, there is no need to pass a disposable that also disposes on editor close
// because all editor resources will be garbage collected anyway on editor close
val disposable = editor.project?.vimDisposable ?: return
val listenersDisposable = Disposer.newDisposable(disposable)
editor.putUserData(editorListenersDisposable, listenersDisposable)
Disposer.register(listenersDisposable) {
if (VimListenerTestObject.enabled) {
VimListenerTestObject.disposedCounter += 1
}
}
val pluginLifetime = VimPlugin.getInstance().createLifetime()
val editorLifetime = (editor as EditorImpl).disposable.createLifetime()
val disposable =
Lifetime.intersect(pluginLifetime, editorLifetime).createNestedDisposable("MyLifetimedDisposable")
editor.contentComponent.addKeyListener(VimKeyListener)
Disposer.register(listenersDisposable) { editor.contentComponent.removeKeyListener(VimKeyListener) }
Disposer.register(disposable) { editor.contentComponent.removeKeyListener(VimKeyListener) }
// Initialise the local options. We MUST do this before anything has the chance to query options
val vimEditor = editor.vim
VimPlugin.getOptionGroup().initialiseLocalOptions(vimEditor, openingEditor, scenario)
VimPlugin.getOptionGroup().initialiseLocalOptions(editor.vim, openingEditor, scenario)
val eventFacade = EventFacade.getInstance()
eventFacade.addEditorMouseListener(editor, EditorMouseHandler, listenersDisposable)
eventFacade.addEditorMouseMotionListener(editor, EditorMouseHandler, listenersDisposable)
eventFacade.addEditorSelectionListener(editor, EditorSelectionHandler, listenersDisposable)
eventFacade.addComponentMouseListener(editor.contentComponent, ComponentMouseListener, listenersDisposable)
eventFacade.addCaretListener(editor, EditorCaretHandler, listenersDisposable)
eventFacade.addEditorMouseListener(editor, EditorMouseHandler, disposable)
eventFacade.addEditorMouseMotionListener(editor, EditorMouseHandler, disposable)
eventFacade.addEditorSelectionListener(editor, EditorSelectionHandler, disposable)
eventFacade.addComponentMouseListener(editor.contentComponent, ComponentMouseListener, disposable)
eventFacade.addCaretListener(editor, EditorCaretHandler, disposable)
VimPlugin.getEditor().editorCreated(editor)
VimPlugin.getChange().editorCreated(editor, listenersDisposable)
VimPlugin.getChange().editorCreated(editor, disposable)
injector.listenersNotifier.notifyEditorCreated(vimEditor)
Disposer.register(listenersDisposable) {
Disposer.register(disposable) {
VimPlugin.getEditorIfCreated()?.editorDeinit(editor, true)
}
}
fun remove(editor: Editor, isReleased: Boolean) {
val editorDisposable = editor.getUserData(editorListenersDisposable)
if (editorDisposable != null) {
Disposer.dispose(editorDisposable)
}
else StrictMode.fail("Editor doesn't have disposable attached. $editor")
editor.contentComponent.removeKeyListener(VimKeyListener)
val eventFacade = EventFacade.getInstance()
eventFacade.removeEditorMouseListener(editor, EditorMouseHandler)
eventFacade.removeEditorMouseMotionListener(editor, EditorMouseHandler)
eventFacade.removeEditorSelectionListener(editor, EditorSelectionHandler)
eventFacade.removeComponentMouseListener(editor.contentComponent, ComponentMouseListener)
eventFacade.removeCaretListener(editor, EditorCaretHandler)
VimPlugin.getEditorIfCreated()?.editorDeinit(editor, isReleased)
VimPlugin.getChange().editorReleased(editor)
}
}
private object VimFocusListener : FocusChangeListener {
override fun focusGained(editor: Editor) {
injector.listenersNotifier.notifyEditorFocusGained(editor.vim)
}
override fun focusLost(editor: Editor) {
injector.listenersNotifier.notifyEditorFocusLost(editor.vim)
}
}
val editorListenersDisposable = Key.create<Disposable>("IdeaVim listeners disposable")
object VimCaretListener : CaretListener {
override fun caretAdded(event: CaretEvent) {
if (vimDisabled(event.editor)) return
@ -304,7 +272,7 @@ internal object VimListenerManager {
class VimFileEditorManagerListener : FileEditorManagerListener {
override fun selectionChanged(event: FileEditorManagerEvent) {
if (VimPlugin.isNotEnabled()) return
if (!VimPlugin.isEnabled()) return
val newEditor = event.newEditor
if (newEditor is TextEditor) {
@ -382,15 +350,13 @@ internal object VimListenerManager {
}
override fun editorReleased(event: EditorFactoryEvent) {
val vimEditor = event.editor.vim
injector.listenersNotifier.notifyEditorReleased(vimEditor)
injector.markService.editorReleased(vimEditor)
injector.markService.editorReleased(event.editor.vim)
}
override fun fileOpenedSync(
source: FileEditorManager,
file: VirtualFile,
editorsWithProviders: List<FileEditorWithProvider>,
editorsWithProviders: List<FileEditorWithProvider>
) {
// This callback is called once all editors are created for a file being opened. The EditorComposite has been
// created (and the list of editors and providers is passed here) and added to an EditorWindow tab, inside a
@ -448,7 +414,6 @@ internal object VimListenerManager {
*/
override fun selectionChanged(selectionEvent: SelectionEvent) {
if (selectionEvent.editor.isIdeaVimDisabledHere) return
VimVisualTimer.drop()
val editor = selectionEvent.editor
val document = editor.document
val ijVimEditor = IjVimEditor(editor)
@ -742,11 +707,6 @@ internal object VimListenerManager {
}
}
internal object VimListenerTestObject {
var enabled: Boolean = false
var disposedCounter = 0
}
private object MouseEventsDataHolder {
const val skipEvents = 3
var skipNDragEvents = skipEvents

View File

@ -54,6 +54,7 @@ import com.maddyhome.idea.vim.helper.isTemplateActive
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
import com.maddyhome.idea.vim.helper.updateCaretsVisualPosition
import com.maddyhome.idea.vim.helper.vimChangeActionSwitchMode
import com.maddyhome.idea.vim.helper.vimKeepingVisualOperatorAction
import com.maddyhome.idea.vim.helper.vimLastSelectionType
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
@ -81,6 +82,11 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
set(value) {
editor.vimChangeActionSwitchMode = value
}
override var vimKeepingVisualOperatorAction: Boolean
get() = editor.vimKeepingVisualOperatorAction
set(value) {
editor.vimKeepingVisualOperatorAction = value
}
override fun fileSize(): Long = editor.fileSize.toLong()

View File

@ -18,7 +18,6 @@ import com.intellij.util.IJSwingUtilities;
import com.maddyhome.idea.vim.KeyHandler;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.ExecutionContext;
import com.maddyhome.idea.vim.diagnostic.VimLogger;
import com.maddyhome.idea.vim.helper.HelperKt;
import com.maddyhome.idea.vim.helper.MessageHelper;
import com.maddyhome.idea.vim.helper.UiHelper;
@ -60,8 +59,6 @@ public class ExOutputPanel extends JPanel {
private boolean myActive = false;
private static final VimLogger LOG = injector.getLogger(ExOutputPanel.class);
private ExOutputPanel(@NotNull Editor editor) {
myEditor = editor;
@ -302,10 +299,6 @@ public class ExOutputPanel extends JPanel {
final KeyStroke key = KeyStroke.getKeyStrokeForEvent(e);
final List<KeyStroke> keys = new ArrayList<>(1);
keys.add(key);
if (LOG.isTrace()) {
LOG.trace("Adding new keys to keyStack as part of playback. State before adding keys: " +
KeyHandler.getInstance().getKeyStack().dump());
}
KeyHandler.getInstance().getKeyStack().addKeys(keys);
ExecutionContext.Editor context = injector.getExecutionContextManager().onEditor(new IjVimEditor(myEditor), null);
VimPlugin.getMacro().playbackKeys(new IjVimEditor(myEditor), context, 1);
@ -365,7 +358,7 @@ public class ExOutputPanel extends JPanel {
public static class LafListener implements LafManagerListener {
@Override
public void lookAndFeelChanged(@NotNull LafManager source) {
if (VimPlugin.isNotEnabled()) return;
if (!VimPlugin.isEnabled()) return;
// Calls updateUI on this and child components
for (Editor editor : HelperKt.localEditors()) {
if (!ExOutputPanel.isPanelActive(editor)) continue;

View File

@ -8,9 +8,6 @@
package com.maddyhome.idea.vim.ui
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.diagnostic.trace
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.helper.isCloseKeyStroke
@ -25,19 +22,13 @@ import javax.swing.KeyStroke
* @author dhleong
*/
public object ModalEntry {
public val LOG: Logger = logger<ModalEntry>()
public inline fun activate(editor: VimEditor, crossinline processor: (KeyStroke) -> Boolean) {
// Firstly we pull the unfinished keys of the current mapping
val mappingStack = KeyHandler.getInstance().keyStack
LOG.trace("Dumping key stack:")
LOG.trace { mappingStack.dump() }
var stroke = mappingStack.feedSomeStroke()
while (stroke != null) {
val result = processor(stroke)
if (!result) {
LOG.trace("Got char from mapping stack")
return
}
stroke = mappingStack.feedSomeStroke()
@ -64,7 +55,6 @@ public object ModalEntry {
KeyHandler.getInstance().modalEntryKeys += stroke
}
if (!processor(stroke)) {
LOG.trace("Got char from keyboard input: $stroke. Event: $e")
KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(this)
loop.exit()
}

View File

@ -19,6 +19,7 @@ import com.intellij.openapi.wm.StatusBarWidget
import com.intellij.openapi.wm.StatusBarWidgetFactory
import com.intellij.openapi.wm.WindowManager
import com.intellij.openapi.wm.impl.status.EditorBasedWidget
import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager
import com.intellij.util.Consumer
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.globalOptions
@ -67,6 +68,13 @@ internal object ShowCmd {
internal object ShowCmdOptionChangeListener : GlobalOptionChangeListener {
override fun onGlobalOptionChanged() {
ShowCmd.update()
val extension = StatusBarWidgetFactory.EP_NAME.findExtension(ShowCmdStatusBarWidgetFactory::class.java) ?: return
val projectManager = ProjectManager.getInstanceIfCreated() ?: return
for (project in projectManager.openProjects) {
val statusBarWidgetsManager = project.getService(StatusBarWidgetsManager::class.java) ?: continue
statusBarWidgetsManager.updateWidget(extension)
}
}
}

View File

@ -453,7 +453,7 @@ public class ExEntryPanel extends JPanel {
public static class LafListener implements LafManagerListener {
@Override
public void lookAndFeelChanged(@NotNull LafManager source) {
if (VimPlugin.isNotEnabled()) return;
if (!VimPlugin.isEnabled()) return;
// Calls updateUI on this and child components
if (ExEntryPanel.isInstanceWithShortcutsActive()) {
IJSwingUtilities.updateComponentTreeUI(ExEntryPanel.getInstance());

View File

@ -1,31 +0,0 @@
/*
* Copyright 2003-2024 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.ui.widgets
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.VimPluginListener
import com.maddyhome.idea.vim.options.GlobalOptionChangeListener
public class VimWidgetListener(private val updateWidget: Runnable) : GlobalOptionChangeListener, VimPluginListener {
init {
injector.listenersNotifier.vimPluginListeners.add(this)
}
override fun onGlobalOptionChanged() {
updateWidget.run()
}
override fun turnedOn() {
updateWidget.run()
}
override fun turnedOff() {
updateWidget.run()
}
}

View File

@ -1,94 +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.ui.widgets.macro
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.wm.StatusBarWidget
import com.intellij.openapi.wm.StatusBarWidgetFactory
import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.MacroRecordingListener
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.ui.widgets.VimWidgetListener
import com.maddyhome.idea.vim.ui.widgets.mode.VimStatusBarWidget
import java.awt.Component
private const val ID = "IdeaVim::Macro"
internal class MacroWidgetFactory : StatusBarWidgetFactory, VimStatusBarWidget {
private var content: String = ""
private val macroRecordingListener = object : MacroRecordingListener {
override fun recordingStarted(editor: VimEditor, register: Char) {
content = "recording @$register"
updateWidgetInStatusBar(ID, editor.ij.project)
}
override fun recordingFinished(editor: VimEditor, register: Char) {
content = ""
updateWidgetInStatusBar(ID, editor.ij.project)
}
}
override fun getId(): String {
return ID
}
override fun getDisplayName(): String {
return "IdeaVim Macro Recording Widget"
}
override fun createWidget(project: Project): StatusBarWidget {
injector.listenersNotifier.macroRecordingListeners.add(macroRecordingListener)
return VimMacroWidget()
}
override fun isAvailable(project: Project): Boolean {
return VimPlugin.isEnabled() && injector.globalIjOptions().showmodewidget
}
private inner class VimMacroWidget : StatusBarWidget {
override fun ID(): String {
return ID
}
override fun getPresentation(): StatusBarWidget.WidgetPresentation {
return VimModeWidgetPresentation()
}
}
private inner class VimModeWidgetPresentation : StatusBarWidget.TextPresentation {
override fun getAlignment(): Float = Component.CENTER_ALIGNMENT
override fun getText(): String {
return content
}
override fun getTooltipText(): String {
return content.ifEmpty {
"No macro recording in progress"
}
}
}
}
public fun updateMacroWidget() {
val factory = StatusBarWidgetFactory.EP_NAME.findExtension(MacroWidgetFactory::class.java) ?: return
for (project in ProjectManager.getInstance().openProjects) {
val statusBarWidgetsManager = project.service<StatusBarWidgetsManager>()
statusBarWidgetsManager.updateWidget(factory)
}
}
public val macroWidgetOptionListener: VimWidgetListener = VimWidgetListener { updateMacroWidget() }

View File

@ -1,45 +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.ui.widgets.mode
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.StatusBarWidget
import com.intellij.openapi.wm.StatusBarWidgetFactory
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.ui.widgets.VimWidgetListener
public class ModeWidgetFactory : StatusBarWidgetFactory {
public companion object {
public const val ID: String = "IdeaVim::Mode"
}
override fun getId(): String {
return ID
}
override fun getDisplayName(): String {
return "IdeaVim Mode Widget"
}
override fun createWidget(project: Project): StatusBarWidget {
return VimModeWidget(project)
}
override fun isAvailable(project: Project): Boolean {
return VimPlugin.isEnabled()
&& injector.globalIjOptions().showmodewidget
&& !project.isDisposed
&& FileEditorManager.getInstance(project).hasOpenFiles()
}
}
public val modeWidgetOptionListener: VimWidgetListener = VimWidgetListener { updateModeWidget() }

View File

@ -1,368 +0,0 @@
/*
* Copyright 2003-2024 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.ui.widgets.mode
import com.intellij.ide.ui.LafManager
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.ui.DialogPanel
import com.intellij.openapi.ui.popup.JBPopup
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.ui.components.JBCheckBox
import com.intellij.ui.components.JBScrollPane
import com.intellij.ui.components.JBTabbedPane
import com.intellij.ui.content.ContentFactory
import com.intellij.ui.dsl.builder.Cell
import com.intellij.ui.dsl.builder.RowLayout
import com.intellij.ui.dsl.builder.TopGap
import com.intellij.ui.dsl.builder.bindItem
import com.intellij.ui.dsl.builder.bindSelected
import com.intellij.ui.dsl.builder.bindText
import com.intellij.ui.dsl.builder.panel
import com.intellij.ui.dsl.builder.selected
import com.intellij.ui.dsl.builder.toNullableProperty
import com.intellij.ui.layout.not
import com.intellij.util.Alarm
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import com.maddyhome.idea.vim.vimscript.model.datatypes.asVimInt
import java.awt.BorderLayout
import java.awt.Dimension
import java.awt.FlowLayout
import javax.swing.BorderFactory
import javax.swing.JButton
import javax.swing.JComponent
import javax.swing.JLabel
import javax.swing.JPanel
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
public class ModeWidgetPopup : AnAction() {
public override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
val popup = createPopup() ?: return
popup.showCenteredInCurrentWindow(project)
}
public companion object {
@Volatile
private var currentPopup: JBPopup? = null
public fun createPopup(): JBPopup? {
synchronized(this) {
if (currentPopup?.isDisposed == false) return null
val mainPanel = JPanel(BorderLayout())
val buttonPanel = JPanel(FlowLayout(FlowLayout.RIGHT))
val applyButton = JButton("Apply").apply { isEnabled = false }
val cancelButton = JButton("Close")
buttonPanel.add(applyButton)
buttonPanel.add(cancelButton)
mainPanel.add(buttonPanel, BorderLayout.SOUTH)
val tabbedPane = JBTabbedPane()
val lightThemeSettings = createPanel(getWidgetThemeColors(true))
val darkThemeSettings = createPanel(getWidgetThemeColors(false))
tabbedPane.addTab(MessageHelper.getMessage("widget.mode.popup.tab.light"), lightThemeSettings.addScrollPane())
tabbedPane.addTab(MessageHelper.getMessage("widget.mode.popup.tab.dark"), darkThemeSettings.addScrollPane())
tabbedPane.preferredSize = Dimension(300, 300)
for (i in 0 until tabbedPane.tabCount) {
val label = JLabel(tabbedPane.getTitleAt(i), JLabel.CENTER)
label.preferredSize = Dimension(126, tabbedPane.getTabComponentAt(i).preferredSize.height)
tabbedPane.setTabComponentAt(i, label)
}
tabbedPane.selectedIndex = if (LafManager.getInstance().currentUIThemeLookAndFeel.isDark) 1 else 0
mainPanel.add(tabbedPane, BorderLayout.CENTER)
val popupContent = ContentFactory.getInstance().createContent(mainPanel, "", false).component
val popup = JBPopupFactory.getInstance()
.createComponentPopupBuilder(popupContent, popupContent)
.setTitle(MessageHelper.getMessage("widget.mode.popup.title"))
.setResizable(true)
.setMovable(true)
.setRequestFocus(true)
.setCancelOnClickOutside(false)
.setCancelKeyEnabled(false)
.createPopup()
applyButton.addActionListener {
lightThemeSettings.apply()
darkThemeSettings.apply()
repaintModeWidget()
}
cancelButton.addActionListener {
popup.cancel()
}
val alarm = Alarm(popup)
fun updateApplyButtonVisibility() {
alarm.addRequest({
applyButton.isEnabled = lightThemeSettings.isModified() || darkThemeSettings.isModified()
updateApplyButtonVisibility()
}, 500L)
}
updateApplyButtonVisibility()
currentPopup = popup
return currentPopup
}
}
private fun getWidgetThemeColors(isLight: Boolean): ModeColors {
val keyPostfix = if (isLight) "_light" else "_dark"
return ModeColors(
"widget_mode_is_full_customization$keyPostfix",
"widget_mode_theme$keyPostfix",
"widget_mode_normal_background$keyPostfix",
"widget_mode_normal_foreground$keyPostfix",
"widget_mode_insert_background$keyPostfix",
"widget_mode_insert_foreground$keyPostfix",
"widget_mode_replace_background$keyPostfix",
"widget_mode_replace_foreground$keyPostfix",
"widget_mode_command_background$keyPostfix",
"widget_mode_command_foreground$keyPostfix",
"widget_mode_visual_background$keyPostfix",
"widget_mode_visual_foreground$keyPostfix",
"widget_mode_visual_line_background$keyPostfix",
"widget_mode_visual_line_foreground$keyPostfix",
"widget_mode_visual_block_background$keyPostfix",
"widget_mode_visual_block_foreground$keyPostfix",
"widget_mode_select_background$keyPostfix",
"widget_mode_select_foreground$keyPostfix",
"widget_mode_select_line_background$keyPostfix",
"widget_mode_select_line_foreground$keyPostfix",
"widget_mode_select_block_background$keyPostfix",
"widget_mode_select_block_foreground$keyPostfix",
)
}
private fun createPanel(modeColors: ModeColors): DialogPanel {
val panel = panel {
lateinit var advancedSettings: Cell<JBCheckBox>
row {
advancedSettings = checkBox(MessageHelper.getMessage("widget.mode.popup.field.advanced.settings")).bindSelected(modeColors::isFullCustomization)
}
group {
row {
label(MessageHelper.getMessage("widget.mode.popup.field.theme"))
comboBox(ModeWidgetTheme.values().toList()).bindItem(modeColors::theme.toNullableProperty())
}
row { browserLink("Suggest your theme", "https://youtrack.jetbrains.com/issue/VIM-1377/Normal-mode-needs-to-be-more-obvious") }
}.topGap(TopGap.NONE).visibleIf(!advancedSettings.selected)
group(MessageHelper.getMessage("widget.mode.popup.group.title.full.customization")) {
row { text(MessageHelper.getMessage("widget.mode.popup.color.instruction")) }
group(MessageHelper.getMessage("widget.mode.popup.group.normal.title")) {
row {
label(MessageHelper.getMessage("widget.mode.popup.field.background"))
textField().bindText(modeColors::normalBg)
}.layout(RowLayout.PARENT_GRID)
row {
label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
textField().bindText(modeColors::normalFg)
}.layout(RowLayout.PARENT_GRID)
}
group(MessageHelper.getMessage("widget.mode.popup.group.insert.title")) {
row {
label(MessageHelper.getMessage("widget.mode.popup.field.background"))
textField().bindText(modeColors::insertBg)
}.layout(RowLayout.PARENT_GRID)
row {
label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
textField().bindText(modeColors::insertFg)
}.layout(RowLayout.PARENT_GRID)
}
group(MessageHelper.getMessage("widget.mode.popup.group.replace.title")) {
row {
label(MessageHelper.getMessage("widget.mode.popup.field.background"))
textField().bindText(modeColors::replaceBg)
}.layout(RowLayout.PARENT_GRID)
row {
label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
textField().bindText(modeColors::replaceFg)
}.layout(RowLayout.PARENT_GRID)
}
group(MessageHelper.getMessage("widget.mode.popup.group.command.title")) {
row {
label(MessageHelper.getMessage("widget.mode.popup.field.background"))
textField().bindText(modeColors::commandBg)
}.layout(RowLayout.PARENT_GRID)
row {
label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
textField().bindText(modeColors::commandFg)
}.layout(RowLayout.PARENT_GRID)
}
group(MessageHelper.getMessage("widget.mode.popup.group.visual.title")) {
row {
label(MessageHelper.getMessage("widget.mode.popup.field.background"))
textField().bindText(modeColors::visualBg)
}.layout(RowLayout.PARENT_GRID)
row {
label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
textField().bindText(modeColors::visualFg)
}.layout(RowLayout.PARENT_GRID)
collapsibleGroup(MessageHelper.getMessage("widget.mode.popup.group.visual.subgroup.line.title")) {
row { text(MessageHelper.getMessage("widget.mode.popup.group.visual.subgroup.instruction")) }
row {
label(MessageHelper.getMessage("widget.mode.popup.field.background"))
textField().bindText(modeColors::visualLineBg)
}.layout(RowLayout.PARENT_GRID)
row {
label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
textField().bindText(modeColors::visualLineFg)
}.layout(RowLayout.PARENT_GRID)
}
collapsibleGroup(MessageHelper.getMessage("widget.mode.popup.group.visual.subgroup.block.title")) {
row { text(MessageHelper.getMessage("widget.mode.popup.group.visual.subgroup.instruction")) }
row {
label(MessageHelper.getMessage("widget.mode.popup.field.background"))
textField().bindText(modeColors::visualBlockBg)
}.layout(RowLayout.PARENT_GRID)
row {
label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
textField().bindText(modeColors::visualBlockFg)
}.layout(RowLayout.PARENT_GRID)
}
}
group(MessageHelper.getMessage("widget.mode.popup.group.select.title")) {
row {
label(MessageHelper.getMessage("widget.mode.popup.field.background"))
textField().bindText(modeColors::selectBg)
}.layout(RowLayout.PARENT_GRID)
row {
label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
textField().bindText(modeColors::selectFg)
}.layout(RowLayout.PARENT_GRID)
collapsibleGroup(MessageHelper.getMessage("widget.mode.popup.group.select.subgroup.line.title")) {
row { text(MessageHelper.getMessage("widget.mode.popup.group.select.subgroup.instruction")) }
row {
label(MessageHelper.getMessage("widget.mode.popup.field.background"))
textField().bindText(modeColors::selectLineBg)
}.layout(RowLayout.PARENT_GRID)
row {
label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
textField().bindText(modeColors::selectLineFg)
}.layout(RowLayout.PARENT_GRID)
}
collapsibleGroup(MessageHelper.getMessage("widget.mode.popup.group.select.subgroup.block.title")) {
row { text(MessageHelper.getMessage("widget.mode.popup.group.select.subgroup.instruction")) }
row {
label(MessageHelper.getMessage("widget.mode.popup.field.background"))
textField().bindText(modeColors::selectBlockBg)
}.layout(RowLayout.PARENT_GRID)
row {
label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
textField().bindText(modeColors::selectBlockFg)
}.layout(RowLayout.PARENT_GRID)
}
}
}.topGap(TopGap.NONE).visibleIf(advancedSettings.selected)
}
return panel
}
private fun JComponent.addScrollPane(): JComponent {
val scrollPane = JBScrollPane(this, JBScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JBScrollPane.HORIZONTAL_SCROLLBAR_NEVER)
scrollPane.border = BorderFactory.createEmptyBorder()
return scrollPane
}
}
private class ModeColors(
isFullCustomizationKey: String, themeKey: String,
normalBgKey: String, normalFgKey: String,
insertBgKey: String, insertFgKey: String,
replaceBgKey: String, replaceFgKey: String,
commandBgKey: String, commandFgKey: String,
visualBgKey: String, visualFgKey: String, visualLineBgKey: String, visualLineFgKey: String, visualBlockBgKey: String, visualBlockFgKey: String,
selectBgKey: String, selectFgKey: String, selectLineBgKey: String, selectLineFgKey: String, selectBlockBgKey: String, selectBlockFgKey: String
) {
var isFullCustomization: Boolean by VimScopeBooleanVariable(isFullCustomizationKey)
var theme: ModeWidgetTheme by VimScopeThemeVariable(themeKey)
var normalBg: String by VimScopeStringVariable(normalBgKey)
var normalFg: String by VimScopeStringVariable(normalFgKey)
var insertBg: String by VimScopeStringVariable(insertBgKey)
var insertFg: String by VimScopeStringVariable(insertFgKey)
var replaceBg: String by VimScopeStringVariable(replaceBgKey)
var replaceFg: String by VimScopeStringVariable(replaceFgKey)
var commandBg: String by VimScopeStringVariable(commandBgKey)
var commandFg: String by VimScopeStringVariable(commandFgKey)
var visualBg: String by VimScopeStringVariable(visualBgKey)
var visualFg: String by VimScopeStringVariable(visualFgKey)
var visualLineBg: String by VimScopeStringVariable(visualLineBgKey)
var visualLineFg: String by VimScopeStringVariable(visualLineFgKey)
var visualBlockBg: String by VimScopeStringVariable(visualBlockBgKey)
var visualBlockFg: String by VimScopeStringVariable(visualBlockFgKey)
var selectBg: String by VimScopeStringVariable(selectBgKey)
var selectFg: String by VimScopeStringVariable(selectFgKey)
var selectLineBg: String by VimScopeStringVariable(selectLineBgKey)
var selectLineFg: String by VimScopeStringVariable(selectLineFgKey)
var selectBlockBg: String by VimScopeStringVariable(selectBlockBgKey)
var selectBlockFg: String by VimScopeStringVariable(selectBlockFgKey)
private class VimScopeBooleanVariable(private var key: String): ReadWriteProperty<ModeColors, Boolean> {
override fun getValue(thisRef: ModeColors, property: KProperty<*>): Boolean {
return injector.variableService.getVimVariable(key)?.asBoolean() ?: false
}
override fun setValue(thisRef: ModeColors, property: KProperty<*>, value: Boolean) {
injector.variableService.storeVimVariable(key, value.asVimInt())
}
}
private class VimScopeStringVariable(private var key: String): ReadWriteProperty<ModeColors, String> {
override fun getValue(thisRef: ModeColors, property: KProperty<*>): String {
return injector.variableService.getVimVariable(key)?.asString() ?: ""
}
override fun setValue(thisRef: ModeColors, property: KProperty<*>, value: String) {
injector.variableService.storeVimVariable(key, VimString(value))
}
}
private class VimScopeThemeVariable(private var key: String): ReadWriteProperty<ModeColors, ModeWidgetTheme> {
override fun getValue(thisRef: ModeColors, property: KProperty<*>): ModeWidgetTheme {
val themeString = injector.variableService.getVimVariable(key)?.asString() ?: return ModeWidgetTheme.getDefaultTheme()
return ModeWidgetTheme.parseString(themeString) ?: ModeWidgetTheme.getDefaultTheme()
}
override fun setValue(thisRef: ModeColors, property: KProperty<*>, value: ModeWidgetTheme) {
injector.variableService.storeVimVariable(key, VimString(value.toString()))
}
}
}
}
public enum class ModeWidgetTheme(private var value: String) {
TEST("Nord-Aurora (testing, will be removed)"),
COLORLESS("Colorless");
override fun toString(): String {
return value
}
public companion object {
public fun parseString(string: String): ModeWidgetTheme? {
return ModeWidgetTheme.values().firstOrNull { it.value == string }
}
public fun getDefaultTheme(): ModeWidgetTheme = TEST
}
}

View File

@ -1,129 +0,0 @@
/*
* Copyright 2003-2024 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.ui.widgets.mode
import com.intellij.ide.ui.LafManager
import com.intellij.util.ui.UIUtil
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import java.awt.Color
public fun getModeBackground(mode: Mode?): Color {
val isLight = !LafManager.getInstance().currentUIThemeLookAndFeel.isDark
val keyPostfix = if (isLight) "_light" else "_dark"
if (injector.variableService.getVimVariable("widget_mode_is_full_customization$keyPostfix")?.asBoolean() != true) {
val themeString = injector.variableService.getVimVariable("widget_mode_theme$keyPostfix")?.asString() ?: ""
val theme = ModeWidgetTheme.parseString(themeString) ?: ModeWidgetTheme.getDefaultTheme()
when (theme) {
ModeWidgetTheme.TEST -> {
return when (mode) {
Mode.INSERT -> Color.decode("#D08770")
Mode.REPLACE -> Color.decode("#EBCB8B")
is Mode.NORMAL -> Color.decode("#BF616A")
is Mode.CMD_LINE -> Color.decode("#A3BE8C")
is Mode.VISUAL -> Color.decode("#B48EAD")
is Mode.SELECT -> Color.decode("#B48EAD")
is Mode.OP_PENDING, null -> UIUtil.getPanelBackground()
}
}
ModeWidgetTheme.COLORLESS -> {
return UIUtil.getPanelBackground()
}
}
} else {
val colorString = when (mode) {
Mode.INSERT -> injector.variableService.getVimVariable("widget_mode_insert_background$keyPostfix")
Mode.REPLACE -> injector.variableService.getVimVariable("widget_mode_replace_background$keyPostfix")
is Mode.NORMAL -> injector.variableService.getVimVariable("widget_mode_normal_background$keyPostfix")
is Mode.CMD_LINE -> injector.variableService.getVimVariable("widget_mode_command_background$keyPostfix")
is Mode.VISUAL -> {
val visualModeBackground = injector.variableService.getVimVariable("widget_mode_visual_background$keyPostfix")
when (mode.selectionType) {
SelectionType.CHARACTER_WISE -> visualModeBackground
SelectionType.LINE_WISE -> injector.variableService.getVimVariable("widget_mode_visual_line_background$keyPostfix") ?: visualModeBackground
SelectionType.BLOCK_WISE -> injector.variableService.getVimVariable("widget_mode_visual_block_background$keyPostfix") ?: visualModeBackground
}
}
is Mode.SELECT -> {
val selectModeBackground = injector.variableService.getVimVariable("widget_mode_select_background$keyPostfix")
when (mode.selectionType) {
SelectionType.CHARACTER_WISE -> selectModeBackground
SelectionType.LINE_WISE -> injector.variableService.getVimVariable("widget_mode_select_line_background$keyPostfix") ?: selectModeBackground
SelectionType.BLOCK_WISE -> injector.variableService.getVimVariable("widget_mode_select_block_background$keyPostfix") ?: selectModeBackground
}
}
is Mode.OP_PENDING, null -> null
}?.asString()
val defaultColor = UIUtil.getPanelBackground()
val color = when (colorString) {
"v:status_bar_bg" -> UIUtil.getPanelBackground()
"v:status_bar_fg" -> UIUtil.getLabelForeground()
else -> {
if (colorString == null) {
defaultColor
} else {
try { Color.decode(colorString) } catch (e: Exception) { defaultColor }
}
}
}
return color
}
}
public fun getModeForeground(mode: Mode?): Color {
val isLight = !LafManager.getInstance().currentUIThemeLookAndFeel.isDark
val keyPostfix = if (isLight) "_light" else "_dark"
if (injector.variableService.getVimVariable("widget_mode_is_full_customization$keyPostfix")?.asBoolean() != true) {
val themeString = injector.variableService.getVimVariable("widget_mode_theme$keyPostfix")?.asString() ?: ""
val theme = ModeWidgetTheme.parseString(themeString) ?: ModeWidgetTheme.getDefaultTheme()
return when (theme) {
ModeWidgetTheme.TEST -> Color.decode("#2E3440")
ModeWidgetTheme.COLORLESS -> UIUtil.getLabelForeground()
}
} else {
val colorString = when (mode) {
Mode.INSERT -> injector.variableService.getVimVariable("widget_mode_insert_foreground$keyPostfix")
Mode.REPLACE -> injector.variableService.getVimVariable("widget_mode_replace_foreground$keyPostfix")
is Mode.NORMAL -> injector.variableService.getVimVariable("widget_mode_normal_foreground$keyPostfix")
is Mode.CMD_LINE -> injector.variableService.getVimVariable("widget_mode_command_foreground$keyPostfix")
is Mode.VISUAL -> {
val visualModeBackground = injector.variableService.getVimVariable("widget_mode_visual_foreground$keyPostfix")
when (mode.selectionType) {
SelectionType.CHARACTER_WISE -> visualModeBackground
SelectionType.LINE_WISE -> injector.variableService.getVimVariable("widget_mode_visual_line_foreground$keyPostfix") ?: visualModeBackground
SelectionType.BLOCK_WISE -> injector.variableService.getVimVariable("widget_mode_visual_block_foreground$keyPostfix") ?: visualModeBackground
}
}
is Mode.SELECT -> {
val selectModeBackground = injector.variableService.getVimVariable("widget_mode_select_foreground$keyPostfix")
when (mode.selectionType) {
SelectionType.CHARACTER_WISE -> selectModeBackground
SelectionType.LINE_WISE -> injector.variableService.getVimVariable("widget_mode_select_line_foreground$keyPostfix") ?: selectModeBackground
SelectionType.BLOCK_WISE -> injector.variableService.getVimVariable("widget_mode_select_block_foreground$keyPostfix") ?: selectModeBackground
}
}
is Mode.OP_PENDING, null -> null
}?.asString()
val defaultColor = UIUtil.getLabelForeground()
val color = when (colorString) {
"v:status_bar_bg" -> UIUtil.getPanelBackground()
"v:status_bar_fg" -> UIUtil.getLabelForeground()
else -> {
if (colorString == null) {
defaultColor
} else {
try { Color.decode(colorString) } catch (e: Exception) { defaultColor }
}
}
}
return color
}
}

View File

@ -1,176 +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.ui.widgets.mode
import com.intellij.openapi.components.service
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.wm.CustomStatusBarWidget
import com.intellij.openapi.wm.StatusBarWidgetFactory
import com.intellij.openapi.wm.WindowManager
import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager
import com.intellij.ui.awt.RelativePoint
import com.intellij.ui.components.JBLabel
import com.intellij.ui.util.width
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetFocusListener
import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetModeListener
import java.awt.Dimension
import java.awt.Point
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import javax.swing.JComponent
import kotlin.math.max
public class VimModeWidget(public val project: Project) : CustomStatusBarWidget, VimStatusBarWidget {
private companion object {
private const val INSERT = "INSERT"
private const val NORMAL = "NORMAL"
private const val REPLACE = "REPLACE"
private const val COMMAND = "COMMAND"
private const val VISUAL = "VISUAL"
private const val VISUAL_LINE = "V-LINE"
private const val VISUAL_BLOCK = "V-BLOCK"
private const val SELECT = "SELECT"
private const val SELECT_LINE = "S-LINE"
private const val SELECT_BLOCK = "S-BLOCK"
}
private val useColors = injector.globalIjOptions().colorfulmodewidget
private val label = JBLabelWiderThan(setOf(REPLACE)).apply {
isOpaque = useColors
}
init {
val mode = getFocusedEditor(project)?.vim?.mode
updateLabel(mode)
injector.listenersNotifier.apply {
modeChangeListeners.add(ModeWidgetModeListener(this@VimModeWidget))
myEditorListeners.add(ModeWidgetFocusListener(this@VimModeWidget))
}
label.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
val popup = ModeWidgetPopup.createPopup() ?: return
val dimension = popup.content.preferredSize
val widgetLocation = e.component.locationOnScreen
popup.show(RelativePoint(Point(
widgetLocation.x + e.component.width - dimension.width,
widgetLocation.y - dimension.height,
)))
}
})
}
override fun ID(): String {
return ModeWidgetFactory.ID
}
override fun getComponent(): JComponent {
return label
}
public fun updateWidget() {
val mode = getFocusedEditor(project)?.vim?.mode
updateWidget(mode)
}
public fun updateWidget(mode: Mode?) {
updateLabel(mode)
updateWidgetInStatusBar(ModeWidgetFactory.ID, project)
}
private fun updateLabel(mode: Mode?) {
label.text = getModeText(mode)
if (useColors) {
label.foreground = getModeForeground(mode)
label.background = getModeBackground(mode)
}
}
private fun getFocusedEditor(project: Project): Editor? {
val fileEditorManager = FileEditorManager.getInstance(project)
return fileEditorManager.selectedTextEditor
}
private fun getModeText(mode: Mode?): String? {
return when (mode) {
Mode.INSERT -> INSERT
Mode.REPLACE -> REPLACE
is Mode.NORMAL -> NORMAL
is Mode.CMD_LINE -> COMMAND
is Mode.VISUAL -> getVisualModeText(mode)
is Mode.SELECT -> getSelectModeText(mode)
is Mode.OP_PENDING, null -> null
}
}
private fun getVisualModeText(mode: Mode.VISUAL) = when (mode.selectionType) {
SelectionType.CHARACTER_WISE -> VISUAL
SelectionType.LINE_WISE -> VISUAL_LINE
SelectionType.BLOCK_WISE -> VISUAL_BLOCK
}
private fun getSelectModeText(mode: Mode.SELECT) = when (mode.selectionType) {
SelectionType.CHARACTER_WISE -> SELECT
SelectionType.LINE_WISE -> SELECT_LINE
SelectionType.BLOCK_WISE -> SELECT_BLOCK
}
private class JBLabelWiderThan(private val words: Collection<String>): JBLabel("", CENTER) {
private val wordWidth: Int
get() {
val fontMetrics = getFontMetrics(font)
return words.maxOfOrNull { fontMetrics.stringWidth(it) } ?: 0
}
override fun getMinimumSize(): Dimension {
val minimumSize = super.getMinimumSize()
return Dimension(max(minimumSize.width, wordWidth + insets.width), minimumSize.height)
}
override fun getPreferredSize(): Dimension {
val preferredSize = super.getPreferredSize()
return Dimension(max(preferredSize.width, wordWidth + insets.width), preferredSize.height)
}
override fun getMaximumSize(): Dimension {
val maximumSize = super.getMaximumSize()
return Dimension(max(maximumSize.width, wordWidth + insets.width), maximumSize.height)
}
}
}
public fun updateModeWidget() {
val factory = StatusBarWidgetFactory.EP_NAME.findExtension(ModeWidgetFactory::class.java) ?: return
for (project in ProjectManager.getInstance().openProjects) {
val statusBarWidgetsManager = project.service<StatusBarWidgetsManager>()
statusBarWidgetsManager.updateWidget(factory)
}
}
public fun repaintModeWidget() {
for (project in ProjectManager.getInstance().openProjects) {
val widgets = WindowManager.getInstance()?.getStatusBar(project)?.allWidgets ?: continue
for (widget in widgets) {
if (widget is VimModeWidget) {
widget.updateWidget()
}
}
}
}

View File

@ -1,59 +0,0 @@
/*
* Copyright 2003-2024 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.ui.widgets.mode
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.WindowManager
import java.util.*
public interface VimStatusBarWidget {
public fun updateWidgetInStatusBar(widgetID: String, project: Project?) {
if (project == null) return
val windowManager = WindowManager.getInstance()
windowManager.getStatusBar(project)?.updateWidget(widgetID) ?: run {
TimerWithRetriesTask(500L, 50) {
val statusBar = windowManager.getStatusBar(project) ?: return@TimerWithRetriesTask false
statusBar.updateWidget(widgetID)
return@TimerWithRetriesTask true
}.execute()
}
}
}
/**
* A task that may be used to address issues with initialization in the Platform, executing code with a reasonable number of retries and a reasonable period.
* Clearly, this is a workaround and its use should be avoided when possible.
*
* Why is it needed for widgets?
* In a single project environment, it is not necessary since the status bar is initialized before the editor opens.
* However, in multi-project setups, the editor window is opened before the status bar initialization.
* And this tasks tries to loops until status bar creation in order to notify it about opened editor.
*/
private class TimerWithRetriesTask(
private val period: Long,
private val retriesLimit: Int,
private val block: () -> Boolean,
) {
private val timer = Timer()
fun execute() {
timer.schedule(object : TimerTask() {
private var counter = 0
override fun run() {
if (counter >= retriesLimit) {
timer.cancel()
} else {
if (this@TimerWithRetriesTask.block()) timer.cancel()
counter++
}
}
}, 0L, period)
}
}

View File

@ -1,53 +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.ui.widgets.mode.listeners
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.project.Project
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.common.EditorListener
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.ui.widgets.mode.VimModeWidget
import com.maddyhome.idea.vim.ui.widgets.mode.updateModeWidget
internal class ModeWidgetFocusListener(private val modeWidget: VimModeWidget): EditorListener {
override fun created(editor: VimEditor) {
updateModeWidget()
val mode = getFocusedEditorForProject(editor.ij.project)?.vim?.mode
modeWidget.updateWidget(mode)
}
override fun released(editor: VimEditor) {
updateModeWidget()
val focusedEditor = getFocusedEditorForProject(editor.ij.project)
if (focusedEditor == null || focusedEditor == editor.ij) {
modeWidget.updateWidget(null)
}
}
override fun focusGained(editor: VimEditor) {
if (editor.ij.project != modeWidget.project) return
val mode = editor.mode
modeWidget.updateWidget(mode)
}
override fun focusLost(editor: VimEditor) {
val mode = getFocusedEditorForProject(editor.ij.project)?.vim?.mode
modeWidget.updateWidget(mode)
}
private fun getFocusedEditorForProject(editorProject: Project?): Editor? {
if (editorProject != modeWidget.project) return null
val fileEditorManager = FileEditorManager.getInstance(editorProject)
return fileEditorManager.selectedTextEditor
}
}

View File

@ -1,25 +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.ui.widgets.mode.listeners
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.common.ModeChangeListener
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.ui.widgets.mode.VimModeWidget
internal class ModeWidgetModeListener(private val modeWidget: VimModeWidget): ModeChangeListener {
override fun modeChanged(editor: VimEditor, oldMode: Mode) {
val editorMode = editor.mode
if (editorMode !is Mode.OP_PENDING && editor.ij.project == modeWidget.project) {
modeWidget.updateWidget(editorMode)
}
}
}

View File

@ -36,28 +36,31 @@ public val VimEditor.isIdeaRefactorModeSelect: Boolean
internal object IdeaRefactorModeHelper {
sealed interface Action {
object RemoveSelection : Action
class SetMode(val newMode: Mode) : Action
class MoveToOffset(val newOffset: Int) : Action
}
fun applyCorrections(corrections: List<Action>, editor: Editor) {
val correctionsApplier = {
corrections.forEach { correction ->
when (correction) {
is Action.MoveToOffset -> {
editor.caretModel.moveToOffset(correction.newOffset)
fun correctSelection(editor: Editor) {
val action: () -> Unit = {
val mode = editor.vim.mode
if (!mode.hasVisualSelection && editor.selectionModel.hasSelection()) {
SelectionVimListenerSuppressor.lock().use {
editor.selectionModel.removeSelection()
}
}
if (mode.hasVisualSelection && editor.selectionModel.hasSelection()) {
val autodetectedSubmode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor.vim)
if (mode.selectionType != autodetectedSubmode) {
// Update the submode
val newMode = when (mode) {
is Mode.SELECT -> mode.copy(selectionType = autodetectedSubmode)
is Mode.VISUAL -> mode.copy(selectionType = autodetectedSubmode)
else -> error("IdeaVim should be either in visual or select modes")
}
editor.vim.vimStateMachine.mode = newMode
}
}
Action.RemoveSelection -> {
SelectionVimListenerSuppressor.lock().use {
editor.selectionModel.removeSelection()
}
}
is Action.SetMode -> {
editor.vim.vimStateMachine.mode = correction.newMode
if (editor.hasBlockOrUnderscoreCaret()) {
TemplateManagerImpl.getTemplateState(editor)?.currentVariableRange?.let { segmentRange ->
if (!segmentRange.isEmpty && segmentRange.endOffset == editor.caretModel.offset && editor.caretModel.offset != 0) {
editor.caretModel.moveToOffset(editor.caretModel.offset - 1)
}
}
}
@ -67,9 +70,7 @@ internal object IdeaRefactorModeHelper {
if (lookup != null) {
val selStart = editor.selectionModel.selectionStart
val selEnd = editor.selectionModel.selectionEnd
lookup.performGuardedChange {
correctionsApplier()
}
lookup.performGuardedChange(action)
lookup.addLookupListener(object : LookupListener {
override fun beforeItemSelected(event: LookupEvent): Boolean {
// FIXME: 01.11.2019 Nasty workaround because of problems in IJ platform
@ -81,41 +82,7 @@ internal object IdeaRefactorModeHelper {
}
})
} else {
correctionsApplier()
action()
}
}
fun calculateCorrections(editor: Editor): List<Action> {
val corrections = mutableListOf<Action>()
val mode = editor.vim.mode
if (!mode.hasVisualSelection && editor.selectionModel.hasSelection()) {
corrections.add(Action.RemoveSelection)
}
if (mode.hasVisualSelection && editor.selectionModel.hasSelection()) {
val autodetectedSubmode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor.vim)
if (mode.selectionType != autodetectedSubmode) {
// Update the submode
val newMode = when (mode) {
is Mode.SELECT -> mode.copy(selectionType = autodetectedSubmode)
is Mode.VISUAL -> mode.copy(selectionType = autodetectedSubmode)
else -> error("IdeaVim should be either in visual or select modes")
}
corrections.add(Action.SetMode(newMode))
}
}
if (editor.hasBlockOrUnderscoreCaret()) {
TemplateManagerImpl.getTemplateState(editor)?.currentVariableRange?.let { segmentRange ->
if (!segmentRange.isEmpty && segmentRange.endOffset == editor.caretModel.offset && editor.caretModel.offset != 0) {
corrections.add(Action.MoveToOffset(editor.caretModel.offset - 1))
}
}
}
return corrections
}
fun correctSelection(editor: Editor) {
val corrections = calculateCorrections(editor)
applyCorrections(corrections, editor)
}
}

View File

@ -8,10 +8,6 @@
package com.maddyhome.idea.vim.vimscript.services
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.RoamingType
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.ex.vimscript.VimScriptGlobalEnvironment
@ -26,10 +22,8 @@ import com.maddyhome.idea.vim.vimscript.model.datatypes.VimList
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import com.maddyhome.idea.vim.vimscript.model.expressions.Scope
import com.maddyhome.idea.vim.vimscript.model.expressions.Variable
import org.jdom.Element
@State(name = "VimVariables", storages = [Storage(value = "\$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)])
internal class IjVariableService : VimVariableServiceBase(), PersistentStateComponent<Element?> {
internal class IjVariableService : VimVariableServiceBase() {
override fun storeVariable(variable: Variable, value: VimDataType, editor: VimEditor, context: ExecutionContext, vimContext: VimLContext) {
super.storeVariable(variable, value, editor, context, vimContext)
@ -53,49 +47,4 @@ internal class IjVariableService : VimVariableServiceBase(), PersistentStateComp
else -> error("Unexpected")
}
}
override fun getState(): Element {
val element = Element("variables")
saveData(element)
return element
}
override fun loadState(state: Element) {
readData(state)
}
private fun saveData(element: Element) {
val vimVariablesElement = Element("vim-variables")
for ((key, value) in vimVariables.entries) {
if (value is VimString) {
val variableElement = Element("variable")
variableElement.setAttribute("key", key)
variableElement.setAttribute("value", value.value)
variableElement.setAttribute("type", "string")
vimVariablesElement.addContent(variableElement)
} else if (value is VimInt) {
val variableElement = Element("variable")
variableElement.setAttribute("key", key)
variableElement.setAttribute("value", value.value.toString())
variableElement.setAttribute("type", "int")
vimVariablesElement.addContent(variableElement)
}
}
element.addContent(vimVariablesElement)
}
private fun readData(element: Element) {
val vimVariablesElement = element.getChild("vim-variables")
val variableElements = vimVariablesElement.getChildren("variable")
for (variableElement in variableElements) {
when (variableElement.getAttributeValue("type")) {
"string" -> {
vimVariables[variableElement.getAttributeValue("key")] = VimString(variableElement.getAttributeValue("value"))
}
"int" -> {
vimVariables[variableElement.getAttributeValue("key")] = VimInt(variableElement.getAttributeValue("value"))
}
}
}
}
}

View File

@ -1,19 +0,0 @@
<!--
~ Copyright 2003-2024 The IdeaVim authors
~
~ Use of this source code is governed by an MIT-style
~ license that can be found in the LICENSE.txt file or at
~ https://opensource.org/licenses/MIT.
-->
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<editorActionHandler action="EditorEscape"
implementationClass="com.maddyhome.idea.vim.handler.VimEscForRiderHandler"
id="ideavim-clion-nova-esc"
order="first, before idea.only.escape"/>
</extensions>
<extensions defaultExtensionNs="IdeaVIM">
<clionNovaProvider implementation="com.maddyhome.idea.vim.ide.ClionNovaProviderImpl"/>
</extensions>
</idea-plugin>

View File

@ -29,8 +29,6 @@
<!--suppress PluginXmlValidity -->
<depends optional="true" config-file="ides/ideavim-withRider.xml">com.intellij.modules.rider</depends>
<!--suppress PluginXmlValidity -->
<depends optional="true" config-file="ides/ideavim-withClionNova.xml">org.jetbrains.plugins.clion.radler</depends>
<!--suppress PluginXmlValidity -->
<depends optional="true" config-file="ides/ideavim-withAppCode.xml">com.intellij.modules.appcode</depends>
<depends optional="true" config-file="ideavim-withAceJump.xml">AceJump</depends>
@ -57,8 +55,6 @@
<extensionPoint name="vimAction" beanClass="com.maddyhome.idea.vim.handler.ActionBeanClass" dynamic="true">
<with attribute="implementation" implements="com.maddyhome.idea.vim.handler.EditorActionHandlerBase"/>
</extensionPoint>
<extensionPoint interface="com.maddyhome.idea.vim.ide.ClionNovaProvider" dynamic="true" name="clionNovaProvider"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
@ -66,9 +62,7 @@
<projectService serviceImplementation="com.maddyhome.idea.vim.group.NotificationService"/>
<projectService serviceImplementation="com.maddyhome.idea.vim.group.LastTabService"/>
<statusBarWidgetFactory id="IdeaVim-Icon" implementation="com.maddyhome.idea.vim.ui.StatusBarIconFactory"/>
<statusBarWidgetFactory id="IdeaVim::Mode" implementation="com.maddyhome.idea.vim.ui.widgets.mode.ModeWidgetFactory" order="last"/>
<statusBarWidgetFactory id="IdeaVim::ShowCmd" implementation="com.maddyhome.idea.vim.ui.ShowCmdStatusBarWidgetFactory" order="first"/>
<statusBarWidgetFactory id="IdeaVim::Macro" implementation="com.maddyhome.idea.vim.ui.widgets.macro.MacroWidgetFactory"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.VimPlugin"/>
@ -152,7 +146,6 @@
<action id="VimShortcutKeyAction" class="com.maddyhome.idea.vim.action.VimShortcutKeyAction"/>
<action id="VimActions" class="com.maddyhome.idea.vim.ui.VimActions"/>
<action id="CustomizeModeWidget" class="com.maddyhome.idea.vim.ui.widgets.mode.ModeWidgetPopup"/>
<group id="IdeaVim.ReloadVimRc.group" class="com.maddyhome.idea.vim.ui.ReloadFloatingToolbarActionGroup">
<action id="IdeaVim.ReloadVimRc.reload" class="com.maddyhome.idea.vim.ui.ReloadVimRc"

View File

@ -84,8 +84,6 @@ action.VimShortcutKeyAction.text=Shortcuts
action.VimActions.text=Vim Actions
action.not.found.0=Action not found: {0}
action.CustomizeModeWidget.text=Mode Widget Settings
action.VimFindActionIdAction.text=IdeaVim: Track Action Ids
action.VimFindActionIdAction.description=Starts tracking ids of executed actions
@ -131,28 +129,6 @@ action.finish.eap.text=Finish EAP
# Don't forget to update README if you modify this entry
action.subscribe.to.eap.text=Subscribe to EAP
widget.mode.popup.title=Mode Widget Colors
widget.mode.popup.tab.light=Light Theme
widget.mode.popup.tab.dark=Dark Theme
widget.mode.popup.color.instruction=Use HEX color values for exact colors; use v:status_bar_bg to use your IDE's status bar background color and v:status_bar_fg for the foreground
widget.mode.popup.field.theme=Widget theme:
widget.mode.popup.field.advanced.settings=Full color customization (advanced)
widget.mode.popup.group.title.full.customization=Full customization
widget.mode.popup.group.normal.title=Normal Mode
widget.mode.popup.group.insert.title=Insert Mode
widget.mode.popup.group.replace.title=Replace Mode
widget.mode.popup.group.command.title=Command Mode
widget.mode.popup.group.visual.title=Visual Mode
widget.mode.popup.group.visual.subgroup.instruction=Leave fields empty to inherit colors from Visual Mode
widget.mode.popup.group.visual.subgroup.line.title=Visual Line
widget.mode.popup.group.visual.subgroup.block.title=Visual Block
widget.mode.popup.group.select.title=Select Mode
widget.mode.popup.group.select.subgroup.instruction=Leave fields empty to inherit colors from Select Mode
widget.mode.popup.group.select.subgroup.line.title=Select Line
widget.mode.popup.group.select.subgroup.block.title=Select Block
widget.mode.popup.field.background=Background:
widget.mode.popup.field.foreground=Text:
configurable.name.vim.emulation=Vim
configurable.keyhandler.link=<html>Use <a>sethandler</a> command to configure handlers from the .ideavimrc file</html>
configurable.noneditablehandler.helper.text.with.example=Non-editable handlers are defined in .ideavimrc file. E.g. ''{0}'' for {1}.

View File

@ -1,37 +0,0 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.ideavim
import com.intellij.testFramework.LoggedErrorProcessor
import com.intellij.testFramework.TestLoggerFactory.TestLoggerAssertionError
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.fail
/**
* By default, LOG.error does three things in tests:
* - rethrows the exception
* - logs error
* - prints to stderr
*
* The problem is that if we catch exception in tests, such an approach will print the exception to stderr and it will
* look like the exception is not processed.
* I don't see a need for printing these caught exceptions, so we can use this processor to only rethrow them.
*/
internal object OnlyThrowLoggedErrorProcessor : LoggedErrorProcessor() {
override fun processError(category: String, message: String, details: Array<out String>, t: Throwable?): Set<Action> {
return setOf(Action.RETHROW)
}
}
/**
* Asserts that [T] was thrown via `LOG.error("message", e)` call where `e` has a type of [T].
*/
internal inline fun <reified T: Throwable> assertThrowsLogError(crossinline action: () -> Unit): T {
val exception = assertThrows<TestLoggerAssertionError> {
LoggedErrorProcessor.executeWith<Throwable>(OnlyThrowLoggedErrorProcessor) {
action()
}
}
val cause = exception.cause
if (cause !is T) fail("Expected ${T::class.java} exception in LOG.error, but got $cause")
return cause
}

View File

@ -14,17 +14,12 @@ import com.intellij.openapi.editor.LogicalPosition
import com.intellij.testFramework.EditorTestUtil
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
import com.intellij.util.containers.toArray
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.key.MappingOwner
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.mode
import org.junit.jupiter.params.provider.Arguments
import kotlin.test.fail
@ -134,15 +129,3 @@ internal fun <T> product(vararg elements: List<T>): List<List<T>> {
}
return res
}
internal class ExceptionHandler : ExtensionHandler {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
error(exceptionMessage)
}
companion object {
internal const val exceptionMessage = "Exception here"
}
}
internal val exceptionMappingOwner = MappingOwner.Plugin.get("Exception mapping owner")

View File

@ -123,7 +123,7 @@ abstract class VimTestCase {
VimPlugin.getOptionGroup().resetAllOptionsForTesting()
VimPlugin.getKey().resetKeyMappings()
VimPlugin.getSearch().resetState()
if (VimPlugin.isNotEnabled()) VimPlugin.setEnabled(true)
if (!VimPlugin.isEnabled()) VimPlugin.setEnabled(true)
injector.globalOptions().ideastrictmode = true
Checks.reset()
clearClipboard()
@ -152,8 +152,8 @@ abstract class VimTestCase {
@AfterEach
open fun tearDown(testInfo: TestInfo) {
val swingTimer = swingTimer
swingTimer?.stop()
swingTimer = null
val bookmarksManager = BookmarksManager.getInstance(fixture.project)
bookmarksManager?.bookmarks?.forEach { bookmark ->
bookmarksManager.remove(bookmark)
@ -170,7 +170,6 @@ abstract class VimTestCase {
injector.jumpService.resetJumps()
VimPlugin.getChange().resetRepeat()
VimPlugin.getKey().savedShortcutConflicts.clear()
assertTrue(KeyHandler.getInstance().keyStack.isEmpty())
// Tear down neovim
NeovimTesting.tearDown(testInfo)

View File

@ -7,40 +7,22 @@
*/
package org.jetbrains.plugins.ideavim.action
import com.intellij.idea.TestFor
import com.intellij.testFramework.LoggedErrorProcessor
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.keys
import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.vim
import org.jetbrains.plugins.ideavim.ExceptionHandler
import org.jetbrains.plugins.ideavim.OnlyThrowLoggedErrorProcessor
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
import org.jetbrains.plugins.ideavim.exceptionMappingOwner
import org.jetbrains.plugins.ideavim.rangeOf
import org.jetbrains.plugins.ideavim.waitAndAssert
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
/**
* @author vlan
*/
class MacroActionTest : VimTestCase() {
@AfterEach
fun tearDown() {
injector.keyGroup.removeKeyMapping(exceptionMappingOwner)
}
// |q|
@Test
fun testRecordMacro() {
@ -196,33 +178,4 @@ class MacroActionTest : VimTestCase() {
""".trimIndent(),
)
}
@TestFor(issues = ["VIM-2929"])
@TestWithoutNeovim(reason = SkipNeovimReason.ACTION_COMMAND)
@Test
fun `macro to handler with exception`() {
configureByText(
"""
Lorem Ipsum
Lorem ipsum dolor sit amet,
${c}consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
injector.keyGroup.putKeyMapping(MappingMode.NXO, keys("abc"), exceptionMappingOwner, ExceptionHandler(), false)
injector.registerGroup.storeText('k', "abc")
injector.registerGroup.storeText('q', "x@ky")
val exception = assertThrows<Throwable> {
LoggedErrorProcessor.executeWith<Throwable>(OnlyThrowLoggedErrorProcessor) {
typeText("@q")
}
}
assertEquals(ExceptionHandler.exceptionMessage, exception.cause!!.cause!!.message)
assertTrue(KeyHandler.getInstance().keyStack.isEmpty())
}
}

View File

@ -1025,10 +1025,10 @@ $c tw${c}o
)
assertState(
"""
${s}one two
<selection>one two
three four
five six
$se
</selection>
""".trimIndent(),
)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,45 +10,26 @@ package org.jetbrains.plugins.ideavim.ex.implementation.commands
import com.intellij.idea.TestFor
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.textarea.TextComponentEditorImpl
import com.intellij.openapi.util.Disposer
import com.intellij.testFramework.LoggedErrorProcessor
import com.intellij.testFramework.TestLoggerFactory.TestLoggerAssertionError
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.keys
import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
import com.maddyhome.idea.vim.history.HistoryConstants
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.plugins.ideavim.ExceptionHandler
import org.jetbrains.plugins.ideavim.OnlyThrowLoggedErrorProcessor
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
import org.jetbrains.plugins.ideavim.assertThrowsLogError
import org.jetbrains.plugins.ideavim.exceptionMappingOwner
import org.jetbrains.plugins.ideavim.waitAndAssert
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import javax.swing.JTextArea
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertTrue
/**
* @author vlan
*/
class MapCommandTest : VimTestCase() {
@AfterEach
fun tearDown() {
injector.keyGroup.removeKeyMapping(exceptionMappingOwner)
}
@TestWithoutNeovim(reason = SkipNeovimReason.UNCLEAR)
@Test
fun testMapKtoJ() {
@ -895,14 +876,13 @@ n ,f <Plug>Foo
indicateErrors = true,
null,
)
val exception = assertThrowsLogError<TestLoggerAssertionError> {
val exception = assertThrows<Throwable> {
typeText(injector.parser.parseKeys("t"))
}
assertIs<ExException>(exception.cause) // Exception is wrapped into LOG.error twice
assertIs<ExException>(exception.cause) // The original exception comes from the LOG.error, so we check the cause
assertPluginError(true)
assertPluginErrorMessageContains("E121: Undefined variable: s:mapping")
editor.caretModel.allCarets.forEach { Disposer.dispose(it) }
}
// todo keyPresses invoked inside a script should have access to the script context
@ -943,10 +923,11 @@ n ,f <Plug>Foo
""".trimIndent()
configureByJavaText(text)
assertThrowsLogError<ExException> {
val exception = assertThrows<Throwable> {
typeText(commandToKeys("inoremap <expr> <cr> unknownFunction() ? '\\<C-y>' : '\\<C-g>u\\<CR>'"))
typeText(injector.parser.parseKeys("i<CR>"))
}
assertIs<ExException>(exception.cause) // The original exception comes from the LOG.error, so we check the cause
assertPluginError(true)
assertPluginErrorMessageContains("E117: Unknown function: unknownFunction")
@ -1116,32 +1097,4 @@ n ,i <Action>(Back)
Cras id tellus in ex imperdiet egestas.
""".trimIndent())
}
@TestFor(issues = ["VIM-2929"])
@TestWithoutNeovim(reason = SkipNeovimReason.ACTION_COMMAND)
@Test
fun `mapping to handler with exception`() {
configureByText(
"""
Lorem Ipsum
Lorem ipsum dolor sit amet,
${c}consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
injector.keyGroup.putKeyMapping(MappingMode.NXO, keys("abc"), exceptionMappingOwner, ExceptionHandler(), false)
typeText(commandToKeys("map k abcx"))
val exception = assertThrows<Throwable> {
LoggedErrorProcessor.executeWith<Throwable>(OnlyThrowLoggedErrorProcessor) {
typeText("k")
}
}
assertEquals(ExceptionHandler.exceptionMessage, exception.cause!!.cause!!.message)
assertTrue(KeyHandler.getInstance().keyStack.isEmpty())
}
}

View File

@ -9,9 +9,7 @@
package org.jetbrains.plugins.ideavim.extension
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.invokeLater
import com.intellij.openapi.util.Disposer
import com.intellij.testFramework.PlatformTestUtil
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
@ -43,23 +41,28 @@ import kotlin.test.assertFalse
import kotlin.test.assertTrue
class OpMappingTest : VimTestCase() {
private lateinit var extension: ExtensionBeanClass
private var initialized = false
private var disposable: Disposable = Disposer.newDisposable()
private lateinit var extension: ExtensionBeanClass
@BeforeEach
override fun setUp(testInfo: TestInfo) {
super.setUp(testInfo)
extension = TestExtension.createBean()
if (!initialized) {
initialized = true
VimExtension.EP_NAME.point.registerExtension(extension, disposable)
enableExtensions("TestExtension")
extension = TestExtension.createBean()
VimExtension.EP_NAME.point.registerExtension(extension, VimPlugin.getInstance())
enableExtensions("TestExtension")
}
}
@AfterEach
override fun tearDown(testInfo: TestInfo) {
Disposer.dispose(disposable)
super.tearDown(testInfo)
@Suppress("DEPRECATION")
VimExtension.EP_NAME.point.unregisterExtension(extension)
super.tearDown(super.testInfo)
}
@Test
@ -135,13 +138,13 @@ class OpMappingTest : VimTestCase() {
typeText(injector.parser.parseKeys("Q"))
assertState("I$c found it in a legendary land")
Disposer.dispose(disposable)
disposable = Disposer.newDisposable()
@Suppress("DEPRECATION")
VimExtension.EP_NAME.point.unregisterExtension(extension)
assertEmpty(VimPlugin.getKey().getKeyMappingByOwner(extension.instance.owner))
typeText(injector.parser.parseKeys("Q"))
assertState("I$c found it in a legendary land")
VimExtension.EP_NAME.point.registerExtension(extension, disposable)
VimExtension.EP_NAME.point.registerExtension(extension, VimPlugin.getInstance())
assertEmpty(VimPlugin.getKey().getKeyMappingByOwner(extension.instance.owner))
enableExtensions("TestExtension")
typeText(injector.parser.parseKeys("Q"))
@ -155,12 +158,12 @@ class OpMappingTest : VimTestCase() {
assertState("I$c found it in a legendary land")
enterCommand("set noTestExtension")
Disposer.dispose(disposable)
disposable = Disposer.newDisposable()
@Suppress("DEPRECATION")
VimExtension.EP_NAME.point.unregisterExtension(extension)
typeText(injector.parser.parseKeys("Q"))
assertState("I$c found it in a legendary land")
VimExtension.EP_NAME.point.registerExtension(extension, disposable)
VimExtension.EP_NAME.point.registerExtension(extension, VimPlugin.getInstance())
enableExtensions("TestExtension")
typeText(injector.parser.parseKeys("Q"))
assertState("I ${c}found it in a legendary land")
@ -198,20 +201,19 @@ class PlugExtensionsTest : VimTestCase() {
private lateinit var extension: ExtensionBeanClass
private var disposable: Disposable = Disposer.newDisposable()
@BeforeEach
override fun setUp(testInfo: TestInfo) {
super.setUp(testInfo)
configureByText("\n")
extension = TestExtension.createBean()
VimExtension.EP_NAME.point.registerExtension(extension, disposable)
VimExtension.EP_NAME.point.registerExtension(extension, VimPlugin.getInstance())
}
@AfterEach
override fun tearDown(testInfo: TestInfo) {
Disposer.dispose(disposable)
@Suppress("DEPRECATION")
VimExtension.EP_NAME.point.unregisterExtension(extension)
super.tearDown(super.testInfo)
}
@ -242,20 +244,19 @@ class PlugMissingKeysTest : VimTestCase() {
private lateinit var extension: ExtensionBeanClass
private var disposable: Disposable = Disposer.newDisposable()
@BeforeEach
override fun setUp(testInfo: TestInfo) {
super.setUp(testInfo)
configureByText("\n")
extension = TestExtension.createBean()
VimExtension.EP_NAME.point.registerExtension(extension, disposable)
VimExtension.EP_NAME.point.registerExtension(extension, VimPlugin.getInstance())
}
@AfterEach
override fun tearDown(testInfo: TestInfo) {
Disposer.dispose(disposable)
@Suppress("DEPRECATION")
VimExtension.EP_NAME.point.unregisterExtension(extension)
super.tearDown(super.testInfo)
}

View File

@ -14,16 +14,16 @@ import com.intellij.codeInsight.template.TemplateManager
import com.intellij.codeInsight.template.impl.ConstantNode
import com.intellij.codeInsight.template.impl.TemplateManagerImpl
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.selectionType
import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl
import com.maddyhome.idea.vim.group.visual.VimVisualTimer
import com.maddyhome.idea.vim.helper.VimBehaviorDiffers
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.listener.VimListenerManager
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.OptionConstants
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.state.mode.selectionType
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestOptionConstants
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
@ -33,7 +33,6 @@ import org.jetbrains.plugins.ideavim.impl.TraceOptions
import org.jetbrains.plugins.ideavim.impl.VimOption
import org.jetbrains.plugins.ideavim.waitAndAssert
import org.jetbrains.plugins.ideavim.waitAndAssertMode
import kotlin.test.assertNull
@TraceOptions(TestOptionConstants.selectmode)
class IdeaVisualControlTest : VimTestCase() {
@ -765,23 +764,6 @@ class IdeaVisualControlTest : VimTestCase() {
assertCaretsVisualAttributes()
}
@OptionTest(VimOption(TestOptionConstants.selectmode, limitedValues = [""]))
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
fun `test control selection interruption`() {
configureByText(
"""
Lorem Ipsum
I $s${c}found$se it in a legendary land
consectetur adipiscing elit
""".trimIndent(),
)
IdeaSelectionControl.controlNonVimSelectionChange(fixture.editor)
typeText(injector.parser.parseKeys("V"))
assertNull(VimVisualTimer.swingTimer)
}
private fun startDummyTemplate() {
TemplateManagerImpl.setTemplateTesting(fixture.testRootDisposable)
val templateManager = TemplateManager.getInstance(fixture.project)

View File

@ -1,37 +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.listener
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.listener.VimListenerTestObject
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
class VimListenersTest : VimTestCase() {
@AfterEach
fun tearDown() {
VimListenerTestObject.disposedCounter = 0
VimListenerTestObject.enabled = false
}
@Test
fun `disposable is called on plugin disable`() {
configureByText("XYZ")
VimListenerTestObject.disposedCounter = 0
VimListenerTestObject.enabled = true
VimPlugin.setEnabled(false)
assertEquals(1, VimListenerTestObject.disposedCounter)
VimPlugin.setEnabled(true)
}
}

View File

@ -12,14 +12,11 @@ import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.textarea.TextComponentEditorImpl
import com.intellij.openapi.util.Disposer
import com.intellij.testFramework.junit5.TestDisposable
import com.intellij.testFramework.replaceService
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.AfterEach
import org.mockito.Mockito
import javax.swing.JTextArea
@ -31,11 +28,6 @@ open class MockTestCase : VimTestCase() {
val editorStub = TextComponentEditorImpl(null, JTextArea()).vim
val contextStub: ExecutionContext = DataContext.EMPTY_CONTEXT.vim
@AfterEach
fun tearDown() {
editorStub.carets().forEach { Disposer.dispose(it.ij) }
}
fun <T : Any> mockService(service: Class<T>): T {
val mock = Mockito.mock(service)
val applicationManager = ApplicationManager.getApplication()

View File

@ -22,11 +22,9 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.EffectiveOptionValueChangeListener
import com.maddyhome.idea.vim.options.NumberOption
import com.maddyhome.idea.vim.options.OptionAccessScope
import com.maddyhome.idea.vim.options.OptionDeclaredScope
import com.maddyhome.idea.vim.options.StringOption
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
@ -36,10 +34,9 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInfo
import javax.swing.SwingConstants
import kotlin.test.assertEquals
import kotlin.test.assertContentEquals
private const val defaultValue = "defaultValue"
private const val defaultNumberValue = 10
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
class EffectiveOptionChangeListenerTest : VimTestCase() {
@ -112,17 +109,10 @@ class EffectiveOptionChangeListenerTest : VimTestCase() {
return option
}
private fun addNumberOption(scope: OptionDeclaredScope): NumberOption {
val option = NumberOption(optionName, scope, optionName, defaultNumberValue)
injector.optionGroup.addOption(option)
injector.optionGroup.addEffectiveOptionValueChangeListener(option, Listener)
return option
}
private fun assertNotifiedEditors(vararg editors: Editor) {
val expected = editors.toSet()
val actual = Listener.notifiedEditors.toSet()
assertEquals(expected, actual)
val sortedExpected = editors.sortedBy { it.virtualFile!!.path }.toTypedArray()
val sortedActual = Listener.notifiedEditors.sortedBy { it.virtualFile!!.path }.toTypedArray()
assertContentEquals(sortedExpected, sortedActual)
}
private fun assertNoNotifications() = assertNotifiedEditors()
@ -282,23 +272,10 @@ class EffectiveOptionChangeListenerTest : VimTestCase() {
@Test
fun `test listener called for all editors when locally modified global-local local-to-buffer option changes at effective scope`() {
val option = addOption(OptionDeclaredScope.GLOBAL_OR_LOCAL_TO_BUFFER)
injector.optionGroup.setOptionValue(option, OptionAccessScope.LOCAL(originalEditor.vim), VimString("localValue"))
injector.optionGroup.setOptionValue(option, OptionAccessScope.LOCAL(otherBufferWindow.vim), VimString("localValue"))
Listener.notifiedEditors.clear()
injector.optionGroup.setOptionValue(option, OptionAccessScope.EFFECTIVE(originalEditor.vim), VimString("newValue"))
assertNotifiedEditors(originalEditor, splitWindow, otherBufferWindow)
}
@Test
fun `test listener called for all editors when locally modified number global-local local-to-buffer option changes at effective scope`() {
// When a number (and therefore also toggle) global-local option is set at effective scope, the local value is not
// reset to [Option.unsetValue] but to a copy of the new value. The local editor(s) should still be notified.
val option = addNumberOption(OptionDeclaredScope.GLOBAL_OR_LOCAL_TO_BUFFER)
injector.optionGroup.setOptionValue(option, OptionAccessScope.LOCAL(originalEditor.vim), VimInt(100))
Listener.notifiedEditors.clear()
injector.optionGroup.setOptionValue(option, OptionAccessScope.EFFECTIVE(originalEditor.vim), VimInt(200))
injector.optionGroup.setOptionValue(option, OptionAccessScope.EFFECTIVE(otherBufferWindow.vim), VimString("newValue"))
assertNotifiedEditors(originalEditor, splitWindow, otherBufferWindow)
}
@ -350,19 +327,6 @@ class EffectiveOptionChangeListenerTest : VimTestCase() {
assertNotifiedEditors(originalEditor, splitWindow, otherBufferWindow)
}
@Test
fun `test listener called for all editors when locally modified number global-local local-to-window option changes at effective scope`() {
// When a number (and therefore also toggle) global-local option is set at effective scope, the local value is not
// reset to [Option.unsetValue] but to a copy of the new value. The local editor(s) should still be notified.
val option = addNumberOption(OptionDeclaredScope.GLOBAL_OR_LOCAL_TO_WINDOW)
injector.optionGroup.setOptionValue(option, OptionAccessScope.LOCAL(originalEditor.vim), VimInt(100))
Listener.notifiedEditors.clear()
injector.optionGroup.setOptionValue(option, OptionAccessScope.EFFECTIVE(originalEditor.vim), VimInt(200))
assertNotifiedEditors(originalEditor, splitWindow, otherBufferWindow)
}
@Test
fun `test listener not called for locally modified editor when global-local local-to-window option changes at global scope`() {
val option = addOption(OptionDeclaredScope.GLOBAL_OR_LOCAL_TO_WINDOW)

View File

@ -129,23 +129,6 @@ class OptionAccessScopeTest: VimTestCase() {
assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.EFFECTIVE(fixture.editor.vim)))
}
@Test
fun `test set local-to-buffer option at effective scope to current value changes global value`() {
val defaultValue = VimInt(10)
val option = NumberOption(OPTION_NAME, OptionDeclaredScope.LOCAL_TO_BUFFER, OPTION_NAME, defaultValue)
injector.optionGroup.addOption(option)
val effectiveValue = VimInt(100)
injector.optionGroup.setOptionValue(option, OptionAccessScope.LOCAL(fixture.editor.vim), effectiveValue)
assertEquals(defaultValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.GLOBAL(fixture.editor.vim)))
injector.optionGroup.setOptionValue(option, OptionAccessScope.EFFECTIVE(fixture.editor.vim), effectiveValue)
assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.GLOBAL(fixture.editor.vim)))
assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.LOCAL(fixture.editor.vim)))
assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.EFFECTIVE(fixture.editor.vim)))
}
// LOCAL_TO_WINDOW
@Test
@ -190,23 +173,6 @@ class OptionAccessScopeTest: VimTestCase() {
assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.EFFECTIVE(fixture.editor.vim)))
}
@Test
fun `test set local-to-window option at effective scope to current value changes global value`() {
val defaultValue = VimInt(10)
val option = NumberOption(OPTION_NAME, OptionDeclaredScope.LOCAL_TO_WINDOW, OPTION_NAME, defaultValue)
injector.optionGroup.addOption(option)
val effectiveValue = VimInt(100)
injector.optionGroup.setOptionValue(option, OptionAccessScope.LOCAL(fixture.editor.vim), effectiveValue)
assertEquals(defaultValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.GLOBAL(fixture.editor.vim)))
injector.optionGroup.setOptionValue(option, OptionAccessScope.EFFECTIVE(fixture.editor.vim), effectiveValue)
assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.GLOBAL(fixture.editor.vim)))
assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.LOCAL(fixture.editor.vim)))
assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.EFFECTIVE(fixture.editor.vim)))
}
// Global-local is tricky. The local value is initially not set, and the global value is used until it is. For string
// options, this is represented by the local value showing as an empty string. Number options usually use the value -1

View File

@ -8,13 +8,14 @@
package ui
import com.automation.remarks.junit5.Video
import com.automation.remarks.junit.VideoRule
import com.automation.remarks.video.annotations.Video
import com.intellij.remoterobot.RemoteRobot
import com.intellij.remoterobot.fixtures.ContainerFixture
import com.intellij.remoterobot.steps.CommonSteps
import com.intellij.remoterobot.stepsProcessing.step
import com.intellij.remoterobot.utils.keyboard
import org.assertj.swing.core.MouseButton
import org.junit.Rule
import org.junit.jupiter.api.Test
import ui.pages.Editor
import ui.pages.IdeaFrame
@ -24,7 +25,6 @@ import ui.pages.dialog
import ui.pages.editor
import ui.pages.gutter
import ui.pages.idea
import ui.pages.searchEverywhere
import ui.pages.welcomeFrame
import ui.utils.JavaExampleSteps
import ui.utils.StepsLogger
@ -38,7 +38,6 @@ import ui.utils.tripleClickOnRight
import ui.utils.uiTest
import ui.utils.vimExit
import java.awt.Point
import java.awt.event.KeyEvent
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@ -48,19 +47,14 @@ class UiTests {
StepsLogger.init()
}
private lateinit var commonSteps: CommonSteps
private val testTextForEditor = """
|One Two
|Three Four
|Five
""".trimMargin()
@Rule
@JvmField
var videoRule = VideoRule()
@Test
@Video
fun ideaVimTest() = uiTest("ideaVimTest") {
val sharedSteps = JavaExampleSteps(this)
commonSteps = CommonSteps(this)
startNewProject()
Thread.sleep(1000)
@ -69,11 +63,16 @@ class UiTests {
Thread.sleep(1000)
idea {
waitSmartMode()
createFile("MyDoc.txt", this@uiTest)
val editor = editor("MyDoc.txt") {
step("Write a text") {
injectText(testTextForEditor)
injectText(
"""
|One Two
|Three Four
|Five
""".trimMargin(),
)
}
}
testSelectTextWithDelay(editor)
@ -90,11 +89,6 @@ class UiTests {
testClickRightFromLineEnd(editor)
testClickOnWord(editor)
testGutterClick(editor)
testAddNewLineInNormalMode(editor)
testMappingToCtrlOrAltEnter(editor)
`simple enter in insert mode`(editor)
testMilticaretEnter(editor)
`simple enter in select mode`(editor)
reenableIdeaVim(editor)
createFile("MyTest.java", this@uiTest)
@ -102,7 +96,7 @@ class UiTests {
step("Write a text") {
injectText(
"""
|class MyTest {
|class Main {
| public static void main() {
| System.out.println("Hello");
| }
@ -121,6 +115,7 @@ class UiTests {
private fun closeUnrelated(sharedSteps: JavaExampleSteps) {
with(sharedSteps) {
closeIdeaVimDialog()
closeTipOfTheDay()
closeAllTabs()
}
@ -131,7 +126,7 @@ class UiTests {
createNewProjectLink.click()
dialog("New Project") {
findText("Java").click()
checkBox("Add sample code").unselect()
checkBox("Add sample code").select()
button("Create").click()
}
}
@ -200,11 +195,9 @@ class UiTests {
private fun IdeaFrame.testTrackActionId(editor: Editor) {
remoteRobot.invokeActionJs("GotoAction")
val searchEverywhere = this@testTrackActionId.searchEverywhere()
commonSteps.invokeAction("VimFindActionIdAction")
keyboard {
editor.keyboard {
enterText("IdeaVim: Track Action Ids")
enter()
escape()
}
@ -218,7 +211,7 @@ class UiTests {
assertEquals(
"""
|EditorEscapeclass MyTest {
|EditorEscapeclass Main {
| public static void main() {
| if (true) {
| System.out.println("Hello");
@ -238,7 +231,7 @@ class UiTests {
private fun IdeaFrame.createFile(fileName: String, remoteRobot: RemoteRobot) {
step("Create $fileName file") {
with(projectViewTree) {
setExpandTimeout(30_000)
setExpandTimeout(15_000)
expand(projectName, "src")
findText("src").click(MouseButton.RIGHT_BUTTON)
}
@ -517,187 +510,4 @@ class UiTests {
vimExit()
}
// For VIM-3159
private fun ContainerFixture.testAddNewLineInNormalMode(editor: Editor) {
println("Run testAddNewLineInNormalMode...")
commonSteps.invokeAction("EditorStartNewLineBefore")
assertEquals(
"""
|
|One Two
|Three Four
|Five
""".trimMargin(), editor.text
)
editor.injectText(testTextForEditor)
commonSteps.invokeAction("EditorStartNewLine")
assertEquals(
"""
|One Two
|
|Three Four
|Five
""".trimMargin(), editor.text
)
editor.injectText(testTextForEditor)
vimExit()
}
// For VIM-3190
private fun ContainerFixture.testMappingToCtrlOrAltEnter(editor: Editor) {
println("Run testMappingToCtrlOrAltEnter...")
keyboard {
enterText(":nmap <C-Enter> k")
enter()
enterText(":nmap <A-Enter> G")
enter()
}
// Set up initial position
keyboard {
enterText("jll")
}
assertEquals(10, editor.caretOffset)
// Checking C-ENTER
keyboard {
pressing(KeyEvent.VK_CONTROL) { enter() }
}
assertEquals(2, editor.caretOffset)
// Checking A-ENTER
keyboard {
pressing(KeyEvent.VK_ALT) { enter() }
}
assertEquals(19, editor.caretOffset)
vimExit()
}
// For VIM-3186
private fun ContainerFixture.testMilticaretEnter(editor: Editor) {
println("Run testMilticaretEnter...")
keyboard {
pressing(KeyEvent.VK_ALT) {
pressing(KeyEvent.VK_SHIFT) {
findText("One").click()
findText("Three").click()
findText("Five").click()
}
}
enterText("A")
enter()
}
assertEquals(3, editor.caretCount)
assertEquals(
"""
|One Two
|
|Three Four
|
|Five
|
""".trimMargin(), editor.text
)
// Reset state
keyboard {
escape()
escape()
}
assertEquals(1, editor.caretCount)
editor.injectText(testTextForEditor)
vimExit()
}
private fun ContainerFixture.`simple enter in insert mode`(editor: Editor) {
println("Run test 'simple enter in insert mode'...")
// Start of file
keyboard {
enterText("i")
enter()
}
assertEquals(
"""
|
|One Two
|Three Four
|Five
""".trimMargin(),
editor.text
)
// Middle of file
findText("Four").click()
keyboard { enter() }
assertEquals(
"""
|
|One Two
|Three
|Four
|Five
""".trimMargin(),
editor.text
)
// End of file
val fivePoint = findText("Five").point
val endOfLine = Point(fivePoint.x + 50, fivePoint.y)
click(endOfLine)
keyboard { enter() }
assertEquals(
"""
|
|One Two
|Three
|Four
|Five
|
""".trimMargin(),
editor.text
)
editor.injectText(testTextForEditor)
vimExit()
}
private fun ContainerFixture.`simple enter in select mode`(editor: Editor) {
println("Run test 'simple enter in select mode'...")
findText("Four").doubleClick()
keyboard {
pressing(KeyEvent.VK_CONTROL) { enterText("g") }
enter()
}
assertEquals(
"""
|One Two
|Three
|
|Five
""".trimMargin(),
editor.text
)
editor.injectText(testTextForEditor)
vimExit()
}
}

View File

@ -44,9 +44,6 @@ class Editor(
val caretOffset: Int
get() = callJs("component.getEditor().getCaretModel().getOffset()", runInEdt = true)
val caretCount: Int
get() = callJs("component.getEditor().getCaretModel().getCaretCount()", runInEdt = true)
val isBlockCursor: Boolean
// get() = callJs("component.getEditor().getSettings().isBlockCursor()", true)
// Doesn't work at the moment because remote robot can't resolve classes from a plugin classloader

View File

@ -51,14 +51,6 @@ class IdeaFrame(
}
}
fun waitSmartMode(timeout: Duration = Duration.ofMinutes(5)) {
step("Wait for smart mode") {
waitFor(duration = timeout, interval = Duration.ofSeconds(5)) {
isDumbMode().not()
}
}
}
private fun isDumbMode(): Boolean {
return callJs("com.intellij.openapi. project.DumbService.isDumb(component.project);", true)
}

View File

@ -1,31 +0,0 @@
/*
* Copyright 2003-2024 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package ui.pages
import com.intellij.remoterobot.RemoteRobot
import com.intellij.remoterobot.data.RemoteComponent
import com.intellij.remoterobot.fixtures.CommonContainerFixture
import com.intellij.remoterobot.fixtures.ContainerFixture
import com.intellij.remoterobot.fixtures.FixtureName
import com.intellij.remoterobot.search.locators.byXpath
@JvmOverloads
fun ContainerFixture.searchEverywhere(function: SearchEverywhere.() -> Unit = {}): SearchEverywhere {
return find<SearchEverywhere>(
byXpath("Search Everywhere", "//div[@accessiblename='Search everywhere' and @class='SearchEverywhereUI']"),
)
.apply { runJs("robot.moveMouse(component);") }
.apply(function)
}
@FixtureName("SearchEverywhere")
class SearchEverywhere(
remoteRobot: RemoteRobot,
remoteComponent: RemoteComponent,
) : CommonContainerFixture(remoteRobot, remoteComponent)

View File

@ -17,11 +17,19 @@ import com.intellij.remoterobot.utils.Keyboard
import ui.pages.DialogFixture
import ui.pages.DialogFixture.Companion.byTitle
import ui.pages.IdeaFrame
import ui.pages.dialog
import ui.pages.idea
class JavaExampleSteps(private val remoteRobot: RemoteRobot) {
@Suppress("unused")
private val keyboard: Keyboard = Keyboard(remoteRobot)
fun closeIdeaVimDialog() = optionalStep("Close Idea Vim dialog if it appears") {
remoteRobot.idea {
dialog("IdeaVim") { button("Yes").click() }
}
}
fun closeTipOfTheDay() = optionalStep("Close Tip of the Day if it appears") {
val idea: IdeaFrame = remoteRobot.find(IdeaFrame::class.java)
idea.dumbAware {

View File

@ -28,7 +28,7 @@ import java.util.*
public class AutoIndentLinesVisualAction : VisualOperatorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MOT_LINEWISE)
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MOT_LINEWISE, CommandFlags.FLAG_EXIT_VISUAL)
override fun executeAction(
editor: VimEditor,

View File

@ -13,10 +13,13 @@ import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
import com.maddyhome.idea.vim.helper.CharacterHelper
import com.maddyhome.idea.vim.helper.enumSetOf
import java.util.*
/**
* @author vlan
@ -25,6 +28,8 @@ import com.maddyhome.idea.vim.helper.CharacterHelper
public class ChangeCaseLowerVisualAction : VisualOperatorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
override fun executeAction(
editor: VimEditor,
caret: VimCaret,

View File

@ -14,10 +14,13 @@ import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
import com.maddyhome.idea.vim.helper.CharacterHelper
import com.maddyhome.idea.vim.helper.enumSetOf
import java.util.*
/**
* @author vlan
@ -26,6 +29,8 @@ import com.maddyhome.idea.vim.helper.CharacterHelper
public class ChangeCaseToggleVisualAction : VisualOperatorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
override fun executeAction(
editor: VimEditor,
caret: VimCaret,

View File

@ -13,10 +13,13 @@ import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
import com.maddyhome.idea.vim.helper.CharacterHelper
import com.maddyhome.idea.vim.helper.enumSetOf
import java.util.*
/**
* @author vlan
@ -25,6 +28,8 @@ import com.maddyhome.idea.vim.helper.CharacterHelper
public class ChangeCaseUpperVisualAction : VisualOperatorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
override fun executeAction(
editor: VimEditor,
caret: VimCaret,

View File

@ -16,6 +16,7 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.CommandFlags.FLAG_MULTIKEY_UNDO
import com.maddyhome.idea.vim.command.CommandFlags.FLAG_NO_REPEAT_INSERT
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
@ -26,7 +27,7 @@ import java.util.*
public class ChangeCharactersAction : ChangeEditorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE
override val flags: EnumSet<CommandFlags> = enumSetOf(FLAG_NO_REPEAT_INSERT)
override val flags: EnumSet<CommandFlags> = enumSetOf(FLAG_NO_REPEAT_INSERT, FLAG_MULTIKEY_UNDO)
override fun execute(
editor: VimEditor,

View File

@ -16,6 +16,7 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.CommandFlags.FLAG_MULTIKEY_UNDO
import com.maddyhome.idea.vim.command.CommandFlags.FLAG_NO_REPEAT_INSERT
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
@ -26,7 +27,7 @@ import java.util.*
public class ChangeEndOfLineAction : ChangeEditorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE
override val flags: EnumSet<CommandFlags> = enumSetOf(FLAG_NO_REPEAT_INSERT)
override val flags: EnumSet<CommandFlags> = enumSetOf(FLAG_NO_REPEAT_INSERT, FLAG_MULTIKEY_UNDO)
override fun execute(
editor: VimEditor,

View File

@ -17,6 +17,7 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.CommandFlags.FLAG_MULTIKEY_UNDO
import com.maddyhome.idea.vim.command.CommandFlags.FLAG_NO_REPEAT_INSERT
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
@ -27,7 +28,7 @@ import java.util.*
public class ChangeLineAction : ChangeEditorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE
override val flags: EnumSet<CommandFlags> = enumSetOf(FLAG_NO_REPEAT_INSERT)
override val flags: EnumSet<CommandFlags> = enumSetOf(FLAG_NO_REPEAT_INSERT, FLAG_MULTIKEY_UNDO)
override fun execute(
editor: VimEditor,

View File

@ -14,13 +14,18 @@ import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf
import java.util.*
@CommandOrMotion(keys = ["R"], modes = [Mode.NORMAL])
public class ChangeReplaceAction : ChangeEditorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.CHANGE
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MULTIKEY_UNDO)
override fun execute(
editor: VimEditor,
context: ExecutionContext,

View File

@ -14,9 +14,12 @@ import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf
import java.util.*
/**
* @author vlan
@ -25,6 +28,8 @@ import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
public class ChangeVisualAction : VisualOperatorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MULTIKEY_UNDO, CommandFlags.FLAG_EXIT_VISUAL)
override fun executeAction(
editor: VimEditor,
caret: VimCaret,

View File

@ -34,7 +34,7 @@ public class ChangeVisualCharacterAction : VisualOperatorActionHandler.ForEachCa
override val argumentType: Argument.Type = Argument.Type.DIGRAPH
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_ALLOW_DIGRAPH)
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_ALLOW_DIGRAPH, CommandFlags.FLAG_EXIT_VISUAL)
override fun executeAction(
editor: VimEditor,

View File

@ -17,7 +17,9 @@ import com.maddyhome.idea.vim.api.getLineStartForOffset
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.CommandFlags.FLAG_EXIT_VISUAL
import com.maddyhome.idea.vim.command.CommandFlags.FLAG_MOT_LINEWISE
import com.maddyhome.idea.vim.command.CommandFlags.FLAG_MULTIKEY_UNDO
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.common.TextRange
@ -33,7 +35,7 @@ import java.util.*
public class ChangeVisualLinesAction : VisualOperatorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE
override val flags: EnumSet<CommandFlags> = enumSetOf(FLAG_MOT_LINEWISE)
override val flags: EnumSet<CommandFlags> = enumSetOf(FLAG_MOT_LINEWISE, FLAG_MULTIKEY_UNDO, FLAG_EXIT_VISUAL)
override fun executeAction(
editor: VimEditor,

View File

@ -17,7 +17,9 @@ import com.maddyhome.idea.vim.api.getLineStartForOffset
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.CommandFlags.FLAG_EXIT_VISUAL
import com.maddyhome.idea.vim.command.CommandFlags.FLAG_MOT_LINEWISE
import com.maddyhome.idea.vim.command.CommandFlags.FLAG_MULTIKEY_UNDO
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.common.TextRange
@ -33,7 +35,7 @@ import java.util.*
public class ChangeVisualLinesEndAction : VisualOperatorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE
override val flags: EnumSet<CommandFlags> = enumSetOf(FLAG_MOT_LINEWISE)
override val flags: EnumSet<CommandFlags> = enumSetOf(FLAG_MOT_LINEWISE, FLAG_MULTIKEY_UNDO, FLAG_EXIT_VISUAL)
override fun executeAction(
editor: VimEditor,

View File

@ -28,7 +28,7 @@ import java.util.*
public class ReformatCodeVisualAction : VisualOperatorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MOT_LINEWISE)
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MOT_LINEWISE, CommandFlags.FLAG_EXIT_VISUAL)
override fun executeAction(
editor: VimEditor,

View File

@ -14,12 +14,16 @@ import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf
import java.util.*
public sealed class IncNumber(public val inc: Int, private val avalanche: Boolean) : VisualOperatorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
override fun executeAction(
editor: VimEditor,

Some files were not shown because too many files have changed in this diff Show More