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

Compare commits

..

19 Commits

Author SHA1 Message Date
12575c3cb9
Set plugin version to chylex-34 2024-05-05 18:54:35 +02:00
8e8420033c
Revert "Factor disposable objects on editor opening"
This reverts commit 1fa78935
2024-05-05 18:54:35 +02:00
7b89362f9d
Fix(VIM-3364): Exception with mapped Generate action 2024-05-05 18:54:35 +02:00
567a8487de
Apply scrolloff after executing native IDEA actions 2024-05-05 18:54:35 +02:00
90ad27e55a
Stay on same line after reindenting 2024-05-05 18:54:35 +02:00
271ea2345d
Update search register when using f/t 2024-05-05 18:54:34 +02:00
82386be533
Automatically add unambiguous imports after running a macro 2024-05-05 18:54:34 +02:00
a36ba6ab3d
Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2024-05-05 18:54:34 +02:00
ce60d1e552
Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2024-05-05 18:54:34 +02:00
7b388a0ce4
Fix(VIM-3166): Workaround to fix broken filtering of visual lines 2024-05-05 18:54:34 +02:00
4a200c23a7
Add support for count for visual and line motion surround 2024-05-05 18:54:34 +02:00
723a6b943f
Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2024-05-05 18:54:34 +02:00
1205b33195
Fix(VIM-696) Restore visual mode after undo/redo, and disable incompatible actions 2024-05-05 18:54:34 +02:00
a0d2d64237
Revert(VIM-2884): Fix moving lines to cursor 2024-04-26 18:56:21 +02:00
2e4e8c058b
Respect count with <Action> mappings 2024-04-26 18:56:19 +02:00
f464d25844
Change matchit plugin to use HTML patterns in unrecognized files 2024-04-26 18:55:35 +02:00
acc12c5b17
Reset insert mode when switching active editor 2024-04-26 18:55:35 +02:00
0c1bbd5e92
Remove update checker 2024-04-26 18:55:35 +02:00
f330e220ad
Set custom plugin version 2024-04-26 18:55:33 +02:00
276 changed files with 3051 additions and 14581 deletions

View File

@ -11,6 +11,6 @@
</option>
</component>
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

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

View File

