mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2024-11-24 22:42:53 +01:00
Compare commits
69 Commits
a978505530
...
063ed0aa84
Author | SHA1 | Date | |
---|---|---|---|
063ed0aa84 | |||
fccfe0c2ea | |||
45ed79d865 | |||
527eb4cbb3 | |||
e32ac125b2 | |||
4d1d3b697c | |||
3246832528 | |||
6505bfc9aa | |||
0c63890e9d | |||
259958e702 | |||
4916545b53 | |||
b8a9bddfa9 | |||
95688b33c8 | |||
07f44f1c93 | |||
2ce6239ad6 | |||
a0d2d64237 | |||
2e4e8c058b | |||
f464d25844 | |||
acc12c5b17 | |||
0c1bbd5e92 | |||
f330e220ad | |||
|
b86ec03dc4 | ||
|
ae75498f8a | ||
|
9d0b68b0f8 | ||
|
eeb5939e59 | ||
|
ef235a47bf | ||
|
b66da76880 | ||
|
54d6119784 | ||
|
0b8c081425 | ||
|
209052ffa6 | ||
|
fe9a6b5cfb | ||
|
9c0f74369f | ||
|
cd27e5229b | ||
|
472732905c | ||
|
485d9f81cd | ||
|
8cf136ce4c | ||
|
116a8ac9d2 | ||
|
fda310bda6 | ||
|
e55619ea33 | ||
|
b952b20128 | ||
|
62d1f85648 | ||
|
5e3c8c0e92 | ||
|
b58dddf2ff | ||
|
78d351a0b0 | ||
|
61dbc948cc | ||
|
c4d92ebe73 | ||
|
d0cf827638 | ||
|
6a6a92b6b9 | ||
|
9869b8a34e | ||
|
60fbf88322 | ||
|
fae3924062 | ||
|
dc2ce64823 | ||
|
d0d86d9178 | ||
|
f417af6148 | ||
|
2fe2860a09 | ||
|
cb40426976 | ||
|
423ed390a2 | ||
|
7652b16ca6 | ||
|
618a010c15 | ||
|
d44a34ed9b | ||
|
c84fc996db | ||
|
43f232543b | ||
|
3f65d1d99a | ||
|
bfcf706ca7 | ||
|
8c1103c461 | ||
|
ab75ace8db | ||
|
4a58e6a282 | ||
|
ac9e4f69b4 | ||
|
581edba7fd |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
6
.github/workflows/runUiOctopusTests.yml
vendored
6
.github/workflows/runUiOctopusTests.yml
vendored
@ -15,11 +15,7 @@ jobs:
|
||||
distribution: zulu
|
||||
java-version: 17
|
||||
- name: Setup FFmpeg
|
||||
uses: FedericoCarboni/setup-ffmpeg@v3
|
||||
with:
|
||||
# Not strictly necessary, but it may prevent rate limit
|
||||
# errors especially on GitHub-hosted macos machines.
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: brew install ffmpeg
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2.4.2
|
||||
- name: Build Plugin
|
||||
|
6
.github/workflows/runUiPyTests.yml
vendored
6
.github/workflows/runUiPyTests.yml
vendored
@ -18,11 +18,7 @@ jobs:
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Setup FFmpeg
|
||||
uses: FedericoCarboni/setup-ffmpeg@v3
|
||||
with:
|
||||
# Not strictly necessary, but it may prevent rate limit
|
||||
# errors especially on GitHub-hosted macos machines.
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: brew install ffmpeg
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2.4.2
|
||||
- name: Build Plugin
|
||||
|
6
.github/workflows/runUiTests.yml
vendored
6
.github/workflows/runUiTests.yml
vendored
@ -15,11 +15,7 @@ jobs:
|
||||
distribution: zulu
|
||||
java-version: 17
|
||||
- name: Setup FFmpeg
|
||||
uses: FedericoCarboni/setup-ffmpeg@v3
|
||||
with:
|
||||
# Not strictly necessary, but it may prevent rate limit
|
||||
# errors especially on GitHub-hosted macos machines.
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: brew install ffmpeg
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2.4.2
|
||||
- name: Build Plugin
|
||||
|
34
.github/workflows/updateAffectedRate.yml
vendored
34
.github/workflows/updateAffectedRate.yml
vendored
@ -1,34 +0,0 @@
|
||||
# This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created
|
||||
# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle
|
||||
|
||||
# This workflow syncs changes from the docs folder of IdeaVim to the IdeaVim.wiki repository
|
||||
|
||||
name: Update Affected Rate field on YouTrack
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 8 * * *'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'JetBrains/ideavim'
|
||||
|
||||
steps:
|
||||
- name: Fetch origin repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
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 YouTrack
|
||||
run: ./gradlew scripts:updateAffectedRates
|
||||
env:
|
||||
YOUTRACK_TOKEN: ${{ secrets.YOUTRACK_TOKEN }}
|
1
.teamcity/_Self/Project.kt
vendored
1
.teamcity/_Self/Project.kt
vendored
@ -25,6 +25,7 @@ object Project : Project({
|
||||
// Active tests
|
||||
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
||||
buildType(TestingBuildType("2023.3", "<default>", version = "2023.3"))
|
||||
buildType(TestingBuildType("2024.1", "<default>"))
|
||||
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
||||
|
||||
buildType(PropertyBased)
|
||||
|
@ -21,7 +21,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.23-1.0.19")
|
||||
compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.23-1.0.20")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion") {
|
||||
// kotlin stdlib is provided by IJ, so there is no need to include it into the distribution
|
||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib")
|
||||
|
@ -51,11 +51,11 @@ buildscript {
|
||||
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.9.0.202403050737-r")
|
||||
classpath("org.kohsuke:github-api:1.305")
|
||||
|
||||
classpath("io.ktor:ktor-client-core:2.3.9")
|
||||
classpath("io.ktor:ktor-client-cio:2.3.9")
|
||||
classpath("io.ktor:ktor-client-auth:2.3.9")
|
||||
classpath("io.ktor:ktor-client-content-negotiation:2.3.9")
|
||||
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.9")
|
||||
classpath("io.ktor:ktor-client-core:2.3.10")
|
||||
classpath("io.ktor:ktor-client-cio:2.3.10")
|
||||
classpath("io.ktor:ktor-client-auth:2.3.10")
|
||||
classpath("io.ktor:ktor-client-content-negotiation:2.3.10")
|
||||
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.10")
|
||||
|
||||
// This comes from the changelog plugin
|
||||
// classpath("org.jetbrains:markdown:0.3.1")
|
||||
@ -69,11 +69,11 @@ plugins {
|
||||
application
|
||||
id("java-test-fixtures")
|
||||
|
||||
id("org.jetbrains.intellij") version "1.17.2"
|
||||
id("org.jetbrains.intellij") version "1.17.3"
|
||||
id("org.jetbrains.changelog") version "2.2.0"
|
||||
|
||||
id("org.jetbrains.kotlinx.kover") version "0.6.1"
|
||||
id("com.dorongold.task-tree") version "2.1.1"
|
||||
id("com.dorongold.task-tree") version "3.0.0"
|
||||
|
||||
id("com.google.devtools.ksp") version "1.9.22-1.0.17"
|
||||
}
|
||||
@ -141,7 +141,7 @@ dependencies {
|
||||
testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
|
||||
|
||||
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
|
||||
testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
|
||||
testImplementation("org.mockito.kotlin:mockito-kotlin:5.3.1")
|
||||
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2")
|
||||
@ -264,9 +264,6 @@ tasks {
|
||||
runPluginVerifier {
|
||||
downloadDir.set("${project.buildDir}/pluginVerifier/ides")
|
||||
teamCityOutputFormat.set(true)
|
||||
|
||||
// The latest version of the plugin verifier is broken, so temporally use the stable version
|
||||
verifierVersion = "1.307"
|
||||
}
|
||||
|
||||
generateGrammarSource {
|
||||
|
@ -9,16 +9,17 @@
|
||||
# suppress inspection "UnusedProperty" for whole file
|
||||
|
||||
#ideaVersion=LATEST-EAP-SNAPSHOT
|
||||
ideaVersion=2023.3.2
|
||||
ideaVersion=2024.1
|
||||
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
|
||||
ideaType=IC
|
||||
downloadIdeaSources=true
|
||||
instrumentPluginCode=true
|
||||
version=SNAPSHOT
|
||||
version=chylex-32
|
||||
javaVersion=17
|
||||
remoteRobotVersion=0.11.22
|
||||
antlrVersion=4.10.1
|
||||
|
||||
kotlin.incremental.useClasspathSnapshot=false
|
||||
|
||||
# Please don't forget to update kotlin version in buildscript section
|
||||
# Also update kotlinxSerializationVersion version
|
||||
@ -40,4 +41,4 @@ org.gradle.jvmargs='-Dfile.encoding=UTF-8'
|
||||
kotlin.stdlib.default.dependency=false
|
||||
|
||||
# Disable incremental annotation processing
|
||||
ksp.incremental=false
|
||||
ksp.incremental=false
|
||||
|
@ -22,11 +22,11 @@ repositories {
|
||||
dependencies {
|
||||
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.23")
|
||||
|
||||
implementation("io.ktor:ktor-client-core:2.3.9")
|
||||
implementation("io.ktor:ktor-client-cio:2.3.9")
|
||||
implementation("io.ktor:ktor-client-content-negotiation:2.3.9")
|
||||
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.9")
|
||||
implementation("io.ktor:ktor-client-auth:2.3.9")
|
||||
implementation("io.ktor:ktor-client-core:2.3.10")
|
||||
implementation("io.ktor:ktor-client-cio:2.3.10")
|
||||
implementation("io.ktor:ktor-client-content-negotiation:2.3.10")
|
||||
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.10")
|
||||
implementation("io.ktor:ktor-client-auth:2.3.10")
|
||||
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
|
||||
|
||||
// This is needed for jgit to connect to ssh
|
||||
@ -58,13 +58,6 @@ tasks.register("checkNewPluginDependencies", JavaExec::class) {
|
||||
classpath = sourceSets["main"].runtimeClasspath
|
||||
}
|
||||
|
||||
tasks.register("updateAffectedRates", JavaExec::class) {
|
||||
group = "verification"
|
||||
description = "This job updates Affected Rate field on YouTrack"
|
||||
mainClass.set("scripts.YouTrackUsersAffectedKt")
|
||||
classpath = sourceSets["main"].runtimeClasspath
|
||||
}
|
||||
|
||||
tasks.register("calculateNewVersion", JavaExec::class) {
|
||||
group = "release"
|
||||
mainClass.set("scripts.release.CalculateNewVersionKt")
|
||||
|
@ -49,17 +49,13 @@ suspend fun main() {
|
||||
}
|
||||
val output = response.body<List<String>>().toSet()
|
||||
println(output)
|
||||
if (knownPlugins != output) {
|
||||
val newPlugins = (output - knownPlugins).map { it to (getPluginLinkByXmlId(it) ?: "Can't find plugin link") }
|
||||
val removedPlugins = (knownPlugins - output.toSet()).map { it to (getPluginLinkByXmlId(it) ?: "Can't find plugin link") }
|
||||
val newPlugins = (output - knownPlugins).map { it to (getPluginLinkByXmlId(it) ?: "Can't find plugin link") }
|
||||
if (newPlugins.isNotEmpty()) {
|
||||
// val removedPlugins = (knownPlugins - output.toSet()).map { it to (getPluginLinkByXmlId(it) ?: "Can't find plugin link") }
|
||||
error(
|
||||
"""
|
||||
|
||||
Unregistered plugins:
|
||||
${if (newPlugins.isNotEmpty()) newPlugins.joinToString(separator = "\n") { it.first + "(" + it.second + ")" } else "No unregistered plugins"}
|
||||
|
||||
Removed plugins:
|
||||
${if (removedPlugins.isNotEmpty()) removedPlugins.joinToString(separator = "\n") { it.first + "(" + it.second + ")" } else "No removed plugins"}
|
||||
${newPlugins.joinToString(separator = "\n") { it.first + "(" + it.second + ")" }}
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright 2003-2023 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package scripts
|
||||
|
||||
import io.ktor.client.call.*
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.put
|
||||
|
||||
val areaWeights = setOf(
|
||||
Triple("118-53212", "Plugins", 50),
|
||||
Triple("118-53220", "Vim Script", 30),
|
||||
Triple("118-54084", "Esc", 100),
|
||||
)
|
||||
|
||||
suspend fun updateRates() {
|
||||
println("Updating rates of the issues")
|
||||
areaWeights.forEach { (id, name, weight) ->
|
||||
val unmappedIssues = unmappedIssues(name)
|
||||
println("Got ${unmappedIssues.size} for $name area")
|
||||
|
||||
unmappedIssues.forEach { issueId ->
|
||||
print("Trying to update issue $issueId: ")
|
||||
val response = updateCustomField(issueId) {
|
||||
put("name", "Affected Rate")
|
||||
put("\$type", "SimpleIssueCustomField")
|
||||
put("value", weight)
|
||||
}
|
||||
|
||||
println(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun unmappedIssues(area: String): List<String> {
|
||||
val areaProcessed = if (" " in area) "{$area}" else area
|
||||
val res = issuesQuery(
|
||||
query = "project: VIM Affected Rate: {No affected rate} Area: $areaProcessed #Unresolved",
|
||||
fields = "id,idReadable"
|
||||
)
|
||||
return res.body<JsonArray>().map { it.jsonObject }.map { it["idReadable"]!!.jsonPrimitive.content }
|
||||
}
|
||||
|
||||
suspend fun getAreasWithoutWeight(): Set<Pair<String, String>> {
|
||||
val allAreas = getAreaValues()
|
||||
return allAreas
|
||||
.filterNot { it.key in areaWeights.map { it.first }.toSet() }
|
||||
.entries
|
||||
.map { it.key to it.value }
|
||||
.toSet()
|
||||
}
|
||||
|
||||
suspend fun main() {
|
||||
updateRates()
|
||||
}
|
@ -77,7 +77,7 @@ public class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActio
|
||||
val modifiers = if (charTyped == ' ' && VimKeyListener.isSpaceShift) KeyEvent.SHIFT_DOWN_MASK else 0
|
||||
val keyStroke = KeyStroke.getKeyStroke(charTyped, modifiers)
|
||||
val startTime = if (traceTime) System.currentTimeMillis() else null
|
||||
handler.handleKey(editor.vim, keyStroke, injector.executionContextManager.onEditor(editor.vim, context.vim), handler.keyHandlerState)
|
||||
handler.handleKey(editor.vim, keyStroke, context.vim, handler.keyHandlerState)
|
||||
if (startTime != null) {
|
||||
val duration = System.currentTimeMillis() - startTime
|
||||
LOG.info("VimTypedAction '$charTyped': $duration ms")
|
||||
|
@ -79,12 +79,7 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
|
||||
try {
|
||||
val start = if (traceTime) System.currentTimeMillis() else null
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
keyHandler.handleKey(
|
||||
editor.vim,
|
||||
keyStroke,
|
||||
injector.executionContextManager.onEditor(editor.vim, e.dataContext.vim),
|
||||
keyHandler.keyHandlerState,
|
||||
)
|
||||
keyHandler.handleKey(editor.vim, keyStroke, e.dataContext.vim, keyHandler.keyHandlerState)
|
||||
if (start != null) {
|
||||
val duration = System.currentTimeMillis() - start
|
||||
LOG.info("VimShortcut update '$keyStroke': $duration ms")
|
||||
@ -381,6 +376,10 @@ private class ActionEnableStatus(
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "ActionEnableStatus(isEnabled=$isEnabled, message='$message', logLevel=$logLevel)"
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG = logger<ActionEnableStatus>()
|
||||
|
||||
|
@ -34,7 +34,7 @@ public class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecu
|
||||
}
|
||||
injector.editorGroup.notifyIdeaJoin(editor)
|
||||
var res = true
|
||||
editor.nativeCarets().sortedByDescending { it.offset.point }.forEach { caret ->
|
||||
editor.nativeCarets().sortedByDescending { it.offset }.forEach { caret ->
|
||||
if (!injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, true, operatorArguments)) {
|
||||
res = false
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ public class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExe
|
||||
return true
|
||||
}
|
||||
var res = true
|
||||
editor.nativeCarets().sortedByDescending { it.offset.point }.forEach { caret ->
|
||||
editor.nativeCarets().sortedByDescending { it.offset }.forEach { caret ->
|
||||
if (!caret.isValid) return@forEach
|
||||
val range = caretsAndSelections[caret] ?: return@forEach
|
||||
if (!injector.changeGroup.deleteJoinRange(
|
||||
|
@ -39,7 +39,7 @@ public class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.Sin
|
||||
return true
|
||||
}
|
||||
var res = true
|
||||
editor.carets().sortedByDescending { it.offset.point }.forEach { caret ->
|
||||
editor.carets().sortedByDescending { it.offset }.forEach { caret ->
|
||||
if (!caret.isValid) return@forEach
|
||||
val range = caretsAndSelections[caret] ?: return@forEach
|
||||
if (!injector.changeGroup.deleteJoinRange(
|
||||
|
@ -9,8 +9,6 @@
|
||||
package com.maddyhome.idea.vim.common
|
||||
|
||||
import com.intellij.application.options.CodeStyle
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.codeStyle.CommonCodeStyleSettings.IndentOptions
|
||||
@ -39,13 +37,12 @@ internal class IndentConfig private constructor(indentOptions: IndentOptions) {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun create(editor: Editor, context: DataContext): IndentConfig {
|
||||
return create(editor, PlatformDataKeys.PROJECT.getData(context))
|
||||
fun create(editor: Editor): IndentConfig {
|
||||
return create(editor, editor.project)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun create(editor: Editor, project: Project? = editor.project): IndentConfig {
|
||||
fun create(editor: Editor, project: Project?): IndentConfig {
|
||||
val indentOptions = if (project != null) {
|
||||
CodeStyle.getIndentOptions(project, editor.document)
|
||||
} else {
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
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
|
||||
@ -142,7 +143,7 @@ public object VimExtensionFacade {
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun executeNormalWithoutMapping(keys: List<KeyStroke>, editor: Editor) {
|
||||
val context = injector.executionContextManager.onEditor(editor.vim)
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(editor.vim)
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
keys.forEach { keyHandler.handleKey(editor.vim, it, context, false, false, keyHandler.keyHandlerState) }
|
||||
}
|
||||
@ -181,8 +182,8 @@ public object VimExtensionFacade {
|
||||
|
||||
/** Returns a string typed in the input box similar to 'input()'. */
|
||||
@JvmStatic
|
||||
public fun inputString(editor: Editor, prompt: String, finishOn: Char?): String {
|
||||
return service<CommandLineHelper>().inputString(editor.vim, prompt, finishOn) ?: ""
|
||||
public fun inputString(editor: Editor, context: DataContext, prompt: String, finishOn: Char?): String {
|
||||
return service<CommandLineHelper>().inputString(editor.vim, context.vim, prompt, finishOn) ?: ""
|
||||
}
|
||||
|
||||
/** Get the current contents of the given register similar to 'getreg()'. */
|
||||
|
@ -233,7 +233,7 @@ private object FileTypePatterns {
|
||||
} else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") {
|
||||
this.cMakePatterns
|
||||
} else {
|
||||
return null
|
||||
this.htmlPatterns
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,25 +8,21 @@
|
||||
|
||||
package com.maddyhome.idea.vim.extension.nerdtree
|
||||
|
||||
import com.intellij.ide.projectView.ProjectView
|
||||
import com.intellij.ide.projectView.impl.AbstractProjectViewPane
|
||||
import com.intellij.ide.projectView.impl.ProjectViewImpl
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||
import com.intellij.openapi.actionSystem.PlatformCoreDataKeys
|
||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
import com.intellij.openapi.startup.ProjectActivity
|
||||
import com.intellij.openapi.wm.ToolWindow
|
||||
import com.intellij.openapi.ui.getUserData
|
||||
import com.intellij.openapi.ui.putUserData
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.wm.ToolWindowId
|
||||
import com.intellij.openapi.wm.ex.ToolWindowManagerEx
|
||||
import com.intellij.openapi.wm.ex.ToolWindowManagerListener
|
||||
import com.intellij.ui.KeyStrokeAdapter
|
||||
import com.intellij.ui.TreeExpandCollapse
|
||||
import com.intellij.ui.speedSearch.SpeedSearchSupply
|
||||
@ -53,6 +49,8 @@ import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JTree
|
||||
import javax.swing.KeyStroke
|
||||
import javax.swing.SwingConstants
|
||||
|
||||
@ -132,7 +130,6 @@ internal class NerdTree : VimExtension {
|
||||
|
||||
synchronized(Util.monitor) {
|
||||
Util.commandsRegistered = true
|
||||
ProjectManager.getInstance().openProjects.forEach { project -> installDispatcher(project) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,39 +161,8 @@ internal class NerdTree : VimExtension {
|
||||
}
|
||||
}
|
||||
|
||||
class ProjectViewListener(private val project: Project) : ToolWindowManagerListener {
|
||||
override fun toolWindowShown(toolWindow: ToolWindow) {
|
||||
if (ToolWindowId.PROJECT_VIEW != toolWindow.id) return
|
||||
|
||||
val dispatcher = NerdDispatcher.getInstance(project)
|
||||
if (dispatcher.speedSearchListenerInstalled) return
|
||||
|
||||
// I specify nullability explicitly as we've got a lot of exceptions saying this property is null
|
||||
val currentProjectViewPane: AbstractProjectViewPane? = ProjectView.getInstance(project).currentProjectViewPane
|
||||
val tree = currentProjectViewPane?.tree ?: return
|
||||
val supply = SpeedSearchSupply.getSupply(tree, true) ?: return
|
||||
|
||||
// NB: Here might be some issues with concurrency, but it's not really bad, I think
|
||||
dispatcher.speedSearchListenerInstalled = true
|
||||
supply.addChangeListener {
|
||||
dispatcher.waitForSearch = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO I'm not sure is this activity runs at all? Should we use [RunOnceUtil] instead?
|
||||
class NerdStartupActivity : ProjectActivity {
|
||||
override suspend fun execute(project: Project) {
|
||||
synchronized(Util.monitor) {
|
||||
if (!Util.commandsRegistered) return
|
||||
installDispatcher(project)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NerdDispatcher : DumbAwareAction() {
|
||||
internal var waitForSearch = false
|
||||
internal var speedSearchListenerInstalled = false
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
var keyStroke = getKeyStroke(e) ?: return
|
||||
@ -244,10 +210,6 @@ internal class NerdTree : VimExtension {
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getInstance(project: Project): NerdDispatcher {
|
||||
return project.getService(NerdDispatcher::class.java)
|
||||
}
|
||||
|
||||
private const val ESCAPE_KEY_CODE = 27
|
||||
}
|
||||
|
||||
@ -283,19 +245,14 @@ internal class NerdTree : VimExtension {
|
||||
registerCommand(
|
||||
"NERDTreeMapActivateNode",
|
||||
"o",
|
||||
NerdAction.Code { project, dataContext, _ ->
|
||||
val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
|
||||
NerdAction.Code { _, dataContext, e ->
|
||||
val tree = getTree(e) ?: return@Code
|
||||
|
||||
val array = CommonDataKeys.NAVIGATABLE_ARRAY.getData(dataContext)?.filter { it.canNavigateToSource() }
|
||||
if (array.isNullOrEmpty()) {
|
||||
val row = tree.selectionRows?.getOrNull(0) ?: return@Code
|
||||
if (tree.isExpanded(row)) {
|
||||
tree.collapseRow(row)
|
||||
} else {
|
||||
tree.expandRow(row)
|
||||
}
|
||||
val row = tree.selectionRows?.getOrNull(0) ?: return@Code
|
||||
if (tree.isExpanded(row)) {
|
||||
tree.collapseRow(row)
|
||||
} else {
|
||||
array.forEach { it.navigate(true) }
|
||||
tree.expandRow(row)
|
||||
}
|
||||
},
|
||||
)
|
||||
@ -374,8 +331,8 @@ internal class NerdTree : VimExtension {
|
||||
registerCommand(
|
||||
"NERDTreeMapOpenRecursively",
|
||||
"O",
|
||||
NerdAction.Code { project, _, _ ->
|
||||
val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
|
||||
NerdAction.Code { _, _, e ->
|
||||
val tree = getTree(e) ?: return@Code
|
||||
TreeExpandCollapse.expandAll(tree)
|
||||
tree.selectionPath?.let {
|
||||
TreeUtil.scrollToVisible(tree, it, false)
|
||||
@ -385,8 +342,8 @@ internal class NerdTree : VimExtension {
|
||||
registerCommand(
|
||||
"NERDTreeMapCloseChildren",
|
||||
"X",
|
||||
NerdAction.Code { project, _, _ ->
|
||||
val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
|
||||
NerdAction.Code { _, _, e ->
|
||||
val tree = getTree(e) ?: return@Code
|
||||
TreeExpandCollapse.collapse(tree)
|
||||
tree.selectionPath?.let {
|
||||
TreeUtil.scrollToVisible(tree, it, false)
|
||||
@ -396,8 +353,8 @@ internal class NerdTree : VimExtension {
|
||||
registerCommand(
|
||||
"NERDTreeMapCloseDir",
|
||||
"x",
|
||||
NerdAction.Code { project, _, _ ->
|
||||
val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
|
||||
NerdAction.Code { _, _, e ->
|
||||
val tree = getTree(e) ?: return@Code
|
||||
val currentPath = tree.selectionPath ?: return@Code
|
||||
if (tree.isExpanded(currentPath)) {
|
||||
tree.collapsePath(currentPath)
|
||||
@ -415,8 +372,8 @@ internal class NerdTree : VimExtension {
|
||||
registerCommand(
|
||||
"NERDTreeMapJumpParent",
|
||||
"p",
|
||||
NerdAction.Code { project, _, _ ->
|
||||
val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
|
||||
NerdAction.Code { _, _, e ->
|
||||
val tree = getTree(e) ?: return@Code
|
||||
val currentPath = tree.selectionPath ?: return@Code
|
||||
val parentPath = currentPath.parentPath ?: return@Code
|
||||
if (parentPath.parentPath != null) {
|
||||
@ -429,8 +386,8 @@ internal class NerdTree : VimExtension {
|
||||
registerCommand(
|
||||
"NERDTreeMapJumpFirstChild",
|
||||
"K",
|
||||
NerdAction.Code { project, _, _ ->
|
||||
val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
|
||||
NerdAction.Code { _, _, e ->
|
||||
val tree = getTree(e) ?: return@Code
|
||||
val currentPath = tree.selectionPath ?: return@Code
|
||||
val parent = currentPath.parentPath ?: return@Code
|
||||
val row = tree.getRowForPath(parent)
|
||||
@ -442,8 +399,8 @@ internal class NerdTree : VimExtension {
|
||||
registerCommand(
|
||||
"NERDTreeMapJumpLastChild",
|
||||
"J",
|
||||
NerdAction.Code { project, _, _ ->
|
||||
val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
|
||||
NerdAction.Code { _, _, e ->
|
||||
val tree = getTree(e) ?: return@Code
|
||||
val currentPath = tree.selectionPath ?: return@Code
|
||||
|
||||
val currentPathCount = currentPath.pathCount
|
||||
@ -488,20 +445,26 @@ internal class NerdTree : VimExtension {
|
||||
|
||||
registerCommand(
|
||||
"/",
|
||||
NerdAction.Code { project, _, _ ->
|
||||
NerdDispatcher.getInstance(project).waitForSearch = true
|
||||
NerdAction.Code { _, _, e ->
|
||||
val tree = getTree(e) ?: return@Code
|
||||
tree.getUserData(KEY)?.waitForSearch = true
|
||||
},
|
||||
)
|
||||
|
||||
registerCommand(
|
||||
"<ESC>",
|
||||
NerdAction.Code { project, _, _ ->
|
||||
val instance = NerdDispatcher.getInstance(project)
|
||||
if (instance.waitForSearch) {
|
||||
instance.waitForSearch = false
|
||||
}
|
||||
NerdAction.Code { _, _, e ->
|
||||
val tree = getTree(e) ?: return@Code
|
||||
tree.getUserData(KEY)?.waitForSearch = false
|
||||
},
|
||||
)
|
||||
|
||||
for (c in ('a'..'z') + ('A'..'Z')) {
|
||||
val ks = KeyStroke.getKeyStroke(c)
|
||||
if (ks !in actionsRoot) {
|
||||
registerCommand(c.toString(), NerdAction.Code { _, _, _ -> })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object Util {
|
||||
@ -526,6 +489,21 @@ internal class NerdTree : VimExtension {
|
||||
companion object {
|
||||
const val pluginName = "NERDTree"
|
||||
private val LOG = logger<NerdTree>()
|
||||
private val KEY = Key.create<NerdDispatcher>("IdeaVim-NerdTree-Dispatcher")
|
||||
|
||||
fun installDispatcher(component: JComponent) {
|
||||
if (component.getUserData(KEY) != null) return
|
||||
|
||||
val dispatcher = NerdDispatcher()
|
||||
component.putUserData(KEY, dispatcher)
|
||||
|
||||
val shortcuts = collectShortcuts(actionsRoot).map { RequiredShortcut(it, MappingOwner.Plugin.get(pluginName)) }
|
||||
dispatcher.registerCustomShortcutSet(KeyGroup.toShortcutSet(shortcuts), component)
|
||||
|
||||
SpeedSearchSupply.getSupply(component, true)?.addChangeListener {
|
||||
dispatcher.waitForSearch = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -560,12 +538,6 @@ private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> {
|
||||
}
|
||||
}
|
||||
|
||||
private fun installDispatcher(project: Project) {
|
||||
val dispatcher = NerdTree.NerdDispatcher.getInstance(project)
|
||||
val shortcuts =
|
||||
collectShortcuts(actionsRoot).map { RequiredShortcut(it, MappingOwner.Plugin.get(NerdTree.pluginName)) }
|
||||
dispatcher.registerCustomShortcutSet(
|
||||
KeyGroup.toShortcutSet(shortcuts),
|
||||
(ProjectView.getInstance(project) as ProjectViewImpl).component,
|
||||
)
|
||||
private fun getTree(e: AnActionEvent): JTree? {
|
||||
return e.dataContext.getData(PlatformCoreDataKeys.CONTEXT_COMPONENT) as? JTree
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
package com.maddyhome.idea.vim.extension.nerdtree
|
||||
|
||||
import com.intellij.ide.ApplicationInitializedListener
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.util.ui.StartupUiUtil
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import java.awt.AWTEvent
|
||||
import java.awt.event.FocusEvent
|
||||
import javax.swing.JTree
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
internal class NerdTreeApplicationListener : ApplicationInitializedListener {
|
||||
override suspend fun execute(asyncScope: CoroutineScope) {
|
||||
StartupUiUtil.addAwtListener(::handleEvent, AWTEvent.FOCUS_EVENT_MASK, ApplicationManager.getApplication().getService(NerdTreeDisposableService::class.java))
|
||||
}
|
||||
|
||||
private fun handleEvent(event: AWTEvent) {
|
||||
if (event is FocusEvent && event.id == FocusEvent.FOCUS_GAINED) {
|
||||
val source = event.source
|
||||
if (source is JTree) {
|
||||
NerdTree.installDispatcher(source)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.maddyhome.idea.vim.extension.nerdtree
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.components.Service
|
||||
|
||||
@Service
|
||||
internal class NerdTreeDisposableService : Disposable {
|
||||
override fun dispose() {}
|
||||
}
|
@ -8,6 +8,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.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
@ -64,7 +65,7 @@ internal class ReplaceWithRegister : VimExtension {
|
||||
val selectionEnd = caret.selectionEnd
|
||||
|
||||
val visualSelection = caret to VimSelection.create(selectionStart, selectionEnd - 1, typeInEditor, editor)
|
||||
doReplace(editor.ij, caret, PutData.VisualSelection(mapOf(visualSelection), typeInEditor))
|
||||
doReplace(editor.ij, context.ij, caret, PutData.VisualSelection(mapOf(visualSelection), typeInEditor))
|
||||
}
|
||||
editor.exitVisualMode()
|
||||
}
|
||||
@ -92,7 +93,7 @@ internal class ReplaceWithRegister : VimExtension {
|
||||
val visualSelection = caret to VimSelection.create(lineStart, lineEnd, SelectionType.LINE_WISE, editor)
|
||||
caretsAndSelections += visualSelection
|
||||
|
||||
doReplace(editor.ij, caret, PutData.VisualSelection(mapOf(visualSelection), SelectionType.LINE_WISE))
|
||||
doReplace(editor.ij, context.ij, caret, PutData.VisualSelection(mapOf(visualSelection), SelectionType.LINE_WISE))
|
||||
}
|
||||
|
||||
editor.sortedCarets().forEach { caret ->
|
||||
@ -120,7 +121,7 @@ internal class ReplaceWithRegister : VimExtension {
|
||||
selectionType ?: SelectionType.CHARACTER_WISE,
|
||||
)
|
||||
// todo multicaret
|
||||
doReplace(ijEditor, editor.primaryCaret(), visualSelection)
|
||||
doReplace(ijEditor, context.ij, editor.primaryCaret(), visualSelection)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -140,7 +141,7 @@ internal class ReplaceWithRegister : VimExtension {
|
||||
}
|
||||
}
|
||||
|
||||
private fun doReplace(editor: Editor, caret: ImmutableVimCaret, visualSelection: PutData.VisualSelection) {
|
||||
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
|
||||
@ -168,7 +169,7 @@ private fun doReplace(editor: Editor, caret: ImmutableVimCaret, visualSelection:
|
||||
ClipboardOptionHelper.IdeaputDisabler().use {
|
||||
VimPlugin.getPut().putText(
|
||||
vimEditor,
|
||||
injector.executionContextManager.onEditor(editor.vim),
|
||||
context.vim,
|
||||
putData,
|
||||
operatorArguments = OperatorArguments(
|
||||
editor.vimStateMachine?.isOperatorPending(vimEditor.mode) ?: false,
|
||||
|
@ -118,7 +118,7 @@ internal class IdeaVimSneakExtension : VimExtension {
|
||||
var lastSymbols: String = ""
|
||||
fun jumpTo(editor: VimEditor, charone: Char, chartwo: Char, sneakDirection: Direction): TextRange? {
|
||||
val caret = editor.primaryCaret()
|
||||
val position = caret.offset.point
|
||||
val position = caret.offset
|
||||
val chars = editor.text()
|
||||
val foundPosition = sneakDirection.findBiChar(editor, chars, position, charone, chartwo)
|
||||
if (foundPosition != null) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,12 +7,14 @@
|
||||
*/
|
||||
package com.maddyhome.idea.vim.extension.surround
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.application.runWriteAction
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
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
|
||||
@ -33,7 +35,11 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMa
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
|
||||
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
|
||||
@ -76,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 {
|
||||
@ -95,7 +101,7 @@ internal class VimSurroundExtension : VimExtension {
|
||||
val ijEditor = editor.ij
|
||||
val c = getChar(ijEditor)
|
||||
if (c.code == 0) return
|
||||
val pair = getOrInputPair(c, ijEditor) ?: return
|
||||
val pair = getOrInputPair(c, ijEditor, context.ij) ?: return
|
||||
|
||||
editor.forEachCaret {
|
||||
val line = it.getBufferPosition().line
|
||||
@ -104,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)
|
||||
}
|
||||
@ -124,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -147,12 +151,16 @@ internal class VimSurroundExtension : VimExtension {
|
||||
val charTo = getChar(editor.ij)
|
||||
if (charTo.code == 0) return
|
||||
|
||||
val newSurround = getOrInputPair(charTo, editor.ij) ?: return
|
||||
val newSurround = getOrInputPair(charTo, editor.ij, context.ij) ?: return
|
||||
runWriteAction { change(editor, context, charFrom, newSurround) }
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -220,12 +228,12 @@ internal class VimSurroundExtension : VimExtension {
|
||||
val searchHelper = injector.searchHelper
|
||||
return when (char) {
|
||||
't' -> searchHelper.findBlockTagRange(editor, caret, 1, true)
|
||||
'(', ')', 'b' -> searchHelper.findBlockRange(editor, caret, '(', 1, true)
|
||||
'[', ']' -> searchHelper.findBlockRange(editor, caret, '[', 1, true)
|
||||
'{', '}', 'B' -> searchHelper.findBlockRange(editor, caret, '{', 1, true)
|
||||
'<', '>' -> searchHelper.findBlockRange(editor, caret, '<', 1, true)
|
||||
'(', ')', 'b' -> findBlockRange(editor, caret, '(', 1, true)
|
||||
'[', ']' -> findBlockRange(editor, caret, '[', 1, true)
|
||||
'{', '}', 'B' -> findBlockRange(editor, caret, '{', 1, true)
|
||||
'<', '>' -> findBlockRange(editor, caret, '<', 1, true)
|
||||
'`', '\'', '"' -> {
|
||||
val caretOffset = caret.offset.point
|
||||
val caretOffset = caret.offset
|
||||
val text = editor.text()
|
||||
if (text.getOrNull(caretOffset - 1) == char && text.getOrNull(caretOffset) == char) {
|
||||
TextRange(caretOffset - 1, caretOffset + 1)
|
||||
@ -260,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) ?: 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)
|
||||
val pair = getOrInputPair(c, ijEditor, context.ij) ?: return false
|
||||
|
||||
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
|
||||
@ -319,8 +348,8 @@ private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_
|
||||
null
|
||||
}
|
||||
|
||||
private fun inputTagPair(editor: Editor): Pair<String, String>? {
|
||||
val tagInput = inputString(editor, "<", '>')
|
||||
private fun inputTagPair(editor: Editor, context: DataContext): Pair<String, String>? {
|
||||
val tagInput = inputString(editor, context, "<", '>')
|
||||
val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
|
||||
return if (matcher.find()) {
|
||||
val tagName = matcher.group(1)
|
||||
@ -333,17 +362,18 @@ private fun inputTagPair(editor: Editor): Pair<String, String>? {
|
||||
|
||||
private fun inputFunctionName(
|
||||
editor: Editor,
|
||||
context: DataContext,
|
||||
withInternalSpaces: Boolean,
|
||||
): Pair<String, String>? {
|
||||
val functionNameInput = inputString(editor, "function: ", null)
|
||||
val functionNameInput = inputString(editor, context, "function: ", null)
|
||||
if (functionNameInput.isEmpty()) return null
|
||||
return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
|
||||
}
|
||||
|
||||
private fun getOrInputPair(c: Char, editor: Editor): Pair<String, String>? = when (c) {
|
||||
'<', 't' -> inputTagPair(editor)
|
||||
'f' -> inputFunctionName(editor, false)
|
||||
'F' -> inputFunctionName(editor, true)
|
||||
private fun getOrInputPair(c: Char, editor: Editor, context: DataContext): Pair<String, String>? = when (c) {
|
||||
'<', 't' -> inputTagPair(editor, context)
|
||||
'f' -> inputFunctionName(editor, context, false)
|
||||
'F' -> inputFunctionName(editor, context, true)
|
||||
else -> getSurroundPair(c)
|
||||
}
|
||||
|
||||
@ -359,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 {
|
||||
@ -375,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)
|
||||
|
@ -79,7 +79,6 @@ 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
|
||||
@ -198,7 +197,7 @@ public class ChangeGroup : VimChangeGroupBase() {
|
||||
val allowWrap = injector.options(editor).whichwrap.contains("~")
|
||||
var motion = injector.motion.getHorizontalMotion(editor, caret, count, true, allowWrap)
|
||||
if (motion is Motion.Error) return false
|
||||
changeCase(editor, caret, caret.offset.point, (motion as AbsoluteOffset).offset, CharacterHelper.CASE_TOGGLE)
|
||||
changeCase(editor, caret, caret.offset, (motion as AbsoluteOffset).offset, CharacterHelper.CASE_TOGGLE)
|
||||
motion = injector.motion.getHorizontalMotion(
|
||||
editor,
|
||||
caret,
|
||||
@ -236,8 +235,7 @@ public class ChangeGroup : VimChangeGroupBase() {
|
||||
}
|
||||
val lineLength = editor.lineLength(line)
|
||||
if (column < VimMotionGroupBase.LAST_COLUMN && lineLength < column) {
|
||||
val pad =
|
||||
EditorHelper.pad((editor as IjVimEditor).editor, (context as IjEditorExecutionContext).context, line, column)
|
||||
val pad = EditorHelper.pad((editor as IjVimEditor).editor, line, column)
|
||||
val offset = editor.getLineEndOffset(line)
|
||||
insertText(editor, caret, offset, pad)
|
||||
}
|
||||
@ -396,6 +394,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
|
||||
@ -420,11 +419,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) {
|
||||
@ -455,7 +450,7 @@ public class ChangeGroup : VimChangeGroupBase() {
|
||||
dir: Int,
|
||||
operatorArguments: OperatorArguments,
|
||||
) {
|
||||
val start = caret.offset.point
|
||||
val start = caret.offset
|
||||
val end = injector.motion.moveCaretToRelativeLineEnd(editor, caret, lines - 1, true)
|
||||
indentRange(editor, caret, context, TextRange(start, end), 1, dir, operatorArguments)
|
||||
}
|
||||
@ -489,7 +484,7 @@ public class ChangeGroup : VimChangeGroupBase() {
|
||||
|
||||
// Remember the current caret column
|
||||
val intendedColumn = caret.vimLastColumn
|
||||
val indentConfig = create((editor as IjVimEditor).editor, (context as IjEditorExecutionContext).context)
|
||||
val indentConfig = create((editor as IjVimEditor).editor)
|
||||
val sline = editor.offsetToBufferPosition(range.startOffset).line
|
||||
val endLogicalPosition = editor.offsetToBufferPosition(range.endOffset)
|
||||
val eline = if (endLogicalPosition.column == 0) max((endLogicalPosition.line - 1).toDouble(), 0.0)
|
||||
|
@ -235,7 +235,7 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
|
||||
// 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 = () -> {
|
||||
ExecutionContext.Editor context = injector.getExecutionContextManager().onEditor(new IjVimEditor(editor), null);
|
||||
ExecutionContext context = injector.getExecutionContextManager().getEditorExecutionContext(new IjVimEditor(editor));
|
||||
VimPlugin.getChange().insertBeforeCursor(new IjVimEditor(editor), context);
|
||||
KeyHandler.getInstance().reset(new IjVimEditor(editor));
|
||||
};
|
||||
|
@ -22,9 +22,12 @@ 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;
|
||||
@ -44,6 +47,7 @@ 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;
|
||||
@ -445,4 +449,28 @@ public class FileGroup extends VimFileBase {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ public object IjOptions {
|
||||
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 oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, 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))
|
||||
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2003-2024 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.group
|
||||
|
||||
import com.intellij.lang.CodeDocumentationAwareCommenter
|
||||
import com.intellij.lang.LanguageCommenters
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.psi.PsiComment
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.VimPsiService
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.helper.PsiHelper
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
|
||||
@Service
|
||||
public 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
|
||||
val language = psiElement.language
|
||||
val commenter = LanguageCommenters.INSTANCE.forLanguage(language)
|
||||
val psiComment = PsiTreeUtil.getParentOfType(psiElement, PsiComment::class.java, false) ?: return null
|
||||
val commentText = psiComment.text
|
||||
|
||||
val blockCommentPrefix = commenter.blockCommentPrefix
|
||||
val blockCommentSuffix = commenter.blockCommentSuffix
|
||||
|
||||
val docCommentPrefix = (commenter as? CodeDocumentationAwareCommenter)?.documentationCommentPrefix
|
||||
val docCommentSuffix = (commenter as? CodeDocumentationAwareCommenter)?.documentationCommentSuffix
|
||||
|
||||
val prefixToSuffix: Pair<String, String>? =
|
||||
if (docCommentPrefix != null && docCommentSuffix != null && commentText.startsWith(docCommentPrefix) && commentText.endsWith(docCommentSuffix)) {
|
||||
docCommentPrefix to docCommentSuffix
|
||||
}
|
||||
else if (blockCommentPrefix != null && blockCommentSuffix != null && commentText.startsWith(blockCommentPrefix) && commentText.endsWith(blockCommentSuffix)) {
|
||||
blockCommentPrefix to blockCommentSuffix
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
return Pair(psiComment.textRange.vim, prefixToSuffix)
|
||||
}
|
||||
|
||||
override fun getDoubleQuotedString(editor: VimEditor, pos: Int, isInner: Boolean): TextRange? {
|
||||
// TODO[ideavim] It wasn't implemented before, but implementing it will significantly improve % motion
|
||||
return getDoubleQuotesRangeNoPSI(editor.text(), pos, isInner)
|
||||
}
|
||||
|
||||
override fun getSingleQuotedString(editor: VimEditor, pos: Int, isInner: Boolean): TextRange? {
|
||||
// TODO[ideavim] It wasn't implemented before, but implementing it will significantly improve % motion
|
||||
return getSingleQuotesRangeNoPSI(editor.text(), pos, isInner)
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -11,41 +11,24 @@ import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.editor.Caret
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.LogicalPosition
|
||||
import com.intellij.openapi.editor.VisualPosition
|
||||
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.EditorWindow
|
||||
import com.intellij.openapi.project.Project
|
||||
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.util.MathUtil.clamp
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.BufferPosition
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
||||
import com.maddyhome.idea.vim.api.VimCaret
|
||||
import com.maddyhome.idea.vim.api.VimChangeGroupBase
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.VimMotionGroupBase
|
||||
import com.maddyhome.idea.vim.api.addJump
|
||||
import com.maddyhome.idea.vim.api.anyNonWhitespace
|
||||
import com.maddyhome.idea.vim.api.getJump
|
||||
import com.maddyhome.idea.vim.api.getJumpSpot
|
||||
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
|
||||
import com.maddyhome.idea.vim.api.getVisualLineCount
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.api.lineLength
|
||||
import com.maddyhome.idea.vim.api.normalizeColumn
|
||||
import com.maddyhome.idea.vim.api.normalizeLine
|
||||
import com.maddyhome.idea.vim.api.normalizeOffset
|
||||
import com.maddyhome.idea.vim.api.normalizeVisualColumn
|
||||
import com.maddyhome.idea.vim.api.normalizeVisualLine
|
||||
import com.maddyhome.idea.vim.api.options
|
||||
import com.maddyhome.idea.vim.api.visualLineToBufferLine
|
||||
import com.maddyhome.idea.vim.command.Argument
|
||||
import com.maddyhome.idea.vim.command.MotionType
|
||||
@ -54,12 +37,9 @@ import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.ex.ExOutputModel
|
||||
import com.maddyhome.idea.vim.handler.Motion
|
||||
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset
|
||||
import com.maddyhome.idea.vim.handler.Motion.AdjustedOffset
|
||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
|
||||
import com.maddyhome.idea.vim.handler.TextObjectActionHandler
|
||||
import com.maddyhome.idea.vim.handler.toMotionOrError
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
import com.maddyhome.idea.vim.helper.SearchHelper
|
||||
import com.maddyhome.idea.vim.helper.exitVisualMode
|
||||
import com.maddyhome.idea.vim.helper.fileSize
|
||||
import com.maddyhome.idea.vim.helper.getNormalizedScrollOffset
|
||||
@ -67,17 +47,13 @@ import com.maddyhome.idea.vim.helper.getNormalizedSideScrollOffset
|
||||
import com.maddyhome.idea.vim.helper.isEndAllowed
|
||||
import com.maddyhome.idea.vim.helper.vimLastColumn
|
||||
import com.maddyhome.idea.vim.listener.AppCodeTemplates
|
||||
import com.maddyhome.idea.vim.mark.Mark
|
||||
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
|
||||
import com.maddyhome.idea.vim.newapi.IjVimCaret
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
|
||||
import org.jetbrains.annotations.Range
|
||||
import java.io.File
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
@ -90,24 +66,6 @@ internal class MotionGroup : VimMotionGroupBase() {
|
||||
AppCodeTemplates.onMovement(editor.ij, caret.ij, oldOffset < offset)
|
||||
}
|
||||
|
||||
private fun selectEditor(project: Project, mark: Mark): Editor? {
|
||||
val virtualFile = markToVirtualFile(mark) ?: return null
|
||||
return selectEditor(project, virtualFile)
|
||||
}
|
||||
|
||||
private fun markToVirtualFile(mark: Mark): VirtualFile? {
|
||||
val protocol = mark.protocol
|
||||
val fileSystem: VirtualFileSystem? = VirtualFileManager.getInstance().getFileSystem(protocol)
|
||||
return fileSystem?.findFileByPath(mark.filepath)
|
||||
}
|
||||
|
||||
private fun selectEditor(project: Project?, file: VirtualFile) =
|
||||
VimPlugin.getFile().selectEditor(project, file)
|
||||
|
||||
override fun moveCaretToMatchingPair(editor: VimEditor, caret: ImmutableVimCaret): Motion {
|
||||
return SearchHelper.findMatchingPairOnCurrentLine(editor.ij, caret.ij).toMotionOrError()
|
||||
}
|
||||
|
||||
override fun moveCaretToFirstDisplayLine(
|
||||
editor: VimEditor,
|
||||
caret: ImmutableVimCaret,
|
||||
@ -130,85 +88,12 @@ internal class MotionGroup : VimMotionGroupBase() {
|
||||
return moveCaretToScreenLocation(editor.ij, caret.ij, ScreenLocation.MIDDLE, 0, false)
|
||||
}
|
||||
|
||||
override fun moveCaretToMark(caret: ImmutableVimCaret, ch: Char, toLineStart: Boolean): Motion {
|
||||
val markService = injector.markService
|
||||
val mark = markService.getMark(caret, ch) ?: return Motion.Error
|
||||
|
||||
val caretEditor = caret.editor
|
||||
val caretVirtualFile = EditorHelper.getVirtualFile((caretEditor as IjVimEditor).editor)
|
||||
|
||||
val line = mark.line
|
||||
|
||||
if (caretVirtualFile!!.path == mark.filepath) {
|
||||
val offset = if (toLineStart) {
|
||||
moveCaretToLineStartSkipLeading(caretEditor, line)
|
||||
} else {
|
||||
caretEditor.bufferPositionToOffset(BufferPosition(line, mark.col, false))
|
||||
}
|
||||
return offset.toMotionOrError()
|
||||
}
|
||||
|
||||
val project = caretEditor.editor.project
|
||||
val markEditor = selectEditor(project!!, mark)
|
||||
if (markEditor != null) {
|
||||
// todo should we move all the carets or only one?
|
||||
for (carett in markEditor.caretModel.allCarets) {
|
||||
val offset = if (toLineStart) {
|
||||
moveCaretToLineStartSkipLeading(IjVimEditor(markEditor), line)
|
||||
} else {
|
||||
// todo should it be the same as getting offset above?
|
||||
markEditor.logicalPositionToOffset(LogicalPosition(line, mark.col))
|
||||
}
|
||||
IjVimCaret(carett!!).moveToOffset(offset)
|
||||
}
|
||||
}
|
||||
return Motion.Error
|
||||
}
|
||||
|
||||
override fun moveCaretToJump(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Motion {
|
||||
val jumpService = injector.jumpService
|
||||
val spot = jumpService.getJumpSpot(editor)
|
||||
val (line, col, fileName) = jumpService.getJump(editor, count) ?: return Motion.Error
|
||||
val vf = EditorHelper.getVirtualFile(editor.ij) ?: return Motion.Error
|
||||
val lp = BufferPosition(line, col, false)
|
||||
val lpNative = LogicalPosition(line, col, false)
|
||||
return if (vf.path != fileName) {
|
||||
val newFile = LocalFileSystem.getInstance().findFileByPath(fileName.replace(File.separatorChar, '/'))
|
||||
?: return Motion.Error
|
||||
selectEditor(editor.ij.project, newFile)?.let { newEditor ->
|
||||
if (spot == -1) {
|
||||
jumpService.addJump(editor, false)
|
||||
}
|
||||
newEditor.vim.let {
|
||||
it.currentCaret().moveToOffset(it.normalizeOffset(newEditor.logicalPositionToOffset(lpNative), false))
|
||||
}
|
||||
}
|
||||
Motion.Error
|
||||
} else {
|
||||
if (spot == -1) {
|
||||
jumpService.addJump(editor, false)
|
||||
}
|
||||
editor.bufferPositionToOffset(lp).toMotionOrError()
|
||||
}
|
||||
}
|
||||
|
||||
override fun moveCaretToCurrentDisplayLineMiddle(editor: VimEditor, caret: ImmutableVimCaret): Motion {
|
||||
val width = EditorHelper.getApproximateScreenWidth(editor.ij) / 2
|
||||
val len = editor.lineLength(editor.currentCaret().getBufferPosition().line)
|
||||
return moveCaretToColumn(editor, caret, max(0, min(len - 1, width)), false)
|
||||
}
|
||||
|
||||
override fun moveCaretToColumn(editor: VimEditor, caret: ImmutableVimCaret, count: Int, allowEnd: Boolean): Motion {
|
||||
val line = caret.getLine().line
|
||||
val column = editor.normalizeColumn(line, count, allowEnd)
|
||||
val offset = editor.bufferPositionToOffset(BufferPosition(line, column, false))
|
||||
return if (column != count) {
|
||||
AdjustedOffset(offset, count)
|
||||
} else {
|
||||
AbsoluteOffset(offset)
|
||||
}
|
||||
}
|
||||
|
||||
override fun moveCaretToCurrentDisplayLineStart(editor: VimEditor, caret: ImmutableVimCaret): Motion {
|
||||
val col = EditorHelper.getVisualColumnAtLeftOfDisplay(editor.ij, caret.getVisualPosition().line)
|
||||
return moveCaretToColumn(editor, caret, col, false)
|
||||
@ -219,7 +104,7 @@ internal class MotionGroup : VimMotionGroupBase() {
|
||||
caret: ImmutableVimCaret,
|
||||
): @Range(from = 0, to = Int.MAX_VALUE.toLong()) Int {
|
||||
val col = EditorHelper.getVisualColumnAtLeftOfDisplay(editor.ij, caret.getVisualPosition().line)
|
||||
val bufferLine = caret.getLine().line
|
||||
val bufferLine = caret.getLine()
|
||||
return editor.getLeadingCharacterOffset(bufferLine, col)
|
||||
}
|
||||
|
||||
@ -232,36 +117,6 @@ internal class MotionGroup : VimMotionGroupBase() {
|
||||
return moveCaretToColumn(editor, caret, col, allowEnd)
|
||||
}
|
||||
|
||||
override fun moveCaretToLineWithSameColumn(
|
||||
editor: VimEditor,
|
||||
line: Int,
|
||||
caret: ImmutableVimCaret,
|
||||
): @Range(from = 0, to = Int.MAX_VALUE.toLong()) Int {
|
||||
var c = caret.vimLastColumn
|
||||
var l = line
|
||||
if (l < 0) {
|
||||
l = 0
|
||||
c = 0
|
||||
} else if (l >= editor.lineCount()) {
|
||||
l = editor.normalizeLine(editor.lineCount() - 1)
|
||||
c = editor.lineLength(l)
|
||||
}
|
||||
val newPos = BufferPosition(l, editor.normalizeColumn(l, c, false))
|
||||
return editor.bufferPositionToOffset(newPos)
|
||||
}
|
||||
|
||||
override fun moveCaretToLineWithStartOfLineOption(
|
||||
editor: VimEditor,
|
||||
line: Int,
|
||||
caret: ImmutableVimCaret,
|
||||
): @Range(from = 0, to = Int.MAX_VALUE.toLong()) Int {
|
||||
return if (injector.options(editor).startofline) {
|
||||
moveCaretToLineStartSkipLeading(editor, line)
|
||||
} else {
|
||||
moveCaretToLineWithSameColumn(editor, line, caret)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If 'absolute' is true, then set tab index to 'value', otherwise add 'value' to tab index with wraparound.
|
||||
*/
|
||||
@ -279,30 +134,18 @@ internal class MotionGroup : VimMotionGroupBase() {
|
||||
}
|
||||
|
||||
override fun moveCaretGotoPreviousTab(editor: VimEditor, context: ExecutionContext, rawCount: Int): Int {
|
||||
val project = editor.ij.project ?: return editor.currentCaret().offset.point
|
||||
val project = editor.ij.project ?: return editor.currentCaret().offset
|
||||
val currentWindow = FileEditorManagerEx.getInstanceEx(project).splitters.currentWindow
|
||||
switchEditorTab(currentWindow, if (rawCount >= 1) -rawCount else -1, false)
|
||||
return editor.currentCaret().offset.point
|
||||
return editor.currentCaret().offset
|
||||
}
|
||||
|
||||
override fun moveCaretGotoNextTab(editor: VimEditor, context: ExecutionContext, rawCount: Int): Int {
|
||||
val absolute = rawCount >= 1
|
||||
val project = editor.ij.project ?: return editor.currentCaret().offset.point
|
||||
val project = editor.ij.project ?: return editor.currentCaret().offset
|
||||
val currentWindow = FileEditorManagerEx.getInstanceEx(project).splitters.currentWindow
|
||||
switchEditorTab(currentWindow, if (absolute) rawCount - 1 else 1, absolute)
|
||||
return editor.currentCaret().offset.point
|
||||
}
|
||||
|
||||
override fun moveCaretToLinePercent(
|
||||
editor: VimEditor,
|
||||
caret: ImmutableVimCaret,
|
||||
count: Int,
|
||||
): @Range(from = 0, to = Int.MAX_VALUE.toLong()) Int {
|
||||
return moveCaretToLineWithStartOfLineOption(
|
||||
editor,
|
||||
editor.normalizeLine((editor.lineCount() * clamp(count, 0, 100) + 99) / 100 - 1),
|
||||
caret,
|
||||
)
|
||||
return editor.currentCaret().offset
|
||||
}
|
||||
|
||||
private enum class ScreenLocation {
|
||||
|
@ -210,8 +210,8 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
||||
* @param patternOffset The pattern offset, e.g. `/{pattern}/{offset}`
|
||||
* @param direction The direction to search
|
||||
*/
|
||||
@TestOnly
|
||||
public void setLastSearchState(@SuppressWarnings("unused") @NotNull Editor editor, @NotNull String pattern,
|
||||
@Override
|
||||
public void setLastSearchState(@SuppressWarnings("unused") @NotNull VimEditor editor, @NotNull String pattern,
|
||||
@NotNull String patternOffset, Direction direction) {
|
||||
if (globalIjOptions(injector).getUseNewRegex()) {
|
||||
super.setLastSearchState(pattern, patternOffset, direction);
|
||||
@ -538,20 +538,24 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
||||
*
|
||||
* @param editor The editor to search in
|
||||
* @param caret The caret to use for initial search offset, and to move for interactive substitution
|
||||
* @param context
|
||||
* @param range Only search and substitute within the given line range. Must be valid
|
||||
* @param excmd The command part of the ex command line, e.g. `s` or `substitute`, or `~`
|
||||
* @param exarg The argument to the substitute command, such as `/{pattern}/{string}/[flags]`
|
||||
* @return True if the substitution succeeds, false on error. Will succeed even if nothing is modified
|
||||
* @return True if the substitution succeeds, false on error. Will succeed even if nothing is modified
|
||||
*/
|
||||
@Override
|
||||
@RWLockLabel.SelfSynchronized
|
||||
public boolean processSubstituteCommand(@NotNull VimEditor editor,
|
||||
@NotNull VimCaret caret,
|
||||
@NotNull ExecutionContext context,
|
||||
@NotNull LineRange range,
|
||||
@NotNull @NonNls String excmd,
|
||||
@NotNull @NonNls String exarg,
|
||||
@NotNull VimLContext parent) {
|
||||
if (globalIjOptions(injector).getUseNewRegex()) return super.processSubstituteCommand(editor, caret, range, excmd, exarg, parent);
|
||||
if (globalIjOptions(injector).getUseNewRegex()) {
|
||||
return super.processSubstituteCommand(editor, caret, context, range, excmd, exarg, parent);
|
||||
}
|
||||
|
||||
// Explicitly exit visual mode here, so that visual mode marks don't change when we move the cursor to a match.
|
||||
List<ExException> exceptions = new ArrayList<>();
|
||||
@ -808,7 +812,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
||||
RangeHighlighter hl =
|
||||
SearchHighlightsHelper.addSubstitutionConfirmationHighlight(((IjVimEditor)editor).getEditor(), startoff,
|
||||
endoff);
|
||||
final ReplaceConfirmationChoice choice = confirmChoice(((IjVimEditor)editor).getEditor(), match, ((IjVimCaret)caret).getCaret(), startoff);
|
||||
final ReplaceConfirmationChoice choice = confirmChoice(((IjVimEditor)editor).getEditor(), context, match, ((IjVimCaret)caret).getCaret(), startoff);
|
||||
((IjVimEditor)editor).getEditor().getMarkupModel().removeHighlighter(hl);
|
||||
switch (choice) {
|
||||
case SUBSTITUTE_THIS:
|
||||
@ -837,8 +841,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
||||
caret.moveToOffset(startoff);
|
||||
if (expression != null) {
|
||||
try {
|
||||
match =
|
||||
expression.evaluate(editor, injector.getExecutionContextManager().onEditor(editor, null), parent).toInsertableString();
|
||||
match = expression.evaluate(editor, context, parent).toInsertableString();
|
||||
}
|
||||
catch (Exception e) {
|
||||
exceptions.add((ExException)e);
|
||||
@ -989,7 +992,9 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
||||
return new Pair<>(true, new Triple<>(regmatch, pattern, sp));
|
||||
}
|
||||
|
||||
private static @NotNull ReplaceConfirmationChoice confirmChoice(@NotNull Editor editor, @NotNull String match, @NotNull Caret caret, int startoff) {
|
||||
private static @NotNull ReplaceConfirmationChoice confirmChoice(@NotNull Editor editor,
|
||||
@NotNull ExecutionContext context,
|
||||
@NotNull String match, @NotNull Caret caret, int startoff) {
|
||||
final Ref<ReplaceConfirmationChoice> result = Ref.create(ReplaceConfirmationChoice.QUIT);
|
||||
final Function1<KeyStroke, Boolean> keyStrokeProcessor = key -> {
|
||||
final ReplaceConfirmationChoice choice;
|
||||
@ -1023,7 +1028,6 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
||||
else {
|
||||
// XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for this method
|
||||
final ExEntryPanel exEntryPanel = ExEntryPanel.getInstanceWithoutShortcuts();
|
||||
ExecutionContext.Editor context = injector.getExecutionContextManager().onEditor(new IjVimEditor(editor), null);
|
||||
exEntryPanel.activate(editor, ((IjEditorExecutionContext)context).getContext(), MessageHelper.message("replace.with.0", match), "", 1);
|
||||
new IjVimCaret(caret).moveToOffset(startoff);
|
||||
ModalEntry.INSTANCE.activate(new IjVimEditor(editor), keyStrokeProcessor);
|
||||
@ -1081,9 +1085,9 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
||||
private @Nullable TextRange findNextSearchForGn(@NotNull VimEditor editor, int count, boolean forwards) {
|
||||
if (forwards) {
|
||||
final EnumSet<SearchOptions> searchOptions = EnumSet.of(SearchOptions.WRAP, SearchOptions.WHOLE_FILE);
|
||||
return VimInjectorKt.getInjector().getSearchHelper().findPattern(editor, getLastUsedPattern(), editor.primaryCaret().getOffset().getPoint(), count, searchOptions);
|
||||
return VimInjectorKt.getInjector().getSearchHelper().findPattern(editor, getLastUsedPattern(), editor.primaryCaret().getOffset(), count, searchOptions);
|
||||
} else {
|
||||
return searchBackward(editor, editor.primaryCaret().getOffset().getPoint(), count);
|
||||
return searchBackward(editor, editor.primaryCaret().getOffset(), count);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,7 +117,7 @@ internal object IdeaSelectionControl {
|
||||
is Mode.VISUAL -> VimPlugin.getVisualMotion().enterVisualMode(editor.vim, mode.selectionType)
|
||||
is Mode.SELECT -> VimPlugin.getVisualMotion().enterSelectMode(editor.vim, mode.selectionType)
|
||||
is Mode.INSERT -> VimPlugin.getChange()
|
||||
.insertBeforeCursor(editor.vim, injector.executionContextManager.onEditor(editor.vim))
|
||||
.insertBeforeCursor(editor.vim, injector.executionContextManager.getEditorExecutionContext(editor.vim))
|
||||
|
||||
is Mode.NORMAL -> Unit
|
||||
else -> error("Unexpected mode: $mode")
|
||||
|
@ -337,7 +337,7 @@ internal abstract class VimKeyHandler(nextHandler: EditorActionHandler?) : Octop
|
||||
|
||||
override fun executeHandler(editor: Editor, caret: Caret?, dataContext: DataContext?) {
|
||||
val enterKey = key(key)
|
||||
val context = injector.executionContextManager.onEditor(editor.vim, dataContext?.vim)
|
||||
val context = dataContext?.vim ?: injector.executionContextManager.getEditorExecutionContext(editor.vim)
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
keyHandler.handleKey(editor.vim, enterKey, context, keyHandler.keyHandlerState)
|
||||
}
|
||||
|
@ -90,6 +90,8 @@ private fun Editor.updatePrimaryCaretVisualAttributes() {
|
||||
|
||||
// Make sure the caret is visible as soon as it's set. It might be invisible while blinking
|
||||
// NOTE: At the moment, this causes project leak in tests
|
||||
// IJPL-928 - this will be fixed in 2024.2
|
||||
// [VERSION UPDATE] 2024.2 - remove if wrapping
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||
(this as? EditorEx)?.setCaretVisible(true)
|
||||
}
|
||||
|
@ -11,8 +11,8 @@ package com.maddyhome.idea.vim.helper
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.maddyhome.idea.vim.action.change.Extension
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.ui.ModalEntry
|
||||
@ -23,7 +23,7 @@ import javax.swing.KeyStroke
|
||||
@Service
|
||||
internal class CommandLineHelper : VimCommandLineHelper {
|
||||
|
||||
override fun inputString(vimEditor: VimEditor, prompt: String, finishOn: Char?): String? {
|
||||
override fun inputString(vimEditor: VimEditor, context: ExecutionContext, prompt: String, finishOn: Char?): String? {
|
||||
val editor = vimEditor.ij
|
||||
if (vimEditor.vimStateMachine.isDotRepeatInProgress) {
|
||||
val input = Extension.consumeString()
|
||||
@ -53,7 +53,7 @@ internal class CommandLineHelper : VimCommandLineHelper {
|
||||
var text: String? = null
|
||||
// XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for input()
|
||||
val exEntryPanel = ExEntryPanel.getInstanceWithoutShortcuts()
|
||||
exEntryPanel.activate(editor, injector.executionContextManager.onEditor(editor.vim).ij, prompt.ifEmpty { " " }, "", 1)
|
||||
exEntryPanel.activate(editor, context.ij, prompt.ifEmpty { " " }, "", 1)
|
||||
ModalEntry.activate(editor.vim) { key: KeyStroke ->
|
||||
return@activate when {
|
||||
key.isCloseKeyStroke() -> {
|
||||
|
@ -14,6 +14,7 @@ import com.intellij.openapi.editor.ex.util.EditorUtil
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.UserDataHolder
|
||||
|
||||
@Deprecated("Do not use context wrappers, use existing provided contexts. If no context available, use `injector.getExecutionContextManager().getEditorExecutionContext(editor)`")
|
||||
internal class EditorDataContext @Deprecated("Please use `init` method") constructor(
|
||||
private val editor: Editor,
|
||||
private val editorContext: DataContext,
|
||||
|
@ -211,15 +211,12 @@ public class EditorHelper {
|
||||
return injector.getEditorGroup().getEditors(new IjVimDocument(doc)).stream().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public static @NotNull String pad(final @NotNull Editor editor,
|
||||
@NotNull DataContext context,
|
||||
int line,
|
||||
final int to) {
|
||||
public static @NotNull String pad(final @NotNull Editor editor, int line, final int to) {
|
||||
final int len = EngineEditorHelperKt.lineLength(new IjVimEditor(editor), line);
|
||||
if (len >= to) return "";
|
||||
|
||||
final int limit = to - len;
|
||||
return IndentConfig.create(editor, context).createIndentBySize(limit);
|
||||
return IndentConfig.create(editor).createIndentBySize(limit);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -326,7 +323,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,6 +21,8 @@ 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
|
||||
@ -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
|
||||
@ -34,7 +35,6 @@ import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.NativeAction
|
||||
import com.maddyhome.idea.vim.api.VimActionExecutor
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
|
||||
import com.maddyhome.idea.vim.newapi.IjNativeAction
|
||||
@ -78,6 +78,7 @@ internal class IjActionExecutor : VimActionExecutor {
|
||||
val dataContext = DataContextWrapper(context.ij)
|
||||
dataContext.putUserData(runFromVimKey, true)
|
||||
|
||||
val actionId = ActionManager.getInstance().getId(ijAction)
|
||||
val event = AnActionEvent(
|
||||
null,
|
||||
dataContext,
|
||||
@ -86,12 +87,20 @@ 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
|
||||
// Another related issue: VIM-2604
|
||||
ijAction.beforeActionPerformedUpdate(event)
|
||||
if (!event.presentation.isEnabled) return false
|
||||
|
||||
// This is a hack to fix the tests and fix VIM-3332
|
||||
// We should get rid of it in VIM-3376
|
||||
if (actionId == "RunClass" || actionId == IdeActions.ACTION_COMMENT_LINE || actionId == IdeActions.ACTION_COMMENT_BLOCK) {
|
||||
ijAction.beforeActionPerformedUpdate(event)
|
||||
if (!event.presentation.isEnabled) return false
|
||||
} else {
|
||||
if (!ActionUtil.lastUpdateAndCheckDumb(ijAction, event, false)) return false
|
||||
}
|
||||
if (ijAction is ActionGroup && !event.presentation.isPerformGroup) {
|
||||
// Some ActionGroups should not be performed, but shown as a popup
|
||||
val popup = JBPopupFactory.getInstance()
|
||||
@ -215,7 +224,7 @@ internal class IjActionExecutor : VimActionExecutor {
|
||||
CommandProcessor.getInstance()
|
||||
.executeCommand(
|
||||
editor.ij.project,
|
||||
{ cmd.execute(editor, injector.executionContextManager.onEditor(editor, context), operatorArguments) },
|
||||
{ cmd.execute(editor, context, operatorArguments) },
|
||||
cmd.id,
|
||||
DocCommandGroupId.noneGroupId(editor.ij.document),
|
||||
UndoConfirmationPolicy.DEFAULT,
|
||||
|
@ -14,7 +14,6 @@ import com.intellij.openapi.editor.VisualPosition
|
||||
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.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.VimVisualPosition
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
@ -51,8 +50,8 @@ internal class IjEditorHelper : EngineEditorHelper {
|
||||
return EditorHelper.getVisualLineAtBottomOfScreen(editor.ij)
|
||||
}
|
||||
|
||||
override fun pad(editor: VimEditor, context: ExecutionContext, line: Int, to: Int): String {
|
||||
return EditorHelper.pad(editor.ij, context.ij, line, to)
|
||||
override fun pad(editor: VimEditor, line: Int, to: Int): String {
|
||||
return EditorHelper.pad(editor.ij, line, to)
|
||||
}
|
||||
|
||||
override fun inlayAwareOffsetToVisualPosition(editor: VimEditor, offset: Int): VimVisualPosition {
|
||||
|
@ -56,7 +56,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
|
||||
|
@ -632,113 +632,6 @@ public class SearchHelper {
|
||||
return new TextRange(bstart, bend + 1);
|
||||
}
|
||||
|
||||
private static int findMatchingBlockCommentPair(@NotNull PsiComment comment,
|
||||
int pos,
|
||||
@Nullable String prefix,
|
||||
@Nullable String suffix) {
|
||||
if (prefix != null && suffix != null) {
|
||||
// TODO: Try to get rid of `getText()` because it takes a lot of time to calculate the string
|
||||
final String commentText = comment.getText();
|
||||
if (commentText.startsWith(prefix) && commentText.endsWith(suffix)) {
|
||||
final int endOffset = comment.getTextOffset() + comment.getTextLength();
|
||||
if (pos < comment.getTextOffset() + prefix.length()) {
|
||||
return endOffset;
|
||||
}
|
||||
else if (pos >= endOffset - suffix.length()) {
|
||||
return comment.getTextOffset();
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int findMatchingBlockCommentPair(@NotNull PsiElement element, int pos) {
|
||||
final Language language = element.getLanguage();
|
||||
final Commenter commenter = LanguageCommenters.INSTANCE.forLanguage(language);
|
||||
final PsiComment comment = PsiTreeUtil.getParentOfType(element, PsiComment.class, false);
|
||||
if (comment != null) {
|
||||
final int ret = findMatchingBlockCommentPair(comment, pos, commenter.getBlockCommentPrefix(),
|
||||
commenter.getBlockCommentSuffix());
|
||||
if (ret >= 0) {
|
||||
return ret;
|
||||
}
|
||||
if (commenter instanceof CodeDocumentationAwareCommenter docCommenter) {
|
||||
return findMatchingBlockCommentPair(comment, pos, docCommenter.getDocumentationCommentPrefix(),
|
||||
docCommenter.getDocumentationCommentSuffix());
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* This looks on the current line, starting at the cursor position for one of {, }, (, ), [, or ]. It then searches
|
||||
* forward or backward, as appropriate for the associated match pair. String in double quotes are skipped over.
|
||||
* Single characters in single quotes are skipped too.
|
||||
*
|
||||
* @param editor The editor to search in
|
||||
* @return The offset within the editor of the found character or -1 if no match was found or none of the characters
|
||||
* were found on the remainder of the current line.
|
||||
*/
|
||||
public static int findMatchingPairOnCurrentLine(@NotNull Editor editor, @NotNull Caret caret) {
|
||||
int pos = caret.getOffset();
|
||||
|
||||
final int commentPos = findMatchingComment(editor, pos);
|
||||
if (commentPos >= 0) {
|
||||
return commentPos;
|
||||
}
|
||||
|
||||
int line = caret.getLogicalPosition().line;
|
||||
final IjVimEditor vimEditor = new IjVimEditor(editor);
|
||||
int end = EngineEditorHelperKt.getLineEndOffset(vimEditor, line, true);
|
||||
|
||||
// To handle the case where visual mode allows the user to go past the end of the line,
|
||||
// which will prevent loc from finding a pairable character below
|
||||
if (pos > 0 && pos == end) {
|
||||
pos = end - 1;
|
||||
}
|
||||
|
||||
final String pairChars = parseMatchPairsOption(vimEditor);
|
||||
|
||||
CharSequence chars = editor.getDocument().getCharsSequence();
|
||||
int loc = -1;
|
||||
// Search the remainder of the current line for one of the candidate characters
|
||||
while (pos < end) {
|
||||
loc = pairChars.indexOf(chars.charAt(pos));
|
||||
if (loc >= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
pos++;
|
||||
}
|
||||
|
||||
int res = -1;
|
||||
// If we found one ...
|
||||
if (loc >= 0) {
|
||||
// What direction should we go now (-1 is backward, 1 is forward)
|
||||
Direction dir = loc % 2 == 0 ? Direction.FORWARDS : Direction.BACKWARDS;
|
||||
// Which character did we find and which should we now search for
|
||||
char found = pairChars.charAt(loc);
|
||||
char match = pairChars.charAt(loc + dir.toInt());
|
||||
res = findBlockLocation(chars, found, match, dir, pos, 1, true);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* If on the start/end of a block comment, jump to the matching of that comment, or vice versa.
|
||||
*/
|
||||
private static int findMatchingComment(@NotNull Editor editor, int pos) {
|
||||
final PsiFile psiFile = PsiHelper.getFile(editor);
|
||||
if (psiFile != null) {
|
||||
final PsiElement element = psiFile.findElementAt(pos);
|
||||
if (element != null) {
|
||||
return findMatchingBlockCommentPair(element, pos);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int findBlockLocation(@NotNull CharSequence chars,
|
||||
char found,
|
||||
char match,
|
||||
|
@ -10,10 +10,14 @@ package com.maddyhome.idea.vim.helper
|
||||
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
|
||||
import com.intellij.openapi.application.ApplicationInfo
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.openapi.command.undo.UndoManager
|
||||
import com.intellij.openapi.components.Service
|
||||
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.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
@ -21,6 +25,8 @@ import com.maddyhome.idea.vim.common.ChangesListener
|
||||
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
|
||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.state.mode.inVisualMode
|
||||
import com.maddyhome.idea.vim.undo.UndoRedoBase
|
||||
|
||||
/**
|
||||
@ -37,22 +43,11 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
||||
val scrollingModel = editor.getScrollingModel()
|
||||
scrollingModel.accumulateViewportChanges()
|
||||
|
||||
if (injector.globalIjOptions().oldundo) {
|
||||
SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
|
||||
// [VERSION UPDATE] 241+ remove this if
|
||||
if (ApplicationInfo.getInstance().build.baselineVersion >= 241) {
|
||||
undoFor241plus(editor, undoManager, fileEditor)
|
||||
} else {
|
||||
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
|
||||
editor.runWithChangeTracking {
|
||||
undoManager.undo(fileEditor)
|
||||
|
||||
// We execute undo one more time if the previous one just restored selection
|
||||
if (!hasChanges && hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
|
||||
undoManager.undo(fileEditor)
|
||||
}
|
||||
}
|
||||
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
removeSelections(editor)
|
||||
}
|
||||
undoForLessThan241(undoManager, fileEditor, editor)
|
||||
}
|
||||
|
||||
scrollingModel.flushViewportChanges()
|
||||
@ -62,6 +57,61 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
||||
return false
|
||||
}
|
||||
|
||||
private fun undoForLessThan241(
|
||||
undoManager: UndoManager,
|
||||
fileEditor: TextEditor,
|
||||
editor: VimEditor,
|
||||
) {if (injector.globalIjOptions().oldundo) {
|
||||
SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
|
||||
restoreVisualMode(editor)
|
||||
} else {
|
||||
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
|
||||
editor.runWithChangeTracking {
|
||||
undoManager.undo(fileEditor)
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun undoFor241plus(
|
||||
editor: VimEditor,
|
||||
undoManager: UndoManager,
|
||||
fileEditor: TextEditor,
|
||||
) {
|
||||
if (injector.globalIjOptions().oldundo) {
|
||||
// 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)
|
||||
}
|
||||
} else {
|
||||
runWithBooleanRegistryOption("ide.undo.transparent.caret.movement", true) {
|
||||
undoManager.undo(fileEditor)
|
||||
}
|
||||
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
removeSelections(editor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasSelection(editor: VimEditor): Boolean {
|
||||
return editor.primaryCaret().ij.hasSelection()
|
||||
}
|
||||
@ -72,8 +122,25 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
||||
val fileEditor = TextEditorProvider.getInstance().getTextEditor(editor.ij)
|
||||
val undoManager = UndoManager.getInstance(project)
|
||||
if (undoManager.isRedoAvailable(fileEditor)) {
|
||||
if (injector.globalIjOptions().oldundo) {
|
||||
// [VERSION UPDATE] 241+ remove this if
|
||||
if (ApplicationInfo.getInstance().build.baselineVersion >= 241) {
|
||||
redoFor241Plus(undoManager, fileEditor, editor)
|
||||
} else {
|
||||
redoForLessThan241(undoManager, fileEditor, editor)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun redoForLessThan241(
|
||||
undoManager: UndoManager,
|
||||
fileEditor: TextEditor,
|
||||
editor: VimEditor,
|
||||
) {if (injector.globalIjOptions().oldundo) {
|
||||
SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
|
||||
restoreVisualMode(editor)
|
||||
} else {
|
||||
undoManager.redo(fileEditor)
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
@ -83,19 +150,50 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
||||
editor.runWithChangeTracking {
|
||||
undoManager.redo(fileEditor)
|
||||
|
||||
// We execute undo one more time if the previous one just restored selection
|
||||
if (!hasChanges && hasSelection(editor) && undoManager.isRedoAvailable(fileEditor)) {
|
||||
undoManager.redo(fileEditor)
|
||||
}
|
||||
}
|
||||
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
removeSelections(editor)
|
||||
// We execute undo one more time if the previous one just restored selection
|
||||
if (!hasChanges && hasSelection(editor) && undoManager.isRedoAvailable(fileEditor)) {
|
||||
undoManager.redo(fileEditor)
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
removeSelections(editor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun redoFor241Plus(
|
||||
undoManager: UndoManager,
|
||||
fileEditor: TextEditor,
|
||||
editor: VimEditor,
|
||||
) {
|
||||
if (injector.globalIjOptions().oldundo) {
|
||||
undoManager.redo(fileEditor)
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
editor.carets().forEach { it.ij.removeSelection() }
|
||||
}
|
||||
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
|
||||
editor.runWithChangeTracking {
|
||||
undoManager.redo(fileEditor)
|
||||
|
||||
// We execute undo one more time if the previous one just restored selection
|
||||
if (!hasChanges && hasSelection(editor) && undoManager.isRedoAvailable(fileEditor)) {
|
||||
undoManager.redo(fileEditor)
|
||||
}
|
||||
}
|
||||
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
removeSelections(editor)
|
||||
}
|
||||
} else {
|
||||
runWithBooleanRegistryOption("ide.undo.transparent.caret.movement", true) {
|
||||
undoManager.redo(fileEditor)
|
||||
}
|
||||
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
removeSelections(editor)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun removeSelections(editor: VimEditor) {
|
||||
@ -109,6 +207,17 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun runWithBooleanRegistryOption(option: String, value: Boolean, block: () -> Unit) {
|
||||
val registry = Registry.get(option)
|
||||
val oldValue = registry.asBoolean()
|
||||
registry.setValue(value)
|
||||
try {
|
||||
block()
|
||||
} finally {
|
||||
registry.setValue(oldValue)
|
||||
}
|
||||
}
|
||||
|
||||
private fun VimEditor.runWithChangeTracking(block: ChangeTracker.() -> Unit) {
|
||||
val tracker = ChangeTracker(this)
|
||||
tracker.block()
|
||||
@ -131,4 +240,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ import com.intellij.codeInsight.template.TemplateManagerListener
|
||||
import com.intellij.codeInsight.template.impl.TemplateState
|
||||
import com.intellij.find.FindModelListener
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.AnActionResult
|
||||
@ -27,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
|
||||
@ -56,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) {
|
||||
@ -64,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
|
||||
@ -95,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
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,7 +182,7 @@ internal object IdeaSpecifics {
|
||||
if (editor.vim.inNormalMode) {
|
||||
VimPlugin.getChange().insertBeforeCursor(
|
||||
editor.vim,
|
||||
injector.executionContextManager.onEditor(editor.vim),
|
||||
injector.executionContextManager.getEditorExecutionContext(editor.vim),
|
||||
)
|
||||
KeyHandler.getInstance().reset(editor.vim)
|
||||
}
|
||||
@ -213,5 +232,7 @@ internal class FindActionIdAction : DumbAwareToggleAction() {
|
||||
override fun setSelected(e: AnActionEvent, state: Boolean) {
|
||||
injector.globalIjOptions().trackactionids = !injector.globalIjOptions().trackactionids
|
||||
}
|
||||
|
||||
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
|
||||
}
|
||||
//endregion
|
||||
|
@ -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
|
||||
@ -79,7 +83,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
|
||||
@ -92,6 +95,7 @@ import com.maddyhome.idea.vim.helper.vimDisabled
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.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
|
||||
@ -101,7 +105,6 @@ 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
|
||||
@ -264,12 +267,10 @@ 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")
|
||||
|
||||
val listenersDisposable = Disposer.newDisposable(disposable)
|
||||
editor.putUserData(editorListenersDisposableKey, listenersDisposable)
|
||||
@ -358,7 +359,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)
|
||||
@ -429,8 +439,6 @@ internal object VimListenerManager {
|
||||
|
||||
event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused))
|
||||
}
|
||||
|
||||
VimStandalonePluginUpdateChecker.instance.pluginUsed()
|
||||
}
|
||||
|
||||
override fun editorReleased(event: EditorFactoryEvent) {
|
||||
@ -531,15 +539,15 @@ internal object VimListenerManager {
|
||||
// When starting on an empty line and dragging vertically upwards onto
|
||||
// another line, the selection should include the entirety of the empty line
|
||||
caret.setSelection(
|
||||
ijVimEditor.coerceOffset(endOffset + 1).point,
|
||||
ijVimEditor.coerceOffset(startOffset).point,
|
||||
ijVimEditor.coerceOffset(endOffset + 1),
|
||||
ijVimEditor.coerceOffset(startOffset),
|
||||
)
|
||||
} else if (lineEnd == startOffset + 1 && startOffset == endOffset) {
|
||||
// When dragging left from EOL on a non-empty line, the selection
|
||||
// should include the last character on the line
|
||||
caret.setSelection(
|
||||
ijVimEditor.coerceOffset(lineEnd).point,
|
||||
ijVimEditor.coerceOffset(lineEnd - 1).point,
|
||||
ijVimEditor.coerceOffset(lineEnd),
|
||||
ijVimEditor.coerceOffset(lineEnd - 1),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -12,16 +12,8 @@ import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.UserDataHolder
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
|
||||
internal open class IjEditorExecutionContext(override val context: DataContext) : ExecutionContext.Editor {
|
||||
override fun updateEditor(editor: VimEditor): ExecutionContext {
|
||||
return IjEditorExecutionContext(injector.executionContextManager.onEditor(editor, context.vim).ij)
|
||||
}
|
||||
}
|
||||
|
||||
internal class IjCaretAndEditorExecutionContext(override val context: DataContext) : IjEditorExecutionContext(context), ExecutionContext.CaretAndEditor
|
||||
internal open class IjEditorExecutionContext(override val context: DataContext) : ExecutionContext
|
||||
|
||||
// This key is stored in data context when the action is started from vim
|
||||
internal val runFromVimKey = Key.create<Boolean>("RunFromVim")
|
||||
|
@ -9,23 +9,15 @@
|
||||
package com.maddyhome.idea.vim.newapi
|
||||
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.editor.actionSystem.CaretSpecificDataContext
|
||||
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.VimCaret
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.helper.EditorDataContext
|
||||
|
||||
@Service
|
||||
internal class IjExecutionContextManager : ExecutionContextManagerBase() {
|
||||
override fun onEditor(editor: VimEditor, prevContext: ExecutionContext?): ExecutionContext.Editor {
|
||||
if (prevContext is ExecutionContext.CaretAndEditor) {
|
||||
return prevContext
|
||||
}
|
||||
return IjEditorExecutionContext(EditorDataContext.init((editor as IjVimEditor).editor, prevContext?.ij))
|
||||
}
|
||||
|
||||
override fun onCaret(caret: VimCaret, prevContext: ExecutionContext.Editor): ExecutionContext.CaretAndEditor {
|
||||
return IjCaretAndEditorExecutionContext(CaretSpecificDataContext.create(prevContext.ij, caret.ij))
|
||||
override fun getEditorExecutionContext(editor: VimEditor): ExecutionContext {
|
||||
return EditorUtil.getEditorDataContext(editor.ij).vim
|
||||
}
|
||||
}
|
||||
|
@ -10,12 +10,10 @@ package com.maddyhome.idea.vim.newapi
|
||||
|
||||
import com.intellij.openapi.editor.RangeMarker
|
||||
import com.maddyhome.idea.vim.common.LiveRange
|
||||
import com.maddyhome.idea.vim.common.Offset
|
||||
import com.maddyhome.idea.vim.common.offset
|
||||
|
||||
internal class IjLiveRange(val marker: RangeMarker) : LiveRange {
|
||||
override val startOffset: Offset
|
||||
get() = marker.startOffset.offset
|
||||
override val startOffset: Int
|
||||
get() = marker.startOffset
|
||||
}
|
||||
|
||||
public val RangeMarker.vim: LiveRange
|
||||
|
@ -34,4 +34,8 @@ internal class IjNativeActionManager : NativeActionManager {
|
||||
public val AnAction.vim: IjNativeAction
|
||||
get() = IjNativeAction(this)
|
||||
|
||||
public class IjNativeAction(override val action: AnAction) : NativeAction
|
||||
public class IjNativeAction(override val action: AnAction) : NativeAction {
|
||||
override fun toString(): String {
|
||||
return "IjNativeAction(action=$action)"
|
||||
}
|
||||
}
|
||||
|
@ -21,10 +21,7 @@ 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.EditorLine
|
||||
import com.maddyhome.idea.vim.common.LiveRange
|
||||
import com.maddyhome.idea.vim.common.Offset
|
||||
import com.maddyhome.idea.vim.common.offset
|
||||
import com.maddyhome.idea.vim.group.visual.VisualChange
|
||||
import com.maddyhome.idea.vim.helper.lastSelectionInfo
|
||||
import com.maddyhome.idea.vim.helper.markStorage
|
||||
@ -78,8 +75,8 @@ internal class IjVimCaret(val caret: Caret) : VimCaretBase() {
|
||||
}
|
||||
override val editor: VimEditor
|
||||
get() = IjVimEditor(caret.editor)
|
||||
override val offset: Offset
|
||||
get() = caret.offset.offset
|
||||
override val offset: Int
|
||||
get() = caret.offset
|
||||
override var vimLastColumn: Int
|
||||
get() = caret.vimLastColumn
|
||||
set(value) {
|
||||
@ -118,8 +115,8 @@ internal class IjVimCaret(val caret: Caret) : VimCaretBase() {
|
||||
this.caret.moveToLogicalPosition(LogicalPosition(position.line, position.column, position.leansForward))
|
||||
}
|
||||
|
||||
override fun getLine(): EditorLine.Pointer {
|
||||
return EditorLine.Pointer.init(caret.logicalPosition.line, editor)
|
||||
override fun getLine(): Int {
|
||||
return caret.logicalPosition.line
|
||||
}
|
||||
|
||||
override fun hasSelection(): Boolean {
|
||||
@ -164,8 +161,8 @@ internal class IjVimCaret(val caret: Caret) : VimCaretBase() {
|
||||
return this
|
||||
}
|
||||
|
||||
override fun setSelection(start: Offset, end: Offset) {
|
||||
caret.setSelection(start.point, end.point)
|
||||
override fun setSelection(start: Int, end: Int) {
|
||||
caret.setSelection(start, end)
|
||||
}
|
||||
|
||||
override fun removeSelection() {
|
||||
|
@ -14,7 +14,6 @@ import com.intellij.openapi.editor.event.DocumentListener
|
||||
import com.maddyhome.idea.vim.api.VimDocument
|
||||
import com.maddyhome.idea.vim.common.ChangesListener
|
||||
import com.maddyhome.idea.vim.common.LiveRange
|
||||
import com.maddyhome.idea.vim.common.Offset
|
||||
|
||||
internal class IjVimDocument(val document: Document) : VimDocument {
|
||||
|
||||
@ -41,7 +40,7 @@ internal class IjVimDocument(val document: Document) : VimDocument {
|
||||
document.removeDocumentListener(nativeListener)
|
||||
}
|
||||
|
||||
override fun getOffsetGuard(offset: Offset): LiveRange? {
|
||||
return document.getOffsetGuard(offset.point)?.vim
|
||||
override fun getOffsetGuard(offset: Int): LiveRange? {
|
||||
return document.getOffsetGuard(offset)?.vim
|
||||
}
|
||||
}
|
||||
|
@ -35,13 +35,11 @@ import com.maddyhome.idea.vim.api.VimScrollingModel
|
||||
import com.maddyhome.idea.vim.api.VimSelectionModel
|
||||
import com.maddyhome.idea.vim.api.VimVisualPosition
|
||||
import com.maddyhome.idea.vim.api.VirtualFile
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.common.EditorLine
|
||||
import com.maddyhome.idea.vim.common.IndentConfig
|
||||
import com.maddyhome.idea.vim.common.LiveRange
|
||||
import com.maddyhome.idea.vim.common.Offset
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.common.offset
|
||||
import com.maddyhome.idea.vim.group.visual.vimSetSystemBlockSelectionSilently
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
import com.maddyhome.idea.vim.helper.StrictMode
|
||||
@ -90,18 +88,18 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
|
||||
return editor.document.lineCount
|
||||
}
|
||||
|
||||
override fun deleteRange(leftOffset: Offset, rightOffset: Offset) {
|
||||
editor.document.deleteString(leftOffset.point, rightOffset.point)
|
||||
override fun deleteRange(leftOffset: Int, rightOffset: Int) {
|
||||
editor.document.deleteString(leftOffset, rightOffset)
|
||||
}
|
||||
|
||||
override fun addLine(atPosition: EditorLine.Offset): EditorLine.Pointer {
|
||||
val offset: Int = if (atPosition.line < lineCount()) {
|
||||
override fun addLine(atPosition: Int): Int {
|
||||
val offset: Int = if (atPosition < lineCount()) {
|
||||
// The new line character is inserted before the new line char of the previous line. So it works line an enter
|
||||
// on a line end. I believe that the correct implementation would be to insert the new line char after the
|
||||
// \n of the previous line, however at the moment this won't update the mark on this line.
|
||||
// https://youtrack.jetbrains.com/issue/IDEA-286587
|
||||
|
||||
val lineStart = (editor.document.getLineStartOffset(atPosition.line) - 1).coerceAtLeast(0)
|
||||
val lineStart = (editor.document.getLineStartOffset(atPosition) - 1).coerceAtLeast(0)
|
||||
val guard = editor.document.getOffsetGuard(lineStart)
|
||||
if (guard != null && guard.endOffset == lineStart + 1) {
|
||||
// Dancing around guarded blocks. It may happen that this concrete position is locked, but the next
|
||||
@ -116,11 +114,11 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
|
||||
fileSize().toInt()
|
||||
}
|
||||
editor.document.insertString(offset, "\n")
|
||||
return EditorLine.Pointer.init(atPosition.line, this)
|
||||
return atPosition
|
||||
}
|
||||
|
||||
override fun insertText(atPosition: Offset, text: CharSequence) {
|
||||
editor.document.insertString(atPosition.point, text)
|
||||
override fun insertText(atPosition: Int, text: CharSequence) {
|
||||
editor.document.insertString(atPosition, text)
|
||||
}
|
||||
|
||||
override fun replaceString(start: Int, end: Int, newString: String) {
|
||||
@ -128,13 +126,13 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
|
||||
}
|
||||
|
||||
// TODO: 30.12.2021 Is end offset inclusive?
|
||||
override fun getLineRange(line: EditorLine.Pointer): Pair<Offset, Offset> {
|
||||
override fun getLineRange(line: Int): Pair<Int, Int> {
|
||||
// TODO: 30.12.2021 getLineEndOffset returns the same value for "xyz" and "xyz\n"
|
||||
return editor.document.getLineStartOffset(line.line).offset to editor.document.getLineEndOffset(line.line).offset
|
||||
return editor.document.getLineStartOffset(line) to editor.document.getLineEndOffset(line)
|
||||
}
|
||||
|
||||
override fun getLine(offset: Offset): EditorLine.Pointer {
|
||||
return EditorLine.Pointer.init(editor.offsetToLogicalPosition(offset.point).line, this)
|
||||
override fun getLine(offset: Int): Int {
|
||||
return editor.offsetToLogicalPosition(offset).line
|
||||
}
|
||||
|
||||
override fun carets(): List<VimCaret> {
|
||||
@ -203,15 +201,15 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
|
||||
return editor.isOneLineMode
|
||||
}
|
||||
|
||||
override fun getText(left: Offset, right: Offset): CharSequence {
|
||||
return editor.document.charsSequence.subSequence(left.point, right.point)
|
||||
override fun getText(left: Int, right: Int): CharSequence {
|
||||
return editor.document.charsSequence.subSequence(left, right)
|
||||
}
|
||||
|
||||
override fun search(
|
||||
pair: Pair<Offset, Offset>,
|
||||
pair: Pair<Int, Int>,
|
||||
editor: VimEditor,
|
||||
shiftType: LineDeleteShift,
|
||||
): Pair<Pair<Offset, Offset>, LineDeleteShift>? {
|
||||
): Pair<Pair<Int, Int>, LineDeleteShift>? {
|
||||
val ijEditor = (editor as IjVimEditor).editor
|
||||
return when (shiftType) {
|
||||
LineDeleteShift.NO_NL -> if (pair.noGuard(ijEditor)) return pair to shiftType else null
|
||||
@ -358,10 +356,10 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
|
||||
return EditorHelper.getVirtualFile(editor)?.getUrl()?.let { VirtualFileManager.extractProtocol(it) }
|
||||
}
|
||||
|
||||
override val projectId = editor.project?.basePath ?: DEFAULT_PROJECT_ID
|
||||
override val projectId = editor.project?.let { injector.file.getProjectId(it) } ?: DEFAULT_PROJECT_ID
|
||||
|
||||
override fun visualPositionToOffset(position: VimVisualPosition): Offset {
|
||||
return editor.visualPositionToOffset(VisualPosition(position.line, position.column, position.leansRight)).offset
|
||||
override fun visualPositionToOffset(position: VimVisualPosition): Int {
|
||||
return editor.visualPositionToOffset(VisualPosition(position.line, position.column, position.leansRight))
|
||||
}
|
||||
|
||||
override fun exitInsertMode(context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||
@ -417,8 +415,8 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
|
||||
return visualPosition.run { VimVisualPosition(line, column, leansRight) }
|
||||
}
|
||||
|
||||
override fun createLiveMarker(start: Offset, end: Offset): LiveRange {
|
||||
return editor.document.createRangeMarker(start.point, end.point).vim
|
||||
override fun createLiveMarker(start: Int, end: Int): LiveRange {
|
||||
return editor.document.createRangeMarker(start, end).vim
|
||||
}
|
||||
|
||||
/**
|
||||
@ -456,10 +454,10 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
|
||||
ijFoldRegion.isExpanded = value
|
||||
}
|
||||
}
|
||||
override val startOffset: Offset
|
||||
get() = Offset(ijFoldRegion.startOffset)
|
||||
override val endOffset: Offset
|
||||
get() = Offset(ijFoldRegion.endOffset)
|
||||
override val startOffset: Int
|
||||
get() = ijFoldRegion.startOffset
|
||||
override val endOffset: Int
|
||||
get() = ijFoldRegion.endOffset
|
||||
|
||||
}
|
||||
}
|
||||
@ -468,17 +466,17 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
|
||||
return caret
|
||||
}
|
||||
|
||||
private fun Pair<Offset, Offset>.noGuard(editor: Editor): Boolean {
|
||||
return editor.document.getRangeGuard(this.first.point, this.second.point) == null
|
||||
private fun Pair<Int, Int>.noGuard(editor: Editor): Boolean {
|
||||
return editor.document.getRangeGuard(this.first, this.second) == null
|
||||
}
|
||||
|
||||
private inline fun Pair<Offset, Offset>.shift(
|
||||
private inline fun Pair<Int, Int>.shift(
|
||||
shiftStart: Int = 0,
|
||||
shiftEnd: Int = 0,
|
||||
action: Pair<Offset, Offset>.() -> Unit,
|
||||
action: Pair<Int, Int>.() -> Unit,
|
||||
) {
|
||||
val data =
|
||||
(this.first.point + shiftStart).coerceAtLeast(0).offset to (this.second.point + shiftEnd).coerceAtLeast(0).offset
|
||||
(this.first + shiftStart).coerceAtLeast(0) to (this.second + shiftEnd).coerceAtLeast(0)
|
||||
data.action()
|
||||
}
|
||||
|
||||
@ -500,3 +498,6 @@ public val Editor.vim: VimEditor
|
||||
get() = IjVimEditor(this)
|
||||
public val VimEditor.ij: Editor
|
||||
get() = (this as IjVimEditor).editor
|
||||
|
||||
public val com.intellij.openapi.util.TextRange.vim: TextRange
|
||||
get() = TextRange(this.startOffset, this.endOffset)
|
||||
|
@ -42,6 +42,7 @@ import com.maddyhome.idea.vim.api.VimMessages
|
||||
import com.maddyhome.idea.vim.api.VimMotionGroup
|
||||
import com.maddyhome.idea.vim.api.VimOptionGroup
|
||||
import com.maddyhome.idea.vim.api.VimProcessGroup
|
||||
import com.maddyhome.idea.vim.api.VimPsiService
|
||||
import com.maddyhome.idea.vim.api.VimRegexpService
|
||||
import com.maddyhome.idea.vim.api.VimScrollGroup
|
||||
import com.maddyhome.idea.vim.api.VimSearchGroup
|
||||
@ -65,6 +66,7 @@ import com.maddyhome.idea.vim.group.FileGroup
|
||||
import com.maddyhome.idea.vim.group.GlobalIjOptions
|
||||
import com.maddyhome.idea.vim.group.HistoryGroup
|
||||
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
|
||||
@ -147,6 +149,8 @@ internal class IjVimInjector : VimInjectorBase() {
|
||||
get() = service<MacroGroup>()
|
||||
override val undo: VimUndoRedo
|
||||
get() = service<UndoRedoHelper>()
|
||||
override val psiService: VimPsiService
|
||||
get() = service<IjVimPsiService>()
|
||||
override val commandLineHelper: VimCommandLineHelper
|
||||
get() = service<CommandLineHelper>()
|
||||
override val nativeActionManager: NativeActionManager
|
||||
|
@ -11,6 +11,7 @@ package com.maddyhome.idea.vim.newapi
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.util.Ref
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.Options
|
||||
import com.maddyhome.idea.vim.api.VimCaret
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
@ -26,13 +27,11 @@ import com.maddyhome.idea.vim.helper.shouldIgnoreCase
|
||||
import com.maddyhome.idea.vim.helper.updateSearchHighlights
|
||||
import com.maddyhome.idea.vim.options.GlobalOptionChangeListener
|
||||
import com.maddyhome.idea.vim.ui.ModalEntry
|
||||
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
|
||||
import com.maddyhome.idea.vim.vimscript.model.functions.handlers.SubmatchFunctionHandler
|
||||
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser.parseExpression
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
public open class IjVimSearchGroup : VimSearchGroupBase() {
|
||||
public abstract class IjVimSearchGroup : VimSearchGroupBase() {
|
||||
|
||||
init {
|
||||
// TODO: Investigate migrating these listeners to use the effective value change listener
|
||||
@ -82,6 +81,7 @@ public open class IjVimSearchGroup : VimSearchGroupBase() {
|
||||
|
||||
override fun confirmChoice(
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
match: String,
|
||||
caret: VimCaret,
|
||||
startOffset: Int,
|
||||
@ -121,7 +121,6 @@ public open class IjVimSearchGroup : VimSearchGroupBase() {
|
||||
// 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()
|
||||
val context = injector.executionContextManager.onEditor(editor, null)
|
||||
exEntryPanel.activate(
|
||||
editor.ij,
|
||||
(context as IjEditorExecutionContext).context,
|
||||
@ -136,10 +135,6 @@ public open class IjVimSearchGroup : VimSearchGroupBase() {
|
||||
return result.get()
|
||||
}
|
||||
|
||||
override fun parseVimScriptExpression(expressionString: String): Expression? {
|
||||
return parseExpression(expressionString)
|
||||
}
|
||||
|
||||
override fun addSubstitutionConfirmationHighlight(editor: VimEditor, startOffset: Int, endOffset: Int) {
|
||||
val hl = addSubstitutionConfirmationHighlight(
|
||||
(editor as IjVimEditor).editor,
|
||||
@ -178,4 +173,4 @@ public open class IjVimSearchGroup : VimSearchGroupBase() {
|
||||
showSearchHighlight = false
|
||||
updateSearchHighlights(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,144 +13,31 @@ 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.anyNonWhitespace
|
||||
import com.maddyhome.idea.vim.api.getLineEndOffset
|
||||
import com.maddyhome.idea.vim.api.getLineStartForOffset
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.api.normalizeOffset
|
||||
import com.maddyhome.idea.vim.common.Direction
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.helper.CharacterHelper
|
||||
import com.maddyhome.idea.vim.helper.CharacterHelper.charType
|
||||
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.checkInString
|
||||
import com.maddyhome.idea.vim.helper.fileSize
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance
|
||||
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
|
||||
import it.unimi.dsi.fastutil.ints.IntComparator
|
||||
import it.unimi.dsi.fastutil.ints.IntComparators
|
||||
import java.util.*
|
||||
import java.util.function.Function
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
|
||||
@Service
|
||||
internal class IjVimSearchHelper : VimSearchHelperBase() {
|
||||
|
||||
companion object {
|
||||
private const val BLOCK_CHARS = "{}()[]<>"
|
||||
private val logger = Logger.getInstance(IjVimSearchHelper::class.java.name)
|
||||
}
|
||||
override fun findSection(
|
||||
editor: VimEditor,
|
||||
caret: ImmutableVimCaret,
|
||||
type: Char,
|
||||
direction: Int,
|
||||
count: Int,
|
||||
)
|
||||
: Int {
|
||||
val documentText: CharSequence = editor.ij.document.charsSequence
|
||||
var currentLine: Int = caret.ij.logicalPosition.line + direction
|
||||
var resultOffset = -1
|
||||
var remainingTargets = count
|
||||
|
||||
while (currentLine in 1 until editor.lineCount() && remainingTargets > 0) {
|
||||
val lineStartOffset = editor.getLineStartOffset(currentLine)
|
||||
if (lineStartOffset < documentText.length) {
|
||||
val currentChar = documentText[lineStartOffset]
|
||||
if (currentChar == type || currentChar == '\u000C') {
|
||||
resultOffset = lineStartOffset
|
||||
remainingTargets--
|
||||
}
|
||||
}
|
||||
currentLine += direction
|
||||
}
|
||||
|
||||
if (resultOffset == -1) {
|
||||
resultOffset = if (direction < 0) 0 else documentText.length - 1
|
||||
}
|
||||
|
||||
return resultOffset
|
||||
}
|
||||
|
||||
override fun findMethodEnd(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Int {
|
||||
// TODO add it to PsiService
|
||||
return PsiHelper.findMethodEnd(editor.ij, caret.ij.offset, count)
|
||||
}
|
||||
|
||||
override fun findMethodStart(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Int {
|
||||
// TODO add it to PsiService
|
||||
return PsiHelper.findMethodStart(editor.ij, caret.ij.offset, count)
|
||||
}
|
||||
|
||||
override fun findUnmatchedBlock(editor: VimEditor, caret: ImmutableVimCaret, type: Char, count: Int): Int {
|
||||
val chars: CharSequence = editor.ij.document.charsSequence
|
||||
var pos: Int = caret.ij.offset
|
||||
val loc = BLOCK_CHARS.indexOf(type)
|
||||
// What direction should we go now (-1 is backward, 1 is forward)
|
||||
val dir = if (loc % 2 == 0) Direction.BACKWARDS else Direction.FORWARDS
|
||||
// Which character did we find and which should we now search for
|
||||
val match = BLOCK_CHARS[loc]
|
||||
val found = BLOCK_CHARS[loc - dir.toInt()]
|
||||
|
||||
if (pos < chars.length && chars[pos] == type) {
|
||||
pos += dir.toInt()
|
||||
}
|
||||
return findBlockLocation(chars, found, match, dir, pos, count)
|
||||
}
|
||||
|
||||
private fun findBlockLocation(
|
||||
chars: CharSequence,
|
||||
found: Char,
|
||||
match: Char,
|
||||
dir: Direction,
|
||||
pos: Int,
|
||||
cnt: Int,
|
||||
): Int {
|
||||
var position = pos
|
||||
var count = cnt
|
||||
var res = -1
|
||||
val initialPos = position
|
||||
val initialInString = checkInString(chars, position, true)
|
||||
val inCheckPosF =
|
||||
Function { x: Int -> if (dir === Direction.BACKWARDS && x > 0) x - 1 else x + 1 }
|
||||
val inCheckPos = inCheckPosF.apply(position)
|
||||
var inString = checkInString(chars, inCheckPos, true)
|
||||
var inChar = checkInString(chars, inCheckPos, false)
|
||||
var stack = 0
|
||||
// Search to start or end of file, as appropriate
|
||||
val charsToSearch: Set<Char> = HashSet(listOf('\'', '"', '\n', match, found))
|
||||
while (position >= 0 && position < chars.length && count > 0) {
|
||||
val (c, second) = SearchHelper.findPositionOfFirstCharacter(chars, position, charsToSearch, true, dir) ?: return -1
|
||||
position = second
|
||||
// If we found a match and we're not in a string...
|
||||
if (c == match && (!inString) && !inChar) {
|
||||
// We found our match
|
||||
if (stack == 0) {
|
||||
res = position
|
||||
count--
|
||||
} else {
|
||||
stack--
|
||||
}
|
||||
} else if (c == '\n') {
|
||||
inString = false
|
||||
inChar = false
|
||||
} else if (position != initialPos) {
|
||||
// We found another character like our original - belongs to another pair
|
||||
if (!inString && !inChar && c == found) {
|
||||
stack++
|
||||
} else if (!inChar) {
|
||||
inString = checkInString(chars, inCheckPosF.apply(position), true)
|
||||
} else if (!inString) {
|
||||
inChar = checkInString(chars, inCheckPosF.apply(position), false)
|
||||
}
|
||||
}
|
||||
position += dir.toInt()
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
override fun findPattern(
|
||||
editor: VimEditor,
|
||||
pattern: String?,
|
||||
@ -173,525 +60,6 @@ internal class IjVimSearchHelper : VimSearchHelperBase() {
|
||||
else SearchHelper.findAll(editor.ij, pattern, startLine, endLine, ignoreCase)
|
||||
}
|
||||
|
||||
override fun findNextCharacterOnLine(editor: VimEditor, caret: ImmutableVimCaret, count: Int, ch: Char): Int {
|
||||
val line: Int = caret.ij.logicalPosition.line
|
||||
val start = editor.getLineStartOffset(line)
|
||||
val end = editor.getLineEndOffset(line, true)
|
||||
val chars: CharSequence = editor.ij.document.charsSequence
|
||||
var found = 0
|
||||
val step = if (count >= 0) 1 else -1
|
||||
var pos: Int = caret.ij.offset + step
|
||||
while (pos in start until end && pos < chars.length) {
|
||||
if (chars[pos] == ch) {
|
||||
found++
|
||||
if (found == abs(count)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
pos += step
|
||||
}
|
||||
|
||||
return if (found == abs(count)) {
|
||||
pos
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
override fun findWordUnderCursor(
|
||||
editor: VimEditor,
|
||||
caret: ImmutableVimCaret,
|
||||
count: Int,
|
||||
dir: Int,
|
||||
isOuter: Boolean,
|
||||
isBig: Boolean,
|
||||
hasSelection: Boolean,
|
||||
): TextRange {
|
||||
if (logger.isDebugEnabled) {
|
||||
logger.debug("count=$count")
|
||||
logger.debug("dir=$dir")
|
||||
logger.debug("isOuter=$isOuter")
|
||||
logger.debug("isBig=$isBig")
|
||||
logger.debug("hasSelection=$hasSelection")
|
||||
}
|
||||
|
||||
val chars: CharSequence = editor.ij.document.charsSequence
|
||||
//int min = EditorHelper.getLineStartOffset(editor, EditorHelper.getCurrentLogicalLine(editor));
|
||||
//int max = EditorHelper.getLineEndOffset(editor, EditorHelper.getCurrentLogicalLine(editor), true);
|
||||
val min = 0
|
||||
val max: Int = editor.ij.fileSize
|
||||
if (max == 0) return TextRange(0, 0)
|
||||
|
||||
if (logger.isDebugEnabled) {
|
||||
logger.debug("min=$min")
|
||||
logger.debug("max=$max")
|
||||
}
|
||||
|
||||
val pos: Int = caret.ij.offset
|
||||
if (chars.length <= pos) return TextRange(chars.length - 1, chars.length - 1)
|
||||
|
||||
val startSpace = charType(editor, chars[pos], isBig) === CharacterHelper.CharacterType.WHITESPACE
|
||||
// Find word start
|
||||
val onWordStart = pos == min ||
|
||||
charType(editor, chars[pos - 1], isBig) !==
|
||||
charType(editor, chars[pos], isBig)
|
||||
var start = pos
|
||||
|
||||
if (logger.isDebugEnabled) {
|
||||
logger.debug("pos=$pos")
|
||||
logger.debug("onWordStart=$onWordStart")
|
||||
}
|
||||
|
||||
if (!onWordStart && !(startSpace && isOuter) || hasSelection || count > 1 && dir == -1) {
|
||||
start = if (dir == 1) {
|
||||
findNextWord(editor, pos, -1, isBig, !isOuter)
|
||||
} else {
|
||||
findNextWord(
|
||||
editor,
|
||||
pos,
|
||||
-(count - if (onWordStart && !hasSelection) 1 else 0),
|
||||
isBig,
|
||||
!isOuter
|
||||
)
|
||||
}
|
||||
start = editor.normalizeOffset(start, false)
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled) logger.debug("start=$start")
|
||||
|
||||
// Find word end
|
||||
|
||||
// Find word end
|
||||
val onWordEnd = pos >= max - 1 ||
|
||||
charType(editor, chars[pos + 1], isBig) !==
|
||||
charType(editor, chars[pos], isBig)
|
||||
|
||||
if (logger.isDebugEnabled) logger.debug("onWordEnd=$onWordEnd")
|
||||
|
||||
var end = pos
|
||||
if (!onWordEnd || hasSelection || count > 1 && dir == 1 || startSpace && isOuter) {
|
||||
end = if (dir == 1) {
|
||||
val c = count - if (onWordEnd && !hasSelection && (!(startSpace && isOuter) || startSpace && !isOuter)) 1 else 0
|
||||
findNextWordEnd(editor, pos, c, isBig, !isOuter)
|
||||
} else {
|
||||
findNextWordEnd(editor, pos, 1, isBig, !isOuter)
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled) logger.debug("end=$end")
|
||||
|
||||
var goBack = startSpace && !hasSelection || !startSpace && hasSelection && !onWordStart
|
||||
if (dir == 1 && isOuter) {
|
||||
var firstEnd = end
|
||||
if (count > 1) {
|
||||
firstEnd = findNextWordEnd(editor, pos, 1, isBig, false)
|
||||
}
|
||||
if (firstEnd < max - 1) {
|
||||
if (charType(editor, chars[firstEnd + 1], false) !== CharacterHelper.CharacterType.WHITESPACE) {
|
||||
goBack = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dir == -1 && isOuter && startSpace) {
|
||||
if (pos > min) {
|
||||
if (charType(editor, chars[pos - 1], false) !== CharacterHelper.CharacterType.WHITESPACE) {
|
||||
goBack = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var goForward = dir == 1 && isOuter && (!startSpace && !onWordEnd || startSpace && onWordEnd && hasSelection)
|
||||
if (!goForward && dir == 1 && isOuter) {
|
||||
var firstEnd = end
|
||||
if (count > 1) {
|
||||
firstEnd = findNextWordEnd(editor, pos, 1, isBig, false)
|
||||
}
|
||||
if (firstEnd < max - 1) {
|
||||
if (charType(editor, chars[firstEnd + 1], false) !== CharacterHelper.CharacterType.WHITESPACE) {
|
||||
goForward = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!goForward && dir == 1 && isOuter && !startSpace && !hasSelection) {
|
||||
if (end < max - 1) {
|
||||
if (charType(editor, chars[end + 1], !isBig) !==
|
||||
charType(editor, chars[end], !isBig)
|
||||
) {
|
||||
goForward = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled) {
|
||||
logger.debug("goBack=$goBack")
|
||||
logger.debug("goForward=$goForward")
|
||||
}
|
||||
|
||||
if (goForward) {
|
||||
if (editor.anyNonWhitespace(end, 1)) {
|
||||
while (end + 1 < max &&
|
||||
charType(editor, chars[end + 1], false) === CharacterHelper.CharacterType.WHITESPACE
|
||||
) {
|
||||
end++
|
||||
}
|
||||
}
|
||||
}
|
||||
if (goBack) {
|
||||
if (editor.anyNonWhitespace(start, -1)) {
|
||||
while (start > min &&
|
||||
charType(editor, chars[start - 1], false) === CharacterHelper.CharacterType.WHITESPACE
|
||||
) {
|
||||
start--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled) {
|
||||
logger.debug("start=$start")
|
||||
logger.debug("end=$end")
|
||||
}
|
||||
|
||||
// End offset is exclusive
|
||||
return TextRange(start, end + 1)
|
||||
}
|
||||
|
||||
override fun findBlockTagRange(editor: VimEditor, caret: ImmutableVimCaret, count: Int, isOuter: Boolean): TextRange? {
|
||||
var counter = count
|
||||
var isOuterVariable = isOuter
|
||||
val position: Int = caret.ij.offset
|
||||
val sequence: CharSequence = editor.ij.document.charsSequence
|
||||
|
||||
val selectionStart: Int = caret.ij.selectionStart
|
||||
val selectionEnd: Int = caret.ij.selectionEnd
|
||||
|
||||
val isRangeSelection = selectionEnd - selectionStart > 1
|
||||
|
||||
var searchStartPosition: Int
|
||||
searchStartPosition = if (!isRangeSelection) {
|
||||
val line: Int = caret.ij.logicalPosition.line
|
||||
val lineBegin: Int = editor.ij.document.getLineStartOffset(line)
|
||||
ignoreWhitespaceAtLineStart(sequence, lineBegin, position)
|
||||
} else {
|
||||
selectionEnd
|
||||
}
|
||||
|
||||
if (isInHTMLTag(sequence, searchStartPosition, false)) {
|
||||
// caret is inside opening tag. Move to closing '>'.
|
||||
while (searchStartPosition < sequence.length && sequence[searchStartPosition] != '>') {
|
||||
searchStartPosition++
|
||||
}
|
||||
} else if (isInHTMLTag(sequence, searchStartPosition, true)) {
|
||||
// caret is inside closing tag. Move to starting '<'.
|
||||
while (searchStartPosition > 0 && sequence[searchStartPosition] != '<') {
|
||||
searchStartPosition--
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
val (closingTagTextRange, tagName) = findUnmatchedClosingTag(sequence, searchStartPosition, counter)
|
||||
?: return null
|
||||
val openingTag = findUnmatchedOpeningTag(sequence, closingTagTextRange.startOffset, tagName)
|
||||
?: return null
|
||||
if (isRangeSelection && openingTag.endOffset - 1 >= selectionStart) {
|
||||
// If there was already some text selected and the new selection would not extend further, we try again
|
||||
searchStartPosition = closingTagTextRange.endOffset
|
||||
counter = 1
|
||||
continue
|
||||
}
|
||||
var selectionEndWithoutNewline = selectionEnd
|
||||
while (selectionEndWithoutNewline < sequence.length && sequence[selectionEndWithoutNewline] == '\n') {
|
||||
selectionEndWithoutNewline++
|
||||
}
|
||||
val mode = getInstance(editor).mode
|
||||
if (mode is VISUAL) {
|
||||
if (closingTagTextRange.startOffset == selectionEndWithoutNewline &&
|
||||
openingTag.endOffset == selectionStart
|
||||
) {
|
||||
// Special case: if the inner tag is already selected we should like isOuter is active
|
||||
// Note that we need to ignore newlines, because their selection is lost between multiple "it" invocations
|
||||
isOuterVariable = true
|
||||
} else if (openingTag.endOffset == closingTagTextRange.startOffset &&
|
||||
selectionStart == openingTag.endOffset
|
||||
) {
|
||||
// Special case: for an empty tag pair (e.g. <a></a>) the whole tag is selected if the caret is in the middle.
|
||||
isOuterVariable = true
|
||||
}
|
||||
}
|
||||
return if (isOuterVariable) {
|
||||
TextRange(openingTag.startOffset, closingTagTextRange.endOffset)
|
||||
} else {
|
||||
TextRange(openingTag.endOffset, closingTagTextRange.startOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* returns new position which ignore whitespaces at beginning of the line
|
||||
*/
|
||||
private fun ignoreWhitespaceAtLineStart(seq: CharSequence, lineStart: Int, pos: Int): Int {
|
||||
var position = pos
|
||||
if (seq.subSequence(lineStart, position).chars().allMatch { codePoint: Int ->
|
||||
Character.isWhitespace(
|
||||
codePoint
|
||||
)
|
||||
}) {
|
||||
while (position < seq.length && seq[position] != '\n' && Character.isWhitespace(seq[position])) {
|
||||
position++
|
||||
}
|
||||
}
|
||||
return position
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there is a html at the given position. Ignores tags with a trailing slash like <aaa></aaa>.
|
||||
*/
|
||||
private fun isInHTMLTag(sequence: CharSequence, position: Int, isEndtag: Boolean): Boolean {
|
||||
var openingBracket = -1
|
||||
run {
|
||||
var i = position
|
||||
while (i >= 0 && i < sequence.length) {
|
||||
if (sequence[i] == '<') {
|
||||
openingBracket = i
|
||||
break
|
||||
}
|
||||
if (sequence[i] == '>' && i != position) {
|
||||
return false
|
||||
}
|
||||
i--
|
||||
}
|
||||
}
|
||||
if (openingBracket == -1) {
|
||||
return false
|
||||
}
|
||||
val hasSlashAfterOpening = openingBracket + 1 < sequence.length && sequence[openingBracket + 1] == '/'
|
||||
if (isEndtag && !hasSlashAfterOpening || !isEndtag && hasSlashAfterOpening) {
|
||||
return false
|
||||
}
|
||||
var closingBracket = -1
|
||||
for (i in openingBracket until sequence.length) {
|
||||
if (sequence[i] == '>') {
|
||||
closingBracket = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return closingBracket != -1 && sequence[closingBracket - 1] != '/'
|
||||
}
|
||||
|
||||
private fun findUnmatchedOpeningTag(
|
||||
sequence: CharSequence,
|
||||
position: Int,
|
||||
tagName: String,
|
||||
): TextRange? {
|
||||
val quotedTagName = Pattern.quote(tagName)
|
||||
val patternString = ("(</%s>)" // match closing tags
|
||||
+
|
||||
"|(<%s" // or opening tags starting with tagName
|
||||
+
|
||||
"(\\s([^>]*" // After at least one whitespace there might be additional text in the tag. E.g. <html lang="en">
|
||||
+
|
||||
"[^/])?)?>)") // Slash is not allowed as last character (this would be a self closing tag).
|
||||
val tagPattern =
|
||||
Pattern.compile(String.format(patternString, quotedTagName, quotedTagName), Pattern.CASE_INSENSITIVE)
|
||||
val matcher = tagPattern.matcher(sequence.subSequence(0, position + 1))
|
||||
val openTags: Deque<TextRange> = ArrayDeque()
|
||||
while (matcher.find()) {
|
||||
val match = TextRange(matcher.start(), matcher.end())
|
||||
if (sequence[matcher.start() + 1] == '/') {
|
||||
if (!openTags.isEmpty()) {
|
||||
openTags.pop()
|
||||
}
|
||||
} else {
|
||||
openTags.push(match)
|
||||
}
|
||||
}
|
||||
return if (openTags.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
openTags.pop()
|
||||
}
|
||||
}
|
||||
|
||||
private fun findUnmatchedClosingTag(
|
||||
sequence: CharSequence,
|
||||
position: Int,
|
||||
count: Int,
|
||||
): Pair<TextRange, String>? {
|
||||
// The tag name may contain any characters except slashes, whitespace and '>'
|
||||
var counter = count
|
||||
val tagNamePattern = "([^/\\s>]+)"
|
||||
// An opening tag consists of '<' followed by a tag name, optionally some additional text after whitespace and a '>'
|
||||
val openingTagPattern = String.format("<%s(?:\\s[^>]*)?>", tagNamePattern)
|
||||
val closingTagPattern = String.format("</%s>", tagNamePattern)
|
||||
val tagPattern = Pattern.compile(String.format("(?:%s)|(?:%s)", openingTagPattern, closingTagPattern))
|
||||
val matcher = tagPattern.matcher(sequence.subSequence(position, sequence.length))
|
||||
val openTags: Deque<String> = ArrayDeque()
|
||||
while (matcher.find()) {
|
||||
val isClosingTag = matcher.group(1) == null
|
||||
if (isClosingTag) {
|
||||
val tagName = matcher.group(2)
|
||||
// Ignore unmatched open tags. Either the file is malformed or it might be a tag like <br> that does not need to be closed.
|
||||
while (!openTags.isEmpty() && !openTags.peek().equals(tagName, ignoreCase = true)) {
|
||||
openTags.pop()
|
||||
}
|
||||
if (openTags.isEmpty()) {
|
||||
if (counter <= 1) {
|
||||
return Pair(TextRange(position + matcher.start(), position + matcher.end()), tagName)
|
||||
} else {
|
||||
counter--
|
||||
}
|
||||
} else {
|
||||
openTags.pop()
|
||||
}
|
||||
} else {
|
||||
val tagName = matcher.group(1)
|
||||
openTags.push(tagName)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun findBlockRange(
|
||||
editor: VimEditor,
|
||||
caret: ImmutableVimCaret,
|
||||
type: Char,
|
||||
count: Int,
|
||||
isOuter: Boolean,
|
||||
): TextRange? {
|
||||
val chars: CharSequence = editor.ij.document.charsSequence
|
||||
var pos: Int = caret.ij.offset
|
||||
var start: Int = caret.ij.selectionStart
|
||||
var end: Int = caret.ij.selectionEnd
|
||||
|
||||
val loc = BLOCK_CHARS.indexOf(type)
|
||||
val close = BLOCK_CHARS[loc + 1]
|
||||
|
||||
// extend the range for blank line after type and before close, as they are excluded when inner match
|
||||
if (!isOuter) {
|
||||
if (start > 1 && chars[start - 2] == type && chars[start - 1] == '\n') {
|
||||
start--
|
||||
}
|
||||
if (end < chars.length && chars[end] == '\n') {
|
||||
var isSingleLineAllWhiteSpaceUntilClose = false
|
||||
var countWhiteSpaceCharacter = 1
|
||||
while (end + countWhiteSpaceCharacter < chars.length) {
|
||||
if (Character.isWhitespace(chars[end + countWhiteSpaceCharacter]) &&
|
||||
chars[end + countWhiteSpaceCharacter] != '\n'
|
||||
) {
|
||||
countWhiteSpaceCharacter++
|
||||
continue
|
||||
}
|
||||
if (chars[end + countWhiteSpaceCharacter] == close) {
|
||||
isSingleLineAllWhiteSpaceUntilClose = true
|
||||
}
|
||||
break
|
||||
}
|
||||
if (isSingleLineAllWhiteSpaceUntilClose) {
|
||||
end += countWhiteSpaceCharacter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var rangeSelection = end - start > 1
|
||||
if (rangeSelection && start == 0) // early return not only for optimization
|
||||
{
|
||||
return null // but also not to break the interval semantic on this edge case (see below)
|
||||
}
|
||||
|
||||
/* In case of successive inner selection. We want to break out of
|
||||
* the block delimiter of the current inner selection.
|
||||
* In other terms, for the rest of the algorithm, a previous inner selection of a block
|
||||
* if equivalent to an outer one. */
|
||||
|
||||
/* In case of successive inner selection. We want to break out of
|
||||
* the block delimiter of the current inner selection.
|
||||
* In other terms, for the rest of the algorithm, a previous inner selection of a block
|
||||
* if equivalent to an outer one. */if (!isOuter && start - 1 >= 0 && type == chars[start - 1] && end < chars.length && close == chars[end]) {
|
||||
start -= 1
|
||||
pos = start
|
||||
rangeSelection = true
|
||||
}
|
||||
|
||||
/* when one char is selected, we want to find the enclosing block of (start,end]
|
||||
* although when a range of characters is selected, we want the enclosing block of [start, end]
|
||||
* shifting the position allow to express which kind of interval we work on */
|
||||
|
||||
/* when one char is selected, we want to find the enclosing block of (start,end]
|
||||
* although when a range of characters is selected, we want the enclosing block of [start, end]
|
||||
* shifting the position allow to express which kind of interval we work on */if (rangeSelection) pos =
|
||||
max(0.0, (start - 1).toDouble()).toInt()
|
||||
|
||||
val initialPosIsInString = checkInString(chars, pos, true)
|
||||
|
||||
var bstart = -1
|
||||
var bend = -1
|
||||
|
||||
var startPosInStringFound = false
|
||||
|
||||
if (initialPosIsInString) {
|
||||
val quoteRange = injector.searchHelper
|
||||
.findBlockQuoteInLineRange(editor, caret, '"', false)
|
||||
if (quoteRange != null) {
|
||||
val startOffset = quoteRange.startOffset
|
||||
val endOffset = quoteRange.endOffset
|
||||
val subSequence = chars.subSequence(startOffset, endOffset)
|
||||
val inQuotePos = pos - startOffset
|
||||
var inQuoteStart =
|
||||
findBlockLocation(subSequence, close, type, Direction.BACKWARDS, inQuotePos, count)
|
||||
if (inQuoteStart == -1) {
|
||||
inQuoteStart =
|
||||
findBlockLocation(subSequence, close, type, Direction.FORWARDS, inQuotePos, count)
|
||||
}
|
||||
if (inQuoteStart != -1) {
|
||||
startPosInStringFound = true
|
||||
val inQuoteEnd =
|
||||
findBlockLocation(subSequence, type, close, Direction.FORWARDS, inQuoteStart, 1)
|
||||
if (inQuoteEnd != -1) {
|
||||
bstart = inQuoteStart + startOffset
|
||||
bend = inQuoteEnd + startOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!startPosInStringFound) {
|
||||
bstart = findBlockLocation(chars, close, type, Direction.BACKWARDS, pos, count)
|
||||
if (bstart == -1) {
|
||||
bstart = findBlockLocation(chars, close, type, Direction.FORWARDS, pos, count)
|
||||
}
|
||||
if (bstart != -1) {
|
||||
bend = findBlockLocation(chars, type, close, Direction.FORWARDS, bstart, 1)
|
||||
}
|
||||
}
|
||||
|
||||
if (bstart == -1 || bend == -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!isOuter) {
|
||||
bstart++
|
||||
// exclude first line break after start for inner match
|
||||
if (chars[bstart] == '\n') {
|
||||
bstart++
|
||||
}
|
||||
val o = editor.getLineStartForOffset(bend)
|
||||
var allWhite = true
|
||||
for (i in o until bend) {
|
||||
if (!Character.isWhitespace(chars[i])) {
|
||||
allWhite = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (allWhite) {
|
||||
bend = o - 2
|
||||
} else {
|
||||
bend--
|
||||
}
|
||||
}
|
||||
|
||||
// End offset exclusive
|
||||
return TextRange(bstart, bend + 1)
|
||||
}
|
||||
|
||||
override fun findMisspelledWord(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Int {
|
||||
val startOffset: Int
|
||||
val endOffset: Int
|
||||
@ -700,17 +68,18 @@ internal class IjVimSearchHelper : VimSearchHelperBase() {
|
||||
|
||||
if (count < 0) {
|
||||
startOffset = 0
|
||||
endOffset = caret.offset.point - 1
|
||||
endOffset = caret.offset - 1
|
||||
skipCount = -count - 1
|
||||
offsetOrdering = IntComparators.OPPOSITE_COMPARATOR
|
||||
}
|
||||
else {
|
||||
startOffset = caret.offset.point + 1
|
||||
startOffset = caret.offset + 1
|
||||
endOffset = editor.ij.document.textLength
|
||||
skipCount = count - 1
|
||||
offsetOrdering = IntComparators.NATURAL_COMPARATOR
|
||||
}
|
||||
|
||||
// TODO add it to PsiService
|
||||
return SearchHelper.findMisspelledWords(editor.ij, startOffset, endOffset, skipCount, offsetOrdering)
|
||||
}
|
||||
}
|
||||
|
@ -307,7 +307,7 @@ public class ExOutputPanel extends JPanel {
|
||||
KeyHandler.getInstance().getKeyStack().dump());
|
||||
}
|
||||
KeyHandler.getInstance().getKeyStack().addKeys(keys);
|
||||
ExecutionContext.Editor context = injector.getExecutionContextManager().onEditor(new IjVimEditor(myEditor), null);
|
||||
ExecutionContext context = injector.getExecutionContextManager().getEditorExecutionContext(new IjVimEditor(myEditor));
|
||||
VimPlugin.getMacro().playbackKeys(new IjVimEditor(myEditor), context, 1);
|
||||
}
|
||||
});
|
||||
|
@ -10,7 +10,6 @@ package com.maddyhome.idea.vim.ui.ex
|
||||
import com.intellij.openapi.diagnostic.debug
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import java.awt.event.ActionEvent
|
||||
@ -126,12 +125,7 @@ internal object ExEditorKit : DefaultEditorKit() {
|
||||
val entry = ExEntryPanel.getInstance().entry
|
||||
val editor = entry.editor
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
keyHandler.handleKey(
|
||||
editor.vim,
|
||||
key,
|
||||
injector.executionContextManager.onEditor(editor.vim, entry.context.vim),
|
||||
keyHandler.keyHandlerState,
|
||||
)
|
||||
keyHandler.handleKey(editor.vim, key, entry.context.vim, keyHandler.keyHandlerState)
|
||||
} else {
|
||||
val event = ActionEvent(e.source, e.id, c.toString(), e.getWhen(), e.modifiers)
|
||||
super.actionPerformed(event)
|
||||
|
@ -13,7 +13,6 @@ import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.KeyboardShortcut
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.KeyStroke
|
||||
@ -37,12 +36,12 @@ internal class ExShortcutKeyAction(private val exEntryPanel: ExEntryPanel) : Dum
|
||||
if (keyStroke != null) {
|
||||
val editor = exEntryPanel.entry.editor
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
keyHandler.handleKey(
|
||||
editor.vim,
|
||||
keyStroke,
|
||||
injector.executionContextManager.onEditor(editor.vim, e.dataContext.vim),
|
||||
keyHandler.keyHandlerState
|
||||
)
|
||||
|
||||
// About the context: we use the context of the main editor to execute actions on it.
|
||||
// e.dataContext will refer to the ex-entry editor and commands will be executed on it,
|
||||
// thus it should not be used. For example, `:action EditorSelectWord` will not work with this context
|
||||
val mainEditorContext = exEntryPanel.entry.context.vim
|
||||
keyHandler.handleKey(editor.vim, keyStroke, mainEditorContext, keyHandler.keyHandlerState)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,8 +74,7 @@ internal class Executor : VimScriptExecutorBase() {
|
||||
VimPlugin.indicateError()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.warn("Caught: ${e.message}")
|
||||
logger.warn(e.stackTrace.toString())
|
||||
logger.warn(e)
|
||||
if (injector.application.isUnitTest()) {
|
||||
throw e
|
||||
}
|
||||
|
@ -113,9 +113,9 @@ internal data class GlobalCommand(val ranges: Ranges, val argument: String, val
|
||||
}
|
||||
|
||||
if (globalBusy) {
|
||||
val match = regex.findInLine(editor, editor.currentCaret().getLine().line)
|
||||
val match = regex.findInLine(editor, editor.currentCaret().getLine())
|
||||
if (match is VimMatchResult.Success == !invert) {
|
||||
globalExecuteOne(editor, context, editor.getLineStartOffset(editor.currentCaret().getLine().line), cmd.toString())
|
||||
globalExecuteOne(editor, context, editor.getLineStartOffset(editor.currentCaret().getLine()), cmd.toString())
|
||||
}
|
||||
} else {
|
||||
val line1 = range.startLine
|
||||
@ -164,8 +164,8 @@ internal data class GlobalCommand(val ranges: Ranges, val argument: String, val
|
||||
val searchcol = 0
|
||||
if (globalBusy) {
|
||||
val offset = editor.currentCaret().offset
|
||||
val lineStartOffset = editor.getLineStartForOffset(offset.point)
|
||||
match = sp.vim_regexec_multi(regmatch, editor, lcount, editor.currentCaret().getLine().line, searchcol)
|
||||
val lineStartOffset = editor.getLineStartForOffset(offset)
|
||||
match = sp.vim_regexec_multi(regmatch, editor, lcount, editor.currentCaret().getLine(), searchcol)
|
||||
if ((!invert && match > 0) || (invert && match <= 0)) {
|
||||
globalExecuteOne(editor, context, lineStartOffset, cmd.toString())
|
||||
}
|
||||
|
@ -136,11 +136,7 @@
|
||||
|
||||
<!-- IdeaVim extensions-->
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<projectService serviceImplementation="com.maddyhome.idea.vim.extension.nerdtree.NerdTree$NerdDispatcher"/>
|
||||
<postStartupActivity implementation="com.maddyhome.idea.vim.extension.nerdtree.NerdTree$NerdStartupActivity"/>
|
||||
<applicationService serviceImplementation="com.maddyhome.idea.vim.extension.nerdtree.NerdTree$NerdDispatcher"/>
|
||||
<applicationInitializedListener implementation="com.maddyhome.idea.vim.extension.nerdtree.NerdTreeApplicationListener"/>
|
||||
</extensions>
|
||||
<projectListeners>
|
||||
<listener class="com.maddyhome.idea.vim.extension.nerdtree.NerdTree$ProjectViewListener"
|
||||
topic="com.intellij.openapi.wm.ex.ToolWindowManagerListener"/>
|
||||
</projectListeners>
|
||||
</idea-plugin>
|
||||
|
@ -1,12 +1,4 @@
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<idea-plugin url="https://plugins.jetbrains.com/plugin/164" xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
<idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
<name>IdeaVim</name>
|
||||
<id>IdeaVIM</id>
|
||||
<description><![CDATA[
|
||||
@ -21,7 +13,7 @@
|
||||
<li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li>
|
||||
</ul>
|
||||
]]></description>
|
||||
<version>SNAPSHOT</version>
|
||||
<version>chylex</version>
|
||||
<vendor>JetBrains</vendor>
|
||||
|
||||
<!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version -->
|
||||
@ -159,5 +151,6 @@
|
||||
</group>
|
||||
|
||||
<action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/>
|
||||
<action id="VimJumpToSource" class="com.intellij.diff.actions.impl.OpenInEditorAction" />
|
||||
</actions>
|
||||
</idea-plugin>
|
||||
|
@ -15,6 +15,7 @@ import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||
import org.jetbrains.plugins.ideavim.VimBehaviorDiffers
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@ -381,10 +382,14 @@ class MotionActionTest : VimTestCase() {
|
||||
|
||||
// VIM-1287 |d| |v_i{|
|
||||
@Test
|
||||
@VimBehaviorDiffers(
|
||||
originalVimAfter = "{\"{foo, ${c}bar\", baz}}",
|
||||
description = "We have PSI and can resolve this case correctly. I'm not sure if it should be fixed"
|
||||
)
|
||||
fun testBadlyNestedBlockInsideString() {
|
||||
val before = "{\"{foo, ${c}bar\", baz}}"
|
||||
val keys = listOf("di{")
|
||||
val after = "{\"{foo, ${c}bar\", baz}}"
|
||||
val after = "{}}"
|
||||
doTest(keys, before, after, Mode.NORMAL())
|
||||
}
|
||||
|
||||
@ -406,6 +411,14 @@ class MotionActionTest : VimTestCase() {
|
||||
doTest(keys, before, after, Mode.INSERT)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeletingInnerBlockWhenItIsPresentInString() {
|
||||
val before = "let variable = ('abc' .. \"br${c}aces ( with content )\")"
|
||||
val keys = listOf("di(")
|
||||
val after = "let variable = ()"
|
||||
doTest(keys, before, after, Mode.NORMAL())
|
||||
}
|
||||
|
||||
// VIM-1008 |c| |v_i{|
|
||||
@Test
|
||||
fun testDeleteInsideSingleQuotesSurroundedBlock() {
|
||||
|
@ -42,10 +42,10 @@ class YankMotionActionTest : VimTestCase() {
|
||||
""".trimIndent()
|
||||
configureByText(file)
|
||||
|
||||
val initialOffset = fixture.editor.caretModel.offset
|
||||
val initialOffset = fixture.editor.caretModel
|
||||
typeText("yy")
|
||||
|
||||
kotlin.test.assertEquals(initialOffset, fixture.editor.caretModel.offset)
|
||||
kotlin.test.assertEquals(initialOffset, fixture.editor.caretModel)
|
||||
}
|
||||
|
||||
@Suppress("DANGEROUS_CHARACTERS")
|
||||
|
@ -13,6 +13,7 @@ package org.jetbrains.plugins.ideavim.action.motion.gn
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.common.Direction
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||
@ -85,7 +86,7 @@ class GnNextTextObjectTest : VimTestCase() {
|
||||
|
||||
private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) {
|
||||
configureByText(before)
|
||||
VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
|
||||
VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
|
||||
typeText(keys)
|
||||
assertState(after)
|
||||
assertState(Mode.NORMAL())
|
||||
|
@ -13,6 +13,7 @@ package org.jetbrains.plugins.ideavim.action.motion.gn
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.common.Direction
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||
@ -63,7 +64,7 @@ class GnPreviousTextObjectTest : VimTestCase() {
|
||||
|
||||
private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) {
|
||||
configureByText(before)
|
||||
VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
|
||||
VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
|
||||
typeText(keys)
|
||||
assertState(after)
|
||||
assertState(Mode.NORMAL())
|
||||
|
@ -12,6 +12,7 @@ import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.common.Direction
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
@ -57,7 +58,7 @@ class VisualSelectNextSearchTest : VimTestCase() {
|
||||
@Test
|
||||
fun testWithoutSpaces() {
|
||||
configureByText("test<caret>test")
|
||||
VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
|
||||
VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
|
||||
typeText(injector.parser.parseKeys("gn"))
|
||||
assertOffset(7)
|
||||
assertSelection("test")
|
||||
|
@ -12,6 +12,7 @@ import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.common.Direction
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
@ -54,7 +55,7 @@ class VisualSelectPreviousSearchTest : VimTestCase() {
|
||||
@Test
|
||||
fun testWithoutSpaces() {
|
||||
configureByText("tes<caret>ttest")
|
||||
VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
|
||||
VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
|
||||
typeText(injector.parser.parseKeys("gN"))
|
||||
assertOffset(0)
|
||||
assertSelection("test")
|
||||
|
@ -10,6 +10,7 @@ package org.jetbrains.plugins.ideavim.action.motion.search
|
||||
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.common.Direction
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
@ -167,7 +168,7 @@ class SearchAgainPreviousActionTest : VimTestCase() {
|
||||
|
||||
private fun doTestWithSearch(keys: String, before: String, after: String) {
|
||||
doTest(keys, before, after) {
|
||||
VimPlugin.getSearch().setLastSearchState(it, "all", "", Direction.FORWARDS)
|
||||
VimPlugin.getSearch().setLastSearchState(it.vim, "all", "", Direction.FORWARDS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ class SelectKeyHandlerTest : VimTestCase() {
|
||||
where it was settled on some sodden sand
|
||||
hard by the torrent of a mountain pass.
|
||||
""".trimIndent(),
|
||||
Mode.INSERT,
|
||||
Mode.INSERT,
|
||||
)
|
||||
}
|
||||
|
||||
@ -67,7 +67,7 @@ Mode.INSERT,
|
||||
where it was settled on some sodden sand
|
||||
hard by the torrent of a mountain pass.
|
||||
""".trimIndent(),
|
||||
Mode.INSERT,
|
||||
Mode.INSERT,
|
||||
)
|
||||
}
|
||||
|
||||
@ -92,7 +92,7 @@ Mode.INSERT,
|
||||
where it was settled on some sodden sand
|
||||
hard by the torrent of a mountain pass.
|
||||
""".trimIndent(),
|
||||
Mode.INSERT,
|
||||
Mode.INSERT,
|
||||
)
|
||||
}
|
||||
|
||||
@ -117,7 +117,7 @@ Mode.INSERT,
|
||||
where it was settled on some sodden sand
|
||||
hard by the torrent of a mountain pass.
|
||||
""".trimIndent(),
|
||||
Mode.INSERT,
|
||||
Mode.INSERT,
|
||||
)
|
||||
}
|
||||
|
||||
@ -143,7 +143,7 @@ Mode.INSERT,
|
||||
where it was settled on some sodden sand
|
||||
hard by the torrent of a mountain pass.
|
||||
""".trimIndent(),
|
||||
Mode.INSERT,
|
||||
Mode.INSERT,
|
||||
)
|
||||
}
|
||||
|
||||
@ -169,7 +169,7 @@ Mode.INSERT,
|
||||
where it was settled on some sodden sand
|
||||
hard by the torrent of a mountain pass.
|
||||
""".trimIndent(),
|
||||
Mode.INSERT,
|
||||
Mode.INSERT,
|
||||
)
|
||||
}
|
||||
|
||||
@ -195,7 +195,7 @@ Mode.INSERT,
|
||||
where it was settled on some sodden sand
|
||||
hard by the torrent of a mountain pass.
|
||||
""".trimIndent(),
|
||||
Mode.INSERT,
|
||||
Mode.INSERT,
|
||||
)
|
||||
}
|
||||
|
||||
@ -221,7 +221,7 @@ Mode.INSERT,
|
||||
where it was settled on some sodden sand
|
||||
hard by the torrent of a mountain pass.
|
||||
""".trimIndent(),
|
||||
Mode.INSERT,
|
||||
Mode.INSERT,
|
||||
)
|
||||
}
|
||||
|
||||
@ -247,7 +247,7 @@ Mode.INSERT,
|
||||
where it was settled on some sodden sand
|
||||
hard by the torrent of a mountain pass.
|
||||
""".trimIndent(),
|
||||
Mode.INSERT,
|
||||
Mode.INSERT,
|
||||
)
|
||||
}
|
||||
|
||||
@ -283,7 +283,7 @@ Mode.INSERT,
|
||||
where it was settled on some sodden sand
|
||||
hard by the torrent of a mountain pass.
|
||||
""".trimIndent(),
|
||||
Mode.INSERT,
|
||||
Mode.INSERT,
|
||||
)
|
||||
}
|
||||
|
||||
@ -309,7 +309,7 @@ Mode.INSERT,
|
||||
where it was settled on some sodden sand
|
||||
hard by the torrent of a mountain pass.
|
||||
""".trimIndent(),
|
||||
Mode.INSERT,
|
||||
Mode.INSERT,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -98,15 +98,6 @@ class MotionUnmatchedBraceOpenActionTest : VimTestCase() {
|
||||
)
|
||||
}
|
||||
|
||||
@VimBehaviorDiffers(
|
||||
originalVimAfter = """
|
||||
class Xxx $c{
|
||||
int main() {
|
||||
|
||||
}
|
||||
}
|
||||
""",
|
||||
)
|
||||
@Test
|
||||
fun `test go to next next bracket with great count`() {
|
||||
doTest(
|
||||
@ -119,9 +110,9 @@ class MotionUnmatchedBraceOpenActionTest : VimTestCase() {
|
||||
}
|
||||
""".trimIndent(),
|
||||
"""
|
||||
class Xxx {
|
||||
class Xxx $c{
|
||||
int main() {
|
||||
$c
|
||||
|
||||
}
|
||||
}
|
||||
""".trimIndent(),
|
||||
|
@ -8,10 +8,12 @@
|
||||
|
||||
package org.jetbrains.plugins.ideavim.action.motion.updown
|
||||
|
||||
import com.intellij.idea.TestFor
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
/**
|
||||
@ -143,6 +145,7 @@ class MotionPercentOrMatchActionTest : VimTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("It will work after implementing all of the methods in VimPsiService")
|
||||
fun `test motion outside text`() {
|
||||
doTest(
|
||||
"%",
|
||||
@ -207,41 +210,45 @@ class MotionPercentOrMatchActionTest : VimTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestWithoutNeovim(SkipNeovimReason.BUG_IN_NEOVIM)
|
||||
fun `test motion in text with escape (outer forward)`() {
|
||||
doTest(
|
||||
"%",
|
||||
""" debugPrint$c(\(var)) """,
|
||||
""" debugPrint(\(var)$c) """,
|
||||
""" debugPrint(\(var$c)) """,
|
||||
Mode.NORMAL(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestWithoutNeovim(SkipNeovimReason.BUG_IN_NEOVIM)
|
||||
fun `test motion in text with escape (outer backward)`() {
|
||||
doTest(
|
||||
"%",
|
||||
""" debugPrint(\(var)$c) """,
|
||||
""" debugPrint$c(\(var)) """,
|
||||
""" debugPrint(\(var)$c) """,
|
||||
Mode.NORMAL(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestWithoutNeovim(SkipNeovimReason.BUG_IN_NEOVIM)
|
||||
fun `test motion in text with escape (inner forward)`() {
|
||||
doTest(
|
||||
"%",
|
||||
""" debugPrint(\$c(var)) """,
|
||||
""" debugPrint(\(var$c)) """,
|
||||
""" debugPrint(\$c(var)) """,
|
||||
Mode.NORMAL(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestWithoutNeovim(SkipNeovimReason.BUG_IN_NEOVIM)
|
||||
fun `test motion in text with escape (inner backward)`() {
|
||||
doTest(
|
||||
"%",
|
||||
""" debugPrint(\$c(var)) """,
|
||||
""" debugPrint(\(var$c)) """,
|
||||
""" debugPrint(\$c(var)) """,
|
||||
Mode.NORMAL(),
|
||||
)
|
||||
}
|
||||
@ -332,4 +339,28 @@ class MotionPercentOrMatchActionTest : VimTestCase() {
|
||||
)
|
||||
assertOffset(10)
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestFor(issues = ["VIM-3294"])
|
||||
fun `test matching with braces inside of string`() {
|
||||
configureByText("""
|
||||
$c("("")")
|
||||
""".trimIndent())
|
||||
typeText("%")
|
||||
assertState("""
|
||||
("("")"$c)
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestFor(issues = ["VIM-3294"])
|
||||
fun `test matching with braces inside of string 2`() {
|
||||
configureByText("""
|
||||
("("")"$c)
|
||||
""".trimIndent())
|
||||
typeText("%")
|
||||
assertState("""
|
||||
$c("("")")
|
||||
""".trimIndent())
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ package org.jetbrains.plugins.ideavim.common.editor
|
||||
|
||||
import com.intellij.openapi.application.runWriteAction
|
||||
import com.intellij.openapi.command.WriteCommandAction
|
||||
import com.maddyhome.idea.vim.common.offset
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||
@ -25,7 +24,7 @@ class VimEditorTest : VimTestCase() {
|
||||
val vimEditor = IjVimEditor(fixture.editor)
|
||||
WriteCommandAction.runWriteCommandAction(fixture.project) {
|
||||
runWriteAction {
|
||||
vimEditor.deleteRange(0.offset, 5.offset)
|
||||
vimEditor.deleteRange(0, 5)
|
||||
}
|
||||
}
|
||||
assertState("567890")
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
package org.jetbrains.plugins.ideavim.extension.entiretextobj
|
||||
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||
@ -57,6 +58,14 @@ class VimTextObjEntireExtensionTest : VimTestCase() {
|
||||
@Test
|
||||
fun testYankEntireBuffer() {
|
||||
doTest("yae", poem, "<caret>$poemNoCaret")
|
||||
assertRegisterString(injector.registerGroup.defaultRegister, poemNoCaret)
|
||||
}
|
||||
|
||||
// |y| |ae|
|
||||
@Test
|
||||
fun testYankEntireBufferWithCustomRegister() {
|
||||
doTest("\"kyae", poem, "<caret>$poemNoCaret")
|
||||
assertRegisterString('k', poemNoCaret)
|
||||
}
|
||||
|
||||
// |gU| |ie|
|
||||
|
@ -203,6 +203,36 @@ class ReplaceWithRegisterTest : VimTestCase() {
|
||||
assertEquals("one", VimPlugin.getRegister().lastRegister?.text)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `with specific register`() {
|
||||
val text = "one ${c}two three four"
|
||||
|
||||
configureByText(text)
|
||||
VimPlugin.getRegister().setKeys('k', injector.parser.parseKeys("one"))
|
||||
typeText(injector.parser.parseKeys("\"kgriw"))
|
||||
assertState("one on${c}e three four")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `with specific register in visual mode`() {
|
||||
val text = "one ${c}two three four"
|
||||
|
||||
configureByText(text)
|
||||
VimPlugin.getRegister().setKeys('k', injector.parser.parseKeys("one"))
|
||||
typeText(injector.parser.parseKeys("ve\"kgr"))
|
||||
assertState("one on${c}e three four")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `with specific register in line mode`() {
|
||||
val text = "one ${c}two three four"
|
||||
|
||||
configureByText(text)
|
||||
VimPlugin.getRegister().setKeys('k', injector.parser.parseKeys("one"))
|
||||
typeText(injector.parser.parseKeys("\"kgrr"))
|
||||
assertState("${c}one\n")
|
||||
}
|
||||
|
||||
// --------------------------------------- grr --------------------------
|
||||
|
||||
@Test
|
||||
|
@ -322,7 +322,7 @@ class CaretVisualAttributesHelperTest : VimTestCase() {
|
||||
)
|
||||
injector.actionExecutor.executeAction(
|
||||
"EditorCloneCaretBelow",
|
||||
injector.executionContextManager.onEditor(fixture.editor.vim),
|
||||
injector.executionContextManager.getEditorExecutionContext(fixture.editor.vim),
|
||||
)
|
||||
kotlin.test.assertEquals(2, fixture.editor.caretModel.caretCount)
|
||||
assertCaretVisualAttributes(CaretVisualAttributes.Shape.BLOCK, 0f)
|
||||
|
@ -9,6 +9,7 @@ package org.jetbrains.plugins.ideavim.helper
|
||||
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.group.findBlockRange
|
||||
import com.maddyhome.idea.vim.helper.checkInString
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
@ -246,8 +247,7 @@ class SearchHelperTest : VimTestCase() {
|
||||
fun findBlockRange(testCase: FindBlockRangeTestCase) {
|
||||
val (_, text, type, count, isOuter, expected) = (testCase)
|
||||
configureByText(text)
|
||||
val actual =
|
||||
injector.searchHelper.findBlockRange(fixture.editor.vim, fixture.editor.vim.currentCaret(), type, count, isOuter)
|
||||
val actual = findBlockRange(fixture.editor.vim, fixture.editor.vim.currentCaret(), type, count, isOuter)
|
||||
kotlin.test.assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@ -294,8 +294,8 @@ class SearchHelperTest : VimTestCase() {
|
||||
FindBlockRangeTestCase("outer match exclude start paren in string when caret at start of quote", "(${c}\"(aa\")", '(', 1, isOuter = true, expected = TextRange(0, 7)),
|
||||
FindBlockRangeTestCase("inner match exclude start paren in string when caret at end of quote", "(\"(aa${c}\")", '(', 1, isOuter = false, expected = TextRange(1, 6)),
|
||||
FindBlockRangeTestCase("outer match exclude start paren in string when caret at end of quote", "(\"(aa${c}\")", '(', 1, isOuter = true, expected = TextRange(0, 7)),
|
||||
FindBlockRangeTestCase("inner match not exclude start paren in string when caret in between quote", "(\"(a${c}a\")", '(', 1, isOuter = false, expected = null),
|
||||
FindBlockRangeTestCase("outer match not exclude start paren in string when caret in between quote", "(\"(a${c}a\")", '(', 1, isOuter = true, expected = null),
|
||||
FindBlockRangeTestCase("inner match not exclude start paren in string when caret in between quote", "(\"(a${c}a\")", '(', 1, isOuter = false, expected = TextRange(1, 6)), // Vim behavior differs, but we have some PSI magic and can resolve such cases
|
||||
FindBlockRangeTestCase("outer match not exclude start paren in string when caret in between quote", "(\"(a${c}a\")", '(', 1, isOuter = true, expected = TextRange(0, 7)), // Vim behavior differs, but we have some PSI magic and can resolve such cases
|
||||
FindBlockRangeTestCase("inner match exclude end paren in string when caret at start of quote", "(${c}\"aa)\")", '(', 1, isOuter = false, expected = TextRange(1, 6)),
|
||||
FindBlockRangeTestCase("outer match exclude end paren in string when caret at start of quote", "(${c}\"aa)\")", '(', 1, isOuter = true, expected = TextRange(0, 7)),
|
||||
FindBlockRangeTestCase("inner match exclude end paren in string when caret at end of quote", "(\"aa)${c}\")", '(', 1, isOuter = false, expected = TextRange(1, 6)),
|
||||
|
@ -79,7 +79,7 @@ class VimRegexEngineTest : VimTestCase() {
|
||||
configureByText("Lor${c}em ${c}Ipsum")
|
||||
val editor = fixture.editor.vim
|
||||
val mark = VimMark.create('m', 0, 0, editor.getPath(), editor.extractProtocol())!!
|
||||
val secondCaret = editor.carets().maxByOrNull { it.offset.point }!!
|
||||
val secondCaret = editor.carets().maxByOrNull { it.offset }!!
|
||||
secondCaret.markStorage.setMark(mark)
|
||||
|
||||
val result = findAll("\\%>'m\\%#.")
|
||||
|
@ -245,6 +245,9 @@ enum class SkipNeovimReason {
|
||||
|
||||
GUARDED_BLOCKS,
|
||||
CTRL_CODES,
|
||||
|
||||
BUG_IN_NEOVIM,
|
||||
PSI,
|
||||
}
|
||||
|
||||
fun LogicalPosition.toVimCoords(): VimCoords {
|
||||
|
@ -15,7 +15,9 @@ import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.ActionPlaces
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.actionSystem.PlatformCoreDataKeys
|
||||
import com.intellij.openapi.actionSystem.ex.ActionUtil
|
||||
import com.intellij.openapi.actionSystem.impl.SimpleDataContext
|
||||
import com.intellij.openapi.application.PathManager
|
||||
import com.intellij.openapi.application.WriteAction
|
||||
import com.intellij.openapi.editor.CaretVisualAttributes
|
||||
@ -25,6 +27,7 @@ import com.intellij.openapi.editor.LogicalPosition
|
||||
import com.intellij.openapi.editor.VisualPosition
|
||||
import com.intellij.openapi.editor.colors.EditorColors
|
||||
import com.intellij.openapi.editor.ex.EditorEx
|
||||
import com.intellij.openapi.editor.ex.util.EditorUtil
|
||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
|
||||
import com.intellij.openapi.fileTypes.FileType
|
||||
import com.intellij.openapi.fileTypes.PlainTextFileType
|
||||
@ -64,7 +67,6 @@ import com.maddyhome.idea.vim.key.ToKeysMappingInfo
|
||||
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.ijOptions
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.options.OptionAccessScope
|
||||
@ -451,7 +453,12 @@ abstract class VimTestCase {
|
||||
val actual = injector.registerGroup.getRegister(char)?.keys?.let(injector.parser::toKeyNotation)
|
||||
assertEquals(expected, actual, "Wrong register contents")
|
||||
}
|
||||
|
||||
|
||||
protected fun assertRegisterString(char: Char, expected: String?) {
|
||||
val actual = injector.registerGroup.getRegister(char)?.keys?.let(injector.parser::toPrintableString)
|
||||
assertEquals(expected, actual, "Wrong register contents")
|
||||
}
|
||||
|
||||
protected fun assertState(modeAfter: Mode) {
|
||||
assertMode(modeAfter)
|
||||
assertCaretsVisualAttributes()
|
||||
@ -755,9 +762,13 @@ abstract class VimTestCase {
|
||||
val event =
|
||||
KeyEvent(editor.component, KeyEvent.KEY_PRESSED, Date().time, key.modifiers, key.keyCode, key.keyChar)
|
||||
|
||||
val context = SimpleDataContext.builder()
|
||||
.setParent(EditorUtil.getEditorDataContext(editor))
|
||||
.add(PlatformCoreDataKeys.CONTEXT_COMPONENT, editor.component)
|
||||
.build()
|
||||
val e = AnActionEvent(
|
||||
event,
|
||||
injector.executionContextManager.onEditor(editor.vim).ij,
|
||||
context,
|
||||
ActionPlaces.KEYBOARD_SHORTCUT,
|
||||
VimShortcutKeyAction.instance.templatePresentation.clone(),
|
||||
ActionManager.getInstance(),
|
||||
@ -810,7 +821,7 @@ abstract class VimTestCase {
|
||||
|
||||
fun typeText(keys: List<KeyStroke?>, editor: Editor, project: Project?) {
|
||||
val keyHandler = KeyHandler.getInstance()
|
||||
val dataContext = injector.executionContextManager.onEditor(editor.vim)
|
||||
val dataContext = injector.executionContextManager.getEditorExecutionContext(editor.vim)
|
||||
TestInputModel.getInstance(editor).setKeyStrokes(keys.filterNotNull())
|
||||
runWriteCommand(
|
||||
project,
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
package org.jetbrains.plugins.ideavim.action.motion.updown
|
||||
|
||||
import com.intellij.idea.TestFor
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||
import org.jetbrains.plugins.ideavim.VimJavaTestCase
|
||||
@ -66,4 +67,98 @@ class MotionPercentOrMatchActionJavaTest : VimJavaTestCase() {
|
||||
typeText("%")
|
||||
assertState("/* foo $c */")
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestFor(issues = ["VIM-1399"])
|
||||
@TestWithoutNeovim(SkipNeovimReason.PSI)
|
||||
fun `test percent ignores brace inside comment`() {
|
||||
configureByJavaText("""
|
||||
protected TokenStream normalize(String fieldName, TokenStream in) {
|
||||
TokenStream result = new EmptyTokenFilter(in); /* $c{
|
||||
* some text
|
||||
*/
|
||||
result = new LowerCaseFilter(result);
|
||||
return result;
|
||||
}
|
||||
""".trimIndent())
|
||||
typeText("%")
|
||||
assertState("""
|
||||
protected TokenStream normalize(String fieldName, TokenStream in) {
|
||||
TokenStream result = new EmptyTokenFilter(in); /* $c{
|
||||
* some text
|
||||
*/
|
||||
result = new LowerCaseFilter(result);
|
||||
return result;
|
||||
}
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestFor(issues = ["VIM-1399"])
|
||||
@TestWithoutNeovim(SkipNeovimReason.PSI)
|
||||
fun `test percent doesnt match brace inside comment`() {
|
||||
configureByJavaText("""
|
||||
protected TokenStream normalize(String fieldName, TokenStream in) $c{
|
||||
TokenStream result = new EmptyTokenFilter(in); /* {
|
||||
* some text
|
||||
*/
|
||||
result = new LowerCaseFilter(result);
|
||||
return result;
|
||||
}
|
||||
""".trimIndent())
|
||||
typeText("%")
|
||||
assertState("""
|
||||
protected TokenStream normalize(String fieldName, TokenStream in) {
|
||||
TokenStream result = new EmptyTokenFilter(in); /* {
|
||||
* some text
|
||||
*/
|
||||
result = new LowerCaseFilter(result);
|
||||
return result;
|
||||
$c}
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestWithoutNeovim(SkipNeovimReason.PSI)
|
||||
fun `test matching works with a sequence of single-line comments`() {
|
||||
configureByJavaText("""
|
||||
protected TokenStream normalize(String fieldName, TokenStream in) {
|
||||
// $c{
|
||||
// result = new LowerCaseFilter(result);
|
||||
// }
|
||||
return result;
|
||||
}
|
||||
""".trimIndent())
|
||||
typeText("%")
|
||||
assertState("""
|
||||
protected TokenStream normalize(String fieldName, TokenStream in) {
|
||||
// {
|
||||
// result = new LowerCaseFilter(result);
|
||||
// $c}
|
||||
return result;
|
||||
}
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestWithoutNeovim(SkipNeovimReason.PSI)
|
||||
fun `test matching doesn't work if a sequence of single-line comments is broken`() {
|
||||
configureByJavaText("""
|
||||
protected TokenStream normalize(String fieldName, TokenStream in) {
|
||||
// $c{
|
||||
result = new LowerCaseFilter(result);
|
||||
// }
|
||||
return result;
|
||||
}
|
||||
""".trimIndent())
|
||||
typeText("%")
|
||||
assertState("""
|
||||
protected TokenStream normalize(String fieldName, TokenStream in) {
|
||||
// $c{
|
||||
result = new LowerCaseFilter(result);
|
||||
// }
|
||||
return result;
|
||||
}
|
||||
""".trimIndent())
|
||||
}
|
||||
}
|
@ -10,7 +10,9 @@ package ui
|
||||
|
||||
import com.automation.remarks.junit5.Video
|
||||
import com.intellij.remoterobot.RemoteRobot
|
||||
import com.intellij.remoterobot.fixtures.ComponentFixture
|
||||
import com.intellij.remoterobot.fixtures.ContainerFixture
|
||||
import com.intellij.remoterobot.search.locators.byXpath
|
||||
import com.intellij.remoterobot.steps.CommonSteps
|
||||
import com.intellij.remoterobot.stepsProcessing.step
|
||||
import com.intellij.remoterobot.utils.keyboard
|
||||
@ -120,6 +122,9 @@ class UiTests {
|
||||
Thread.sleep(5000)
|
||||
wrapWithIf(javaEditor)
|
||||
testTrackActionId(javaEditor)
|
||||
testActionGenerate(javaEditor)
|
||||
testActionNewElementSamePlace(javaEditor)
|
||||
testActionCopy(javaEditor)
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,6 +243,66 @@ class UiTests {
|
||||
vimExit()
|
||||
}
|
||||
|
||||
private fun IdeaFrame.testActionGenerate(editor: Editor) {
|
||||
val label = findAll<ComponentFixture>(byXpath("//div[@class='EngravedLabel']"))
|
||||
assertTrue(label.isEmpty())
|
||||
|
||||
keyboard {
|
||||
enterText(":action Generate")
|
||||
enter()
|
||||
}
|
||||
|
||||
waitFor {
|
||||
val generateDialog = findAll<ComponentFixture>(byXpath("//div[@class='EngravedLabel']"))
|
||||
if (generateDialog.size == 1) {
|
||||
return@waitFor generateDialog.single().hasText("Generate")
|
||||
}
|
||||
return@waitFor false
|
||||
}
|
||||
|
||||
keyboard { escape() }
|
||||
}
|
||||
|
||||
private fun IdeaFrame.testActionNewElementSamePlace(editor: Editor) {
|
||||
val label = findAll<ComponentFixture>(byXpath("//div[@class='EngravedLabel']"))
|
||||
assertTrue(label.isEmpty())
|
||||
|
||||
keyboard {
|
||||
enterText(":action NewElementSamePlace")
|
||||
enter()
|
||||
}
|
||||
|
||||
waitFor {
|
||||
val generateDialog = findAll<ComponentFixture>(byXpath("//div[@class='EngravedLabel']"))
|
||||
if (generateDialog.size == 1) {
|
||||
return@waitFor generateDialog.single().hasText("New in This Directory")
|
||||
}
|
||||
return@waitFor false
|
||||
}
|
||||
|
||||
keyboard { escape() }
|
||||
}
|
||||
|
||||
private fun IdeaFrame.testActionCopy(editor: Editor) {
|
||||
val label = findAll<ComponentFixture>(byXpath("//div[@class='EngravedLabel']"))
|
||||
assertTrue(label.isEmpty())
|
||||
|
||||
keyboard {
|
||||
enterText(":action CopyReferencePopupGroup")
|
||||
enter()
|
||||
}
|
||||
|
||||
waitFor {
|
||||
val generateDialog = findAll<ComponentFixture>(byXpath("//div[@class='EngravedLabel']"))
|
||||
if (generateDialog.size == 1) {
|
||||
return@waitFor generateDialog.single().hasText("Copy Path/Reference…")
|
||||
}
|
||||
return@waitFor false
|
||||
}
|
||||
|
||||
keyboard { escape() }
|
||||
}
|
||||
|
||||
private fun IdeaFrame.createFile(fileName: String, remoteRobot: RemoteRobot) {
|
||||
step("Create $fileName file") {
|
||||
with(projectViewTree) {
|
||||
|
@ -48,7 +48,11 @@ class PyCharmTest {
|
||||
findAllText("Python Packages").isNotEmpty() &&
|
||||
isSmartMode()
|
||||
}
|
||||
findText("Python Console").click()
|
||||
|
||||
// Open tool window by id.
|
||||
// id taken from PythonConsoleToolWindowFactory.ID but it's not resolved in robot by some reason
|
||||
// the last 'x' is just to return some serializable value
|
||||
callJs<String>("com.intellij.openapi.wm.ToolWindowManager.getInstance(component.project).getToolWindow('Python Console').activate(null, true); 'x'", true)
|
||||
|
||||
Thread.sleep(10_000)
|
||||
|
||||
|
@ -55,7 +55,7 @@ dependencies {
|
||||
compileOnly(project(":annotation-processors"))
|
||||
compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion")
|
||||
|
||||
testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
|
||||
testImplementation("org.mockito.kotlin:mockito-kotlin:5.3.1")
|
||||
}
|
||||
|
||||
tasks {
|
||||
|
@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.command.Command
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.handler.VimActionHandler
|
||||
|
||||
@CommandOrMotion(keys = ["<C-R>"], modes = [Mode.NORMAL])
|
||||
@CommandOrMotion(keys = ["U", "<C-R>"], modes = [Mode.NORMAL, Mode.VISUAL])
|
||||
public class RedoAction : VimActionHandler.SingleExecution() {
|
||||
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
|
||||
|
||||
|
@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.command.Command
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.handler.VimActionHandler
|
||||
|
||||
@CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL])
|
||||
@CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL, Mode.VISUAL])
|
||||
public class UndoAction : VimActionHandler.SingleExecution() {
|
||||
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
|
||||
|
||||
|
@ -8,7 +8,6 @@
|
||||
package com.maddyhome.idea.vim.action.change.change
|
||||
|
||||
import com.intellij.vim.annotations.CommandOrMotion
|
||||
import com.intellij.vim.annotations.Mode
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimCaret
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
@ -22,7 +21,7 @@ import com.maddyhome.idea.vim.helper.CharacterHelper
|
||||
/**
|
||||
* @author vlan
|
||||
*/
|
||||
@CommandOrMotion(keys = ["u"], modes = [Mode.VISUAL])
|
||||
@CommandOrMotion(keys = [], modes = [])
|
||||
public class ChangeCaseLowerVisualAction : VisualOperatorActionHandler.ForEachCaret() {
|
||||
override val type: Command.Type = Command.Type.CHANGE
|
||||
|
||||
|
@ -8,7 +8,6 @@
|
||||
package com.maddyhome.idea.vim.action.change.change
|
||||
|
||||
import com.intellij.vim.annotations.CommandOrMotion
|
||||
import com.intellij.vim.annotations.Mode
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimCaret
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
@ -22,7 +21,7 @@ import com.maddyhome.idea.vim.helper.CharacterHelper
|
||||
/**
|
||||
* @author vlan
|
||||
*/
|
||||
@CommandOrMotion(keys = ["U"], modes = [Mode.VISUAL])
|
||||
@CommandOrMotion(keys = [], modes = [])
|
||||
public class ChangeCaseUpperVisualAction : VisualOperatorActionHandler.ForEachCaret() {
|
||||
override val type: Command.Type = Command.Type.CHANGE
|
||||
|
||||
|
@ -59,7 +59,7 @@ private fun changeCharacter(editor: VimEditor, caret: VimCaret, count: Int, ch:
|
||||
val col = caret.getBufferPosition().column
|
||||
// TODO: Is this correct? Should we really use only current caret? We have a caret as an argument
|
||||
val len = editor.lineLength(editor.currentCaret().getBufferPosition().line)
|
||||
val offset = caret.offset.point
|
||||
val offset = caret.offset
|
||||
if (len - col < count) {
|
||||
return false
|
||||
}
|
||||
|
@ -31,6 +31,6 @@ public class ChangeLastGlobalSearchReplaceAction : ChangeEditorActionHandler.Sin
|
||||
): Boolean {
|
||||
val range = LineRange(0, editor.lineCount() - 1)
|
||||
return injector.searchGroup
|
||||
.processSubstituteCommand(editor, editor.primaryCaret(), range, "s", "//~/&", Script(listOf()))
|
||||
.processSubstituteCommand(editor, editor.primaryCaret(), context, range, "s", "//~/&", Script(listOf()))
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ public class ChangeLastSearchReplaceAction : ChangeEditorActionHandler.SingleExe
|
||||
for (caret in editor.carets()) {
|
||||
val line = caret.getBufferPosition().line
|
||||
if (!injector.searchGroup
|
||||
.processSubstituteCommand(editor, caret, LineRange(line, line), "s", "//~/", Script(listOf()))
|
||||
.processSubstituteCommand(editor, caret, context, LineRange(line, line), "s", "//~/", Script(listOf()))
|
||||
) {
|
||||
result = false
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ public class FilterVisualLinesAction : VimActionHandler.SingleExecution() {
|
||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MOT_LINEWISE)
|
||||
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
||||
injector.markService.setVisualSelectionMarks(editor)
|
||||
injector.processGroup.startFilterCommand(editor, context, cmd)
|
||||
editor.exitVisualMode()
|
||||
return true
|
||||
|
@ -75,7 +75,7 @@ private fun insertCharacterAroundCursor(editor: VimEditor, caret: VimCaret, dir:
|
||||
vp = VimVisualPosition(vp.line + dir, vp.column, false)
|
||||
val len = editor.lineLength(editor.visualLineToBufferLine(vp.line))
|
||||
if (vp.column < len) {
|
||||
val offset = editor.visualPositionToOffset(VimVisualPosition(vp.line, vp.column, false)).point
|
||||
val offset = editor.visualPositionToOffset(VimVisualPosition(vp.line, vp.column, false))
|
||||
val charsSequence = editor.text()
|
||||
if (offset < charsSequence.length) {
|
||||
val ch = charsSequence[offset]
|
||||
|
@ -17,11 +17,10 @@ import com.maddyhome.idea.vim.command.Argument
|
||||
import com.maddyhome.idea.vim.command.Command
|
||||
import com.maddyhome.idea.vim.command.CommandFlags
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.common.Offset
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
|
||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import java.util.*
|
||||
|
||||
@CommandOrMotion(keys = ["<C-U>"], modes = [Mode.INSERT])
|
||||
@ -57,13 +56,13 @@ private fun insertDeleteInsertedText(
|
||||
var deleteTo = caret.vimInsertStart.startOffset
|
||||
val offset = caret.offset
|
||||
if (offset == deleteTo) {
|
||||
deleteTo = Offset(injector.motion.moveCaretToCurrentLineStartSkipLeading(editor, caret))
|
||||
deleteTo = injector.motion.moveCaretToCurrentLineStartSkipLeading(editor, caret)
|
||||
}
|
||||
if (deleteTo.point != -1) {
|
||||
if (deleteTo != -1) {
|
||||
injector.changeGroup.deleteRange(
|
||||
editor,
|
||||
caret,
|
||||
TextRange(deleteTo.point, offset.point),
|
||||
TextRange(deleteTo, offset),
|
||||
SelectionType.CHARACTER_WISE,
|
||||
false,
|
||||
operatorArguments,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user