mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2024-11-24 22:42:53 +01:00
Compare commits
157 Commits
57ddf2083e
...
c79286b9b0
Author | SHA1 | Date | |
---|---|---|---|
c79286b9b0 | |||
5f59b47b19 | |||
8d51537f79 | |||
052de10e3a | |||
9ece9a7a04 | |||
84c868afc3 | |||
f29ebab390 | |||
0cb8bba3fd | |||
c0ff2b5cd0 | |||
460234553d | |||
cdd5b2abaf | |||
9db1732eb3 | |||
63e292b21f | |||
362175431d | |||
5e2cab4eda | |||
b63792c8f8 | |||
f543b6a1d1 | |||
d367b3bc72 | |||
da2d8d707f | |||
|
75a417773f | ||
|
b3b3ee4f21 | ||
|
07b1db4b28 | ||
|
dc775a0f22 | ||
|
10228f953e | ||
|
afceecadbe | ||
|
b2a4e59571 | ||
|
b0b944bbf3 | ||
|
89a3d74b93 | ||
|
f4eef04750 | ||
|
e62c86b99f | ||
|
82bd792da5 | ||
|
a58c9065e6 | ||
|
e8bf984b76 | ||
|
23e1a3499f | ||
|
6b4e4bacd7 | ||
|
a84c04ca08 | ||
|
e67c71e440 | ||
|
5078ff9c7a | ||
|
647510de5d | ||
|
84e11e4236 | ||
|
9538714af1 | ||
|
ffd832d990 | ||
|
8de2b8976b | ||
|
a6aa26b5d9 | ||
|
2505651c68 | ||
|
e67c7b23ff | ||
|
453cca3b0c | ||
|
6cee04a4be | ||
|
ae8b9b4773 | ||
|
e748b7b265 | ||
|
c2401ec013 | ||
|
8073d7ecd0 | ||
|
64f7859ba7 | ||
|
f1b94d7026 | ||
|
79653b6048 | ||
|
b0e6b72281 | ||
|
e6220e5e53 | ||
|
3c064845b1 | ||
|
736cb219ca | ||
|
fb30e4e387 | ||
|
74550ffa16 | ||
|
d0a0672282 | ||
|
16e92ddf60 | ||
|
4d8e68d800 | ||
|
bbebfaf32a | ||
|
7e56331e47 | ||
|
750db8e71c | ||
|
4255ef68a3 | ||
|
3313464214 | ||
|
683ba32a15 | ||
|
90a60155e5 | ||
|
b25d06ed9e | ||
|
706ae3dd91 | ||
|
9b15ed8181 | ||
|
f355bef36b | ||
|
4391e69c48 | ||
|
0710d80391 | ||
|
cf41a3a76c | ||
|
31b2cd872f | ||
|
2b6945cbb2 | ||
|
ae5f43918f | ||
|
6b6bc2752e | ||
|
4556adae3c | ||
|
1b0886041b | ||
|
16e18f3ca7 | ||
|
ee0d67fbbb | ||
|
450527f172 | ||
|
135518ee39 | ||
|
58715ecb5f | ||
|
75e26b101d | ||
|
6421a6face | ||
|
948520f90a | ||
|
0765118ce2 | ||
|
efd4c7b617 | ||
|
c5346fbece | ||
|
fe8e8ccc3e | ||
|
eae111bc2c | ||
|
6a6c1dc6b4 | ||
|
86bbb282ab | ||
|
28aa156cb7 | ||
|
a7814e69de | ||
|
1452c116cf | ||
|
23dfc4b339 | ||
|
931d4be972 | ||
|
7dceda587b | ||
|
52a969074d | ||
|
e7b87d31cf | ||
|
5eb0fae08f | ||
|
798d805a0f | ||
|
0d4ba06e57 | ||
|
4913b13a2d | ||
|
b0bab992db | ||
|
af5f4227b7 | ||
|
fa6a694ea4 | ||
|
1da7ffc052 | ||
|
c673f5818c | ||
|
ec78a87644 | ||
|
69d14ddcf5 | ||
|
f62819df00 | ||
|
39a85b6bc2 | ||
|
f76ae3e867 | ||
|
3651e5f2f0 | ||
|
89e016ef6c | ||
|
e4996f4c4d | ||
|
c44ed58142 | ||
|
0091af2a41 | ||
|
d1eea68719 | ||
|
133aff7fd8 | ||
|
efde94db7a | ||
|
6ec072b34e | ||
|
4027a21514 | ||
|
3665b1ab00 | ||
|
cf6b292f0c | ||
|
507e4173d3 | ||
|
abc3575d3e | ||
|
2c0ff587e3 | ||
|
26c87535d6 | ||
|
6ac8e672be | ||
|
04ee2dd1e7 | ||
|
3106a98aee | ||
|
73769a3472 | ||
|
085e253d77 | ||
|
b2af8f153e | ||
|
37fb41fca8 | ||
|
e2b05ab639 | ||
|
354fd8fef0 | ||
|
9b97867be1 | ||
|
06685d1721 | ||
|
ae4b88a06b | ||
|
c83ecc46ed | ||
|
c32050a208 | ||
|
4a8c7227e6 | ||
|
55e61a7094 | ||
|
60977d05b6 | ||
|
601747f720 | ||
|
0c91bc3207 | ||
|
f5cd2c173f |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
6
.github/workflows/closeYoutrackOnCommit.yml
vendored
6
.github/workflows/closeYoutrackOnCommit.yml
vendored
@ -20,10 +20,10 @@ jobs:
|
||||
fetch-depth: 300
|
||||
- name: Get tags
|
||||
run: git fetch --tags origin
|
||||
- name: Set up JDK 11
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '11'
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
||||
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
||||
@ -34,7 +34,7 @@ jobs:
|
||||
echo "LAST_COMMIT=$(git rev-list -n 1 tags/workflow-close-youtrack)" >> $GITHUB_ENV
|
||||
|
||||
- name: Update YouTrack
|
||||
run: ./gradlew updateYoutrackOnCommit
|
||||
run: ./gradlew --no-configuration-cache updateYoutrackOnCommit
|
||||
env:
|
||||
SUCCESS_COMMIT: ${{ env.LAST_COMMIT }}
|
||||
YOUTRACK_TOKEN: ${{ secrets.YOUTRACK_TOKEN }}
|
||||
|
4
.github/workflows/integrationsTest.yml
vendored
4
.github/workflows/integrationsTest.yml
vendored
@ -18,10 +18,10 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 300
|
||||
- name: Set up JDK 11
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '11'
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
||||
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
||||
|
4
.github/workflows/kover.yml
vendored
4
.github/workflows/kover.yml
vendored
@ -18,10 +18,10 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 300
|
||||
- name: Set up JDK 11
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '11'
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
||||
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
||||
|
6
.github/workflows/mergePr.yml
vendored
6
.github/workflows/mergePr.yml
vendored
@ -20,17 +20,17 @@ jobs:
|
||||
fetch-depth: 50
|
||||
# See end of file updateChangeslog.yml for explanation of this secret
|
||||
ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }}
|
||||
- name: Set up JDK 11
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '11'
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
||||
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
||||
|
||||
- name: Update authors
|
||||
id: update_authors
|
||||
run: ./gradlew updateMergedPr -PprId=${{ github.event.number }}
|
||||
run: ./gradlew --no-configuration-cache updateMergedPr -PprId=${{ github.event.number }}
|
||||
env:
|
||||
GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
6
.github/workflows/updateAuthors.yml
vendored
6
.github/workflows/updateAuthors.yml
vendored
@ -25,10 +25,10 @@ jobs:
|
||||
ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }}
|
||||
- name: Get tags
|
||||
run: git fetch --tags origin
|
||||
- name: Set up JDK 11
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '11'
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
||||
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
||||
@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
- name: Update authors
|
||||
id: update_authors
|
||||
run: ./gradlew updateAuthors --stacktrace
|
||||
run: ./gradlew --no-configuration-cache updateAuthors --stacktrace
|
||||
env:
|
||||
SUCCESS_COMMIT: ${{ env.LAST_COMMIT }}
|
||||
GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
8
.github/workflows/updateChangelog.yml
vendored
8
.github/workflows/updateChangelog.yml
vendored
@ -22,10 +22,10 @@ jobs:
|
||||
ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }}
|
||||
- name: Get tags
|
||||
run: git fetch --tags origin
|
||||
- name: Set up JDK 11
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '11'
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
||||
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
||||
@ -36,7 +36,7 @@ jobs:
|
||||
echo "LAST_COMMIT=$(git rev-list -n 1 tags/workflow-changelog)" >> $GITHUB_ENV
|
||||
|
||||
- name: Update changelog
|
||||
run: ./gradlew updateChangelog
|
||||
run: ./gradlew --no-configuration-cache updateChangelog
|
||||
env:
|
||||
SUCCESS_COMMIT: ${{ env.LAST_COMMIT }}
|
||||
|
||||
@ -60,4 +60,4 @@ jobs:
|
||||
# dependabot updates. See mergeDependatobPR.yml file.
|
||||
# However, it turned out that GitHub accepts pushes from the actions as a PR and requires checks, that are always
|
||||
# false for pushing from actions.
|
||||
# This secret is created to implement the workaround described in https://stackoverflow.com/a/76135647/3124227
|
||||
# This secret is created to implement the workaround described in https://stackoverflow.com/a/76135647/3124227
|
||||
|
4
.github/workflows/updateFormatting.yml
vendored
4
.github/workflows/updateFormatting.yml
vendored
@ -20,10 +20,10 @@ jobs:
|
||||
fetch-depth: 50
|
||||
# See end of file updateChangeslog.yml for explanation of this secret
|
||||
ssh-key: ${{ secrets.PUSH_TO_PROTECTED_BRANCH_SECRET }}
|
||||
- name: Set up JDK 11
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '11'
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
||||
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -10,6 +10,8 @@
|
||||
!/.idea/runConfigurations
|
||||
!/.idea/codeStyles
|
||||
!/.idea/vcs.xml
|
||||
!/.idea/misc.xml
|
||||
!/.idea/.name
|
||||
|
||||
**/build/
|
||||
**/out/
|
||||
@ -22,7 +24,7 @@
|
||||
.teamcity/*.iml
|
||||
|
||||
# Generated by gradle task "generateGrammarSource"
|
||||
src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated
|
||||
vim-engine/src/main/java/com/maddyhome/idea/vim/parser/generated
|
||||
vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated
|
||||
# Generated JSONs for lazy classloading
|
||||
/vim-engine/src/main/resources/ksp-generated
|
||||
|
1
.idea/.name
Normal file
1
.idea/.name
Normal file
@ -0,0 +1 @@
|
||||
IdeaVim
|
22
.idea/misc.xml
Normal file
22
.idea/misc.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="EntryPointsManager">
|
||||
<list size="3">
|
||||
<item index="0" class="java.lang.String" itemvalue="com.intellij.vim.annotations.CommandOrMotion" />
|
||||
<item index="1" class="java.lang.String" itemvalue="com.intellij.vim.annotations.ExCommand" />
|
||||
<item index="2" class="java.lang.String" itemvalue="com.intellij.vim.annotations.VimscriptFunction" />
|
||||
</list>
|
||||
</component>
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/.teamcity/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="corretto-17" project-jdk-type="JavaSDK" />
|
||||
</project>
|
@ -0,0 +1,25 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Start IJ with IdeaVim (Split Mode)" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<log_file alias="idea.log" path="$PROJECT_DIR$/build/idea-sandbox/system/log/idea.log" />
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value="runIdeSplitMode" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" value="" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>false</RunAsTest>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
14
.teamcity/_Self/buildTypes/ReleasePlugin.kt
vendored
14
.teamcity/_Self/buildTypes/ReleasePlugin.kt
vendored
@ -19,8 +19,6 @@ 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 ReleaseMajor : ReleasePlugin("major")
|
||||
object ReleaseMinor : ReleasePlugin("minor")
|
||||
@ -158,16 +156,4 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
12
AUTHORS.md
12
AUTHORS.md
@ -511,6 +511,18 @@ Contributors:
|
||||
[![icon][github]](https://github.com/Aisper)
|
||||
|
||||
Egor Nikolaevsky
|
||||
* [![icon][mail]](mailto:77796630+throwaway69420-69420@users.noreply.github.com)
|
||||
[![icon][github]](https://github.com/kun-codes)
|
||||
|
||||
Bishwa Saha,
|
||||
* [![icon][mail]](mailto:alexfu@fastmail.com)
|
||||
[![icon][github]](https://github.com/alexfu)
|
||||
|
||||
Alex Fu
|
||||
* [![icon][mail]](mailto:jakepeters199@hotmail.com)
|
||||
[![icon][github]](https://github.com/LazyScaper)
|
||||
|
||||
Jake
|
||||
|
||||
Previous contributors:
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
IdeaVim project is licensed under MIT license except the following parts of it:
|
||||
|
||||
* File [RegExp.kt](src/main/java/com/maddyhome/idea/vim/regexp/RegExp.kt) is licensed under Vim License.
|
||||
* File [ScrollViewHelper.kt](com/maddyhome/idea/vim/helper/ScrollViewHelper.kt) is licensed under Vim License.
|
||||
* File [Tutor.kt](src/main/java/com/maddyhome/idea/vim/ui/Tutor.kt) is licensed under Vim License.
|
||||
|
||||
|
@ -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:2.0.0-1.0.22")
|
||||
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")
|
||||
|
@ -13,7 +13,7 @@ import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
|
||||
import com.google.devtools.ksp.processing.SymbolProcessorProvider
|
||||
import com.intellij.vim.processors.VimscriptFunctionProcessor
|
||||
|
||||
public class VimscriptFunctionProcessorProvider : SymbolProcessorProvider {
|
||||
class VimscriptFunctionProcessorProvider : SymbolProcessorProvider {
|
||||
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
|
||||
return VimscriptFunctionProcessor(environment)
|
||||
}
|
||||
|
145
build.gradle.kts
145
build.gradle.kts
@ -48,14 +48,14 @@ buildscript {
|
||||
classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
|
||||
|
||||
// This is needed for jgit to connect to ssh
|
||||
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.9.0.202403050737-r")
|
||||
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.10.0.202406032230-r")
|
||||
classpath("org.kohsuke:github-api:1.305")
|
||||
|
||||
classpath("io.ktor:ktor-client-core:2.3.11")
|
||||
classpath("io.ktor:ktor-client-core:2.3.12")
|
||||
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.12")
|
||||
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.12")
|
||||
|
||||
// This comes from the changelog plugin
|
||||
// classpath("org.jetbrains:markdown:0.3.1")
|
||||
@ -63,7 +63,6 @@ buildscript {
|
||||
}
|
||||
|
||||
plugins {
|
||||
antlr
|
||||
java
|
||||
kotlin("jvm") version "1.9.22"
|
||||
application
|
||||
@ -73,26 +72,11 @@ plugins {
|
||||
id("org.jetbrains.changelog") version "2.2.0"
|
||||
|
||||
id("org.jetbrains.kotlinx.kover") version "0.6.1"
|
||||
id("com.dorongold.task-tree") version "3.0.0"
|
||||
id("com.dorongold.task-tree") version "4.0.0"
|
||||
|
||||
id("com.google.devtools.ksp") version "1.9.22-1.0.17"
|
||||
}
|
||||
|
||||
ksp {
|
||||
arg("generated_directory", "$projectDir/src/main/resources/ksp-generated")
|
||||
arg("vimscript_functions_file", "intellij_vimscript_functions.json")
|
||||
arg("ex_commands_file", "intellij_ex_commands.json")
|
||||
arg("commands_file", "intellij_commands.json")
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
// tasks.named("kspKotlin").configure { dependsOn("clean") }
|
||||
tasks.named("kspKotlin").configure { dependsOn("generateGrammarSource") }
|
||||
tasks.named("kspTestFixturesKotlin").configure { enabled = false }
|
||||
tasks.named("kspTestFixturesKotlin").configure { enabled = false }
|
||||
tasks.named("kspTestKotlin").configure { enabled = false }
|
||||
}
|
||||
|
||||
// Import variables from gradle.properties file
|
||||
val javaVersion: String by project
|
||||
val kotlinVersion: String by project
|
||||
@ -100,8 +84,8 @@ val ideaVersion: String by project
|
||||
val ideaType: String by project
|
||||
val downloadIdeaSources: String by project
|
||||
val instrumentPluginCode: String by project
|
||||
val antlrVersion: String by project
|
||||
val remoteRobotVersion: String by project
|
||||
val splitModeVersion: String by project
|
||||
|
||||
val publishChannels: String by project
|
||||
val publishToken: String by project
|
||||
@ -109,6 +93,8 @@ val publishToken: String by project
|
||||
val slackUrl: String by project
|
||||
val youtrackToken: String by project
|
||||
|
||||
val releaseType: String? by project
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
|
||||
@ -117,12 +103,10 @@ repositories {
|
||||
dependencies {
|
||||
api(project(":vim-engine"))
|
||||
ksp(project(":annotation-processors"))
|
||||
implementation(project(":annotation-processors"))
|
||||
compileOnly(project(":annotation-processors"))
|
||||
|
||||
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
|
||||
compileOnly("org.jetbrains:annotations:24.1.0")
|
||||
runtimeOnly("org.antlr:antlr4-runtime:$antlrVersion")
|
||||
antlr("org.antlr:antlr4:$antlrVersion")
|
||||
|
||||
// --------- Test dependencies ----------
|
||||
|
||||
@ -143,12 +127,12 @@ dependencies {
|
||||
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
|
||||
testImplementation("org.mockito.kotlin:mockito-kotlin:5.3.1")
|
||||
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.2")
|
||||
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2")
|
||||
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2")
|
||||
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.2")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.3")
|
||||
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3")
|
||||
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3")
|
||||
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.3")
|
||||
}
|
||||
|
||||
configurations {
|
||||
@ -159,6 +143,8 @@ configurations {
|
||||
|
||||
tasks {
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
|
||||
// Set teamcity env variable locally to run additional tests for leaks.
|
||||
// By default, this test runs on TC only, but this test doesn't take a lot of time,
|
||||
// so we can turn it on for local development
|
||||
@ -171,6 +157,9 @@ tasks {
|
||||
}
|
||||
|
||||
compileJava {
|
||||
// CodeQL can't resolve the 'by project' property, so we need to give it a hint. This is the minimum version we need
|
||||
// so doesn't have to match exactly
|
||||
// Hint for the CodeQL autobuilder: sourceCompatibility = 17
|
||||
sourceCompatibility = javaVersion
|
||||
targetCompatibility = javaVersion
|
||||
|
||||
@ -196,6 +185,10 @@ tasks {
|
||||
}
|
||||
}
|
||||
|
||||
runIde {
|
||||
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
|
||||
}
|
||||
|
||||
downloadRobotServerPlugin {
|
||||
version.set(remoteRobotVersion)
|
||||
}
|
||||
@ -206,11 +199,33 @@ tasks {
|
||||
systemProperty("jb.privacy.policy.text", "<!--999.999-->")
|
||||
systemProperty("jb.consents.confirmation.enabled", "false")
|
||||
systemProperty("ide.show.tips.on.startup.default.value", "false")
|
||||
|
||||
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
|
||||
}
|
||||
|
||||
runIde {
|
||||
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
|
||||
// Add plugin open API sources to the plugin ZIP
|
||||
val createOpenApiSourceJar by registering(Jar::class) {
|
||||
// Java sources
|
||||
from(sourceSets.main.get().java) {
|
||||
include("**/com/maddyhome/idea/vim/**/*.java")
|
||||
}
|
||||
from(project(":vim-engine").sourceSets.main.get().java) {
|
||||
include("**/com/maddyhome/idea/vim/**/*.java")
|
||||
}
|
||||
// Kotlin sources
|
||||
from(kotlin.sourceSets.main.get().kotlin) {
|
||||
include("**/com/maddyhome/idea/vim/**/*.kt")
|
||||
}
|
||||
from(project(":vim-engine").kotlin.sourceSets.main.get().kotlin) {
|
||||
include("**/com/maddyhome/idea/vim/**/*.kt")
|
||||
}
|
||||
destinationDirectory.set(layout.buildDirectory.dir("libs"))
|
||||
archiveClassifier.set("src")
|
||||
}
|
||||
|
||||
buildPlugin {
|
||||
dependsOn(createOpenApiSourceJar)
|
||||
from(createOpenApiSourceJar) { into("lib/src") }
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,7 +236,6 @@ java {
|
||||
}
|
||||
|
||||
kotlin {
|
||||
explicitApi()
|
||||
jvmToolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(javaVersion))
|
||||
}
|
||||
@ -266,48 +280,6 @@ tasks {
|
||||
teamCityOutputFormat.set(true)
|
||||
}
|
||||
|
||||
generateGrammarSource {
|
||||
maxHeapSize = "128m"
|
||||
arguments.addAll(listOf("-package", "com.maddyhome.idea.vim.vimscript.parser.generated", "-visitor"))
|
||||
outputDirectory = file("src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated")
|
||||
}
|
||||
|
||||
named("compileKotlin") {
|
||||
dependsOn("generateGrammarSource")
|
||||
}
|
||||
named("compileTestKotlin") {
|
||||
dependsOn("generateTestGrammarSource")
|
||||
}
|
||||
named("compileTestFixturesKotlin") {
|
||||
dependsOn("generateTestFixturesGrammarSource")
|
||||
}
|
||||
|
||||
// Add plugin open API sources to the plugin ZIP
|
||||
val createOpenApiSourceJar by registering(Jar::class) {
|
||||
dependsOn("generateGrammarSource")
|
||||
// Java sources
|
||||
from(sourceSets.main.get().java) {
|
||||
include("**/com/maddyhome/idea/vim/**/*.java")
|
||||
}
|
||||
from(project(":vim-engine").sourceSets.main.get().java) {
|
||||
include("**/com/maddyhome/idea/vim/**/*.java")
|
||||
}
|
||||
// Kotlin sources
|
||||
from(kotlin.sourceSets.main.get().kotlin) {
|
||||
include("**/com/maddyhome/idea/vim/**/*.kt")
|
||||
}
|
||||
from(project(":vim-engine").kotlin.sourceSets.main.get().kotlin) {
|
||||
include("**/com/maddyhome/idea/vim/**/*.kt")
|
||||
}
|
||||
destinationDirectory.set(layout.buildDirectory.dir("libs"))
|
||||
archiveClassifier.set("src")
|
||||
}
|
||||
|
||||
buildPlugin {
|
||||
dependsOn(createOpenApiSourceJar)
|
||||
from(createOpenApiSourceJar) { into("lib/src") }
|
||||
}
|
||||
|
||||
patchPluginXml {
|
||||
// Don't forget to update plugin.xml
|
||||
sinceBuild.set("241.15989.150")
|
||||
@ -318,14 +290,21 @@ tasks {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tests
|
||||
|
||||
tasks {
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
ksp {
|
||||
arg("generated_directory", "$projectDir/src/main/resources/ksp-generated")
|
||||
arg("vimscript_functions_file", "intellij_vimscript_functions.json")
|
||||
arg("ex_commands_file", "intellij_ex_commands.json")
|
||||
arg("commands_file", "intellij_commands.json")
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
// tasks.named("kspKotlin").configure { dependsOn("clean") }
|
||||
tasks.named("kspTestFixturesKotlin").configure { enabled = false }
|
||||
tasks.named("kspTestFixturesKotlin").configure { enabled = false }
|
||||
tasks.named("kspTestKotlin").configure { enabled = false }
|
||||
}
|
||||
|
||||
|
||||
// --- Changelog
|
||||
|
||||
changelog {
|
||||
@ -451,6 +430,8 @@ val fixVersionsElementType = "VersionBundleElement"
|
||||
tasks.register("releaseActions") {
|
||||
group = "other"
|
||||
doLast {
|
||||
if (releaseType == "patch") return@doLast
|
||||
|
||||
val tickets = getYoutrackTicketsByQuery("%23%7BReady+To+Release%7D%20and%20tag:%20%7BIdeaVim%20Released%20In%20EAP%7D%20")
|
||||
if (tickets.isNotEmpty()) {
|
||||
println("Updating statuses for tickets: $tickets")
|
||||
|
@ -129,8 +129,26 @@ Original plugin: [vim-multiple-cursors](https://github.com/terryma/vim-multiple-
|
||||
</details>
|
||||
|
||||
### Instructions
|
||||
|
||||
https://github.com/terryma/vim-multiple-cursors/blob/master/doc/multiple_cursors.txt
|
||||
|
||||
At the moment, the default key binds for this plugin do not get mapped correctly in IdeaVim (see [VIM-2178](https://youtrack.jetbrains.com/issue/VIM-2178)). To enable the default key binds, add the following to your `.ideavimrc` file...
|
||||
|
||||
```
|
||||
" Remap multiple-cursors shortcuts to match terryma/vim-multiple-cursors
|
||||
nmap <C-n> <Plug>NextWholeOccurrence
|
||||
xmap <C-n> <Plug>NextWholeOccurrence
|
||||
nmap g<C-n> <Plug>NextOccurrence
|
||||
xmap g<C-n> <Plug>NextOccurrence
|
||||
xmap <C-x> <Plug>SkipOccurrence
|
||||
xmap <C-p> <Plug>RemoveOccurrence
|
||||
|
||||
" Note that the default <A-n> and g<A-n> shortcuts don't work on Mac due to dead keys.
|
||||
" <A-n> is used to enter accented text e.g. ñ
|
||||
" Feel free to pick your own mappings that are not affected. I like to use <leader>
|
||||
nmap <leader><C-n> <Plug>AllWholeOccurrences
|
||||
xmap <leader><C-n> <Plug>AllWholeOccurrences
|
||||
nmap <leader>g<C-n> <Plug>AllOccurrences
|
||||
xmap <leader>g<C-n> <Plug>AllOccurrences
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
@ -40,30 +40,33 @@ Plug 'nerdtree'
|
||||
- `:NERDTreeFind`
|
||||
- `:NERDTreeRefreshRoot`
|
||||
|
||||
| Key | Description | Map Setting |
|
||||
|---------|---------------------------------------------------------|--------------------------------|
|
||||
| `o` | Open files, directories and bookmarks | `g:NERDTreeMapActivateNode` |
|
||||
| `go` | Open selected file, but leave cursor in the NERDTree | `g:NERDTreeMapPreview` |
|
||||
| `t` | Open selected node/bookmark in a new tab | `g:NERDTreeMapOpenInTab` |
|
||||
| `T` | Same as 't' but keep the focus on the current tab | `g:NERDTreeMapOpenInTabSilent` |
|
||||
| `i` | Open selected file in a split window | `g:NERDTreeMapOpenSplit` |
|
||||
| `gi` | Same as i, but leave the cursor on the NERDTree | `g:NERDTreeMapPreviewSplit` |
|
||||
| `s` | Open selected file in a new vsplit | `g:NERDTreeMapOpenVSplit` |
|
||||
| `gs` | Same as s, but leave the cursor on the NERDTree | `g:NERDTreeMapPreviewVSplit` |
|
||||
| `O` | Recursively open the selected directory | `g:NERDTreeMapOpenRecursively` |
|
||||
| `x` | Close the current nodes parent | `g:NERDTreeMapCloseDir` |
|
||||
| `X` | Recursively close all children of the current node | `g:NERDTreeMapCloseChildren` |
|
||||
| `P` | Jump to the root node | `g:NERDTreeMapJumpRoot` |
|
||||
| `p` | Jump to current nodes parent | `g:NERDTreeMapJumpParent` |
|
||||
| `K` | Jump up inside directories at the current tree depth | `g:NERDTreeMapJumpFirstChild` |
|
||||
| `J` | Jump down inside directories at the current tree depth | `g:NERDTreeMapJumpLastChild` |
|
||||
| `<C-J>` | Jump down to next sibling of the current directory | `g:NERDTreeMapJumpNextSibling` |
|
||||
| `<C-K>` | Jump up to previous sibling of the current directory | `g:NERDTreeMapJumpPrevSibling` |
|
||||
| `r` | Recursively refresh the current directory | `g:NERDTreeMapRefresh` |
|
||||
| `R` | Recursively refresh the current root | `g:NERDTreeMapRefreshRoot` |
|
||||
| `m` | Display the NERDTree menu | `g:NERDTreeMapMenu` |
|
||||
| `q` | Close the NERDTree window | `g:NERDTreeMapQuit` |
|
||||
| `A` | Zoom (maximize/minimize) the NERDTree window | `g:NERDTreeMapToggleZoom` |
|
||||
| Key | Description | Map Setting |
|
||||
|---------|--------------------------------------------------------|--------------------------------|
|
||||
| `o` | Open files, directories and bookmarks | `g:NERDTreeMapActivateNode` |
|
||||
| `go` | Open selected file, but leave cursor in the NERDTree | `g:NERDTreeMapPreview` |
|
||||
| `t` | Open selected node/bookmark in a new tab | `g:NERDTreeMapOpenInTab` |
|
||||
| `T` | Same as 't' but keep the focus on the current tab | `g:NERDTreeMapOpenInTabSilent` |
|
||||
| `i` | Open selected file in a split window | `g:NERDTreeMapOpenSplit` |
|
||||
| `gi` | Same as i, but leave the cursor on the NERDTree | `g:NERDTreeMapPreviewSplit` |
|
||||
| `s` | Open selected file in a new vsplit | `g:NERDTreeMapOpenVSplit` |
|
||||
| `gs` | Same as s, but leave the cursor on the NERDTree | `g:NERDTreeMapPreviewVSplit` |
|
||||
| `O` | Recursively open the selected directory | `g:NERDTreeMapOpenRecursively` |
|
||||
| `x` | Close the current nodes parent | `g:NERDTreeMapCloseDir` |
|
||||
| `X` | Recursively close all children of the current node | `g:NERDTreeMapCloseChildren` |
|
||||
| `P` | Jump to the root node | `g:NERDTreeMapJumpRoot` |
|
||||
| `p` | Jump to current nodes parent | `g:NERDTreeMapJumpParent` |
|
||||
| `K` | Jump up inside directories at the current tree depth | `g:NERDTreeMapJumpFirstChild` |
|
||||
| `J` | Jump down inside directories at the current tree depth | `g:NERDTreeMapJumpLastChild` |
|
||||
| `<C-J>` | Jump down to next sibling of the current directory | `g:NERDTreeMapJumpNextSibling` |
|
||||
| `<C-K>` | Jump up to previous sibling of the current directory | `g:NERDTreeMapJumpPrevSibling` |
|
||||
| `r` | Recursively refresh the current directory | `g:NERDTreeMapRefresh` |
|
||||
| `R` | Recursively refresh the current root | `g:NERDTreeMapRefreshRoot` |
|
||||
| `m` | Display the NERDTree menu | `g:NERDTreeMapMenu` |
|
||||
| `q` | Close the NERDTree window | `g:NERDTreeMapQuit` |
|
||||
| `A` | Zoom (maximize/minimize) the NERDTree window | `g:NERDTreeMapToggleZoom` |
|
||||
| `d` | Delete file or directory | `g:NERDTreeMapDelete` |
|
||||
| `n` | Create File | `g:NERDTreeMapNewFile` |
|
||||
| `N` | Create Directory | `g:NERDTreeMapNewDir` |
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
|
@ -8,17 +8,30 @@
|
||||
|
||||
# suppress inspection "UnusedProperty" for whole file
|
||||
|
||||
#ideaVersion=LATEST-EAP-SNAPSHOT
|
||||
# ideaVersion is the version of the IDE that will be added as a compile-time dependency. The format can be either
|
||||
# product version (e.g. 2024.1, 2024.1.1) or build (e.g. 241.15989.150, 241-EAP-SNAPSHOT). The dependency will be
|
||||
# resolved against the configured repositories, which by default includes Maven releases and snapshots, the CDN used to
|
||||
# download consumer releases, the plugin marketplace and so on.
|
||||
# You can find an example list of all CDN based versions for IDEA Community here:
|
||||
# https://data.services.jetbrains.com/products?code=IC
|
||||
# Maven releases are here: https://www.jetbrains.com/intellij-repository/releases
|
||||
# And snapshots: https://www.jetbrains.com/intellij-repository/snapshots
|
||||
ideaVersion=2024.1.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=SNAPSHOT
|
||||
version=chylex-36
|
||||
javaVersion=17
|
||||
remoteRobotVersion=0.11.22
|
||||
antlrVersion=4.10.1
|
||||
|
||||
# [VERSION UPDATE] 2024.2 - remove when IdeaVim targets 2024.2
|
||||
# Running IdeaVim in split mode requires 242. Update this version once 242 has been released, and remove it completely
|
||||
# when IdeaVim targets 242, at which point runIdeSplitMode will run correctly with the target version.
|
||||
# See also runIdeSplitMode
|
||||
splitModeVersion=242-EAP-SNAPSHOT
|
||||
|
||||
|
||||
# Please don't forget to update kotlin version in buildscript section
|
||||
# Also update kotlinxSerializationVersion version
|
||||
@ -35,9 +48,10 @@ youtrackToken=
|
||||
|
||||
# Gradle settings
|
||||
org.gradle.jvmargs='-Dfile.encoding=UTF-8'
|
||||
org.gradle.caching=true
|
||||
|
||||
# Disable warning from gradle-intellij-plugin. Kotlin stdlib is included as compileOnly, so the warning is unnecessary
|
||||
kotlin.stdlib.default.dependency=false
|
||||
|
||||
# Disable incremental annotation processing
|
||||
ksp.incremental=false
|
||||
ksp.incremental=false
|
||||
|
@ -22,15 +22,15 @@ repositories {
|
||||
dependencies {
|
||||
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.24")
|
||||
|
||||
implementation("io.ktor:ktor-client-core:2.3.11")
|
||||
implementation("io.ktor:ktor-client-core:2.3.12")
|
||||
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.12")
|
||||
implementation("io.ktor:ktor-client-auth:2.3.12")
|
||||
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
|
||||
|
||||
// This is needed for jgit to connect to ssh
|
||||
implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.9.0.202403050737-r")
|
||||
implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.10.0.202406032230-r")
|
||||
implementation("com.vdurmont:semver4j:3.1.0")
|
||||
}
|
||||
|
||||
|
@ -1,20 +0,0 @@
|
||||
// Set repository for snapshot versions of gradle plugin
|
||||
pluginManagement {
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://oss.sonatype.org/content/repositories/snapshots/'
|
||||
}
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = 'IdeaVIM'
|
||||
include 'vim-engine'
|
||||
include 'scripts'
|
||||
include 'annotation-processors'
|
||||
include 'tests:java-tests'
|
||||
include 'tests:property-tests'
|
||||
include 'tests:long-running-tests'
|
||||
include 'tests:ui-ij-tests'
|
||||
include 'tests:ui-py-tests'
|
||||
include 'tests:ui-fixtures'
|
21
settings.gradle.kts
Normal file
21
settings.gradle.kts
Normal file
@ -0,0 +1,21 @@
|
||||
// Set repository for snapshot versions of gradle plugin
|
||||
pluginManagement {
|
||||
repositories {
|
||||
maven {
|
||||
url = uri("https://oss.sonatype.org/content/repositories/snapshots/")
|
||||
}
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "IdeaVIM"
|
||||
|
||||
include("vim-engine")
|
||||
include("scripts")
|
||||
include("annotation-processors")
|
||||
include("tests:java-tests")
|
||||
include("tests:property-tests")
|
||||
include("tests:long-running-tests")
|
||||
include("tests:ui-ij-tests")
|
||||
include("tests:ui-py-tests")
|
||||
include("tests:ui-fixtures")
|
@ -14,36 +14,36 @@ import com.maddyhome.idea.vim.key.MappingOwner
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
public object RegisterActions {
|
||||
object RegisterActions {
|
||||
/**
|
||||
* Register all the key/action mappings for the plugin.
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun registerActions() {
|
||||
fun registerActions() {
|
||||
registerVimCommandActions()
|
||||
registerShortcutsWithoutActions()
|
||||
}
|
||||
|
||||
public fun findAction(id: String): EditorActionHandlerBase? {
|
||||
val commandBean = EngineCommandProvider.getCommands().firstOrNull { it.actionId == id }
|
||||
?: IntellijCommandProvider.getCommands().firstOrNull { it.actionId == id } ?: return null
|
||||
fun findAction(id: String): EditorActionHandlerBase? {
|
||||
val commandBean = IntellijCommandProvider.getCommands().firstOrNull { it.actionId == id }
|
||||
?: EngineCommandProvider.getCommands().firstOrNull { it.actionId == id } ?: return null
|
||||
return commandBean.instance
|
||||
}
|
||||
|
||||
public fun findActionOrDie(id: String): EditorActionHandlerBase {
|
||||
fun findActionOrDie(id: String): EditorActionHandlerBase {
|
||||
return findAction(id) ?: throw RuntimeException("Action $id is not registered")
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
public fun unregisterActions() {
|
||||
fun unregisterActions() {
|
||||
val keyGroup = VimPlugin.getKeyIfCreated()
|
||||
keyGroup?.unregisterCommandActions()
|
||||
}
|
||||
|
||||
private fun registerVimCommandActions() {
|
||||
val parser = VimPlugin.getKey()
|
||||
EngineCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
|
||||
IntellijCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
|
||||
EngineCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
|
||||
}
|
||||
|
||||
private fun registerShortcutsWithoutActions() {
|
||||
|
@ -37,8 +37,8 @@ import com.maddyhome.idea.vim.group.visual.VisualMotionGroup;
|
||||
import com.maddyhome.idea.vim.helper.MacKeyRepeat;
|
||||
import com.maddyhome.idea.vim.listener.VimListenerManager;
|
||||
import com.maddyhome.idea.vim.newapi.IjVimInjector;
|
||||
import com.maddyhome.idea.vim.newapi.IjVimSearchGroup;
|
||||
import com.maddyhome.idea.vim.ui.StatusBarIconFactory;
|
||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
|
||||
import com.maddyhome.idea.vim.vimscript.services.VariableService;
|
||||
import com.maddyhome.idea.vim.yank.YankGroupBase;
|
||||
import org.jdom.Element;
|
||||
@ -46,6 +46,7 @@ import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
|
||||
import static com.maddyhome.idea.vim.group.EditorGroup.EDITOR_STORE_ELEMENT;
|
||||
import static com.maddyhome.idea.vim.group.KeyGroup.SHORTCUT_CONFLICTS_ELEMENT;
|
||||
import static com.maddyhome.idea.vim.vimscript.services.VimRcService.executeIdeaVimRc;
|
||||
@ -123,12 +124,12 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
||||
return (FileGroup)VimInjectorKt.getInjector().getFile();
|
||||
}
|
||||
|
||||
public static @NotNull SearchGroup getSearch() {
|
||||
return ApplicationManager.getApplication().getService(SearchGroup.class);
|
||||
public static @NotNull IjVimSearchGroup getSearch() {
|
||||
return ApplicationManager.getApplication().getService(IjVimSearchGroup.class);
|
||||
}
|
||||
|
||||
public static @Nullable SearchGroup getSearchIfCreated() {
|
||||
return ApplicationManager.getApplication().getServiceIfCreated(SearchGroup.class);
|
||||
public static @Nullable IjVimSearchGroup getSearchIfCreated() {
|
||||
return ApplicationManager.getApplication().getServiceIfCreated(IjVimSearchGroup.class);
|
||||
}
|
||||
|
||||
public static @NotNull ProcessGroup getProcess() {
|
||||
@ -283,11 +284,11 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
||||
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
try {
|
||||
VimInjectorKt.injector.getOptionGroup().startInitVimRc();
|
||||
injector.getOptionGroup().startInitVimRc();
|
||||
executeIdeaVimRc(editor);
|
||||
}
|
||||
finally {
|
||||
VimInjectorKt.injector.getOptionGroup().endInitVimRc();
|
||||
injector.getOptionGroup().endInitVimRc();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -345,14 +346,14 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
||||
}
|
||||
|
||||
private void turnOffPlugin(boolean unsubscribe) {
|
||||
SearchGroup searchGroup = getSearchIfCreated();
|
||||
IjVimSearchGroup searchGroup = getSearchIfCreated();
|
||||
if (searchGroup != null) {
|
||||
searchGroup.turnOff();
|
||||
}
|
||||
if (unsubscribe) {
|
||||
VimListenerManager.INSTANCE.turnOff();
|
||||
}
|
||||
ExEntryPanel.fullReset();
|
||||
injector.getCommandLine().fullReset();
|
||||
|
||||
// Unregister vim actions in command mode
|
||||
RegisterActions.unregisterActions();
|
||||
|
@ -12,13 +12,13 @@ import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.maddyhome.idea.vim.group.EditorHolderService
|
||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
|
||||
|
||||
@Service(Service.Level.PROJECT)
|
||||
internal class VimProjectService(val project: Project) : Disposable {
|
||||
override fun dispose() {
|
||||
// Not sure if this is a best solution
|
||||
EditorHolderService.getInstance().editor = null
|
||||
ExEntryPanel.getInstance().editor = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -32,7 +32,7 @@ import javax.swing.KeyStroke
|
||||
* This class is used in Which-Key plugin, so don't make it internal. Generally, we should provide a proper
|
||||
* way to get ideavim keys for this plugin. See VIM-3085
|
||||
*/
|
||||
public class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandlerEx {
|
||||
class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandlerEx {
|
||||
private val handler = KeyHandler.getInstance()
|
||||
private val traceTime = injector.globalOptions().ideatracetime
|
||||
|
||||
|
@ -8,6 +8,6 @@
|
||||
|
||||
package com.maddyhome.idea.vim.action
|
||||
|
||||
public object IntellijCommandProvider : CommandProvider {
|
||||
object IntellijCommandProvider : CommandProvider {
|
||||
override val commandListFileName: String = "intellij_commands.json"
|
||||
}
|
@ -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,6 +44,7 @@ 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.ExEntryPanel
|
||||
import com.maddyhome.idea.vim.ui.ex.ExTextField
|
||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
||||
import java.awt.event.InputEvent
|
||||
@ -60,7 +60,7 @@ import javax.swing.KeyStroke
|
||||
* This class is used in Which-Key plugin, so don't make it internal. Generally, we should provide a proper
|
||||
* way to get ideavim keys for this plugin. See VIM-3085
|
||||
*/
|
||||
public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
|
||||
class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
|
||||
private val traceTime: Boolean
|
||||
get() {
|
||||
// Make sure the injector is initialized
|
||||
@ -257,7 +257,7 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
|
||||
private fun getEditor(e: AnActionEvent): Editor? {
|
||||
return e.getData(PlatformDataKeys.EDITOR)
|
||||
?: if (e.getData(PlatformDataKeys.CONTEXT_COMPONENT) is ExTextField) {
|
||||
EditorHolderService.getInstance().editor
|
||||
ExEntryPanel.getInstance().editor
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
|
||||
import com.maddyhome.idea.vim.newapi.ijOptions
|
||||
|
||||
@CommandOrMotion(keys = ["gJ"], modes = [Mode.NORMAL])
|
||||
public class DeleteJoinLinesAction : ChangeEditorActionHandler.ConditionalSingleExecution() {
|
||||
class DeleteJoinLinesAction : ChangeEditorActionHandler.ConditionalSingleExecution() {
|
||||
override val type: Command.Type = Command.Type.DELETE
|
||||
override fun runAsMulticaret(
|
||||
editor: VimEditor,
|
||||
|
@ -19,7 +19,7 @@ import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
|
||||
import com.maddyhome.idea.vim.newapi.ijOptions
|
||||
|
||||
@CommandOrMotion(keys = ["J"], modes = [Mode.NORMAL])
|
||||
public class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecution() {
|
||||
class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecution() {
|
||||
override val type: Command.Type = Command.Type.DELETE
|
||||
|
||||
override fun execute(
|
||||
|
@ -23,7 +23,7 @@ import com.maddyhome.idea.vim.newapi.ijOptions
|
||||
* @author vlan
|
||||
*/
|
||||
@CommandOrMotion(keys = ["gJ"], modes = [Mode.VISUAL])
|
||||
public class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution() {
|
||||
class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution() {
|
||||
override val type: Command.Type = Command.Type.DELETE
|
||||
|
||||
override fun executeForAllCarets(
|
||||
|
@ -23,7 +23,7 @@ import com.maddyhome.idea.vim.newapi.ijOptions
|
||||
* @author vlan
|
||||
*/
|
||||
@CommandOrMotion(keys = ["J"], modes = [Mode.VISUAL])
|
||||
public class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExecution() {
|
||||
class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExecution() {
|
||||
override val type: Command.Type = Command.Type.DELETE
|
||||
|
||||
override fun executeForAllCarets(
|
||||
|
@ -36,6 +36,18 @@ internal class VimEditorDelete : IdeActionHandler(IdeActions.ACTION_EDITOR_DELET
|
||||
internal class VimEditorDown : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_DOWN) {
|
||||
override val type: Command.Type = Command.Type.MOTION
|
||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES)
|
||||
|
||||
override fun execute(
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
cmd: Command,
|
||||
operatorArguments: OperatorArguments
|
||||
): Boolean {
|
||||
val undo = injector.undo
|
||||
val nanoTime = System.nanoTime()
|
||||
editor.forEachCaret { undo.endInsertSequence(it, it.offset, nanoTime) }
|
||||
return super.execute(editor, context, cmd, operatorArguments)
|
||||
}
|
||||
}
|
||||
|
||||
@CommandOrMotion(keys = ["<Tab>", "<C-I>"], modes = [Mode.INSERT])
|
||||
@ -48,6 +60,18 @@ internal class VimEditorTab : IdeActionHandler(IdeActions.ACTION_EDITOR_TAB) {
|
||||
internal class VimEditorUp : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_UP) {
|
||||
override val type: Command.Type = Command.Type.MOTION
|
||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES)
|
||||
|
||||
override fun execute(
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
cmd: Command,
|
||||
operatorArguments: OperatorArguments
|
||||
): Boolean {
|
||||
val undo = injector.undo
|
||||
val nanoTime = System.nanoTime()
|
||||
editor.forEachCaret { undo.endInsertSequence(it, it.offset, nanoTime) }
|
||||
return super.execute(editor, context, cmd, operatorArguments)
|
||||
}
|
||||
}
|
||||
|
||||
@CommandOrMotion(keys = ["K"], modes = [Mode.NORMAL])
|
||||
|
@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright 2003-2023 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.command
|
||||
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
|
||||
/**
|
||||
* COMPATIBILITY-LAYER: Additional class
|
||||
* Please see: https://jb.gg/zo8n0r
|
||||
*/
|
||||
public class CommandState(private val machine: VimStateMachine) {
|
||||
|
||||
public val isOperatorPending: Boolean
|
||||
get() = machine.isOperatorPending(machine.mode)
|
||||
|
||||
public val mode: Mode
|
||||
get() {
|
||||
val myMode = machine.mode
|
||||
return when (myMode) {
|
||||
is com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> Mode.CMD_LINE
|
||||
com.maddyhome.idea.vim.state.mode.Mode.INSERT -> Mode.INSERT
|
||||
is com.maddyhome.idea.vim.state.mode.Mode.NORMAL -> Mode.COMMAND
|
||||
is com.maddyhome.idea.vim.state.mode.Mode.OP_PENDING -> Mode.OP_PENDING
|
||||
com.maddyhome.idea.vim.state.mode.Mode.REPLACE -> Mode.REPLACE
|
||||
is com.maddyhome.idea.vim.state.mode.Mode.SELECT -> Mode.SELECT
|
||||
is com.maddyhome.idea.vim.state.mode.Mode.VISUAL -> Mode.VISUAL
|
||||
}
|
||||
}
|
||||
|
||||
public val commandBuilder: CommandBuilder
|
||||
get() = machine.commandBuilder
|
||||
|
||||
public val mappingState: MappingState
|
||||
get() = machine.mappingState
|
||||
|
||||
public enum class Mode {
|
||||
// Basic modes
|
||||
COMMAND, VISUAL, SELECT, INSERT, CMD_LINE, /*EX*/
|
||||
|
||||
// Additional modes
|
||||
OP_PENDING, REPLACE /*, VISUAL_REPLACE*/, INSERT_NORMAL, INSERT_VISUAL, INSERT_SELECT
|
||||
}
|
||||
|
||||
public enum class SubMode {
|
||||
NONE, VISUAL_CHARACTER, VISUAL_LINE, VISUAL_BLOCK
|
||||
}
|
||||
|
||||
public companion object {
|
||||
@JvmStatic
|
||||
public fun getInstance(editor: Editor): CommandState {
|
||||
return CommandState(editor.vim.vimStateMachine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal val CommandState.SubMode.engine: SelectionType
|
||||
get() = when (this) {
|
||||
CommandState.SubMode.NONE -> error("Unexpected value")
|
||||
CommandState.SubMode.VISUAL_CHARACTER -> SelectionType.CHARACTER_WISE
|
||||
CommandState.SubMode.VISUAL_LINE -> SelectionType.LINE_WISE
|
||||
CommandState.SubMode.VISUAL_BLOCK -> SelectionType.BLOCK_WISE
|
||||
}
|
@ -12,18 +12,18 @@ import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.LogicalPosition
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
|
||||
public class CharacterPosition(line: Int, col: Int) : LogicalPosition(line, col) {
|
||||
public fun toOffset(editor: Editor): Int = editor.vim.getLineStartOffset(line) + column
|
||||
class CharacterPosition(line: Int, col: Int) : LogicalPosition(line, col) {
|
||||
fun toOffset(editor: Editor): Int = editor.vim.getLineStartOffset(line) + column
|
||||
|
||||
public companion object {
|
||||
public fun fromOffset(editor: Editor, offset: Int): CharacterPosition {
|
||||
companion object {
|
||||
fun fromOffset(editor: Editor, offset: Int): CharacterPosition {
|
||||
// logical position "expands" tabs
|
||||
val logicalPosition = editor.offsetToLogicalPosition(offset)
|
||||
val lineStartOffset = editor.vim.getLineStartOffset(logicalPosition.line)
|
||||
return CharacterPosition(logicalPosition.line, offset - lineStartOffset)
|
||||
}
|
||||
|
||||
public fun atCaret(editor: Editor): CharacterPosition {
|
||||
fun atCaret(editor: Editor): CharacterPosition {
|
||||
return fromOffset(editor, editor.caretModel.offset)
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ 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
|
||||
public class ExOutputModel private constructor(private val myEditor: Editor) : VimExOutputPanel {
|
||||
class ExOutputModel private constructor(private val myEditor: Editor) : VimExOutputPanel {
|
||||
private var isActiveInTestMode = false
|
||||
|
||||
override val isActive: Boolean
|
||||
@ -28,14 +28,18 @@ public class ExOutputModel private constructor(private val myEditor: Editor) : V
|
||||
get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||
ExOutputPanel.getInstance(myEditor).text
|
||||
} else {
|
||||
field
|
||||
// ExOutputPanel always returns a non-null string
|
||||
field ?: ""
|
||||
}
|
||||
set(value) {
|
||||
// ExOutputPanel will strip a trailing newline. We'll do it now so that tests have the same behaviour. We also
|
||||
// never pass null to ExOutputPanel, but we do store it for tests, so we know if we're active or not
|
||||
val newValue = value?.removeSuffix("\n")
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||
ExOutputPanel.getInstance(myEditor).setText(value ?: "")
|
||||
ExOutputPanel.getInstance(myEditor).setText(newValue ?: "")
|
||||
} else {
|
||||
field = value
|
||||
isActiveInTestMode = !value.isNullOrEmpty()
|
||||
field = newValue
|
||||
isActiveInTestMode = !newValue.isNullOrEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,9 +60,9 @@ public class ExOutputModel private constructor(private val myEditor: Editor) : V
|
||||
}
|
||||
}
|
||||
|
||||
public companion object {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
public fun getInstance(editor: Editor): ExOutputModel {
|
||||
fun getInstance(editor: Editor): ExOutputModel {
|
||||
var model = editor.vimExOutput
|
||||
if (model == null) {
|
||||
model = ExOutputModel(editor)
|
||||
@ -66,5 +70,8 @@ public class ExOutputModel private constructor(private val myEditor: Editor) : V
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun tryGetInstance(editor: Editor) = editor.vimExOutput
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ package com.maddyhome.idea.vim.extension
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
@ -51,13 +50,13 @@ import javax.swing.KeyStroke
|
||||
*
|
||||
* @author vlan
|
||||
*/
|
||||
public object VimExtensionFacade {
|
||||
object VimExtensionFacade {
|
||||
|
||||
private val LOG = logger<VimExtensionFacade>()
|
||||
|
||||
/** The 'map' command for mapping keys to handlers defined in extensions. */
|
||||
@JvmStatic
|
||||
public fun putExtensionHandlerMapping(
|
||||
fun putExtensionHandlerMapping(
|
||||
modes: Set<MappingMode>,
|
||||
fromKeys: List<KeyStroke>,
|
||||
pluginOwner: MappingOwner,
|
||||
@ -68,13 +67,15 @@ public object VimExtensionFacade {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* COMPATIBILITY-LAYER: Additional method
|
||||
* Please see: https://jb.gg/zo8n0r
|
||||
*/
|
||||
/** The 'map' command for mapping keys to handlers defined in extensions. */
|
||||
@JvmStatic
|
||||
public fun putExtensionHandlerMapping(
|
||||
@Deprecated(
|
||||
"Use VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)",
|
||||
ReplaceWith(
|
||||
"VimPlugin.getKey().putKeyMapping(modes, fromKeys, pluginOwner, extensionHandler, recursive)",
|
||||
"com.maddyhome.idea.vim.VimPlugin"
|
||||
)
|
||||
)
|
||||
fun putExtensionHandlerMapping(
|
||||
modes: Set<MappingMode>,
|
||||
fromKeys: List<KeyStroke>,
|
||||
pluginOwner: MappingOwner,
|
||||
@ -86,7 +87,7 @@ public object VimExtensionFacade {
|
||||
|
||||
/** The 'map' command for mapping keys to other keys. */
|
||||
@JvmStatic
|
||||
public fun putKeyMapping(
|
||||
fun putKeyMapping(
|
||||
modes: Set<MappingMode>,
|
||||
fromKeys: List<KeyStroke>,
|
||||
pluginOwner: MappingOwner,
|
||||
@ -98,7 +99,7 @@ public object VimExtensionFacade {
|
||||
|
||||
/** The 'map' command for mapping keys to other keys if there is no other mapping to these keys */
|
||||
@JvmStatic
|
||||
public fun putKeyMappingIfMissing(
|
||||
fun putKeyMappingIfMissing(
|
||||
modes: Set<MappingMode>,
|
||||
fromKeys: List<KeyStroke>,
|
||||
pluginOwner: MappingOwner,
|
||||
@ -112,7 +113,7 @@ public object VimExtensionFacade {
|
||||
/**
|
||||
* Equivalent to calling 'command' to set up a user-defined command or alias
|
||||
*/
|
||||
public fun addCommand(
|
||||
fun addCommand(
|
||||
name: String,
|
||||
handler: CommandAliasHandler,
|
||||
) {
|
||||
@ -123,7 +124,7 @@ public object VimExtensionFacade {
|
||||
* Equivalent to calling 'command' to set up a user-defined command or alias
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun addCommand(
|
||||
fun addCommand(
|
||||
name: String,
|
||||
minimumNumberOfArguments: Int,
|
||||
maximumNumberOfArguments: Int,
|
||||
@ -141,7 +142,7 @@ public object VimExtensionFacade {
|
||||
* leaves the editor in the insert mode if it's been activated.
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun executeNormalWithoutMapping(keys: List<KeyStroke>, editor: Editor) {
|
||||
fun executeNormalWithoutMapping(keys: List<KeyStroke>, editor: Editor) {
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(editor.vim)
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
keys.forEach { keyHandler.handleKey(editor.vim, it, context, false, false, keyHandler.keyHandlerState) }
|
||||
@ -149,7 +150,7 @@ public object VimExtensionFacade {
|
||||
|
||||
/** Returns a single key stroke from the user input similar to 'getchar()'. */
|
||||
@JvmStatic
|
||||
public fun inputKeyStroke(editor: Editor): KeyStroke {
|
||||
fun inputKeyStroke(editor: Editor): KeyStroke {
|
||||
if (editor.vim.vimStateMachine.isDotRepeatInProgress) {
|
||||
val input = Extension.consumeKeystroke()
|
||||
LOG.trace("inputKeyStroke: dot repeat in progress. Input: $input")
|
||||
@ -181,43 +182,43 @@ 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 {
|
||||
fun inputString(editor: Editor, context: DataContext, prompt: String, finishOn: Char?): String {
|
||||
return injector.commandLine.inputString(editor.vim, context.vim, prompt, finishOn) ?: ""
|
||||
}
|
||||
|
||||
/** Get the current contents of the given register similar to 'getreg()'. */
|
||||
@JvmStatic
|
||||
public fun getRegister(register: Char): List<KeyStroke>? {
|
||||
fun getRegister(register: Char): List<KeyStroke>? {
|
||||
val reg = VimPlugin.getRegister().getRegister(register) ?: return null
|
||||
return reg.keys
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
public fun getRegisterForCaret(register: Char, caret: VimCaret): List<KeyStroke>? {
|
||||
val reg = caret.registerStorage.getRegister(register) ?: return null
|
||||
fun getRegisterForCaret(register: Char, caret: VimCaret): List<KeyStroke>? {
|
||||
val reg = injector.registerGroup.getRegister(register) ?: return null
|
||||
return reg.keys
|
||||
}
|
||||
|
||||
/** Set the current contents of the given register */
|
||||
@JvmStatic
|
||||
public fun setRegister(register: Char, keys: List<KeyStroke?>?) {
|
||||
fun setRegister(register: Char, keys: List<KeyStroke?>?) {
|
||||
VimPlugin.getRegister().setKeys(register, keys?.filterNotNull() ?: emptyList())
|
||||
}
|
||||
|
||||
/** Set the current contents of the given register */
|
||||
@JvmStatic
|
||||
public fun setRegisterForCaret(register: Char, caret: ImmutableVimCaret, keys: List<KeyStroke?>?) {
|
||||
caret.registerStorage.setKeys(register, keys?.filterNotNull() ?: emptyList())
|
||||
fun setRegisterForCaret(register: Char, caret: ImmutableVimCaret, keys: List<KeyStroke?>?) {
|
||||
injector.registerGroup.setKeys(register, keys?.filterNotNull() ?: emptyList())
|
||||
}
|
||||
|
||||
/** Set the current contents of the given register */
|
||||
@JvmStatic
|
||||
public fun setRegister(register: Char, keys: List<KeyStroke?>?, type: SelectionType) {
|
||||
fun setRegister(register: Char, keys: List<KeyStroke?>?, type: SelectionType) {
|
||||
VimPlugin.getRegister().setKeys(register, keys?.filterNotNull() ?: emptyList(), type)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
public fun exportScriptFunction(
|
||||
fun exportScriptFunction(
|
||||
scope: Scope?,
|
||||
name: String,
|
||||
args: List<String>,
|
||||
@ -253,7 +254,7 @@ public object VimExtensionFacade {
|
||||
}
|
||||
}
|
||||
|
||||
public fun VimExtensionFacade.exportOperatorFunction(name: String, function: OperatorFunction) {
|
||||
fun VimExtensionFacade.exportOperatorFunction(name: String, function: OperatorFunction) {
|
||||
exportScriptFunction(null, name, listOf("type"), emptyList(), false, noneOfEnum()) {
|
||||
editor, context, args ->
|
||||
|
||||
@ -274,6 +275,6 @@ public fun VimExtensionFacade.exportOperatorFunction(name: String, function: Ope
|
||||
}
|
||||
}
|
||||
|
||||
public fun interface ScriptFunction {
|
||||
public fun execute(editor: VimEditor, context: ExecutionContext, args: Map<String, VimDataType>): ExecutionResult
|
||||
}
|
||||
fun interface ScriptFunction {
|
||||
fun execute(editor: VimEditor, context: ExecutionContext, args: Map<String, VimDataType>): ExecutionResult
|
||||
}
|
||||
|
@ -19,12 +19,12 @@ import com.maddyhome.idea.vim.newapi.ij
|
||||
* COMPATIBILITY-LAYER: Created a class, renamed original class
|
||||
* Please see: https://jb.gg/zo8n0r
|
||||
*/
|
||||
public interface VimExtensionHandler : ExtensionHandler {
|
||||
interface VimExtensionHandler : ExtensionHandler {
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||
execute(editor.ij, context.ij)
|
||||
}
|
||||
|
||||
public fun execute(editor: Editor, context: DataContext)
|
||||
fun execute(editor: Editor, context: DataContext)
|
||||
|
||||
public abstract class WithCallback : ExtensionHandler.WithCallback(), VimExtensionHandler
|
||||
abstract class WithCallback : ExtensionHandler.WithCallback(), VimExtensionHandler
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
package com.maddyhome.idea.vim.extension.argtextobj;
|
||||
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.maddyhome.idea.vim.KeyHandler;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.api.*;
|
||||
import com.maddyhome.idea.vim.command.*;
|
||||
@ -23,7 +24,7 @@ import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor;
|
||||
import com.maddyhome.idea.vim.listener.VimListenerSuppressor;
|
||||
import com.maddyhome.idea.vim.newapi.IjVimCaret;
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine;
|
||||
import com.maddyhome.idea.vim.state.KeyHandlerState;
|
||||
import com.maddyhome.idea.vim.state.mode.Mode;
|
||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
@ -244,19 +245,18 @@ public class VimArgTextObjExtension implements VimExtension {
|
||||
|
||||
@Override
|
||||
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
|
||||
|
||||
IjVimEditor vimEditor = (IjVimEditor) editor;
|
||||
@NotNull VimStateMachine vimStateMachine = VimStateMachine.Companion.getInstance(vimEditor);
|
||||
int count = Math.max(1, vimStateMachine.getCommandBuilder().getCount());
|
||||
@NotNull KeyHandler keyHandler = KeyHandler.getInstance();
|
||||
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
|
||||
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
|
||||
|
||||
final ArgumentTextObjectHandler textObjectHandler = new ArgumentTextObjectHandler(isInner);
|
||||
//noinspection DuplicatedCode
|
||||
if (!vimStateMachine.isOperatorPending(editor.getMode())) {
|
||||
if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) {
|
||||
editor.nativeCarets().forEach((VimCaret caret) -> {
|
||||
final TextRange range = textObjectHandler.getRange(editor, caret, context, count, 0);
|
||||
if (range != null) {
|
||||
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
|
||||
if (vimStateMachine.getMode() instanceof Mode.VISUAL) {
|
||||
if (editor.getMode() instanceof Mode.VISUAL) {
|
||||
com.maddyhome.idea.vim.group.visual.EngineVisualGroupKt.vimSetSelection(caret, range.getStartOffset(), range.getEndOffset() - 1, true);
|
||||
} else {
|
||||
InlayHelperKt.moveToInlayAwareOffset(((IjVimCaret)caret).getCaret(), range.getStartOffset());
|
||||
@ -265,7 +265,7 @@ public class VimArgTextObjExtension implements VimExtension {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
vimStateMachine.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
|
||||
keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
|
||||
textObjectHandler, Command.Type.MOTION, EnumSet.noneOf(CommandFlags.class))));
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiWhiteSpace
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
@ -183,10 +184,10 @@ internal class CommentaryExtension : VimExtension {
|
||||
override val isRepeatable = true
|
||||
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||
val commandState = editor.vimStateMachine
|
||||
|
||||
val command = Command(operatorArguments.count1, CommentaryTextObjectMotionHandler, Command.Type.MOTION, EnumSet.noneOf(CommandFlags::class.java))
|
||||
commandState.commandBuilder.completeCommandPart(Argument(command))
|
||||
|
||||
val keyState = KeyHandler.getInstance().keyHandlerState
|
||||
keyState.commandBuilder.completeCommandPart(Argument(command))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiComment
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
||||
@ -40,7 +41,6 @@ import com.maddyhome.idea.vim.handler.toMotionOrError
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
import com.maddyhome.idea.vim.helper.PsiHelper
|
||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
@ -91,22 +91,23 @@ internal class Matchit : VimExtension {
|
||||
private class MatchitHandler(private val reverse: Boolean) : ExtensionHandler {
|
||||
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||
val commandState = editor.vimStateMachine
|
||||
val count = commandState.commandBuilder.count
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
val keyState = keyHandler.keyHandlerState
|
||||
val count = keyState.commandBuilder.count
|
||||
|
||||
// Reset the command count so it doesn't transfer onto subsequent commands.
|
||||
editor.vimStateMachine.commandBuilder.resetCount()
|
||||
keyState.commandBuilder.resetCount()
|
||||
|
||||
// Normally we want to jump to the start of the matching pair. But when moving forward in operator
|
||||
// pending mode, we want to include the entire match. isInOpPending makes that distinction.
|
||||
val isInOpPending = commandState.isOperatorPending(editor.mode)
|
||||
val isInOpPending = keyHandler.isOperatorPending(editor.mode, keyState)
|
||||
|
||||
if (isInOpPending) {
|
||||
val matchitAction = MatchitAction()
|
||||
matchitAction.reverse = reverse
|
||||
matchitAction.isInOpPending = true
|
||||
|
||||
commandState.commandBuilder.completeCommandPart(
|
||||
keyState.commandBuilder.completeCommandPart(
|
||||
Argument(
|
||||
Command(
|
||||
count,
|
||||
@ -233,7 +234,7 @@ private object FileTypePatterns {
|
||||
} else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") {
|
||||
this.cMakePatterns
|
||||
} else {
|
||||
return null
|
||||
this.htmlPatterns
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,11 +30,11 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMa
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
|
||||
import com.maddyhome.idea.vim.group.visual.vimSetSelection
|
||||
import com.maddyhome.idea.vim.helper.MessageHelper
|
||||
import com.maddyhome.idea.vim.helper.SearchHelper
|
||||
import com.maddyhome.idea.vim.helper.SearchOptions
|
||||
import com.maddyhome.idea.vim.helper.endOffsetInclusive
|
||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||
import com.maddyhome.idea.vim.helper.exitVisualMode
|
||||
import com.maddyhome.idea.vim.helper.findWordUnderCursor
|
||||
import com.maddyhome.idea.vim.helper.inVisualMode
|
||||
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
|
||||
import com.maddyhome.idea.vim.helper.userData
|
||||
@ -235,7 +235,7 @@ internal class VimMultipleCursorsExtension : VimExtension {
|
||||
val text = if (editor.inVisualMode) {
|
||||
primaryCaret.selectedText ?: return
|
||||
} else {
|
||||
val range = SearchHelper.findWordUnderCursor(editor, primaryCaret) ?: return
|
||||
val range = findWordUnderCursor(editor, primaryCaret) ?: return
|
||||
if (range.startOffset > primaryCaret.offset) return
|
||||
IjVimEditor(editor).getText(range)
|
||||
}
|
||||
@ -300,7 +300,7 @@ internal class VimMultipleCursorsExtension : VimExtension {
|
||||
}
|
||||
|
||||
private fun selectWordUnderCaret(editor: Editor, caret: Caret): TextRange? {
|
||||
val range = SearchHelper.findWordUnderCursor(editor, caret) ?: return null
|
||||
val range = findWordUnderCursor(editor, caret) ?: return null
|
||||
if (range.startOffset > caret.offset) return null
|
||||
|
||||
enterVisualMode(editor.vim)
|
||||
@ -327,6 +327,6 @@ internal class VimMultipleCursorsExtension : VimExtension {
|
||||
|
||||
private fun makePattern(text: String, whole: Boolean): String {
|
||||
// Pattern is "very nomagic" (ignore regex chars) and "force case sensitive". This is vim-multiple-cursors behaviour
|
||||
return "\\V\\C" + SearchHelper.makeSearchPattern(text, whole)
|
||||
return "\\V\\C" + if (whole) "\\<$text\\>" else text
|
||||
}
|
||||
}
|
||||
|
@ -478,6 +478,9 @@ internal class NerdTree : VimExtension {
|
||||
NerdAction.ToIj("SynchronizeCurrentFile"),
|
||||
)
|
||||
registerCommand("NERDTreeMapToggleHidden", "I", NerdAction.ToIj("ProjectView.ShowExcludedFiles"))
|
||||
registerCommand("NERDTreeMapNewFile", "n", NerdAction.ToIj("NewFile"))
|
||||
registerCommand("NERDTreeMapNewDir", "N", NerdAction.ToIj("NewDir"))
|
||||
registerCommand("NERDTreeMapDelete", "d", NerdAction.ToIj("\$Delete"))
|
||||
registerCommand("NERDTreeMapRefreshRoot", "R", NerdAction.ToIj("Synchronize"))
|
||||
registerCommand("NERDTreeMapMenu", "m", NerdAction.ToIj("ShowPopupMenu"))
|
||||
registerCommand("NERDTreeMapQuit", "q", NerdAction.ToIj("HideActiveWindow"))
|
||||
|
@ -10,6 +10,7 @@ package com.maddyhome.idea.vim.extension.replacewithregister
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
||||
@ -28,7 +29,6 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissin
|
||||
import com.maddyhome.idea.vim.extension.exportOperatorFunction
|
||||
import com.maddyhome.idea.vim.group.visual.VimSelection
|
||||
import com.maddyhome.idea.vim.helper.exitVisualMode
|
||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||
import com.maddyhome.idea.vim.key.OperatorFunction
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
@ -144,7 +144,7 @@ internal class ReplaceWithRegister : VimExtension {
|
||||
private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimCaret, visualSelection: PutData.VisualSelection) {
|
||||
val registerGroup = injector.registerGroup
|
||||
val lastRegisterChar = if (editor.caretModel.caretCount == 1) registerGroup.currentRegister else registerGroup.getCurrentRegisterForMulticaret()
|
||||
val savedRegister = caret.registerStorage.getRegister(lastRegisterChar) ?: return
|
||||
val savedRegister = registerGroup.getRegister(lastRegisterChar) ?: return
|
||||
|
||||
var usedType = savedRegister.type
|
||||
var usedText = savedRegister.text
|
||||
@ -166,17 +166,18 @@ private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimC
|
||||
putToLine = -1,
|
||||
)
|
||||
val vimEditor = editor.vim
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
ClipboardOptionHelper.IdeaputDisabler().use {
|
||||
VimPlugin.getPut().putText(
|
||||
vimEditor,
|
||||
context.vim,
|
||||
putData,
|
||||
operatorArguments = OperatorArguments(
|
||||
editor.vimStateMachine?.isOperatorPending(vimEditor.mode) ?: false,
|
||||
keyHandler.isOperatorPending(vimEditor.mode, keyHandler.keyHandlerState),
|
||||
0,
|
||||
editor.vim.mode,
|
||||
),
|
||||
saveToRegister = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
package com.maddyhome.idea.vim.extension.surround
|
||||
|
||||
import com.intellij.util.text.CharSequenceSubSequence
|
||||
|
||||
internal data class RepeatedCharSequence(val text: CharSequence, val count: Int) : CharSequence {
|
||||
override val length = text.length * count
|
||||
|
||||
override fun get(index: Int): Char {
|
||||
if (index < 0 || index >= length) throw IndexOutOfBoundsException()
|
||||
return text[index % text.length]
|
||||
}
|
||||
|
||||
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
|
||||
return CharSequenceSubSequence(this, startIndex, endIndex)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return text.repeat(count)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun of(text: CharSequence, count: Int): CharSequence {
|
||||
return when (count) {
|
||||
0 -> ""
|
||||
1 -> text
|
||||
else -> RepeatedCharSequence(text, count)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimCaret
|
||||
import com.maddyhome.idea.vim.api.VimChangeGroup
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.endsWithNewLine
|
||||
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
|
||||
@ -35,7 +36,10 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissin
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
|
||||
import com.maddyhome.idea.vim.extension.exportOperatorFunction
|
||||
import com.maddyhome.idea.vim.group.findBlockRange
|
||||
import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
|
||||
import com.maddyhome.idea.vim.key.OperatorFunction
|
||||
import com.maddyhome.idea.vim.newapi.IjVimCaret
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
|
||||
@ -78,7 +82,7 @@ internal class VimSurroundExtension : VimExtension {
|
||||
putKeyMappingIfMissing(MappingMode.XO, injector.parser.parseKeys("S"), owner, injector.parser.parseKeys("<Plug>VSurround"), true)
|
||||
}
|
||||
|
||||
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator())
|
||||
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator(supportsMultipleCursors = false, count = 1)) // TODO
|
||||
}
|
||||
|
||||
private class YSurroundHandler : ExtensionHandler {
|
||||
@ -106,7 +110,7 @@ internal class VimSurroundExtension : VimExtension {
|
||||
val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset)
|
||||
if (lastNonWhiteSpaceOffset != null) {
|
||||
val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1)
|
||||
performSurround(pair, range, it)
|
||||
performSurround(pair, range, it, count = operatorArguments.count1)
|
||||
}
|
||||
// it.moveToOffset(lineStartOffset)
|
||||
}
|
||||
@ -126,15 +130,13 @@ internal class VimSurroundExtension : VimExtension {
|
||||
|
||||
private class VSurroundHandler : ExtensionHandler {
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||
val selectionStart = editor.ij.caretModel.primaryCaret.selectionStart
|
||||
// NB: Operator ignores SelectionType anyway
|
||||
if (!Operator().apply(editor, context, editor.mode.selectionType)) {
|
||||
if (!Operator(supportsMultipleCursors = true, count = operatorArguments.count1).apply(editor, context, editor.mode.selectionType)) {
|
||||
return
|
||||
}
|
||||
runWriteAction {
|
||||
// Leave visual mode
|
||||
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
|
||||
editor.ij.caretModel.moveToOffset(selectionStart)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -155,6 +157,10 @@ internal class VimSurroundExtension : VimExtension {
|
||||
|
||||
companion object {
|
||||
fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
|
||||
editor.ij.runWithEveryCaretAndRestore { changeAtCaret(editor, context, charFrom, newSurround) }
|
||||
}
|
||||
|
||||
fun changeAtCaret(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
|
||||
// Save old register values for carets
|
||||
val surroundings = editor.sortedCarets()
|
||||
.map {
|
||||
@ -262,20 +268,41 @@ internal class VimSurroundExtension : VimExtension {
|
||||
}
|
||||
}
|
||||
|
||||
private class Operator : OperatorFunction {
|
||||
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
|
||||
val ijEditor = editor.ij
|
||||
private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction {
|
||||
override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
|
||||
val ijEditor = vimEditor.ij
|
||||
val c = getChar(ijEditor)
|
||||
if (c.code == 0) return true
|
||||
|
||||
val pair = getOrInputPair(c, ijEditor, context.ij) ?: return false
|
||||
// XXX: Will it work with line-wise or block-wise selections?
|
||||
val range = getSurroundRange(editor.currentCaret()) ?: return false
|
||||
performSurround(pair, range, editor.currentCaret(), selectionType == SelectionType.LINE_WISE)
|
||||
// Jump back to start
|
||||
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
|
||||
|
||||
runWriteAction {
|
||||
val change = VimPlugin.getChange()
|
||||
if (supportsMultipleCursors) {
|
||||
ijEditor.runWithEveryCaretAndRestore {
|
||||
applyOnce(ijEditor, change, pair, count)
|
||||
}
|
||||
}
|
||||
else {
|
||||
applyOnce(ijEditor, change, pair, count)
|
||||
// Jump back to start
|
||||
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: Pair<String, String>, count: Int) {
|
||||
// XXX: Will it work with line-wise or block-wise selections?
|
||||
val primaryCaret = editor.caretModel.primaryCaret
|
||||
val range = getSurroundRange(primaryCaret.vim)
|
||||
if (range != null) {
|
||||
val start = RepeatedCharSequence.of(pair.first, count)
|
||||
val end = RepeatedCharSequence.of(pair.second, count)
|
||||
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, start)
|
||||
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.endOffset + start.length, end)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSurroundRange(caret: VimCaret): TextRange? {
|
||||
val editor = caret.editor
|
||||
@ -362,15 +389,15 @@ private fun getChar(editor: Editor): Char {
|
||||
return res
|
||||
}
|
||||
|
||||
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, tagsOnNewLines: Boolean = false) {
|
||||
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
|
||||
runWriteAction {
|
||||
val editor = caret.editor
|
||||
val change = VimPlugin.getChange()
|
||||
val leftSurround = pair.first + if (tagsOnNewLines) "\n" else ""
|
||||
val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count)
|
||||
|
||||
val isEOF = range.endOffset == editor.text().length
|
||||
val hasNewLine = editor.endsWithNewLine()
|
||||
val rightSurround = if (tagsOnNewLines) {
|
||||
val rightSurround = (if (tagsOnNewLines) {
|
||||
if (isEOF && !hasNewLine) {
|
||||
"\n" + pair.second
|
||||
} else {
|
||||
@ -378,7 +405,7 @@ private fun performSurround(pair: Pair<String, String>, range: TextRange, caret:
|
||||
}
|
||||
} else {
|
||||
pair.second
|
||||
}
|
||||
}).let { RepeatedCharSequence.of(it, count) }
|
||||
|
||||
change.insertText(editor, caret, range.startOffset, leftSurround)
|
||||
change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)
|
||||
|
@ -9,6 +9,7 @@
|
||||
package com.maddyhome.idea.vim.extension.textobjentire;
|
||||
|
||||
import com.intellij.openapi.editor.Caret;
|
||||
import com.maddyhome.idea.vim.KeyHandler;
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext;
|
||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret;
|
||||
import com.maddyhome.idea.vim.api.VimEditor;
|
||||
@ -23,7 +24,7 @@ import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor;
|
||||
import com.maddyhome.idea.vim.listener.VimListenerSuppressor;
|
||||
import com.maddyhome.idea.vim.newapi.IjVimCaret;
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine;
|
||||
import com.maddyhome.idea.vim.state.KeyHandlerState;
|
||||
import com.maddyhome.idea.vim.state.mode.Mode;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@ -133,17 +134,18 @@ public class VimTextObjEntireExtension implements VimExtension {
|
||||
|
||||
@Override
|
||||
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
|
||||
@NotNull VimStateMachine vimStateMachine = VimStateMachine.Companion.getInstance(editor);
|
||||
int count = Math.max(1, vimStateMachine.getCommandBuilder().getCount());
|
||||
@NotNull KeyHandler keyHandler = KeyHandler.getInstance();
|
||||
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
|
||||
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
|
||||
|
||||
final EntireTextObjectHandler textObjectHandler = new EntireTextObjectHandler(ignoreLeadingAndTrailing);
|
||||
//noinspection DuplicatedCode
|
||||
if (!vimStateMachine.isOperatorPending(editor.getMode())) {
|
||||
if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) {
|
||||
((IjVimEditor) editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
|
||||
final TextRange range = textObjectHandler.getRange(editor, new IjVimCaret(caret), context, count, 0);
|
||||
if (range != null) {
|
||||
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
|
||||
if (vimStateMachine.getMode() instanceof Mode.VISUAL) {
|
||||
if (editor.getMode() instanceof Mode.VISUAL) {
|
||||
com.maddyhome.idea.vim.group.visual.EngineVisualGroupKt.vimSetSelection(new IjVimCaret(caret), range.getStartOffset(), range.getEndOffset() - 1, true);
|
||||
} else {
|
||||
InlayHelperKt.moveToInlayAwareOffset(caret, range.getStartOffset());
|
||||
@ -153,7 +155,7 @@ public class VimTextObjEntireExtension implements VimExtension {
|
||||
|
||||
});
|
||||
} else {
|
||||
vimStateMachine.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
|
||||
keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
|
||||
textObjectHandler, Command.Type.MOTION,
|
||||
EnumSet.noneOf(CommandFlags.class))));
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
package com.maddyhome.idea.vim.extension.textobjindent;
|
||||
|
||||
import com.intellij.openapi.editor.Caret;
|
||||
import com.maddyhome.idea.vim.KeyHandler;
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext;
|
||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret;
|
||||
import com.maddyhome.idea.vim.api.VimEditor;
|
||||
@ -24,7 +25,7 @@ import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor;
|
||||
import com.maddyhome.idea.vim.listener.VimListenerSuppressor;
|
||||
import com.maddyhome.idea.vim.newapi.IjVimCaret;
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine;
|
||||
import com.maddyhome.idea.vim.state.KeyHandlerState;
|
||||
import com.maddyhome.idea.vim.state.mode.Mode;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@ -263,17 +264,18 @@ public class VimIndentObject implements VimExtension {
|
||||
@Override
|
||||
public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context, @NotNull OperatorArguments operatorArguments) {
|
||||
IjVimEditor vimEditor = (IjVimEditor)editor;
|
||||
@NotNull VimStateMachine vimStateMachine = VimStateMachine.Companion.getInstance(vimEditor);
|
||||
int count = Math.max(1, vimStateMachine.getCommandBuilder().getCount());
|
||||
@NotNull KeyHandler keyHandler = KeyHandler.getInstance();
|
||||
@NotNull KeyHandlerState keyHandlerState = KeyHandler.getInstance().getKeyHandlerState();
|
||||
int count = Math.max(1, keyHandlerState.getCommandBuilder().getCount());
|
||||
|
||||
final IndentObjectHandler textObjectHandler = new IndentObjectHandler(includeAbove, includeBelow);
|
||||
|
||||
if (!vimStateMachine.isOperatorPending(editor.getMode())) {
|
||||
if (!keyHandler.isOperatorPending(editor.getMode(), keyHandlerState)) {
|
||||
((IjVimEditor)editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
|
||||
final TextRange range = textObjectHandler.getRange(vimEditor, new IjVimCaret(caret), context, count, 0);
|
||||
if (range != null) {
|
||||
try (VimListenerSuppressor.Locked ignored = SelectionVimListenerSuppressor.INSTANCE.lock()) {
|
||||
if (vimStateMachine.getMode() instanceof Mode.VISUAL) {
|
||||
if (editor.getMode() instanceof Mode.VISUAL) {
|
||||
EngineVisualGroupKt.vimSetSelection(new IjVimCaret(caret), range.getStartOffset(), range.getEndOffset() - 1, true);
|
||||
} else {
|
||||
InlayHelperKt.moveToInlayAwareOffset(caret, range.getStartOffset());
|
||||
@ -283,7 +285,7 @@ public class VimIndentObject implements VimExtension {
|
||||
|
||||
});
|
||||
} else {
|
||||
vimStateMachine.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
|
||||
keyHandlerState.getCommandBuilder().completeCommandPart(new Argument(new Command(count,
|
||||
textObjectHandler, Command.Type.MOTION,
|
||||
EnumSet.noneOf(CommandFlags.class))));
|
||||
}
|
||||
|
@ -57,8 +57,9 @@ import com.maddyhome.idea.vim.helper.CharacterHelper.changeCase
|
||||
import com.maddyhome.idea.vim.helper.CharacterHelper.charType
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
import com.maddyhome.idea.vim.helper.NumberType
|
||||
import com.maddyhome.idea.vim.helper.SearchHelper
|
||||
import com.maddyhome.idea.vim.helper.endOffsetInclusive
|
||||
import com.maddyhome.idea.vim.helper.findNumberUnderCursor
|
||||
import com.maddyhome.idea.vim.helper.findNumbersInRange
|
||||
import com.maddyhome.idea.vim.helper.inInsertMode
|
||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition
|
||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
|
||||
@ -79,12 +80,11 @@ import java.math.BigInteger
|
||||
import java.util.*
|
||||
import java.util.function.Consumer
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* Provides all the insert/replace related functionality
|
||||
*/
|
||||
public class ChangeGroup : VimChangeGroupBase() {
|
||||
class ChangeGroup : VimChangeGroupBase() {
|
||||
private val insertListeners = ContainerUtil.createLockFreeCopyOnWriteList<VimInsertListener>()
|
||||
private val listener: EditorMouseListener = object : EditorMouseListener {
|
||||
override fun mouseClicked(event: EditorMouseEvent) {
|
||||
@ -95,7 +95,7 @@ public class ChangeGroup : VimChangeGroupBase() {
|
||||
}
|
||||
}
|
||||
|
||||
public fun editorCreated(editor: Editor?, disposable: Disposable) {
|
||||
fun editorCreated(editor: Editor?, disposable: Disposable) {
|
||||
EventFacade.getInstance().addEditorMouseListener(editor!!, listener, disposable)
|
||||
}
|
||||
|
||||
@ -103,6 +103,9 @@ public class ChangeGroup : VimChangeGroupBase() {
|
||||
val editor = (vimEditor as IjVimEditor).editor
|
||||
val ijContext = context.ij
|
||||
val doc = vimEditor.editor.document
|
||||
val undo = injector.undo
|
||||
val nanoTime = System.nanoTime()
|
||||
vimEditor.forEachCaret { undo.startInsertSequence(it, it.offset, nanoTime) }
|
||||
CommandProcessor.getInstance().executeCommand(
|
||||
editor.project, {
|
||||
ApplicationManager.getApplication()
|
||||
@ -395,6 +398,7 @@ public class ChangeGroup : VimChangeGroupBase() {
|
||||
context: ExecutionContext,
|
||||
range: TextRange,
|
||||
) {
|
||||
val startPos = editor.offsetToBufferPosition(caret.offset)
|
||||
val startOffset = editor.getLineStartForOffset(range.startOffset)
|
||||
val endOffset = editor.getLineEndForOffset(range.endOffset)
|
||||
val ijEditor = (editor as IjVimEditor).editor
|
||||
@ -419,11 +423,7 @@ public class ChangeGroup : VimChangeGroupBase() {
|
||||
}
|
||||
}
|
||||
val afterAction = {
|
||||
val firstLine = editor.offsetToBufferPosition(
|
||||
min(startOffset.toDouble(), endOffset.toDouble()).toInt()
|
||||
).line
|
||||
val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine)
|
||||
caret.moveToOffset(newOffset)
|
||||
caret.moveToOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, startPos.line))
|
||||
restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
|
||||
}
|
||||
if (project != null) {
|
||||
@ -654,7 +654,7 @@ public class ChangeGroup : VimChangeGroupBase() {
|
||||
val alpha = nf.contains("alpha")
|
||||
val hex = nf.contains("hex")
|
||||
val octal = nf.contains("octal")
|
||||
val numberRanges = SearchHelper.findNumbersInRange((editor as IjVimEditor).editor, selectedRange, alpha, hex, octal)
|
||||
val numberRanges = findNumbersInRange((editor as IjVimEditor).editor, selectedRange, alpha, hex, octal)
|
||||
val newNumbers: MutableList<String?> = ArrayList()
|
||||
for (i in numberRanges.indices) {
|
||||
val numberRange = numberRanges[i]
|
||||
@ -677,8 +677,7 @@ public class ChangeGroup : VimChangeGroupBase() {
|
||||
val alpha = nf.contains("alpha")
|
||||
val hex = nf.contains("hex")
|
||||
val octal = nf.contains("octal")
|
||||
val range =
|
||||
SearchHelper.findNumberUnderCursor((editor as IjVimEditor).editor, (caret as IjVimCaret).caret, alpha, hex, octal)
|
||||
val range = findNumberUnderCursor((editor as IjVimEditor).editor, (caret as IjVimCaret).caret, alpha, hex, octal)
|
||||
if (range == null) {
|
||||
logger.debug("no number on line")
|
||||
return false
|
||||
@ -787,11 +786,11 @@ public class ChangeGroup : VimChangeGroupBase() {
|
||||
return number
|
||||
}
|
||||
|
||||
public fun addInsertListener(listener: VimInsertListener) {
|
||||
fun addInsertListener(listener: VimInsertListener) {
|
||||
insertListeners.add(listener)
|
||||
}
|
||||
|
||||
public fun removeInsertListener(listener: VimInsertListener) {
|
||||
fun removeInsertListener(listener: VimInsertListener) {
|
||||
insertListeners.remove(listener)
|
||||
}
|
||||
|
||||
|
@ -20,11 +20,13 @@ import com.intellij.openapi.components.Storage;
|
||||
import com.intellij.openapi.editor.*;
|
||||
import com.intellij.openapi.editor.event.CaretEvent;
|
||||
import com.intellij.openapi.editor.event.CaretListener;
|
||||
import com.intellij.openapi.editor.ex.EditorEx;
|
||||
import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.maddyhome.idea.vim.KeyHandler;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.api.*;
|
||||
import com.maddyhome.idea.vim.ex.ExOutputModel;
|
||||
import com.maddyhome.idea.vim.helper.CaretVisualAttributesHelperKt;
|
||||
import com.maddyhome.idea.vim.helper.CommandStateHelper;
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper;
|
||||
@ -38,6 +40,8 @@ import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
@ -45,6 +49,7 @@ 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;
|
||||
|
||||
/**
|
||||
@ -208,22 +213,28 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
|
||||
|
||||
initLineNumbers(editor);
|
||||
|
||||
// We add Vim bindings to all opened editors, even read-only editors. We also add bindings to editors that are used
|
||||
// elsewhere in the IDE, rather than just for editing project files. This includes editors used as part of the UI,
|
||||
// such as the VCS commit message, or used as read-only viewers for text output, such as log files in run
|
||||
// configurations or the Git Console tab. And editors are used for interactive stdin/stdout for console-based run
|
||||
// configurations.
|
||||
// Listen for changes to the font size, so we can hide the ex text field/output panel
|
||||
if (editor instanceof EditorEx editorEx) {
|
||||
editorEx.addPropertyChangeListener(FontSizeChangeListener.INSTANCE);
|
||||
}
|
||||
|
||||
// We add Vim bindings to all opened editors, including editors used as UI controls rather than just project file
|
||||
// editors. This includes editors used as part of the UI, such as the VCS commit message, or used as read-only
|
||||
// viewers for text output, such as log files in run configurations or the Git Console tab. And editors are used for
|
||||
// interactive stdin/stdout for console-based run configurations.
|
||||
// We want to provide an intuitive experience for working with these additional editors, so we automatically switch
|
||||
// to INSERT mode for interactive editors. Recognising these can be a bit tricky.
|
||||
// to INSERT mode if they are interactive editors. Recognising these can be a bit tricky.
|
||||
// These additional interactive editors are not file-based, but must have a writable document. However, log output
|
||||
// documents are also writable (the IDE is writing new content as it becomes available) just not user-editable. So
|
||||
// we must also check that the editor is not in read-only "viewer" mode (this includes "rendered" mode, which is
|
||||
// read-only and also hides the caret).
|
||||
// Furthermore, the interactive stdin/stdout console output is hosted in a read-only editor, but it can still be
|
||||
// edited. The `ConsoleViewImpl` class installs a typing handler that ignores the editor's `isViewer` property and
|
||||
// allows typing if the associated process (if any) is still running. We can get the editor's console view and check
|
||||
// this ourselves, but we have to wait until the editor has finished initialising before it's available in user
|
||||
// data.
|
||||
// Furthermore, interactive stdin/stdout console output in run configurations is hosted in a read-only editor, but
|
||||
// it can still be edited. The `ConsoleViewImpl` class installs a typing handler that ignores the editor's
|
||||
// `isViewer` property and allows typing if the associated process (if any) is still running. We can get the
|
||||
// editor's console view and check this ourselves, but we have to wait until the editor has finished initialising
|
||||
// before it's available in user data.
|
||||
// Finally, we have a special check for diff windows. If we compare against clipboard, we get a diff editor that is
|
||||
// not file based, is writable, and not a viewer, but we don't want to treat this as an interactive editor.
|
||||
// Note that we need a similar check in `VimEditor.isWritable` to allow Escape to work to exit insert mode. We need
|
||||
// to know that a read-only editor that is hosting a console view with a running process can be treated as writable.
|
||||
Runnable switchToInsertMode = () -> {
|
||||
@ -234,7 +245,8 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
|
||||
if (!editor.isViewer() &&
|
||||
!EditorHelper.isFileEditor(editor) &&
|
||||
editor.getDocument().isWritable() &&
|
||||
!CommandStateHelper.inInsertMode(editor)) {
|
||||
!CommandStateHelper.inInsertMode(editor) &&
|
||||
editor.getEditorKind() != EditorKind.DIFF) {
|
||||
switchToInsertMode.run();
|
||||
}
|
||||
ApplicationManager.getApplication().invokeLater(
|
||||
@ -253,6 +265,9 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
|
||||
UserDataManager.unInitializeEditor(editor);
|
||||
VimPlugin.getKey().unregisterShortcutKeys(new IjVimEditor(editor));
|
||||
CaretVisualAttributesHelperKt.removeCaretsVisualAttributes(editor);
|
||||
if (editor instanceof EditorEx editorEx) {
|
||||
editorEx.removePropertyChangeListener(FontSizeChangeListener.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
public void notifyIdeaJoin(@Nullable Project project, @NotNull VimEditor editor) {
|
||||
@ -315,7 +330,7 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
|
||||
@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, ijVimEditor).getNumber();
|
||||
final int caretLine = editor.getCaretModel().getLogicalPosition().line;
|
||||
|
||||
// lineNumber is 1 based
|
||||
@ -382,4 +397,33 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
|
||||
return Stream.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to property changes from the editor to hide ex text field/output panel when the editor's font is zoomed
|
||||
*/
|
||||
private static class FontSizeChangeListener implements PropertyChangeListener {
|
||||
public static FontSizeChangeListener INSTANCE = new FontSizeChangeListener();
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
if (VimPlugin.isNotEnabled()) return;
|
||||
if (evt.getPropertyName().equals(EditorEx.PROP_FONT_SIZE)) {
|
||||
Object source = evt.getSource();
|
||||
if (source instanceof Editor editor) {
|
||||
// The editor is being zoomed, so hide the command line or output panel, if they're being shown. On the one
|
||||
// hand, it's a little rude to cancel a command line for the user, but on the other, the panels obscure the
|
||||
// zoom indicator, so it looks nicer if we hide them.
|
||||
// Note that IDE scale is handled by LafManager.lookAndFeelChanged
|
||||
VimCommandLine activeCommandLine = injector.getCommandLine().getActiveCommandLine();
|
||||
if (activeCommandLine != null) {
|
||||
injector.getProcessGroup().cancelExEntry(new IjVimEditor(editor), false);
|
||||
}
|
||||
ExOutputModel exOutputModel = ExOutputModel.tryGetInstance(editor);
|
||||
if (exOutputModel != null) {
|
||||
exOutputModel.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright 2003-2023 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.group
|
||||
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.editor.Editor
|
||||
|
||||
@Service
|
||||
internal class EditorHolderService {
|
||||
var editor: Editor? = null
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getInstance(): EditorHolderService = service()
|
||||
}
|
||||
}
|
@ -1,476 +0,0 @@
|
||||
/*
|
||||
* Copyright 2003-2023 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.group;
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.actionSystem.PlatformDataKeys;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.LogicalPosition;
|
||||
import com.intellij.openapi.fileEditor.*;
|
||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
|
||||
import com.intellij.openapi.fileEditor.impl.EditorWindow;
|
||||
import com.intellij.openapi.fileEditor.impl.EditorsSplitters;
|
||||
import com.intellij.openapi.fileTypes.FileType;
|
||||
import com.intellij.openapi.fileTypes.FileTypeManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.project.ProjectManager;
|
||||
import com.intellij.openapi.roots.ProjectRootManager;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.vfs.VirtualFileManager;
|
||||
import com.intellij.openapi.vfs.VirtualFileSystem;
|
||||
import com.intellij.psi.search.FilenameIndex;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.search.ProjectScope;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.api.*;
|
||||
import com.maddyhome.idea.vim.common.TextRange;
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper;
|
||||
import com.maddyhome.idea.vim.helper.EditorHelperRt;
|
||||
import com.maddyhome.idea.vim.helper.MessageHelper;
|
||||
import com.maddyhome.idea.vim.helper.SearchHelper;
|
||||
import com.maddyhome.idea.vim.newapi.ExecuteExtensionKt;
|
||||
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext;
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine;
|
||||
import com.maddyhome.idea.vim.state.mode.Mode;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
|
||||
import static com.maddyhome.idea.vim.newapi.IjVimInjectorKt.globalIjOptions;
|
||||
|
||||
public class FileGroup extends VimFileBase {
|
||||
public boolean openFile(@NotNull String filename, @NotNull ExecutionContext context) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("openFile(" + filename + ")");
|
||||
}
|
||||
final Project project = PlatformDataKeys.PROJECT.getData(((IjEditorExecutionContext) context).getContext()); // API change - don't merge
|
||||
if (project == null) return false;
|
||||
|
||||
VirtualFile found = findFile(filename, project);
|
||||
|
||||
if (found != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("found file: " + found);
|
||||
}
|
||||
// Can't open a file unless it has a known file type. The next call will return the known type.
|
||||
// If unknown, IDEA will prompt the user to pick a type.
|
||||
FileType type = FileTypeManager.getInstance().getKnownFileTypeOrAssociate(found, project);
|
||||
|
||||
//noinspection IfStatementWithIdenticalBranches
|
||||
if (type != null) {
|
||||
FileEditorManager fem = FileEditorManager.getInstance(project);
|
||||
fem.openFile(found, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// There was no type and user didn't pick one. Don't open the file
|
||||
// Return true here because we found the file but the user canceled by not picking a type.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
VimPlugin.showMessage(MessageHelper.message("unable.to.find.0", filename));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable VirtualFile findFile(@NotNull String filename, @NotNull Project project) {
|
||||
VirtualFile found;
|
||||
// Vim supports both ~/ and ~\ (tested on Mac and Windows). On Windows, it supports forward- and back-slashes, but
|
||||
// it only supports forward slash on Unix (tested on Mac)
|
||||
// VFS works with both directory separators (tested on Mac and Windows)
|
||||
if (filename.startsWith("~/") || filename.startsWith("~\\")) {
|
||||
String relativePath = filename.substring(2);
|
||||
String dir = System.getProperty("user.home");
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("home dir file");
|
||||
logger.debug("looking for " + relativePath + " in " + dir);
|
||||
}
|
||||
found = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(dir, relativePath));
|
||||
}
|
||||
else {
|
||||
found = LocalFileSystem.getInstance().findFileByIoFile(new File(filename));
|
||||
|
||||
if (found == null) {
|
||||
found = findByNameInContentRoots(filename, project);
|
||||
if (found == null) {
|
||||
found = findByNameInProject(filename, project);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private VirtualFile findByNameInContentRoots(@NotNull String filename, @NotNull Project project) {
|
||||
VirtualFile found = null;
|
||||
ProjectRootManager prm = ProjectRootManager.getInstance(project);
|
||||
VirtualFile[] roots = prm.getContentRoots();
|
||||
for (int i = 0; i < roots.length; i++) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("root[" + i + "] = " + roots[i].getPath());
|
||||
}
|
||||
found = roots[i].findFileByRelativePath(filename);
|
||||
if (found != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static VirtualFile findByNameInProject(@NotNull String filename, @NotNull Project project) {
|
||||
GlobalSearchScope projectScope = ProjectScope.getProjectScope(project);
|
||||
Collection<VirtualFile> names = FilenameIndex.getVirtualFilesByName(filename, projectScope);
|
||||
if (!names.isEmpty()) {
|
||||
return names.stream().findFirst().get();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the current editor.
|
||||
*/
|
||||
@Override
|
||||
public void closeFile(@NotNull VimEditor editor, @NotNull ExecutionContext context) {
|
||||
final Project project = PlatformDataKeys.PROJECT.getData(((DataContext)context.getContext()));
|
||||
if (project != null) {
|
||||
final FileEditorManagerEx fileEditorManager = FileEditorManagerEx.getInstanceEx(project);
|
||||
final EditorWindow window = fileEditorManager.getCurrentWindow();
|
||||
final VirtualFile virtualFile = fileEditorManager.getCurrentFile();
|
||||
|
||||
if (virtualFile != null && window != null) {
|
||||
// During the work on VIM-2912 I've changed the close function to this one.
|
||||
// However, the function with manager seems to work weirdly and it causes VIM-2953
|
||||
//window.getManager().closeFile(virtualFile, true, false);
|
||||
window.closeFile(virtualFile);
|
||||
|
||||
// Get focus after closing tab
|
||||
window.requestFocus(true);
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
// This thing doesn't have an implementation in test mode
|
||||
EditorsSplitters.focusDefaultComponentInSplittersIfPresent(project);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes editor.
|
||||
*/
|
||||
@Override
|
||||
public void closeFile(int number, @NotNull ExecutionContext context) {
|
||||
final Project project = PlatformDataKeys.PROJECT.getData(((IjEditorExecutionContext) context).getContext());
|
||||
if (project == null) return;
|
||||
final FileEditorManagerEx fileEditorManager = FileEditorManagerEx.getInstanceEx(project);
|
||||
final EditorWindow window = fileEditorManager.getCurrentWindow();
|
||||
VirtualFile[] editors = fileEditorManager.getOpenFiles();
|
||||
if (window != null) {
|
||||
if (number >= 0 && number < editors.length) {
|
||||
fileEditorManager.closeFile(editors[number], window);
|
||||
}
|
||||
} if (!ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
// This thing doesn't have an implementation in test mode
|
||||
EditorsSplitters.focusDefaultComponentInSplittersIfPresent(project);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves specific file in the project.
|
||||
*/
|
||||
@Override
|
||||
public void saveFile(@NotNull ExecutionContext context) {
|
||||
NativeAction action;
|
||||
if (globalIjOptions(injector).getIdeawrite().contains(IjOptionConstants.ideawrite_all)) {
|
||||
action = injector.getNativeActionManager().getSaveAll();
|
||||
}
|
||||
else {
|
||||
action = injector.getNativeActionManager().getSaveCurrent();
|
||||
}
|
||||
ExecuteExtensionKt.execute(action, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves all files in the project.
|
||||
*/
|
||||
public void saveFiles(@NotNull ExecutionContext context) {
|
||||
ExecuteExtensionKt.execute(VimInjectorKt.getInjector().getNativeActionManager().getSaveAll(), context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects then next or previous editor.
|
||||
*/
|
||||
@Override
|
||||
public boolean selectFile(int count, @NotNull ExecutionContext context) {
|
||||
final Project project = PlatformDataKeys.PROJECT.getData(((IjEditorExecutionContext) context).getContext());
|
||||
if (project == null) return false;
|
||||
FileEditorManager fem = FileEditorManager.getInstance(project); // API change - don't merge
|
||||
VirtualFile[] editors = fem.getOpenFiles();
|
||||
if (count == 99) {
|
||||
count = editors.length - 1;
|
||||
}
|
||||
if (count < 0 || count >= editors.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fem.openFile(editors[count], true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects then next or previous editor.
|
||||
*/
|
||||
public void selectNextFile(int count, @NotNull ExecutionContext context) {
|
||||
Project project = PlatformDataKeys.PROJECT.getData(((IjEditorExecutionContext) context).getContext());
|
||||
if (project == null) return;
|
||||
FileEditorManager fem = FileEditorManager.getInstance(project); // API change - don't merge
|
||||
VirtualFile[] editors = fem.getOpenFiles();
|
||||
VirtualFile current = fem.getSelectedFiles()[0];
|
||||
for (int i = 0; i < editors.length; i++) {
|
||||
if (editors[i].equals(current)) {
|
||||
int pos = (i + (count % editors.length) + editors.length) % editors.length;
|
||||
|
||||
fem.openFile(editors[pos], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects previous editor tab.
|
||||
*/
|
||||
@Override
|
||||
public void selectPreviousTab(@NotNull ExecutionContext context) {
|
||||
Project project = PlatformDataKeys.PROJECT.getData(((DataContext)context.getContext()));
|
||||
if (project == null) return;
|
||||
VirtualFile vf = LastTabService.getInstance(project).getLastTab();
|
||||
if (vf != null && vf.isValid()) {
|
||||
FileEditorManager.getInstance(project).openFile(vf, true);
|
||||
}
|
||||
else {
|
||||
VimPlugin.indicateError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the previous tab.
|
||||
*/
|
||||
public @Nullable VirtualFile getPreviousTab(@NotNull DataContext context) {
|
||||
Project project = PlatformDataKeys.PROJECT.getData(context);
|
||||
if (project == null) return null;
|
||||
VirtualFile vf = LastTabService.getInstance(project).getLastTab();
|
||||
if (vf != null && vf.isValid()) {
|
||||
return vf;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable Editor selectEditor(Project project, @NotNull VirtualFile file) {
|
||||
FileEditorManager fMgr = FileEditorManager.getInstance(project);
|
||||
FileEditor[] feditors = fMgr.openFile(file, true);
|
||||
if (feditors.length > 0) {
|
||||
if (feditors[0] instanceof TextEditor) {
|
||||
Editor editor = ((TextEditor)feditors[0]).getEditor();
|
||||
if (!editor.isDisposed()) {
|
||||
return editor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayLocationInfo(@NotNull VimEditor vimEditor) {
|
||||
Editor editor = ((IjVimEditor)vimEditor).getEditor();
|
||||
StringBuilder msg = new StringBuilder();
|
||||
Document doc = editor.getDocument();
|
||||
|
||||
if (!(VimStateMachine.Companion.getInstance(new IjVimEditor(editor)).getMode() instanceof Mode.VISUAL)) {
|
||||
LogicalPosition lp = editor.getCaretModel().getLogicalPosition();
|
||||
int col = editor.getCaretModel().getOffset() - doc.getLineStartOffset(lp.line);
|
||||
int endoff = doc.getLineEndOffset(lp.line);
|
||||
if (endoff < EditorHelperRt.getFileSize(editor) && doc.getCharsSequence().charAt(endoff) == '\n') {
|
||||
endoff--;
|
||||
}
|
||||
int ecol = endoff - doc.getLineStartOffset(lp.line);
|
||||
LogicalPosition elp = editor.offsetToLogicalPosition(endoff);
|
||||
|
||||
msg.append("Col ").append(col + 1);
|
||||
if (col != lp.column) {
|
||||
msg.append("-").append(lp.column + 1);
|
||||
}
|
||||
|
||||
msg.append(" of ").append(ecol + 1);
|
||||
if (ecol != elp.column) {
|
||||
msg.append("-").append(elp.column + 1);
|
||||
}
|
||||
|
||||
int lline = editor.getCaretModel().getLogicalPosition().line;
|
||||
int total = new IjVimEditor(editor).lineCount();
|
||||
|
||||
msg.append("; Line ").append(lline + 1).append(" of ").append(total);
|
||||
|
||||
SearchHelper.CountPosition cp = SearchHelper.countWords(editor);
|
||||
|
||||
msg.append("; Word ").append(cp.getPosition()).append(" of ").append(cp.getCount());
|
||||
|
||||
int offset = editor.getCaretModel().getOffset();
|
||||
int size = EditorHelperRt.getFileSize(editor);
|
||||
|
||||
msg.append("; Character ").append(offset + 1).append(" of ").append(size);
|
||||
}
|
||||
else {
|
||||
msg.append("Selected ");
|
||||
|
||||
TextRange vr = new TextRange(editor.getSelectionModel().getBlockSelectionStarts(),
|
||||
editor.getSelectionModel().getBlockSelectionEnds());
|
||||
vr.normalize();
|
||||
|
||||
int lines;
|
||||
SearchHelper.CountPosition cp = SearchHelper.countWords(editor);
|
||||
int words = cp.getCount();
|
||||
int word = 0;
|
||||
if (vr.isMultiple()) {
|
||||
lines = vr.size();
|
||||
int cols = vr.getMaxLength();
|
||||
|
||||
msg.append(cols).append(" Cols; ");
|
||||
|
||||
for (int i = 0; i < vr.size(); i++) {
|
||||
cp = SearchHelper.countWords(editor, vr.getStartOffsets()[i], vr.getEndOffsets()[i] - 1);
|
||||
word += cp.getCount();
|
||||
}
|
||||
}
|
||||
else {
|
||||
LogicalPosition slp = editor.offsetToLogicalPosition(vr.getStartOffset());
|
||||
LogicalPosition elp = editor.offsetToLogicalPosition(vr.getEndOffset());
|
||||
|
||||
lines = elp.line - slp.line + 1;
|
||||
|
||||
cp = SearchHelper.countWords(editor, vr.getStartOffset(), vr.getEndOffset() - 1);
|
||||
word = cp.getCount();
|
||||
}
|
||||
|
||||
int total = new IjVimEditor(editor).lineCount();
|
||||
|
||||
msg.append(lines).append(" of ").append(total).append(" Lines");
|
||||
|
||||
msg.append("; ").append(word).append(" of ").append(words).append(" Words");
|
||||
|
||||
int chars = vr.getSelectionCount();
|
||||
int size = EditorHelperRt.getFileSize(editor);
|
||||
|
||||
msg.append("; ").append(chars).append(" of ").append(size).append(" Characters");
|
||||
}
|
||||
|
||||
VimPlugin.showMessage(msg.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayFileInfo(@NotNull VimEditor vimEditor, boolean fullPath) {
|
||||
Editor editor = ((IjVimEditor)vimEditor).getEditor();
|
||||
StringBuilder msg = new StringBuilder();
|
||||
VirtualFile vf = EditorHelper.getVirtualFile(editor);
|
||||
if (vf != null) {
|
||||
msg.append('"');
|
||||
if (fullPath) {
|
||||
msg.append(vf.getPath());
|
||||
}
|
||||
else {
|
||||
Project project = editor.getProject();
|
||||
if (project != null) {
|
||||
VirtualFile root = ProjectRootManager.getInstance(project).getFileIndex().getContentRootForFile(vf);
|
||||
if (root != null) {
|
||||
msg.append(vf.getPath().substring(root.getPath().length() + 1));
|
||||
}
|
||||
else {
|
||||
msg.append(vf.getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
msg.append("\" ");
|
||||
}
|
||||
else {
|
||||
msg.append("\"[No File]\" ");
|
||||
}
|
||||
|
||||
Document doc = editor.getDocument();
|
||||
if (!doc.isWritable()) {
|
||||
msg.append("[RO] ");
|
||||
}
|
||||
else if (FileDocumentManager.getInstance().isDocumentUnsaved(doc)) {
|
||||
msg.append("[+] ");
|
||||
}
|
||||
|
||||
int lline = editor.getCaretModel().getLogicalPosition().line;
|
||||
int total = new IjVimEditor(editor).lineCount();
|
||||
int pct = (int)((float)lline / (float)total * 100f + 0.5);
|
||||
|
||||
msg.append("line ").append(lline + 1).append(" of ").append(total);
|
||||
msg.append(" --").append(pct).append("%-- ");
|
||||
|
||||
LogicalPosition lp = editor.getCaretModel().getLogicalPosition();
|
||||
int col = editor.getCaretModel().getOffset() - doc.getLineStartOffset(lline);
|
||||
|
||||
msg.append("col ").append(col + 1);
|
||||
if (col != lp.column) {
|
||||
msg.append("-").append(lp.column + 1);
|
||||
}
|
||||
|
||||
VimPlugin.showMessage(msg.toString());
|
||||
}
|
||||
|
||||
private static final @NotNull Logger logger = Logger.getInstance(FileGroup.class.getName());
|
||||
|
||||
/**
|
||||
* Respond to editor tab selection and remember the last used tab
|
||||
*/
|
||||
public static void fileEditorManagerSelectionChangedCallback(@NotNull FileEditorManagerEvent event) {
|
||||
if (event.getOldFile() != null) {
|
||||
LastTabService.getInstance(event.getManager().getProject()).setLastTab(event.getOldFile());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public VimEditor selectEditor(@NotNull String projectId, @NotNull String documentPath, @Nullable String protocol) {
|
||||
VirtualFileSystem fileSystem = VirtualFileManager.getInstance().getFileSystem(protocol);
|
||||
if (fileSystem == null) return null;
|
||||
VirtualFile virtualFile = fileSystem.findFileByPath(documentPath);
|
||||
if (virtualFile == null) return null;
|
||||
|
||||
Project project = Arrays.stream(ProjectManager.getInstance().getOpenProjects())
|
||||
.filter(p -> injector.getFile().getProjectId(p).equals(projectId))
|
||||
.findFirst().orElseThrow();
|
||||
|
||||
Editor editor = selectEditor(project, virtualFile);
|
||||
if (editor == null) return null;
|
||||
return new IjVimEditor(editor);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getProjectId(@NotNull Object project) {
|
||||
if (!(project instanceof Project)) throw new IllegalArgumentException();
|
||||
return ((Project) project).getName();
|
||||
}
|
||||
}
|
444
src/main/java/com/maddyhome/idea/vim/group/FileGroup.kt
Normal file
444
src/main/java/com/maddyhome/idea/vim/group/FileGroup.kt
Normal file
@ -0,0 +1,444 @@
|
||||
/*
|
||||
* Copyright 2003-2023 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
package com.maddyhome.idea.vim.group
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
|
||||
import com.intellij.openapi.fileEditor.TextEditor
|
||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
|
||||
import com.intellij.openapi.fileEditor.impl.EditorsSplitters
|
||||
import com.intellij.openapi.fileTypes.FileTypeManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
import com.intellij.openapi.roots.ProjectRootManager
|
||||
import com.intellij.openapi.vfs.LocalFileSystem
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.VirtualFileManager
|
||||
import com.intellij.psi.search.FilenameIndex
|
||||
import com.intellij.psi.search.ProjectScope
|
||||
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.VimFileBase
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.group.LastTabService.Companion.getInstance
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
import com.maddyhome.idea.vim.helper.MessageHelper.message
|
||||
import com.maddyhome.idea.vim.helper.countWords
|
||||
import com.maddyhome.idea.vim.helper.fileSize
|
||||
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.execute
|
||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance
|
||||
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
class FileGroup : VimFileBase() {
|
||||
override fun openFile(filename: String, context: ExecutionContext): Boolean {
|
||||
if (logger.isDebugEnabled) {
|
||||
logger.debug("openFile($filename)")
|
||||
}
|
||||
val project = PlatformDataKeys.PROJECT.getData((context as IjEditorExecutionContext).context)
|
||||
?: return false // API change - don't merge
|
||||
|
||||
val found = findFile(filename, project)
|
||||
|
||||
if (found != null) {
|
||||
if (logger.isDebugEnabled) {
|
||||
logger.debug("found file: $found")
|
||||
}
|
||||
// Can't open a file unless it has a known file type. The next call will return the known type.
|
||||
// If unknown, IDEA will prompt the user to pick a type.
|
||||
val type = FileTypeManager.getInstance().getKnownFileTypeOrAssociate(found, project)
|
||||
|
||||
if (type != null) {
|
||||
val fem = FileEditorManager.getInstance(project)
|
||||
fem.openFile(found, true)
|
||||
|
||||
return true
|
||||
} else {
|
||||
// There was no type and user didn't pick one. Don't open the file
|
||||
// Return true here because we found the file but the user canceled by not picking a type.
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
VimPlugin.showMessage(message("unable.to.find.0", filename))
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fun findFile(filename: String, project: Project): VirtualFile? {
|
||||
var found: VirtualFile?
|
||||
// Vim supports both ~/ and ~\ (tested on Mac and Windows). On Windows, it supports forward- and back-slashes, but
|
||||
// it only supports forward slash on Unix (tested on Mac)
|
||||
// VFS works with both directory separators (tested on Mac and Windows)
|
||||
if (filename.startsWith("~/") || filename.startsWith("~\\")) {
|
||||
val relativePath = filename.substring(2)
|
||||
val dir = System.getProperty("user.home")
|
||||
if (logger.isDebugEnabled) {
|
||||
logger.debug("home dir file")
|
||||
logger.debug("looking for $relativePath in $dir")
|
||||
}
|
||||
found = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(File(dir, relativePath))
|
||||
} else {
|
||||
found = LocalFileSystem.getInstance().findFileByIoFile(File(filename))
|
||||
|
||||
if (found == null) {
|
||||
found = findByNameInContentRoots(filename, project)
|
||||
if (found == null) {
|
||||
found = findByNameInProject(filename, project)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
private fun findByNameInContentRoots(filename: String, project: Project): VirtualFile? {
|
||||
var found: VirtualFile? = null
|
||||
val prm = ProjectRootManager.getInstance(project)
|
||||
val roots = prm.contentRoots
|
||||
for (i in roots.indices) {
|
||||
if (logger.isDebugEnabled) {
|
||||
logger.debug("root[" + i + "] = " + roots[i].path)
|
||||
}
|
||||
found = roots[i].findFileByRelativePath(filename)
|
||||
if (found != null) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the current editor.
|
||||
*/
|
||||
override fun closeFile(editor: VimEditor, context: ExecutionContext) {
|
||||
val project = PlatformDataKeys.PROJECT.getData((context.context as DataContext))
|
||||
if (project != null) {
|
||||
val fileEditorManager = FileEditorManagerEx.getInstanceEx(project)
|
||||
val window = fileEditorManager.currentWindow
|
||||
val virtualFile = fileEditorManager.currentFile
|
||||
|
||||
if (virtualFile != null && window != null) {
|
||||
// During the work on VIM-2912 I've changed the close function to this one.
|
||||
// However, the function with manager seems to work weirdly and it causes VIM-2953
|
||||
//window.getManager().closeFile(virtualFile, true, false);
|
||||
window.closeFile(virtualFile)
|
||||
|
||||
// Get focus after closing tab
|
||||
window.requestFocus(true)
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||
// This thing doesn't have an implementation in test mode
|
||||
EditorsSplitters.focusDefaultComponentInSplittersIfPresent(project)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes editor.
|
||||
*/
|
||||
override fun closeFile(number: Int, context: ExecutionContext) {
|
||||
val project = PlatformDataKeys.PROJECT.getData((context as IjEditorExecutionContext).context) ?: return
|
||||
val fileEditorManager = FileEditorManagerEx.getInstanceEx(project)
|
||||
val window = fileEditorManager.currentWindow
|
||||
val editors = fileEditorManager.openFiles
|
||||
if (window != null) {
|
||||
if (number >= 0 && number < editors.size) {
|
||||
fileEditorManager.closeFile(editors[number], window)
|
||||
}
|
||||
}
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||
// This thing doesn't have an implementation in test mode
|
||||
EditorsSplitters.focusDefaultComponentInSplittersIfPresent(project)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves specific file in the project.
|
||||
*/
|
||||
override fun saveFile(context: ExecutionContext) {
|
||||
val action = if (injector.globalIjOptions().ideawrite.contains(IjOptionConstants.ideawrite_all)) {
|
||||
injector.nativeActionManager.saveAll
|
||||
} else {
|
||||
injector.nativeActionManager.saveCurrent
|
||||
}
|
||||
action.execute(context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves all files in the project.
|
||||
*/
|
||||
override fun saveFiles(context: ExecutionContext) {
|
||||
injector.nativeActionManager.saveAll.execute(context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects then next or previous editor.
|
||||
*/
|
||||
override fun selectFile(count: Int, context: ExecutionContext): Boolean {
|
||||
var count = count
|
||||
val project = PlatformDataKeys.PROJECT.getData((context as IjEditorExecutionContext).context) ?: return false
|
||||
val fem = FileEditorManager.getInstance(project) // API change - don't merge
|
||||
val editors = fem.openFiles
|
||||
if (count == 99) {
|
||||
count = editors.size - 1
|
||||
}
|
||||
if (count < 0 || count >= editors.size) {
|
||||
return false
|
||||
}
|
||||
|
||||
fem.openFile(editors[count], true)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects then next or previous editor.
|
||||
*/
|
||||
override fun selectNextFile(count: Int, context: ExecutionContext) {
|
||||
val project = PlatformDataKeys.PROJECT.getData((context as IjEditorExecutionContext).context) ?: return
|
||||
val fem = FileEditorManager.getInstance(project) // API change - don't merge
|
||||
val editors = fem.openFiles
|
||||
val current = fem.selectedFiles[0]
|
||||
for (i in editors.indices) {
|
||||
if (editors[i] == current) {
|
||||
val pos = (i + (count % editors.size) + editors.size) % editors.size
|
||||
|
||||
fem.openFile(editors[pos], true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects previous editor tab.
|
||||
*/
|
||||
override fun selectPreviousTab(context: ExecutionContext) {
|
||||
val project = PlatformDataKeys.PROJECT.getData((context.context as DataContext)) ?: return
|
||||
val vf = getInstance(project).lastTab
|
||||
if (vf != null && vf.isValid) {
|
||||
FileEditorManager.getInstance(project).openFile(vf, true)
|
||||
} else {
|
||||
VimPlugin.indicateError()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the previous tab.
|
||||
*/
|
||||
fun getPreviousTab(context: DataContext): VirtualFile? {
|
||||
val project = PlatformDataKeys.PROJECT.getData(context) ?: return null
|
||||
val vf = getInstance(project).lastTab
|
||||
if (vf != null && vf.isValid) {
|
||||
return vf
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun selectEditor(project: Project, file: VirtualFile): Editor? {
|
||||
val fMgr = FileEditorManager.getInstance(project)
|
||||
val feditors = fMgr.openFile(file, true)
|
||||
if (feditors.size > 0) {
|
||||
if (feditors[0] is TextEditor) {
|
||||
val editor = (feditors[0] as TextEditor).editor
|
||||
if (!editor.isDisposed) {
|
||||
return editor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override fun displayLocationInfo(vimEditor: VimEditor) {
|
||||
val editor = (vimEditor as IjVimEditor).editor
|
||||
val msg = StringBuilder()
|
||||
val doc = editor.document
|
||||
|
||||
if (getInstance(IjVimEditor(editor)).mode !is VISUAL) {
|
||||
val lp = editor.caretModel.logicalPosition
|
||||
val col = editor.caretModel.offset - doc.getLineStartOffset(lp.line)
|
||||
var endoff = doc.getLineEndOffset(lp.line)
|
||||
if (endoff < editor.fileSize && doc.charsSequence[endoff] == '\n') {
|
||||
endoff--
|
||||
}
|
||||
val ecol = endoff - doc.getLineStartOffset(lp.line)
|
||||
val elp = editor.offsetToLogicalPosition(endoff)
|
||||
|
||||
msg.append("Col ").append(col + 1)
|
||||
if (col != lp.column) {
|
||||
msg.append("-").append(lp.column + 1)
|
||||
}
|
||||
|
||||
msg.append(" of ").append(ecol + 1)
|
||||
if (ecol != elp.column) {
|
||||
msg.append("-").append(elp.column + 1)
|
||||
}
|
||||
|
||||
val lline = editor.caretModel.logicalPosition.line
|
||||
val total = IjVimEditor(editor).lineCount()
|
||||
|
||||
msg.append("; Line ").append(lline + 1).append(" of ").append(total)
|
||||
|
||||
val cp = countWords(vimEditor)
|
||||
|
||||
msg.append("; Word ").append(cp.position).append(" of ").append(cp.count)
|
||||
|
||||
val offset = editor.caretModel.offset
|
||||
val size = editor.fileSize
|
||||
|
||||
msg.append("; Character ").append(offset + 1).append(" of ").append(size)
|
||||
} else {
|
||||
msg.append("Selected ")
|
||||
|
||||
val vr = TextRange(
|
||||
editor.selectionModel.blockSelectionStarts,
|
||||
editor.selectionModel.blockSelectionEnds
|
||||
)
|
||||
vr.normalize()
|
||||
|
||||
val lines: Int
|
||||
var cp = countWords(vimEditor)
|
||||
val words = cp.count
|
||||
var word = 0
|
||||
if (vr.isMultiple) {
|
||||
lines = vr.size()
|
||||
val cols = vr.maxLength
|
||||
|
||||
msg.append(cols).append(" Cols; ")
|
||||
|
||||
for (i in 0 until vr.size()) {
|
||||
cp = countWords(vimEditor, vr.startOffsets[i], (vr.endOffsets[i] - 1).toLong())
|
||||
word += cp.count
|
||||
}
|
||||
} else {
|
||||
val slp = editor.offsetToLogicalPosition(vr.startOffset)
|
||||
val elp = editor.offsetToLogicalPosition(vr.endOffset)
|
||||
|
||||
lines = elp.line - slp.line + 1
|
||||
|
||||
cp = countWords(vimEditor, vr.startOffset, (vr.endOffset - 1).toLong())
|
||||
word = cp.count
|
||||
}
|
||||
|
||||
val total = IjVimEditor(editor).lineCount()
|
||||
|
||||
msg.append(lines).append(" of ").append(total).append(" Lines")
|
||||
|
||||
msg.append("; ").append(word).append(" of ").append(words).append(" Words")
|
||||
|
||||
val chars = vr.selectionCount
|
||||
val size = editor.fileSize
|
||||
|
||||
msg.append("; ").append(chars).append(" of ").append(size).append(" Characters")
|
||||
}
|
||||
|
||||
VimPlugin.showMessage(msg.toString())
|
||||
}
|
||||
|
||||
override fun displayFileInfo(vimEditor: VimEditor, fullPath: Boolean) {
|
||||
val editor = (vimEditor as IjVimEditor).editor
|
||||
val msg = StringBuilder()
|
||||
val vf = EditorHelper.getVirtualFile(editor)
|
||||
if (vf != null) {
|
||||
msg.append('"')
|
||||
if (fullPath) {
|
||||
msg.append(vf.path)
|
||||
} else {
|
||||
val project = editor.project
|
||||
if (project != null) {
|
||||
val root = ProjectRootManager.getInstance(project).fileIndex.getContentRootForFile(vf)
|
||||
if (root != null) {
|
||||
msg.append(vf.path.substring(root.path.length + 1))
|
||||
} else {
|
||||
msg.append(vf.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
msg.append("\" ")
|
||||
} else {
|
||||
msg.append("\"[No File]\" ")
|
||||
}
|
||||
|
||||
val doc = editor.document
|
||||
if (!doc.isWritable) {
|
||||
msg.append("[RO] ")
|
||||
} else if (FileDocumentManager.getInstance().isDocumentUnsaved(doc)) {
|
||||
msg.append("[+] ")
|
||||
}
|
||||
|
||||
val lline = editor.caretModel.logicalPosition.line
|
||||
val total = IjVimEditor(editor).lineCount()
|
||||
val pct = (lline.toFloat() / total.toFloat() * 100f + 0.5).toInt()
|
||||
|
||||
msg.append("line ").append(lline + 1).append(" of ").append(total)
|
||||
msg.append(" --").append(pct).append("%-- ")
|
||||
|
||||
val lp = editor.caretModel.logicalPosition
|
||||
val col = editor.caretModel.offset - doc.getLineStartOffset(lline)
|
||||
|
||||
msg.append("col ").append(col + 1)
|
||||
if (col != lp.column) {
|
||||
msg.append("-").append(lp.column + 1)
|
||||
}
|
||||
|
||||
VimPlugin.showMessage(msg.toString())
|
||||
}
|
||||
|
||||
override fun selectEditor(projectId: String, documentPath: String, protocol: String?): VimEditor? {
|
||||
val fileSystem = VirtualFileManager.getInstance().getFileSystem(protocol) ?: return null
|
||||
val virtualFile = fileSystem.findFileByPath(documentPath) ?: return null
|
||||
|
||||
val project = Arrays.stream(ProjectManager.getInstance().openProjects)
|
||||
.filter { p: Project? -> injector.file.getProjectId(p!!) == projectId }
|
||||
.findFirst().orElseThrow()
|
||||
|
||||
val editor = selectEditor(project, virtualFile) ?: return null
|
||||
return IjVimEditor(editor)
|
||||
}
|
||||
|
||||
override fun getProjectId(project: Any): String {
|
||||
require(project is Project)
|
||||
return project.name + "-" + project.locationHash
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun findByNameInProject(filename: String, project: Project): VirtualFile? {
|
||||
val projectScope = ProjectScope.getProjectScope(project)
|
||||
val names = FilenameIndex.getVirtualFilesByName(filename, projectScope)
|
||||
if (!names.isEmpty()) {
|
||||
return names.stream().findFirst().get()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private val logger = Logger.getInstance(
|
||||
FileGroup::class.java.name
|
||||
)
|
||||
|
||||
/**
|
||||
* Respond to editor tab selection and remember the last used tab
|
||||
*/
|
||||
fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
|
||||
if (event.oldFile != null) {
|
||||
getInstance(event.manager.project).lastTab = event.oldFile
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -19,24 +19,22 @@ import com.maddyhome.idea.vim.options.OptionAccessScope
|
||||
* options
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
public open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesBase(scope) {
|
||||
public var ide: String by optionProperty(IjOptions.ide)
|
||||
public var ideamarks: Boolean by optionProperty(IjOptions.ideamarks)
|
||||
public var ideastatusicon: String by optionProperty(IjOptions.ideastatusicon)
|
||||
public val ideavimsupport: StringListOptionValue by optionProperty(IjOptions.ideavimsupport)
|
||||
public var ideawrite: String by optionProperty(IjOptions.ideawrite)
|
||||
public val lookupkeys: StringListOptionValue by optionProperty(IjOptions.lookupkeys)
|
||||
public var trackactionids: Boolean by optionProperty(IjOptions.trackactionids)
|
||||
public var visualdelay: Int by optionProperty(IjOptions.visualdelay)
|
||||
open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesBase(scope) {
|
||||
var ide: String by optionProperty(IjOptions.ide)
|
||||
var ideamarks: Boolean by optionProperty(IjOptions.ideamarks)
|
||||
var ideastatusicon: String by optionProperty(IjOptions.ideastatusicon)
|
||||
val ideavimsupport: StringListOptionValue by optionProperty(IjOptions.ideavimsupport)
|
||||
var ideawrite: String by optionProperty(IjOptions.ideawrite)
|
||||
val lookupkeys: StringListOptionValue by optionProperty(IjOptions.lookupkeys)
|
||||
var trackactionids: Boolean by optionProperty(IjOptions.trackactionids)
|
||||
var visualdelay: Int by optionProperty(IjOptions.visualdelay)
|
||||
|
||||
// Temporary options to control work-in-progress behaviour
|
||||
public var closenotebooks: Boolean by optionProperty(IjOptions.closenotebooks)
|
||||
public var commandOrMotionAnnotation: Boolean by optionProperty(IjOptions.commandOrMotionAnnotation)
|
||||
public var exCommandAnnotation: Boolean by optionProperty(IjOptions.exCommandAnnotation)
|
||||
public var oldundo: Boolean by optionProperty(IjOptions.oldundo)
|
||||
public var unifyjumps: Boolean by optionProperty(IjOptions.unifyjumps)
|
||||
public var useNewRegex: Boolean by optionProperty(IjOptions.useNewRegex)
|
||||
public var vimscriptFunctionAnnotation: Boolean by optionProperty(IjOptions.vimscriptFunctionAnnotation)
|
||||
var closenotebooks: Boolean by optionProperty(IjOptions.closenotebooks)
|
||||
var commandOrMotionAnnotation: Boolean by optionProperty(IjOptions.commandOrMotionAnnotation)
|
||||
var oldundo: Boolean by optionProperty(IjOptions.oldundo)
|
||||
var unifyjumps: Boolean by optionProperty(IjOptions.unifyjumps)
|
||||
var vimscriptFunctionAnnotation: Boolean by optionProperty(IjOptions.vimscriptFunctionAnnotation)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -44,20 +42,19 @@ 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) {
|
||||
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)
|
||||
var breakindent: Boolean by optionProperty(IjOptions.breakindent)
|
||||
val colorcolumn: StringListOptionValue by optionProperty(IjOptions.colorcolumn)
|
||||
var cursorline: Boolean by optionProperty(IjOptions.cursorline)
|
||||
var fileformat: String by optionProperty(IjOptions.fileformat)
|
||||
var list: Boolean by optionProperty(IjOptions.list)
|
||||
var relativenumber: Boolean by optionProperty(IjOptions.relativenumber)
|
||||
var textwidth: Int by optionProperty(IjOptions.textwidth)
|
||||
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)
|
||||
var ideacopypreprocess: Boolean by optionProperty(IjOptions.ideacopypreprocess)
|
||||
var ideajoin: Boolean by optionProperty(IjOptions.ideajoin)
|
||||
var idearefactormode: String by optionProperty(IjOptions.idearefactormode)
|
||||
}
|
||||
|
@ -26,9 +26,9 @@ import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
|
||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
public object IjOptions {
|
||||
object IjOptions {
|
||||
|
||||
public fun initialise() {
|
||||
fun initialise() {
|
||||
// Calling this method allows for deterministic initialisation of IjOptions, specifically initialising the
|
||||
// properties and registering the IJ specific options. Once added, they can be safely accessed by name, e.g. by the
|
||||
// implementation of `:set` while executing ~/.ideavimrc
|
||||
@ -39,8 +39,8 @@ public object IjOptions {
|
||||
}
|
||||
|
||||
// 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", "") {
|
||||
val breakindent: ToggleOption = addOption(ToggleOption("breakindent", LOCAL_TO_WINDOW, "bri", false))
|
||||
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) {
|
||||
@ -55,19 +55,18 @@ public object IjOptions {
|
||||
}
|
||||
}
|
||||
})
|
||||
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))
|
||||
val cursorline: ToggleOption = addOption(ToggleOption("cursorline", LOCAL_TO_WINDOW, "cul", false))
|
||||
val list: ToggleOption = addOption(ToggleOption("list", LOCAL_TO_WINDOW, "list", false))
|
||||
val relativenumber: ToggleOption = addOption(ToggleOption("relativenumber", LOCAL_TO_WINDOW, "rnu", false))
|
||||
val textwidth: NumberOption = addOption(UnsignedNumberOption("textwidth", LOCAL_TO_BUFFER, "tw", 0))
|
||||
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
|
||||
// based on the value of 'fileformats' or 'fileencodings'. To prevent unexpected file conversion, we treat them as
|
||||
// local-noglobal. See `:help local-noglobal`, `:help 'fileformats'` and `:help 'fileencodings'`
|
||||
public val bomb: ToggleOption =
|
||||
val bomb: ToggleOption =
|
||||
addOption(ToggleOption("bomb", LOCAL_TO_BUFFER, "bomb", false, isLocalNoGlobal = true))
|
||||
public val fileencoding: StringOption = addOption(
|
||||
val fileencoding: StringOption = addOption(
|
||||
StringOption(
|
||||
"fileencoding",
|
||||
LOCAL_TO_BUFFER,
|
||||
@ -76,7 +75,7 @@ public object IjOptions {
|
||||
isLocalNoGlobal = true
|
||||
)
|
||||
)
|
||||
public val fileformat: StringOption = addOption(
|
||||
val fileformat: StringOption = addOption(
|
||||
StringOption(
|
||||
"fileformat",
|
||||
LOCAL_TO_BUFFER,
|
||||
@ -88,15 +87,15 @@ public object IjOptions {
|
||||
)
|
||||
|
||||
// IntelliJ specific functionality - custom options
|
||||
public val ide: StringOption = addOption(
|
||||
val ide: StringOption = addOption(
|
||||
StringOption("ide", GLOBAL, "ide", ApplicationNamesInfo.getInstance().fullProductNameWithEdition)
|
||||
)
|
||||
public val ideacopypreprocess: ToggleOption = addOption(
|
||||
val ideacopypreprocess: ToggleOption = addOption(
|
||||
ToggleOption("ideacopypreprocess", GLOBAL_OR_LOCAL_TO_BUFFER, "ideacopypreprocess", false)
|
||||
)
|
||||
public val ideajoin: ToggleOption = addOption(ToggleOption("ideajoin", GLOBAL_OR_LOCAL_TO_BUFFER, "ideajoin", false))
|
||||
public val ideamarks: ToggleOption = addOption(ToggleOption("ideamarks", GLOBAL, "ideamarks", true))
|
||||
public val idearefactormode: StringOption = addOption(
|
||||
val ideajoin: ToggleOption = addOption(ToggleOption("ideajoin", GLOBAL_OR_LOCAL_TO_BUFFER, "ideajoin", false))
|
||||
val ideamarks: ToggleOption = addOption(ToggleOption("ideamarks", GLOBAL, "ideamarks", true))
|
||||
val idearefactormode: StringOption = addOption(
|
||||
StringOption(
|
||||
"idearefactormode",
|
||||
GLOBAL_OR_LOCAL_TO_BUFFER,
|
||||
@ -105,7 +104,7 @@ public object IjOptions {
|
||||
IjOptionConstants.ideaRefactorModeValues
|
||||
)
|
||||
)
|
||||
public val ideastatusicon: StringOption = addOption(
|
||||
val ideastatusicon: StringOption = addOption(
|
||||
StringOption(
|
||||
"ideastatusicon",
|
||||
GLOBAL,
|
||||
@ -114,7 +113,7 @@ public object IjOptions {
|
||||
IjOptionConstants.ideaStatusIconValues
|
||||
)
|
||||
)
|
||||
public val ideavimsupport: StringListOption = addOption(
|
||||
val ideavimsupport: StringListOption = addOption(
|
||||
StringListOption(
|
||||
"ideavimsupport",
|
||||
GLOBAL,
|
||||
@ -123,27 +122,26 @@ public object IjOptions {
|
||||
IjOptionConstants.ideavimsupportValues
|
||||
)
|
||||
)
|
||||
@JvmField public val ideawrite: StringOption = addOption(
|
||||
@JvmField
|
||||
val ideawrite: StringOption = addOption(
|
||||
StringOption("ideawrite", GLOBAL, "ideawrite", "all", IjOptionConstants.ideaWriteValues)
|
||||
)
|
||||
public val lookupkeys: StringListOption = addOption(
|
||||
val lookupkeys: StringListOption = addOption(
|
||||
StringListOption(
|
||||
"lookupkeys",
|
||||
GLOBAL,
|
||||
"lookupkeys",
|
||||
"<Tab>,<Down>,<Up>,<Enter>,<Left>,<Right>,<C-Down>,<C-Up>,<PageUp>,<PageDown>,<C-J>,<C-Q>")
|
||||
)
|
||||
public val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false))
|
||||
public val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100))
|
||||
val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false))
|
||||
val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100))
|
||||
|
||||
// Temporary feature flags during development, not really intended for external use
|
||||
public val closenotebooks: ToggleOption = addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true))
|
||||
public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true))
|
||||
public val exCommandAnnotation: ToggleOption = addOption(ToggleOption("excommandannotation", GLOBAL, "excommandannotation", true, isHidden = true))
|
||||
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", false, isHidden = true))
|
||||
public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true))
|
||||
public val useNewRegex: ToggleOption = addOption(ToggleOption("usenewregex", GLOBAL, "usenewregex", true, isHidden = true))
|
||||
public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isHidden = true))
|
||||
val closenotebooks: ToggleOption = addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true))
|
||||
val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true))
|
||||
val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isHidden = true))
|
||||
val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true))
|
||||
val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isHidden = true))
|
||||
|
||||
// This needs to be Option<out VimDataType> so that it can work with derived option types, such as NumberOption, which
|
||||
// derives from Option<VimInt>
|
||||
|
@ -21,7 +21,7 @@ import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
|
||||
@Service
|
||||
public class IjVimPsiService: VimPsiService {
|
||||
class IjVimPsiService: VimPsiService {
|
||||
override fun getCommentAtPos(editor: VimEditor, pos: Int): Pair<TextRange, Pair<String, String>?>? {
|
||||
val psiFile = PsiHelper.getFile(editor.ij) ?: return null
|
||||
val psiElement = psiFile.findElementAt(pos) ?: return null
|
||||
|
@ -15,7 +15,7 @@ import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.VimRedrawService
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
|
||||
public class IjVimRedrawService : VimRedrawService {
|
||||
class IjVimRedrawService : VimRedrawService {
|
||||
override fun redraw() {
|
||||
// The only thing IntelliJ needs to redraw is the status line. Everything else is handled automatically.
|
||||
redrawStatusLine()
|
||||
@ -25,11 +25,11 @@ public class IjVimRedrawService : VimRedrawService {
|
||||
injector.messages.clearStatusBarMessage()
|
||||
}
|
||||
|
||||
public companion object {
|
||||
companion object {
|
||||
/**
|
||||
* Simulate Vim's redraw when the current editor changes
|
||||
*/
|
||||
public fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
|
||||
fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
|
||||
injector.redrawService.redraw()
|
||||
}
|
||||
}
|
||||
@ -39,7 +39,7 @@ public class IjVimRedrawService : VimRedrawService {
|
||||
*
|
||||
* Only redraw if lines are added/removed.
|
||||
*/
|
||||
public object RedrawListener : DocumentListener {
|
||||
object RedrawListener : DocumentListener {
|
||||
override fun documentChanged(event: DocumentEvent) {
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
if (event.newFragment.contains("\n") || event.oldFragment.contains("\n")) {
|
||||
|
@ -0,0 +1,68 @@
|
||||
package com.maddyhome.idea.vim.group
|
||||
|
||||
import com.intellij.codeInsight.daemon.ReferenceImporter
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.ReadAction
|
||||
import com.intellij.openapi.command.WriteCommandAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.progress.Task
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiRecursiveElementWalkingVisitor
|
||||
import java.util.function.BooleanSupplier
|
||||
|
||||
internal object MacroAutoImport {
|
||||
fun run(editor: Editor, dataContext: DataContext) {
|
||||
val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return
|
||||
val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: return
|
||||
|
||||
if (!FileDocumentManager.getInstance().requestWriting(editor.document, project)) {
|
||||
return
|
||||
}
|
||||
|
||||
val importers = ReferenceImporter.EP_NAME.extensionList
|
||||
if (importers.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Auto import", true) {
|
||||
override fun run(indicator: ProgressIndicator) {
|
||||
val fixes = ReadAction.nonBlocking<List<BooleanSupplier>> {
|
||||
val fixes = mutableListOf<BooleanSupplier>()
|
||||
|
||||
file.accept(object : PsiRecursiveElementWalkingVisitor() {
|
||||
override fun visitElement(element: PsiElement) {
|
||||
for (reference in element.references) {
|
||||
if (reference.resolve() != null) {
|
||||
continue
|
||||
}
|
||||
for (importer in importers) {
|
||||
importer.computeAutoImportAtOffset(editor, file, element.textRange.startOffset, true)
|
||||
?.let(fixes::add)
|
||||
}
|
||||
}
|
||||
super.visitElement(element)
|
||||
}
|
||||
})
|
||||
|
||||
return@nonBlocking fixes
|
||||
}.executeSynchronously()
|
||||
|
||||
ApplicationManager.getApplication().invokeAndWait {
|
||||
WriteCommandAction.writeCommandAction(project)
|
||||
.withName("Auto Import")
|
||||
.withGroupId("IdeaVimAutoImportAfterMacro")
|
||||
.shouldRecordActionForActiveDocument(true)
|
||||
.run<RuntimeException> {
|
||||
fixes.forEach { it.asBoolean }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.helper.MessageHelper.message
|
||||
import com.maddyhome.idea.vim.macro.VimMacroBase
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
|
||||
/**
|
||||
* Used to handle playback of macros
|
||||
@ -93,6 +94,9 @@ internal class MacroGroup : VimMacroBase() {
|
||||
} finally {
|
||||
keyStack.removeFirst()
|
||||
}
|
||||
if (!isInternalMacro) {
|
||||
MacroAutoImport.run(editor.ij, context.ij)
|
||||
}
|
||||
}
|
||||
|
||||
if (isInternalMacro) {
|
||||
|
@ -314,7 +314,7 @@ internal class MotionGroup : VimMotionGroupBase() {
|
||||
}
|
||||
is Mode.CMD_LINE -> {
|
||||
injector.processGroup.cancelExEntry(vimEditor, false)
|
||||
ExOutputModel.getInstance(editor).clear()
|
||||
ExOutputModel.tryGetInstance(editor)?.close()
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
@ -12,10 +12,12 @@ import com.intellij.application.options.CodeStyle
|
||||
import com.intellij.codeStyle.AbstractConvertLineSeparatorsAction
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.editor.EditorKind
|
||||
import com.intellij.openapi.editor.EditorSettings.LineNumerationType
|
||||
import com.intellij.openapi.editor.ScrollPositionCalculator
|
||||
import com.intellij.openapi.editor.ex.EditorEx
|
||||
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable
|
||||
import com.intellij.openapi.editor.impl.softwrap.SoftWrapAppliancePlaces
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
|
||||
import com.intellij.openapi.fileEditor.TextEditor
|
||||
@ -94,12 +96,12 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup, InternalOpt
|
||||
addOptionValueOverride(IjOptions.fileencoding, FileEncodingOptionMapper())
|
||||
addOptionValueOverride(IjOptions.fileformat, FileFormatOptionMapper())
|
||||
addOptionValueOverride(IjOptions.list, ListOptionMapper(IjOptions.list, this))
|
||||
addOptionValueOverride(IjOptions.number, NumberOptionMapper(IjOptions.number, this))
|
||||
addOptionValueOverride(IjOptions.relativenumber, RelativeNumberOptionMapper(IjOptions.relativenumber, this))
|
||||
addOptionValueOverride(IjOptions.textwidth, TextWidthOptionMapper(IjOptions.textwidth))
|
||||
addOptionValueOverride(IjOptions.wrap, WrapOptionMapper(IjOptions.wrap, this))
|
||||
|
||||
// These options are defined and implemented in vim-engine, but IntelliJ has similar features with settings we can map
|
||||
addOptionValueOverride(Options.number, NumberOptionMapper(Options.number, this))
|
||||
addOptionValueOverride(Options.scrolljump, ScrollJumpOptionMapper(Options.scrolljump, this))
|
||||
addOptionValueOverride(Options.sidescroll, SideScrollOptionMapper(Options.sidescroll, this))
|
||||
addOptionValueOverride(Options.scrolloff, ScrollOffOptionMapper(Options.scrolloff, this))
|
||||
@ -926,7 +928,9 @@ private class ScrollJumpOptionMapper(option: NumberOption, internalOptionValueAc
|
||||
override fun getEffectiveExternalValue(editor: VimEditor) = editor.ij.settings.verticalScrollJump.asVimInt()
|
||||
|
||||
override fun setLocalExternalValue(editor: VimEditor, value: VimInt) {
|
||||
editor.ij.settings.verticalScrollJump = value.value
|
||||
// Note that Vim supports -1 to -100 as a percentage value. IntelliJ does not have any validation, but does not
|
||||
// handle or expect negative values
|
||||
editor.ij.settings.verticalScrollJump = value.value.coerceAtLeast(0)
|
||||
}
|
||||
|
||||
override fun resetLocalExternalValue(editor: VimEditor, defaultValue: VimInt) {
|
||||
@ -975,26 +979,35 @@ private class SideScrollOptionMapper(option: NumberOption, internalOptionValueAc
|
||||
* setting value, and there is no UI to modify the local IntelliJ settings. Once the value has been set in IdeaVim, it
|
||||
* takes precedence over the global, persistent setting until the option is reset with either `:set scrolloff&` or
|
||||
* `:setlocal scrolloff<`.
|
||||
*
|
||||
* Note that when the IdeaVim value is set, we set the IntelliJ local value to 0 rather than sharing the value. This is
|
||||
* to prevent conflicts between IntelliJ and IdeaVim's separate implementations for scrolling. IntelliJ's scrolling
|
||||
* includes virtual space at the bottom of the file, while (Idea)Vim doesn't. Combining this with a non-zero
|
||||
* `'scrolloff'` value can reposition the bottom of the file. E.g., using `G` will position the last line at the bottom
|
||||
* of the file, but then IntelliJ moves it up `'scrolloff'` when the caret is moved.
|
||||
*
|
||||
* With a large value like `999`, IntelliJ will try to move the current line to the centre of the screen, but then
|
||||
* IdeaVim will try to reposition. Normally, this doesn't cause too much of a problem, because setting the scroll
|
||||
* position will cancel any outstanding animations. However, using backspace updates the scroll position with animations
|
||||
* disabled, so the scroll happens immediately, with a visible "twitch" as the editor scrolls for IntelliJ and then back
|
||||
* for IdeaVim.
|
||||
*
|
||||
* We should consider implementing [ScrollPositionCalculator] which would allow IdeaVim to completely take over
|
||||
* scrolling from IntelliJ. This would be a non-trivial change, and it might be better to move the scrolling to
|
||||
* vim-engine so it can also work in Fleet.
|
||||
*/
|
||||
private class ScrollOffOptionMapper(option: NumberOption, internalOptionValueAccessor: InternalOptionValueAccessor)
|
||||
: GlobalLocalOptionToGlobalLocalIdeaSettingMapper<VimInt>(option, internalOptionValueAccessor) {
|
||||
private class ScrollOffOptionMapper(
|
||||
scrollOffOption: NumberOption,
|
||||
internalOptionValueAccessor: InternalOptionValueAccessor,
|
||||
) : OneWayGlobalLocalOptionToGlobalLocalIdeaSettingMapper<VimInt>(scrollOffOption, internalOptionValueAccessor) {
|
||||
|
||||
override val ideaPropertyName: String = EditorSettingsExternalizable.PropNames.PROP_VERTICAL_SCROLL_OFFSET
|
||||
|
||||
// The IntelliJ setting is in practice global. The base implementation relies on this fact
|
||||
override val canUserModifyExternalLocalValue: Boolean = false
|
||||
override fun getExternalGlobalValue() =
|
||||
EditorSettingsExternalizable.getInstance().verticalScrollOffset.asVimInt()
|
||||
|
||||
override fun getGlobalExternalValue() = EditorSettingsExternalizable.getInstance().verticalScrollOffset.asVimInt()
|
||||
override fun getEffectiveExternalValue(editor: VimEditor) = editor.ij.settings.verticalScrollOffset.asVimInt()
|
||||
|
||||
override fun setLocalExternalValue(editor: VimEditor, value: VimInt) {
|
||||
editor.ij.settings.verticalScrollOffset = value.value
|
||||
}
|
||||
|
||||
override fun removeLocalExternalValue(editor: VimEditor) {
|
||||
// Unexpectedly, verticalScrollOffset accepts `-1` as a value to clear any local overrides, and this will reset the
|
||||
// effective value to return the global value
|
||||
editor.ij.settings.verticalScrollOffset = -1
|
||||
override fun suppressExternalLocalValue(editor: VimEditor) {
|
||||
editor.ij.settings.verticalScrollOffset = 0
|
||||
}
|
||||
}
|
||||
|
||||
@ -1002,15 +1015,14 @@ private class ScrollOffOptionMapper(option: NumberOption, internalOptionValueAcc
|
||||
/**
|
||||
* Map the `'sidescrolloff'` global-local Vim option to the IntelliJ global-local horizontal scroll offset setting
|
||||
*
|
||||
* Ideally, we would implement this in a similar manner to [SideScrollOptionMapper], setting the external local
|
||||
* horizontal scroll offset value when the user explicitly sets the Vim value, so that IntelliJ could also use the
|
||||
* value. Unfortunately, IntelliJ's scrolling calculation logic is based on integer font width maths, which causes
|
||||
* problems with fractional font widths (such as on a Mac when running tests).
|
||||
* IntelliJ supports horizontal scroll offset in a similar manner to Vim. However, the implementation calculates offsets
|
||||
* using integer font sizes, which can lead to minor inaccuracies when compared to the IdeaVim implementation, such as
|
||||
* differences running tests on a Mac.
|
||||
*
|
||||
* For example, given a `'sidescrolloff'` value of `10`, and a fractional font width of `7.8`, IntelliJ will scroll `80`
|
||||
* pixels instead of `78`. This is a very minor difference, but because it overshoots, it means that IdeaVim doesn't
|
||||
* need to scroll, which in turn can cause issues with `'sidescroll'`, because IntelliJ doesn't support `sidescroll=0`,
|
||||
* which would scroll to position the caret in the middle of the display.
|
||||
* need to scroll, which in turn can cause issues with `'sidescroll'` (jump), because IntelliJ doesn't support
|
||||
* `sidescroll=0`, which would scroll to position the caret in the middle of the display.
|
||||
*
|
||||
* It also causes precision problems in the tests. The display is scrolled to a couple of pixels _before_ the leftmost
|
||||
* column, which means the rightmost column ends a couple of pixels _after_ the rightmost edge of the display. The tests
|
||||
@ -1028,78 +1040,98 @@ private class ScrollOffOptionMapper(option: NumberOption, internalOptionValueAcc
|
||||
* vim-engine so it can also work in Fleet.
|
||||
*/
|
||||
private class SideScrollOffOptionMapper(
|
||||
private val sideScrollOffOption: NumberOption,
|
||||
private val internalOptionValueAccessor: InternalOptionValueAccessor,
|
||||
) : GlobalOptionValueOverride<VimInt>, LocalOptionValueOverride<VimInt>, IdeaBackedOptionValueOverride {
|
||||
sideScrollOffOption: NumberOption,
|
||||
internalOptionValueAccessor: InternalOptionValueAccessor,
|
||||
) : OneWayGlobalLocalOptionToGlobalLocalIdeaSettingMapper<VimInt>(sideScrollOffOption, internalOptionValueAccessor) {
|
||||
|
||||
override val ideaPropertyName: String = EditorSettingsExternalizable.PropNames.PROP_HORIZONTAL_SCROLL_OFFSET
|
||||
|
||||
override fun getGlobalValue(storedValue: OptionValue<VimInt>, editor: VimEditor?): OptionValue<VimInt> {
|
||||
override fun getExternalGlobalValue() =
|
||||
EditorSettingsExternalizable.getInstance().horizontalScrollOffset.asVimInt()
|
||||
|
||||
override fun suppressExternalLocalValue(editor: VimEditor) {
|
||||
editor.ij.settings.horizontalScrollOffset = 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An abstract base class to map a global-local IDEA setting to a global-local Vim option. The IDEA setting is not
|
||||
* updated to reflect the Vim changes, but is kept at a neutral value.
|
||||
*
|
||||
* This class is used for Vim options that have an IDEA equivalent, but the implementation is handled by IdeaVim, e.g.,
|
||||
* scroll jumps and offsets. The IDEA value is not updated, and kept to a neutral value, so that the IDEA implementation
|
||||
* does not interfere with the IdeaVim implementation.
|
||||
*/
|
||||
private abstract class OneWayGlobalLocalOptionToGlobalLocalIdeaSettingMapper<T : VimDataType>(
|
||||
private val option: Option<T>,
|
||||
private val internalOptionValueAccessor: InternalOptionValueAccessor,
|
||||
) : GlobalOptionValueOverride<T>, LocalOptionValueOverride<T>, IdeaBackedOptionValueOverride {
|
||||
|
||||
override fun getGlobalValue(storedValue: OptionValue<T>, editor: VimEditor?): OptionValue<T> {
|
||||
if (storedValue is OptionValue.Default) {
|
||||
return OptionValue.Default(EditorSettingsExternalizable.getInstance().horizontalScrollOffset.asVimInt())
|
||||
return OptionValue.Default(getExternalGlobalValue())
|
||||
}
|
||||
|
||||
// If it's not the default value, it's got to be the stored value
|
||||
return storedValue
|
||||
}
|
||||
|
||||
override fun setGlobalValue(
|
||||
storedValue: OptionValue<VimInt>,
|
||||
newValue: OptionValue<VimInt>,
|
||||
editor: VimEditor?,
|
||||
): Boolean {
|
||||
// The user has typed `:setlocal`. Just make sure that the IntelliJ value doesn't interfere with the Vim value
|
||||
injector.editorGroup.getEditors().forEach { it.ij.settings.horizontalScrollOffset = 0 }
|
||||
override fun setGlobalValue(storedValue: OptionValue<T>, newValue: OptionValue<T>, editor: VimEditor?): Boolean {
|
||||
// The user is updating the global Vim value, via `:setglobal`. IdeaVim scrolling will be using this value. Make
|
||||
// sure the IntelliJ values won't interfere
|
||||
// Note that we don't reset the local IntelliJ value for `:set {option}&` or `:set {option}<` because the current
|
||||
// global IntelliJ value might still interfere with IdeaVim's implementation. We continue to suppress the IntelliJ
|
||||
// value.
|
||||
injector.editorGroup.getEditors().forEach { suppressExternalLocalValue(it) }
|
||||
return storedValue.value != newValue.value
|
||||
}
|
||||
|
||||
override fun getLocalValue(storedValue: OptionValue<VimInt>?, editor: VimEditor): OptionValue<VimInt> {
|
||||
override fun getLocalValue(storedValue: OptionValue<T>?, editor: VimEditor): OptionValue<T> {
|
||||
if (storedValue == null) {
|
||||
// Initialisation. Report the global value of the setting. We ignore the local value because the user doesn't have
|
||||
// a way to set it, and we set it to 0 so that it doesn't affect our scroll calculations (because IntelliJ doesn't
|
||||
// handle sidescroll=0 to mean half a page)
|
||||
return OptionValue.Default(EditorSettingsExternalizable.getInstance().horizontalScrollOffset.asVimInt())
|
||||
// a way to set it. If it has been changed (unlikely if stored value hasn't been set yet), then it would be 0
|
||||
return OptionValue.Default(getExternalGlobalValue())
|
||||
}
|
||||
|
||||
if (storedValue is OptionValue.Default && storedValue.value != sideScrollOffOption.unsetValue) {
|
||||
// The local value is set to the default value (as a copy of the global value), so return the global external
|
||||
// value as a default
|
||||
return OptionValue.Default(EditorSettingsExternalizable.getInstance().horizontalScrollOffset.asVimInt())
|
||||
if (storedValue is OptionValue.Default && storedValue.value != option.unsetValue) {
|
||||
// The local value has been reset to Default. It's not the Vim default of "unset", but a copy of the global value.
|
||||
// Return the current value of the global external value
|
||||
return OptionValue.Default(getExternalGlobalValue())
|
||||
}
|
||||
|
||||
// Whatever is left is either explicitly set by the user, or option.unsetValue
|
||||
return storedValue
|
||||
}
|
||||
|
||||
override fun setLocalValue(
|
||||
storedValue: OptionValue<VimInt>?,
|
||||
newValue: OptionValue<VimInt>,
|
||||
editor: VimEditor,
|
||||
): Boolean {
|
||||
// This is setting the Vim local value. We do nothing but reset the local horizontal scroll jump so IntelliJ's
|
||||
// scrolling doesn't affect our scrolling
|
||||
editor.ij.settings.horizontalScrollOffset = 0
|
||||
override fun setLocalValue(storedValue: OptionValue<T>?, newValue: OptionValue<T>, editor: VimEditor): Boolean {
|
||||
// Vim local value is being set. We do nothing but set the local IntelliJ value to 0, so IntelliJ's scrolling
|
||||
// doesn't affect IdeaVim's scrolling
|
||||
suppressExternalLocalValue(editor)
|
||||
return storedValue?.value != newValue.value
|
||||
}
|
||||
|
||||
override fun onGlobalIdeaValueChanged(propertyName: String) {
|
||||
if (propertyName == ideaPropertyName) {
|
||||
// Again, just make sure the IntelliJ local value is 0
|
||||
injector.editorGroup.getEditors().forEach { it.ij.settings.horizontalScrollOffset = 0 }
|
||||
// The IntelliJ global value has changed. We want to use this as the Vim global value. Since we control scrolling,
|
||||
// set the local IntelliJ value to 0
|
||||
injector.editorGroup.getEditors().forEach { suppressExternalLocalValue(it) }
|
||||
|
||||
// Update the stored Vim global value. This will not override any existing local values
|
||||
// Now update the Vim global value to match the new IntelliJ global value. If the current Vim global value is
|
||||
// Default, then it will already reflect the current global external value. Otherwise, update the Vim global value
|
||||
// to the external global value.
|
||||
val globalScope = OptionAccessScope.GLOBAL(null)
|
||||
val storedValue = internalOptionValueAccessor.getOptionValueInternal(sideScrollOffOption, globalScope)
|
||||
val storedValue = internalOptionValueAccessor.getOptionValueInternal(option, globalScope)
|
||||
if (storedValue !is OptionValue.Default) {
|
||||
val externalGlobalValue = EditorSettingsExternalizable.getInstance().horizontalScrollOffset
|
||||
internalOptionValueAccessor.setOptionValueInternal(
|
||||
sideScrollOffOption,
|
||||
option,
|
||||
globalScope,
|
||||
OptionValue.External(VimInt(externalGlobalValue))
|
||||
OptionValue.External(getExternalGlobalValue())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun getExternalGlobalValue(): T
|
||||
protected abstract fun suppressExternalLocalValue(editor: VimEditor)
|
||||
}
|
||||
|
||||
|
||||
@ -1198,23 +1230,63 @@ private class WrapOptionMapper(wrapOption: ToggleOption, internalOptionValueAcce
|
||||
setIsUseSoftWraps(editor, value.asBoolean())
|
||||
}
|
||||
|
||||
private fun getGlobalIsUseSoftWraps(editor: VimEditor): Boolean {
|
||||
val settings = EditorSettingsExternalizable.getInstance()
|
||||
if (settings.isUseSoftWraps) {
|
||||
val masks = settings.softWrapFileMasks
|
||||
if (masks.trim() == "*") return true
|
||||
override fun canInitialiseOptionFrom(sourceEditor: VimEditor, targetEditor: VimEditor): Boolean {
|
||||
// IntelliJ's soft-wrap settings are based on editor kind, so there can be different wrap options for consoles,
|
||||
// main editors, etc. This is particularly noticeable in the console when running an application. The main editor
|
||||
// might have the Vim default with line wrap enabled. Initialising the run console will also have a default value,
|
||||
// and won't be updated by the options subsystem. It might have wrap enabled or not. If the editors were the same
|
||||
// kind, the same default value would be used.
|
||||
// However, if the main editor has 'wrap' explicitly set, this value is copied to the console, so the behaviour is
|
||||
// inconsistent. Furthermore, the run console has a soft-wraps toggle button that works at the global level, and
|
||||
// IdeaVim only sets the local value, so the toggle button can be inconsistent too.
|
||||
// By denying copying the main editor value during initialisation, the console gets the default value, and the IDE
|
||||
// decides what it should be. The behaviour is now more consistent.
|
||||
// We're happy to initialise diff editors from main editors, as there isn't a different soft wrap setting there.
|
||||
// Preview tabs might also have different settings, but because they're a type of main editor, it doesn't matter
|
||||
// so much
|
||||
fun editorKindToSoftWrapAppliancesPlace(kind: EditorKind) = when (kind) {
|
||||
EditorKind.UNTYPED,
|
||||
EditorKind.DIFF,
|
||||
EditorKind.MAIN_EDITOR -> SoftWrapAppliancePlaces.MAIN_EDITOR
|
||||
EditorKind.CONSOLE -> SoftWrapAppliancePlaces.CONSOLE
|
||||
// Treat PREVIEW as a kind of MAIN_EDITOR instead of SWAP.PREVIEW. There are fewer noticeable differences
|
||||
EditorKind.PREVIEW -> SoftWrapAppliancePlaces.MAIN_EDITOR
|
||||
}
|
||||
|
||||
editor.ij.virtualFile?.let { file ->
|
||||
masks.split(";").forEach { mask ->
|
||||
val trimmed = mask.trim()
|
||||
if (trimmed.isNotEmpty() && PatternUtil.fromMask(trimmed).matcher(file.name).matches()) {
|
||||
return true
|
||||
val sourceKind = editorKindToSoftWrapAppliancesPlace(sourceEditor.ij.editorKind)
|
||||
val targetKind = editorKindToSoftWrapAppliancesPlace(targetEditor.ij.editorKind)
|
||||
return sourceKind == targetKind
|
||||
}
|
||||
|
||||
|
||||
private fun getGlobalIsUseSoftWraps(editor: VimEditor): Boolean {
|
||||
val softWrapAppliancePlace = when (editor.ij.editorKind) {
|
||||
EditorKind.UNTYPED,
|
||||
EditorKind.DIFF,
|
||||
EditorKind.MAIN_EDITOR -> SoftWrapAppliancePlaces.MAIN_EDITOR
|
||||
EditorKind.CONSOLE -> SoftWrapAppliancePlaces.CONSOLE
|
||||
EditorKind.PREVIEW -> SoftWrapAppliancePlaces.PREVIEW
|
||||
}
|
||||
|
||||
val settings = EditorSettingsExternalizable.getInstance()
|
||||
if (softWrapAppliancePlace == SoftWrapAppliancePlaces.MAIN_EDITOR) {
|
||||
if (settings.isUseSoftWraps) {
|
||||
val masks = settings.softWrapFileMasks
|
||||
if (masks.trim() == "*") return true
|
||||
|
||||
editor.ij.virtualFile?.let { file ->
|
||||
masks.split(";").forEach { mask ->
|
||||
val trimmed = mask.trim()
|
||||
if (trimmed.isNotEmpty() && PatternUtil.fromMask(trimmed).matcher(file.name).matches()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
return settings.isUseSoftWraps(softWrapAppliancePlace)
|
||||
}
|
||||
|
||||
private fun getEffectiveIsUseSoftWraps(editor: VimEditor) = editor.ij.settings.isUseSoftWraps
|
||||
@ -1241,28 +1313,28 @@ private class WrapOptionMapper(wrapOption: ToggleOption, internalOptionValueAcce
|
||||
}
|
||||
|
||||
|
||||
public class IjOptionConstants {
|
||||
class IjOptionConstants {
|
||||
@Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate", "ConstPropertyName")
|
||||
public companion object {
|
||||
companion object {
|
||||
|
||||
public const val idearefactormode_keep: String = "keep"
|
||||
public const val idearefactormode_select: String = "select"
|
||||
public const val idearefactormode_visual: String = "visual"
|
||||
const val idearefactormode_keep: String = "keep"
|
||||
const val idearefactormode_select: String = "select"
|
||||
const val idearefactormode_visual: String = "visual"
|
||||
|
||||
public const val ideastatusicon_enabled: String = "enabled"
|
||||
public const val ideastatusicon_gray: String = "gray"
|
||||
public const val ideastatusicon_disabled: String = "disabled"
|
||||
const val ideastatusicon_enabled: String = "enabled"
|
||||
const val ideastatusicon_gray: String = "gray"
|
||||
const val ideastatusicon_disabled: String = "disabled"
|
||||
|
||||
public const val ideavimsupport_dialog: String = "dialog"
|
||||
public const val ideavimsupport_singleline: String = "singleline"
|
||||
public const val ideavimsupport_dialoglegacy: String = "dialoglegacy"
|
||||
const val ideavimsupport_dialog: String = "dialog"
|
||||
const val ideavimsupport_singleline: String = "singleline"
|
||||
const val ideavimsupport_dialoglegacy: String = "dialoglegacy"
|
||||
|
||||
public const val ideawrite_all: String = "all"
|
||||
public const val ideawrite_file: String = "file"
|
||||
const val ideawrite_all: String = "all"
|
||||
const val ideawrite_file: String = "file"
|
||||
|
||||
public val ideaStatusIconValues: Set<String> = setOf(ideastatusicon_enabled, ideastatusicon_gray, ideastatusicon_disabled)
|
||||
public val ideaRefactorModeValues: Set<String> = setOf(idearefactormode_keep, idearefactormode_select, idearefactormode_visual)
|
||||
public val ideaWriteValues: Set<String> = setOf(ideawrite_all, ideawrite_file)
|
||||
public val ideavimsupportValues: Set<String> = setOf(ideavimsupport_dialog, ideavimsupport_singleline, ideavimsupport_dialoglegacy)
|
||||
val ideaStatusIconValues: Set<String> = setOf(ideastatusicon_enabled, ideastatusicon_gray, ideastatusicon_disabled)
|
||||
val ideaRefactorModeValues: Set<String> = setOf(idearefactormode_keep, idearefactormode_select, idearefactormode_visual)
|
||||
val ideaWriteValues: Set<String> = setOf(ideawrite_all, ideawrite_file)
|
||||
val ideavimsupportValues: Set<String> = setOf(ideavimsupport_dialog, ideavimsupport_singleline, ideavimsupport_dialoglegacy)
|
||||
}
|
||||
}
|
||||
|
@ -18,111 +18,22 @@ import com.intellij.openapi.progress.ProgressIndicatorProvider
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.util.execution.ParametersListUtil
|
||||
import com.intellij.util.text.CharSequenceReader
|
||||
import com.maddyhome.idea.vim.KeyHandler.Companion.getInstance
|
||||
import com.maddyhome.idea.vim.KeyProcessResult
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.VimProcessGroupBase
|
||||
import com.maddyhome.idea.vim.api.globalOptions
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.command.Command
|
||||
import com.maddyhome.idea.vim.helper.requestFocus
|
||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
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 java.io.BufferedWriter
|
||||
import java.io.IOException
|
||||
import java.io.OutputStreamWriter
|
||||
import java.io.Reader
|
||||
import java.io.Writer
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
public class ProcessGroup : VimProcessGroupBase() {
|
||||
override var lastCommand: String? = null
|
||||
private set
|
||||
override var isCommandProcessing: Boolean = false
|
||||
override var modeBeforeCommandProcessing: Mode? = null
|
||||
|
||||
public override fun startExEntry(
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
command: Command,
|
||||
label: String,
|
||||
initialCommandText: String,
|
||||
) {
|
||||
// Don't allow ex commands in one line editors
|
||||
if (editor.isOneLineMode()) return
|
||||
|
||||
val currentMode = editor.vimStateMachine.mode
|
||||
check(currentMode is ReturnableFromCmd) {
|
||||
"Cannot enable cmd mode from current mode $currentMode"
|
||||
}
|
||||
|
||||
isCommandProcessing = true
|
||||
modeBeforeCommandProcessing = currentMode
|
||||
|
||||
// Make sure the Visual selection marks are up to date before we use them.
|
||||
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)
|
||||
}
|
||||
|
||||
public override fun processExKey(editor: VimEditor, stroke: KeyStroke, processResultBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean {
|
||||
// This will only get called if somehow the key focus ended up in the editor while the ex entry window
|
||||
// is open. So I'll put focus back in the editor and process the key.
|
||||
|
||||
val panel = ExEntryPanel.getInstance()
|
||||
if (panel.isActive) {
|
||||
processResultBuilder.addExecutionStep { _, _, _ ->
|
||||
requestFocus(panel.entry)
|
||||
panel.handleKey(stroke)
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
processResultBuilder.addExecutionStep { _, lambdaEditor, _ ->
|
||||
lambdaEditor.mode = Mode.NORMAL()
|
||||
getInstance().reset(lambdaEditor)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
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 -> ""
|
||||
}
|
||||
|
||||
class ProcessGroup : VimProcessGroupBase() {
|
||||
@Throws(ExecutionException::class, ProcessCanceledException::class)
|
||||
public override fun executeCommand(
|
||||
override fun executeCommand(
|
||||
editor: VimEditor,
|
||||
command: String,
|
||||
input: CharSequence?,
|
||||
@ -221,7 +132,7 @@ public class ProcessGroup : VimProcessGroupBase() {
|
||||
}
|
||||
}
|
||||
|
||||
public companion object {
|
||||
companion object {
|
||||
private val logger = logger<ProcessGroup>()
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -53,6 +53,7 @@ internal class VimJumpServiceImpl : VimJumpServiceBase(), PersistentStateCompone
|
||||
jumpElem.setAttribute("line", jump.line.toString())
|
||||
jumpElem.setAttribute("column", jump.col.toString())
|
||||
jumpElem.setAttribute("filename", StringUtil.notNullize(jump.filepath))
|
||||
jumpElem.setAttribute("protocol", StringUtil.notNullize(jump.protocol))
|
||||
projectElement.addContent(jumpElem)
|
||||
if (logger.isDebug()) {
|
||||
logger.debug("saved jump = $jump")
|
||||
@ -73,6 +74,7 @@ internal class VimJumpServiceImpl : VimJumpServiceBase(), PersistentStateCompone
|
||||
Integer.parseInt(jumpElement.getAttributeValue("line")),
|
||||
Integer.parseInt(jumpElement.getAttributeValue("column")),
|
||||
jumpElement.getAttributeValue("filename"),
|
||||
jumpElement.getAttributeValue("protocol", "file"),
|
||||
)
|
||||
jumps.add(jump)
|
||||
}
|
||||
@ -94,7 +96,7 @@ internal class JumpsListener(val project: Project) : RecentPlacesListener {
|
||||
if (changePlace.timeStamp < jumpService.lastJumpTimeStamp) return // this listener is notified asynchronously, and
|
||||
// we do not want jumps that were processed before
|
||||
val jump = buildJump(changePlace) ?: return
|
||||
jumpService.addJump(project.basePath ?: IjVimEditor.DEFAULT_PROJECT_ID, jump, true)
|
||||
jumpService.addJump(injector.file.getProjectId(project), jump, true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,7 +108,7 @@ internal class JumpsListener(val project: Project) : RecentPlacesListener {
|
||||
if (changePlace.timeStamp < jumpService.lastJumpTimeStamp) return // this listener is notified asynchronously, and
|
||||
// we do not want jumps that were processed before
|
||||
val jump = buildJump(changePlace) ?: return
|
||||
jumpService.removeJump(project.basePath ?: IjVimEditor.DEFAULT_PROJECT_ID, jump)
|
||||
jumpService.removeJump(injector.file.getProjectId(project), jump)
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,6 +122,6 @@ internal class JumpsListener(val project: Project) : RecentPlacesListener {
|
||||
|
||||
val path = place.file.path
|
||||
|
||||
return Jump(line, col, path)
|
||||
return Jump(line, col, path, place.file.fileSystem.protocol)
|
||||
}
|
||||
}
|
||||
|
@ -83,6 +83,11 @@ internal class PutGroup : VimPutBase() {
|
||||
val editor = (vimEditor as IjVimEditor).editor
|
||||
val context = vimContext.context as DataContext
|
||||
val carets: MutableMap<Caret, RangeMarker> = mutableMapOf()
|
||||
if (editor.isInsertMode) {
|
||||
val undo = injector.undo
|
||||
val nanoTime = System.nanoTime()
|
||||
vimEditor.forEachCaret { undo.startInsertSequence(it, it.offset, nanoTime) }
|
||||
}
|
||||
EditorHelper.getOrderedCaretsList(editor).forEach { caret ->
|
||||
val startOffset =
|
||||
prepareDocumentAndGetStartOffsets(
|
||||
|
@ -53,12 +53,12 @@ import javax.swing.Timer
|
||||
* no adjustment gets performed and IdeaVim stays in insert mode.
|
||||
*/
|
||||
// Do not remove until it's used in EasyMotion plugin in tests
|
||||
public object VimVisualTimer {
|
||||
object VimVisualTimer {
|
||||
|
||||
public var swingTimer: Timer? = null
|
||||
public var mode: Mode? = null
|
||||
var swingTimer: Timer? = null
|
||||
var mode: Mode? = null
|
||||
|
||||
public inline fun singleTask(currentMode: Mode, crossinline task: (initialMode: Mode?) -> Unit) {
|
||||
inline fun singleTask(currentMode: Mode, crossinline task: (initialMode: Mode?) -> Unit) {
|
||||
swingTimer?.stop()
|
||||
|
||||
if (mode == null) mode = currentMode
|
||||
@ -70,7 +70,7 @@ public object VimVisualTimer {
|
||||
swingTimer = timer
|
||||
}
|
||||
|
||||
public fun doNow() {
|
||||
fun doNow() {
|
||||
val swingTimer1 = swingTimer
|
||||
if (swingTimer1 != null) {
|
||||
swingTimer1.stop()
|
||||
@ -80,12 +80,12 @@ public object VimVisualTimer {
|
||||
}
|
||||
}
|
||||
|
||||
public fun drop() {
|
||||
fun drop() {
|
||||
swingTimer?.stop()
|
||||
swingTimer = null
|
||||
}
|
||||
|
||||
public inline fun timerAction(task: (initialMode: Mode?) -> Unit) {
|
||||
inline fun timerAction(task: (initialMode: Mode?) -> Unit) {
|
||||
task(mode)
|
||||
swingTimer = null
|
||||
mode = null
|
||||
|
@ -9,13 +9,9 @@
|
||||
package com.maddyhome.idea.vim.group.visual
|
||||
|
||||
import com.intellij.find.FindManager
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.VimVisualMotionGroupBase
|
||||
import com.maddyhome.idea.vim.command.CommandState
|
||||
import com.maddyhome.idea.vim.command.engine
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
|
||||
/**
|
||||
@ -31,12 +27,4 @@ internal class VisualMotionGroup : VimVisualMotionGroupBase() {
|
||||
|
||||
return super.autodetectVisualSubmode(editor)
|
||||
}
|
||||
|
||||
/**
|
||||
* COMPATIBILITY-LAYER: Added a method
|
||||
* Please see: https://jb.gg/zo8n0r
|
||||
*/
|
||||
fun enterVisualMode(editor: Editor, subMode: CommandState.SubMode? = null): Boolean {
|
||||
return this.enterVisualMode(editor.vim, subMode?.engine)
|
||||
}
|
||||
}
|
||||
|
@ -144,7 +144,7 @@ private object AttributesCache {
|
||||
@TestOnly
|
||||
internal fun getGuiCursorMode(editor: Editor) = editor.guicursorMode()
|
||||
|
||||
public class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener {
|
||||
class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener {
|
||||
override fun isReplaceCharChanged(editor: VimEditor) {
|
||||
updateCaretsVisual(editor)
|
||||
}
|
||||
@ -163,7 +163,7 @@ public class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeLi
|
||||
}
|
||||
}
|
||||
|
||||
public fun updateAllEditorsCaretsVisual() {
|
||||
fun updateAllEditorsCaretsVisual() {
|
||||
injector.editorGroup.getEditors().forEach { editor ->
|
||||
val ijEditor = (editor as IjVimEditor).editor
|
||||
ijEditor.updateCaretsVisualAttributes()
|
||||
|
@ -11,13 +11,7 @@
|
||||
package com.maddyhome.idea.vim.helper
|
||||
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.api.Options
|
||||
import com.maddyhome.idea.vim.api.hasValue
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.command.CommandState
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.options.OptionAccessScope
|
||||
import com.maddyhome.idea.vim.options.OptionConstants
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.inVisualMode
|
||||
|
||||
@ -27,55 +21,15 @@ internal val Mode.hasVisualSelection
|
||||
else -> false
|
||||
}
|
||||
|
||||
/**
|
||||
* COMPATIBILITY-LAYER: New method
|
||||
* Please see: https://jb.gg/zo8n0r
|
||||
*/
|
||||
public val Editor.mode: CommandState.Mode
|
||||
get() {
|
||||
val mode = this.vim.vimStateMachine.mode
|
||||
return when (mode) {
|
||||
is Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
|
||||
Mode.INSERT -> CommandState.Mode.INSERT
|
||||
is Mode.NORMAL -> CommandState.Mode.COMMAND
|
||||
is Mode.OP_PENDING -> CommandState.Mode.OP_PENDING
|
||||
Mode.REPLACE -> CommandState.Mode.REPLACE
|
||||
is Mode.SELECT -> CommandState.Mode.SELECT
|
||||
is Mode.VISUAL -> CommandState.Mode.VISUAL
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* COMPATIBILITY-LAYER: New method
|
||||
* Please see: https://jb.gg/zo8n0r
|
||||
*/
|
||||
@Deprecated("Please migrate to VimEditor.isEndAllowed which can correctly access virtualedit at the right scope",
|
||||
replaceWith = ReplaceWith("VimEditor.isEndAllowed"))
|
||||
public val CommandState.Mode.isEndAllowed: Boolean
|
||||
get() {
|
||||
fun possiblyUsesVirtualSpace(): Boolean {
|
||||
// virtualedit is GLOBAL_OR_LOCAL_TO_WINDOW. We should be using EFFECTIVE, but we don't have a valid editor (which
|
||||
// is why this property is deprecated). Fetch the global value, passing in the fallback window to avoid asserts
|
||||
// DO NOT COPY THIS APPROACH - ALWAYS USE A REAL WINDOW FOR NON-GLOBAL OPTIONS!
|
||||
return injector.optionGroup.hasValue(Options.virtualedit, OptionAccessScope.GLOBAL(injector.fallbackWindow), OptionConstants.virtualedit_onemore)
|
||||
}
|
||||
|
||||
return when (this) {
|
||||
CommandState.Mode.INSERT, CommandState.Mode.VISUAL, CommandState.Mode.SELECT -> true
|
||||
CommandState.Mode.COMMAND, CommandState.Mode.CMD_LINE, CommandState.Mode.REPLACE, CommandState.Mode.OP_PENDING -> possiblyUsesVirtualSpace()
|
||||
CommandState.Mode.INSERT_NORMAL, CommandState.Mode.INSERT_VISUAL, CommandState.Mode.INSERT_SELECT -> possiblyUsesVirtualSpace()
|
||||
}
|
||||
}
|
||||
|
||||
public val Mode.inNormalMode: Boolean
|
||||
val Mode.inNormalMode: Boolean
|
||||
get() = this is Mode.NORMAL
|
||||
|
||||
@get:JvmName("inInsertMode")
|
||||
public val Editor.inInsertMode: Boolean
|
||||
val Editor.inInsertMode: Boolean
|
||||
get() = this.vim.mode == Mode.INSERT || this.vim.mode == Mode.REPLACE
|
||||
|
||||
@get:JvmName("inVisualMode")
|
||||
public val Editor.inVisualMode: Boolean
|
||||
val Editor.inVisualMode: Boolean
|
||||
get() = this.vim.inVisualMode
|
||||
|
||||
@get:JvmName("inExMode")
|
||||
|
@ -100,15 +100,6 @@ public class EditorHelper {
|
||||
return EngineEditorHelperKt.normalizeVisualLine(new IjVimEditor(editor), line);
|
||||
}
|
||||
|
||||
/**
|
||||
* COMPATIBILITY-LAYER: Created a function
|
||||
* Please see: <a href="https://jb.gg/zo8n0r">doc</a>
|
||||
*/
|
||||
public static int getVisualLineCount(final @NotNull Editor editor) {
|
||||
@NotNull final VimEditor editor1 = new IjVimEditor(editor);
|
||||
return EngineEditorHelperKt.getVisualLineCount(editor1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Best efforts to ensure that scroll offset doesn't overlap itself.
|
||||
* <p>
|
||||
@ -335,7 +326,7 @@ public class EditorHelper {
|
||||
|
||||
final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
|
||||
@NotNull final VimEditor editor1 = new IjVimEditor(editor);
|
||||
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
|
||||
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) + editor.getSettings().getAdditionalLinesCount();
|
||||
final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine);
|
||||
|
||||
// For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen.
|
||||
|
@ -12,6 +12,7 @@ package com.maddyhome.idea.vim.helper
|
||||
|
||||
import com.intellij.codeWithMe.ClientId
|
||||
import com.intellij.openapi.editor.Caret
|
||||
import com.intellij.openapi.editor.CaretState
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.ex.util.EditorUtil
|
||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
|
||||
@ -20,12 +21,14 @@ import com.maddyhome.idea.vim.api.StringListOptionValue
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.group.IjOptionConstants
|
||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.mode.inBlockSelection
|
||||
import java.awt.Component
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JTable
|
||||
|
||||
@Deprecated("Use fileSize from VimEditor")
|
||||
public val Editor.fileSize: Int
|
||||
val Editor.fileSize: Int
|
||||
get() = document.textLength
|
||||
|
||||
/**
|
||||
@ -96,3 +99,41 @@ internal val Caret.vimLine: Int
|
||||
*/
|
||||
internal val Editor.vimLine: Int
|
||||
get() = this.caretModel.currentCaret.vimLine
|
||||
|
||||
internal inline fun Editor.runWithEveryCaretAndRestore(action: () -> Unit) {
|
||||
val caretModel = this.caretModel
|
||||
val carets = if (this.vim.inBlockSelection) null else caretModel.allCarets
|
||||
if (carets == null || carets.size == 1) {
|
||||
action()
|
||||
}
|
||||
else {
|
||||
var initialDocumentSize = this.document.textLength
|
||||
var documentSizeDifference = 0
|
||||
|
||||
val caretOffsets = carets.map { it.selectionStart to it.selectionEnd }
|
||||
val restoredCarets = mutableListOf<CaretState>()
|
||||
|
||||
caretModel.removeSecondaryCarets()
|
||||
|
||||
for ((selectionStart, selectionEnd) in caretOffsets) {
|
||||
if (selectionStart == selectionEnd) {
|
||||
caretModel.primaryCaret.moveToOffset(selectionStart + documentSizeDifference)
|
||||
}
|
||||
else {
|
||||
caretModel.primaryCaret.setSelection(
|
||||
selectionStart + documentSizeDifference,
|
||||
selectionEnd + documentSizeDifference
|
||||
)
|
||||
}
|
||||
|
||||
action()
|
||||
restoredCarets.add(caretModel.caretsAndSelections.single())
|
||||
|
||||
val documentLength = this.document.textLength
|
||||
documentSizeDifference += documentLength - initialDocumentSize
|
||||
initialDocumentSize = documentLength
|
||||
}
|
||||
|
||||
caretModel.caretsAndSelections = restoredCarets
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import com.intellij.openapi.actionSystem.PlatformDataKeys
|
||||
import com.intellij.openapi.actionSystem.ex.ActionManagerEx
|
||||
import com.intellij.openapi.actionSystem.ex.ActionUtil
|
||||
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
|
||||
import com.intellij.openapi.actionSystem.impl.Utils
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.openapi.command.UndoConfirmationPolicy
|
||||
import com.intellij.openapi.components.Service
|
||||
@ -86,6 +87,7 @@ internal class IjActionExecutor : VimActionExecutor {
|
||||
ActionManager.getInstance(),
|
||||
0,
|
||||
)
|
||||
Utils.initUpdateSession(event)
|
||||
// beforeActionPerformedUpdate should be called to update the action. It fixes some rider-specific problems.
|
||||
// because rider use async update method. See VIM-1819.
|
||||
// This method executes inside of lastUpdateAndCheckDumb
|
||||
|
@ -15,6 +15,7 @@ import com.intellij.openapi.editor.actionSystem.EditorActionManager
|
||||
import com.intellij.openapi.editor.ex.util.EditorUtil
|
||||
import com.maddyhome.idea.vim.api.EngineEditorHelper
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.VimRangeMarker
|
||||
import com.maddyhome.idea.vim.api.VimVisualPosition
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
@ -57,4 +58,20 @@ internal class IjEditorHelper : EngineEditorHelper {
|
||||
override fun inlayAwareOffsetToVisualPosition(editor: VimEditor, offset: Int): VimVisualPosition {
|
||||
return EditorUtil.inlayAwareOffsetToVisualPosition(editor.ij, offset).vim
|
||||
}
|
||||
|
||||
override fun createRangeMarker(editor: VimEditor, startOffset: Int, endOffset: Int): VimRangeMarker {
|
||||
val ijRangeMarker = editor.ij.document.createRangeMarker(startOffset, endOffset)
|
||||
return object : VimRangeMarker {
|
||||
override val startOffset: Int
|
||||
get() = ijRangeMarker.startOffset
|
||||
override val endOffset: Int
|
||||
get() = ijRangeMarker.endOffset
|
||||
override val isValid: Boolean
|
||||
get() = ijRangeMarker.isValid
|
||||
|
||||
override fun dispose() {
|
||||
ijRangeMarker.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ internal object ScrollViewHelper {
|
||||
// that this needs to be replaced as a more or less dumb line for line rewrite.
|
||||
val topLine = getVisualLineAtTopOfScreen(editor)
|
||||
val bottomLine = getVisualLineAtBottomOfScreen(editor)
|
||||
val lastLine = vimEditor.getVisualLineCount() - 1
|
||||
val lastLine = vimEditor.getVisualLineCount() + editor.settings.additionalLinesCount
|
||||
|
||||
// We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred
|
||||
val scrollOffset = injector.options(vimEditor).scrolloff
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -8,152 +8,26 @@
|
||||
|
||||
package com.maddyhome.idea.vim.helper
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfo
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.editor.Caret
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.spellchecker.SpellCheckerSeveritiesProvider
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.getLineEndOffset
|
||||
import com.maddyhome.idea.vim.api.getText
|
||||
import com.maddyhome.idea.vim.api.globalOptions
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.common.Direction
|
||||
import com.maddyhome.idea.vim.helper.SearchHelper.findPositionOfFirstCharacter
|
||||
|
||||
private data class State(val position: Int, val trigger: Char, val inQuote: Boolean?, val lastOpenSingleQuotePos: Int)
|
||||
|
||||
// bounds are considered inside corresponding quotes
|
||||
internal fun checkInString(chars: CharSequence, currentPos: Int, str: Boolean): Boolean {
|
||||
val begin = findPositionOfFirstCharacter(chars, currentPos, setOf('\n'), false, Direction.BACKWARDS)?.second?.plus(1) ?: 0
|
||||
val changes = quoteChanges(chars, begin)
|
||||
// TODO: here we need to keep only the latest element in beforePos (if any) and
|
||||
// don't need atAndAfterPos to be eagerly collected
|
||||
var (beforePos, atAndAfterPos) = changes.partition { it.position < currentPos }
|
||||
|
||||
var (atPos, afterPos) = atAndAfterPos.partition { it.position == currentPos }
|
||||
assert(atPos.size <= 1) { "Multiple characters at position $currentPos in string $chars" }
|
||||
if (atPos.isNotEmpty()) {
|
||||
val atPosChange = atPos[0]
|
||||
if (afterPos.isEmpty()) {
|
||||
// it is situation when cursor is on closing quote, so we must consider that we are inside quotes pair
|
||||
afterPos = afterPos.toMutableList()
|
||||
afterPos.add(atPosChange)
|
||||
} else {
|
||||
// it is situation when cursor is on opening quote, so we must consider that we are inside quotes pair
|
||||
beforePos = beforePos.toMutableList()
|
||||
beforePos.add(atPosChange)
|
||||
}
|
||||
}
|
||||
|
||||
val lastBeforePos = beforePos.lastOrNull()
|
||||
|
||||
// if opening quote was found before pos (inQuote=true), it doesn't mean pos is in string, we need
|
||||
// to find closing quote to be sure
|
||||
var posInQuote = lastBeforePos?.inQuote?.let { if (it) null else it }
|
||||
|
||||
val lastOpenSingleQuotePosBeforeCurrentPos = lastBeforePos?.lastOpenSingleQuotePos ?: -1
|
||||
var posInChar = if (lastOpenSingleQuotePosBeforeCurrentPos == -1) false else null
|
||||
|
||||
var inQuote: Boolean? = null
|
||||
|
||||
for ((_, trigger, inQuoteAfter, lastOpenSingleQuotePosAfter) in afterPos) {
|
||||
inQuote = inQuoteAfter
|
||||
if (posInQuote != null && posInChar != null) break
|
||||
if (posInQuote == null && inQuoteAfter != null) {
|
||||
// if we found double quote
|
||||
if (trigger == '"') {
|
||||
// then previously it has opposite value
|
||||
posInQuote = !inQuoteAfter
|
||||
// if we found single quote
|
||||
} else if (trigger == '\'') {
|
||||
// then we found closing single quote
|
||||
posInQuote = inQuoteAfter
|
||||
}
|
||||
}
|
||||
if (posInChar == null && lastOpenSingleQuotePosAfter != lastOpenSingleQuotePosBeforeCurrentPos) {
|
||||
// if we found double quote and we reset position of last single quote
|
||||
if (trigger == '"' && lastOpenSingleQuotePosAfter == -1) {
|
||||
// then it means previously there supposed to be open single quote
|
||||
posInChar = false
|
||||
// if we found single quote
|
||||
} else if (trigger == '\'') {
|
||||
// if we reset position of last single quote
|
||||
// it means we found closing single quote
|
||||
// else it means we found opening single quote
|
||||
posInChar = lastOpenSingleQuotePosAfter == -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return if (str) posInQuote != null && posInQuote && (inQuote == null || !inQuote) else posInChar != null && posInChar
|
||||
}
|
||||
|
||||
// yields changes of inQuote and lastOpenSingleQuotePos during while iterating over chars
|
||||
// rules are that:
|
||||
// - escaped quotes are skipped
|
||||
// - single quoted group may enclose only one character, maybe escaped,
|
||||
// - so distance between opening and closing single quotes cannot be more than 3
|
||||
// - bounds are considered inside corresponding quotes
|
||||
private fun quoteChanges(chars: CharSequence, begin: Int) = sequence {
|
||||
// position of last found unpaired single quote
|
||||
var lastOpenSingleQuotePos = -1
|
||||
// whether we are in double quotes
|
||||
// true - definitely yes
|
||||
// false - definitely no
|
||||
// null - maybe yes, in case we found such combination: '"
|
||||
// in that situation it may be double quote inside single quotes, so we cannot threat it as double quote pair open/close
|
||||
var inQuote: Boolean? = false
|
||||
val charsToSearch = setOf('\'', '"', '\n')
|
||||
var found = findPositionOfFirstCharacter(chars, begin, charsToSearch, false, Direction.FORWARDS)
|
||||
while (found != null && found.first != '\n') {
|
||||
val i = found.second
|
||||
|
||||
val c = found.first
|
||||
when (c) {
|
||||
'"' -> {
|
||||
// if [maybe] in quote, then we know we found closing quote, so now we surely are not in quote
|
||||
if (inQuote == null || inQuote) {
|
||||
// we just found closing double quote
|
||||
inQuote = false
|
||||
// reset last found single quote, as it was in string literal
|
||||
lastOpenSingleQuotePos = -1
|
||||
// if we previously found unclosed single quote
|
||||
} else if (lastOpenSingleQuotePos >= 0) {
|
||||
// ...but we are too far from it
|
||||
if (i - lastOpenSingleQuotePos > 2) {
|
||||
// then it definitely was not opening single quote
|
||||
lastOpenSingleQuotePos = -1
|
||||
// and we found opening double quote
|
||||
inQuote = true
|
||||
} else {
|
||||
// else we don't know if we inside double or single quotes or not
|
||||
inQuote = null
|
||||
}
|
||||
// we were not in double nor in single quote, so now we are in double quote
|
||||
} else {
|
||||
inQuote = true
|
||||
}
|
||||
}
|
||||
'\'' -> {
|
||||
// if we previously found unclosed single quote
|
||||
if (lastOpenSingleQuotePos >= 0) {
|
||||
// ...but we are too far from it
|
||||
if (i - lastOpenSingleQuotePos > 3) {
|
||||
// ... forget about it and threat current one as unclosed
|
||||
lastOpenSingleQuotePos = i
|
||||
} else {
|
||||
// else we found closing single quote
|
||||
lastOpenSingleQuotePos = -1
|
||||
// and if we didn't know whether we are in double quote or not
|
||||
if (inQuote == null) {
|
||||
// then now we are definitely not in
|
||||
inQuote = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// we found opening single quote
|
||||
lastOpenSingleQuotePos = i
|
||||
}
|
||||
}
|
||||
}
|
||||
yield(State(i, c, inQuote, lastOpenSingleQuotePos))
|
||||
found =
|
||||
findPositionOfFirstCharacter(chars, i + Direction.FORWARDS.toInt(), charsToSearch, false, Direction.FORWARDS)
|
||||
}
|
||||
}
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.helper.CharacterHelper.charType
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import it.unimi.dsi.fastutil.ints.IntComparator
|
||||
import it.unimi.dsi.fastutil.ints.IntIterator
|
||||
import it.unimi.dsi.fastutil.ints.IntRBTreeSet
|
||||
import it.unimi.dsi.fastutil.ints.IntSortedSet
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Check ignorecase and smartcase options to see if a case insensitive search should be performed with the given pattern.
|
||||
@ -180,3 +54,354 @@ private fun containsUpperCase(pattern: String): Boolean {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* This counts all the words in the file.
|
||||
*/
|
||||
fun countWords(
|
||||
vimEditor: VimEditor,
|
||||
start: Int = 0,
|
||||
end: Long = vimEditor.fileSize(),
|
||||
): CountPosition {
|
||||
val offset = vimEditor.currentCaret().offset
|
||||
|
||||
var count = 1
|
||||
var position = 0
|
||||
var last = -1
|
||||
var res = start
|
||||
while (true) {
|
||||
res = injector.searchHelper.findNextWord(vimEditor, res, 1, true, false)
|
||||
if (res == start || res == 0 || res > end || res == last) {
|
||||
break
|
||||
}
|
||||
|
||||
count++
|
||||
|
||||
if (res == offset) {
|
||||
position = count
|
||||
} else if (last < offset && res >= offset) {
|
||||
position = if (count == 2) {
|
||||
1
|
||||
} else {
|
||||
count - 1
|
||||
}
|
||||
}
|
||||
|
||||
last = res
|
||||
}
|
||||
|
||||
if (position == 0 && res == offset) {
|
||||
position = count
|
||||
}
|
||||
|
||||
return CountPosition(count, position)
|
||||
}
|
||||
|
||||
fun findNumbersInRange(
|
||||
editor: Editor,
|
||||
textRange: TextRange,
|
||||
alpha: Boolean,
|
||||
hex: Boolean,
|
||||
octal: Boolean,
|
||||
): List<Pair<TextRange, NumberType>> {
|
||||
val result: MutableList<Pair<TextRange, NumberType>> = ArrayList()
|
||||
|
||||
|
||||
for (i in 0 until textRange.size()) {
|
||||
val startOffset = textRange.startOffsets[i]
|
||||
val end = textRange.endOffsets[i]
|
||||
val text: String = editor.vim.getText(startOffset, end)
|
||||
val textChunks = text.split("\\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
var chunkStart = 0
|
||||
for (chunk in textChunks) {
|
||||
val number = findNumberInText(chunk, 0, alpha, hex, octal)
|
||||
|
||||
if (number != null) {
|
||||
result.add(
|
||||
Pair(
|
||||
TextRange(
|
||||
number.first.startOffset + startOffset + chunkStart,
|
||||
number.first.endOffset + startOffset + chunkStart
|
||||
),
|
||||
number.second
|
||||
)
|
||||
)
|
||||
}
|
||||
chunkStart += 1 + chunk.length
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun findNumberUnderCursor(
|
||||
editor: Editor,
|
||||
caret: Caret,
|
||||
alpha: Boolean,
|
||||
hex: Boolean,
|
||||
octal: Boolean,
|
||||
): Pair<TextRange, NumberType>? {
|
||||
val lline = caret.logicalPosition.line
|
||||
val text = IjVimEditor(editor).getLineText(lline).lowercase(Locale.getDefault())
|
||||
val startLineOffset = IjVimEditor(editor).getLineStartOffset(lline)
|
||||
val posOnLine = caret.offset - startLineOffset
|
||||
|
||||
val numberTextRange = findNumberInText(text, posOnLine, alpha, hex, octal) ?: return null
|
||||
|
||||
return Pair(
|
||||
TextRange(
|
||||
numberTextRange.first.startOffset + startLineOffset,
|
||||
numberTextRange.first.endOffset + startLineOffset
|
||||
),
|
||||
numberTextRange.second
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for number in given text from start position
|
||||
*
|
||||
* @param textInRange - text to search in
|
||||
* @param startPosOnLine - start offset to search
|
||||
* @return - text range with number
|
||||
*/
|
||||
fun findNumberInText(
|
||||
textInRange: String,
|
||||
startPosOnLine: Int,
|
||||
alpha: Boolean,
|
||||
hex: Boolean,
|
||||
octal: Boolean,
|
||||
): Pair<TextRange, NumberType>? {
|
||||
if (logger.isDebugEnabled) {
|
||||
logger.debug("text=$textInRange")
|
||||
}
|
||||
|
||||
var pos = startPosOnLine
|
||||
val lineEndOffset = textInRange.length
|
||||
|
||||
while (true) {
|
||||
// Skip over current whitespace if any
|
||||
while (pos < lineEndOffset && !isNumberChar(textInRange[pos], alpha, hex, octal, true)) {
|
||||
pos++
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled) logger.debug("pos=$pos")
|
||||
if (pos >= lineEndOffset) {
|
||||
logger.debug("no number char on line")
|
||||
return null
|
||||
}
|
||||
|
||||
val isHexChar = "abcdefABCDEF".indexOf(textInRange[pos]) >= 0
|
||||
|
||||
if (hex) {
|
||||
// Ox and OX handling
|
||||
if (textInRange[pos] == '0' && pos < lineEndOffset - 1 && "xX".indexOf(textInRange[pos + 1]) >= 0) {
|
||||
pos += 2
|
||||
} else if ("xX".indexOf(textInRange[pos]) >= 0 && pos > 0 && textInRange[pos - 1] == '0') {
|
||||
pos++
|
||||
}
|
||||
|
||||
logger.debug("checking hex")
|
||||
val range = findRange(textInRange, pos, false, true, false, false)
|
||||
val start = range.first
|
||||
val end = range.second
|
||||
|
||||
// Ox and OX
|
||||
if (start >= 2 && textInRange.substring(start - 2, start).equals("0x", ignoreCase = true)) {
|
||||
logger.debug("found hex")
|
||||
return Pair(TextRange(start - 2, end), NumberType.HEX)
|
||||
}
|
||||
|
||||
if (!isHexChar || alpha) {
|
||||
break
|
||||
} else {
|
||||
pos++
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (octal) {
|
||||
logger.debug("checking octal")
|
||||
val range = findRange(textInRange, pos, false, false, true, false)
|
||||
val start = range.first
|
||||
val end = range.second
|
||||
|
||||
if (end - start == 1 && textInRange[start] == '0') {
|
||||
return Pair(TextRange(start, end), NumberType.DEC)
|
||||
}
|
||||
if (textInRange[start] == '0' && end > start &&
|
||||
!(start > 0 && isNumberChar(textInRange[start - 1], false, false, false, true))
|
||||
) {
|
||||
logger.debug("found octal")
|
||||
return Pair(TextRange(start, end), NumberType.OCT)
|
||||
}
|
||||
}
|
||||
|
||||
if (alpha) {
|
||||
if (logger.isDebugEnabled) logger.debug("checking alpha for " + textInRange[pos])
|
||||
if (isNumberChar(textInRange[pos], true, false, false, false)) {
|
||||
if (logger.isDebugEnabled) logger.debug("found alpha at $pos")
|
||||
return Pair(TextRange(pos, pos + 1), NumberType.ALPHA)
|
||||
}
|
||||
}
|
||||
|
||||
val range = findRange(textInRange, pos, false, false, false, true)
|
||||
var start = range.first
|
||||
val end = range.second
|
||||
if (start > 0 && textInRange[start - 1] == '-') {
|
||||
start--
|
||||
}
|
||||
|
||||
return Pair(TextRange(start, end), NumberType.DEC)
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for digits block that matches parameters
|
||||
*/
|
||||
private fun findRange(
|
||||
text: String,
|
||||
pos: Int,
|
||||
alpha: Boolean,
|
||||
hex: Boolean,
|
||||
octal: Boolean,
|
||||
decimal: Boolean,
|
||||
): Pair<Int, Int> {
|
||||
var end = pos
|
||||
while (end < text.length && isNumberChar(text[end], alpha, hex, octal, decimal || octal)) {
|
||||
end++
|
||||
}
|
||||
var start = pos
|
||||
while (start >= 0 && isNumberChar(text[start], alpha, hex, octal, decimal || octal)) {
|
||||
start--
|
||||
}
|
||||
if (start < end &&
|
||||
(start == -1 ||
|
||||
0 <= start && start < text.length &&
|
||||
!isNumberChar(text[start], alpha, hex, octal, decimal || octal))
|
||||
) {
|
||||
start++
|
||||
}
|
||||
if (octal) {
|
||||
for (i in start until end) {
|
||||
if (!isNumberChar(text[i], false, false, true, false)) return Pair(0, 0)
|
||||
}
|
||||
}
|
||||
return Pair(start, end)
|
||||
}
|
||||
|
||||
private fun isNumberChar(ch: Char, alpha: Boolean, hex: Boolean, octal: Boolean, decimal: Boolean): Boolean {
|
||||
return if (alpha && ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))) {
|
||||
true
|
||||
} else if (octal && (ch >= '0' && ch <= '7')) {
|
||||
true
|
||||
} else if (hex && ((ch >= '0' && ch <= '9') || "abcdefABCDEF".indexOf(ch) >= 0)) {
|
||||
true
|
||||
} else {
|
||||
decimal && (ch >= '0' && ch <= '9')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the word under the cursor or the next word to the right of the cursor on the current line.
|
||||
*
|
||||
* @param editor The editor to find the word in
|
||||
* @param caret The caret to find word under
|
||||
* @return The text range of the found word or null if there is no word under/after the cursor on the line
|
||||
*/
|
||||
fun findWordUnderCursor(editor: Editor, caret: Caret): TextRange? {
|
||||
val vimEditor = IjVimEditor(editor)
|
||||
val chars = editor.document.charsSequence
|
||||
val stop = vimEditor.getLineEndOffset(caret.logicalPosition.line, true)
|
||||
|
||||
val pos = caret.offset
|
||||
// Technically the first condition is covered by the second one, but let it be
|
||||
if (chars.length == 0 || chars.length <= pos) return null
|
||||
|
||||
//if (pos == chars.length() - 1) return new TextRange(chars.length() - 1, chars.length());
|
||||
var start = pos
|
||||
val types = arrayOf(
|
||||
CharacterHelper.CharacterType.KEYWORD,
|
||||
CharacterHelper.CharacterType.PUNCTUATION
|
||||
)
|
||||
for (i in 0..1) {
|
||||
start = pos
|
||||
val type = charType(vimEditor, chars[start], false)
|
||||
if (type == types[i]) {
|
||||
// Search back for start of word
|
||||
while (start > 0 && charType(vimEditor, chars[start - 1], false) == types[i]) {
|
||||
start--
|
||||
}
|
||||
} else {
|
||||
// Search forward for start of word
|
||||
while (start < stop && charType(vimEditor, chars[start], false) != types[i]) {
|
||||
start++
|
||||
}
|
||||
}
|
||||
|
||||
if (start != stop) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (start == stop) {
|
||||
return null
|
||||
}
|
||||
// Special case 1 character words because 'findNextWordEnd' returns one to many chars
|
||||
val end = if (start < stop &&
|
||||
(start >= chars.length - 1 ||
|
||||
charType(vimEditor, chars[start + 1], false) != CharacterHelper.CharacterType.KEYWORD)
|
||||
) {
|
||||
start + 1
|
||||
} else {
|
||||
injector.searchHelper.findNextWordEnd(vimEditor, start, 1, false, false) + 1
|
||||
}
|
||||
|
||||
return TextRange(start, end)
|
||||
}
|
||||
|
||||
fun findMisspelledWords(
|
||||
editor: Editor,
|
||||
startOffset: Int,
|
||||
endOffset: Int,
|
||||
skipCount: Int,
|
||||
offsetOrdering: IntComparator?,
|
||||
): Int {
|
||||
val project = editor.project ?: return -1
|
||||
|
||||
val offsets: IntSortedSet = IntRBTreeSet(offsetOrdering)
|
||||
DaemonCodeAnalyzerEx.processHighlights(
|
||||
editor.document, project, SpellCheckerSeveritiesProvider.TYPO,
|
||||
startOffset, endOffset
|
||||
) { highlight: HighlightInfo ->
|
||||
if (highlight.severity === SpellCheckerSeveritiesProvider.TYPO) {
|
||||
val offset = highlight.getStartOffset()
|
||||
if (offset >= startOffset && offset <= endOffset) {
|
||||
offsets.add(offset)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
if (offsets.isEmpty()) {
|
||||
return -1
|
||||
}
|
||||
|
||||
if (skipCount >= offsets.size) {
|
||||
return offsets.lastInt()
|
||||
} else {
|
||||
val offsetIterator: IntIterator = offsets.iterator()
|
||||
skip(offsetIterator, skipCount)
|
||||
return offsetIterator.nextInt()
|
||||
}
|
||||
}
|
||||
|
||||
private fun skip(iterator: IntIterator, n: Int) {
|
||||
require(n >= 0) { "Argument must be nonnegative: $n" }
|
||||
var i = n
|
||||
while (i-- != 0 && iterator.hasNext()) iterator.nextInt()
|
||||
}
|
||||
|
||||
class CountPosition(val count: Int, val position: Int)
|
||||
|
||||
private val logger = logger<SearchLogger>()
|
||||
private class SearchLogger
|
@ -210,12 +210,28 @@ private fun findClosestMatch(
|
||||
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 sortedResults = if (forwards) {
|
||||
results.sortedBy { it.startOffset }
|
||||
} else {
|
||||
results.sortedByDescending { it.startOffset }
|
||||
}
|
||||
val toDrop = (nextIndex + count - 1).let { if (injector.globalOptions().wrapscan) it % results.size else it }
|
||||
return sortedResults.drop(toDrop).firstOrNull()?.startOffset ?: -1
|
||||
val closestIndex = if (forwards) {
|
||||
sortedResults.indexOfFirst { it.startOffset > initialOffset }
|
||||
}
|
||||
else {
|
||||
sortedResults.indexOfFirst { it.startOffset < initialOffset }
|
||||
}
|
||||
|
||||
if (closestIndex == -1 && !injector.globalOptions().wrapscan) {
|
||||
return -1
|
||||
}
|
||||
|
||||
val nextIndex = closestIndex.coerceAtLeast(0) + (count - 1)
|
||||
if (nextIndex >= sortedResults.size && !injector.globalOptions().wrapscan) {
|
||||
return -1
|
||||
}
|
||||
|
||||
return sortedResults[nextIndex % results.size].startOffset
|
||||
}
|
||||
|
||||
internal fun highlightSearchResults(editor: Editor, pattern: String, results: List<TextRange>, currentMatchOffset: Int) {
|
||||
|
@ -8,31 +8,17 @@
|
||||
package com.maddyhome.idea.vim.helper
|
||||
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.util.*
|
||||
import java.util.stream.Collectors
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
/**
|
||||
* COMPATIBILITY-LAYER: Created a helper class
|
||||
* Please see: https://jb.gg/zo8n0r
|
||||
*/
|
||||
public object StringHelper {
|
||||
@JvmStatic
|
||||
@Deprecated("Use injector.parser.parseKeys(string)",
|
||||
ReplaceWith("injector.parser.parseKeys(string)", "com.maddyhome.idea.vim.api.injector")
|
||||
)
|
||||
public fun parseKeys(string: String): List<KeyStroke> {
|
||||
return injector.parser.parseKeys(string)
|
||||
}
|
||||
|
||||
object StringHelper {
|
||||
@JvmStatic
|
||||
@Deprecated("Use injector.parser.parseKeys(string)")
|
||||
public fun parseKeys(vararg string: String): List<KeyStroke> {
|
||||
@ApiStatus.ScheduledForRemoval
|
||||
fun parseKeys(vararg string: String): List<KeyStroke> {
|
||||
return Arrays.stream(string).flatMap { o: String -> injector.parser.parseKeys(o).stream() }
|
||||
.collect(Collectors.toList())
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Deprecated("Use key.isCloseKeyStroke()", ReplaceWith("key.isCloseKeyStroke()"))
|
||||
public fun isCloseKeyStroke(key: KeyStroke): Boolean = key.isCloseKeyStroke()
|
||||
}
|
||||
|
@ -12,14 +12,14 @@ import com.intellij.openapi.editor.Editor
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
// Do not remove until it's used in EasyMotion plugin in tests
|
||||
public class TestInputModel private constructor() {
|
||||
class TestInputModel private constructor() {
|
||||
private val myKeyStrokes: MutableList<KeyStroke> = Lists.newArrayList()
|
||||
public fun setKeyStrokes(keyStrokes: List<KeyStroke>) {
|
||||
fun setKeyStrokes(keyStrokes: List<KeyStroke>) {
|
||||
myKeyStrokes.clear()
|
||||
myKeyStrokes.addAll(keyStrokes)
|
||||
}
|
||||
|
||||
public fun nextKeyStroke(): KeyStroke? {
|
||||
fun nextKeyStroke(): KeyStroke? {
|
||||
// Return key from the unfinished mapping
|
||||
/*
|
||||
MappingStack mappingStack = KeyHandler.getInstance().getMappingStack();
|
||||
@ -34,9 +34,9 @@ if (mappingStack.hasStroke()) {
|
||||
}
|
||||
}
|
||||
|
||||
public companion object {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
public fun getInstance(editor: Editor): TestInputModel {
|
||||
fun getInstance(editor: Editor): TestInputModel {
|
||||
var model = editor.vimTestInputModel
|
||||
if (model == null) {
|
||||
model = TestInputModel()
|
||||
|
@ -10,8 +10,13 @@
|
||||
|
||||
package com.maddyhome.idea.vim.helper
|
||||
|
||||
import com.intellij.ide.ui.UISettings
|
||||
import com.intellij.ide.ui.UISettingsUtils
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.EditorKind
|
||||
import com.intellij.openapi.editor.colors.EditorColorsManager
|
||||
import com.intellij.openapi.editor.impl.EditorImpl
|
||||
import com.intellij.openapi.wm.IdeFocusManager
|
||||
import java.awt.Font
|
||||
import javax.swing.JComponent
|
||||
@ -32,11 +37,21 @@ internal fun runAfterGotFocus(runnable: Runnable) {
|
||||
IdeFocusManager.findInstance().doWhenFocusSettlesDown(runnable, ModalityState.defaultModalityState())
|
||||
}
|
||||
|
||||
internal fun selectFont(forStr: String): Font {
|
||||
val scheme = EditorColorsManager.getInstance().globalScheme
|
||||
internal fun selectEditorFont(editor: Editor?, forText: String): Font {
|
||||
val fontSize = when {
|
||||
editor is EditorImpl -> editor.fontSize2D
|
||||
UISettings.getInstance().presentationMode -> UISettingsUtils.getInstance().presentationModeFontSize
|
||||
editor?.editorKind == EditorKind.CONSOLE -> UISettingsUtils.getInstance().scaledConsoleFontSize
|
||||
else -> UISettingsUtils.getInstance().scaledEditorFontSize
|
||||
}
|
||||
|
||||
val fontName = scheme.fontPreferences.realFontFamilies.firstOrNull {
|
||||
Font(it, Font.PLAIN, scheme.editorFontSize).canDisplayUpTo(forStr) == -1
|
||||
} ?: return Font(scheme.editorFontName, Font.PLAIN, scheme.editorFontSize)
|
||||
return Font(fontName, Font.PLAIN, scheme.editorFontSize)
|
||||
val scheme = EditorColorsManager.getInstance().globalScheme
|
||||
scheme.fontPreferences.realFontFamilies.forEach { fontName ->
|
||||
val font = Font(fontName, Font.PLAIN, scheme.editorFontSize)
|
||||
if (font.canDisplayUpTo(forText) == -1) {
|
||||
return font.deriveFont(fontSize)
|
||||
}
|
||||
}
|
||||
|
||||
return Font(scheme.editorFontName, Font.PLAIN, scheme.editorFontSize).deriveFont(fontSize)
|
||||
}
|
||||
|
@ -13,15 +13,22 @@ import com.intellij.openapi.actionSystem.PlatformDataKeys
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.openapi.command.undo.UndoManager
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.fileEditor.TextEditor
|
||||
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimCaret
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.common.ChangesListener
|
||||
import com.maddyhome.idea.vim.newapi.IjVimCaret
|
||||
import com.maddyhome.idea.vim.common.InsertSequence
|
||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.state.mode.inVisualMode
|
||||
import com.maddyhome.idea.vim.undo.UndoRedoBase
|
||||
|
||||
/**
|
||||
@ -29,6 +36,10 @@ import com.maddyhome.idea.vim.undo.UndoRedoBase
|
||||
*/
|
||||
@Service
|
||||
internal class UndoRedoHelper : UndoRedoBase() {
|
||||
companion object {
|
||||
private val logger = logger<UndoRedoHelper>()
|
||||
}
|
||||
|
||||
override fun undo(editor: VimEditor, context: ExecutionContext): Boolean {
|
||||
val ijContext = context.context as DataContext
|
||||
val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
|
||||
@ -56,19 +67,21 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
||||
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
|
||||
editor.runWithChangeTracking {
|
||||
undoManager.undo(fileEditor)
|
||||
|
||||
// We execute undo one more time if the previous one just restored selection
|
||||
if (!hasChanges && hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
|
||||
undoManager.undo(fileEditor)
|
||||
}
|
||||
}
|
||||
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
removeSelections(editor)
|
||||
restoreVisualMode(editor)
|
||||
}
|
||||
} else {
|
||||
runWithBooleanRegistryOption("ide.undo.transparent.caret.movement", true) {
|
||||
undoManager.undo(fileEditor)
|
||||
var nextUndoNanoTime = undoManager.getNextUndoNanoTime(fileEditor)
|
||||
val insertInfo = (editor.primaryCaret() as IjVimCaret).getInsertSequenceForTime(nextUndoNanoTime)
|
||||
if (insertInfo == null || undoManager.isNextUndoAskConfirmation(fileEditor)) {
|
||||
undoManager.undo(fileEditor)
|
||||
} else {
|
||||
while (insertInfo.contains(nextUndoNanoTime)) {
|
||||
undoManager.undo(fileEditor)
|
||||
nextUndoNanoTime = undoManager.getNextUndoNanoTime(fileEditor)
|
||||
if (undoManager.isNextUndoAskConfirmation(fileEditor)) break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
@ -80,7 +93,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
||||
private fun hasSelection(editor: VimEditor): Boolean {
|
||||
return editor.primaryCaret().ij.hasSelection()
|
||||
}
|
||||
|
||||
|
||||
override fun redo(editor: VimEditor, context: ExecutionContext): Boolean {
|
||||
val ijContext = context.context as DataContext
|
||||
val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
|
||||
@ -94,6 +107,22 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun startInsertSequence(caret: VimCaret, startOffset: Int, startNanoTime: Long) {
|
||||
(caret as IjVimCaret).startInsertSequence(startOffset, startNanoTime)
|
||||
}
|
||||
|
||||
override fun endInsertSequence(caret: VimCaret, endOffset: Int, endNanoTime: Long) {
|
||||
(caret as IjVimCaret).endInsertSequence(endOffset, endNanoTime)
|
||||
}
|
||||
|
||||
override fun abandonCurrentInsertSequence(caret: VimCaret) {
|
||||
(caret as IjVimCaret).abandonCurrentInsertSequece()
|
||||
}
|
||||
|
||||
override fun getInsertSequence(caret: VimCaret, nanoTime: Long): InsertSequence? {
|
||||
return (caret as IjVimCaret).getInsertSequenceForTime(nanoTime)
|
||||
}
|
||||
|
||||
private fun performRedo(
|
||||
undoManager: UndoManager,
|
||||
fileEditor: TextEditor,
|
||||
@ -119,10 +148,23 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
||||
}
|
||||
} else {
|
||||
runWithBooleanRegistryOption("ide.undo.transparent.caret.movement", true) {
|
||||
undoManager.redo(fileEditor)
|
||||
var nextRedoNanoTime = undoManager.getNextRedoNanoTime(fileEditor)
|
||||
val insertInfo = (editor.primaryCaret() as IjVimCaret).getInsertSequenceForTime(nextRedoNanoTime)
|
||||
if (insertInfo == null || undoManager.isNextRedoAskConfirmation(fileEditor)) {
|
||||
undoManager.redo(fileEditor)
|
||||
} else {
|
||||
while (insertInfo.contains(nextRedoNanoTime)) {
|
||||
undoManager.redo(fileEditor)
|
||||
nextRedoNanoTime = undoManager.getNextRedoNanoTime(fileEditor)
|
||||
if (undoManager.isNextRedoAskConfirmation(fileEditor)) break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
// TODO all the carets should be moved to their corresponding insertInfo.startOffset
|
||||
// It's a bit tricky because the offsets where calculated before text in input sequence was inserted
|
||||
// So it will require adjusting offsets to proper one in multicaret case
|
||||
removeSelections(editor)
|
||||
}
|
||||
}
|
||||
@ -172,4 +214,21 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
||||
val hasChanges: Boolean
|
||||
get() = changeListener.hasChanged || initialPath != editor.getPath()
|
||||
}
|
||||
|
||||
private fun restoreVisualMode(editor: VimEditor) {
|
||||
if (!editor.inVisualMode && editor.getSelectionModel().hasSelection()) {
|
||||
val detectedMode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor)
|
||||
|
||||
// Visual block selection is restored into multiple carets, so multi-carets that form a block are always
|
||||
// identified as visual block mode, leading to false positives.
|
||||
// Since I use visual block mode much less often than multi-carets, this is a judgment call to never restore
|
||||
// visual block mode.
|
||||
val wantedMode = if (detectedMode == SelectionType.BLOCK_WISE)
|
||||
SelectionType.CHARACTER_WISE
|
||||
else
|
||||
detectedMode
|
||||
|
||||
VimPlugin.getVisualMotion().enterVisualMode(editor, wantedMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,9 +18,9 @@ import com.intellij.openapi.editor.VisualPosition
|
||||
import com.intellij.openapi.editor.markup.RangeHighlighter
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.UserDataHolder
|
||||
import com.maddyhome.idea.vim.api.CaretRegisterStorageBase
|
||||
import com.maddyhome.idea.vim.api.LocalMarkStorage
|
||||
import com.maddyhome.idea.vim.api.SelectionInfo
|
||||
import com.maddyhome.idea.vim.common.InsertSequence
|
||||
import com.maddyhome.idea.vim.ex.ExOutputModel
|
||||
import com.maddyhome.idea.vim.group.visual.VisualChange
|
||||
import com.maddyhome.idea.vim.group.visual.vimLeadSelectionOffset
|
||||
@ -44,7 +44,7 @@ import kotlin.reflect.KProperty
|
||||
/**
|
||||
* Caret's offset when entering visual mode
|
||||
*/
|
||||
public var Caret.vimSelectionStart: Int
|
||||
var Caret.vimSelectionStart: Int
|
||||
get() {
|
||||
val selectionStart = _vimSelectionStart
|
||||
if (selectionStart == null) {
|
||||
@ -95,7 +95,6 @@ internal var Caret.vimInsertStart: RangeMarker by userDataOr {
|
||||
}
|
||||
|
||||
// TODO: Data could be lost during visual block motion
|
||||
internal var Caret.registerStorage: CaretRegisterStorageBase? by userDataCaretToEditor()
|
||||
internal var Caret.markStorage: LocalMarkStorage? by userDataCaretToEditor()
|
||||
internal var Caret.lastSelectionInfo: SelectionInfo? by userDataCaretToEditor()
|
||||
|
||||
@ -128,6 +127,9 @@ internal var Editor.vimTestInputModel: TestInputModel? by userData()
|
||||
|
||||
internal var Editor.vimChangeActionSwitchMode: Mode? by userData()
|
||||
|
||||
internal var Caret.currentInsert: InsertSequence? by userData()
|
||||
internal val Caret.insertHistory: MutableList<InsertSequence> by userDataOr { mutableListOf() }
|
||||
|
||||
/**
|
||||
* Function for delegated properties.
|
||||
* The property will be delegated to UserData and has nullable type.
|
||||
|
@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright 2003-2023 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.helper
|
||||
|
||||
import com.intellij.ide.plugins.StandalonePluginUpdateChecker
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.group.NotificationService
|
||||
import com.maddyhome.idea.vim.icons.VimIcons
|
||||
|
||||
@Service(Service.Level.APP)
|
||||
internal class VimStandalonePluginUpdateChecker : StandalonePluginUpdateChecker(
|
||||
VimPlugin.getPluginId(),
|
||||
updateTimestampProperty = PROPERTY_NAME,
|
||||
NotificationService.IDEAVIM_STICKY_GROUP,
|
||||
VimIcons.IDEAVIM,
|
||||
) {
|
||||
|
||||
override fun skipUpdateCheck(): Boolean = VimPlugin.isNotEnabled() || "dev" in VimPlugin.getVersion()
|
||||
|
||||
companion object {
|
||||
private const val PROPERTY_NAME = "ideavim.statistics.timestamp"
|
||||
val instance: VimStandalonePluginUpdateChecker = service()
|
||||
}
|
||||
}
|
@ -28,6 +28,7 @@ import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||
import com.intellij.openapi.actionSystem.ex.AnActionListener
|
||||
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.impl.ScrollingModelImpl
|
||||
import com.intellij.openapi.project.DumbAwareToggleAction
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
@ -57,6 +58,7 @@ internal object IdeaSpecifics {
|
||||
private val surrounderAction =
|
||||
"com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction"
|
||||
private var editor: Editor? = null
|
||||
private var caretOffset = -1
|
||||
private var completionPrevDocumentLength: Int? = null
|
||||
private var completionPrevDocumentOffset: Int? = null
|
||||
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
|
||||
@ -65,6 +67,7 @@ internal object IdeaSpecifics {
|
||||
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
|
||||
if (hostEditor != null) {
|
||||
editor = hostEditor
|
||||
caretOffset = hostEditor.caretModel.offset
|
||||
}
|
||||
|
||||
val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction
|
||||
@ -96,43 +99,58 @@ internal object IdeaSpecifics {
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
|
||||
val editor = editor
|
||||
if (editor != null && action is ChooseItemAction && injector.registerGroup.isRecording) {
|
||||
val prevDocumentLength = completionPrevDocumentLength
|
||||
val prevDocumentOffset = completionPrevDocumentOffset
|
||||
if (editor != null) {
|
||||
if (action is ChooseItemAction && injector.registerGroup.isRecording) {
|
||||
val prevDocumentLength = completionPrevDocumentLength
|
||||
val prevDocumentOffset = completionPrevDocumentOffset
|
||||
|
||||
if (prevDocumentLength != null && prevDocumentOffset != null) {
|
||||
val register = VimPlugin.getRegister()
|
||||
val addedTextLength = editor.document.textLength - prevDocumentLength
|
||||
val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
|
||||
val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
|
||||
if (prevDocumentLength != null && prevDocumentOffset != null) {
|
||||
val register = VimPlugin.getRegister()
|
||||
val addedTextLength = editor.document.textLength - prevDocumentLength
|
||||
val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
|
||||
val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
|
||||
|
||||
register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength)))
|
||||
repeat(caretShift.coerceAtLeast(0)) {
|
||||
register.recordKeyStroke(leftArrow)
|
||||
register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength)))
|
||||
repeat(caretShift.coerceAtLeast(0)) {
|
||||
register.recordKeyStroke(leftArrow)
|
||||
}
|
||||
}
|
||||
|
||||
this.completionPrevDocumentLength = null
|
||||
this.completionPrevDocumentOffset = null
|
||||
}
|
||||
|
||||
//region Enter insert mode after surround with if
|
||||
if (surrounderAction == action.javaClass.name && surrounderItems.any {
|
||||
action.templatePresentation.text.endsWith(
|
||||
it,
|
||||
)
|
||||
}
|
||||
) {
|
||||
editor?.let {
|
||||
val commandState = it.vim.vimStateMachine
|
||||
it.vim.mode = Mode.NORMAL()
|
||||
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
|
||||
KeyHandler.getInstance().reset(it.vim)
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
this.completionPrevDocumentLength = null
|
||||
this.completionPrevDocumentOffset = null
|
||||
}
|
||||
|
||||
//region Enter insert mode after surround with if
|
||||
if (surrounderAction == action.javaClass.name && surrounderItems.any {
|
||||
action.templatePresentation.text.endsWith(
|
||||
it,
|
||||
)
|
||||
}
|
||||
) {
|
||||
editor?.let {
|
||||
val commandState = it.vim.vimStateMachine
|
||||
it.vim.mode = Mode.NORMAL()
|
||||
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
|
||||
KeyHandler.getInstance().reset(it.vim)
|
||||
if (caretOffset != -1 && caretOffset != editor.caretModel.offset) {
|
||||
val scrollModel = editor.scrollingModel as ScrollingModelImpl
|
||||
if (scrollModel.isScrollingNow) {
|
||||
val v = scrollModel.verticalScrollOffset
|
||||
val h = scrollModel.horizontalScrollOffset
|
||||
scrollModel.finishAnimation()
|
||||
scrollModel.scroll(h, v)
|
||||
scrollModel.finishAnimation()
|
||||
}
|
||||
injector.scroll.scrollCaretIntoView(editor.vim)
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
this.editor = null
|
||||
this.caretOffset = -1
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,6 @@ package com.maddyhome.idea.vim.listener
|
||||
|
||||
import com.intellij.openapi.editor.Editor
|
||||
|
||||
public interface VimInsertListener {
|
||||
public fun insertModeStarted(editor: Editor)
|
||||
interface VimInsertListener {
|
||||
fun insertModeStarted(editor: Editor)
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ import com.intellij.openapi.editor.ex.DocumentEx
|
||||
import com.intellij.openapi.editor.ex.EditorEventMulticasterEx
|
||||
import com.intellij.openapi.editor.ex.FocusChangeListener
|
||||
import com.intellij.openapi.editor.impl.EditorComponentImpl
|
||||
import com.intellij.openapi.editor.impl.EditorImpl
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
|
||||
import com.intellij.openapi.fileEditor.FileEditorManagerListener
|
||||
@ -45,11 +46,14 @@ import com.intellij.openapi.fileEditor.ex.FileEditorWithProvider
|
||||
import com.intellij.openapi.fileEditor.impl.EditorComposite
|
||||
import com.intellij.openapi.fileEditor.impl.EditorWindow
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
import com.intellij.openapi.rd.createLifetime
|
||||
import com.intellij.openapi.rd.createNestedDisposable
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.removeUserData
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.util.ExceptionUtil
|
||||
import com.jetbrains.rd.util.lifetime.Lifetime
|
||||
import com.maddyhome.idea.vim.EventFacade
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.KeyHandlerStateResetter
|
||||
@ -71,7 +75,6 @@ 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
|
||||
import com.maddyhome.idea.vim.group.SearchGroup
|
||||
import com.maddyhome.idea.vim.group.VimMarkServiceImpl
|
||||
import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl
|
||||
import com.maddyhome.idea.vim.group.visual.VimVisualTimer
|
||||
@ -81,7 +84,6 @@ import com.maddyhome.idea.vim.handler.keyCheckRequests
|
||||
import com.maddyhome.idea.vim.helper.CaretVisualAttributesListener
|
||||
import com.maddyhome.idea.vim.helper.GuicursorChangeListener
|
||||
import com.maddyhome.idea.vim.helper.StrictMode
|
||||
import com.maddyhome.idea.vim.helper.VimStandalonePluginUpdateChecker
|
||||
import com.maddyhome.idea.vim.helper.exitSelectMode
|
||||
import com.maddyhome.idea.vim.helper.exitVisualMode
|
||||
import com.maddyhome.idea.vim.helper.forceBarCursor
|
||||
@ -91,19 +93,21 @@ import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
|
||||
import com.maddyhome.idea.vim.helper.resetVimLastColumn
|
||||
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
|
||||
import com.maddyhome.idea.vim.helper.vimDisabled
|
||||
import com.maddyhome.idea.vim.helper.vimInitialised
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.InsertTimeRecorder
|
||||
import com.maddyhome.idea.vim.newapi.IjVimSearchGroup
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.inSelectMode
|
||||
import com.maddyhome.idea.vim.state.mode.selectionType
|
||||
import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener
|
||||
import com.maddyhome.idea.vim.ui.ShowCmdWidgetUpdater
|
||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
|
||||
import com.maddyhome.idea.vim.ui.widgets.macro.MacroWidgetListener
|
||||
import com.maddyhome.idea.vim.ui.widgets.macro.macroWidgetOptionListener
|
||||
import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetListener
|
||||
import com.maddyhome.idea.vim.ui.widgets.mode.modeWidgetOptionListener
|
||||
import com.maddyhome.idea.vim.vimDisposable
|
||||
import java.awt.event.MouseAdapter
|
||||
import java.awt.event.MouseEvent
|
||||
import javax.swing.SwingUtilities
|
||||
@ -127,8 +131,23 @@ import javax.swing.SwingUtilities
|
||||
* Make sure the selected editor isn't the new editor, which can happen if there are no other editors open.
|
||||
*/
|
||||
private fun getOpeningEditor(newEditor: Editor) = newEditor.project?.let { project ->
|
||||
FileEditorManager.getInstance(project).selectedTextEditor?.takeUnless { it == newEditor }
|
||||
// Some TextEditor implementations create a dummy Editor instance on demand, e.g., while downloading a file to edit
|
||||
// (see BaseRemoteFileEditor). This can cause recursion if the newly opened/created TextEditor is also the currently
|
||||
// selected TextEditor, because we will be notified of the new dummy Editor before it has finished initialisation, and
|
||||
// try to get its opening editor, causing a new dummy Editor to be created and notifications sent, and so on.
|
||||
// This was reported for 232 and 233 (see VIM-3066), but I can't recreate in 241. The callstack looks different, now
|
||||
// using coroutines, so it's possible the deadlock has been broken. However, it's sensible to leave the recursion
|
||||
// guard in.
|
||||
if (openingEditorRecursionGuard) return null
|
||||
openingEditorRecursionGuard = true
|
||||
try {
|
||||
FileEditorManager.getInstance(project).selectedTextEditor?.takeUnless { it == newEditor }
|
||||
}
|
||||
finally {
|
||||
openingEditorRecursionGuard = false
|
||||
}
|
||||
}
|
||||
private var openingEditorRecursionGuard = false
|
||||
|
||||
internal object VimListenerManager {
|
||||
|
||||
@ -147,6 +166,9 @@ internal object VimListenerManager {
|
||||
injector.listenersNotifier.isReplaceCharListeners.add(caretVisualAttributesListener)
|
||||
caretVisualAttributesListener.updateAllEditorsCaretsVisual()
|
||||
|
||||
val insertTimeRecorder = InsertTimeRecorder()
|
||||
injector.listenersNotifier.modeChangeListeners.add(insertTimeRecorder)
|
||||
|
||||
val modeWidgetListener = ModeWidgetListener()
|
||||
injector.listenersNotifier.modeChangeListeners.add(modeWidgetListener)
|
||||
injector.listenersNotifier.myEditorListeners.add(modeWidgetListener)
|
||||
@ -179,7 +201,7 @@ internal object VimListenerManager {
|
||||
}
|
||||
|
||||
val optionGroup = VimPlugin.getOptionGroup()
|
||||
optionGroup.addEffectiveOptionValueChangeListener(IjOptions.number, EditorGroup.NumberChangeListener.INSTANCE)
|
||||
optionGroup.addEffectiveOptionValueChangeListener(Options.number, EditorGroup.NumberChangeListener.INSTANCE)
|
||||
optionGroup.addEffectiveOptionValueChangeListener(IjOptions.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
|
||||
optionGroup.addEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
|
||||
optionGroup.addEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
|
||||
@ -211,7 +233,7 @@ internal object VimListenerManager {
|
||||
EventFacade.getInstance().restoreTypedActionHandler()
|
||||
|
||||
val optionGroup = VimPlugin.getOptionGroup()
|
||||
optionGroup.removeEffectiveOptionValueChangeListener(IjOptions.number, EditorGroup.NumberChangeListener.INSTANCE)
|
||||
optionGroup.removeEffectiveOptionValueChangeListener(Options.number, EditorGroup.NumberChangeListener.INSTANCE)
|
||||
optionGroup.removeEffectiveOptionValueChangeListener(IjOptions.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
|
||||
optionGroup.removeEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
|
||||
optionGroup.removeGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
|
||||
@ -266,12 +288,15 @@ internal object VimListenerManager {
|
||||
// TODO: If the user changes the 'ideavimsupport' option, existing editors won't be initialised
|
||||
if (vimDisabled(editor)) return
|
||||
|
||||
// As I understand, there is no need to pass a disposable that also disposes on editor close
|
||||
// because all editor resources will be garbage collected anyway on editor close
|
||||
// Note that this uses the plugin's main disposable, rather than VimPlugin.onOffDisposable, because we don't need
|
||||
// to - we explicitly call VimListenerManager.removeAll from VimPlugin.turnOffPlugin, and this disposes each
|
||||
// editor's disposable individually.
|
||||
val disposable = editor.project?.vimDisposable ?: return
|
||||
val pluginLifetime = VimPlugin.getInstance().createLifetime()
|
||||
val editorLifetime = (editor as EditorImpl).disposable.createLifetime()
|
||||
val disposable =
|
||||
Lifetime.intersect(pluginLifetime, editorLifetime).createNestedDisposable("MyLifetimedDisposable")
|
||||
|
||||
// Protect against double initialisation
|
||||
if (editor.getUserData(editorListenersDisposableKey) != null) {
|
||||
return
|
||||
}
|
||||
|
||||
val listenersDisposable = Disposer.newDisposable(disposable)
|
||||
editor.putUserData(editorListenersDisposableKey, listenersDisposable)
|
||||
@ -342,13 +367,13 @@ internal object VimListenerManager {
|
||||
private object VimDocumentListener : DocumentListener {
|
||||
override fun beforeDocumentChange(event: DocumentEvent) {
|
||||
VimMarkServiceImpl.MarkUpdater.beforeDocumentChange(event)
|
||||
SearchGroup.DocumentSearchListener.INSTANCE.beforeDocumentChange(event)
|
||||
IjVimSearchGroup.DocumentSearchListener.INSTANCE.beforeDocumentChange(event)
|
||||
IjVimRedrawService.RedrawListener.beforeDocumentChange(event)
|
||||
}
|
||||
|
||||
override fun documentChanged(event: DocumentEvent) {
|
||||
VimMarkServiceImpl.MarkUpdater.documentChanged(event)
|
||||
SearchGroup.DocumentSearchListener.INSTANCE.documentChanged(event)
|
||||
IjVimSearchGroup.DocumentSearchListener.INSTANCE.documentChanged(event)
|
||||
IjVimRedrawService.RedrawListener.documentChanged(event)
|
||||
}
|
||||
}
|
||||
@ -362,7 +387,16 @@ internal object VimListenerManager {
|
||||
override fun selectionChanged(event: FileEditorManagerEvent) {
|
||||
// We can't rely on being passed a non-null editor, so check for Code With Me scenarios explicitly
|
||||
if (VimPlugin.isNotEnabled() || !ClientId.isCurrentlyUnderLocalId) return
|
||||
|
||||
|
||||
val newEditor = event.newEditor
|
||||
if (newEditor is TextEditor) {
|
||||
val editor = newEditor.editor
|
||||
if (editor.isInsertMode) {
|
||||
editor.vim.mode = Mode.NORMAL()
|
||||
KeyHandler.getInstance().reset(editor.vim)
|
||||
}
|
||||
}
|
||||
|
||||
MotionGroup.fileEditorManagerSelectionChangedCallback(event)
|
||||
FileGroup.fileEditorManagerSelectionChangedCallback(event)
|
||||
VimPlugin.getSearch().fileEditorManagerSelectionChangedCallback(event)
|
||||
@ -434,8 +468,6 @@ internal object VimListenerManager {
|
||||
|
||||
event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused))
|
||||
}
|
||||
|
||||
VimStandalonePluginUpdateChecker.instance.pluginUsed()
|
||||
}
|
||||
|
||||
override fun editorReleased(event: EditorFactoryEvent) {
|
||||
@ -465,6 +497,9 @@ internal object VimListenerManager {
|
||||
(it.fileEditor as? TextEditor)?.editor?.let { editor ->
|
||||
if (vimDisabled(editor)) return@let
|
||||
|
||||
// Protect against double initialisation, in case the editor was already initialised in editorCreated
|
||||
if (editor.vimInitialised) return@let
|
||||
|
||||
val openingEditor = editor.removeUserData(openingEditorKey)
|
||||
val owningEditorWindow = getOwningEditorWindow(editor)
|
||||
val isInSameSplit = owningEditorWindow == openingEditor?.owningEditorWindow
|
||||
@ -708,13 +743,13 @@ internal object VimListenerManager {
|
||||
logger.debug("Mouse clicked")
|
||||
|
||||
if (event.area == EditorMouseEventArea.EDITING_AREA) {
|
||||
VimPlugin.getMotion()
|
||||
val editor = event.editor
|
||||
if (ExEntryPanel.getInstance().isActive) {
|
||||
VimPlugin.getProcess().cancelExEntry(editor.vim, false)
|
||||
val commandLine = injector.commandLine.getActiveCommandLine()
|
||||
if (commandLine != null) {
|
||||
injector.processGroup.cancelExEntry(editor.vim, false)
|
||||
}
|
||||
|
||||
ExOutputModel.getInstance(editor).clear()
|
||||
ExOutputModel.tryGetInstance(editor)?.close()
|
||||
|
||||
val caretModel = editor.caretModel
|
||||
if (editor.vim.mode.selectionType != null) {
|
||||
@ -740,12 +775,12 @@ internal object VimListenerManager {
|
||||
event.area != EditorMouseEventArea.FOLDING_OUTLINE_AREA &&
|
||||
event.mouseEvent.button != MouseEvent.BUTTON3
|
||||
) {
|
||||
VimPlugin.getMotion()
|
||||
if (ExEntryPanel.getInstance().isActive) {
|
||||
VimPlugin.getProcess().cancelExEntry(event.editor.vim, false)
|
||||
val commandLine = injector.commandLine.getActiveCommandLine()
|
||||
if (commandLine != null) {
|
||||
injector.processGroup.cancelExEntry(event.editor.vim, false)
|
||||
}
|
||||
|
||||
ExOutputModel.getInstance(event.editor).clear()
|
||||
ExOutputModel.getInstance(event.editor).close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ import com.intellij.ide.bookmark.BookmarkType
|
||||
import com.intellij.ide.bookmark.BookmarksManager
|
||||
import com.intellij.ide.bookmark.LineBookmark
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFileManager
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
internal class IntellijMark(bookmark: LineBookmark, override val col: Int, project: Project?) : Mark {
|
||||
@ -24,8 +23,8 @@ internal class IntellijMark(bookmark: LineBookmark, override val col: Int, proje
|
||||
get() = getMark()?.line ?: 0
|
||||
override val filepath: String
|
||||
get() = getMark()?.file?.path ?: ""
|
||||
override val protocol: String?
|
||||
get() = getMark()?.file?.let { VirtualFileManager.extractProtocol(it.url) } ?: ""
|
||||
override val protocol: String
|
||||
get() = getMark()?.file?.fileSystem?.protocol ?: ""
|
||||
|
||||
fun clear() {
|
||||
val mark = getMark() ?: return
|
||||
|
@ -24,8 +24,8 @@ internal val runFromVimKey = Key.create<Boolean>("RunFromVim")
|
||||
internal val DataContext.actionStartedFromVim: Boolean
|
||||
get() = (this as? UserDataHolder)?.getUserData(runFromVimKey) ?: false
|
||||
|
||||
public val DataContext.vim: ExecutionContext
|
||||
val DataContext.vim: ExecutionContext
|
||||
get() = IjEditorExecutionContext(this)
|
||||
|
||||
public val ExecutionContext.ij: DataContext
|
||||
val ExecutionContext.ij: DataContext
|
||||
get() = (this as IjEditorExecutionContext).context
|
||||
|
@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright 2003-2023 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.newapi
|
||||
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.maddyhome.idea.vim.api.ExEntryPanel
|
||||
|
||||
@Service
|
||||
internal class IjExEntryPanel : ExEntryPanel {
|
||||
override fun isActive(): Boolean {
|
||||
return com.maddyhome.idea.vim.ui.ex.ExEntryPanel.getInstance().isActive
|
||||
}
|
||||
|
||||
override fun clearCurrentAction() {
|
||||
com.maddyhome.idea.vim.ui.ex.ExEntryPanel.getInstance().entry.clearCurrentAction()
|
||||
}
|
||||
|
||||
override fun setCurrentActionPromptCharacter(char: Char) {
|
||||
com.maddyhome.idea.vim.ui.ex.ExEntryPanel.getInstance().entry.setCurrentActionPromptCharacter(char)
|
||||
}
|
||||
}
|
@ -13,7 +13,6 @@ import com.intellij.openapi.editor.ex.util.EditorUtil
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.ExecutionContextManagerBase
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.helper.EditorDataContext
|
||||
|
||||
@Service
|
||||
internal class IjExecutionContextManager : ExecutionContextManagerBase() {
|
||||
|
@ -16,8 +16,8 @@ internal class IjLiveRange(val marker: RangeMarker) : LiveRange {
|
||||
get() = marker.startOffset
|
||||
}
|
||||
|
||||
public val RangeMarker.vim: LiveRange
|
||||
val RangeMarker.vim: LiveRange
|
||||
get() = IjLiveRange(this)
|
||||
|
||||
public val LiveRange.ij: RangeMarker
|
||||
val LiveRange.ij: RangeMarker
|
||||
get() = (this as IjLiveRange).marker
|
||||
|
@ -31,10 +31,10 @@ internal class IjNativeActionManager : NativeActionManager {
|
||||
}
|
||||
}
|
||||
|
||||
public val AnAction.vim: IjNativeAction
|
||||
val AnAction.vim: IjNativeAction
|
||||
get() = IjNativeAction(this)
|
||||
|
||||
public class IjNativeAction(override val action: AnAction) : NativeAction {
|
||||
class IjNativeAction(override val action: AnAction) : NativeAction {
|
||||
override fun toString(): String {
|
||||
return "IjNativeAction(action=$action)"
|
||||
}
|
||||
|
@ -12,8 +12,6 @@ import com.intellij.openapi.editor.Caret
|
||||
import com.intellij.openapi.editor.LogicalPosition
|
||||
import com.intellij.openapi.editor.VisualPosition
|
||||
import com.maddyhome.idea.vim.api.BufferPosition
|
||||
import com.maddyhome.idea.vim.api.CaretRegisterStorage
|
||||
import com.maddyhome.idea.vim.api.CaretRegisterStorageBase
|
||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
||||
import com.maddyhome.idea.vim.api.LocalMarkStorage
|
||||
import com.maddyhome.idea.vim.api.SelectionInfo
|
||||
@ -21,12 +19,14 @@ import com.maddyhome.idea.vim.api.VimCaret
|
||||
import com.maddyhome.idea.vim.api.VimCaretBase
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.VimVisualPosition
|
||||
import com.maddyhome.idea.vim.common.InsertSequence
|
||||
import com.maddyhome.idea.vim.common.LiveRange
|
||||
import com.maddyhome.idea.vim.group.visual.VisualChange
|
||||
import com.maddyhome.idea.vim.helper.currentInsert
|
||||
import com.maddyhome.idea.vim.helper.insertHistory
|
||||
import com.maddyhome.idea.vim.helper.lastSelectionInfo
|
||||
import com.maddyhome.idea.vim.helper.markStorage
|
||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
|
||||
import com.maddyhome.idea.vim.helper.registerStorage
|
||||
import com.maddyhome.idea.vim.helper.resetVimLastColumn
|
||||
import com.maddyhome.idea.vim.helper.vimInsertStart
|
||||
import com.maddyhome.idea.vim.helper.vimLastColumn
|
||||
@ -38,17 +38,6 @@ import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
|
||||
internal class IjVimCaret(val caret: Caret) : VimCaretBase() {
|
||||
|
||||
override val registerStorage: CaretRegisterStorage
|
||||
get() {
|
||||
var storage = this.caret.registerStorage
|
||||
if (storage == null) {
|
||||
storage = CaretRegisterStorageBase(this)
|
||||
this.caret.registerStorage = storage
|
||||
} else if (storage.caret != this) {
|
||||
storage.caret = this
|
||||
}
|
||||
return storage
|
||||
}
|
||||
override val markStorage: LocalMarkStorage
|
||||
get() {
|
||||
var storage = this.caret.markStorage
|
||||
@ -169,15 +158,44 @@ internal class IjVimCaret(val caret: Caret) : VimCaretBase() {
|
||||
caret.removeSelection()
|
||||
}
|
||||
|
||||
internal fun getInsertSequenceForTime(time: Long): InsertSequence? {
|
||||
val insertHistory = caret.insertHistory
|
||||
for (i in insertHistory.lastIndex downTo 0) {
|
||||
val insertInfo = insertHistory[i]
|
||||
if (time > insertInfo.endNanoTime) return null
|
||||
if (time >= insertInfo.startNanoTime) return insertInfo
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
internal fun startInsertSequence(startOffset: Int, startNanoTime: Long) {
|
||||
if (caret.currentInsert != null) {
|
||||
return
|
||||
}
|
||||
caret.currentInsert = InsertSequence(startOffset, startNanoTime)
|
||||
}
|
||||
|
||||
internal fun endInsertSequence(endInsert: Int, endNanoTime: Long) {
|
||||
val currentInsert = caret.currentInsert ?: return
|
||||
currentInsert.endNanoTime = endNanoTime
|
||||
currentInsert.endOffset = endInsert
|
||||
caret.insertHistory.add(currentInsert)
|
||||
caret.currentInsert = null
|
||||
}
|
||||
|
||||
internal fun abandonCurrentInsertSequece() {
|
||||
caret.currentInsert = null
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean = this.caret == (other as? IjVimCaret)?.caret
|
||||
|
||||
override fun hashCode(): Int = this.caret.hashCode()
|
||||
}
|
||||
|
||||
public val VimCaret.ij: Caret
|
||||
val VimCaret.ij: Caret
|
||||
get() = (this as IjVimCaret).caret
|
||||
public val ImmutableVimCaret.ij: Caret
|
||||
val ImmutableVimCaret.ij: Caret
|
||||
get() = (this as IjVimCaret).caret
|
||||
|
||||
public val Caret.vim: VimCaret
|
||||
val Caret.vim: VimCaret
|
||||
get() = IjVimCaret(this)
|
||||
|
@ -39,6 +39,7 @@ import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.common.IndentConfig
|
||||
import com.maddyhome.idea.vim.common.LiveRange
|
||||
import com.maddyhome.idea.vim.common.ModeChangeListener
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.group.visual.vimSetSystemBlockSelectionSilently
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
@ -117,7 +118,10 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
|
||||
return atPosition
|
||||
}
|
||||
|
||||
override fun insertText(atPosition: Int, text: CharSequence) {
|
||||
override fun insertText(caret: VimCaret, atPosition: Int, text: CharSequence) {
|
||||
if (editor.isInsertMode) {
|
||||
injector.undo.startInsertSequence(caret, atPosition, System.nanoTime())
|
||||
}
|
||||
editor.document.insertString(atPosition, text)
|
||||
}
|
||||
|
||||
@ -147,21 +151,40 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
|
||||
return editor.caretModel.allCarets.map { IjVimCaret(it) }
|
||||
}
|
||||
|
||||
override var isFirstCaret = false
|
||||
override var isReversingCarets = false
|
||||
|
||||
@Suppress("ideavimRunForEachCaret")
|
||||
override fun forEachCaret(action: (VimCaret) -> Unit) {
|
||||
if (editor.vim.inBlockSelection) {
|
||||
action(IjVimCaret(editor.caretModel.primaryCaret))
|
||||
} else {
|
||||
editor.caretModel.runForEachCaret({
|
||||
if (it.isValid) {
|
||||
action(IjVimCaret(it))
|
||||
}
|
||||
}, false)
|
||||
isFirstCaret = true
|
||||
try {
|
||||
editor.caretModel.runForEachCaret({
|
||||
if (it.isValid) {
|
||||
action(IjVimCaret(it))
|
||||
isFirstCaret = false
|
||||
}
|
||||
}, false)
|
||||
} finally {
|
||||
isFirstCaret = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean) {
|
||||
editor.caretModel.runForEachCaret({ action(IjVimCaret(it)) }, reverse)
|
||||
isFirstCaret = true
|
||||
isReversingCarets = reverse
|
||||
try {
|
||||
editor.caretModel.runForEachCaret({
|
||||
action(IjVimCaret(it))
|
||||
isFirstCaret = false
|
||||
}, reverse)
|
||||
} finally {
|
||||
isFirstCaret = false
|
||||
isReversingCarets = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun isInForEachCaretScope(): Boolean {
|
||||
@ -256,7 +279,8 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
|
||||
val vf = EditorHelper.getVirtualFile(editor)
|
||||
return vf?.let {
|
||||
object : VirtualFile {
|
||||
override val path = vf.path
|
||||
override val path: String = vf.path
|
||||
override val protocol: String = vf.fileSystem.protocol
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -494,10 +518,21 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
|
||||
}
|
||||
}
|
||||
|
||||
public val Editor.vim: VimEditor
|
||||
val Editor.vim: VimEditor
|
||||
get() = IjVimEditor(this)
|
||||
public val VimEditor.ij: Editor
|
||||
val VimEditor.ij: Editor
|
||||
get() = (this as IjVimEditor).editor
|
||||
|
||||
public val com.intellij.openapi.util.TextRange.vim: TextRange
|
||||
val com.intellij.openapi.util.TextRange.vim: TextRange
|
||||
get() = TextRange(this.startOffset, this.endOffset)
|
||||
|
||||
internal class InsertTimeRecorder: ModeChangeListener {
|
||||
override fun modeChanged(editor: VimEditor, oldMode: Mode) {
|
||||
editor as IjVimEditor
|
||||
if (oldMode == Mode.INSERT) {
|
||||
val undo = injector.undo
|
||||
val nanoTime = System.nanoTime()
|
||||
editor.forEachCaret { undo.endInsertSequence(it, it.offset, nanoTime) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.textarea.TextComponentEditorImpl
|
||||
import com.maddyhome.idea.vim.api.EngineEditorHelper
|
||||
import com.maddyhome.idea.vim.api.ExEntryPanel
|
||||
import com.maddyhome.idea.vim.api.ExecutionContextManager
|
||||
import com.maddyhome.idea.vim.api.LocalOptionInitialisationScenario
|
||||
import com.maddyhome.idea.vim.api.NativeActionManager
|
||||
@ -45,6 +44,7 @@ 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.VimRegexServiceBase
|
||||
import com.maddyhome.idea.vim.api.VimRegexpService
|
||||
import com.maddyhome.idea.vim.api.VimScrollGroup
|
||||
import com.maddyhome.idea.vim.api.VimSearchGroup
|
||||
@ -71,7 +71,6 @@ import com.maddyhome.idea.vim.group.IjVimOptionGroup
|
||||
import com.maddyhome.idea.vim.group.IjVimPsiService
|
||||
import com.maddyhome.idea.vim.group.MacroGroup
|
||||
import com.maddyhome.idea.vim.group.MotionGroup
|
||||
import com.maddyhome.idea.vim.group.SearchGroup
|
||||
import com.maddyhome.idea.vim.group.TabService
|
||||
import com.maddyhome.idea.vim.group.VimWindowGroup
|
||||
import com.maddyhome.idea.vim.group.WindowGroup
|
||||
@ -90,7 +89,6 @@ import com.maddyhome.idea.vim.state.VimStateMachine
|
||||
import com.maddyhome.idea.vim.ui.VimRcFileState
|
||||
import com.maddyhome.idea.vim.undo.VimUndoRedo
|
||||
import com.maddyhome.idea.vim.vimscript.Executor
|
||||
import com.maddyhome.idea.vim.vimscript.services.PatternService
|
||||
import com.maddyhome.idea.vim.vimscript.services.VariableService
|
||||
import com.maddyhome.idea.vim.yank.VimYankGroup
|
||||
import com.maddyhome.idea.vim.yank.YankGroupBase
|
||||
@ -107,8 +105,6 @@ internal class IjVimInjector : VimInjectorBase() {
|
||||
|
||||
override val actionExecutor: VimActionExecutor
|
||||
get() = service<IjActionExecutor>()
|
||||
override val exEntryPanel: ExEntryPanel
|
||||
get() = service<IjExEntryPanel>()
|
||||
override val exOutputPanel: VimExOutputPanelService
|
||||
get() = object : VimExOutputPanelService {
|
||||
override fun getPanel(editor: VimEditor): VimExOutputPanel {
|
||||
@ -122,7 +118,7 @@ internal class IjVimInjector : VimInjectorBase() {
|
||||
override val tabService: TabService
|
||||
get() = service()
|
||||
override val regexpService: VimRegexpService
|
||||
get() = PatternService
|
||||
get() = VimRegexServiceBase()
|
||||
override val clipboardManager: VimClipboardManager
|
||||
get() = service<IjClipboardManager>()
|
||||
override val searchHelper: VimSearchHelper
|
||||
@ -136,7 +132,7 @@ internal class IjVimInjector : VimInjectorBase() {
|
||||
override val templateManager: VimTemplateManager
|
||||
get() = service<IjTemplateManager>()
|
||||
override val searchGroup: VimSearchGroup
|
||||
get() = service<SearchGroup>()
|
||||
get() = service<IjVimSearchGroup>()
|
||||
override val put: VimPut
|
||||
get() = service<PutGroup>()
|
||||
override val window: VimWindowGroup
|
||||
@ -236,10 +232,10 @@ internal class IjVimInjector : VimInjectorBase() {
|
||||
/**
|
||||
* Convenience function to get the IntelliJ implementation specific global option accessor
|
||||
*/
|
||||
public fun VimInjector.globalIjOptions(): GlobalIjOptions = (this.optionGroup as IjVimOptionGroup).getGlobalIjOptions()
|
||||
fun VimInjector.globalIjOptions(): GlobalIjOptions = (this.optionGroup as IjVimOptionGroup).getGlobalIjOptions()
|
||||
|
||||
/**
|
||||
* Convenience function to get the IntelliJ implementation specific option accessor for the given editor's scope
|
||||
*/
|
||||
public fun VimInjector.ijOptions(editor: VimEditor): EffectiveIjOptions =
|
||||
fun VimInjector.ijOptions(editor: VimEditor): EffectiveIjOptions =
|
||||
(this.optionGroup as IjVimOptionGroup).getEffectiveIjOptions(editor)
|
||||
|
@ -9,8 +9,15 @@
|
||||
package com.maddyhome.idea.vim.newapi
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.PersistentStateComponent
|
||||
import com.intellij.openapi.components.RoamingType
|
||||
import com.intellij.openapi.components.State
|
||||
import com.intellij.openapi.components.Storage
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.event.DocumentEvent
|
||||
import com.intellij.openapi.editor.event.DocumentListener
|
||||
import com.intellij.openapi.editor.markup.RangeHighlighter
|
||||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
|
||||
import com.intellij.openapi.util.Ref
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
@ -20,6 +27,9 @@ import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.VimSearchGroupBase
|
||||
import com.maddyhome.idea.vim.api.globalOptions
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.common.Direction
|
||||
import com.maddyhome.idea.vim.common.Direction.Companion.fromInt
|
||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
import com.maddyhome.idea.vim.helper.MessageHelper
|
||||
import com.maddyhome.idea.vim.helper.TestInputModel.Companion.getInstance
|
||||
import com.maddyhome.idea.vim.helper.addSubstitutionConfirmationHighlight
|
||||
@ -27,13 +37,23 @@ import com.maddyhome.idea.vim.helper.highlightSearchResults
|
||||
import com.maddyhome.idea.vim.helper.isCloseKeyStroke
|
||||
import com.maddyhome.idea.vim.helper.shouldIgnoreCase
|
||||
import com.maddyhome.idea.vim.helper.updateSearchHighlights
|
||||
import com.maddyhome.idea.vim.helper.vimLastHighlighters
|
||||
import com.maddyhome.idea.vim.options.GlobalOptionChangeListener
|
||||
import com.maddyhome.idea.vim.ui.ModalEntry
|
||||
import com.maddyhome.idea.vim.vimscript.model.functions.handlers.SubmatchFunctionHandler
|
||||
import org.jdom.Element
|
||||
import org.jetbrains.annotations.Contract
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
public open class IjVimSearchGroup : VimSearchGroupBase() {
|
||||
@State(
|
||||
name = "VimSearchSettings",
|
||||
storages = [Storage(value = "\$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)]
|
||||
)
|
||||
open class IjVimSearchGroup : VimSearchGroupBase(), PersistentStateComponent<Element> {
|
||||
companion object {
|
||||
private val logger = vimLogger<IjVimSearchGroup>()
|
||||
}
|
||||
|
||||
init {
|
||||
// We use the global option listener instead of the effective listener that gets called for each affected editor
|
||||
@ -117,18 +137,15 @@ public open class IjVimSearchGroup : VimSearchGroupBase() {
|
||||
}
|
||||
} else {
|
||||
// XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for this method
|
||||
val exEntryPanel: com.maddyhome.idea.vim.ui.ex.ExEntryPanel =
|
||||
com.maddyhome.idea.vim.ui.ex.ExEntryPanel.getInstanceWithoutShortcuts()
|
||||
exEntryPanel.activate(
|
||||
editor.ij,
|
||||
(context as IjEditorExecutionContext).context,
|
||||
val exEntryPanel = injector.commandLine.createWithoutShortcuts(
|
||||
editor,
|
||||
context,
|
||||
MessageHelper.message("replace.with.0", match),
|
||||
"",
|
||||
1
|
||||
)
|
||||
caret.moveToOffset(startOffset)
|
||||
ModalEntry.activate(editor, keyStrokeProcessor)
|
||||
exEntryPanel.deactivate(true, false)
|
||||
exEntryPanel.deactivate(refocusOwningEditor = true, resetCaret = false)
|
||||
}
|
||||
return result.get()
|
||||
}
|
||||
@ -178,6 +195,104 @@ public open class IjVimSearchGroup : VimSearchGroupBase() {
|
||||
updateSearchHighlights(false)
|
||||
}
|
||||
|
||||
fun saveData(element: Element) {
|
||||
logger.debug("saveData")
|
||||
val search = Element("search")
|
||||
|
||||
addOptionalTextElement(search, "last-search", lastSearchPattern)
|
||||
addOptionalTextElement(search, "last-substitute", lastSubstitutePattern)
|
||||
addOptionalTextElement(search, "last-offset", lastPatternTrailing)
|
||||
addOptionalTextElement(search, "last-replace", lastReplaceString)
|
||||
addOptionalTextElement(
|
||||
search,
|
||||
"last-pattern",
|
||||
if (lastPatternType == PatternType.SEARCH) lastSearchPattern else lastSubstitutePattern
|
||||
)
|
||||
addOptionalTextElement(search, "last-dir", getLastSearchDirection().toInt().toString())
|
||||
addOptionalTextElement(search, "show-last", showSearchHighlight.toString())
|
||||
|
||||
element.addContent(search)
|
||||
}
|
||||
|
||||
private fun addOptionalTextElement(element: Element, name: String, text: String?) {
|
||||
if (text != null) {
|
||||
element.addContent(VimPlugin.getXML().setSafeXmlText(Element(name), text))
|
||||
}
|
||||
}
|
||||
|
||||
fun readData(element: Element) {
|
||||
logger.debug("readData")
|
||||
val search = element.getChild("search") ?: return
|
||||
|
||||
lastSearchPattern = getSafeChildText(search, "last-search")
|
||||
lastSubstitutePattern = getSafeChildText(search, "last-substitute")
|
||||
lastReplaceString = getSafeChildText(search, "last-replace")
|
||||
lastPatternTrailing = getSafeChildText(search, "last-offset", "")
|
||||
|
||||
val lastPatternText = getSafeChildText(search, "last-pattern")
|
||||
if (lastPatternText == null || lastPatternText == lastSearchPattern) {
|
||||
lastPatternType = PatternType.SEARCH
|
||||
} else {
|
||||
lastPatternType = PatternType.SUBSTITUTE
|
||||
}
|
||||
|
||||
val dir = search.getChild("last-dir")
|
||||
try {
|
||||
lastDirection = fromInt(dir.text.toInt())
|
||||
} catch (e: NumberFormatException) {
|
||||
lastDirection = Direction.FORWARDS
|
||||
}
|
||||
|
||||
val show = search.getChild("show-last")
|
||||
val disableHighlight = injector.globalOptions().viminfo.contains("h")
|
||||
showSearchHighlight = !disableHighlight && show.text.toBoolean()
|
||||
if (logger.isDebug()) {
|
||||
logger.debug("show=" + show + "(" + show.text + ")")
|
||||
logger.debug("showSearchHighlight=$showSearchHighlight")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSafeChildText(element: Element, name: String): String? {
|
||||
val child = element.getChild(name)
|
||||
return if (child != null) VimPlugin.getXML().getSafeXmlText(child) else null
|
||||
}
|
||||
|
||||
private fun getSafeChildText(element: Element, name: String, defaultValue: String): String {
|
||||
val child = element.getChild(name)
|
||||
if (child != null) {
|
||||
val value = VimPlugin.getXML().getSafeXmlText(child)
|
||||
return value ?: defaultValue
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
override fun getState(): Element? {
|
||||
val element = Element("search")
|
||||
saveData(element)
|
||||
return element
|
||||
}
|
||||
|
||||
override fun loadState(state: Element) {
|
||||
readData(state)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates search highlights when the selected editor changes
|
||||
*/
|
||||
fun fileEditorManagerSelectionChangedCallback(@Suppress("unused") event: FileEditorManagerEvent) {
|
||||
updateSearchHighlights(false)
|
||||
}
|
||||
|
||||
fun turnOn() {
|
||||
updateSearchHighlights(false)
|
||||
}
|
||||
|
||||
fun turnOff() {
|
||||
val show = showSearchHighlight
|
||||
clearSearchHighlight()
|
||||
showSearchHighlight = show
|
||||
}
|
||||
|
||||
private class IjSearchHighlight(private val editor: Editor, private val highlighter: RangeHighlighter) :
|
||||
SearchHighlight() {
|
||||
|
||||
@ -185,4 +300,60 @@ public open class IjVimSearchGroup : VimSearchGroupBase() {
|
||||
editor.markupModel.removeHighlighter(highlighter)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes and adds highlights for current search pattern when the document is edited
|
||||
*/
|
||||
class DocumentSearchListener @Contract(pure = true) private constructor() : DocumentListener {
|
||||
override fun documentChanged(event: DocumentEvent) {
|
||||
// Loop over all local editors for the changed document, across all projects, and update search highlights.
|
||||
// Note that the change may have come from a remote guest in Code With Me scenarios (in which case
|
||||
// ClientId.current will be a guest ID), but we don't care - we still need to add/remove highlights for the
|
||||
// changed text. Make sure we only update local editors, though.
|
||||
val document = event.document
|
||||
for (vimEditor in injector.editorGroup.getEditors(IjVimDocument(document))) {
|
||||
val editor = (vimEditor as IjVimEditor).editor
|
||||
var existingHighlighters = editor.vimLastHighlighters ?: continue
|
||||
|
||||
if (logger.isDebug()) {
|
||||
logger.debug("hls=$existingHighlighters")
|
||||
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.
|
||||
val startPosition = editor.offsetToLogicalPosition(event.offset)
|
||||
val endPosition = editor.offsetToLogicalPosition(event.offset + event.newLength)
|
||||
val startLineOffset = document.getLineStartOffset(startPosition.line)
|
||||
val endLineOffset = document.getLineEndOffset(endPosition.line)
|
||||
|
||||
// Remove any highlights that have already been deleted, and remove + clear those that intersect with the change
|
||||
val iter = existingHighlighters.iterator()
|
||||
while (iter.hasNext()) {
|
||||
val highlighter = iter.next()
|
||||
if (!highlighter.isValid) {
|
||||
iter.remove()
|
||||
} else if (highlighter.textRange.intersects(startLineOffset, endLineOffset)) {
|
||||
iter.remove()
|
||||
editor.markupModel.removeHighlighter(highlighter)
|
||||
}
|
||||
}
|
||||
|
||||
(injector.searchGroup as VimSearchGroupBase).highlightSearchLines(editor.vim, startPosition.line, endPosition.line)
|
||||
|
||||
if (logger.isDebug()) {
|
||||
existingHighlighters = editor.vimLastHighlighters!!
|
||||
logger.debug("sl=" + startPosition.line + ", el=" + endPosition.line)
|
||||
logger.debug("hls=$existingHighlighters")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
var INSTANCE: DocumentSearchListener = DocumentSearchListener()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,14 +13,10 @@ import com.intellij.openapi.diagnostic.Logger
|
||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.VimSearchHelperBase
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.helper.PsiHelper
|
||||
import com.maddyhome.idea.vim.helper.SearchHelper
|
||||
import com.maddyhome.idea.vim.helper.SearchOptions
|
||||
import com.maddyhome.idea.vim.helper.findMisspelledWords
|
||||
import it.unimi.dsi.fastutil.ints.IntComparator
|
||||
import it.unimi.dsi.fastutil.ints.IntComparators
|
||||
import java.util.*
|
||||
|
||||
@Service
|
||||
internal class IjVimSearchHelper : VimSearchHelperBase() {
|
||||
@ -38,28 +34,6 @@ internal class IjVimSearchHelper : VimSearchHelperBase() {
|
||||
return PsiHelper.findMethodStart(editor.ij, caret.ij.offset, count)
|
||||
}
|
||||
|
||||
override fun findPattern(
|
||||
editor: VimEditor,
|
||||
pattern: String?,
|
||||
startOffset: Int,
|
||||
count: Int,
|
||||
searchOptions: EnumSet<SearchOptions>?,
|
||||
): TextRange? {
|
||||
return if (injector.globalIjOptions().useNewRegex) super.findPattern(editor, pattern, startOffset, count, searchOptions)
|
||||
else SearchHelper.findPattern(editor.ij, pattern, startOffset, count, searchOptions)
|
||||
}
|
||||
|
||||
override fun findAll(
|
||||
editor: VimEditor,
|
||||
pattern: String,
|
||||
startLine: Int,
|
||||
endLine: Int,
|
||||
ignoreCase: Boolean,
|
||||
): List<TextRange> {
|
||||
return if (injector.globalIjOptions().useNewRegex) super.findAll(editor, pattern, startLine, endLine, ignoreCase)
|
||||
else SearchHelper.findAll(editor.ij, pattern, startLine, endLine, ignoreCase)
|
||||
}
|
||||
|
||||
override fun findMisspelledWord(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Int {
|
||||
val startOffset: Int
|
||||
val endOffset: Int
|
||||
@ -80,6 +54,6 @@ internal class IjVimSearchHelper : VimSearchHelperBase() {
|
||||
}
|
||||
|
||||
// TODO add it to PsiService
|
||||
return SearchHelper.findMisspelledWords(editor.ij, startOffset, endOffset, skipCount, offsetOrdering)
|
||||
return findMisspelledWords(editor.ij, startOffset, endOffset, skipCount, offsetOrdering)
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user