@ -11,7 +11,6 @@ import _Self.subprojects.GitHub
import _Self.subprojects.OldTests
import _Self.subprojects.Releases
import _Self.vcsRoots.GitHubPullRequest
import _Self.vcsRoots.ReleasesVcsRoot
import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType
import jetbrains.buildServer.configs.kotlin.v2019_2.Project
@ -22,11 +21,11 @@ object Project : Project({
// VCS roots
vcsRoot(GitHubPullRequest)
vcsRoot(ReleasesVcsRoot)
// Active tests
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(TestingBuildType("2024.1.1", "<default>"))
buildType(TestingBuildType("2023.3", "<default>", version = "2023.3"))
buildType(TestingBuildType("2024.1", "<default>"))
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(PropertyBased)

View File

@ -1,72 +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 _Self.buildTypes
import _Self.IdeaVimBuildType
import jetbrains.buildServer.configs.kotlin.v2019_2.CheckoutMode
import jetbrains.buildServer.configs.kotlin.v2019_2.DslContext
import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.sshAgent
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script
object CreateNewReleaseBranchFromMaster : IdeaVimBuildType({
name = "EXP: Create new release branch from master"
vcs {
root(DslContext.settingsRoot)
branchFilter = "+:<default>"
checkoutMode = CheckoutMode.AUTO
}
steps {
script {
name = "Calculate next potential release version"
scriptContent = """
#!/bin/bash
# Fetch all remote branches
git fetch --all
# Get a list of all branches matching the pattern releases/x.y.z
branches=${'$'}(git branch -r | grep -oE 'releases/[0-9]+\.[0-9]+\.x')
# If no matching branches are found, print a message and exit
if [[ -z "${'$'}branches" ]]; then
echo "No release branches found"
exit 1
fi
# Find the largest release version
largest_release=${'$'}(echo "${'$'}branches" | sort -V | tail -n 1)
# Print the largest release
echo "Largest release branch: ${'$'}largest_release"
echo "##teamcity[setParameter name='env.POTENTIAL_VERSION' value='${'$'}largest_release']"
""".trimIndent()
}
script {
name = "Show potential release version"
scriptContent = """
#!/bin/bash
echo "Calculated or user-provided parameter value is: %env.POTENTIAL_VERSION%"
""".trimIndent()
}
}
params {
param("env.POTENTIAL_VERSION", "")
}
features {
sshAgent {
teamcitySshKey = "IdeaVim ssh keys"
}
}
})

View File

@ -1,42 +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 _Self.buildTypes
import _Self.IdeaVimBuildType
import _Self.vcsRoots.ReleasesVcsRoot
import jetbrains.buildServer.configs.kotlin.v2019_2.CheckoutMode
import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.sshAgent
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script
object PrintReleaseBranch : IdeaVimBuildType({
name = "EXP: Print release branch"
vcs {
root(ReleasesVcsRoot)
branchFilter = "+:heads/releases/*"
checkoutMode = CheckoutMode.AUTO
}
steps {
script {
name = "Print current branch"
scriptContent = """
echo "Current branch is: %teamcity.build.branch%"
""".trimIndent()
}
}
features {
sshAgent {
teamcitySshKey = "IdeaVim ssh keys"
}
}
})

View File

@ -26,7 +26,7 @@ object PublishVimEngine : IdeaVimBuildType({
vcs {
root(DslContext.settingsRoot)
branchFilter = "+:fleet"
branchFilter = "+:<default>"
checkoutMode = CheckoutMode.AUTO
}

View File

@ -1,106 +0,0 @@
package _Self.buildTypes
import _Self.Constants.EAP_CHANNEL
import _Self.Constants.RELEASE_EAP
import _Self.IdeaVimBuildType
import _Self.vcsRoots.ReleasesVcsRoot
import jetbrains.buildServer.configs.kotlin.v2019_2.CheckoutMode
import jetbrains.buildServer.configs.kotlin.v2019_2.ParameterDisplay
import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.sshAgent
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script
import jetbrains.buildServer.configs.kotlin.v2019_2.failureConditions.BuildFailureOnMetric
import jetbrains.buildServer.configs.kotlin.v2019_2.failureConditions.failOnMetricChange
object ReleaseEapFromBranch : IdeaVimBuildType({
name = "EXP: Publish EAP Build from branch"
description = "Build and publish EAP of IdeaVim plugin"
artifactRules = "build/distributions/*"
params {
param("env.ORG_GRADLE_PROJECT_ideaVersion", RELEASE_EAP)
password(
"env.ORG_GRADLE_PROJECT_publishToken",
"credentialsJSON:61a36031-4da1-4226-a876-b8148bf32bde",
label = "Password"
)
param("env.ORG_GRADLE_PROJECT_publishChannels", EAP_CHANNEL)
password(
"env.ORG_GRADLE_PROJECT_slackUrl",
"credentialsJSON:a8ab8150-e6f8-4eaf-987c-bcd65eac50b5",
label = "Slack Token"
)
password(
"env.YOUTRACK_TOKEN",
"credentialsJSON:2479995b-7b60-4fbb-b095-f0bafae7f622",
display = ParameterDisplay.HIDDEN
)
}
vcs {
root(ReleasesVcsRoot)
branchFilter = """
+:heads/(releases/*)
""".trimIndent()
checkoutMode = CheckoutMode.AUTO
}
steps {
script {
name = "Pull git tags"
scriptContent = "git fetch --tags origin"
}
script {
name = "Pull git history"
scriptContent = "git fetch --unshallow"
}
gradle {
name = "Calculate new eap version from branch"
tasks = "scripts:calculateNewEapVersionFromBranch"
}
gradle {
name = "Set TeamCity build number"
tasks = "scripts:setTeamCityBuildNumber"
}
gradle {
name = "Add release tag"
tasks = "scripts:addReleaseTag"
}
gradle {
name = "Publish plugin"
tasks = "publishPlugin"
}
script {
name = "Push changes to the repo"
scriptContent = """
branch=$(git branch --show-current)
echo current branch is ${'$'}branch
git push origin %build.number%
""".trimIndent()
}
gradle {
name = "YouTrack post release actions"
tasks = "scripts:eapReleaseActions"
}
}
features {
sshAgent {
teamcitySshKey = "IdeaVim ssh keys"
}
}
failureConditions {
failOnMetricChange {
metric = BuildFailureOnMetric.MetricType.ARTIFACT_SIZE
threshold = 5
units = BuildFailureOnMetric.MetricUnit.PERCENTS
comparison = BuildFailureOnMetric.MetricComparison.DIFF
compareTo = build {
buildRule = lastSuccessful()
}
}
}
})

View File

@ -147,10 +147,10 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
name = "Run Integrations"
tasks = "releaseActions"
}
// gradle {
// name = "Slack Notification"
// tasks = "slackNotification"
// }
gradle {
name = "Slack Notification"
tasks = "slackNotification"
}
}
features {

View File

@ -16,10 +16,10 @@ object GitHub : Project({
name = "Pull Requests checks"
description = "Automatic checking of GitHub Pull Requests"
buildType(GithubBuildType("clean test", "Tests"))
buildType(Github("clean test", "Tests"))
})
class GithubBuildType(command: String, desc: String) : IdeaVimBuildType({
class Github(command: String, desc: String) : IdeaVimBuildType({
name = "GitHub Pull Requests $desc"
description = "Test GitHub pull requests $desc"

View File

@ -1,11 +1,8 @@
package _Self.subprojects
import _Self.buildTypes.CreateNewReleaseBranchFromMaster
import _Self.buildTypes.PrintReleaseBranch
import _Self.buildTypes.PublishVimEngine
import _Self.buildTypes.ReleaseDev
import _Self.buildTypes.ReleaseEap
import _Self.buildTypes.ReleaseEapFromBranch
import _Self.buildTypes.ReleaseMajor
import _Self.buildTypes.ReleaseMinor
import _Self.buildTypes.ReleasePatch
@ -41,8 +38,4 @@ object Releases : Project({
buildType(ReleaseEap)
buildType(ReleaseDev)
buildType(PublishVimEngine)
buildType(CreateNewReleaseBranchFromMaster)
buildType(PrintReleaseBranch)
buildType(ReleaseEapFromBranch)
})

View File

@ -1,13 +0,0 @@
package _Self.vcsRoots
import jetbrains.buildServer.configs.kotlin.v2019_2.vcs.GitVcsRoot
object ReleasesVcsRoot : GitVcsRoot({
name = "IdeaVim Releases"
url = "git@github.com:JetBrains/ideavim.git"
branch = "refs/heads/master"
branchSpec = "+:refs/(*)"
authMethod = uploadedKey {
uploadedKey = "IdeaVim ssh keys"
}
})

View File

@ -5,15 +5,15 @@ 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 buildType with id = 'ReleaseEapFromBranch'
To apply the patch, change the buildType with id = 'PublishVimEngine'
accordingly, and delete the patch script.
*/
changeBuildType(RelativeId("ReleaseEapFromBranch")) {
changeBuildType(RelativeId("PublishVimEngine")) {
vcs {
check(branchFilter == "+:heads/(releases/*)") {
check(branchFilter == "+:<default>") {
"Unexpected option value: branchFilter = $branchFilter"
}
branchFilter = "heads/releases/*"
branchFilter = "+:fleet"
}
}

View File

@ -30,5 +30,5 @@ node (Plugins -> teamcity-configs -> teamcity-configs:generate),
the 'Debug' option is available in the context menu for the task.
*/
version = "2024.03"
version = "2023.11"
project(_Self.Project)

View File

@ -503,14 +503,6 @@ Contributors:
[![icon][github]](https://github.com/Parker7123)
&nbsp;
FilipParker
* [![icon][mail]](mailto:7138209+duhaesbaert@users.noreply.github.com)
[![icon][github]](https://github.com/duhaesbaert)
&nbsp;
Eduardo Haesbaert
* [![icon][mail]](mailto:nikolaevsky.egor@gmail.com)
[![icon][github]](https://github.com/Aisper)
&nbsp;
Egor Nikolaevsky
Previous contributors:

View File

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

@ -51,11 +51,11 @@ buildscript {
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.9.0.202403050737-r")
classpath("org.kohsuke:github-api:1.305")
classpath("io.ktor:ktor-client-core:2.3.11")
classpath("io.ktor:ktor-client-core:2.3.10")
classpath("io.ktor:ktor-client-cio:2.3.10")
classpath("io.ktor:ktor-client-auth:2.3.11")
classpath("io.ktor:ktor-client-auth:2.3.10")
classpath("io.ktor:ktor-client-content-negotiation:2.3.10")
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.11")
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.10")
// This comes from the changelog plugin
// classpath("org.jetbrains:markdown:0.3.1")
@ -310,7 +310,7 @@ tasks {
patchPluginXml {
// Don't forget to update plugin.xml
sinceBuild.set("241.15989.150")
sinceBuild.set("233.11799.67")
changeNotes.set(
"""<a href="https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20Fix%20versions:%20${version.get()}">Changelog</a>"""

View File

@ -27,7 +27,7 @@ Plug 'nerdtree'
### Preview
<details>
<summary>Click for the preview</summary>
<summary>Click to the the preview</summary>
<img src="images/nerdtree.gif" alt="NERDTree example"/>
</details>

View File

@ -1,268 +1,140 @@
List of Supported Options
=========================
List of Supported Set Commands
==============================
The following options can be set with the `:set`, `:setglobal` and `:setlocal` commands.
They can be added to the `~/.ideavimrc` file, or set manually in Command-line mode.
For more details of each option, please see the Vim documentation.
Every effort is made to make these options compatible with Vim behaviour.
However, some differences are inevitable.
The following `:set` commands can appear in `~/.ideavimrc` or be set manually in the command mode:
```
'clipboard' 'cb' Defines clipboard behavioue
A comma-separated list of words to control clipboard behaviour:
unnamed The clipboard register '*' is used instead of the
unnamed register
unnamedplus The clipboard register '+' is used instead of the
unnamed register
ideaput Uses the IDEs own paste implementation for put
operations rather than simply inserting the text
'digraph' 'dg' Enable using <BS> to enter digraphs in Insert mode
'gdefault' 'gd' The ":substitute" flag 'g' is by default
'guicursor' 'gcr' Controls the shape of the cursor for different modes
'history' 'hi' Number of command-lines that are remembered
'hlsearch' 'hls' Highlight matches with the last search pattern
'ignorecase' 'ic' Ignore case in search patterns
'incsearch' 'is' Show where search pattern typed so far matches
'iskeyword' 'isk' Defines keywords for commands like 'w', '*', etc.
'keymodel' 'km' Controls selection behaviour with special keys
List of comma separated words, which enable special things that keys
can do. These values can be used:
startsel Using a shifted special key starts selection (either
Select mode or Visual mode, depending on "key" being
present in 'selectmode')
stopsel Using a NOT-shifted special key stops selection.
Automatically enables `stopselect` and `stopvisual`
stopselect Using a NOT-shifted special key stops select mode
and removes selection - IdeaVim ONLY
stopvisual Using a NOT-shifted special key stops visual mode
and removes selection - IdeaVim ONLY
continueselect Using a shifted arrow key doesn't start selection,
but in select mode acts like startsel is enabled
- IdeaVim ONLY
continuevisual Using a shifted arrow key doesn't start selection,
but in visual mode acts like startsel is enabled
- IdeaVim ONLY
'clipboard' 'cb' clipboard options
Standard clipboard options plus
Special keys in this context are the cursor keys, <End>, <Home>,
<PageUp> and <PageDown>.
`ideaput` (default on) - IdeaVim ONLY
enable native idea paste action for put operations
'digraph' 'dg' enable the entering of digraphs in Insert mode
'gdefault' 'gd' the ":substitute" flag 'g' is by default
'history' 'hi' number of command-lines that are remembered
'hlsearch' 'hls' highlight matches with the last search pattern
'ignorecase' 'ic' ignore case in search patterns
'iskeyword' 'isk' defines keywords for commands like 'w', '*', etc.
'incsearch' 'is' show where search pattern typed so far matches
`keymodel` `km` String (default "continueselect,stopselect")
'matchpairs' 'mps' Pairs of characters that "%" can match
'maxmapdepth' 'mmd' Maximum depth of mappings
'more' 'more' When on, listings pause when the whole screen is filled
'nrformats' 'nf' Number formats recognized for CTRL-A command
'operatorfunc' 'opfunc' Name of a function to call with the g@ operator
'scroll' 'scr' Number of lines to scroll with CTRL-U and CTRL-D
'selection' 'sel' What type of selection to use
'selectmode' 'slm' Controls when to start Select mode instead of Visual
This is a comma-separated list of words:
mouse When using the mouse
key When using shifted special[1] keys
cmd When using "v", "V", or <C-V>
ideaselection When IDE sets a selection - IdeaVim ONLY
(e.g.: extend selection, wrap with while, etc.)
List of comma separated words, which enable special things that keys
can do. These values can be used:
startsel Using a shifted special[1] key starts selection (either
Select mode or Visual mode, depending on "key" being
present in 'selectmode').
stopsel Using a NOT-shifted special[1] key stops selection.
Automatically enables `stopselect` and `stopvisual`
stopselect Using a NOT-shifted special[1] key stops - IdeaVim ONLY
select mode and removes selection.
stopvisual Using a NOT-shifted special[1] key stops - IdeaVim ONLY
visual mode and removes selection.
continueselect Using a shifted arrow key doesn't - IdeaVim ONLY
start selection, but in select mode
acts like startsel is enabled
continuevisual Using a shifted arrow key doesn't - IdeaVim ONLY
start selection, but in visual mode
acts like startsel is enabled
'matchpairs' 'mps' pairs of characters that "%" can match
'maxmapdepth' 'mmd' Maximum depth of mappings
'more' 'more' When on, listings pause when the whole screen is filled.
'nrformats' 'nf' number formats recognized for CTRL-A command
'number' 'nu' print the line number in front of each line
'relativenumber' 'rnu' show the line number relative to the line with
the cursor
'scroll' 'scr' lines to scroll with CTRL-U and CTRL-D
'scrolljump' 'sj' minimum number of lines to scroll
'scrolloff' 'so' minimum number of lines above and below the cursor
'selection' 'sel' what type of selection to use
'shell' 'sh' The shell to use to execute commands with ! and :!
'shellcmdflag' 'shcf' The command flag passed to the shell
'shellxescape' 'sxe' The characters to be escaped when calling a shell
'shellxquote' 'sxq' The quote character to use in a shell command
'showcmd' 'sc' Show (partial) command in the status bar
'showmode' 'smd' Show the current mode in the status bar
'smartcase' 'scs' Use case sensitive search if any character in the
pattern is uppercase
'startofline' 'sol' When on, some commands move the cursor to the first
non-blank of the line
When off, the cursor is kept in the same column
(if possible)
'timeout' 'to' Use timeout for mapped key sequences
'timeoutlen' 'tm' Timeout duration for a mapped key sequence
'viminfo' 'vi' Information to remember after restart
'virtualedit' 've' Placement of the cursor where there is no actual text
A comma-separated list of these words:
block Allow virtual editing in Visual mode (not supported)
insert Allow virtual editing in Insert mode (not supported)
all Allow virtual editing in all modes (not supported)
onemore Allow the cursor to move just past the end of the line
`selectmode` `slm` String (default "")
'visualbell' 'vb' When on, prevents beeping on error
'whichwrap' 'ww' Which keys that move the cursor left/right can wrap to
other lines
A comma-separated list of these flags:
char key modes
b <BS> Normal and Visual
s <Space> Normal and Visual
h "h" Normal and Visual
l "l" Normal and Visual
< <Left> Normal and Visual
> <Right> Normal and Visual
~ "~" Normal
[ <Left> Insert and Replace
] <Right> Insert and Replace
This is a comma-separated list of words, which specify when to start
Select mode instead of Visual mode, when a selection is started.
Possible values:
mouse when using the mouse
key when using shifted special[1] keys
cmd when using "v", "V", or <C-V>
ideaselection when IDE sets a selection - IdeaVim ONLY
(examples: extend selection, wrap with while, etc.)
'wrapscan' 'ws' Search will wrap around the end of file
```
`startofline` `sol` When "on" some commands move the cursor to the first non-blank of the line.
When off the cursor is kept in the same column (if possible).
## IdeaVim options mapped to IntelliJ-based IDE settings
'showmode' 'smd' message on the status line to show current mode
'showcmd' 'sc' show (partial) command in the status bar
'sidescroll' 'ss' minimum number of columns to scroll horizontally
'sidescrolloff' 'siso' min. number of columns to left and right of cursor
'smartcase' 'scs' no ignore case when pattern is uppercase
'timeout' 'to' use timeout for mapped key sequences
'timeoutlen' 'tm' timeout duration for a mapped key sequence
'undolevels' 'ul' maximum number of changes that can be undone
'viminfo' 'vi' information to remember after restart
'visualbell' 'vb' use visual bell instead of beeping
'wrapscan' 'ws' searches wrap around the end of file
IdeaVim only commands:
IdeaVim provides its own implementation for handling scroll jump and offset, even though IntelliJ-based IDEs have similar functionality (there are differences in behaviour).
When IdeaVim is hosted in an IntelliJ-based IDE (but not JetBrains Fleet), the following options map to the equivalent IDE settings:
`ideamarks` `ideamarks` Boolean (default true)
If true, creation of global mark will trigger creation of IDE's bookmark
and vice versa.
`idearefactormode` `idearefactormode` String(default "select")
Define the mode that would be enabled during
the refactoring (renaming, live template, introduce variable, etc)
Use one of the following values:
- keep - keep the mode that was enabled before starting a refactoring
- select - start refactoring in select mode
- visual - start refactoring in visual mode
This option has effect if you are in normal, insert or replace mode before refactoring start.
Visual or select mode are not changed.
`ideajoin` `ideajoin` Boolean (default false)
If true, join command will be performed via IDE
See wiki/`ideajoin` examples
`ideastatusicon` `ideastatusicon` String(default "enabled")
Define the behavior of IdeaVim icon in the status bar.
Use one of the following values:
- enabled - icon is shown in the status bar
- gray - use the gray version of the icon
- disabled - hide the icon
```
'scrolljump' 'sj' Minimal number of lines to scroll
'scrolloff' 'so' Minimal number of lines above and below the cursor
'sidescroll' 'ss' Minimal number of columns to scroll horizontally
'sidescrolloff' 'siso' Minimal number of columns to left and right of cursor
```
`ideawrite` `ideawrite` String (default "all")
"file" or "all". Defines the behaviour of ":w" command.
Value "all" enables execution of ":wa" (save all) command on ":w" (save).
This feature exists because some IJ options like "Prettier on save" or "ESlint on save"
work only with "save all" action. If this option is set to "all", these actions work
also with ":w" command.
`lookupkeys` `lookupkeys` List of strings
List of keys that should be processed by the IDE during the active lookup (autocompletion).
For example, <Tab> and <Enter> are used by the IDE to finish the lookup,
but <C-W> should be passed to IdeaVim.
Default value:
"<Tab>", "<Down>", "<Up>", "<Enter>", "<Left>", "<Right>",
"<C-Down>", "<C-Up>", "<PageUp>", "<PageDown>",
"<C-J>", "<C-Q>"
`ideavimsupport` `ideavimsupport` List of strings (default "dialog")
Define the list of additional buffers where IdeaVim is enabled.
- dialog - enable IdeaVim in dialogs
- singleline - enable IdeaVim in single line editors (not suggested)
## IdeaVim options for IntelliJ-based IDE features
Some Vim features cannot be implemented by IdeaVim, and must be implemented by the host IDE, such as showing whitespace and line numbers, and enabling soft-wrap.
The following options modify equivalent settings and features implemented by IntelliJ-based IDEs.
There is some mismatch when trying to map Vim options, most of which are local options, to IDE settings, which are mostly global-local.
The Vim option will always reflect the effective value of the IDE setting for the current editor, and modifying the Vim option will update the local value of the IDE setting.
The default value of the Vim option set during startup is not passed to the IDE setting.
If the IDE setting has a way to modify the local value, such as entries in the _View | Active Editor_ menu, then changing this will update the current editor and be reflected in the Vim option value.
If the IDE setting can only modify its global setting in the main _Settings_ dialog, this change does not always update the current editor (because the local IDE setting has been modified and takes precedence).
IdeaVim tries to make this work more naturally by updating the editor and local Vim option when a global value changes unless the Vim option has been explicitly set in Command-line mode.
In other words, if the local Vim value is explicitly set for a window or buffer, interactively, then it should not be reset.
If the Vim option was explicitly set in `~/.ideavimrc` however, then the value will be reset, because this can be viewed as a "global" value - set once and applied to subsequently opened windows.
(This should not be confused with Vim's concept of global options, which are mainly used to initialise new windows.)
The local Vim option can always be reset to the global IDE setting value by resetting the Vim option to default with the `:set {option}&` syntax.
```
'bomb' 'bomb' Add or remove a byte order mark (BOM) to the
current file. Unlike Vim, the file is modified
immediately, and not when saved
'breakindent' 'bri' Indent soft wrapped lines to match the first
line's indent
'colorcolumn' 'cc' Maps to IntelliJ's visual guide columns
'cursorline' 'cul' Highlight the line containing the cursor
'fileencoding' 'fenc' Change the encoding of the current file. The file
is modified and written immediately, rather than
waiting to be saved
Note that the names of the encoding might not
match Vim's known names
'fileformat' 'ff' Change the file format - dos, unix or mac
The file is modified immediately, rather than
when saved
'list' 'list' Show whitespace. Maps to the editor's local
setting in the View | Active Editor menu
'number' 'nu' Show line numbers. Maps to the editor's local
setting in the View | Active Editor menu
'relativenumber' 'rnu' Show line numbers relative to the current line
'textwidth' 'tw' Set the column at which text is automatically
wrapped
'wrap' 'wrap' Enable soft-wraps. Maps to the editor's local
setting in the View | Active Editor menu
```
## IdeaVim only options
These options are IdeaVim only, and not supported by Vim.
They control integration with the host IDE.
Unless otherwise stated, these options do not have abbreviations.
```
'ideacopypreprocess' boolean (default off)
global or local to buffer
When enabled, the IDE will run custom copy pre-processors over text
copied to registers. These pre-processors can perform transformations
on the text, such as converting escape characters in a string literal
into the actual control characters in a Java file.
This is not usually the expected behaviour, so this option's default
value is off. The equivalent processing for paste is controlled by the
"ideaput" value to the 'clipboard' option.
'ideaglobalmode' boolean (default off)
global
This option will cause IdeaVim to share a single mode across all open
windows. In other words, entering Insert mode in one window will
enable Insert mode in all windows.
'ideajoin' boolean (default off)
global or local to buffer
When enabled, join commands will be handled by the IDE's "smart join"
feature. The IDE can change syntax when joining lines, such as merging
string literals or if statements. See the wiki for more examples. Not
all languages support smart join functionality.
'ideamarks' boolean (default on)
global
Maps Vim's global marks to IDE bookmarks.
'idearefactormode' string (default "select")
global or local to buffer
Specifies the mode to be used when a refactoring selects text to be
edited (e.g. renaming, live template fields, introduce variable, etc):
keep Keep the current mode
select Switch to Select mode
visual Switch to Visual mode
This option is only used when the refactoring is started in Normal,
Insert or Replace mode. Visual or Select modes are not changed.
'ideastatusicon' string (default "enabled")
global
This option controls the behaviour and appearance of the IdeaVim icon
in the status bar:
enabled Show the icon in the status bar
gray Show the gray version of the icon
disabled Hide the icon
'ideavimsupport' string (default "dialog")
global
A comma-separated list of additional buffers or locations where
IdeaVim should be enabled:
dialog Enable IdeaVim in editors hosted in dialogs
singleline Enable IdeaVim in single line editors (not recommended)
The IDE's editor component can be used in many places, such as VCS
commit tool window, or inside dialogs, and even as single line fields.
'ideawrite' string (default "all")
global
This option defines the behaviour of the :w command:
file Save the current file only
all The :w command works like :wa and invokes the Save All
IDE action. This allows options such as "Prettier on
save" or "ESlint on save" to work with the :w command,
but means all files are saved.
'lookupkeys' string (default "<Tab>,<Down>,<Up>,<Enter>,
<Left>,<Right>,<C-Down>,<C-Up>,
<PageUp>,<PageDown>, <C-J>,<C-Q>")
global
Comma-separated list of keys that should be processed by the IDE while
a code completion lookup popup is active. For example, <Tab> and
<Enter> are used by the IDE to complete the lookup and insert text,
but <C-W> should be passed IdeaVim to continue editing the text.
'trackactionids' boolean (default off)
global
When on, IdeaVim will try to track the current IDE action and display
the action name in a notification. This action ID can then be used in
a mapping to the action in the form <Action>(...).
'visualdelay' number (default 100)
global
This option specifies the delay, in milliseconds before converting an
IDE selection into Visual mode.
Some IDE features make a selection to help modify text (e.g. backspace
in Python or Yaml selects an indent and invokes the "remove selection"
action). IdeaVim listens for changes in selection to switch to Visual
mode, and will return to Normal mode when the selection is removed,
even if originally in Insert mode.
By waiting before converting to Visual mode, temporary selections can
be ignored and the current Vim mode maintained.
It is not expected that this value will need to be changed.
```
----------
[1] - cursor keys, <End>, <Home>, <PageUp> and <PageDown>

View File

@ -9,12 +9,12 @@
# suppress inspection "UnusedProperty" for whole file
#ideaVersion=LATEST-EAP-SNAPSHOT
ideaVersion=2024.1.1
ideaVersion=2024.1
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
ideaType=IC
downloadIdeaSources=true
instrumentPluginCode=true
version=chylex-35
version=chylex-34
javaVersion=17
remoteRobotVersion=0.11.22
antlrVersion=4.10.1

View File

@ -20,13 +20,13 @@ repositories {
}
dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.24")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.23")
implementation("io.ktor:ktor-client-core:2.3.11")
implementation("io.ktor:ktor-client-core:2.3.10")
implementation("io.ktor:ktor-client-cio:2.3.10")
implementation("io.ktor:ktor-client-content-negotiation:2.3.10")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.11")
implementation("io.ktor:ktor-client-auth:2.3.11")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.10")
implementation("io.ktor:ktor-client-auth:2.3.10")
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh
@ -107,13 +107,6 @@ tasks.register("calculateNewEapVersion", JavaExec::class) {
args = listOf("${rootProject.rootDir}")
}
tasks.register("calculateNewEapVersionFromBranch", JavaExec::class) {
group = "release"
mainClass.set("scripts.release.CalculateNewEapVersionFromBranchKt")
classpath = sourceSets["main"].runtimeClasspath
args = listOf("${rootProject.rootDir}")
}
tasks.register("calculateNewDevVersion", JavaExec::class) {
group = "release"
mainClass.set("scripts.release.CalculateNewDevVersionKt")

View File

@ -1,49 +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 scripts.release
fun main(args: Array<String> ) {
val projectDir = args[0]
println("Working directory: $projectDir")
val branch = withRepo(projectDir) { it.branch }
val (majorBranchVersion, minorBranchVersion) = versions(branch)
val versions = getVersionsExistingVersionsFor(majorBranchVersion, minorBranchVersion, projectDir)
val maxExistingVersion = versions.keys.maxOrNull()
val nextVersion = if (maxExistingVersion != null) {
if (maxExistingVersion.suffixTokens.isEmpty()) {
maxExistingVersion.nextPatch().withSuffix("eap.1").value
}
else {
check(maxExistingVersion.suffixTokens.size == 2) {
"We should have exactly two suffix tokens. Current tokens: ${maxExistingVersion.suffixTokens.toList()}"
}
check(maxExistingVersion.suffixTokens[0] == "eap") {
"First suffix token must be eap. Current tokens: ${maxExistingVersion.suffixTokens.toList()}"
}
val newEapNumber = maxExistingVersion.suffixTokens[1].toInt().inc()
maxExistingVersion.withSuffix("eap.$newEapNumber").value
}
} else {
"$majorBranchVersion.$minorBranchVersion.0-eap.1"
}
println("Next eap version: $nextVersion")
println("##teamcity[setParameter name='env.ORG_GRADLE_PROJECT_version' value='$nextVersion']")
}
private val regex = "releases/(\\d+)\\.(\\d+)\\.x".toRegex()
private fun versions(branchName: String): Pair<Int, Int> {
val match = regex.matchEntire(branchName) ?: error("Cannot match branch: $branchName")
val major = match.groups[1]
val minor = match.groups[2]
return major!!.value.toInt() to minor!!.value.toInt()
}

View File

@ -64,31 +64,6 @@ enum class ReleaseType {
STABLE_NO_PATCH, // Version that ends on 0. Like 2.5.0
}
internal fun getVersionsExistingVersionsFor(
majorVersion: Int,
minorVersion: Int,
projectDir: String,
): Map<Semver, ObjectId> {
val repository = RepositoryBuilder().setGitDir(File("$projectDir/.git")).build()
val git = Git(repository)
println(git.log().call().first())
println(git.tagList().call().first())
return git.tagList().call().mapNotNull { ref ->
runCatching {
// Git has two types of tags: light and annotated. This code detect hash of the commit for both types of tags
val revWalk = RevWalk(repository)
val tag = revWalk.parseAny(ref.objectId)
val commitHash = revWalk.peel(tag).id
val semver = Semver(ref.name.removePrefix("refs/tags/"))
if (semver.major == majorVersion && semver.minor == minorVersion) {
semver to commitHash
} else null
}.getOrNull()
}
.toMap()
}
internal fun getVersion(projectDir: String, releaseType: ReleaseType): Pair<Semver, ObjectId> {
val repository = RepositoryBuilder().setGitDir(File("$projectDir/.git")).build()
val git = Git(repository)

View File

@ -101,14 +101,10 @@ command:
(WS | COLON)* range? (WS | COLON)* EXECUTE WS* (expr WS*)* (NEW_LINE | BAR)+
#ExecuteCommand|
(WS | COLON)* range? (WS | COLON)* lShift
WS* ((commandArgumentWithoutBars? inline_comment NEW_LINE) | (commandArgumentWithoutBars? NEW_LINE) | (commandArgumentWithoutBars? BAR))
(NEW_LINE | BAR)*
(WS | COLON)* range? (WS | COLON)* lShift (WS* commandArgument = ~(NEW_LINE | BAR)+)? ((inline_comment NEW_LINE+) | (NEW_LINE | BAR)+)
#ShiftLeftCommand|
(WS | COLON)* range? (WS | COLON)* rShift
WS* ((commandArgumentWithoutBars? inline_comment NEW_LINE) | (commandArgumentWithoutBars? NEW_LINE) | (commandArgumentWithoutBars? BAR))
(NEW_LINE | BAR)*
(WS | COLON)* range? (WS | COLON)* rShift (WS* commandArgument = ~(NEW_LINE | BAR)+)? ((inline_comment NEW_LINE+) | (NEW_LINE | BAR)+)
#ShiftRightCommand|
(WS | COLON)* range? (WS | COLON)*
@ -828,4 +824,4 @@ AUGROUP_SKIP: NEW_LINE (WS|COLON)* AUGROUP .*? AUGROUP WS+ END -> skip
// All the other symbols
UNICODE_CHAR: '\u0000'..'\uFFFE';
EMOJI: [\p{Emoji}];
EMOJI: [\p{Emoji}];

View File

@ -282,13 +282,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
ideavimrcRegistered = true;
if (!ApplicationManager.getApplication().isUnitTestMode()) {
try {
VimInjectorKt.injector.getOptionGroup().startInitVimRc();
executeIdeaVimRc(editor);
}
finally {
VimInjectorKt.injector.getOptionGroup().endInitVimRc();
}
executeIdeaVimRc(editor);
}
}

View File

@ -1,32 +1,27 @@
/*
* Copyright 2003-2024 The IdeaVim authors
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.action.ex
package com.maddyhome.idea.vim.action
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf
import java.util.*
@CommandOrMotion(keys = [":"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
internal class ExEntryAction : VimActionHandler.SingleExecution() {
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_START_EX)
override val type: Command.Type = Command.Type.OTHER_READONLY
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
if (editor.isOneLineMode()) return false
injector.processGroup.startExEntry(editor, context, cmd)
VimPlugin.getProcess().startExCommand(editor, context, cmd)
return true
}
}

View File

@ -1,39 +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.action
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler
/**
* Clear and redraw the screen
*
* Typically, an IDE does not need to redraw the screen - it's editor handles scrolling, etc. However, it can be
* necessary to clear the status line.
*/
@CommandOrMotion(keys = ["<C-L>"], modes = [Mode.NORMAL])
internal class RedrawAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_READONLY
override fun execute(
editor: VimEditor,
context: ExecutionContext,
cmd: Command,
operatorArguments: OperatorArguments,
): Boolean {
injector.redrawService.redraw()
return true
}
}

View File

@ -26,7 +26,6 @@ import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.group.EditorHolderService
import com.maddyhome.idea.vim.group.IjOptionConstants
import com.maddyhome.idea.vim.group.IjOptions
import com.maddyhome.idea.vim.handler.enableOctopus
@ -45,7 +44,6 @@ import com.maddyhome.idea.vim.listener.AceJumpService
import com.maddyhome.idea.vim.listener.AppCodeTemplates.appCodeTemplateCaptured
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.ui.ex.ExTextField
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import java.awt.event.InputEvent
import java.awt.event.KeyEvent
@ -254,14 +252,7 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
return null
}
private fun getEditor(e: AnActionEvent): Editor? {
return e.getData(PlatformDataKeys.EDITOR)
?: if (e.getData(PlatformDataKeys.CONTEXT_COMPONENT) is ExTextField) {
EditorHolderService.getInstance().editor
} else {
null
}
}
private fun getEditor(e: AnActionEvent): Editor? = e.getData(PlatformDataKeys.EDITOR)
/**
* Every time the key pressed with an active lookup, there is a decision:

View File

@ -58,11 +58,11 @@ internal class RepeatChangeAction : VimActionHandler.SingleExecution() {
)
} else if (!repeatHandler && lastCommand != null) {
if (cmd.rawCount > 0) {
lastCommand.rawCount = cmd.count
lastCommand.count = cmd.count
val arg = lastCommand.argument
if (arg != null) {
val mot = arg.motion
mot.rawCount = 0
mot.count = 0
}
}
state.executingCommand = lastCommand

View File

@ -0,0 +1,35 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.action.ex
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler
import java.util.*
/**
* Called by KeyHandler to process the contents of the ex entry panel
*
* The mapping for this action means that the ex command is executed as a write action
*/
@CommandOrMotion(keys = ["<CR>", "<C-M>", "<C-J>"], modes = [Mode.CMD_LINE])
public class ProcessExEntryAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
override val flags: EnumSet<CommandFlags> = EnumSet.of(CommandFlags.FLAG_COMPLETE_EX)
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
return VimPlugin.getProcess().processExEntry(editor, context)
}
}

View File

@ -13,46 +13,24 @@ import com.maddyhome.idea.vim.api.VimExOutputPanel
import com.maddyhome.idea.vim.helper.vimExOutput
import com.maddyhome.idea.vim.ui.ExOutputPanel
// TODO: We need a nicer way to handle output, especially wrt testing, appending + clearing
/**
* @author vlan
*/
public class ExOutputModel private constructor(private val myEditor: Editor) : VimExOutputPanel {
private var isActiveInTestMode = false
override val isActive: Boolean
get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.isPanelActive(myEditor)
} else {
isActiveInTestMode
}
override var text: String? = null
get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).text
} else {
field
}
set(value) {
if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).setText(value ?: "")
} else {
field = value
isActiveInTestMode = !value.isNullOrEmpty()
}
}
private set
override fun output(text: String) {
this.text = text
if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).setText(text)
}
}
override fun clear() {
text = null
}
override fun close() {
if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).close()
}
else {
isActiveInTestMode = false
ExOutputPanel.getInstance(myEditor).deactivate(false)
}
}

View File

@ -24,6 +24,7 @@ import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.common.CommandAlias
import com.maddyhome.idea.vim.common.CommandAliasHandler
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.helper.CommandLineHelper
import com.maddyhome.idea.vim.helper.TestInputModel
import com.maddyhome.idea.vim.helper.noneOfEnum
import com.maddyhome.idea.vim.helper.vimStateMachine
@ -182,7 +183,7 @@ public object VimExtensionFacade {
/** Returns a string typed in the input box similar to 'input()'. */
@JvmStatic
public fun inputString(editor: Editor, context: DataContext, prompt: String, finishOn: Char?): String {
return injector.commandLine.inputString(editor.vim, context.vim, prompt, finishOn) ?: ""
return service<CommandLineHelper>().inputString(editor.vim, context.vim, prompt, finishOn) ?: ""
}
/** Get the current contents of the given register similar to 'getreg()'. */

View File

@ -32,8 +32,7 @@ import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.command.TextObjectVisualType
import com.maddyhome.idea.vim.common.CommandAliasHandler
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ranges.Range
import com.maddyhome.idea.vim.ex.ranges.toTextRange
import com.maddyhome.idea.vim.ex.ranges.Ranges
import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.extension.VimExtensionFacade
@ -249,14 +248,8 @@ internal class CommentaryExtension : VimExtension {
* Used like `:1,3Commentary` or `g/fun/Commentary`
*/
private class CommentaryCommandAliasHandler : CommandAliasHandler {
override fun execute(command: String, range: Range, editor: VimEditor, context: ExecutionContext) {
Util.doCommentary(
editor,
context,
range.getLineRange(editor, editor.primaryCaret()).toTextRange(editor),
SelectionType.LINE_WISE,
false
)
override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) {
Util.doCommentary(editor, context, ranges.getTextRange(editor, -1), SelectionType.LINE_WISE, false)
}
}
}

View File

@ -10,7 +10,7 @@ package com.maddyhome.idea.vim.extension.highlightedyank
import com.intellij.ide.ui.LafManager
import com.intellij.ide.ui.LafManagerListener
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.colors.EditorColors
import com.intellij.openapi.editor.markup.EffectType
@ -19,24 +19,25 @@ import com.intellij.openapi.editor.markup.HighlighterTargetArea
import com.intellij.openapi.editor.markup.RangeHighlighter
import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.util.Disposer
import com.intellij.util.Alarm
import com.intellij.util.Alarm.ThreadToUse
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.VimProjectService
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.StrictMode
import com.maddyhome.idea.vim.helper.VimNlsSafe
import com.maddyhome.idea.vim.listener.VimInsertListener
import com.maddyhome.idea.vim.listener.VimYankListener
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import org.jetbrains.annotations.NonNls
import java.awt.Color
import java.awt.Font
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
internal const val DEFAULT_HIGHLIGHT_DURATION: Int = 300
internal const val DEFAULT_HIGHLIGHT_DURATION: Long = 300
@NonNls
private val HIGHLIGHT_DURATION_VARIABLE_NAME = "highlightedyank_highlight_duration"
@ -77,128 +78,111 @@ internal class HighlightColorResetter : LafManagerListener {
*/
internal class VimHighlightedYank : VimExtension, VimYankListener, VimInsertListener {
private val highlightHandler = HighlightHandler()
private var initialised = false
override fun getName() = "highlightedyank"
override fun init() {
// Note that these listeners will still be registered when IdeaVim is disabled. However, they'll never get called
VimPlugin.getYank().addListener(this)
VimPlugin.getChange().addInsertListener(this)
// Register our own disposable to remove highlights when IdeaVim is disabled. Somehow, we need to re-register when
// IdeaVim is re-enabled. We don't get a call back for that, but because the listeners are active until the
// _extension_ is disabled, make sure we're properly initialised each time we're called.
registerIdeaVimDisabledCallback()
initialised = true
}
private fun registerIdeaVimDisabledCallback() {
// TODO: IdeaVim should help with lifecycle management here - VIM-3419
// IdeaVim should possibly unregister extensions, but it would also need to re-register them. We have to do this
// state management manually for now
Disposer.register(VimPlugin.getInstance().onOffDisposable) {
highlightHandler.clearYankHighlighters()
initialised = false
}
}
override fun dispose() {
// Called when the extension is disabled with `:set no{extension}` or if the plugin owning the extension unloads
VimPlugin.getYank().removeListener(this)
VimPlugin.getChange().removeInsertListener(this)
highlightHandler.clearYankHighlighters()
initialised = false
}
override fun yankPerformed(editor: VimEditor, range: TextRange) {
ensureInitialised()
highlightHandler.highlightYankRange(editor.ij, range)
}
override fun insertModeStarted(editor: Editor) {
ensureInitialised()
highlightHandler.clearYankHighlighters()
}
private fun ensureInitialised() {
if (!initialised) {
registerIdeaVimDisabledCallback()
initialised = true
}
highlightHandler.clearAllYankHighlighters()
}
private class HighlightHandler {
private val alarm = Alarm(ThreadToUse.SWING_THREAD)
private var lastEditor: Editor? = null
private val highlighters = mutableSetOf<RangeHighlighter>()
private var editor: Editor? = null
private val yankHighlighters: MutableSet<RangeHighlighter> = mutableSetOf()
fun highlightYankRange(editor: Editor, range: TextRange) {
// from vim-highlightedyank docs: When a new text is yanked or user starts editing, the old highlighting would be deleted
clearYankHighlighters()
clearAllYankHighlighters()
lastEditor = editor
val attributes = getHighlightTextAttributes(editor)
for (i in 0 until range.size()) {
val highlighter = editor.markupModel.addRangeHighlighter(
range.startOffsets[i],
range.endOffsets[i],
HighlighterLayer.SELECTION,
attributes,
HighlighterTargetArea.EXACT_RANGE,
)
highlighters.add(highlighter)
}
// from vim-highlightedyank docs: A negative number makes the highlight persistent.
val timeout = extractUsersHighlightDuration()
if (timeout >= 0) {
// Note modality. This is important when highlighting an editor when a modal dialog is open, such as the resolve
// conflict diff view
alarm.addRequest(
{ clearYankHighlighters() },
timeout,
ModalityState.any()
)
}
}
fun clearYankHighlighters() {
alarm.cancelAllRequests()
// Make sure the last editor we saved is still alive before we use it. We can't just use the list of open editors
// because this list is empty when IdeaVim is disabled, so we're unable to clean up
lastEditor?.let { editor ->
if (!editor.isDisposed) {
highlighters.forEach { highlighter -> editor.markupModel.removeHighlighter(highlighter) }
this.editor = editor
val project = editor.project
if (project != null) {
Disposer.register(
VimProjectService.getInstance(project),
) {
this.editor = null
yankHighlighters.clear()
}
}
lastEditor = null
highlighters.clear()
if (range.isMultiple) {
for (i in 0 until range.size()) {
highlightSingleRange(editor, range.startOffsets[i]..range.endOffsets[i])
}
} else {
highlightSingleRange(editor, range.startOffset..range.endOffset)
}
}
private fun getHighlightTextAttributes(editor: Editor) = TextAttributes(
fun clearAllYankHighlighters() {
yankHighlighters.forEach { highlighter ->
editor?.markupModel?.removeHighlighter(highlighter) ?: StrictMode.fail("Highlighters without an editor")
}
yankHighlighters.clear()
}
private fun highlightSingleRange(editor: Editor, range: ClosedRange<Int>) {
val highlighter = editor.markupModel.addRangeHighlighter(
range.start,
range.endInclusive,
HighlighterLayer.SELECTION,
getHighlightTextAttributes(),
HighlighterTargetArea.EXACT_RANGE,
)
yankHighlighters.add(highlighter)
setClearHighlightRangeTimer(highlighter)
}
private fun setClearHighlightRangeTimer(highlighter: RangeHighlighter) {
val timeout = extractUsersHighlightDuration()
// from vim-highlightedyank docs: A negative number makes the highlight persistent.
if (timeout >= 0) {
Executors.newSingleThreadScheduledExecutor().schedule(
{
ApplicationManager.getApplication().invokeLater {
editor?.markupModel?.removeHighlighter(highlighter) ?: StrictMode.fail("Highlighters without an editor")
}
},
timeout,
TimeUnit.MILLISECONDS,
)
}
}
private fun getHighlightTextAttributes() = TextAttributes(
null,
extractUsersHighlightColor(),
editor.colorsScheme.getColor(EditorColors.CARET_COLOR),
editor?.colorsScheme?.getColor(EditorColors.CARET_COLOR),
EffectType.SEARCH_MATCH,
Font.PLAIN,
)
private fun extractUsersHighlightDuration(): Int {
private fun extractUsersHighlightDuration(): Long {
return extractVariable(HIGHLIGHT_DURATION_VARIABLE_NAME, DEFAULT_HIGHLIGHT_DURATION) {
// toVimNumber will return 0 for an invalid string. Let's force it to throw
when (it) {
is VimString -> it.value.toInt()
else -> it.toVimNumber().value
}
it.toLong()
}
}
private fun extractUsersHighlightColor(): Color {
return extractVariable(HIGHLIGHT_COLOR_VARIABLE_NAME, getDefaultHighlightTextColor()) { value ->
val rgba = value.asString()
val rgba = value
.substring(4)
.filter { it != '(' && it != ')' && !it.isWhitespace() }
.split(',')
@ -208,11 +192,12 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, VimInsertList
}
}
private fun <T> extractVariable(variable: String, default: T, extractFun: (value: VimDataType) -> T): T {
private fun <T> extractVariable(variable: String, default: T, extractFun: (value: String) -> T): T {
val value = VimPlugin.getVariableService().getGlobalVariableValue(variable)
if (value != null) {
if (value is VimString) {
return try {
extractFun(value)
extractFun(value.value)
} catch (e: Exception) {
@VimNlsSafe val message = MessageHelper.message(
"highlightedyank.invalid.value.of.0.1",

View File

@ -37,7 +37,7 @@ import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.CommandAlias
import com.maddyhome.idea.vim.common.CommandAliasHandler
import com.maddyhome.idea.vim.ex.ranges.Range
import com.maddyhome.idea.vim.ex.ranges.Ranges
import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.group.KeyGroup
import com.maddyhome.idea.vim.helper.MessageHelper
@ -137,13 +137,13 @@ internal class NerdTree : VimExtension {
}
class IjCommandHandler(private val actionId: String) : CommandAliasHandler {
override fun execute(command: String, range: Range, editor: VimEditor, context: ExecutionContext) {
override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) {
Util.callAction(editor, actionId, context)
}
}
class ToggleHandler : CommandAliasHandler {
override fun execute(command: String, range: Range, editor: VimEditor, context: ExecutionContext) {
override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) {
val project = editor.ij.project ?: return
val toolWindow = ToolWindowManagerEx.getInstanceEx(project).getToolWindow(ToolWindowId.PROJECT_VIEW) ?: return
if (toolWindow.isVisible) {
@ -155,7 +155,7 @@ internal class NerdTree : VimExtension {
}
class CloseHandler : CommandAliasHandler {
override fun execute(command: String, range: Range, editor: VimEditor, context: ExecutionContext) {
override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) {
val project = editor.ij.project ?: return
val toolWindow = ToolWindowManagerEx.getInstanceEx(project).getToolWindow(ToolWindowId.PROJECT_VIEW) ?: return
if (toolWindow.isVisible) {
@ -477,7 +477,6 @@ internal class NerdTree : VimExtension {
"r",
NerdAction.ToIj("SynchronizeCurrentFile"),
)
registerCommand("NERDTreeMapToggleHidden", "I", NerdAction.ToIj("ProjectView.ShowExcludedFiles"))
registerCommand("NERDTreeMapRefreshRoot", "R", NerdAction.ToIj("Synchronize"))
registerCommand("NERDTreeMapMenu", "m", NerdAction.ToIj("ShowPopupMenu"))
registerCommand("NERDTreeMapQuit", "q", NerdAction.ToIj("HideActiveWindow"))

View File

@ -131,11 +131,10 @@ public class VimIndentObject implements VimExtension {
// This is done as a separate step so that it works even when the caret is inside the indentation.
int offset = caretLineStartOffset;
int indentSize = 0;
while (offset < charSequence.length()) {
while (++offset < charSequence.length()) {
final char ch = charSequence.charAt(offset);
if (ch == ' ' || ch == '\t') {
++indentSize;
++offset;
} else {
break;
}

View File

@ -567,7 +567,7 @@ public class ChangeGroup : VimChangeGroupBase() {
): Boolean {
val startLine = range.startLine
val endLine = range.endLine
val count = range.size
val count = endLine - startLine + 1
if (count < 2) {
return false
}

View File

@ -39,12 +39,11 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.intellij.openapi.editor.EditorSettings.*;
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
import static com.maddyhome.idea.vim.api.VimInjectorKt.options;
import static com.maddyhome.idea.vim.newapi.IjVimInjectorKt.ijOptions;
/**
@ -56,31 +55,12 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
private Boolean isKeyRepeat = null;
// TODO: Get rid of this custom line converter once we support soft wraps properly
// The builtin relative line converter looks like it's using Vim's logical lines for counting, where a Vim logical
// line is a buffer line, or a single line representing a fold of several buffer lines. This converter is counting
// screen lines (but badly - if you're on the second line of a wrapped line, it still counts like you're on the first.
// We really want to use Vim logical lines, but we don't currently support them for movement - we move by screen line.
private final CaretListener myLineNumbersCaretListener = new CaretListener() {
@Override
public void caretPositionChanged(@NotNull CaretEvent e) {
// We don't get notified when the IDE's settings change, so make sure we're up-to-date when the caret moves
final Editor editor = e.getEditor();
boolean relativenumber = ijOptions(injector, new IjVimEditor(editor)).getRelativenumber();
if (relativenumber) {
if (!hasRelativeLineNumbersInstalled(editor)) {
installRelativeLineNumbers(editor);
}
else {
// We must repaint on each caret move, so we update when caret's visual line doesn't match logical line
repaintRelativeLineNumbers(editor);
}
}
else {
if (hasRelativeLineNumbersInstalled(editor)) {
removeRelativeLineNumbers(editor);
}
final boolean requiresRepaint = e.getNewPosition().line != e.getOldPosition().line;
if (requiresRepaint && options(injector, new IjVimEditor(e.getEditor())).getRelativenumber()) {
repaintRelativeLineNumbers(e.getEditor());
}
}
};
@ -93,10 +73,11 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
editor.getCaretModel().addCaretListener(myLineNumbersCaretListener);
UserDataManager.setVimEditorGroup(editor, true);
UserDataManager.setVimLineNumbersInitialState(editor, editor.getSettings().isLineNumbersShown());
updateLineNumbers(editor);
}
private void deinitLineNumbers(@NotNull Editor editor) {
private void deinitLineNumbers(@NotNull Editor editor, boolean isReleasing) {
if (isProjectDisposed(editor) || !supportsVimLineNumbers(editor) || !UserDataManager.getVimEditorGroup(editor)) {
return;
}
@ -105,6 +86,14 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
UserDataManager.setVimEditorGroup(editor, false);
removeRelativeLineNumbers(editor);
// Don't reset the built in line numbers if we're releasing the editor. If we do, EditorSettings.setLineNumbersShown
// can cause the editor to refresh settings and can call into FileManagerImpl.getCachedPsiFile AFTER FileManagerImpl
// has been disposed (Closing the project with a Find Usages result showing a preview panel is a good repro case).
// See IDEA-184351 and VIM-1671
if (!isReleasing) {
setBuiltinLineNumbers(editor, UserDataManager.getVimLineNumbersInitialState(editor));
}
}
private static boolean supportsVimLineNumbers(final @NotNull Editor editor) {
@ -118,22 +107,41 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
}
private static void updateLineNumbers(final @NotNull Editor editor) {
final boolean isLineNumbersShown = editor.getSettings().isLineNumbersShown();
if (!isLineNumbersShown) {
return;
final EffectiveOptions options = options(injector, new IjVimEditor(editor));
final boolean relativeNumber = options.getRelativenumber();
final boolean number = options.getNumber();
final boolean showBuiltinEditorLineNumbers = shouldShowBuiltinLineNumbers(editor, number, relativeNumber);
final EditorSettings settings = editor.getSettings();
if (settings.isLineNumbersShown() ^ showBuiltinEditorLineNumbers) {
// Update line numbers later since it may be called from a caret listener
// on the caret move and it may move the caret internally
ApplicationManager.getApplication().invokeLater(() -> {
if (editor.isDisposed()) return;
setBuiltinLineNumbers(editor, showBuiltinEditorLineNumbers);
});
}
final LineNumerationType lineNumerationType = editor.getSettings().getLineNumerationType();
if (lineNumerationType == LineNumerationType.RELATIVE || lineNumerationType == LineNumerationType.HYBRID) {
if (relativeNumber) {
if (!hasRelativeLineNumbersInstalled(editor)) {
installRelativeLineNumbers(editor);
}
}
else {
else if (hasRelativeLineNumbersInstalled(editor)) {
removeRelativeLineNumbers(editor);
}
}
private static boolean shouldShowBuiltinLineNumbers(final @NotNull Editor editor, boolean number, boolean relativeNumber) {
final boolean initialState = UserDataManager.getVimLineNumbersInitialState(editor);
return initialState || number || relativeNumber;
}
private static void setBuiltinLineNumbers(final @NotNull Editor editor, boolean show) {
editor.getSettings().setLineNumbersShown(show);
}
private static boolean hasRelativeLineNumbersInstalled(final @NotNull Editor editor) {
return UserDataManager.getVimHasRelativeLineNumbersInstalled(editor);
}
@ -248,8 +256,8 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
updateCaretsVisualAttributes(new IjVimEditor(editor));
}
public void editorDeinit(@NotNull Editor editor) {
deinitLineNumbers(editor);
public void editorDeinit(@NotNull Editor editor, boolean isReleased) {
deinitLineNumbers(editor, isReleased);
UserDataManager.unInitializeEditor(editor);
VimPlugin.getKey().unregisterShortcutKeys(new IjVimEditor(editor));
CaretVisualAttributesHelperKt.removeCaretsVisualAttributes(editor);
@ -314,18 +322,17 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
private static class RelativeLineNumberConverter implements LineNumberConverter {
@Override
public Integer convert(@NotNull Editor editor, int lineNumber) {
final IjVimEditor ijVimEditor = new IjVimEditor(editor);
final boolean number = ijOptions(injector, ijVimEditor).getNumber();
final boolean number = options(injector, new IjVimEditor(editor)).getNumber();
final int caretLine = editor.getCaretModel().getLogicalPosition().line;
// lineNumber is 1 based
if ((lineNumber - 1) == caretLine) {
return number ? lineNumber : 0;
if (number && (lineNumber - 1) == caretLine) {
return lineNumber;
}
else {
final int visualLine = ijVimEditor.bufferLineToVisualLine(lineNumber - 1);
final int caretVisualLine = editor.getCaretModel().getVisualPosition().line;
return Math.abs(caretVisualLine - visualLine);
final int visualLine = new IjVimEditor(editor).bufferLineToVisualLine(lineNumber - 1);
final int currentVisualLine = new IjVimEditor(editor).bufferLineToVisualLine(caretLine);
return Math.abs(currentVisualLine - visualLine);
}
}
@ -364,22 +371,16 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
private Stream<Editor> getLocalEditors() {
// Always fetch local editors. If we're hosting a Code With Me session, any connected guests will create hidden
// editors to handle syntax highlighting, completion requests, etc. We need to make sure that IdeaVim only makes
// changes (e.g., adding search highlights) to local editors so things don't incorrectly flow through to any Clients.
// changes (e.g. adding search highlights) to local editors, so things don't incorrectly flow through to any Clients.
// In non-CWM scenarios, or if IdeaVim is installed on the Client, there are only ever local editors, so this will
// also work there. In Gateway remote development scenarios, IdeaVim should not be installed on the host, only the
// Client, so all should work there too.
// Note that most IdeaVim operations are in response to interactive keystrokes, which would mean that
// ClientEditorManager.getCurrentInstance would return local editors. However, some operations are in response to
// events such as document change (to update search highlights), and these can come from CWM guests, and we'd get
// the remote editors.
// This invocation will always get local editors, regardless of the current context.
List<ClientAppSession> appSessions = ClientSessionsManager.getAppSessions(ClientKind.ALL);
if (!appSessions.isEmpty()) {
ClientAppSession localSession = appSessions.get(0);
return localSession.getService(ClientEditorManager.class).editors();
}
else {
return Stream.empty();
}
// events such as document change (to update search highlights) and these can come from CWM guests, and we'd get the
// remote editors.
// This invocation will always get local editors, regardless of current context.
final ClientAppSession localSession = ClientSessionsManager.getAppSessions(ClientKind.LOCAL).get(0);
return localSession.getService(ClientEditorManager.class).editors();
}
}

View File

@ -45,18 +45,6 @@ public open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesB
* As a convenience, this class also provides access to the IntelliJ specific global options, via inheritance.
*/
public class EffectiveIjOptions(scope: OptionAccessScope.EFFECTIVE): GlobalIjOptions(scope) {
// Vim options that are implemented purely by existing IntelliJ features and not used by vim-engine
public var breakindent: Boolean by optionProperty(IjOptions.breakindent)
public val colorcolumn: StringListOptionValue by optionProperty(IjOptions.colorcolumn)
public var cursorline: Boolean by optionProperty(IjOptions.cursorline)
public var fileformat: String by optionProperty(IjOptions.fileformat)
public var list: Boolean by optionProperty(IjOptions.list)
public var number: Boolean by optionProperty(IjOptions.number)
public var relativenumber: Boolean by optionProperty(IjOptions.relativenumber)
public var textwidth: Int by optionProperty(IjOptions.textwidth)
public var wrap: Boolean by optionProperty(IjOptions.wrap)
// IntelliJ specific options
public var ideacopypreprocess: Boolean by optionProperty(IjOptions.ideacopypreprocess)
public var ideajoin: Boolean by optionProperty(IjOptions.ideajoin)
public var idearefactormode: String by optionProperty(IjOptions.idearefactormode)

View File

@ -10,14 +10,9 @@ package com.maddyhome.idea.vim.group
import com.intellij.openapi.application.ApplicationNamesInfo
import com.maddyhome.idea.vim.api.Options
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.ex.exExceptionMessage
import com.maddyhome.idea.vim.options.NumberOption
import com.maddyhome.idea.vim.options.Option
import com.maddyhome.idea.vim.options.OptionDeclaredScope.GLOBAL
import com.maddyhome.idea.vim.options.OptionDeclaredScope.GLOBAL_OR_LOCAL_TO_BUFFER
import com.maddyhome.idea.vim.options.OptionDeclaredScope.LOCAL_TO_BUFFER
import com.maddyhome.idea.vim.options.OptionDeclaredScope.LOCAL_TO_WINDOW
import com.maddyhome.idea.vim.options.StringListOption
import com.maddyhome.idea.vim.options.StringOption
import com.maddyhome.idea.vim.options.ToggleOption
@ -38,56 +33,6 @@ public object IjOptions {
Options.overrideDefaultValue(Options.clipboard, VimString("ideaput,autoselect,exclude:cons\\|linux"))
}
// Vim options that are implemented purely by existing IntelliJ features and not used by vim-engine
public val breakindent: ToggleOption = addOption(ToggleOption("breakindent", LOCAL_TO_WINDOW, "bri", false))
public val colorcolumn: StringListOption = addOption(object : StringListOption("colorcolumn", LOCAL_TO_WINDOW, "cc", "") {
override fun checkIfValueValid(value: VimDataType, token: String) {
super.checkIfValueValid(value, token)
if (value != VimString.EMPTY) {
// Each element in the comma-separated string list needs to be a number. No spaces. Vim supports numbers
// beginning "+" or "-" to draw a highlight column relative to the 'textwidth' value. We don't fully support
// that, but we do automatically add "+0" because IntelliJ always displays the right margin
split((value as VimString).asString()).forEach {
if (!it.matches(Regex("[+-]?[0-9]+"))) {
throw exExceptionMessage("E474", token)
}
}
}
}
})
public val cursorline: ToggleOption = addOption(ToggleOption("cursorline", LOCAL_TO_WINDOW, "cul", false))
public val list: ToggleOption = addOption(ToggleOption("list", LOCAL_TO_WINDOW, "list", false))
public val number: ToggleOption = addOption(ToggleOption("number", LOCAL_TO_WINDOW, "nu", false))
public val relativenumber: ToggleOption = addOption(ToggleOption("relativenumber", LOCAL_TO_WINDOW, "rnu", false))
public val textwidth: NumberOption = addOption(UnsignedNumberOption("textwidth", LOCAL_TO_BUFFER, "tw", 0))
public val wrap: ToggleOption = addOption(ToggleOption("wrap", LOCAL_TO_WINDOW, "wrap", true))
// These options are not explicitly listed as local-noglobal in Vim's help, but are set when a new buffer is edited,
// based on the value of 'fileformats' or 'fileencodings'. To prevent unexpected file cnversion, we treat them as
// local-noglobal. See `:help local-noglobal`, `:help 'fileformats'` and `:help 'fileencodings'`
public val bomb: ToggleOption =
addOption(ToggleOption("bomb", LOCAL_TO_BUFFER, "bomb", false, isLocalNoGlobal = true))
public val fileencoding: StringOption = addOption(
StringOption(
"fileencoding",
LOCAL_TO_BUFFER,
"fenc",
VimString.EMPTY,
isLocalNoGlobal = true
)
)
public val fileformat: StringOption = addOption(
StringOption(
"fileformat",
LOCAL_TO_BUFFER,
"ff",
if (injector.systemInfoService.isWindows) "dos" else "unix",
boundedValues = setOf("dos", "unix", "mac"),
isLocalNoGlobal = true
)
)
// IntelliJ specific functionality - custom options
public val ide: StringOption = addOption(
StringOption("ide", GLOBAL, "ide", ApplicationNamesInfo.getInstance().fullProductNameWithEdition)
)

View File

@ -1,50 +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.group
import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.editor.event.DocumentListener
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.VimRedrawService
import com.maddyhome.idea.vim.api.injector
public class IjVimRedrawService : VimRedrawService {
override fun redraw() {
// The only thing IntelliJ needs to redraw is the status line. Everything else is handled automatically.
redrawStatusLine()
}
override fun redrawStatusLine() {
injector.messages.clearStatusBarMessage()
}
public companion object {
/**
* Simulate Vim's redraw when the current editor changes
*/
public fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
injector.redrawService.redraw()
}
}
/**
* Simulates Vim's redraw when the document changes
*
* Only redraw if lines are added/removed.
*/
public object RedrawListener : DocumentListener {
override fun documentChanged(event: DocumentEvent) {
if (VimPlugin.isNotEnabled()) return
if (event.newFragment.contains("\n") || event.oldFragment.contains("\n")) {
injector.redrawService.redraw()
}
}
}
}

View File

@ -26,7 +26,6 @@ import com.maddyhome.idea.vim.api.VimMotionGroupBase
import com.maddyhome.idea.vim.api.anyNonWhitespace
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
import com.maddyhome.idea.vim.api.getVisualLineCount
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.lineLength
import com.maddyhome.idea.vim.api.normalizeVisualColumn
import com.maddyhome.idea.vim.api.normalizeVisualLine
@ -47,12 +46,13 @@ import com.maddyhome.idea.vim.helper.getNormalizedScrollOffset
import com.maddyhome.idea.vim.helper.getNormalizedSideScrollOffset
import com.maddyhome.idea.vim.helper.isEndAllowed
import com.maddyhome.idea.vim.helper.vimLastColumn
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.listener.AppCodeTemplates
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
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 kotlin.math.max
import kotlin.math.min
@ -302,21 +302,16 @@ internal class MotionGroup : VimMotionGroupBase() {
}
fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
ExEntryPanel.deactivateAll()
val fileEditor = event.oldEditor
if (fileEditor is TextEditor) {
val editor = fileEditor.editor
if (!editor.isDisposed) {
ExOutputModel.getInstance(editor).clear()
editor.vim.let { vimEditor ->
when (vimEditor.vimStateMachine.mode) {
is Mode.VISUAL -> {
vimEditor.exitVisualMode()
KeyHandler.getInstance().reset(vimEditor)
}
is Mode.CMD_LINE -> {
injector.processGroup.cancelExEntry(vimEditor, false)
ExOutputModel.getInstance(editor).clear()
}
else -> {}
if (VimStateMachine.getInstance(vimEditor).mode is Mode.VISUAL) {
vimEditor.exitVisualMode()
KeyHandler.getInstance().reset(vimEditor)
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@ import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.process.CapturingProcessHandler
import com.intellij.execution.process.ProcessAdapter
import com.intellij.execution.process.ProcessEvent
import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.progress.ProgressIndicatorProvider
@ -27,20 +28,25 @@ import com.maddyhome.idea.vim.api.VimProcessGroupBase
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.ex.InvalidCommandException
import com.maddyhome.idea.vim.helper.requestFocus
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.Mode.NORMAL
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd
import com.maddyhome.idea.vim.state.mode.inVisualMode
import com.maddyhome.idea.vim.state.mode.returnTo
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
import java.io.BufferedWriter
import java.io.IOException
import java.io.OutputStreamWriter
import java.io.Reader
import java.io.Writer
import javax.swing.KeyStroke
import javax.swing.SwingUtilities
public class ProcessGroup : VimProcessGroupBase() {
override var lastCommand: String? = null
@ -48,13 +54,25 @@ public class ProcessGroup : VimProcessGroupBase() {
override var isCommandProcessing: Boolean = false
override var modeBeforeCommandProcessing: Mode? = null
public override fun startExEntry(
editor: VimEditor,
context: ExecutionContext,
command: Command,
label: String,
initialCommandText: String,
) {
public override fun startSearchCommand(editor: VimEditor, context: ExecutionContext, count: Int, leader: Char) {
// Don't allow searching in one line editors
if (editor.isOneLineMode()) return
val initText = ""
val label = leader.toString()
val panel = ExEntryPanel.getInstance()
panel.activate(editor.ij, context.ij, label, initText, count)
}
public override fun endSearchCommand(): String {
val panel = ExEntryPanel.getInstance()
panel.deactivate(true)
return panel.text
}
public override fun startExCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
// Don't allow ex commands in one line editors
if (editor.isOneLineMode()) return
@ -65,24 +83,11 @@ public class ProcessGroup : VimProcessGroupBase() {
isCommandProcessing = true
modeBeforeCommandProcessing = currentMode
// Make sure the Visual selection marks are up to date before we use them.
val initText = getRange(editor, cmd)
injector.markService.setVisualSelectionMarks(editor)
val rangeText = getRange(editor, command)
// Note that we should remove selection and reset caret offset before we switch back to Normal mode and then enter
// Command-line mode. However, some IdeaVim commands can handle multiple carets, including multiple carets with
// selection (which might or might not be a block selection). Unfortunately, because we're just entering
// Command-line mode, we don't know which command is going to be entered, so we can't remove selection here.
// Therefore, we switch to Normal and then Command-line even though we might still have a Visual selection...
// On the plus side, it means we still show selection while editing the command line, which Vim also does
// (Normal then Command-line is not strictly necessary, but done for completeness and autocmd)
// Caret selection is finally handled in Command.execute
editor.mode = Mode.NORMAL()
editor.mode = Mode.CMD_LINE(currentMode)
injector.commandLine.create(editor, context, ":", rangeText + initialCommandText, 1)
val panel = ExEntryPanel.getInstance()
panel.activate(editor.ij, context.ij, ":", initText, 1)
}
public override fun processExKey(editor: VimEditor, stroke: KeyStroke, processResultBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean {
@ -98,27 +103,86 @@ public class ProcessGroup : VimProcessGroupBase() {
return true
} else {
processResultBuilder.addExecutionStep { _, lambdaEditor, _ ->
lambdaEditor.mode = Mode.NORMAL()
lambdaEditor.mode = NORMAL()
getInstance().reset(lambdaEditor)
}
return false
}
}
public override fun processExEntry(editor: VimEditor, context: ExecutionContext): Boolean {
val panel = ExEntryPanel.getInstance()
panel.deactivate(true)
var res = true
try {
editor.mode = NORMAL()
logger.debug("processing command")
val text = panel.text
if (panel.label != ":") {
// Search is handled via Argument.Type.EX_STRING. Although ProcessExEntryAction is registered as the handler for
// <CR> in both command and search modes, it's only invoked for command mode (see KeyHandler.handleCommandNode).
// We should never be invoked for anything other than an actual ex command.
throw InvalidCommandException("Expected ':' command. Got '" + panel.label + "'", text)
}
logger.debug {
"swing=" + SwingUtilities.isEventDispatchThread()
}
injector.vimscriptExecutor.execute(text, editor, context, skipHistory(editor), true, CommandLineVimLContext)
} catch (e: ExException) {
VimPlugin.showMessage(e.message)
VimPlugin.indicateError()
res = false
} catch (bad: Exception) {
logger.error(bad)
VimPlugin.indicateError()
res = false
} finally {
isCommandProcessing = false
modeBeforeCommandProcessing = null
}
return res
}
// commands executed from map command / macro should not be added to history
private fun skipHistory(editor: VimEditor): Boolean {
return getInstance(editor).mappingState.isExecutingMap() || injector.macro.isExecutingMacro
}
public override fun cancelExEntry(editor: VimEditor, resetCaret: Boolean) {
// If 'cpoptions' contains 'x', then Escape should execute the command line. This is the default for Vi but not Vim.
// IdeaVim does not (currently?) support 'cpoptions', so sticks with Vim's default behaviour. Escape cancels.
editor.mode = editor.mode.returnTo()
editor.mode = NORMAL()
getInstance().reset(editor)
val panel = ExEntryPanel.getInstance()
panel.deactivate(true, resetCaret)
}
private fun getRange(editor: VimEditor, cmd: Command) = when {
editor.inVisualMode -> "'<,'>"
cmd.rawCount == 1 -> "."
cmd.rawCount > 1 -> ".,.+" + (cmd.count - 1)
else -> ""
public override fun startFilterCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
val initText = getRange(editor, cmd) + "!"
val currentMode = editor.mode
check(currentMode is ReturnableFromCmd) { "Cannot enable cmd mode from $currentMode" }
editor.mode = Mode.CMD_LINE(currentMode)
val panel = ExEntryPanel.getInstance()
panel.activate(editor.ij, context.ij, ":", initText, 1)
}
private fun getRange(editor: VimEditor, cmd: Command): String {
var initText = ""
if (editor.vimStateMachine.mode is VISUAL) {
initText = "'<,'>"
} else if (cmd.rawCount > 0) {
initText = if (cmd.count == 1) {
"."
} else {
".,.+" + (cmd.count - 1)
}
}
return initText
}
@Throws(ExecutionException::class, ProcessCanceledException::class)

View File

@ -24,6 +24,7 @@ import com.maddyhome.idea.vim.helper.ScrollViewHelper
import com.maddyhome.idea.vim.helper.StrictMode
import com.maddyhome.idea.vim.helper.getNormalizedScrollOffset
import com.maddyhome.idea.vim.helper.getNormalizedSideScrollOffset
import com.maddyhome.idea.vim.helper.vimEditorGroup
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.EffectiveOptionValueChangeListener
@ -257,7 +258,11 @@ internal class ScrollGroup : VimScrollGroup {
object ScrollOptionsChangeListener : EffectiveOptionValueChangeListener {
override fun onEffectiveValueChanged(editor: VimEditor) {
editor.ij.apply { ScrollViewHelper.scrollCaretIntoView(this) }
editor.ij.apply {
if (vimEditorGroup) {
ScrollViewHelper.scrollCaretIntoView(this)
}
}
}
}

View File

@ -24,7 +24,6 @@ import com.intellij.openapi.fileEditor.FileEditorManagerEvent;
import com.intellij.openapi.util.Ref;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.*;
import com.maddyhome.idea.vim.command.MotionType;
import com.maddyhome.idea.vim.common.CharacterPosition;
import com.maddyhome.idea.vim.common.Direction;
import com.maddyhome.idea.vim.common.TextRange;
@ -61,19 +60,23 @@ import static com.maddyhome.idea.vim.helper.SearchHelperKtKt.shouldIgnoreCase;
import static com.maddyhome.idea.vim.newapi.IjVimInjectorKt.globalIjOptions;
import static com.maddyhome.idea.vim.register.RegisterConstants.LAST_SEARCH_REGISTER;
/**
* @deprecated Replace with IjVimSearchGroup
*/
@State(name = "VimSearchSettings", storages = {
@Storage(value = "$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)
})
@Deprecated
/**
* @deprecated Replace with IjVimSearchGroup
*/
public class SearchGroup extends IjVimSearchGroup implements PersistentStateComponent<Element> {
public SearchGroup() {
super();
if (!globalIjOptions(injector).getUseNewRegex()) {
// We use the global option listener instead of the effective listener that gets called for each affected editor
// because we handle updating the affected editors ourselves (e.g., we can filter for visible windows).
// TODO: Investigate migrating these listeners to use the effective value change listener
// This would allow us to update the editor we're told to update, rather than looping over all projects and updating
// the highlights in that project's current document's open editors (see VIM-2779).
// However, we probably only want to update the editors associated with the current document, so maybe the whole
// code needs to be reworked. We're currently using the same update code for changes in the search term as well as
// changes in the search options.
VimPlugin.getOptionGroup().addGlobalOptionChangeListener(Options.hlsearch, () -> {
resetShowSearchHighlight();
forceUpdateSearchHighlights();
@ -147,11 +150,11 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
@Override
protected @Nullable String getLastUsedPattern() {
if (globalIjOptions(injector).getUseNewRegex()) return super.getLastUsedPattern();
return switch (lastPatternIdx) {
case RE_SEARCH -> lastSearch;
case RE_SUBST -> lastSubstitute;
default -> null;
};
switch (lastPatternIdx) {
case RE_SEARCH: return lastSearch;
case RE_SUBST: return lastSubstitute;
}
return null;
}
/**
@ -269,18 +272,11 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
* Can include a trailing offset, e.g. /{pattern}/{offset}, or multiple commands separated by a semicolon.
* If the pattern is empty, the last used (search? substitute?) pattern (and offset?) is used.
* @param dir The direction to search
* @return Pair containing the offset to the next occurrence of the pattern, and the [MotionType] based on
* the search offset. The value will be `null` if no result is found.
* @return Offset to the next occurrence of the pattern or -1 if not found
*/
@Nullable
@Override
public Pair<Integer, MotionType> processSearchCommand(@NotNull VimEditor editor,
@NotNull String command,
int startOffset,
int count1,
@NotNull Direction dir) {
if (globalIjOptions(injector).getUseNewRegex()) return super.processSearchCommand(editor, command, startOffset, count1, dir);
public int processSearchCommand(@NotNull VimEditor editor, @NotNull String command, int startOffset, @NotNull Direction dir) {
if (globalIjOptions(injector).getUseNewRegex()) return super.processSearchCommand(editor, command, startOffset, dir);
boolean isNewPattern = false;
String pattern = null;
@ -288,7 +284,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
final char type = dir == Direction.FORWARDS ? '/' : '?';
if (!command.isEmpty()) {
if (command.length() > 0) {
if (command.charAt(0) != type) {
CharPointer p = new CharPointer(command);
CharPointer end = RegExp.skip_regexp(p.ref(0), type, true);
@ -340,19 +336,17 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
// Direction is saved in do_search
// IgnoreSmartCase is only ever set for searching words (`*`, `#`, `g*`, etc.) and is reset for all other operations
if (pattern == null || pattern.isEmpty()) {
if (pattern == null || pattern.length() == 0) {
pattern = getLastSearchPattern();
if (pattern == null || pattern.isEmpty()) {
patternOffset = lastPatternOffset;
if (pattern == null || pattern.length() == 0) {
isNewPattern = true;
pattern = getLastSubstitutePattern();
if (pattern == null || pattern.isEmpty()) {
if (pattern == null || pattern.length() == 0) {
VimPlugin.showMessage(MessageHelper.message("e_noprevre"));
return null;
return -1;
}
}
if (patternOffset == null || patternOffset.isEmpty()) {
patternOffset = lastPatternOffset;
}
}
// Save the pattern. If it's explicitly entered, or comes from RE_SUBST, isNewPattern is true, and this becomes
@ -371,7 +365,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
resetShowSearchHighlight();
forceUpdateSearchHighlights();
return findItOffset(((IjVimEditor)editor).getEditor(), startOffset, count1, lastDir);
return findItOffset(((IjVimEditor)editor).getEditor(), startOffset, 1, lastDir);
}
/**
@ -413,7 +407,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
resetShowSearchHighlight();
forceUpdateSearchHighlights();
final Pair<Integer, MotionType> result = findItOffset(editor, startOffset, 1, lastDir);
final int result = findItOffset(editor, startOffset, 1, lastDir);
// Set lastPatternOffset AFTER searching so it doesn't affect the result
lastPatternOffset = patternOffset != 0 ? Integer.toString(patternOffset) : "";
@ -424,7 +418,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
logger.debug("lastDir=" + lastDir);
}
return result != null ? result.getFirst() : -1;
return result;
}
/**
@ -469,9 +463,8 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
resetShowSearchHighlight();
forceUpdateSearchHighlights();
final Pair<Integer, MotionType> offsetAndMotion =
findItOffset(((IjVimEditor)editor).getEditor(), range.getStartOffset(), count, lastDir);
return offsetAndMotion == null ? range.getStartOffset() : offsetAndMotion.getFirst();
final int offset = findItOffset(((IjVimEditor)editor).getEditor(), range.getStartOffset(), count, lastDir);
return offset == -1 ? range.getStartOffset() : offset;
}
/**
@ -514,14 +507,14 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
resetShowSearchHighlight();
updateSearchHighlights();
final int startOffset = caret.getOffset();
Pair<Integer, MotionType> offsetAndMotion = findItOffset(editor, startOffset, count, dir);
if (offsetAndMotion != null && offsetAndMotion.getFirst() == startOffset) {
int offset = findItOffset(editor, startOffset, count, dir);
if (offset == startOffset) {
/* Avoid getting stuck on the current cursor position, which can
* happen when an offset is given and the cursor is on the last char
* in the buffer: Repeat with count + 1. */
offsetAndMotion = findItOffset(editor, startOffset, count + 1, dir);
offset = findItOffset(editor, startOffset, count + 1, dir);
}
return offsetAndMotion != null ? offsetAndMotion.getFirst() : -1;
return offset;
}
@ -811,7 +804,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
CharacterPosition startpos = new CharacterPosition(lnum + regmatch.startpos[0].lnum, regmatch.startpos[0].col);
CharacterPosition endpos = new CharacterPosition(lnum + regmatch.endpos[0].lnum, regmatch.endpos[0].col);
int startoff = startpos.toOffset(((IjVimEditor)editor).getEditor());
int endoff = (endpos.line >= lcount) ? (int) editor.fileSize() : endpos.toOffset(((IjVimEditor)editor).getEditor());
int endoff = endpos.toOffset(((IjVimEditor)editor).getEditor());
if (do_all || line != lastLine) {
boolean doReplace = true;
@ -870,23 +863,23 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
lastLine = line;
lnum += nmatch - 1;
if (do_all && startoff != endoff) {
if (newpos != null) {
lnum = newpos.line;
searchcol = newpos.column;
}
else {
lnum += Math.max(1, nmatch - 1);
searchcol = endpos.column;
}
}
else {
lnum += Math.max(1, nmatch - 1);
searchcol = 0;
lnum++;
}
}
else {
lnum += Math.max(1, nmatch - 1);
lnum++;
searchcol = 0;
}
}
@ -897,8 +890,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
VimPlugin.getMotion().moveCaretToLineStartSkipLeading(editor, editor.offsetToBufferPosition(lastMatch).getLine()));
}
else {
// E486: Pattern not found: {0}
VimPlugin.showMessage(MessageHelper.message("E486", pattern));
VimPlugin.showMessage(MessageHelper.message(Msg.e_patnotf2, pattern));
}
}
@ -960,17 +952,17 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
if (which_pat == RE_LAST) {
which_pat = lastPatternIdx;
}
String errorMessage = switch (which_pat) {
case RE_SEARCH -> {
String errorMessage = null;
switch (which_pat) {
case RE_SEARCH:
pattern = lastSearch;
yield MessageHelper.message("e_nopresub");
}
case RE_SUBST -> {
errorMessage = MessageHelper.message("e_nopresub");
break;
case RE_SUBST:
pattern = lastSubstitute;
yield MessageHelper.message("e_noprevre");
}
default -> null;
};
errorMessage = MessageHelper.message("e_noprevre");
break;
}
// Pattern was never defined
if (pattern == null) {
@ -1220,33 +1212,27 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
final Document document = event.getDocument();
for (VimEditor vimEditor : injector.getEditorGroup().getEditors(new IjVimDocument(document))) {
final Editor editor = ((IjVimEditor)vimEditor).getEditor();
Collection<RangeHighlighter> existingHighlighters = UserDataManager.getVimLastHighlighters(editor);
if (existingHighlighters == null) {
Collection<RangeHighlighter> hls = UserDataManager.getVimLastHighlighters(editor);
if (hls == null) {
continue;
}
if (logger.isDebugEnabled()) {
logger.debug("hls=" + existingHighlighters);
logger.debug("hls=" + hls);
logger.debug("event=" + event);
}
// We can only re-highlight whole lines, so clear any highlights in the affected lines.
// If we're deleting lines, this will clear + re-highlight the new current line, which hasn't been modified.
// However, we still want to re-highlight this line in case any highlights cross the line boundaries.
// If we're adding lines, this will clear + re-highlight all new lines.
// We can only re-highlight whole lines, so clear any highlights in the affected lines
final LogicalPosition startPosition = editor.offsetToLogicalPosition(event.getOffset());
final LogicalPosition endPosition = editor.offsetToLogicalPosition(event.getOffset() + event.getNewLength());
final int startLineOffset = document.getLineStartOffset(startPosition.line);
final int endLineOffset = document.getLineEndOffset(endPosition.line);
// Remove any highlights that have already been deleted, and remove + clear those that intersect with the change
final Iterator<RangeHighlighter> iter = existingHighlighters.iterator();
final Iterator<RangeHighlighter> iter = hls.iterator();
while (iter.hasNext()) {
final RangeHighlighter highlighter = iter.next();
if (!highlighter.isValid()) {
iter.remove();
}
else if (highlighter.getTextRange().intersects(startLineOffset, endLineOffset)) {
if (!highlighter.isValid() ||
(highlighter.getStartOffset() >= startLineOffset && highlighter.getEndOffset() <= endLineOffset)) {
iter.remove();
editor.getMarkupModel().removeHighlighter(highlighter);
}
@ -1255,9 +1241,9 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
VimPlugin.getSearch().highlightSearchLines(editor, startPosition.line, endPosition.line);
if (logger.isDebugEnabled()) {
existingHighlighters = UserDataManager.getVimLastHighlighters(editor);
hls = UserDataManager.getVimLastHighlighters(editor);
logger.debug("sl=" + startPosition.line + ", el=" + endPosition.line);
logger.debug("hls=" + existingHighlighters);
logger.debug("hls=" + hls);
}
}
}
@ -1283,11 +1269,9 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
* @param startOffset The offset to search from
* @param count Find the nth occurrence
* @param dir The direction to search in
* @return Pair containing the offset to the next occurrence of the pattern, and the [MotionType] based
* on the search offset. The value will be `null` if no result is found.
* @return The offset to the occurrence or -1 if not found
*/
@Nullable
private Pair<Integer, MotionType> findItOffset(@NotNull Editor editor, int startOffset, int count, Direction dir) {
private int findItOffset(@NotNull Editor editor, int startOffset, int count, Direction dir) {
boolean wrap = globalOptions(injector).getWrapscan();
logger.debug("Perform search. Direction: " + dir + " wrap: " + wrap);
@ -1297,7 +1281,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
ParsePosition pp = new ParsePosition(0);
if (!lastPatternOffset.isEmpty()) {
if (lastPatternOffset.length() > 0) {
if (Character.isDigit(lastPatternOffset.charAt(0)) || lastPatternOffset.charAt(0) == '+' || lastPatternOffset.charAt(0) == '-') {
offsetIsLineOffset = true;
@ -1333,18 +1317,6 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
}
}
// `/{pattern}/{offset}` is inclusive if offset contains `e`, and linewise if there's a line offset
final MotionType motionType;
if (offset != 0 && !hasEndOffset) {
motionType = MotionType.LINE_WISE;
}
else if (hasEndOffset) {
motionType = MotionType.INCLUSIVE;
}
else {
motionType = MotionType.EXCLUSIVE;
}
/*
* If there is a character offset, subtract it from the current
* position, so we don't get stuck at "?pat?e+2" or "/pat/s-2".
@ -1366,7 +1338,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
TextRange range = injector.getSearchHelper().findPattern(new IjVimEditor(editor), getLastUsedPattern(), startOffset, count, searchOptions);
if (range == null) {
logger.warn("No range is found");
return null;
return -1;
}
int res = range.getStartOffset();
@ -1374,6 +1346,8 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
if (offsetIsLineOffset) {
int line = editor.offsetToLogicalPosition(range.getStartOffset()).line;
int newLine = EngineEditorHelperKt.normalizeLine(new IjVimEditor(editor), line + offset);
// TODO: Don't move the caret!
res = VimPlugin.getMotion().moveCaretToLineStart(new IjVimEditor(editor), newLine);
}
else if (hasEndOffset || offset != 0) {
@ -1391,19 +1365,16 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
nextDir = Direction.BACKWARDS;
}
else {
return new Pair(res, motionType);
return res;
}
if (lastPatternOffset.length() - ppos > 2) {
ppos++;
}
Pair<Integer, MotionType> offsetAndMotion =
processSearchCommand(new IjVimEditor(editor), lastPatternOffset.substring(ppos + 1), res, 1, nextDir);
res = offsetAndMotion != null ? offsetAndMotion.getFirst() : -1;
res = processSearchCommand(new IjVimEditor(editor), lastPatternOffset.substring(ppos + 1), res, nextDir);
}
return new Pair<Integer, MotionType>(res, motionType);
return res;
}
@ -1419,7 +1390,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
addOptionalTextElement(search, "last-search", lastSearch);
addOptionalTextElement(search, "last-substitute", lastSubstitute);
addOptionalTextElement(search, "last-offset", !lastPatternOffset.isEmpty() ? lastPatternOffset : null);
addOptionalTextElement(search, "last-offset", lastPatternOffset.length() > 0 ? lastPatternOffset : null);
addOptionalTextElement(search, "last-replace", lastReplace);
addOptionalTextElement(search, "last-pattern", lastPatternIdx == RE_SEARCH ? lastSearch : lastSubstitute);
addOptionalTextElement(search, "last-dir", Integer.toString(lastDir.toInt()));

View File

@ -26,11 +26,9 @@ 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.inCommandLineMode
import com.maddyhome.idea.vim.state.mode.inNormalMode
import com.maddyhome.idea.vim.state.mode.inSelectMode
import com.maddyhome.idea.vim.state.mode.inVisualMode
import com.maddyhome.idea.vim.state.mode.returnTo
import com.maddyhome.idea.vim.vimscript.model.options.helpers.IdeaRefactorModeHelper
import com.maddyhome.idea.vim.vimscript.model.options.helpers.isIdeaRefactorModeKeep
import com.maddyhome.idea.vim.vimscript.model.options.helpers.isIdeaRefactorModeSelect
@ -69,11 +67,6 @@ internal object IdeaSelectionControl {
}
if (editor.selectionModel.hasSelection(true)) {
if (editor.vim.inCommandLineMode && editor.vim.mode.returnTo().hasVisualSelection) {
logger.trace { "Modifying selection while in Command-line mode, most likely incsearch" }
return@singleTask
}
if (dontChangeMode(editor)) {
IdeaRefactorModeHelper.correctSelection(editor)
logger.trace { "Selection corrected for refactoring" }

View File

@ -1,32 +1,27 @@
/*
* Copyright 2003-2024 The IdeaVim authors
* 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.ex
package com.maddyhome.idea.vim.helper
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.Service
import com.maddyhome.idea.vim.action.change.Extension
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCommandLine
import com.maddyhome.idea.vim.api.VimCommandLineService
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.TestInputModel
import com.maddyhome.idea.vim.helper.isCloseKeyStroke
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.ui.ModalEntry
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
import java.awt.event.KeyEvent
import javax.swing.KeyStroke
public class ExEntryPanelService : VimCommandLineService {
public override fun getActiveCommandLine(): VimCommandLine? {
return ExEntryPanel.instance
}
@Service
internal class CommandLineHelper : VimCommandLineHelper {
override fun inputString(vimEditor: VimEditor, context: ExecutionContext, prompt: String, finishOn: Char?): String? {
val editor = vimEditor.ij
@ -57,26 +52,27 @@ public class ExEntryPanelService : VimCommandLineService {
} else {
var text: String? = null
// XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for input()
val commandLine = injector.commandLine.create(vimEditor, context, prompt.ifEmpty { " " }, "", 1)
val exEntryPanel = ExEntryPanel.getInstanceWithoutShortcuts()
exEntryPanel.activate(editor, context.ij, prompt.ifEmpty { " " }, "", 1)
ModalEntry.activate(editor.vim) { key: KeyStroke ->
return@activate when {
key.isCloseKeyStroke() -> {
commandLine.deactivate(true)
exEntryPanel.deactivate(true)
false
}
key.keyCode == KeyEvent.VK_ENTER -> {
text = commandLine.text
commandLine.deactivate(true)
text = exEntryPanel.text
exEntryPanel.deactivate(true)
false
}
finishOn != null && key.keyChar == finishOn -> {
commandLine.handleKey(key)
text = commandLine.text
commandLine.deactivate(true)
exEntryPanel.handleKey(key)
text = exEntryPanel.text
exEntryPanel.deactivate(true)
false
}
else -> {
commandLine.handleKey(key)
exEntryPanel.handleKey(key)
true
}
}
@ -87,10 +83,4 @@ public class ExEntryPanelService : VimCommandLineService {
return text
}
}
public override fun create(editor: VimEditor, context: ExecutionContext, label: String, initText: String, count: Int): VimCommandLine {
val panel = ExEntryPanel.getInstance()
panel.activate(editor.ij, context.ij, label, initText, count)
return panel
}
}

View File

@ -8,12 +8,10 @@
package com.maddyhome.idea.vim.helper;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.impl.EditorImpl;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.LightVirtualFile;
import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
@ -54,10 +52,6 @@ public class EditorHelper {
final ScrollingModel scrollingModel = editor.getScrollingModel();
final Rectangle area = scrollingModel.getVisibleAreaOnScrollingFinished();
scrollingModel.scroll(area.x, verticalOffset);
// Simulate Vim's redraw (essentially to clear messages) if the screen is scrolled
if (area.y != verticalOffset && area.y >= 0) {
injector.getRedrawService().redraw();
}
return scrollingModel.getVisibleAreaOnScrollingFinished().y != area.y;
}
@ -65,10 +59,6 @@ public class EditorHelper {
final ScrollingModel scrollingModel = editor.getScrollingModel();
final Rectangle area = scrollingModel.getVisibleAreaOnScrollingFinished();
scrollingModel.scroll(horizontalOffset, area.y);
// Simulate Vim's redraw (essentially to clear messages) if the screen is scrolled
if (area.x != horizontalOffset && area.x >= 0) {
injector.getRedrawService().redraw();
}
}
public static int getVisualLineAtTopOfScreen(final @NotNull Editor editor) {
@ -87,12 +77,10 @@ public class EditorHelper {
public static int getNonNormalizedVisualLineAtBottomOfScreen(final @NotNull Editor editor) {
// The editor will return line numbers of virtual space if the text doesn't reach the end of the visible area
// (either because it's too short, or it's been scrolled up).
// Adjust available height if the ex entry text field is visible
// (either because it's too short, or it's been scrolled up)
final Rectangle visibleArea = getVisibleArea(editor);
final int height = visibleArea.height - getExEntryHeight() - getHorizontalScrollbarHeight(editor);
return getFullVisualLine(editor, visibleArea.y + height, visibleArea.y,
visibleArea.y + height);
return getFullVisualLine(editor, visibleArea.y + visibleArea.height, visibleArea.y,
visibleArea.y + visibleArea.height);
}
public static int getVisualLineAtBottomOfScreen(final @NotNull Editor editor) {
@ -367,40 +355,24 @@ public class EditorHelper {
}
private static int getOffsetToScrollVisualLineToBottomOfScreen(@NotNull Editor editor, int nonNormalisedVisualLine) {
int exPanelHeight = 0;
if (ExEntryPanel.getInstance().isActive()) {
exPanelHeight = ExEntryPanel.getInstance().getHeight();
}
if (ExEntryPanel.getInstanceWithoutShortcuts().isActive()) {
exPanelHeight += ExEntryPanel.getInstanceWithoutShortcuts().getHeight();
}
// Note that we explicitly do not normalise the visual line, as we might be trying to scroll a virtual line, at the
// end of the file.
// Adjust available height if the ex entry text field is visible
// end of the file
final int lineHeight = editor.getLineHeight();
final int screenHeight = getVisibleArea(editor).height - getExEntryHeight() - getHorizontalScrollbarHeight(editor);
final int screenHeight = getVisibleArea(editor).height - exPanelHeight;
final int inlayHeight = EditorUtil.getInlaysHeight(editor, nonNormalisedVisualLine, false);
final int maxInlayHeight = BLOCK_INLAY_MAX_LINE_HEIGHT * lineHeight;
final int y = editor.visualLineToY(nonNormalisedVisualLine) + lineHeight + min(inlayHeight, maxInlayHeight);
return max(0, y - screenHeight);
}
private static int getExEntryHeight() {
if (ExEntryPanel.getInstance().isActive()) {
return ExEntryPanel.getInstance().getHeight();
}
if (ExEntryPanel.getInstanceWithoutShortcuts().isActive()) {
return ExEntryPanel.getInstanceWithoutShortcuts().getHeight();
}
return 0;
}
private static int getHorizontalScrollbarHeight(@NotNull final Editor editor) {
// Horizontal scrollbars on macOS are either transparent AND auto-hide, so we don't need to worry about obscured
// text, or always visible, opaque and outside the content area, so we don't need to adjust for them
// Transparent scrollbars on Windows and Linux are overlays on the editor content area, and always visible. That
// means they can obscure text, so we want to adjust by the scrollbar height. If they are not transparent, then they
// are not overlays and are outside the content area. We don't need to adjust
if (!SystemInfo.isMac && editor instanceof EditorImpl editorImpl && Registry.is("editor.transparent.scrollbar")) {
return editorImpl.getScrollPane().getHorizontalScrollBar().getHeight();
}
return 0;
}
public static void scrollColumnToLeftOfScreen(@NotNull Editor editor, int visualLine, int visualColumn) {
int targetVisualColumn = visualColumn;

View File

@ -8,7 +8,6 @@ package com.maddyhome.idea.vim.helper
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.VisualPosition
import com.intellij.openapi.editor.textarea.TextComponentEditor
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.getVisualLineCount
import com.maddyhome.idea.vim.api.injector
@ -40,9 +39,6 @@ import kotlin.math.roundToInt
internal object ScrollViewHelper {
@JvmStatic
fun scrollCaretIntoView(editor: Editor) {
// TextComponentEditor doesn't support scrolling. We only support TextComponentEditor for the fallback window
if (editor is TextComponentEditor) return
val position = editor.caretModel.visualPosition
scrollCaretIntoViewVertically(editor, position.line)
scrollCaretIntoViewHorizontally(editor, position)

View File

@ -10,10 +10,18 @@ package com.maddyhome.idea.vim.helper;
import com.google.common.collect.Lists;
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx;
import com.intellij.lang.CodeDocumentationAwareCommenter;
import com.intellij.lang.Commenter;
import com.intellij.lang.Language;
import com.intellij.lang.LanguageCommenters;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.spellchecker.SpellCheckerSeveritiesProvider;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
@ -310,17 +318,11 @@ public class SearchHelper {
lnum = lineCount - 1;
//if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG))
// give_warning((char_u *)_(top_bot_msg), TRUE);
if (searchOptions.contains(SearchOptions.SHOW_MESSAGES)) {
VimPlugin.showMessage(MessageHelper.message("message.search.hit.top"));
}
}
else {
lnum = 0;
//if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG))
// give_warning((char_u *)_(bot_top_msg), TRUE);
if (searchOptions.contains(SearchOptions.SHOW_MESSAGES)) {
VimPlugin.showMessage(MessageHelper.message("message.search.hit.bottom"));
}
}
}
//if (got_int || called_emsg || break_loop)
@ -332,15 +334,12 @@ public class SearchHelper {
//if ((options & SEARCH_MSG) == SEARCH_MSG)
if (searchOptions.contains(SearchOptions.SHOW_MESSAGES)) {
if (searchOptions.contains(SearchOptions.WRAP)) {
// E486: Pattern not found: {0}
VimPlugin.showMessage(MessageHelper.message("E486", pattern));
VimPlugin.showMessage(MessageHelper.message(Msg.e_patnotf2, pattern));
}
else if (lnum <= 0) {
// E384: Search hit TOP without match for: {0}
VimPlugin.showMessage(MessageHelper.message(Msg.E384, pattern));
}
else {
// E385: Search hit BOTTOM without match for: {0}
VimPlugin.showMessage(MessageHelper.message(Msg.E385, pattern));
}
}
@ -376,7 +375,7 @@ public class SearchHelper {
final List<TextRange> results = Lists.newArrayList();
if (globalIjOptions(injector).getUseNewRegex()) {
final EnumSet<VimRegexOptions> options = EnumSet.noneOf(VimRegexOptions.class);
final List<VimRegexOptions> options = new ArrayList<>();
if (globalOptions(injector).getSmartcase()) options.add(VimRegexOptions.SMART_CASE);
if (globalOptions(injector).getIgnorecase()) options.add(VimRegexOptions.IGNORE_CASE);
VimEditor vimEditor = new IjVimEditor(editor);
@ -415,7 +414,7 @@ public class SearchHelper {
final CharacterPosition endPos = new CharacterPosition(line + regMatch.endpos[0].lnum,
regMatch.endpos[0].col);
int start = startPos.toOffset(editor);
int end = endPos.line >= lineCount ? editor.getDocument().getTextLength() : endPos.toOffset(editor);
int end = endPos.toOffset(editor);
results.add(new TextRange(start, end));
if (start != end) {

View File

@ -12,20 +12,25 @@ package com.maddyhome.idea.vim.helper
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.colors.EditorColors
import com.intellij.openapi.editor.colors.EditorColorsScheme
import com.intellij.openapi.editor.markup.EffectType
import com.intellij.openapi.editor.markup.HighlighterLayer
import com.intellij.openapi.editor.markup.HighlighterTargetArea
import com.intellij.openapi.editor.markup.RangeHighlighter
import com.intellij.openapi.editor.markup.TextAttributes
import com.maddyhome.idea.vim.api.VimEditor
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.project.ProjectManager
import com.intellij.ui.ColorUtil
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.options
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ranges.LineRange
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.IjVimDocument
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.vim
import org.jetbrains.annotations.Contract
import java.awt.Color
import java.awt.Font
import java.util.*
@ -35,35 +40,20 @@ internal fun updateSearchHighlights(
showHighlights: Boolean,
forceUpdate: Boolean,
) {
updateSearchHighlights(null, pattern, 1, shouldIgnoreSmartCase, showHighlights, -1, null, true, forceUpdate)
updateSearchHighlights(pattern, shouldIgnoreSmartCase, showHighlights, -1, null, true, forceUpdate)
}
internal fun updateIncsearchHighlights(
editor: Editor,
pattern: String,
count1: Int,
forwards: Boolean,
caretOffset: Int,
searchRange: LineRange?,
): Int {
val searchStartOffset = if (searchRange != null && searchRange.startLine < editor.document.lineCount) {
editor.vim.getLineStartOffset(searchRange.startLine)
}
else {
caretOffset
}
val searchStartOffset =
if (searchRange != null) editor.vim.getLineStartOffset(searchRange.startLine) else caretOffset
val showHighlights = injector.options(editor.vim).hlsearch
return updateSearchHighlights(
editor.vim,
pattern,
count1,
false,
showHighlights,
searchStartOffset,
searchRange,
forwards,
false
)
return updateSearchHighlights(pattern, false, showHighlights, searchStartOffset, searchRange, forwards, false)
}
internal fun addSubstitutionConfirmationHighlight(editor: Editor, start: Int, end: Int): RangeHighlighter {
@ -84,12 +74,10 @@ internal fun addSubstitutionConfirmationHighlight(editor: Editor, start: Int, en
}
/**
* Refreshes current search highlights for all visible editors
* Refreshes current search highlights for all editors of currently active text editor/document
*/
private fun updateSearchHighlights(
currentEditor: VimEditor?,
pattern: String?,
count1: Int,
shouldIgnoreSmartCase: Boolean,
showHighlights: Boolean,
initialOffset: Int,
@ -97,82 +85,72 @@ private fun updateSearchHighlights(
forwards: Boolean,
forceUpdate: Boolean,
): Int {
var currentEditorCurrentMatchOffset = -1
var currentMatchOffset = -1
val projectManager = ProjectManager.getInstanceIfCreated() ?: return currentMatchOffset
// Update highlights in all visible editors. We update non-visible editors when they get focus.
// Note that this now includes all editors - main, diff windows, even toolwindows like the Commit editor and consoles
val editors = injector.editorGroup.getEditors().filter {
injector.application.isUnitTest() || it.ij.component.isShowing
}
// TODO: This implementation needs rethinking
// It's a bit weird that we update search highlights across all open projects. It would make more sense to treat top
// level project frame windows as separate applications, but we can't do this because IdeaVim does not maintain state
// per-project.
// So, to be clear, this will loop over each project, and therefore, for each project top-level frame, will update
// search highlights in all editors for the document of the currently selected editor. It does not update highlights
// for editors for the document that are in other projects.
editors.forEach {
val editor = it.ij
var currentMatchOffset = -1
for (project in projectManager.openProjects) {
val current = FileEditorManager.getInstance(project).selectedTextEditor ?: continue
val editors = injector.editorGroup.getEditors(IjVimDocument(current.document))
for (vimEditor in editors) {
val editor = (vimEditor as IjVimEditor).editor
if (editor.project != project) {
continue
}
// Try to keep existing highlights if possible. Update if hlsearch has changed or if the pattern has changed.
// Force update for the situations where the text is the same, but the ignore case values have changed.
// E.g., Use `*` to search for a word (which ignores smartcase), then use `/<Up>` to search for the same pattern,
// which will match smartcase. Or changing the smartcase/ignorecase settings
if (shouldRemoveSearchHighlights(editor, pattern, showHighlights) || forceUpdate) {
removeSearchHighlights(editor)
}
// Try to keep existing highlights if possible. Update if hlsearch has changed or if the pattern has changed.
// Force update for the situations where the text is the same, but the ignore case values have changed.
// E.g. Use `*` to search for a word (which ignores smartcase), then use `/<Up>` to search for the same pattern,
// which will match smartcase. Or changing the smartcase/ignorecase settings
if (shouldRemoveSearchHighlights(editor, pattern, showHighlights) || forceUpdate) {
removeSearchHighlights(editor)
}
if (pattern == null) return@forEach
if (pattern == null) continue
if (shouldAddAllSearchHighlights(editor, pattern, showHighlights)) {
// hlsearch (+ incsearch/noincsearch)
// Make sure the range fits this editor. Note that Vim will use the same range for all windows. E.g., given
// `:1,5s/foo`, Vim will highlight all occurrences of `foo` in the first five lines of all visible windows
val vimEditor = editor.vim
val editorLastLine = vimEditor.lineCount() - 1
val searchStartLine = searchRange?.startLine ?: 0
val searchEndLine = (searchRange?.endLine ?: -1).coerceAtMost(editorLastLine)
if (searchStartLine <= editorLastLine) {
if (shouldAddAllSearchHighlights(editor, pattern, showHighlights)) {
// hlsearch (+ incsearch/noincsearch)
val startLine = searchRange?.startLine ?: 0
val endLine = searchRange?.endLine ?: -1
val results =
injector.searchHelper.findAll(
vimEditor,
pattern,
searchStartLine,
searchEndLine,
shouldIgnoreCase(pattern, shouldIgnoreSmartCase)
)
injector.searchHelper.findAll(IjVimEditor(editor), pattern, startLine, endLine, shouldIgnoreCase(pattern, shouldIgnoreSmartCase))
if (results.isNotEmpty()) {
if (editor === currentEditor?.ij) {
currentMatchOffset = findClosestMatch(results, initialOffset, count1, forwards)
}
currentMatchOffset = findClosestMatch(editor, results, initialOffset, forwards)
highlightSearchResults(editor, pattern, results, currentMatchOffset)
}
}
editor.vimLastSearch = pattern
} else if (shouldAddCurrentMatchSearchHighlight(pattern, showHighlights, initialOffset)) {
// nohlsearch + incsearch. Only highlight the current editor
if (editor === currentEditor?.ij) {
editor.vimLastSearch = pattern
} else if (shouldAddCurrentMatchSearchHighlight(pattern, showHighlights, initialOffset)) {
// nohlsearch + incsearch
val searchOptions = EnumSet.of(SearchOptions.WHOLE_FILE)
if (injector.globalOptions().wrapscan) searchOptions.add(SearchOptions.WRAP)
if (injector.globalOptions().wrapscan) {
searchOptions.add(SearchOptions.WRAP)
}
if (shouldIgnoreSmartCase) searchOptions.add(SearchOptions.IGNORE_SMARTCASE)
if (!forwards) searchOptions.add(SearchOptions.BACKWARDS)
val result = injector.searchHelper.findPattern(it, pattern, initialOffset, count1, searchOptions)
val result = injector.searchHelper.findPattern(IjVimEditor(editor), pattern, initialOffset, 1, searchOptions)
if (result != null) {
val results = listOf(result)
highlightSearchResults(editor, pattern, results, result.startOffset)
currentMatchOffset = result.startOffset
val results = listOf(result)
highlightSearchResults(editor, pattern, results, currentMatchOffset)
}
} else if (shouldMaintainCurrentMatchOffset(pattern, initialOffset)) {
// incsearch. If nothing has changed (e.g. we've edited offset values in `/foo/e+2`) make sure we return the
// current match offset so the caret remains at the current incsarch match
val offset = editor.vimIncsearchCurrentMatchOffset
if (offset != null) {
currentMatchOffset = offset
}
}
} else if (shouldMaintainCurrentMatchOffset(pattern, initialOffset)) {
// incsearch. If nothing has changed (e.g., we've edited offset values in `/foo/e+2`) make sure we return the
// current match offset so the caret remains at the current incsarch match
val offset = editor.vimIncsearchCurrentMatchOffset
if (offset != null && editor === currentEditor?.ij) {
currentMatchOffset = offset
}
}
if (editor === currentEditor?.ij) {
currentEditorCurrentMatchOffset = currentMatchOffset
}
}
return currentEditorCurrentMatchOffset
return currentMatchOffset
}
/**
@ -200,22 +178,37 @@ private fun shouldAddAllSearchHighlights(editor: Editor, newPattern: String?, hl
return hlSearch && newPattern != null && newPattern != editor.vimLastSearch && newPattern != ""
}
private fun findClosestMatch(
results: List<TextRange>,
initialOffset: Int,
count: Int,
forwards: Boolean,
): Int {
private fun findClosestMatch(editor: Editor, results: List<TextRange>, initialOffset: Int, forwards: Boolean): Int {
if (results.isEmpty() || initialOffset == -1) {
return -1
}
val sortedResults = results.sortedBy { it.startOffset }.let { if (!forwards) it.reversed() else it }
val nextIndex = sortedResults.indexOfFirst {
if (forwards) it.startOffset > initialOffset else it.startOffset < initialOffset
val size = editor.fileSize
val max = Collections.max(results) { r1: TextRange, r2: TextRange ->
val d1 = distance(r1, initialOffset, forwards, size)
val d2 = distance(r2, initialOffset, forwards, size)
if (d1 < 0 && d2 >= 0) {
return@max Int.MAX_VALUE
}
d2 - d1
}
if (!injector.globalOptions().wrapscan) {
val start = max.startOffset
if (forwards && start < initialOffset) {
return -1
} else if (start >= initialOffset) {
return -1
}
}
return max.startOffset
}
private fun distance(range: TextRange, pos: Int, forwards: Boolean, size: Int): Int {
val start = range.startOffset
return if (start <= pos) {
if (forwards) size - pos + start else pos - start
} else {
if (forwards) start - pos else pos + size - start
}
val toDrop = (nextIndex + count - 1).let { if (injector.globalOptions().wrapscan) it % results.size else it }
return sortedResults.drop(toDrop).firstOrNull()?.startOffset ?: -1
}
internal fun highlightSearchResults(editor: Editor, pattern: String, results: List<TextRange>, currentMatchOffset: Int) {
@ -233,46 +226,57 @@ internal fun highlightSearchResults(editor: Editor, pattern: String, results: Li
}
private fun highlightMatch(editor: Editor, start: Int, end: Int, current: Boolean, tooltip: String): RangeHighlighter {
val layer = HighlighterLayer.SELECTION - 1
val targetArea = HighlighterTargetArea.EXACT_RANGE
if (!current) {
// If we use a text attribute key, it will update automatically when the editor's colour scheme changes
val highlighter =
editor.markupModel.addRangeHighlighter(EditorColors.TEXT_SEARCH_RESULT_ATTRIBUTES, start, end, layer, targetArea)
highlighter.errorStripeTooltip = tooltip
return highlighter
var attributes = editor.colorsScheme.getAttributes(EditorColors.TEXT_SEARCH_RESULT_ATTRIBUTES)
if (current) {
// This mimics what IntelliJ does with the Find live preview
attributes = attributes.clone()
attributes.effectType = EffectType.ROUNDED_BOX
attributes.effectColor = editor.colorsScheme.getColor(EditorColors.CARET_COLOR)
}
// There isn't a text attribute key for current selection. This means we won't update automatically when the editor's
// colour scheme changes. However, this is only used during incsearch, so it should be replaced pretty quickly. It's a
// small visual glitch that will fix itself quickly. Let's not bother implementing an editor colour scheme listener
// just for this.
// These are the same modifications that the Find live preview does. We could look at using LivePreviewPresentation,
// which might also be useful for text attributes in selection (if we supported that)
val attributes = editor.colorsScheme.getAttributes(EditorColors.TEXT_SEARCH_RESULT_ATTRIBUTES).clone().apply {
effectType = EffectType.ROUNDED_BOX
effectColor = editor.colorsScheme.getColor(EditorColors.CARET_COLOR)
}
return editor.markupModel.addRangeHighlighter(start, end, layer, attributes, targetArea).apply {
errorStripeTooltip = tooltip
if (attributes.errorStripeColor == null) {
attributes.errorStripeColor = getFallbackErrorStripeColor(attributes, editor.colorsScheme)
}
val highlighter = editor.markupModel.addRangeHighlighter(
start,
end,
HighlighterLayer.SELECTION - 1,
attributes,
HighlighterTargetArea.EXACT_RANGE,
)
highlighter.errorStripeTooltip = tooltip
return highlighter
}
/**
* Add search highlight for current match if hlsearch is false, and we're performing incsearch highlights
* Return a valid error stripe colour based on editor background
*
*
* Based on HighlightManager#addRangeHighlight behaviour, which we can't use because it will hide highlights
* when hitting Escape.
*/
private fun getFallbackErrorStripeColor(attributes: TextAttributes, colorsScheme: EditorColorsScheme): Color? {
if (attributes.backgroundColor != null) {
val isDark = ColorUtil.isDark(colorsScheme.defaultBackground)
return if (isDark) attributes.backgroundColor.brighter() else attributes.backgroundColor.darker()
}
return null
}
/**
* Add search highlight for current match if hlsearch is false and we're performing incsearch highlights
*/
@Contract("_, true, _ -> false")
private fun shouldAddCurrentMatchSearchHighlight(pattern: String?, hlSearch: Boolean, initialOffset: Int): Boolean {
return !hlSearch && isIncrementalSearchHighlights(initialOffset) && !pattern.isNullOrEmpty()
return !hlSearch && isIncrementalSearchHighlights(initialOffset) && pattern != null && pattern.isNotEmpty()
}
/**
* Keep the current match offset if the pattern is still valid, and we're performing incremental search highlights
* Keep the current match offset if the pattern is still valid and we're performing incremental search highlights
* This will keep the caret position when editing the offset in e.g. `/foo/e+1`
*/
@Contract("null, _ -> false")
private fun shouldMaintainCurrentMatchOffset(pattern: String?, initialOffset: Int): Boolean {
return !pattern.isNullOrEmpty() && isIncrementalSearchHighlights(initialOffset)
return pattern != null && pattern.isNotEmpty() && isIncrementalSearchHighlights(initialOffset)
}
/**

View File

@ -10,6 +10,7 @@ package com.maddyhome.idea.vim.helper
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.application.ApplicationInfo
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.undo.UndoManager
import com.intellij.openapi.components.Service
@ -21,6 +22,7 @@ import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.ChangesListener
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.SelectionType
@ -41,7 +43,12 @@ internal class UndoRedoHelper : UndoRedoBase() {
val scrollingModel = editor.getScrollingModel()
scrollingModel.accumulateViewportChanges()
performUndo(editor, undoManager, fileEditor)
// [VERSION UPDATE] 241+ remove this if
if (ApplicationInfo.getInstance().build.baselineVersion >= 241) {
undoFor241plus(editor, undoManager, fileEditor)
} else {
undoForLessThan241(undoManager, fileEditor, editor)
}
scrollingModel.flushViewportChanges()
@ -50,7 +57,29 @@ internal class UndoRedoHelper : UndoRedoBase() {
return false
}
private fun performUndo(
private fun undoForLessThan241(
undoManager: UndoManager,
fileEditor: TextEditor,
editor: VimEditor,
) {
if (injector.globalIjOptions().oldundo) {
SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
restoreVisualMode(editor)
} else {
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
editor.runWithChangeTracking {
undoManager.undo(fileEditor)
restoreVisualMode(editor)
}
CommandProcessor.getInstance().runUndoTransparentAction {
removeSelections(editor)
}
}
}
private fun undoFor241plus(
editor: VimEditor,
undoManager: UndoManager,
fileEditor: TextEditor,
@ -82,14 +111,47 @@ internal class UndoRedoHelper : UndoRedoBase() {
val fileEditor = TextEditorProvider.getInstance().getTextEditor(editor.ij)
val undoManager = UndoManager.getInstance(project)
if (undoManager.isRedoAvailable(fileEditor)) {
performRedo(undoManager, fileEditor, editor)
// [VERSION UPDATE] 241+ remove this if
if (ApplicationInfo.getInstance().build.baselineVersion >= 241) {
redoFor241Plus(undoManager, fileEditor, editor)
} else {
redoForLessThan241(undoManager, fileEditor, editor)
}
return true
}
return false
}
private fun performRedo(
private fun redoForLessThan241(
undoManager: UndoManager,
fileEditor: TextEditor,
editor: VimEditor,
) {
if (injector.globalIjOptions().oldundo) {
SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
} else {
undoManager.redo(fileEditor)
CommandProcessor.getInstance().runUndoTransparentAction {
editor.carets().forEach { it.ij.removeSelection() }
}
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
editor.runWithChangeTracking {
undoManager.redo(fileEditor)
// We execute undo one more time if the previous one just restored selection
if (!hasChanges && hasSelection(editor) && undoManager.isRedoAvailable(fileEditor)) {
undoManager.redo(fileEditor)
}
}
CommandProcessor.getInstance().runUndoTransparentAction {
removeSelections(editor)
}
}
}
private fun redoFor241Plus(
undoManager: UndoManager,
fileEditor: TextEditor,
editor: VimEditor,

View File

@ -121,6 +121,7 @@ internal var Editor.vimIncsearchCurrentMatchOffset: Int? by userData()
internal var Editor.vimLastSelectionType: SelectionType? by userData()
internal var Editor.vimStateMachine: VimStateMachine? by userData()
internal var Editor.vimEditorGroup: Boolean by userDataOr { false }
internal var Editor.vimLineNumbersInitialState: Boolean by userDataOr { false }
internal var Editor.vimHasRelativeLineNumbersInstalled: Boolean by userDataOr { false }
internal var Editor.vimMorePanel: ExOutputPanel? by userData()
internal var Editor.vimExOutput: ExOutputModel? by userData()

View File

@ -70,8 +70,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.IjVimRedrawService
import com.maddyhome.idea.vim.group.MotionGroup
import com.maddyhome.idea.vim.group.OptionGroup
import com.maddyhome.idea.vim.group.ScrollGroup
@ -182,8 +180,8 @@ internal object VimListenerManager {
}
val optionGroup = VimPlugin.getOptionGroup()
optionGroup.addEffectiveOptionValueChangeListener(IjOptions.number, EditorGroup.NumberChangeListener.INSTANCE)
optionGroup.addEffectiveOptionValueChangeListener(IjOptions.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
optionGroup.addEffectiveOptionValueChangeListener(Options.number, EditorGroup.NumberChangeListener.INSTANCE)
optionGroup.addEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
optionGroup.addEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
optionGroup.addEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
optionGroup.addGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
@ -214,8 +212,8 @@ internal object VimListenerManager {
EventFacade.getInstance().restoreTypedActionHandler()
val optionGroup = VimPlugin.getOptionGroup()
optionGroup.removeEffectiveOptionValueChangeListener(IjOptions.number, EditorGroup.NumberChangeListener.INSTANCE)
optionGroup.removeEffectiveOptionValueChangeListener(IjOptions.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
optionGroup.removeEffectiveOptionValueChangeListener(Options.number, EditorGroup.NumberChangeListener.INSTANCE)
optionGroup.removeEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
optionGroup.removeEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
optionGroup.removeGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
optionGroup.removeGlobalOptionChangeListener(Options.showmode, modeWidgetOptionListener)
@ -303,7 +301,7 @@ internal object VimListenerManager {
injector.listenersNotifier.notifyEditorCreated(vimEditor)
Disposer.register(listenersDisposable) {
VimPlugin.getEditor().editorDeinit(editor)
VimPlugin.getEditor().editorDeinit(editor, true)
}
}
@ -344,13 +342,11 @@ internal object VimListenerManager {
override fun beforeDocumentChange(event: DocumentEvent) {
VimMarkServiceImpl.MarkUpdater.beforeDocumentChange(event)
SearchGroup.DocumentSearchListener.INSTANCE.beforeDocumentChange(event)
IjVimRedrawService.RedrawListener.beforeDocumentChange(event)
}
override fun documentChanged(event: DocumentEvent) {
VimMarkServiceImpl.MarkUpdater.documentChanged(event)
SearchGroup.DocumentSearchListener.INSTANCE.documentChanged(event)
IjVimRedrawService.RedrawListener.documentChanged(event)
}
}
@ -377,7 +373,6 @@ internal object VimListenerManager {
FileGroup.fileEditorManagerSelectionChangedCallback(event)
VimPlugin.getSearch().fileEditorManagerSelectionChangedCallback(event)
OptionGroup.fileEditorManagerSelectionChangedCallback(event)
IjVimRedrawService.fileEditorManagerSelectionChangedCallback(event)
}
}

View File

@ -24,7 +24,6 @@ import com.maddyhome.idea.vim.api.VimApplication
import com.maddyhome.idea.vim.api.VimChangeGroup
import com.maddyhome.idea.vim.api.VimClipboardManager
import com.maddyhome.idea.vim.api.VimCommandGroup
import com.maddyhome.idea.vim.api.VimCommandLineService
import com.maddyhome.idea.vim.api.VimDigraphGroup
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimEditorGroup
@ -44,7 +43,6 @@ import com.maddyhome.idea.vim.api.VimMotionGroup
import com.maddyhome.idea.vim.api.VimOptionGroup
import com.maddyhome.idea.vim.api.VimProcessGroup
import com.maddyhome.idea.vim.api.VimPsiService
import com.maddyhome.idea.vim.api.VimRedrawService
import com.maddyhome.idea.vim.api.VimRegexpService
import com.maddyhome.idea.vim.api.VimScrollGroup
import com.maddyhome.idea.vim.api.VimSearchGroup
@ -76,10 +74,12 @@ import com.maddyhome.idea.vim.group.TabService
import com.maddyhome.idea.vim.group.VimWindowGroup
import com.maddyhome.idea.vim.group.WindowGroup
import com.maddyhome.idea.vim.group.copy.PutGroup
import com.maddyhome.idea.vim.helper.CommandLineHelper
import com.maddyhome.idea.vim.helper.IjActionExecutor
import com.maddyhome.idea.vim.helper.IjEditorHelper
import com.maddyhome.idea.vim.helper.IjVimStringParser
import com.maddyhome.idea.vim.helper.UndoRedoHelper
import com.maddyhome.idea.vim.helper.VimCommandLineHelper
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.history.VimHistory
import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl
@ -151,6 +151,8 @@ internal class IjVimInjector : VimInjectorBase() {
get() = service<UndoRedoHelper>()
override val psiService: VimPsiService
get() = service<IjVimPsiService>()
override val commandLineHelper: VimCommandLineHelper
get() = service<CommandLineHelper>()
override val nativeActionManager: NativeActionManager
get() = service<IjNativeActionManager>()
override val messages: VimMessages
@ -195,8 +197,6 @@ internal class IjVimInjector : VimInjectorBase() {
get() = service<Executor>()
override val vimscriptParser: VimscriptParser
get() = com.maddyhome.idea.vim.vimscript.parser.VimscriptParser
override val commandLine: VimCommandLineService
get() = service()
override val optionGroup: VimOptionGroup
get() = service()
@ -207,8 +207,6 @@ internal class IjVimInjector : VimInjectorBase() {
get() = service()
override val vimStorageService: VimStorageService
get() = service()
override val redrawService: VimRedrawService
get() = service()
override fun commandStateFor(editor: VimEditor): VimStateMachine {
var res = editor.ij.vimStateMachine

View File

@ -10,7 +10,6 @@ package com.maddyhome.idea.vim.newapi
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.Service
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.wm.WindowManager
import com.maddyhome.idea.vim.api.VimEditor
@ -27,59 +26,27 @@ internal class IjVimMessages : VimMessagesBase() {
private var message: String? = null
private var error = false
private var lastBeepTimeMillis = 0L
private var allowClearStatusBarMessage = true
override fun showStatusBarMessage(editor: VimEditor?, message: String?) {
fun setStatusBarMessage(project: Project, message: String?) {
WindowManager.getInstance().getStatusBar(project)?.let {
it.info = if (message.isNullOrBlank()) "" else "Vim - $message"
if (ApplicationManager.getApplication().isUnitTestMode) {
this.message = message
}
val pm = ProjectManager.getInstance()
val projects = pm.openProjects
for (project in projects) {
val bar = WindowManager.getInstance().getStatusBar(project)
if (bar != null) {
if (message.isNullOrEmpty()) {
bar.info = ""
} else {
bar.info = "VIM - $message"
}
}
}
this.message = message
val project = editor?.ij?.project
if (project != null) {
setStatusBarMessage(project, message)
}
else {
// TODO: We really shouldn't set the status bar text for other projects. That's rude.
ProjectManager.getInstance().openProjects.forEach {
setStatusBarMessage(it, message)
}
}
// Redraw happens automatically based on changes or scrolling. If we've just set the message (e.g., searching for a
// string, hitting the bottom and scrolling to the top), make sure we don't immediately clear it when scrolling.
allowClearStatusBarMessage = false
ApplicationManager.getApplication().invokeLater {
allowClearStatusBarMessage = true
}
}
override fun getStatusBarMessage(): String? = message
// Vim doesn't appear to have a policy about clearing the status bar, other than on "redraw". This can be forced with
// <C-L> or the `:redraw` command, but also happens as the screen changes, e.g., when inserting or deleting lines,
// scrolling, entering Command-line mode and probably lots more. We should manually clear the status bar when these
// things happen.
override fun clearStatusBarMessage() {
if (message.isNullOrEmpty()) return
// Don't clear the status bar message if we've only just set it
if (!allowClearStatusBarMessage) return
ProjectManager.getInstance().openProjects.forEach { project ->
WindowManager.getInstance().getStatusBar(project)?.let { statusBar ->
// Only clear the status bar if it's showing our last message
if (statusBar.info?.contains(message.toString()) == true) {
statusBar.info = ""
}
}
}
message = null
}
override fun indicateError() {
error = true
if (!ApplicationManager.getApplication().isUnitTestMode) {
@ -101,7 +68,6 @@ internal class IjVimMessages : VimMessagesBase() {
override fun isError(): Boolean = error
override fun message(key: String, vararg params: Any): String = MessageHelper.message(key, *params)
override fun updateStatusBar(editor: VimEditor) {
ShowCmd.update()
}

View File

@ -9,8 +9,6 @@
package com.maddyhome.idea.vim.newapi
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.markup.RangeHighlighter
import com.intellij.openapi.util.Ref
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
@ -36,10 +34,14 @@ import javax.swing.KeyStroke
public abstract class IjVimSearchGroup : VimSearchGroupBase() {
init {
// We use the global option listener instead of the effective listener that gets called for each affected editor
// because we handle updating the affected editors ourselves (e.g., we can filter for visible windows).
// TODO: Investigate migrating these listeners to use the effective value change listener
// This would allow us to update the editor we're told to update, rather than looping over all projects and updating
// the highlights in that project's current document's open editors (see VIM-2779).
// However, we probably only want to update the editors associated with the current document, so maybe the whole
// code needs to be reworked. We're currently using the same update code for changes in the search term as well as
// changes in the search options.
VimPlugin.getOptionGroup().addGlobalOptionChangeListener(Options.hlsearch) {
setShouldShowSearchHighlights()
resetSearchHighlight()
updateSearchHighlights(true)
}
@ -133,19 +135,13 @@ public abstract class IjVimSearchGroup : VimSearchGroupBase() {
return result.get()
}
override fun addSubstitutionConfirmationHighlight(
editor: VimEditor,
startOffset: Int,
endOffset: Int,
): SearchHighlight {
val ijEditor = (editor as IjVimEditor).editor
val highlighter = addSubstitutionConfirmationHighlight(
ijEditor,
override fun addSubstitutionConfirmationHighlight(editor: VimEditor, startOffset: Int, endOffset: Int) {
val hl = addSubstitutionConfirmationHighlight(
(editor as IjVimEditor).editor,
startOffset,
endOffset
)
return IjSearchHighlight(ijEditor, highlighter)
editor.editor.markupModel.removeHighlighter(hl)
}
override fun setLatestMatch(match: String) {
@ -169,7 +165,7 @@ public abstract class IjVimSearchGroup : VimSearchGroupBase() {
showSearchHighlight = injector.globalOptions().hlsearch
}
override fun setShouldShowSearchHighlights() {
override fun resetSearchHighlight() {
showSearchHighlight = injector.globalOptions().hlsearch
}
@ -177,12 +173,4 @@ public abstract class IjVimSearchGroup : VimSearchGroupBase() {
showSearchHighlight = false
updateSearchHighlights(false)
}
private class IjSearchHighlight(private val editor: Editor, private val highlighter: RangeHighlighter) :
SearchHighlight() {
override fun remove() {
editor.markupModel.removeHighlighter(highlighter)
}
}
}

View File

@ -136,7 +136,7 @@
* |-| {@link com.maddyhome.idea.vim.action.motion.updown.MotionUpFirstNonSpaceAction}
* |.| {@link com.maddyhome.idea.vim.action.change.RepeatChangeAction}
* |/| {@link com.maddyhome.idea.vim.action.motion.search.SearchEntryFwdAction}
* |:| {@link com.maddyhome.idea.vim.action.ex.ExEntryAction}
* |:| {@link com.maddyhome.idea.vim.action.ExEntryAction}
* |;| {@link com.maddyhome.idea.vim.action.motion.leftright.MotionLastMatchCharAction}
* |<| {@link com.maddyhome.idea.vim.action.change.shift.ShiftLeftMotionAction}
* |<<| translated to <_
@ -618,6 +618,7 @@
* |c_CTRL-N| {@link com.maddyhome.idea.vim.ui.ex.HistoryDownAction}
* |c_CTRL-P| {@link com.maddyhome.idea.vim.ui.ex.HistoryUpAction}
* |c_CTRL-Q| Handled by KeyHandler
* |c_CTRL-R| {@link com.maddyhome.idea.vim.ui.ex.InsertRegisterAction}
* |c_CTRL-R_CTRL-A| TO BE IMPLEMENTED
* |c_CTRL-R_CTRL-F| TO BE IMPLEMENTED
* |c_CTRL-R_CTRL-L| TO BE IMPLEMENTED

View File

@ -1460,9 +1460,6 @@ internal class RegExp {
* can't go before line 1 */
return if (reg_firstlnum + lnum < 0) {
null
} else if (reg_firstlnum + lnum >= reg_buf!!.lineCount()) {
// Must have matched the "\n" in the last line.
CharPointer("")
} else {
CharPointer(
reg_buf!!.getLineBuffer(reg_firstlnum + lnum),
@ -1510,7 +1507,7 @@ internal class RegExp {
reg_buf = buf
// reg_win = win;
reg_firstlnum = lnum
reg_maxline = lcount - lnum - 1 // Remember, lnum is 0-based, while in Vim, it's 1-based
reg_maxline = lcount - lnum
ireg_ic = rmp.rmm_ic
/* Need to switch to buffer "buf" to make vim_iswordc() work. */
@ -1877,9 +1874,7 @@ internal class RegExp {
reginput!!.inc()
}
IDENT -> {
// Character.isJavaIdentifier treats '\0' as a valid identifier!
// Also, this should really be using 'isident' instead of Character.isJavaIdentifierPart
if (c == '\u0000' || !Character.isJavaIdentifierPart(c)) {
if (!Character.isJavaIdentifierPart(c)) {
return false
}
reginput!!.inc()
@ -2493,7 +2488,7 @@ internal class RegExp {
return false
}
NEWL -> {
if (c != '\u0000' || reglnum > reg_maxline) {
if (c != '\u0000' || reglnum == reg_maxline) {
return false
}
reg_nextline()
@ -2535,7 +2530,7 @@ internal class RegExp {
++count
scan.inc()
}
if (!WITH_NL(p.OP()) || reglnum > reg_maxline || count == maxcount) {
if (!WITH_NL(p.OP()) || reglnum == reg_maxline || count == maxcount) {
break
}
++count /* count the line-break */
@ -2553,7 +2548,7 @@ internal class RegExp {
) {
scan.inc()
} else if (scan.isNul) {
if (!WITH_NL(p.OP()) || reglnum > reg_maxline) {
if (!WITH_NL(p.OP()) || reglnum == reg_maxline) {
break
}
reg_nextline()
@ -2573,7 +2568,7 @@ internal class RegExp {
) {
scan.inc()
} else if (scan.isNul) {
if (!WITH_NL(p.OP()) || reglnum > reg_maxline) {
if (!WITH_NL(p.OP()) || reglnum == reg_maxline) {
break
}
reg_nextline()
@ -2592,7 +2587,7 @@ internal class RegExp {
if (CharacterClasses.isWord(scan.charAt()) && (testval == 1 || !Character.isDigit(scan.charAt()))) {
scan.inc()
} else if (scan.isNul) {
if (!WITH_NL(p.OP()) || reglnum > reg_maxline) {
if (!WITH_NL(p.OP()) || reglnum == reg_maxline) {
break
}
reg_nextline()
@ -2610,7 +2605,7 @@ internal class RegExp {
if (CharacterClasses.isWord(scan.charAt()) && (testval == 1 || !Character.isDigit(scan.charAt()))) {
scan.inc()
} else if (scan.isNul) {
if (!WITH_NL(p.OP()) || reglnum > reg_maxline) {
if (!WITH_NL(p.OP()) || reglnum == reg_maxline) {
break
}
reg_nextline()
@ -2629,7 +2624,7 @@ internal class RegExp {
if (CharacterClasses.isFile(scan.charAt()) && (testval == 1 || !Character.isDigit(scan.charAt()))) {
scan.inc()
} else if (scan.isNul) {
if (!WITH_NL(p.OP()) || reglnum > reg_maxline) {
if (!WITH_NL(p.OP()) || reglnum == reg_maxline) {
break
}
reg_nextline()
@ -2647,7 +2642,7 @@ internal class RegExp {
if (CharacterClasses.isFile(scan.charAt()) && (testval == 1 || !Character.isDigit(scan.charAt()))) {
scan.inc()
} else if (scan.isNul) {
if (!WITH_NL(p.OP()) || reglnum > reg_maxline) {
if (!WITH_NL(p.OP()) || reglnum == reg_maxline) {
break
}
reg_nextline()
@ -2664,7 +2659,7 @@ internal class RegExp {
testval = 1
while (count < maxcount) {
if (scan.isNul) {
if (!WITH_NL(p.OP()) || reglnum > reg_maxline) {
if (!WITH_NL(p.OP()) || reglnum == reg_maxline) {
break
}
reg_nextline()
@ -2684,7 +2679,7 @@ internal class RegExp {
}
SPRINT, SPRINT + ADD_NL -> while (count < maxcount) {
if (scan.isNul) {
if (!WITH_NL(p.OP()) || reglnum > reg_maxline) {
if (!WITH_NL(p.OP()) || reglnum == reg_maxline) {
break
}
reg_nextline()
@ -2770,7 +2765,7 @@ internal class RegExp {
testval = 1
while (count < maxcount) {
if (scan.isNul) {
if (!WITH_NL(p.OP()) || reglnum > reg_maxline) {
if (!WITH_NL(p.OP()) || reglnum == reg_maxline) {
break
}
reg_nextline()
@ -2789,7 +2784,7 @@ internal class RegExp {
}
ANYBUT, ANYBUT + ADD_NL -> while (count < maxcount) {
if (scan.isNul) {
if (!WITH_NL(p.OP()) || reglnum > reg_maxline) {
if (!WITH_NL(p.OP()) || reglnum == reg_maxline) {
break
}
reg_nextline()
@ -2805,7 +2800,7 @@ internal class RegExp {
}
++count
}
NEWL -> while (count < maxcount && scan.isNul && reglnum <= reg_maxline) {
NEWL -> while (count < maxcount && scan.isNul && reglnum < reg_maxline) {
count++
reg_nextline()
scan = reginput!!.ref(0)
@ -2818,7 +2813,7 @@ internal class RegExp {
if (mask != 0) {
while (count < maxcount) {
if (scan.isNul) {
if (!WITH_NL(p.OP()) || reglnum < reg_maxline) {
if (!WITH_NL(p.OP()) || reglnum == reg_maxline) {
break
}
reg_nextline()
@ -3170,7 +3165,7 @@ internal class RegExp {
reg_mmatch = rmp
// reg_buf = curbuf; /* always works on the current buffer! */
reg_firstlnum = lnum
reg_maxline = reg_buf!!.lineCount() - lnum - 1 // lnum is 0-based, so make sure maxline is too
reg_maxline = reg_buf!!.lineCount() - lnum
return vim_regsub_both(source, magic, backslash)
}
@ -3312,7 +3307,7 @@ internal class RegExp {
if (reg_mmatch!!.endpos[no]!!.lnum == clnum) {
break
}
dst.append('\n')
dst.append('\r')
s = reg_getline(++clnum)
len = if (reg_mmatch!!.endpos[no]!!.lnum == clnum) {
reg_mmatch!!.endpos[no]!!.col

View File

@ -151,10 +151,6 @@ public class ExOutputPanel extends JPanel {
}
}
public String getText() {
return myText.getText();
}
@SuppressWarnings("ConstantConditions")
@Override
public Color getForeground() {
@ -292,7 +288,7 @@ public class ExOutputPanel extends JPanel {
}
}
public void close() {
private void close() {
close(null);
}

View File

@ -26,12 +26,10 @@ import javax.swing.text.TextAction
import kotlin.math.abs
import kotlin.math.min
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
internal interface MultiStepAction : Action {
fun reset()
}
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
internal class HistoryUpAction : TextAction(ExEditorKit.HistoryUp) {
override fun actionPerformed(actionEvent: ActionEvent) {
val target = getTextComponent(actionEvent) as ExTextField
@ -39,7 +37,6 @@ internal class HistoryUpAction : TextAction(ExEditorKit.HistoryUp) {
}
}
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
internal class HistoryDownAction : TextAction(ExEditorKit.HistoryDown) {
override fun actionPerformed(actionEvent: ActionEvent) {
val target = getTextComponent(actionEvent) as ExTextField
@ -47,7 +44,6 @@ internal class HistoryDownAction : TextAction(ExEditorKit.HistoryDown) {
}
}
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
internal class HistoryUpFilterAction : TextAction(ExEditorKit.HistoryUpFilter) {
override fun actionPerformed(actionEvent: ActionEvent) {
val target = getTextComponent(actionEvent) as ExTextField
@ -55,7 +51,6 @@ internal class HistoryUpFilterAction : TextAction(ExEditorKit.HistoryUpFilter) {
}
}
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
internal class HistoryDownFilterAction : TextAction(ExEditorKit.HistoryDownFilter) {
override fun actionPerformed(actionEvent: ActionEvent) {
val target = getTextComponent(actionEvent) as ExTextField
@ -63,7 +58,51 @@ internal class HistoryDownFilterAction : TextAction(ExEditorKit.HistoryDownFilte
}
}
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
internal class InsertRegisterAction : TextAction(ExEditorKit.InsertRegister), MultiStepAction {
private enum class State {
SKIP_CTRL_R, WAIT_REGISTER
}
private var state = State.SKIP_CTRL_R
override fun actionPerformed(e: ActionEvent) {
val target = getTextComponent(e) as ExTextField
val key = ExEditorKit.convert(e)
if (key != null) {
when (state) {
State.SKIP_CTRL_R -> {
state = State.WAIT_REGISTER
target.setCurrentAction(this, '\"')
}
State.WAIT_REGISTER -> {
state = State.SKIP_CTRL_R
target.clearCurrentAction()
val c = key.keyChar
if (c != KeyEvent.CHAR_UNDEFINED) {
val register = VimPlugin.getRegister().getRegister(c)
if (register != null) {
val oldText = target.actualText
val text = register.text
if (text != null) {
val offset = target.caretPosition
target.text = oldText.substring(0, offset) + text + oldText.substring(offset)
target.caretPosition = offset + text.length
}
}
} else if (key.modifiers and KeyEvent.CTRL_DOWN_MASK != 0 && key.keyCode == KeyEvent.VK_C) {
// Eat any unused keys, unless it's <C-C>, in which case forward on and cancel entry
target.handleKey(key)
}
}
}
}
}
override fun reset() {
state = State.SKIP_CTRL_R
}
}
internal class CompleteEntryAction : TextAction(ExEditorKit.CompleteEntry) {
override fun actionPerformed(actionEvent: ActionEvent) {
logger.debug("complete entry")
@ -84,7 +123,6 @@ internal class CompleteEntryAction : TextAction(ExEditorKit.CompleteEntry) {
}
}
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
internal class CancelEntryAction : TextAction(ExEditorKit.CancelEntry) {
override fun actionPerformed(e: ActionEvent) {
val target = getTextComponent(e) as ExTextField
@ -92,7 +130,6 @@ internal class CancelEntryAction : TextAction(ExEditorKit.CancelEntry) {
}
}
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
internal class EscapeCharAction : TextAction(ExEditorKit.EscapeChar) {
override fun actionPerformed(e: ActionEvent) {
val target = getTextComponent(e) as ExTextField
@ -100,7 +137,6 @@ internal class EscapeCharAction : TextAction(ExEditorKit.EscapeChar) {
}
}
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
internal abstract class DeleteCharAction internal constructor(name: String?) : TextAction(name) {
@kotlin.jvm.Throws(BadLocationException::class)
fun deleteSelection(doc: Document, dot: Int, mark: Int): Boolean {
@ -148,7 +184,6 @@ internal abstract class DeleteCharAction internal constructor(name: String?) : T
}
}
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
internal class DeleteNextCharAction : DeleteCharAction(DefaultEditorKit.deleteNextCharAction) {
override fun actionPerformed(e: ActionEvent) {
val target = getTextComponent(e) as ExTextField
@ -167,7 +202,6 @@ internal class DeleteNextCharAction : DeleteCharAction(DefaultEditorKit.deleteNe
}
}
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
internal class DeletePreviousCharAction : DeleteCharAction(DefaultEditorKit.deletePrevCharAction) {
override fun actionPerformed(e: ActionEvent) {
val target = getTextComponent(e) as ExTextField
@ -188,7 +222,6 @@ internal class DeletePreviousCharAction : DeleteCharAction(DefaultEditorKit.dele
}
}
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
internal class DeletePreviousWordAction : TextAction(DefaultEditorKit.deletePrevWordAction) {
/**
* Invoked when an action occurs.
@ -225,7 +258,6 @@ internal class DeletePreviousWordAction : TextAction(DefaultEditorKit.deletePrev
}
}
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
internal class DeleteToCursorAction : TextAction(ExEditorKit.DeleteToCursor) {
/**
* Invoked when an action occurs.
@ -243,7 +275,6 @@ internal class DeleteToCursorAction : TextAction(ExEditorKit.DeleteToCursor) {
}
}
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
internal class ToggleInsertReplaceAction : TextAction(ExEditorKit.ToggleInsertReplace) {
/**
* Invoked when an action occurs.

View File

@ -20,7 +20,6 @@ import javax.swing.text.DefaultEditorKit
import javax.swing.text.Document
import javax.swing.text.TextAction
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
internal object ExEditorKit : DefaultEditorKit() {
@NonNls
@ -38,6 +37,9 @@ internal object ExEditorKit : DefaultEditorKit() {
@NonNls
val ToggleInsertReplace: String = "toggle-insert"
@NonNls
val InsertRegister: String = "insert-register"
@NonNls
val HistoryUp: String = "history-up"
@ -105,6 +107,7 @@ internal object ExEditorKit : DefaultEditorKit() {
HistoryUpFilterAction(),
HistoryDownFilterAction(),
ToggleInsertReplaceAction(),
InsertRegisterAction(),
)
class DefaultExKeyHandler : DefaultKeyTypedAction() {

View File

@ -18,13 +18,7 @@ import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ScrollingModel;
import com.intellij.ui.DocumentAdapter;
import com.intellij.util.IJSwingUtilities;
import com.maddyhome.idea.vim.EventFacade;
import com.maddyhome.idea.vim.KeyHandler;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.action.VimShortcutKeyAction;
import com.maddyhome.idea.vim.api.VimCommandLine;
import com.maddyhome.idea.vim.api.VimCommandLineCaret;
import com.maddyhome.idea.vim.api.VimKeyGroupBase;
import com.maddyhome.idea.vim.ex.ranges.LineRange;
import com.maddyhome.idea.vim.helper.SearchHighlightsHelper;
import com.maddyhome.idea.vim.helper.UiHelper;
@ -34,7 +28,6 @@ import com.maddyhome.idea.vim.regexp.CharPointer;
import com.maddyhome.idea.vim.regexp.RegExp;
import com.maddyhome.idea.vim.ui.ExPanelBorder;
import com.maddyhome.idea.vim.vimscript.model.commands.Command;
import com.maddyhome.idea.vim.vimscript.model.commands.GlobalCommand;
import com.maddyhome.idea.vim.vimscript.model.commands.SubstituteCommand;
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser;
import org.jetbrains.annotations.Contract;
@ -51,13 +44,12 @@ import java.awt.event.ComponentListener;
import static com.maddyhome.idea.vim.api.VimInjectorKt.globalOptions;
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
import static com.maddyhome.idea.vim.group.KeyGroup.toShortcutSet;
/**
* This is used to enter ex commands such as searches and "colon" commands
*/
public class ExEntryPanel extends JPanel implements VimCommandLine {
public static ExEntryPanel instance;
public class ExEntryPanel extends JPanel {
private static ExEntryPanel instance;
private static ExEntryPanel instanceWithoutShortcuts;
private ExEntryPanel(boolean enableShortcuts) {
@ -79,11 +71,6 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
if (enableShortcuts) {
// This does not need to be unregistered, it's registered as a custom UI property on this
EventFacade.getInstance().registerCustomShortcutSet(
VimShortcutKeyAction.getInstance(),
toShortcutSet(((VimKeyGroupBase) injector.getKeyGroup()).getRequiredShortcutKeys()),
entry
);
new ExShortcutKeyAction(this).registerCustomShortcutSet();
}
@ -187,7 +174,6 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
/**
* Turns off the ex entry field and optionally puts the focus back to the original component
*/
@Override
public void deactivate(boolean refocusOwningEditor, boolean resetCaret) {
logger.info("Deactivate ex entry panel");
if (!active) return;
@ -268,72 +254,51 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
private final @NotNull DocumentListener incSearchDocumentListener = new DocumentAdapter() {
@Override
protected void textChanged(@NotNull DocumentEvent e) {
try {
final Editor editor = entry.getEditor();
final Editor editor = entry.getEditor();
final String labelText = label.getText(); // Either '/', '?' or ':'boolean searchCommand = false;
boolean searchCommand = false;
LineRange searchRange = null;
char separator = labelText.charAt(0);
String searchText = entry.getActualText();
if (labelText.equals(":")) {
if (searchText.isEmpty()) return;
final Command command = getIncsearchCommand(searchText);
if (command == null) {
return;
}
searchCommand = true;
searchText = "";
final String argument = command.getCommandArgument();
if (argument.length() > 1) { // E.g. skip '/' in `:%s/`. `%` is range, `s` is command, `/` is argument
separator = argument.charAt(0);
searchText = argument.substring(1);
}
if (!searchText.isEmpty()) {
searchRange = command.getLineRangeSafe(new IjVimEditor(editor));
boolean searchCommand = false;
LineRange searchRange = null;
char separator = label.getText().charAt(0);
String searchText = entry.getActualText();
if (label.getText().equals(":")) {
if (searchText.isEmpty()) return;
final Command command = getIncsearchCommand(searchText);
if (command == null) {
return;
}
if (searchText.isEmpty() || searchRange == null) {
// Reset back to the original search highlights after deleting a search from a substitution command.Or if
// there is no search range (because the user entered an invalid range, e.g. mark not set).
// E.g. Highlight `whatever`, type `:%s/foo` + highlight `foo`, delete back to `:%s/` and reset highlights
// back to `whatever`
VimPlugin.getSearch().resetIncsearchHighlights();
resetCaretOffset(editor);
return;
}
searchCommand = true;
searchText = "";
final String argument = command.getCommandArgument();
if (argument.length() > 1) { // E.g. skip '/' in `:%s/`. `%` is range, `s` is command, `/` is argument
separator = argument.charAt(0);
searchText = argument.substring(1);
}
// Get the current count from the command builder. This value is coerced to at least 1, so will always be valid.
// The aggregated value includes any counts for operator and register selections, and the uncommitted count for
// the search command (`/` or `?`). E.g., `2"a3"b4"c5d6/` would return 720.
// If we're showing highlights for an ex command like `:s`, there won't be a command, but the value is already
// coerced to at least 1.
int count1 = KeyHandler.getInstance().getKeyHandlerState().getEditorCommandBuilder().getAggregatedUncommittedCount();
if (labelText.equals("/") || labelText.equals("?") || searchCommand) {
final boolean forwards = !labelText.equals("?"); // :s, :g, :v are treated as forwards
final String pattern;
final CharPointer p = new CharPointer(searchText);
final CharPointer end = RegExp.skip_regexp(new CharPointer(searchText), separator, true);
pattern = p.substring(end.pointer() - p.pointer());
VimPlugin.getEditor().closeEditorSearchSession(editor);
final int matchOffset =
SearchHighlightsHelper.updateIncsearchHighlights(editor, pattern, count1, forwards, caretOffset,
searchRange);
if (matchOffset != -1) {
new IjVimCaret(editor.getCaretModel().getPrimaryCaret()).moveToOffset(matchOffset);
}
else {
resetCaretOffset(editor);
}
if (searchText.length() == 0) {
// Reset back to the original search highlights after deleting a search from a substitution command.
// E.g. Highlight `whatever`, type `:%s/foo` + highlight `foo`, delete back to `:%s/` and reset highlights
// back to `whatever`
VimPlugin.getSearch().resetIncsearchHighlights();
return;
}
searchRange = command.getLineRange(new IjVimEditor(editor));
}
catch (Throwable ex) {
// Make sure the exception doesn't leak out of the handler, because it can break the text entry field and
// require the editor to be closed/reopened. The worst that will happen is no incsearch highlights
logger.warn("Error while trying to show incsearch highlights", ex);
final String labelText = label.getText();
if (labelText.equals("/") || labelText.equals("?") || searchCommand) {
final boolean forwards = !labelText.equals("?"); // :s, :g, :v are treated as forwards
final String pattern;
final CharPointer p = new CharPointer(searchText);
final CharPointer end = RegExp.skip_regexp(new CharPointer(searchText), separator, true);
pattern = p.substring(end.pointer() - p.pointer());
VimPlugin.getEditor().closeEditorSearchSession(editor);
final int matchOffset = SearchHighlightsHelper.updateIncsearchHighlights(editor, pattern, forwards, caretOffset, searchRange);
if (matchOffset != -1) {
new IjVimCaret(editor.getCaretModel().getPrimaryCaret()).moveToOffset(matchOffset);
}
else {
resetCaretOffset(editor);
}
}
}
@ -342,8 +307,8 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
if (commandText == null) return null;
try {
final Command exCommand = VimscriptParser.INSTANCE.parseCommand(commandText);
// TODO: Add smagic and snomagic here if/when the commands are supported
if (exCommand instanceof SubstituteCommand || exCommand instanceof GlobalCommand) {
// TODO: Add global, vglobal, smagic and snomagic here when the commands are supported
if (exCommand instanceof SubstituteCommand) {
return exCommand;
}
}
@ -360,7 +325,6 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
*
* @return The ex entry label
*/
@Override
public String getLabel() {
return label.getText();
}
@ -388,16 +352,10 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
*
* @return The user entered text
*/
@Override
public @NotNull String getText() {
return entry.getActualText();
}
@Override
public void setText(@NotNull String s) {
entry.setText(s);
}
public @NotNull ExTextField getEntry() {
return entry;
}
@ -407,7 +365,6 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
*
* @param stroke The keystroke
*/
@Override
public void handleKey(@NotNull KeyStroke stroke) {
entry.handleKey(stroke);
}
@ -493,12 +450,6 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
private static final Logger logger = Logger.getInstance(ExEntryPanel.class.getName());
@NotNull
@Override
public VimCommandLineCaret getCaret() {
return (VimCommandLineCaret) entry.getCaret();
}
public static class LafListener implements LafManagerListener {
@Override
public void lookAndFeelChanged(@NotNull LafManager source) {

View File

@ -13,7 +13,6 @@ import javax.swing.KeyStroke
import javax.swing.text.DefaultEditorKit
import javax.swing.text.JTextComponent.KeyBinding
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
internal object ExKeyBindings {
// TODO - add the following keys:
@ -72,6 +71,8 @@ internal object ExKeyBindings {
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.StartLiteral),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_Q, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.StartLiteral),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_R, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.InsertRegister),
// These appear to be non-Vim shortcuts
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.META_DOWN_MASK), DefaultEditorKit.pasteAction),
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, KeyEvent.SHIFT_DOWN_MASK), DefaultEditorKit.pasteAction),

View File

@ -29,7 +29,6 @@ import javax.swing.KeyStroke
* component has focus. It registers all shortcuts used by the Swing actions and forwards them directly to the key
* handler.
*/
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
internal class ExShortcutKeyAction(private val exEntryPanel: ExEntryPanel) : DumbAwareAction() {
override fun actionPerformed(e: AnActionEvent) {

View File

@ -14,7 +14,6 @@ import com.intellij.openapi.editor.Editor;
import com.intellij.ui.paint.PaintUtil;
import com.intellij.util.ui.JBUI;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.VimCommandLineCaret;
import com.maddyhome.idea.vim.group.EditorHolderService;
import com.maddyhome.idea.vim.helper.UiHelper;
import com.maddyhome.idea.vim.history.HistoryConstants;
@ -409,7 +408,7 @@ public class ExTextField extends JTextField {
caret.setAttributes(GuiCursorOptionHelper.INSTANCE.getAttributes(GuiCursorMode.CMD_LINE_REPLACE));
}
private static class CommandLineCaret extends DefaultCaret implements VimCommandLineCaret {
private static class CommandLineCaret extends DefaultCaret {
private GuiCursorType mode;
private int thickness = 100;
@ -558,16 +557,6 @@ public class ExTextField extends JTextField {
}
return fullHeight;
}
@Override
public int getOffset() {
return getDot();
}
@Override
public void setOffset(int i) {
setDot(i);
}
}
@TestOnly

View File

@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.ex.ranges.Range
import com.maddyhome.idea.vim.ex.ranges.Ranges
import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
@ -26,7 +26,7 @@ import java.util.*
* @author smartbomb
*/
@ExCommand(command = "actionl[ist]")
internal data class ActionListCommand(val range: Range, val argument: String) : Command.SingleExecution(range) {
internal data class ActionListCommand(val ranges: Ranges, val argument: String) : Command.SingleExecution(ranges) {
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {

View File

@ -15,7 +15,7 @@ import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.ex.ranges.Range
import com.maddyhome.idea.vim.ex.ranges.Ranges
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.newapi.ij
@ -27,7 +27,7 @@ import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
* @author John Weigel
*/
@ExCommand(command = "b[uffer]")
internal data class BufferCommand(val range: Range, val argument: String) : Command.SingleExecution(range) {
internal data class BufferCommand(val ranges: Ranges, val argument: String) : Command.SingleExecution(ranges) {
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {

View File

@ -19,7 +19,7 @@ import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.ex.ranges.Range
import com.maddyhome.idea.vim.ex.ranges.Ranges
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.vimLine
import com.maddyhome.idea.vim.newapi.ij
@ -32,7 +32,7 @@ import org.jetbrains.annotations.NonNls
* @author John Weigel
*/
@ExCommand(command = "ls,files,buffers")
internal data class BufferListCommand(val range: Range, val argument: String) : Command.SingleExecution(range) {
internal data class BufferListCommand(val ranges: Ranges, val argument: String) : Command.SingleExecution(ranges) {
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
companion object {

View File

@ -18,8 +18,7 @@ import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.ex.ranges.Range
import com.maddyhome.idea.vim.ex.ranges.toTextRange
import com.maddyhome.idea.vim.ex.ranges.Ranges
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.newapi.ij
@ -29,7 +28,7 @@ import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
* see "h :!"
*/
@ExCommand(command = "!")
internal data class CmdFilterCommand(val range: Range, val argument: String) : Command.SingleExecution(range) {
internal data class CmdFilterCommand(val ranges: Ranges, val argument: String) : Command.SingleExecution(ranges) {
override val argFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.SELF_SYNCHRONIZED)
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
@ -71,7 +70,7 @@ internal data class CmdFilterCommand(val range: Range, val argument: String) : C
val workingDirectory = editor.ij.project?.basePath
return try {
if (range.size() == 0) {
if (ranges.size() == 0) {
// Show command output in a window
VimPlugin.getProcess().executeCommand(editor, command, null, workingDirectory)?.let {
ExOutputModel.getInstance(editor.ij).output(it)
@ -79,7 +78,7 @@ internal data class CmdFilterCommand(val range: Range, val argument: String) : C
ExecutionResult.Success
} else {
// Filter
val range = getLineRange(editor).toTextRange(editor)
val range = this.getTextRange(editor, false)
val input = editor.ij.document.charsSequence.subSequence(range.startOffset, range.endOffset)
VimPlugin.getProcess().executeCommand(editor, command, input, workingDirectory)?.let {
ApplicationManager.getApplication().runWriteAction {

View File

@ -13,12 +13,11 @@ import com.intellij.vim.annotations.ExCommand
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimSearchGroupBase
import com.maddyhome.idea.vim.api.getLineStartForOffset
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.ex.ranges.LineRange
import com.maddyhome.idea.vim.ex.ranges.Range
import com.maddyhome.idea.vim.ex.ranges.Ranges
import com.maddyhome.idea.vim.group.SearchGroup
import com.maddyhome.idea.vim.group.SearchGroup.RE_BOTH
import com.maddyhome.idea.vim.group.SearchGroup.RE_LAST
@ -30,6 +29,7 @@ import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.regexp.CharPointer
import com.maddyhome.idea.vim.regexp.RegExp
import com.maddyhome.idea.vim.regexp.VimRegex
import com.maddyhome.idea.vim.regexp.VimRegexException
import com.maddyhome.idea.vim.regexp.match.VimMatchResult
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
@ -38,20 +38,20 @@ import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
* see "h :global" / "h :vglobal"
*/
@ExCommand(command = "g[lobal],v[global]")
internal data class GlobalCommand(val range: Range, val argument: String, val invert: Boolean) : Command.SingleExecution(range, argument) {
init {
// Most commands have a default range of the current line ("."). Global has a default range of the whole file
defaultRange = "%"
}
internal data class GlobalCommand(val ranges: Ranges, val argument: String, val invert: Boolean) : Command.SingleExecution(ranges, argument) {
override val argFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.SELF_SYNCHRONIZED)
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
var result: ExecutionResult = ExecutionResult.Success
editor.removeSecondaryCarets()
val caret = editor.currentCaret()
val lineRange = getLineRange(editor, caret)
// For :g command the default range is %
val lineRange: LineRange = if (ranges.size() == 0) {
LineRange(0, editor.lineCount() - 1)
} else {
getLineRange(editor, caret)
}
if (!processGlobalCommand(editor, context, lineRange)) {
result = ExecutionResult.Error
}
@ -106,7 +106,7 @@ internal data class GlobalCommand(val range: Range, val argument: String, val in
if (injector.globalIjOptions().useNewRegex) {
val regex = try {
(injector.searchGroup as VimSearchGroupBase).prepareRegex(pat, whichPat, RE_BOTH)
VimRegex(pat.toString())
} catch (e: VimRegexException) {
injector.messages.showStatusBarMessage(editor, e.message)
return false

View File

@ -13,7 +13,7 @@ import com.intellij.vim.annotations.ExCommand
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.ex.ranges.Range
import com.maddyhome.idea.vim.ex.ranges.Ranges
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
import org.jetbrains.annotations.NonNls
import java.io.UnsupportedEncodingException
@ -24,7 +24,7 @@ import java.net.URLEncoder
* see "h :help"
*/
@ExCommand(command = "h[elp]")
internal data class HelpCommand(val range: Range, val argument: String) : Command.SingleExecution(range, argument) {
internal data class HelpCommand(val ranges: Ranges, val argument: String) : Command.SingleExecution(ranges, argument) {
override val argFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
BrowserUtil.browse(helpTopicUrl(argument))

View File

@ -106,14 +106,13 @@ private fun variableToPosition(editor: VimEditor, variable: VimDataType, dollarF
// Visual start
if (name == "v") {
if (!editor.inVisualMode) {
if (editor.inVisualMode) {
return editor.ij.vimLine.asVimInt() to currentCol(editor)
}
val vimStart = editor.currentCaret().vimSelectionStart
val bufferPosition = editor.offsetToBufferPosition(vimStart)
val visualLine = (bufferPosition.line + 1).asVimInt()
val visualCol = (bufferPosition.column + 1).asVimInt()
val visualLine = (editor.offsetToBufferPosition(vimStart).line + 1).asVimInt()
val visualCol = (editor.offsetToBufferPosition(vimStart).column + 1).asVimInt()
return visualLine to visualCol
}

View File

@ -12,9 +12,9 @@ import com.intellij.openapi.diagnostic.logger
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.ex.ranges.Address
import com.maddyhome.idea.vim.ex.ranges.Address.Companion.createRangeAddresses
import com.maddyhome.idea.vim.ex.ranges.Range
import com.maddyhome.idea.vim.ex.ranges.Range.Companion.createRange
import com.maddyhome.idea.vim.ex.ranges.Ranges
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.vimscript.model.commands.ActionCommand
import com.maddyhome.idea.vim.vimscript.model.commands.ActionListCommand
@ -147,145 +147,140 @@ internal object CommandVisitor : VimscriptBaseVisitor<Command>() {
}
}
private fun parseRangeUnit(ctx: VimscriptParser.RangeUnitContext): Array<Address> {
private fun parseRangesUnit(ctx: VimscriptParser.RangeUnitContext): Array<Range> {
val valueAndOffset = parseRangeExpression(ctx.rangeExpression())
val move = ctx.rangeSeparator()?.text == ";"
val addresses = createRangeAddresses(valueAndOffset.first, valueAndOffset.second, move)
if (addresses == null) {
logger.warn("Could not create an address for node ${ctx.text}")
throw ExException("Could not create an address ${ctx.text}")
val ranges = createRange(valueAndOffset.first, valueAndOffset.second, move)
if (ranges == null) {
logger.warn("Could not create a range for node ${ctx.text}")
throw ExException("Could not create a range ${ctx.text}")
}
return addresses
return ranges
}
private fun parseRange(ctx: RangeContext?): Range {
val range = Range()
private fun parseRanges(ctx: RangeContext?): Ranges {
val ranges = Ranges()
if (ctx?.rangeUnit() != null) {
val addresses = ctx.rangeUnit()
for (unit in addresses) {
range.addAddresses(parseRangeUnit(unit))
}
// If the range ends with a dangling separator, the last address is an implied current line address
if (addresses.last().rangeSeparator()?.text == ",") {
createRangeAddresses(".", 0, false)?.let { range.addAddresses(it) }
for (unit in ctx.rangeUnit()) {
ranges.addRange(parseRangesUnit(unit))
}
}
return range
return ranges
}
override fun visitLet1Command(ctx: VimscriptParser.Let1CommandContext): Command {
val range: Range = parseRange(ctx.range())
val ranges: Ranges = parseRanges(ctx.range())
val variable: Expression = expressionVisitor.visit(ctx.expr(0))
val operator = getByValue(ctx.assignmentOperator().text)
val expression: Expression = expressionVisitor.visit(ctx.expr(1))
val command = LetCommand(range, variable, operator, expression, true)
val command = LetCommand(ranges, variable, operator, expression, true)
command.rangeInScript = ctx.getTextRange()
return command
}
override fun visitLet2Command(ctx: VimscriptParser.Let2CommandContext): Command {
val command = LetCommand(Range(), SimpleExpression(0), AssignmentOperator.ASSIGNMENT, SimpleExpression(0), false)
val command = LetCommand(Ranges(), SimpleExpression(0), AssignmentOperator.ASSIGNMENT, SimpleExpression(0), false)
command.rangeInScript = ctx.getTextRange()
return command
}
override fun visitEchoCommand(ctx: EchoCommandContext): Command {
val range: Range = parseRange(ctx.range())
val ranges: Ranges = parseRanges(ctx.range())
val expressions = ctx.expr().stream()
.map { tree: ExprContext ->
expressionVisitor.visit(tree)
}
.collect(Collectors.toList())
val command = EchoCommand(range, expressions)
val command = EchoCommand(ranges, expressions)
command.rangeInScript = ctx.getTextRange()
return command
}
override fun visitCallCommand(ctx: CallCommandContext): Command {
val range: Range = parseRange(ctx.range())
val ranges: Ranges = parseRanges(ctx.range())
val functionCall = ExpressionVisitor.visit(ctx.expr())
val command = CallCommand(range, functionCall)
val command = CallCommand(ranges, functionCall)
command.rangeInScript = ctx.getTextRange()
return command
}
override fun visitDelfunctionCommand(ctx: DelfunctionCommandContext): DelfunctionCommand {
val range: Range = parseRange(ctx.range())
val ranges: Ranges = parseRanges(ctx.range())
val functionScope =
if (ctx.functionScope() != null) Scope.getByValue(ctx.functionScope().text) else null
val functionName = ctx.functionName().text
val ignoreIfMissing = ctx.replace != null
val command = DelfunctionCommand(range, functionScope, functionName, ignoreIfMissing)
val command = DelfunctionCommand(ranges, functionScope, functionName, ignoreIfMissing)
command.rangeInScript = ctx.getTextRange()
return command
}
override fun visitGoToLineCommand(ctx: VimscriptParser.GoToLineCommandContext): Command {
val range: Range
val ranges: Ranges
if (ctx.range() != null) {
range = parseRange(ctx.range())
ranges = parseRanges(ctx.range())
} else {
range = Range()
range.addAddresses(
createRangeAddresses(ctx.shortRange().text, 0, false)
ranges = Ranges()
ranges.addRange(
createRange(ctx.shortRange().text, 0, false)
?: throw ExException("Could not create a range"),
)
}
val command = GoToLineCommand(range)
val command = GoToLineCommand(ranges)
command.rangeInScript = ctx.getTextRange()
return command
}
override fun visitCommandWithComment(ctx: VimscriptParser.CommandWithCommentContext): Command {
val ranges = parseRange(ctx.range())
val ranges = parseRanges(ctx.range())
val commandName = ctx.name.text
val argument = ctx.commandArgumentWithoutBars()?.text ?: ""
return createCommandByCommandContext(ranges, argument, commandName, ctx)
}
override fun visitCommandWithoutComments(ctx: VimscriptParser.CommandWithoutCommentsContext): Command {
val ranges = parseRange(ctx.range())
val ranges = parseRanges(ctx.range())
val commandName = ctx.name.text
val argument = ctx.commandArgumentWithoutBars()?.text ?: ""
return createCommandByCommandContext(ranges, argument, commandName, ctx)
}
override fun visitCommandWithBars(ctx: VimscriptParser.CommandWithBarsContext): Command {
val ranges = parseRange(ctx.range())
val ranges = parseRanges(ctx.range())
val commandName = ctx.name.text
val argument = ctx.commandArgumentWithBars()?.text ?: ""
return createCommandByCommandContext(ranges, argument, commandName, ctx)
}
private fun createCommandByCommandContext(range: Range, argument: String, commandName: String, ctx: ParserRuleContext): Command {
private fun createCommandByCommandContext(ranges: Ranges, argument: String, commandName: String, ctx: ParserRuleContext): Command {
val command = when (getCommandByName(commandName)) {
MapCommand::class -> MapCommand(range, argument, commandName)
MapClearCommand::class -> MapClearCommand(range, argument, commandName)
UnMapCommand::class -> UnMapCommand(range, argument, commandName)
MapCommand::class -> MapCommand(ranges, argument, commandName)
MapClearCommand::class -> MapClearCommand(ranges, argument, commandName)
UnMapCommand::class -> UnMapCommand(ranges, argument, commandName)
GlobalCommand::class -> {
if (commandName.startsWith("v")) {
GlobalCommand(range, argument, true)
GlobalCommand(ranges, argument, true)
} else {
if (argument.startsWith("!")) GlobalCommand(range, argument.substring(1), true) else GlobalCommand(range, argument, false)
if (argument.startsWith("!")) GlobalCommand(ranges, argument.substring(1), true) else GlobalCommand(ranges, argument, false)
}
}
SplitCommand::class -> {
if (commandName.startsWith("v")) {
SplitCommand(range, argument, SplitType.VERTICAL)
SplitCommand(ranges, argument, SplitType.VERTICAL)
} else {
SplitCommand(range, argument, SplitType.HORIZONTAL)
SplitCommand(ranges, argument, SplitType.HORIZONTAL)
}
}
SubstituteCommand::class -> SubstituteCommand(range, argument, commandName)
else -> getCommandByName(commandName).primaryConstructor!!.call(range, argument)
SubstituteCommand::class -> SubstituteCommand(ranges, argument, commandName)
else -> getCommandByName(commandName).primaryConstructor!!.call(ranges, argument)
}
command.rangeInScript = ctx.getTextRange()
return command
}
override fun visitShiftLeftCommand(ctx: VimscriptParser.ShiftLeftCommandContext): ShiftLeftCommand {
val ranges = parseRange(ctx.range())
val argument = ctx.commandArgumentWithoutBars()?.text ?: ""
val ranges = parseRanges(ctx.range())
val argument = (ctx.commandArgument?.text ?: "").trim()
val length = ctx.lShift().text.length
val command = ShiftLeftCommand(ranges, argument, length)
command.rangeInScript = ctx.getTextRange()
@ -293,8 +288,8 @@ internal object CommandVisitor : VimscriptBaseVisitor<Command>() {
}
override fun visitShiftRightCommand(ctx: VimscriptParser.ShiftRightCommandContext): ShiftRightCommand {
val ranges = parseRange(ctx.range())
val argument = ctx.commandArgumentWithoutBars()?.text ?: ""
val ranges = parseRanges(ctx.range())
val argument = (ctx.commandArgument?.text ?: "").trim()
val length = ctx.rShift().text.length
val command = ShiftRightCommand(ranges, argument, length)
command.rangeInScript = ctx.getTextRange()
@ -302,7 +297,7 @@ internal object CommandVisitor : VimscriptBaseVisitor<Command>() {
}
override fun visitExecuteCommand(ctx: VimscriptParser.ExecuteCommandContext): ExecuteCommand {
val ranges = parseRange(ctx.range())
val ranges = parseRanges(ctx.range())
val expressions = ctx.expr().stream()
.map { tree: ExprContext ->
expressionVisitor.visit(tree)
@ -314,26 +309,26 @@ internal object CommandVisitor : VimscriptBaseVisitor<Command>() {
}
override fun visitLetCommand(ctx: VimscriptParser.LetCommandContext): Command {
val command = com.maddyhome.idea.vim.vimscript.parser.VimscriptParser.parseLetCommand(ctx.text) ?: LetCommand(Range(), SimpleExpression(0), AssignmentOperator.ASSIGNMENT, SimpleExpression(0), false)
val command = com.maddyhome.idea.vim.vimscript.parser.VimscriptParser.parseLetCommand(ctx.text) ?: LetCommand(Ranges(), SimpleExpression(0), AssignmentOperator.ASSIGNMENT, SimpleExpression(0), false)
command.rangeInScript = ctx.getTextRange()
return command
}
override fun visitOtherCommand(ctx: OtherCommandContext): Command {
val range: Range = parseRange(ctx.range())
val ranges: Ranges = parseRanges(ctx.range())
val name = ctx.commandName().text
val argument = ctx.commandArgumentWithBars()?.text ?: ""
val alphabeticPart = name.split(Regex("\\P{Alpha}"))[0]
if (setOf("s", "su", "sub", "subs", "subst", "substi", "substit", "substitu", "substitut", "substitut", "substitute").contains(alphabeticPart)) {
val command = SubstituteCommand(range, name.replaceFirst(alphabeticPart, "") + argument, alphabeticPart)
val command = SubstituteCommand(ranges, name.replaceFirst(alphabeticPart, "") + argument, alphabeticPart)
command.rangeInScript = ctx.getTextRange()
return command
}
val commandConstructor = getCommandByName(name).constructors
.filter { it.parameters.size == 2 }
.firstOrNull { it.parameters[0].type == Range::class.createType() && it.parameters[1].type == String::class.createType() }
val command = commandConstructor?.call(range, argument) ?: UnknownCommand(range, name, argument)
.firstOrNull { it.parameters[0].type == Ranges::class.createType() && it.parameters[1].type == String::class.createType() }
val command = commandConstructor?.call(ranges, argument) ?: UnknownCommand(ranges, name, argument)
command.rangeInScript = ctx.getTextRange()
return command
}

View File

@ -22,8 +22,6 @@
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.SearchGroup"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.ProcessGroup"
serviceInterface="com.maddyhome.idea.vim.api.VimProcessGroup"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.ui.ex.ExEntryPanelService"
serviceInterface="com.maddyhome.idea.vim.api.VimCommandLineService"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.DigraphGroup"
serviceInterface="com.maddyhome.idea.vim.api.VimDigraphGroup"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.HistoryGroup"/>
@ -49,8 +47,6 @@
serviceInterface="com.maddyhome.idea.vim.api.VimStorageService"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.IjVimSystemInfoService"
serviceInterface="com.maddyhome.idea.vim.api.SystemInfoService"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.IjVimRedrawService"
serviceInterface="com.maddyhome.idea.vim.api.VimRedrawService"/>
</extensions>
</idea-plugin>

View File

@ -19,7 +19,7 @@
<!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version -->
<!-- Check for [Version Update] tag in YouTrack as well -->
<!-- Also, please update the value in build.gradle.kts file-->
<idea-version since-build="241.15989.150"/>
<idea-version since-build="233.11799.67"/>
<!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) -->
<depends>com.intellij.modules.platform</depends>

View File

@ -2,31 +2,24 @@ ideavim
ideavimrc
maddyhome
setglobal
setlocal
vglobal
breakindent
colorcolumn
cursorline
fileencoding
fileformat
gdefault
guicursor
hlsearch
ideamarks
ignorecase
incsearch
iskeyword
keymodel
lookupKeys
mapleader
matchpairs
maxmapdepth
mapleader
nrformats
relativenumber
scrolljump
scrolloff
selectmode
setglobal
setlocal
shellcmdflag
shellxescape
shellxquote
@ -36,47 +29,25 @@ sidescroll
sidescrolloff
smartcase
startofline
swapfile
textwidth
ideajoin
timeoutlen
undolevels
viminfo
virtualedit
visualbell
visualdelay
wrapscan
nobomb
nobreakindent
nocursorline
nodigraph
nogdefault
nohlsearch
noignorecase
noincsearch
nolist
nomore
nonumber
norelativenumber
noshowcmd
noshowmode
nosmartcase
nostartofline
notimeout
novisualbell
nowrap
nowrapscan
ideacopypreprocess
ideaglobalmode
ideajoin
ideamarks
visualdelay
idearefactormode
ideastatusicon
ideastrictmode
ideatracetime
ideavimsupport
ideawrite
ideavimsupport
maxmapdepth
ideacopypreprocess
ideatracetime
swapfile
noswapfile
ideaglobalmode
sethandler
packadd
@ -178,4 +149,4 @@ mauris
Cras
tellus
imperdiet
egestas
egestas

View File

@ -8,28 +8,22 @@
notexcmd=Not an editor command: {0}
intbadcmd=Internal error - invalid command: {0}
e_backslash=E10: \\ should be followed by /, ? or &
e_badrange=Unexpected character ''{0}'' in range
e_norange=No range allowed
e_rangereq=Range required
e_argreq=Argument required
e_argforb=Argument forbidden
e_nopresub=E33: No previous substitute regular expression
e_noprev=E34: No previous command
e_noprevre=E35: No previous regular expression
E191=E191: Argument must be a letter or forward/backward quote
e_backrange=Backwards range given
E146=E146: Regular expressions can''t be delimited by letters
e_zerocount=Zero count
e_trailing=Trailing characters
e_invcmd=Invalid command
e_null=Null argument
e_internal=Internal error
synerror=Syntax error in {0}{...}
unkopt=Unknown option: {0}
e_invarg=Invalid argument: {0}
e_backslash=E10: \\ should be followed by /, ? or &
e_invrange=E16: Invalid range
E20=E20: Mark not set
e_nopresub=E33: No previous substitute regular expression
e_noprev=E34: No previous command
e_noprevre=E35: No previous regular expression
e_re_damg=E43: Damaged match string
e_re_corr=E44: Currupted regexp program
E50=E50: Too many \\z(
E51=E51: Too many {0}(
E52=E52: Unmatched \\z(
@ -52,26 +46,23 @@ E68=E68: Invalid character after \\z
E69=E69: Missing ] after {0}%[
E70=E70: Empty {0}%[]
E71=E71: Invalid character after {0}%
e_toomsbra=E76: Too many [
E146=E146: Regular expressions can''t be delimited by letters
E147=E147: Cannot do :global recursive with a range
E148=E148: Regular expression missing from global
e174.command.already.exists.add.to.replace.it=E174: Command already exists: add ! to replace it
e176.invalid.number.of.arguments=E176: Invalid number of arguments
e183.user.defined.commands.must.start.with.an.uppercase.letter=E183: User defined commands must start with an uppercase letter
e184.no.such.user.defined.command.0=E184: No such user-defined command: {0}
E191=E191: Argument must be a letter or forward/backward quote
E223=E223: recursive mapping
e_invrange=E16: Invalid range
e_toomsbra=E76: Too many [
e_internal=Internal error
synerror=Syntax error in {0}{...}
E363=E363: pattern caused out-of-stack error
e_re_corr=E44: Currupted regexp program
e_re_damg=E43: Damaged match string
E369=E369: invalid item in {0}%[]
E384=E384: Search hit TOP without match for: {0}
E385=E385: Search hit BOTTOM without match for: {0}
E471=E471: Argument required
E384=E384: search hit TOP without match for: {0}
E385=E385: search hit BOTTOM without match for: {0}
e_patnotf2=Pattern not found: {0}
unkopt=Unknown option: {0}
e_invarg=Invalid argument: {0}
E474=E474: Invalid argument: {0}
E475=E475: Invalid argument: {0}
E481=E481: No range allowed
E486=E486: Pattern not found: {0}
E488=E488: Trailing characters: {0}
# Vim's message includes alternate files and the :p:h file name modifier, which we don't support
# E499: Empty file name for '%' or '#', only works with ":p:h"
E499=E499: Empty file name for '%'
@ -82,11 +73,6 @@ E546=E546: Illegal mode: {0}
E548=E548: Digit expected: {0}
E549=E549: Illegal percentage: {0}
E774=E774: 'operatorfunc' is empty
e841.reserved.name.cannot.be.used.for.user.defined.command=E841: Reserved name, cannot be used for user defined command
E939=E939: Positive count required
message.search.hit.bottom=search hit BOTTOM, continuing at TOP
message.search.hit.top=search hit TOP, continuing at BOTTOM
action.VimPluginToggle.text=Vim
action.VimPluginToggle.description=Toggle the vim plugin On/Off
@ -104,13 +90,19 @@ action.VimFindActionIdAction.text=IdeaVim: Track Action Ids
action.VimFindActionIdAction.description=Starts tracking ids of executed actions
ex.show.all.actions.0.1=--- Actions ---{0}{1}
e471.argument.required=E471: Argument required
buffer.0.does.not.exist=Buffer {0} does not exist
no.matching.buffer.for.0=No matching buffer for {0}
no.write.since.last.change.add.to.override=No write since last change (add ! to override)
more.than.one.match.for.0=More than one match for {0}
e176.invalid.number.of.arguments=E176: Invalid number of arguments
e183.user.defined.commands.must.start.with.an.uppercase.letter=E183: User defined commands must start with an uppercase letter
e841.reserved.name.cannot.be.used.for.user.defined.command=E841: Reserved name, cannot be used for user defined command
e174.command.already.exists.add.to.replace.it=E174: Command already exists: add ! to replace it
recursion.detected.maximum.alias.depth.reached=Recursion detected, maximum alias depth reached.
show.mode.recording=recording
e184.no.such.user.defined.command.0=E184: No such user-defined command: {0}
unable.to.find.0=Unable to find {0}
more.ret.line.space.page.d.half.page.q.quit=-- MORE -- (RET: line, SPACE: page, d: half page, q: quit)
hit.enter.or.type.command.to.continue=Hit ENTER or type command to continue
@ -166,6 +158,7 @@ configurable.keyhandler.link=<html>Use <a>sethandler</a> command to configure ha
configurable.noneditablehandler.helper.text.with.example=Non-editable handlers are defined in .ideavimrc file. E.g. ''{0}'' for {1}.
border.title.shortcut.conflicts.for.active.keymap=Shortcut Conflicts for Active Keymap
message.no.more.matches=No more matches
E223=E223: recursive mapping
action.copy.action.id.text=Copy Action Id

View File

@ -9,6 +9,7 @@ package org.jetbrains.plugins.ideavim.action
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
@ -16,7 +17,6 @@ import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimBehaviorDiffers
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
/**
@ -51,7 +51,6 @@ class MotionActionTest : VimTestCase() {
}
@Test
@Disabled("VIM-3376")
fun testEscapeInCommand() {
val content = """
on${c}e two
@ -685,81 +684,6 @@ class MotionActionTest : VimTestCase() {
doTest(keys, before, after, Mode.INSERT)
}
@Test
fun testDeleteToSearchResult() {
val before = "Lorem ${c}ipsum dolor sit amet, consectetur adipiscing elit"
val after = "Lorem ${c}sit amet, consectetur adipiscing elit"
doTest("d/sit<CR>", before, after)
}
@Test
fun testDeleteToLastSearchResult() {
val before = "Lorem ${c}ipsum dolor sit amet, consectetur adipiscing elit"
val after = "Lorem ${c}sit amet, consectetur adipiscing elit"
doTest("dn", before, after) {
enterSearch("sit")
typeText("0w") // Cursor back on first "ipsum"
}
}
@Test
fun testDeleteToSearchResultWithCount() {
doTest(
"d3/ipsum<CR>",
"lorem 1 ipsum lorem 2 ipsum lorem 3 ipsum lorem 4 ipsum lorem 5 ipsum",
"ipsum lorem 4 ipsum lorem 5 ipsum"
)
}
@Test
fun testDeleteToSearchResultWithCountAndOperatorCount() {
doTest(
"2d3/ipsum<CR>",
"lorem 1 ipsum lorem 2 ipsum lorem 3 ipsum lorem 4 ipsum lorem 5 ipsum lorem 6 ipsum lorem 7 ipsum",
"ipsum lorem 7 ipsum"
)
}
@Test
fun testDeleteToSearchResultWithLinewiseOffset() {
val before = """
|First line
|Lorem ${c}ipsum dolor sit amet, consectetur adipiscing elit
|Lorem ipsum dolor sit amet, consectetur adipiscing elit
|Lorem ipsum dolor sit amet, consectetur adipiscing elit
|Lorem ipsum dolor sit amet, consectetur adipiscing elit
|Last line
""".trimMargin()
val after = """
|First line
|${c}Last line
""".trimMargin()
doTest("d/sit/3<CR>", before, after)
}
@Test
fun testDeleteToEndOfSearchResultInclusive() {
val before = "Lorem ${c}ipsum dolor sit amet, consectetur adipiscing elit"
val after = "Lorem ${c} amet, consectetur adipiscing elit"
doTest("d/sit/e<CR>", before, after)
}
@Test
fun testDeleteToEndOfSearchResultWithOffset() {
val before = "Lorem ${c}ipsum dolor sit amet, consectetur adipiscing elit"
val after = "Lorem ${c}et, consectetur adipiscing elit"
doTest("d/sit/e+3<CR>", before, after)
}
@Test
fun testDeleteToSearchResultWithIncsearch() {
val before = "Lorem ${c}ipsum dolor sit amet, consectetur adipiscing elit"
val after = "Lorem ${c}sit amet, consectetur adipiscing elit"
doTest("d/sit<CR>", before, after) {
enterCommand("set incsearch")
}
}
@Test
fun testDeleteToDigraph() {
val keys = listOf("d/<C-K>O:<CR>")
@ -768,64 +692,6 @@ class MotionActionTest : VimTestCase() {
doTest(keys, before, after, Mode.NORMAL())
}
@Test
fun testDeleteBackwardsToSearchResult() {
val before = "Lorem ipsum dolor sit amet, ${c}consectetur adipiscing elit"
val after = "Lorem ipsum dolor ${c}consectetur adipiscing elit"
doTest("d/sit<CR>", before, after)
}
@Test
fun testDeleteBackwardsToLastSearchResult() {
val before = "Lorem ipsum dolor sit amet, ${c}consectetur adipiscing elit"
val after = "Lorem ipsum dolor ${c}elit"
doTest("dn", before, after) {
enterSearch("sit", false)
typeText("\$b") // Cursor to start of "elit"
}
}
@Test
fun testDeleteBackwardsToSearchResultWithLinewiseOffset() {
val before = """
|First line
|Lorem ipsum dolor sit amet, consectetur ${c}adipiscing elit
|Lorem ipsum dolor sit amet, consectetur adipiscing elit
|Lorem ipsum dolor sit amet, consectetur adipiscing elit
|Lorem ipsum dolor sit amet, consectetur adipiscing elit
|Last line
""".trimMargin()
val after = """
|First line
|${c}Last line
""".trimMargin()
doTest("d?sit?3<CR>", before, after)
}
@Suppress("SpellCheckingInspection")
@Test
fun testDeleteBackwardsToEndOfSearchResultInclusive() {
val before = "Lorem ipsum dolor sit amet, ${c}consectetur adipiscing elit"
val after = "Lorem ipsum dolor si${c}onsectetur adipiscing elit"
doTest("d?sit?e<CR>", before, after)
}
@Test
fun testDeleteBackwardsToEndOfSearchResultWithOffset() {
val before = "Lorem ipsum dolor sit amet, ${c}consectetur adipiscing elit"
val after = "Lorem ipsum dolor sit a${c}onsectetur adipiscing elit"
doTest("d?sit?e+3<CR>", before, after)
}
@Test
fun testDeleteBackwardsToSearchResultWithIncsearch() {
val before = "Lorem ipsum dolor sit amet, ${c}consectetur adipiscing elit"
val after = "Lorem ipsum dolor ${c}consectetur adipiscing elit"
doTest("d?sit<CR>", before, after) {
enterCommand("set incsearch")
}
}
// |[(|
@Test
fun testUnmatchedOpenParenthesis() {
@ -1346,14 +1212,14 @@ two
@Test
fun `test gv after backwards selection`() {
configureByText("${c}Oh, hi Mark\n")
typeText("yw", "$", "vb", "p", "gv")
typeText(parseKeys("yw" + "$" + "vb" + "p" + "gv"))
assertSelection("Oh")
}
@Test
fun `test gv after linewise selection`() {
configureByText("${c}Oh, hi Mark\nOh, hi Markus\n")
typeText("V", "y", "j", "V", "p", "gv")
typeText(parseKeys("V" + "y" + "j" + "V" + "p" + "gv"))
assertSelection("Oh, hi Mark\n")
}
}

View File

@ -1,23 +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 org.jetbrains.plugins.ideavim.action
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
class RedrawActionTest : VimTestCase() {
@Test
fun `test redraw action clears status line`() {
configureByText("lorem ipsum")
enterSearch("dolor") // Pattern not found: dolor
assertStatusLineMessageContains("Pattern not found: dolor")
typeText("<C-L>")
assertStatusLineCleared()
}
}

View File

@ -112,7 +112,6 @@ class DeleteCharacterLeftActionTest : VimTestCase() {
@Test
fun `test deleting characters scrolls caret into view`() {
configureByText("Hello world".repeat(200))
enterCommand("set nowrap")
enterCommand("set sidescrolloff=5")
// Scroll 70 characters to the left. First character on line should now be 71. sidescrolloff puts us at 76

View File

@ -244,13 +244,4 @@ class DeleteMotionActionTest : VimTestCase() {
""".trimIndent(),
)
}
@Test
fun `test delete line clears status line`() {
configureByPages(5) // Lorem ipsum
enterSearch("egestas")
assertStatusLineMessageContains("Pattern not found: egestas")
typeText("dd")
assertStatusLineCleared()
}
}

View File

@ -33,12 +33,6 @@ class InsertBackspaceActionTest : VimTestCase() {
enterCommand("set sidescrolloff=10")
typeText("70zl", "i", "<BS>")
// Note that because 'sidescroll' has the default value of 0, we scroll the caret to the middle of the screen, as
// well as applying sidescrolloff. Leftmost column was 69 (zero-based), and the caret is on column 80. Deleting a
// character moves the caret to column 79, which is within 'sidescrolloff' of the left edge of the screen. The
// screen is scrolled by 'sidescroll', which has the default value of 0, so we scroll until the caret is in the
// middle of the screen, which is 80 characters wide: 79-(80/2)=39
assertVisibleLineBounds(0, 39, 118)
}
}

View File

@ -8,16 +8,11 @@
package org.jetbrains.plugins.ideavim.action.change.insert
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.ijOptions
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class InsertNewLineAboveActionTest : VimTestCase() {
@Test
@ -130,46 +125,4 @@ class InsertNewLineAboveActionTest : VimTestCase() {
""".trimMargin()
doTest("O", before, after, Mode.INSERT)
}
@Test
fun `test insert line above first line that is soft-wrapped`() {
val before = """
Lorem Ipsumm dolor sit amet, consectetur adipiscing elit. Morbi gravida commodo orci, egestas placerat ${c}purus rhoncus non. Donec efficitur placerat lorem, non ullamcorper nisl. Aliquam vestibulum, purus a pretium sodales, lorem libero placerat tortor, ut gravida est arcu nec purus. Suspendisse luctus euismod mi, at consectetur sapien facilisis sed. Duis eu magna id nisi lacinia vehicula in quis mauris. Donec tincidunt, erat in euismod placerat, tortor eros efficitur ligula, non finibus metus enim in ex. Nam commodo libero quis vestibulum congue. Vivamus sit amet tincidunt orci, in luctus tortor. Ut aliquam porttitor pharetra. Sed vel mi lacinia, auctor eros vel, condimentum eros. Fusce suscipit auctor venenatis. Aliquam elit risus, eleifend quis mollis eu, venenatis quis ex. Nunc varius consectetur eros sit amet efficitur. Donec a elit rutrum, tristique est in, maximus sem. Donec eleifend magna vitae suscipit viverra. Phasellus luctus aliquam tellus viverra consequat.
Aliquam tristique eros vel magna dictum tincidunt. Duis sagittis mi et bibendum congue. Donec sollicitudin, ipsum quis pellentesque efficitur, metus quam congue nulla, vel rutrum neque lectus vitae sem. In accumsan scelerisque risus, ac sollicitudin purus ornare in. Proin leo erat, tempus vitae purus nec, lobortis bibendum tortor. Aenean mauris sem, interdum id facilisis et, ullamcorper ut libero. Quisque magna ligula, euismod sit amet ipsum non, maximus ultrices nulla. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras facilisis arcu vitae orci scelerisque, vel dignissim massa dapibus. Fusce sed urna ut orci pellentesque consectetur. Maecenas rutrum erat ac libero elementum dictum. Donec pulvinar, sem feugiat suscipit mattis, turpis tellus consectetur dui, vitae vehicula dolor purus eu lectus. Nullam lorem ligula, aliquet id eros sed, rhoncus consequat neque. Cras eget erat non nunc convallis accumsan id in ipsum.
In id lacus diam. Curabitur orci libero, sollicitudin sed magna efficitur, finibus elementum mi. Cras aliquam enim eu scelerisque consectetur. Ut lacinia, velit sed dictum sollicitudin, mauris metus fringilla quam, vitae pellentesque tortor leo ut lectus. Fusce facilisis, eros ac egestas porttitor, enim arcu molestie purus, ut porta erat neque ac est. Ut facilisis, ante vel feugiat ultricies, metus nulla vestibulum dui, eget luctus lorem urna sed ex. Mauris quis lectus efficitur, sollicitudin urna vel, suscipit mi. Aliquam fringilla fermentum nunc. Phasellus suscipit nunc a dui gravida, sed euismod elit mattis. Donec pharetra, sem at finibus fermentum, dui lacus ornare arcu, eget maximus massa purus at ipsum.
Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc quis ligula sed quam suscipit varius id et metus. Ut hendrerit diam eu turpis semper luctus. Aliquam efficitur tortor ut eros consectetur tristique. Vestibulum odio nunc, finibus eu ex auctor, pharetra congue urna. Proin sit amet malesuada nisl. Proin sagittis metus diam, vitae sollicitudin eros rutrum id. Nam imperdiet lacus et mi iaculis, vitae suscipit felis consequat.
Fusce in lectus eros. Vivamus imperdiet sodales enim id vulputate. Ut tincidunt hendrerit cursus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Cras maximus et justo et congue. Nam iaculis elementum ultrices. Quisque nec semper eros. Nulla nisl nunc, finibus ac ligula vel, ullamcorper egestas risus. Nunc dictum cursus leo, id pulvinar augue ullamcorper ac. Vivamus condimentum nunc non justo convallis, in condimentum ante malesuada. Vivamus gravida et metus vitae porta. Integer blandit magna metus, sodales commodo nibh rutrum ac. Ut tincidunt et justo a luctus. Nunc lacus lorem, finibus id vehicula eu, gravida ut augue.
""".trimMargin()
val after = """
${c}
Lorem Ipsumm dolor sit amet, consectetur adipiscing elit. Morbi gravida commodo orci, egestas placerat purus rhoncus non. Donec efficitur placerat lorem, non ullamcorper nisl. Aliquam vestibulum, purus a pretium sodales, lorem libero placerat tortor, ut gravida est arcu nec purus. Suspendisse luctus euismod mi, at consectetur sapien facilisis sed. Duis eu magna id nisi lacinia vehicula in quis mauris. Donec tincidunt, erat in euismod placerat, tortor eros efficitur ligula, non finibus metus enim in ex. Nam commodo libero quis vestibulum congue. Vivamus sit amet tincidunt orci, in luctus tortor. Ut aliquam porttitor pharetra. Sed vel mi lacinia, auctor eros vel, condimentum eros. Fusce suscipit auctor venenatis. Aliquam elit risus, eleifend quis mollis eu, venenatis quis ex. Nunc varius consectetur eros sit amet efficitur. Donec a elit rutrum, tristique est in, maximus sem. Donec eleifend magna vitae suscipit viverra. Phasellus luctus aliquam tellus viverra consequat.
Aliquam tristique eros vel magna dictum tincidunt. Duis sagittis mi et bibendum congue. Donec sollicitudin, ipsum quis pellentesque efficitur, metus quam congue nulla, vel rutrum neque lectus vitae sem. In accumsan scelerisque risus, ac sollicitudin purus ornare in. Proin leo erat, tempus vitae purus nec, lobortis bibendum tortor. Aenean mauris sem, interdum id facilisis et, ullamcorper ut libero. Quisque magna ligula, euismod sit amet ipsum non, maximus ultrices nulla. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras facilisis arcu vitae orci scelerisque, vel dignissim massa dapibus. Fusce sed urna ut orci pellentesque consectetur. Maecenas rutrum erat ac libero elementum dictum. Donec pulvinar, sem feugiat suscipit mattis, turpis tellus consectetur dui, vitae vehicula dolor purus eu lectus. Nullam lorem ligula, aliquet id eros sed, rhoncus consequat neque. Cras eget erat non nunc convallis accumsan id in ipsum.
In id lacus diam. Curabitur orci libero, sollicitudin sed magna efficitur, finibus elementum mi. Cras aliquam enim eu scelerisque consectetur. Ut lacinia, velit sed dictum sollicitudin, mauris metus fringilla quam, vitae pellentesque tortor leo ut lectus. Fusce facilisis, eros ac egestas porttitor, enim arcu molestie purus, ut porta erat neque ac est. Ut facilisis, ante vel feugiat ultricies, metus nulla vestibulum dui, eget luctus lorem urna sed ex. Mauris quis lectus efficitur, sollicitudin urna vel, suscipit mi. Aliquam fringilla fermentum nunc. Phasellus suscipit nunc a dui gravida, sed euismod elit mattis. Donec pharetra, sem at finibus fermentum, dui lacus ornare arcu, eget maximus massa purus at ipsum.
Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc quis ligula sed quam suscipit varius id et metus. Ut hendrerit diam eu turpis semper luctus. Aliquam efficitur tortor ut eros consectetur tristique. Vestibulum odio nunc, finibus eu ex auctor, pharetra congue urna. Proin sit amet malesuada nisl. Proin sagittis metus diam, vitae sollicitudin eros rutrum id. Nam imperdiet lacus et mi iaculis, vitae suscipit felis consequat.
Fusce in lectus eros. Vivamus imperdiet sodales enim id vulputate. Ut tincidunt hendrerit cursus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Cras maximus et justo et congue. Nam iaculis elementum ultrices. Quisque nec semper eros. Nulla nisl nunc, finibus ac ligula vel, ullamcorper egestas risus. Nunc dictum cursus leo, id pulvinar augue ullamcorper ac. Vivamus condimentum nunc non justo convallis, in condimentum ante malesuada. Vivamus gravida et metus vitae porta. Integer blandit magna metus, sodales commodo nibh rutrum ac. Ut tincidunt et justo a luctus. Nunc lacus lorem, finibus id vehicula eu, gravida ut augue.
""".trimMargin()
doTest("O", before, after, Mode.INSERT) {
// This test is for soft wraps. The caret should be on 0 logical position and 1 visual position
val editor = fixture.editor.vim
assertTrue(injector.ijOptions(editor).wrap)
assertEquals(1, editor.carets().single().getVisualPosition().line)
}
}
@Test
fun `test insert new line above clears status line`() {
configureByText("lorem ipsum")
enterSearch("dolor")
assertStatusLineMessageContains("Pattern not found: dolor")
typeText("O")
assertStatusLineCleared()
}
}

View File

@ -202,13 +202,4 @@ class InsertNewLineBelowActionTest : VimTestCase() {
performTest("o", after, Mode.INSERT)
}
@Test
fun `test insert new line above clears status line`() {
configureByText("lorem ipsum")
enterSearch("dolor")
assertStatusLineMessageContains("Pattern not found: dolor")
typeText("o")
assertStatusLineCleared()
}
}

View File

@ -1885,7 +1885,6 @@ class PutVisualTextActionTest : VimTestCase() {
Fusce in lectus eros. Vivamus imperdiet sodales enim$c id vulputate. Ut tincidunt hendrerit cursus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Cras maximus et justo et congue. Nam iaculis elementum ultrices. Quisque nec semper eros. Nulla nisl nunc, finibus ac ligula vel, ullamcorper egestas risus. Nunc dictum cursus leo, id pulvinar augue ullamcorper ac. Vivamus condimentum nunc non justo convallis, in condimentum ante malesuada. Vivamus gravida et metus vitae porta. Integer blandit magna metus, sodales commodo nibh rutrum ac. Ut tincidunt et justo a luctus. Nunc lacus lorem, finibus id vehicula eu, gravida ut augue.
""".trimIndent()
configureByText(before)
enterCommand("set nowrap")
typeText(injector.parser.parseKeys("Ypj<C-V>P"))
val after = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi gravida commodo orci, egestas placerat purus rhoncus non. Donec efficitur placerat lorem, non ullamcorper nisl. Aliquam vestibulum, purus a pretium sodales, lorem libero placerat tortor, ut gravida est arcu nec purus. Suspendisse luctus euismod mi, at consectetur sapien facilisis sed. Duis eu magna id nisi lacinia vehicula in quis mauris. Donec tincidunt, erat in euismod placerat, tortor eros efficitur ligula, non finibus metus enim in ex. Nam commodo libero quis vestibulum congue. Vivamus sit amet tincidunt orci, in luctus tortor. Ut aliquam porttitor pharetra. Sed vel mi lacinia, auctor eros vel, condimentum eros. Fusce suscipit auctor venenatis. Aliquam elit risus, eleifend quis mollis eu, venenatis quis ex. Nunc varius consectetur eros sit amet efficitur. Donec a elit rutrum, tristique est in, maximus sem. Donec eleifend magna vitae suscipit viverra. Phasellus luctus aliquam tellus viverra consequat.

View File

@ -12,42 +12,6 @@ import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
class SearchAgainNextActionTest : VimTestCase() {
@Test
fun `test search next updates status line`() {
doTest(
listOf(searchCommand("/ipsum"), "n"),
"""
${c}Lorem ipsum dolor sit amet,
Lorem ipsum dolor sit amet,
Lorem ipsum dolor sit amet,
""".trimIndent(),
"""
Lorem ipsum dolor sit amet,
Lorem ${c}ipsum dolor sit amet,
Lorem ipsum dolor sit amet,
""".trimIndent(),
)
assertStatusLineMessageContains("/ipsum")
}
@Test
fun `test search next for backwards search updates status line correctly`() {
doTest(
listOf(searchCommand("?ipsum"), "n"),
"""
Lorem ipsum dolor sit amet,
Lorem ipsum dolor sit amet,
${c}Lorem ipsum dolor sit amet,
""".trimIndent(),
"""
Lorem ${c}ipsum dolor sit amet,
Lorem ipsum dolor sit amet,
Lorem ipsum dolor sit amet,
""".trimIndent(),
)
assertStatusLineMessageContains("?ipsum")
}
@Test
fun `test search next after search command with offset`() {
val before = """

View File

@ -17,42 +17,6 @@ import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
class SearchAgainPreviousActionTest : VimTestCase() {
@Test
fun `test search previous updates status line`() {
doTest(
listOf(searchCommand("/ipsum"), "N"),
"""
Lorem ipsum dolor sit amet,
Lorem ipsum dolor sit amet,
${c}Lorem ipsum dolor sit amet,
""".trimIndent(),
"""
Lorem ipsum dolor sit amet,
Lorem ${c}ipsum dolor sit amet,
Lorem ipsum dolor sit amet,
""".trimIndent(),
)
assertStatusLineMessageContains("?ipsum")
}
@Test
fun `test search previous for backwards search updates status line correctly`() {
doTest(
listOf(searchCommand("?ipsum"), "N"),
"""
Lorem ipsum dolor sit amet,
Lorem ipsum dolor sit amet,
${c}Lorem ipsum dolor sit amet,
""".trimIndent(),
"""
Lorem ipsum dolor sit amet,
Lorem ipsum dolor sit amet,
Lorem ${c}ipsum dolor sit amet,
""".trimIndent(),
)
assertStatusLineMessageContains("/ipsum")
}
@TestWithoutNeovim(reason = SkipNeovimReason.EDITOR_MODIFICATION)
@Test
fun `test search with tabs`() {

View File

@ -15,15 +15,6 @@ import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
class SearchEntryFwdActionTest : VimTestCase() {
@Test
fun `test search clears status line`() {
configureByText("lorem ipsum")
enterSearch("dolor") // Shows "pattern not found message"
assertPluginErrorMessageContains("Pattern not found: dolor")
typeText("/") // No <CR>
assertStatusLineCleared()
}
@Test
fun `search in visual mode`() {
doTest(
@ -128,4 +119,4 @@ class SearchEntryFwdActionTest : VimTestCase() {
Mode.SELECT(SelectionType.CHARACTER_WISE),
)
}
}
}

View File

@ -13,7 +13,6 @@ import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimBehaviorDiffers
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
/**
@ -74,7 +73,6 @@ class SelectKeyHandlerTest : VimTestCase() {
@TestWithoutNeovim(SkipNeovimReason.SELECT_MODE)
@Test
@Disabled("VIM-3376")
fun `test char mode backspace`() {
this.doTest(
listOf("gh", "<BS>"),
@ -100,7 +98,6 @@ class SelectKeyHandlerTest : VimTestCase() {
@TestWithoutNeovim(SkipNeovimReason.SELECT_MODE)
@Test
@Disabled("VIM-3376")
fun `test char mode delete`() {
this.doTest(
listOf("gh", "<DEL>"),

View File

@ -65,7 +65,6 @@ class ScrollColumnRightActionTest : VimTestCase() {
repeat(200) { append("0") }
},
)
enterCommand("set nowrap")
typeText("j$")
// Assert we got initial scroll correct
// Note, this matches Vim - we've scrolled to centre (but only because the line above allows us to scroll without

View File

@ -130,14 +130,4 @@ class ScrollLineDownActionTest : VimTestCase() {
assertPosition(95, 4)
assertVisibleArea(76, 99)
}
@Test
fun `test scroll clears status line`() {
configureByPages(5)
setPositionAndScroll(29, 29)
enterSearch("egestas")
assertStatusLineMessageContains("Pattern not found: egestas")
typeText("<C-E>")
assertStatusLineCleared()
}
}

View File

@ -158,14 +158,4 @@ class ScrollLineUpActionTest : VimTestCase() {
assertVisibleArea(64, 98)
assertVisualPosition(88, 4) // Moves caret up by scrolloff
}
@Test
fun `test scroll clears status line`() {
configureByPages(5)
setPositionAndScroll(29, 29)
enterSearch("egestas")
assertStatusLineMessageContains("Pattern not found: egestas")
typeText("<C-Y>")
assertStatusLineCleared()
}
}

View File

@ -26,12 +26,6 @@ import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
// TODO: Split this class
// This class should handle simple ex entry features, such as starting ex entry, accepting/cancelling, cursor shape etc.
// Individual actions such as c_CTRL-B or c_CTRL-E (beginning/end of line), c_CTRL-R (insert register), insert digraph
// or literal, etc. should have individual test classes in the ideavim.ex.action package
// :cmap should also be tested separately
class ExEntryTest : VimTestCase() {
@BeforeEach
override fun setUp(testInfo: TestInfo) {
@ -39,31 +33,6 @@ class ExEntryTest : VimTestCase() {
configureByText("\n")
}
@Test
fun `test initial text set to empty string`() {
typeExInput(":")
assertExText("")
}
@Test
fun `test initial text set to current line range with count of 1`() {
typeExInput("1:")
assertExText(".")
}
@Test
fun `test initial text set to current line with offset for count greater than 1`() {
typeExInput("10:")
assertExText(".,.+9")
}
@Test
fun `test initial text set to visual marks when invoked in Visual mode`() {
configureByText("lorem ipsum\nlorem ipsum")
typeText("V", ":")
assertExText("'<,'>")
}
@Test
fun `test cancel entry`() {
assertFalse(options().incsearch)
@ -110,14 +79,6 @@ class ExEntryTest : VimTestCase() {
assertIsDeactivated()
}
@Test
fun `test ex entry clears status line`() {
enterSearch("lorem")
assertStatusLineMessageContains("Pattern not found: lorem")
typeExInput(":")
assertStatusLineCleared()
}
@Test
fun `test caret shape`() {
// Show block at end of input (normal)
@ -673,8 +634,8 @@ class ExEntryTest : VimTestCase() {
private fun typeExInput(text: String) {
assertTrue(
Regex("""\d*[:/?].*""").matches(text),
"Ex command must start with '[count]:', '[count]/' or '[count]?'",
text.startsWith(":") || text.startsWith('/') || text.startsWith('?'),
"Ex command must start with ':', '/' or '?'",
)
val keys = mutableListOf<KeyStroke>()

View File

@ -9,7 +9,9 @@
package org.jetbrains.plugins.ideavim.ex
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.OptionConstants
import com.maddyhome.idea.vim.state.mode.SelectionType
@ -73,17 +75,18 @@ class MultipleCaretsTest : VimTestCase() {
val before = "qwe\n" + "r${c}ty\n" + "asd\n" + "fg${c}h\n" + "zxc\n" + "vbn\n"
configureByText(before)
typeText(commandToKeys("j"))
val after = "qwe\n${c}rty asd\n${c}fgh zxc\nvbn\n"
val after = "qwe\nrty$c asd\nfgh$c zxc\nvbn\n"
assertState(after)
}
@Test
@Disabled
fun testJoinVisualLines() {
val before = "qwe\n" + "r${c}ty\n" + "asd\n" + "fg${c}h\n" + "zxc\n" + "vbn\n"
configureByText(before)
typeText("vj")
typeText(parseKeys("vj"))
typeText(commandToKeys("j"))
val after = "qwe\n${c}rty asd\n${c}fgh zxc\nvbn\n"
val after = "qwe\nrty$c asd\nfgh$c zxc\nvbn\n"
fixture.checkResult(after)
}
@ -101,7 +104,7 @@ class MultipleCaretsTest : VimTestCase() {
fun testCopyVisualText() {
val before = "qwe\n" + "${c}rty\n" + "asd\n" + "f${c}gh\n" + "zxc\n" + "vbn\n"
configureByText(before)
typeText("vj")
typeText(parseKeys("vj"))
typeText(commandToKeys(":co 2"))
val after =
"qwe\n" + "rty\n" + "${c}rty\n" + "asd\n" + "${c}fgh\n" + "zxc\n" + "asd\n" + "fgh\n" + "zxc\n" + "vbn\n"
@ -260,7 +263,7 @@ class MultipleCaretsTest : VimTestCase() {
VimPlugin.getRegister()
.storeText(editor.vim, editor.vim.primaryCaret(), TextRange(16, 19), SelectionType.CHARACTER_WISE, false)
typeText("vj")
typeText(parseKeys("vj"))
typeText(commandToKeys("pu"))
val after = "qwe\n" + "rty\n" + "${c}zxc\n" + "asd\n" + "fgh\n" + "${c}zxc\n" + "zxc\n" + "vbn\n"
@ -314,7 +317,7 @@ class MultipleCaretsTest : VimTestCase() {
val text = lastRegister.text
assertNotNull<Any>(text)
typeText("p")
typeText(injector.parser.parseKeys("p"))
val after = """qwe
|rty
|${c}rty

View File

@ -12,7 +12,7 @@ import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
class AddressTest : VimTestCase() {
class RangeTest : VimTestCase() {
@Test
fun testNoRange() {
configureByText("1\n2\n<caret>3\n4\n5\n")

View File

@ -8,7 +8,7 @@
package org.jetbrains.plugins.ideavim.ex
import com.maddyhome.idea.vim.ex.ranges.Range
import com.maddyhome.idea.vim.ex.ranges.Ranges
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
import com.maddyhome.idea.vim.vimscript.model.Script
import com.maddyhome.idea.vim.vimscript.model.commands.EchoCommand
@ -22,7 +22,7 @@ class VimLContextTest {
@Test
fun `get first context test`() {
val echoCommand = EchoCommand(Range(), listOf(SimpleExpression("oh, hi Mark")))
val echoCommand = EchoCommand(Ranges(), listOf(SimpleExpression("oh, hi Mark")))
val ifStatement1 = IfStatement(listOf(Pair(SimpleExpression(1), listOf(echoCommand))))
val ifStatement2 = IfStatement(listOf(Pair(SimpleExpression(1), listOf(ifStatement1))))
val script = Script(listOf(ifStatement2))

View File

@ -12,9 +12,17 @@ import org.jetbrains.plugins.ideavim.VimBehaviorDiffers
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
@Suppress("SpellCheckingInspection")
class CopyCommandTest : VimTestCase() {
@Test
@VimBehaviorDiffers(
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas efficitur nec odio vel malesuada.
Nunc tincidunt viverra ligula non scelerisque. Aliquam erat volutpat. Praesent in fermentum orci.
Nunc tincidunt viverra ligula non ${c}scelerisque. Aliquam erat volutpat. Praesent in fermentum orci.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
accumsan vitae, facilisis ac nulla.
""",
"caret position differs")
fun `test duplicate line below`() {
configureByText("""
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas efficitur nec odio vel malesuada.
@ -31,171 +39,4 @@ class CopyCommandTest : VimTestCase() {
accumsan vitae, facilisis ac nulla.
""".trimIndent())
}
@VimBehaviorDiffers(
originalVimAfter = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas efficitur nec odio vel malesuada.
Nunc tincidunt viverra ligula non scelerisque. Aliquam erat volutpat. Praesent in fermentum orci.
Nunc tincidunt viverra ligula non ${c}scelerisque. Aliquam erat volutpat. Praesent in fermentum orci.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
accumsan vitae, facilisis ac nulla.
""",
description = "Caret placement is wrong",
shouldBeFixed = true
)
@Test
fun `test duplicate line below with 'nostartofline'`() {
doTest(
exCommand("copy ."),
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas efficitur nec odio vel malesuada.
Nunc tincidunt viverra ligula non ${c}scelerisque. Aliquam erat volutpat. Praesent in fermentum orci.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
accumsan vitae, facilisis ac nulla.
""".trimIndent(),
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas efficitur nec odio vel malesuada.
Nunc tincidunt viverra ligula non scelerisque. Aliquam erat volutpat. Praesent in fermentum orci.
${c}Nunc tincidunt viverra ligula non scelerisque. Aliquam erat volutpat. Praesent in fermentum orci.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
accumsan vitae, facilisis ac nulla.
""".trimIndent(),
) {
enterCommand("set nostartofline")
}
}
@VimBehaviorDiffers(
originalVimAfter = """
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
${c}accumsan vitae, facilisis ac nulla.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas efficitur nec odio vel malesuada.
Nunc tincidunt viverra ligula non scelerisque. Aliquam erat volutpat. Praesent in fermentum orci.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
accumsan vitae, facilisis ac nulla.
""",
description = "Caret placement is incorrect",
shouldBeFixed = true
)
@Test
fun `test copy range to above first line`() {
doTest(
exCommand("3,4copy 0"),
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas efficitur nec odio vel malesuada.
Nunc tincidunt viverra ${c}ligula non scelerisque. Aliquam erat volutpat. Praesent in fermentum orci.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
accumsan vitae, facilisis ac nulla.
""".trimIndent(),
"""
${c}Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
accumsan vitae, facilisis ac nulla.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas efficitur nec odio vel malesuada.
Nunc tincidunt viverra ligula non scelerisque. Aliquam erat volutpat. Praesent in fermentum orci.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
accumsan vitae, facilisis ac nulla.
""".trimIndent(),
)
}
@VimBehaviorDiffers(
originalVimAfter = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas efficitur nec odio vel malesuada.
Nunc tincidunt viverra ligula non scelerisque. Aliquam erat volutpat. Praesent in fermentum orci.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
accumsan vitae, facilisis ac nulla.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
${c}accumsan vitae, facilisis ac nulla.
""",
description = "Caret placement is incorrect",
shouldBeFixed = true
)
@Test
fun `test copy range to below last line`() {
doTest(
exCommand("3,4copy $"),
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas efficitur nec odio vel malesuada.
Nunc tincidunt viverra ${c}ligula non scelerisque. Aliquam erat volutpat. Praesent in fermentum orci.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
accumsan vitae, facilisis ac nulla.
""".trimIndent(),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas efficitur nec odio vel malesuada.
|Nunc tincidunt viverra ligula non scelerisque. Aliquam erat volutpat. Praesent in fermentum orci.
|Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
|accumsan vitae, facilisis ac nulla.
|${c}Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
|accumsan vitae, facilisis ac nulla.
|""".trimMargin(),
)
}
@VimBehaviorDiffers(
originalVimAfter = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas efficitur nec odio vel malesuada.
Nunc tincidunt viverra ligula non scelerisque. Aliquam erat volutpat. Praesent in fermentum orci.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
accumsan vitae, facilisis ac nulla.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
${c}accumsan vitae, facilisis ac nulla.
""",
description = "Caret placement is incorrect",
shouldBeFixed = true
)
@Test
fun `test copy with no space between command and address`() {
doTest(
exCommand("3,4copy$"),
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas efficitur nec odio vel malesuada.
Nunc tincidunt viverra ${c}ligula non scelerisque. Aliquam erat volutpat. Praesent in fermentum orci.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
accumsan vitae, facilisis ac nulla.
""".trimIndent(),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas efficitur nec odio vel malesuada.
|Nunc tincidunt viverra ligula non scelerisque. Aliquam erat volutpat. Praesent in fermentum orci.
|Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
|accumsan vitae, facilisis ac nulla.
|${c}Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
|accumsan vitae, facilisis ac nulla.
|""".trimMargin(),
)
assertPluginError(false)
}
@VimBehaviorDiffers(
originalVimAfter = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas efficitur nec odio vel malesuada.
Nunc tincidunt viverra ligula non scelerisque. Aliquam erat volutpat. Praesent in fermentum orci.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
accumsan vitae, facilisis ac nulla.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
${c}accumsan vitae, facilisis ac nulla.
""",
description = "Caret placement is wrong",
shouldBeFixed = true
)
@Test
fun `test copy to below first address of range with no errors`() {
doTest(
exCommand("3,4copy 4,1,2,.,0"),
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas efficitur nec odio vel malesuada.
Nunc tincidunt viverra ${c}ligula non scelerisque. Aliquam erat volutpat. Praesent in fermentum orci.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
accumsan vitae, facilisis ac nulla.
""".trimIndent(),
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas efficitur nec odio vel malesuada.
Nunc tincidunt viverra ligula non scelerisque. Aliquam erat volutpat. Praesent in fermentum orci.
${c}Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
accumsan vitae, facilisis ac nulla.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor. Aliquam felis neque, varius eu
accumsan vitae, facilisis ac nulla.
""".trimIndent(),
)
assertPluginError(false)
}
}
}

View File

@ -11,7 +11,6 @@ package org.jetbrains.plugins.ideavim.ex.implementation.commands
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
@Suppress("SpellCheckingInspection")
class DeleteLinesCommandTest : VimTestCase() {
@Test
fun `test delete command without range`() {
@ -28,411 +27,4 @@ class DeleteLinesCommandTest : VimTestCase() {
accumsan vitae, facilisis ac nulla.
""".trimIndent())
}
@Test
fun `test delete command with single line range`() {
doTest(
exCommand("2d"),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|${c}Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
}
@Test
fun `test delete first line with 0`() {
doTest(
exCommand("0d"),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin(),
"""
|${c}Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
}
@Test
fun `test delete command with line range`() {
doTest(
exCommand("2,5d"),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|${c}Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
}
@Test
fun `test delete command with range starting with 0`() {
doTest(
exCommand("0,5d"),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin(),
"""
|${c}Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
}
@Test
fun `test delete command with missing start address in range`() {
doTest(
exCommand(",5d"),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|${c}Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|${c}Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
}
@Test
fun `test delete command with missing end address in range`() {
doTest(
exCommand("2,d"),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|${c}Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
}
@Test
fun `test delete command with range starting with negative offset`() {
doTest(
exCommand("-10,5d"),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
assertPluginError(true)
assertPluginErrorMessageContains("E16: Invalid range")
}
@Test
fun `test delete to register`() {
doTest(
exCommand("d a"),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|${c}Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
assertRegister('a', "Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.^J")
}
@Test
fun `test delete appends to register`() {
doTest(
listOf("\"ayy", exCommand("d A")),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|${c}Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
assertRegister(
'a', "Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.^J" +
"Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.^J"
)
}
@Test
fun `test delete to read only register`() {
doTest(
exCommand("d :"), // `%` and `.` are both read-only registers and range addresses
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
assertPluginError(true)
assertPluginErrorMessageContains("E488: Trailing characters: :")
}
@Test
fun `test delete to invalid register`() {
doTest(
exCommand("d ("),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
assertPluginError(true)
assertPluginErrorMessageContains("E488: Trailing characters: (")
}
@Test
fun `test delete with count`() {
doTest(
exCommand("d 3"),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|${c}Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
}
@Test
fun `test delete with count with no separating space`() {
doTest(
exCommand("d3"),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|${c}Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
}
@Test
fun `test delete with count and single line range`() {
doTest(
exCommand("2d4"),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|${c}Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
}
@Test
fun `test delete with count and line range`() {
doTest(
exCommand("2,4d3"),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|${c}Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|${c}Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
}
@Test
fun `test delete command with count specified as range reports errors`() {
doTest(
exCommand("d 1,4"),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
assertPluginError(true)
assertPluginErrorMessageContains("E488: Trailing characters: ,4")
}
@Test
fun `test delete command with count specified as address reports errors`() {
doTest(
exCommand("d ."),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
assertPluginError(true)
assertPluginErrorMessageContains("E488: Trailing characters: .")
}
@Test
fun `test delete with register and count`() {
doTest(
exCommand("d a 3"),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|${c}Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
assertRegister('a', "Morbi nec luctus tortor, id venenatis lacus.^J" +
"Nunc sit amet tellus vel purus cursus posuere et at purus.^J" +
"Ut id dapibus augue.^J")
}
}
}

View File

@ -8,12 +8,10 @@
package org.jetbrains.plugins.ideavim.ex.implementation.commands
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.vimscript.model.commands.FileCommand
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
import kotlin.test.assertContains
import kotlin.test.assertTrue
class FileCommandTest : VimTestCase() {
@ -22,28 +20,4 @@ class FileCommandTest : VimTestCase() {
val command = injector.vimscriptParser.parseCommand("file")
assertTrue(command is FileCommand)
}
@Test
fun `test file outputs file details as message`() {
configureByText("lorem ipsum")
enterCommand("file")
assertContains("\"/src/aaa.txt\" line 1 of 1 --0%-- col 1", VimPlugin.getMessage())
}
@Test
fun `test file with range reports errors`() {
configureByText("lorem ipsum")
enterCommand("3file")
assertPluginError(true)
assertPluginErrorMessageContains("E474: Invalid argument")
}
@Test
fun `test file with count reports errors`() {
configureByText("lorem ipsum")
// Technically, this would rename the current file to "3"
enterCommand("file 3")
assertPluginError(true)
assertPluginErrorMessageContains("E488: Trailing characters: 3")
}
}
}

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