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

Compare commits

..

19 Commits

Author SHA1 Message Date
a978505530
Set plugin version to chylex-31 2024-03-27 13:49:38 +01:00
a3bc8033a8
Fix(VIM-3364): Exception with mapped Generate action 2024-03-27 13:49:27 +01:00
500756c86f
Disable speed search in Project tool window when NERDTree is enabled 2024-03-22 07:06:45 +01:00
5967979113
Apply scrolloff after executing native IDEA actions 2024-03-22 07:06:44 +01:00
358a13c22c
Stay on same line after reindenting 2024-03-22 07:06:44 +01:00
64229d327a
Update search register when using f/t 2024-03-22 07:06:44 +01:00
62d38eb4df
Automatically add unambiguous imports after running a macro 2024-03-22 07:06:44 +01:00
4e15b65cec
Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2024-03-22 07:06:44 +01:00
e4d31379f0
Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2024-03-22 07:06:44 +01:00
195910b3a5
Fix(VIM-3166): Workaround to fix broken filtering of visual lines 2024-03-22 07:06:44 +01:00
10d476340f
Add support for count for visual and line motion surround 2024-03-22 07:06:44 +01:00
2c4ccc77b9
Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2024-03-22 07:06:44 +01:00
4b0c2bce18
Fix(VIM-696) Restore visual mode after undo/redo, and disable incompatible actions 2024-03-22 07:06:43 +01:00
a2d5adbc6c
Revert(VIM-2884): Fix moving lines to cursor 2024-03-22 07:06:43 +01:00
3b5b4ed84c
Respect count with <Action> mappings 2024-03-22 07:06:43 +01:00
a33addb6ea
Change matchit plugin to use HTML patterns in unrecognized files 2024-03-22 07:06:43 +01:00
626b6ee0af
Reset insert mode when switching active editor 2024-03-22 07:06:43 +01:00
f353f47987
Remove update checker 2024-03-21 23:00:08 +01:00
5410187bd5
Set custom plugin version 2024-03-21 23:00:06 +01:00
158 changed files with 1798 additions and 2430 deletions

View File

@ -15,7 +15,11 @@ jobs:
distribution: zulu distribution: zulu
java-version: 17 java-version: 17
- name: Setup FFmpeg - name: Setup FFmpeg
run: brew install 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 }}
- name: Setup Gradle - name: Setup Gradle
uses: gradle/gradle-build-action@v2.4.2 uses: gradle/gradle-build-action@v2.4.2
- name: Build Plugin - name: Build Plugin

View File

@ -18,7 +18,11 @@ jobs:
with: with:
python-version: '3.10' python-version: '3.10'
- name: Setup FFmpeg - name: Setup FFmpeg
run: brew install 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 }}
- name: Setup Gradle - name: Setup Gradle
uses: gradle/gradle-build-action@v2.4.2 uses: gradle/gradle-build-action@v2.4.2
- name: Build Plugin - name: Build Plugin

View File

@ -15,7 +15,11 @@ jobs:
distribution: zulu distribution: zulu
java-version: 17 java-version: 17
- name: Setup FFmpeg - name: Setup FFmpeg
run: brew install 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 }}
- name: Setup Gradle - name: Setup Gradle
uses: gradle/gradle-build-action@v2.4.2 uses: gradle/gradle-build-action@v2.4.2
- name: Build Plugin - name: Build Plugin

View File

@ -0,0 +1,34 @@
# 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 }}

View File

@ -25,7 +25,6 @@ object Project : Project({
// Active tests // Active tests
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT")) buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(TestingBuildType("2023.3", "<default>", version = "2023.3")) 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(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(PropertyBased) buildType(PropertyBased)

View File

@ -21,7 +21,7 @@ repositories {
} }
dependencies { dependencies {
compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.23-1.0.20") compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.23-1.0.19")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion") { 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 // kotlin stdlib is provided by IJ, so there is no need to include it into the distribution
exclude("org.jetbrains.kotlin", "kotlin-stdlib") exclude("org.jetbrains.kotlin", "kotlin-stdlib")

View File

@ -51,11 +51,11 @@ buildscript {
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.9.0.202403050737-r") classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.9.0.202403050737-r")
classpath("org.kohsuke:github-api:1.305") classpath("org.kohsuke:github-api:1.305")
classpath("io.ktor:ktor-client-core:2.3.10") classpath("io.ktor:ktor-client-core:2.3.9")
classpath("io.ktor:ktor-client-cio:2.3.10") classpath("io.ktor:ktor-client-cio:2.3.9")
classpath("io.ktor:ktor-client-auth:2.3.10") classpath("io.ktor:ktor-client-auth:2.3.9")
classpath("io.ktor:ktor-client-content-negotiation:2.3.10") classpath("io.ktor:ktor-client-content-negotiation:2.3.9")
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.10") classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.9")
// This comes from the changelog plugin // This comes from the changelog plugin
// classpath("org.jetbrains:markdown:0.3.1") // classpath("org.jetbrains:markdown:0.3.1")
@ -69,11 +69,11 @@ plugins {
application application
id("java-test-fixtures") id("java-test-fixtures")
id("org.jetbrains.intellij") version "1.17.3" id("org.jetbrains.intellij") version "1.17.2"
id("org.jetbrains.changelog") version "2.2.0" id("org.jetbrains.changelog") version "2.2.0"
id("org.jetbrains.kotlinx.kover") version "0.6.1" id("org.jetbrains.kotlinx.kover") version "0.6.1"
id("com.dorongold.task-tree") version "3.0.0" id("com.dorongold.task-tree") version "2.1.1"
id("com.google.devtools.ksp") version "1.9.22-1.0.17" id("com.google.devtools.ksp") version "1.9.22-1.0.17"
} }
@ -141,7 +141,7 @@ dependencies {
testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion") testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin // https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
testImplementation("org.mockito.kotlin:mockito-kotlin:5.3.1") testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2") testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2") testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2")
@ -264,6 +264,9 @@ tasks {
runPluginVerifier { runPluginVerifier {
downloadDir.set("${project.buildDir}/pluginVerifier/ides") downloadDir.set("${project.buildDir}/pluginVerifier/ides")
teamCityOutputFormat.set(true) teamCityOutputFormat.set(true)
// The latest version of the plugin verifier is broken, so temporally use the stable version
verifierVersion = "1.307"
} }
generateGrammarSource { generateGrammarSource {

View File

@ -9,12 +9,12 @@
# suppress inspection "UnusedProperty" for whole file # suppress inspection "UnusedProperty" for whole file
#ideaVersion=LATEST-EAP-SNAPSHOT #ideaVersion=LATEST-EAP-SNAPSHOT
ideaVersion=2024.1 ideaVersion=2023.3.3
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type # Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
ideaType=IC ideaType=IC
downloadIdeaSources=true downloadIdeaSources=true
instrumentPluginCode=true instrumentPluginCode=true
version=chylex-32 version=chylex-31
javaVersion=17 javaVersion=17
remoteRobotVersion=0.11.22 remoteRobotVersion=0.11.22
antlrVersion=4.10.1 antlrVersion=4.10.1
@ -42,3 +42,4 @@ kotlin.stdlib.default.dependency=false
# Disable incremental annotation processing # Disable incremental annotation processing
ksp.incremental=false ksp.incremental=false

View File

@ -22,11 +22,11 @@ repositories {
dependencies { dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.23") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.23")
implementation("io.ktor:ktor-client-core:2.3.10") implementation("io.ktor:ktor-client-core:2.3.9")
implementation("io.ktor:ktor-client-cio:2.3.10") implementation("io.ktor:ktor-client-cio:2.3.9")
implementation("io.ktor:ktor-client-content-negotiation:2.3.10") implementation("io.ktor:ktor-client-content-negotiation:2.3.9")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.10") implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.9")
implementation("io.ktor:ktor-client-auth:2.3.10") implementation("io.ktor:ktor-client-auth:2.3.9")
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r") implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh // This is needed for jgit to connect to ssh
@ -58,6 +58,13 @@ tasks.register("checkNewPluginDependencies", JavaExec::class) {
classpath = sourceSets["main"].runtimeClasspath 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) { tasks.register("calculateNewVersion", JavaExec::class) {
group = "release" group = "release"
mainClass.set("scripts.release.CalculateNewVersionKt") mainClass.set("scripts.release.CalculateNewVersionKt")

View File

@ -49,13 +49,17 @@ suspend fun main() {
} }
val output = response.body<List<String>>().toSet() val output = response.body<List<String>>().toSet()
println(output) println(output)
if (knownPlugins != output) {
val newPlugins = (output - knownPlugins).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") }
// val removedPlugins = (knownPlugins - output.toSet()).map { it to (getPluginLinkByXmlId(it) ?: "Can't find plugin link") }
error( error(
""" """
Unregistered plugins: Unregistered plugins:
${newPlugins.joinToString(separator = "\n") { it.first + "(" + it.second + ")" }} ${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"}
""".trimIndent() """.trimIndent()
) )
} }

View File

@ -0,0 +1,62 @@
/*
* 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()
}

View File

@ -77,7 +77,7 @@ public class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActio
val modifiers = if (charTyped == ' ' && VimKeyListener.isSpaceShift) KeyEvent.SHIFT_DOWN_MASK else 0 val modifiers = if (charTyped == ' ' && VimKeyListener.isSpaceShift) KeyEvent.SHIFT_DOWN_MASK else 0
val keyStroke = KeyStroke.getKeyStroke(charTyped, modifiers) val keyStroke = KeyStroke.getKeyStroke(charTyped, modifiers)
val startTime = if (traceTime) System.currentTimeMillis() else null val startTime = if (traceTime) System.currentTimeMillis() else null
handler.handleKey(editor.vim, keyStroke, context.vim, handler.keyHandlerState) handler.handleKey(editor.vim, keyStroke, injector.executionContextManager.onEditor(editor.vim, context.vim), handler.keyHandlerState)
if (startTime != null) { if (startTime != null) {
val duration = System.currentTimeMillis() - startTime val duration = System.currentTimeMillis() - startTime
LOG.info("VimTypedAction '$charTyped': $duration ms") LOG.info("VimTypedAction '$charTyped': $duration ms")

View File

@ -79,7 +79,12 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
try { try {
val start = if (traceTime) System.currentTimeMillis() else null val start = if (traceTime) System.currentTimeMillis() else null
val keyHandler = KeyHandler.getInstance() val keyHandler = KeyHandler.getInstance()
keyHandler.handleKey(editor.vim, keyStroke, e.dataContext.vim, keyHandler.keyHandlerState) keyHandler.handleKey(
editor.vim,
keyStroke,
injector.executionContextManager.onEditor(editor.vim, e.dataContext.vim),
keyHandler.keyHandlerState,
)
if (start != null) { if (start != null) {
val duration = System.currentTimeMillis() - start val duration = System.currentTimeMillis() - start
LOG.info("VimShortcut update '$keyStroke': $duration ms") LOG.info("VimShortcut update '$keyStroke': $duration ms")
@ -376,10 +381,6 @@ private class ActionEnableStatus(
} }
} }
override fun toString(): String {
return "ActionEnableStatus(isEnabled=$isEnabled, message='$message', logLevel=$logLevel)"
}
companion object { companion object {
private val LOG = logger<ActionEnableStatus>() private val LOG = logger<ActionEnableStatus>()

View File

@ -34,7 +34,7 @@ public class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecu
} }
injector.editorGroup.notifyIdeaJoin(editor) injector.editorGroup.notifyIdeaJoin(editor)
var res = true var res = true
editor.nativeCarets().sortedByDescending { it.offset }.forEach { caret -> editor.nativeCarets().sortedByDescending { it.offset.point }.forEach { caret ->
if (!injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, true, operatorArguments)) { if (!injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, true, operatorArguments)) {
res = false res = false
} }

View File

@ -39,7 +39,7 @@ public class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExe
return true return true
} }
var res = true var res = true
editor.nativeCarets().sortedByDescending { it.offset }.forEach { caret -> editor.nativeCarets().sortedByDescending { it.offset.point }.forEach { caret ->
if (!caret.isValid) return@forEach if (!caret.isValid) return@forEach
val range = caretsAndSelections[caret] ?: return@forEach val range = caretsAndSelections[caret] ?: return@forEach
if (!injector.changeGroup.deleteJoinRange( if (!injector.changeGroup.deleteJoinRange(

View File

@ -39,7 +39,7 @@ public class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.Sin
return true return true
} }
var res = true var res = true
editor.carets().sortedByDescending { it.offset }.forEach { caret -> editor.carets().sortedByDescending { it.offset.point }.forEach { caret ->
if (!caret.isValid) return@forEach if (!caret.isValid) return@forEach
val range = caretsAndSelections[caret] ?: return@forEach val range = caretsAndSelections[caret] ?: return@forEach
if (!injector.changeGroup.deleteJoinRange( if (!injector.changeGroup.deleteJoinRange(

View File

@ -9,6 +9,8 @@
package com.maddyhome.idea.vim.common package com.maddyhome.idea.vim.common
import com.intellij.application.options.CodeStyle 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.editor.Editor
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.codeStyle.CommonCodeStyleSettings.IndentOptions import com.intellij.psi.codeStyle.CommonCodeStyleSettings.IndentOptions
@ -37,12 +39,13 @@ internal class IndentConfig private constructor(indentOptions: IndentOptions) {
companion object { companion object {
@JvmStatic @JvmStatic
fun create(editor: Editor): IndentConfig { fun create(editor: Editor, context: DataContext): IndentConfig {
return create(editor, editor.project) return create(editor, PlatformDataKeys.PROJECT.getData(context))
} }
@JvmStatic @JvmStatic
fun create(editor: Editor, project: Project?): IndentConfig { @JvmOverloads
fun create(editor: Editor, project: Project? = editor.project): IndentConfig {
val indentOptions = if (project != null) { val indentOptions = if (project != null) {
CodeStyle.getIndentOptions(project, editor.document) CodeStyle.getIndentOptions(project, editor.document)
} else { } else {

View File

@ -7,7 +7,6 @@
*/ */
package com.maddyhome.idea.vim.extension package com.maddyhome.idea.vim.extension
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
@ -143,7 +142,7 @@ public object VimExtensionFacade {
*/ */
@JvmStatic @JvmStatic
public fun executeNormalWithoutMapping(keys: List<KeyStroke>, editor: Editor) { public fun executeNormalWithoutMapping(keys: List<KeyStroke>, editor: Editor) {
val context = injector.executionContextManager.getEditorExecutionContext(editor.vim) val context = injector.executionContextManager.onEditor(editor.vim)
val keyHandler = KeyHandler.getInstance() val keyHandler = KeyHandler.getInstance()
keys.forEach { keyHandler.handleKey(editor.vim, it, context, false, false, keyHandler.keyHandlerState) } keys.forEach { keyHandler.handleKey(editor.vim, it, context, false, false, keyHandler.keyHandlerState) }
} }
@ -182,8 +181,8 @@ public object VimExtensionFacade {
/** Returns a string typed in the input box similar to 'input()'. */ /** Returns a string typed in the input box similar to 'input()'. */
@JvmStatic @JvmStatic
public fun inputString(editor: Editor, context: DataContext, prompt: String, finishOn: Char?): String { public fun inputString(editor: Editor, prompt: String, finishOn: Char?): String {
return service<CommandLineHelper>().inputString(editor.vim, context.vim, prompt, finishOn) ?: "" return service<CommandLineHelper>().inputString(editor.vim, prompt, finishOn) ?: ""
} }
/** Get the current contents of the given register similar to 'getreg()'. */ /** Get the current contents of the given register similar to 'getreg()'. */

View File

@ -8,21 +8,25 @@
package com.maddyhome.idea.vim.extension.nerdtree 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.ActionManager
import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.PlatformCoreDataKeys
import com.intellij.openapi.actionSystem.PlatformDataKeys import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.ui.getUserData import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.putUserData import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.util.Key import com.intellij.openapi.startup.ProjectActivity
import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowId import com.intellij.openapi.wm.ToolWindowId
import com.intellij.openapi.wm.ex.ToolWindowManagerEx import com.intellij.openapi.wm.ex.ToolWindowManagerEx
import com.intellij.openapi.wm.ex.ToolWindowManagerListener
import com.intellij.ui.KeyStrokeAdapter import com.intellij.ui.KeyStrokeAdapter
import com.intellij.ui.TreeExpandCollapse import com.intellij.ui.TreeExpandCollapse
import com.intellij.ui.speedSearch.SpeedSearchSupply import com.intellij.ui.speedSearch.SpeedSearchSupply
@ -49,8 +53,6 @@ import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import javax.swing.JComponent
import javax.swing.JTree
import javax.swing.KeyStroke import javax.swing.KeyStroke
import javax.swing.SwingConstants import javax.swing.SwingConstants
@ -130,6 +132,7 @@ internal class NerdTree : VimExtension {
synchronized(Util.monitor) { synchronized(Util.monitor) {
Util.commandsRegistered = true Util.commandsRegistered = true
ProjectManager.getInstance().openProjects.forEach { project -> installDispatcher(project) }
} }
} }
@ -161,8 +164,39 @@ 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() { class NerdDispatcher : DumbAwareAction() {
internal var waitForSearch = false internal var waitForSearch = false
internal var speedSearchListenerInstalled = false
override fun actionPerformed(e: AnActionEvent) { override fun actionPerformed(e: AnActionEvent) {
var keyStroke = getKeyStroke(e) ?: return var keyStroke = getKeyStroke(e) ?: return
@ -210,6 +244,10 @@ internal class NerdTree : VimExtension {
} }
companion object { companion object {
fun getInstance(project: Project): NerdDispatcher {
return project.getService(NerdDispatcher::class.java)
}
private const val ESCAPE_KEY_CODE = 27 private const val ESCAPE_KEY_CODE = 27
} }
@ -245,15 +283,20 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"NERDTreeMapActivateNode", "NERDTreeMapActivateNode",
"o", "o",
NerdAction.Code { _, dataContext, e -> NerdAction.Code { project, dataContext, _ ->
val tree = getTree(e) ?: return@Code val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
val array = CommonDataKeys.NAVIGATABLE_ARRAY.getData(dataContext)?.filter { it.canNavigateToSource() }
if (array.isNullOrEmpty()) {
val row = tree.selectionRows?.getOrNull(0) ?: return@Code val row = tree.selectionRows?.getOrNull(0) ?: return@Code
if (tree.isExpanded(row)) { if (tree.isExpanded(row)) {
tree.collapseRow(row) tree.collapseRow(row)
} else { } else {
tree.expandRow(row) tree.expandRow(row)
} }
} else {
array.forEach { it.navigate(true) }
}
}, },
) )
registerCommand( registerCommand(
@ -331,8 +374,8 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"NERDTreeMapOpenRecursively", "NERDTreeMapOpenRecursively",
"O", "O",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
TreeExpandCollapse.expandAll(tree) TreeExpandCollapse.expandAll(tree)
tree.selectionPath?.let { tree.selectionPath?.let {
TreeUtil.scrollToVisible(tree, it, false) TreeUtil.scrollToVisible(tree, it, false)
@ -342,8 +385,8 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"NERDTreeMapCloseChildren", "NERDTreeMapCloseChildren",
"X", "X",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
TreeExpandCollapse.collapse(tree) TreeExpandCollapse.collapse(tree)
tree.selectionPath?.let { tree.selectionPath?.let {
TreeUtil.scrollToVisible(tree, it, false) TreeUtil.scrollToVisible(tree, it, false)
@ -353,8 +396,8 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"NERDTreeMapCloseDir", "NERDTreeMapCloseDir",
"x", "x",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
val currentPath = tree.selectionPath ?: return@Code val currentPath = tree.selectionPath ?: return@Code
if (tree.isExpanded(currentPath)) { if (tree.isExpanded(currentPath)) {
tree.collapsePath(currentPath) tree.collapsePath(currentPath)
@ -372,8 +415,8 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"NERDTreeMapJumpParent", "NERDTreeMapJumpParent",
"p", "p",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
val currentPath = tree.selectionPath ?: return@Code val currentPath = tree.selectionPath ?: return@Code
val parentPath = currentPath.parentPath ?: return@Code val parentPath = currentPath.parentPath ?: return@Code
if (parentPath.parentPath != null) { if (parentPath.parentPath != null) {
@ -386,8 +429,8 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"NERDTreeMapJumpFirstChild", "NERDTreeMapJumpFirstChild",
"K", "K",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
val currentPath = tree.selectionPath ?: return@Code val currentPath = tree.selectionPath ?: return@Code
val parent = currentPath.parentPath ?: return@Code val parent = currentPath.parentPath ?: return@Code
val row = tree.getRowForPath(parent) val row = tree.getRowForPath(parent)
@ -399,8 +442,8 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"NERDTreeMapJumpLastChild", "NERDTreeMapJumpLastChild",
"J", "J",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
val currentPath = tree.selectionPath ?: return@Code val currentPath = tree.selectionPath ?: return@Code
val currentPathCount = currentPath.pathCount val currentPathCount = currentPath.pathCount
@ -445,17 +488,18 @@ internal class NerdTree : VimExtension {
registerCommand( registerCommand(
"/", "/",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code NerdDispatcher.getInstance(project).waitForSearch = true
tree.getUserData(KEY)?.waitForSearch = true
}, },
) )
registerCommand( registerCommand(
"<ESC>", "<ESC>",
NerdAction.Code { _, _, e -> NerdAction.Code { project, _, _ ->
val tree = getTree(e) ?: return@Code val instance = NerdDispatcher.getInstance(project)
tree.getUserData(KEY)?.waitForSearch = false if (instance.waitForSearch) {
instance.waitForSearch = false
}
}, },
) )
@ -489,21 +533,6 @@ internal class NerdTree : VimExtension {
companion object { companion object {
const val pluginName = "NERDTree" const val pluginName = "NERDTree"
private val LOG = logger<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
}
}
} }
} }
@ -538,6 +567,12 @@ private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> {
} }
} }
private fun getTree(e: AnActionEvent): JTree? { private fun installDispatcher(project: Project) {
return e.dataContext.getData(PlatformCoreDataKeys.CONTEXT_COMPONENT) as? JTree 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,
)
} }

View File

@ -1,25 +0,0 @@
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)
}
}
}
}

View File

@ -1,9 +0,0 @@
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() {}
}

View File

@ -8,7 +8,6 @@
package com.maddyhome.idea.vim.extension.replacewithregister package com.maddyhome.idea.vim.extension.replacewithregister
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
@ -65,7 +64,7 @@ internal class ReplaceWithRegister : VimExtension {
val selectionEnd = caret.selectionEnd val selectionEnd = caret.selectionEnd
val visualSelection = caret to VimSelection.create(selectionStart, selectionEnd - 1, typeInEditor, editor) val visualSelection = caret to VimSelection.create(selectionStart, selectionEnd - 1, typeInEditor, editor)
doReplace(editor.ij, context.ij, caret, PutData.VisualSelection(mapOf(visualSelection), typeInEditor)) doReplace(editor.ij, caret, PutData.VisualSelection(mapOf(visualSelection), typeInEditor))
} }
editor.exitVisualMode() editor.exitVisualMode()
} }
@ -93,7 +92,7 @@ internal class ReplaceWithRegister : VimExtension {
val visualSelection = caret to VimSelection.create(lineStart, lineEnd, SelectionType.LINE_WISE, editor) val visualSelection = caret to VimSelection.create(lineStart, lineEnd, SelectionType.LINE_WISE, editor)
caretsAndSelections += visualSelection caretsAndSelections += visualSelection
doReplace(editor.ij, context.ij, caret, PutData.VisualSelection(mapOf(visualSelection), SelectionType.LINE_WISE)) doReplace(editor.ij, caret, PutData.VisualSelection(mapOf(visualSelection), SelectionType.LINE_WISE))
} }
editor.sortedCarets().forEach { caret -> editor.sortedCarets().forEach { caret ->
@ -121,7 +120,7 @@ internal class ReplaceWithRegister : VimExtension {
selectionType ?: SelectionType.CHARACTER_WISE, selectionType ?: SelectionType.CHARACTER_WISE,
) )
// todo multicaret // todo multicaret
doReplace(ijEditor, context.ij, editor.primaryCaret(), visualSelection) doReplace(ijEditor, editor.primaryCaret(), visualSelection)
return true return true
} }
@ -141,7 +140,7 @@ internal class ReplaceWithRegister : VimExtension {
} }
} }
private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimCaret, visualSelection: PutData.VisualSelection) { private fun doReplace(editor: Editor, caret: ImmutableVimCaret, visualSelection: PutData.VisualSelection) {
val registerGroup = injector.registerGroup val registerGroup = injector.registerGroup
val lastRegisterChar = if (editor.caretModel.caretCount == 1) registerGroup.currentRegister else registerGroup.getCurrentRegisterForMulticaret() val lastRegisterChar = if (editor.caretModel.caretCount == 1) registerGroup.currentRegister else registerGroup.getCurrentRegisterForMulticaret()
val savedRegister = caret.registerStorage.getRegister(lastRegisterChar) ?: return val savedRegister = caret.registerStorage.getRegister(lastRegisterChar) ?: return
@ -169,7 +168,7 @@ private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimC
ClipboardOptionHelper.IdeaputDisabler().use { ClipboardOptionHelper.IdeaputDisabler().use {
VimPlugin.getPut().putText( VimPlugin.getPut().putText(
vimEditor, vimEditor,
context.vim, injector.executionContextManager.onEditor(editor.vim),
putData, putData,
operatorArguments = OperatorArguments( operatorArguments = OperatorArguments(
editor.vimStateMachine?.isOperatorPending(vimEditor.mode) ?: false, editor.vimStateMachine?.isOperatorPending(vimEditor.mode) ?: false,

View File

@ -118,7 +118,7 @@ internal class IdeaVimSneakExtension : VimExtension {
var lastSymbols: String = "" var lastSymbols: String = ""
fun jumpTo(editor: VimEditor, charone: Char, chartwo: Char, sneakDirection: Direction): TextRange? { fun jumpTo(editor: VimEditor, charone: Char, chartwo: Char, sneakDirection: Direction): TextRange? {
val caret = editor.primaryCaret() val caret = editor.primaryCaret()
val position = caret.offset val position = caret.offset.point
val chars = editor.text() val chars = editor.text()
val foundPosition = sneakDirection.findBiChar(editor, chars, position, charone, chartwo) val foundPosition = sneakDirection.findBiChar(editor, chars, position, charone, chartwo)
if (foundPosition != null) { if (foundPosition != null) {

View File

@ -7,7 +7,6 @@
*/ */
package com.maddyhome.idea.vim.extension.surround package com.maddyhome.idea.vim.extension.surround
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
@ -35,7 +34,6 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMa
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
import com.maddyhome.idea.vim.extension.exportOperatorFunction 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.helper.runWithEveryCaretAndRestore
import com.maddyhome.idea.vim.key.OperatorFunction import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimCaret import com.maddyhome.idea.vim.newapi.IjVimCaret
@ -46,6 +44,7 @@ import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
import com.maddyhome.idea.vim.put.PutData import com.maddyhome.idea.vim.put.PutData
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.state.mode.selectionType import com.maddyhome.idea.vim.state.mode.selectionType
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
@ -101,7 +100,7 @@ internal class VimSurroundExtension : VimExtension {
val ijEditor = editor.ij val ijEditor = editor.ij
val c = getChar(ijEditor) val c = getChar(ijEditor)
if (c.code == 0) return if (c.code == 0) return
val pair = getOrInputPair(c, ijEditor, context.ij) ?: return val pair = getOrInputPair(c, ijEditor) ?: return
editor.forEachCaret { editor.forEachCaret {
val line = it.getBufferPosition().line val line = it.getBufferPosition().line
@ -151,7 +150,7 @@ internal class VimSurroundExtension : VimExtension {
val charTo = getChar(editor.ij) val charTo = getChar(editor.ij)
if (charTo.code == 0) return if (charTo.code == 0) return
val newSurround = getOrInputPair(charTo, editor.ij, context.ij) ?: return val newSurround = getOrInputPair(charTo, editor.ij) ?: return
runWriteAction { change(editor, context, charFrom, newSurround) } runWriteAction { change(editor, context, charFrom, newSurround) }
} }
@ -228,12 +227,12 @@ internal class VimSurroundExtension : VimExtension {
val searchHelper = injector.searchHelper val searchHelper = injector.searchHelper
return when (char) { return when (char) {
't' -> searchHelper.findBlockTagRange(editor, caret, 1, true) 't' -> searchHelper.findBlockTagRange(editor, caret, 1, true)
'(', ')', 'b' -> findBlockRange(editor, caret, '(', 1, true) '(', ')', 'b' -> searchHelper.findBlockRange(editor, caret, '(', 1, true)
'[', ']' -> findBlockRange(editor, caret, '[', 1, true) '[', ']' -> searchHelper.findBlockRange(editor, caret, '[', 1, true)
'{', '}', 'B' -> findBlockRange(editor, caret, '{', 1, true) '{', '}', 'B' -> searchHelper.findBlockRange(editor, caret, '{', 1, true)
'<', '>' -> findBlockRange(editor, caret, '<', 1, true) '<', '>' -> searchHelper.findBlockRange(editor, caret, '<', 1, true)
'`', '\'', '"' -> { '`', '\'', '"' -> {
val caretOffset = caret.offset val caretOffset = caret.offset.point
val text = editor.text() val text = editor.text()
if (text.getOrNull(caretOffset - 1) == char && text.getOrNull(caretOffset) == char) { if (text.getOrNull(caretOffset - 1) == char && text.getOrNull(caretOffset) == char) {
TextRange(caretOffset - 1, caretOffset + 1) TextRange(caretOffset - 1, caretOffset + 1)
@ -270,23 +269,23 @@ internal class VimSurroundExtension : VimExtension {
private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction { private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction {
override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean { override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val ijEditor = vimEditor.ij val editor = vimEditor.ij
val c = getChar(ijEditor) val c = getChar(editor)
if (c.code == 0) return true if (c.code == 0) return true
val pair = getOrInputPair(c, ijEditor, context.ij) ?: return false val pair = getOrInputPair(c, editor) ?: return false
runWriteAction { runWriteAction {
val change = VimPlugin.getChange() val change = VimPlugin.getChange()
if (supportsMultipleCursors) { if (supportsMultipleCursors) {
ijEditor.runWithEveryCaretAndRestore { editor.runWithEveryCaretAndRestore {
applyOnce(ijEditor, change, pair, count) applyOnce(editor, change, pair, count)
} }
} }
else { else {
applyOnce(ijEditor, change, pair, count) applyOnce(editor, change, pair, count)
// Jump back to start // Jump back to start
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor) executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor)
} }
} }
return true return true
@ -348,8 +347,8 @@ private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_
null null
} }
private fun inputTagPair(editor: Editor, context: DataContext): Pair<String, String>? { private fun inputTagPair(editor: Editor): Pair<String, String>? {
val tagInput = inputString(editor, context, "<", '>') val tagInput = inputString(editor, "<", '>')
val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput) val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
return if (matcher.find()) { return if (matcher.find()) {
val tagName = matcher.group(1) val tagName = matcher.group(1)
@ -362,18 +361,17 @@ private fun inputTagPair(editor: Editor, context: DataContext): Pair<String, Str
private fun inputFunctionName( private fun inputFunctionName(
editor: Editor, editor: Editor,
context: DataContext,
withInternalSpaces: Boolean, withInternalSpaces: Boolean,
): Pair<String, String>? { ): Pair<String, String>? {
val functionNameInput = inputString(editor, context, "function: ", null) val functionNameInput = inputString(editor, "function: ", null)
if (functionNameInput.isEmpty()) return null if (functionNameInput.isEmpty()) return null
return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")" return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
} }
private fun getOrInputPair(c: Char, editor: Editor, context: DataContext): Pair<String, String>? = when (c) { private fun getOrInputPair(c: Char, editor: Editor): Pair<String, String>? = when (c) {
'<', 't' -> inputTagPair(editor, context) '<', 't' -> inputTagPair(editor)
'f' -> inputFunctionName(editor, context, false) 'f' -> inputFunctionName(editor, false)
'F' -> inputFunctionName(editor, context, true) 'F' -> inputFunctionName(editor, true)
else -> getSurroundPair(c) else -> getSurroundPair(c)
} }

View File

@ -197,7 +197,7 @@ public class ChangeGroup : VimChangeGroupBase() {
val allowWrap = injector.options(editor).whichwrap.contains("~") val allowWrap = injector.options(editor).whichwrap.contains("~")
var motion = injector.motion.getHorizontalMotion(editor, caret, count, true, allowWrap) var motion = injector.motion.getHorizontalMotion(editor, caret, count, true, allowWrap)
if (motion is Motion.Error) return false if (motion is Motion.Error) return false
changeCase(editor, caret, caret.offset, (motion as AbsoluteOffset).offset, CharacterHelper.CASE_TOGGLE) changeCase(editor, caret, caret.offset.point, (motion as AbsoluteOffset).offset, CharacterHelper.CASE_TOGGLE)
motion = injector.motion.getHorizontalMotion( motion = injector.motion.getHorizontalMotion(
editor, editor,
caret, caret,
@ -235,7 +235,8 @@ public class ChangeGroup : VimChangeGroupBase() {
} }
val lineLength = editor.lineLength(line) val lineLength = editor.lineLength(line)
if (column < VimMotionGroupBase.LAST_COLUMN && lineLength < column) { if (column < VimMotionGroupBase.LAST_COLUMN && lineLength < column) {
val pad = EditorHelper.pad((editor as IjVimEditor).editor, line, column) val pad =
EditorHelper.pad((editor as IjVimEditor).editor, (context as IjEditorExecutionContext).context, line, column)
val offset = editor.getLineEndOffset(line) val offset = editor.getLineEndOffset(line)
insertText(editor, caret, offset, pad) insertText(editor, caret, offset, pad)
} }
@ -394,7 +395,7 @@ public class ChangeGroup : VimChangeGroupBase() {
context: ExecutionContext, context: ExecutionContext,
range: TextRange, range: TextRange,
) { ) {
val startPos = editor.offsetToBufferPosition(caret.offset) val startPos = editor.offsetToBufferPosition(caret.offset.point)
val startOffset = editor.getLineStartForOffset(range.startOffset) val startOffset = editor.getLineStartForOffset(range.startOffset)
val endOffset = editor.getLineEndForOffset(range.endOffset) val endOffset = editor.getLineEndForOffset(range.endOffset)
val ijEditor = (editor as IjVimEditor).editor val ijEditor = (editor as IjVimEditor).editor
@ -450,7 +451,7 @@ public class ChangeGroup : VimChangeGroupBase() {
dir: Int, dir: Int,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
) { ) {
val start = caret.offset val start = caret.offset.point
val end = injector.motion.moveCaretToRelativeLineEnd(editor, caret, lines - 1, true) val end = injector.motion.moveCaretToRelativeLineEnd(editor, caret, lines - 1, true)
indentRange(editor, caret, context, TextRange(start, end), 1, dir, operatorArguments) indentRange(editor, caret, context, TextRange(start, end), 1, dir, operatorArguments)
} }
@ -484,7 +485,7 @@ public class ChangeGroup : VimChangeGroupBase() {
// Remember the current caret column // Remember the current caret column
val intendedColumn = caret.vimLastColumn val intendedColumn = caret.vimLastColumn
val indentConfig = create((editor as IjVimEditor).editor) val indentConfig = create((editor as IjVimEditor).editor, (context as IjEditorExecutionContext).context)
val sline = editor.offsetToBufferPosition(range.startOffset).line val sline = editor.offsetToBufferPosition(range.startOffset).line
val endLogicalPosition = editor.offsetToBufferPosition(range.endOffset) val endLogicalPosition = editor.offsetToBufferPosition(range.endOffset)
val eline = if (endLogicalPosition.column == 0) max((endLogicalPosition.line - 1).toDouble(), 0.0) val eline = if (endLogicalPosition.column == 0) max((endLogicalPosition.line - 1).toDouble(), 0.0)

View File

@ -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 // 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. // to know that a read-only editor that is hosting a console view with a running process can be treated as writable.
Runnable switchToInsertMode = () -> { Runnable switchToInsertMode = () -> {
ExecutionContext context = injector.getExecutionContextManager().getEditorExecutionContext(new IjVimEditor(editor)); ExecutionContext.Editor context = injector.getExecutionContextManager().onEditor(new IjVimEditor(editor), null);
VimPlugin.getChange().insertBeforeCursor(new IjVimEditor(editor), context); VimPlugin.getChange().insertBeforeCursor(new IjVimEditor(editor), context);
KeyHandler.getInstance().reset(new IjVimEditor(editor)); KeyHandler.getInstance().reset(new IjVimEditor(editor));
}; };

View File

@ -22,12 +22,9 @@ import com.intellij.openapi.fileEditor.impl.EditorsSplitters;
import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile; 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.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.ProjectScope; import com.intellij.psi.search.ProjectScope;
@ -47,7 +44,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector; import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
@ -449,28 +445,4 @@ public class FileGroup extends VimFileBase {
LastTabService.getInstance(event.getManager().getProject()).setLastTab(event.getOldFile()); 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();
}
} }

View File

@ -1,61 +0,0 @@
/*
* Copyright 2003-2024 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.group
import com.intellij.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)
}
}

View File

@ -11,24 +11,41 @@ import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.components.Service import com.intellij.openapi.components.Service
import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.VisualPosition import com.intellij.openapi.editor.VisualPosition
import com.intellij.openapi.fileEditor.FileEditorManagerEvent import com.intellij.openapi.fileEditor.FileEditorManagerEvent
import com.intellij.openapi.fileEditor.TextEditor import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.fileEditor.impl.EditorWindow 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.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.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimChangeGroupBase import com.maddyhome.idea.vim.api.VimChangeGroupBase
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimMotionGroupBase 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.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.getLeadingCharacterOffset
import com.maddyhome.idea.vim.api.getVisualLineCount 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.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.normalizeVisualColumn
import com.maddyhome.idea.vim.api.normalizeVisualLine 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.api.visualLineToBufferLine
import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.MotionType import com.maddyhome.idea.vim.command.MotionType
@ -37,9 +54,12 @@ import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ExOutputModel import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.handler.Motion import com.maddyhome.idea.vim.handler.Motion
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset 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.MotionActionHandler
import com.maddyhome.idea.vim.handler.TextObjectActionHandler 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.EditorHelper
import com.maddyhome.idea.vim.helper.SearchHelper
import com.maddyhome.idea.vim.helper.exitVisualMode import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.fileSize import com.maddyhome.idea.vim.helper.fileSize
import com.maddyhome.idea.vim.helper.getNormalizedScrollOffset import com.maddyhome.idea.vim.helper.getNormalizedScrollOffset
@ -47,13 +67,17 @@ import com.maddyhome.idea.vim.helper.getNormalizedSideScrollOffset
import com.maddyhome.idea.vim.helper.isEndAllowed import com.maddyhome.idea.vim.helper.isEndAllowed
import com.maddyhome.idea.vim.helper.vimLastColumn import com.maddyhome.idea.vim.helper.vimLastColumn
import com.maddyhome.idea.vim.listener.AppCodeTemplates 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.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.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.VimStateMachine import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
import org.jetbrains.annotations.Range import org.jetbrains.annotations.Range
import java.io.File
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@ -66,6 +90,24 @@ internal class MotionGroup : VimMotionGroupBase() {
AppCodeTemplates.onMovement(editor.ij, caret.ij, oldOffset < offset) 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( override fun moveCaretToFirstDisplayLine(
editor: VimEditor, editor: VimEditor,
caret: ImmutableVimCaret, caret: ImmutableVimCaret,
@ -88,12 +130,85 @@ internal class MotionGroup : VimMotionGroupBase() {
return moveCaretToScreenLocation(editor.ij, caret.ij, ScreenLocation.MIDDLE, 0, false) 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 { override fun moveCaretToCurrentDisplayLineMiddle(editor: VimEditor, caret: ImmutableVimCaret): Motion {
val width = EditorHelper.getApproximateScreenWidth(editor.ij) / 2 val width = EditorHelper.getApproximateScreenWidth(editor.ij) / 2
val len = editor.lineLength(editor.currentCaret().getBufferPosition().line) val len = editor.lineLength(editor.currentCaret().getBufferPosition().line)
return moveCaretToColumn(editor, caret, max(0, min(len - 1, width)), false) 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 { override fun moveCaretToCurrentDisplayLineStart(editor: VimEditor, caret: ImmutableVimCaret): Motion {
val col = EditorHelper.getVisualColumnAtLeftOfDisplay(editor.ij, caret.getVisualPosition().line) val col = EditorHelper.getVisualColumnAtLeftOfDisplay(editor.ij, caret.getVisualPosition().line)
return moveCaretToColumn(editor, caret, col, false) return moveCaretToColumn(editor, caret, col, false)
@ -104,7 +219,7 @@ internal class MotionGroup : VimMotionGroupBase() {
caret: ImmutableVimCaret, caret: ImmutableVimCaret,
): @Range(from = 0, to = Int.MAX_VALUE.toLong()) Int { ): @Range(from = 0, to = Int.MAX_VALUE.toLong()) Int {
val col = EditorHelper.getVisualColumnAtLeftOfDisplay(editor.ij, caret.getVisualPosition().line) val col = EditorHelper.getVisualColumnAtLeftOfDisplay(editor.ij, caret.getVisualPosition().line)
val bufferLine = caret.getLine() val bufferLine = caret.getLine().line
return editor.getLeadingCharacterOffset(bufferLine, col) return editor.getLeadingCharacterOffset(bufferLine, col)
} }
@ -117,6 +232,36 @@ internal class MotionGroup : VimMotionGroupBase() {
return moveCaretToColumn(editor, caret, col, allowEnd) 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. * If 'absolute' is true, then set tab index to 'value', otherwise add 'value' to tab index with wraparound.
*/ */
@ -134,18 +279,30 @@ internal class MotionGroup : VimMotionGroupBase() {
} }
override fun moveCaretGotoPreviousTab(editor: VimEditor, context: ExecutionContext, rawCount: Int): Int { override fun moveCaretGotoPreviousTab(editor: VimEditor, context: ExecutionContext, rawCount: Int): Int {
val project = editor.ij.project ?: return editor.currentCaret().offset val project = editor.ij.project ?: return editor.currentCaret().offset.point
val currentWindow = FileEditorManagerEx.getInstanceEx(project).splitters.currentWindow val currentWindow = FileEditorManagerEx.getInstanceEx(project).splitters.currentWindow
switchEditorTab(currentWindow, if (rawCount >= 1) -rawCount else -1, false) switchEditorTab(currentWindow, if (rawCount >= 1) -rawCount else -1, false)
return editor.currentCaret().offset return editor.currentCaret().offset.point
} }
override fun moveCaretGotoNextTab(editor: VimEditor, context: ExecutionContext, rawCount: Int): Int { override fun moveCaretGotoNextTab(editor: VimEditor, context: ExecutionContext, rawCount: Int): Int {
val absolute = rawCount >= 1 val absolute = rawCount >= 1
val project = editor.ij.project ?: return editor.currentCaret().offset val project = editor.ij.project ?: return editor.currentCaret().offset.point
val currentWindow = FileEditorManagerEx.getInstanceEx(project).splitters.currentWindow val currentWindow = FileEditorManagerEx.getInstanceEx(project).splitters.currentWindow
switchEditorTab(currentWindow, if (absolute) rawCount - 1 else 1, absolute) switchEditorTab(currentWindow, if (absolute) rawCount - 1 else 1, absolute)
return editor.currentCaret().offset 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,
)
} }
private enum class ScreenLocation { private enum class ScreenLocation {

View File

@ -538,7 +538,6 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
* *
* @param editor The editor to search in * @param editor The editor to search in
* @param caret The caret to use for initial search offset, and to move for interactive substitution * @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 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 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]` * @param exarg The argument to the substitute command, such as `/{pattern}/{string}/[flags]`
@ -548,14 +547,11 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
@RWLockLabel.SelfSynchronized @RWLockLabel.SelfSynchronized
public boolean processSubstituteCommand(@NotNull VimEditor editor, public boolean processSubstituteCommand(@NotNull VimEditor editor,
@NotNull VimCaret caret, @NotNull VimCaret caret,
@NotNull ExecutionContext context,
@NotNull LineRange range, @NotNull LineRange range,
@NotNull @NonNls String excmd, @NotNull @NonNls String excmd,
@NotNull @NonNls String exarg, @NotNull @NonNls String exarg,
@NotNull VimLContext parent) { @NotNull VimLContext parent) {
if (globalIjOptions(injector).getUseNewRegex()) { if (globalIjOptions(injector).getUseNewRegex()) return super.processSubstituteCommand(editor, caret, range, excmd, exarg, parent);
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. // 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<>(); List<ExException> exceptions = new ArrayList<>();
@ -812,7 +808,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
RangeHighlighter hl = RangeHighlighter hl =
SearchHighlightsHelper.addSubstitutionConfirmationHighlight(((IjVimEditor)editor).getEditor(), startoff, SearchHighlightsHelper.addSubstitutionConfirmationHighlight(((IjVimEditor)editor).getEditor(), startoff,
endoff); endoff);
final ReplaceConfirmationChoice choice = confirmChoice(((IjVimEditor)editor).getEditor(), context, match, ((IjVimCaret)caret).getCaret(), startoff); final ReplaceConfirmationChoice choice = confirmChoice(((IjVimEditor)editor).getEditor(), match, ((IjVimCaret)caret).getCaret(), startoff);
((IjVimEditor)editor).getEditor().getMarkupModel().removeHighlighter(hl); ((IjVimEditor)editor).getEditor().getMarkupModel().removeHighlighter(hl);
switch (choice) { switch (choice) {
case SUBSTITUTE_THIS: case SUBSTITUTE_THIS:
@ -841,7 +837,8 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
caret.moveToOffset(startoff); caret.moveToOffset(startoff);
if (expression != null) { if (expression != null) {
try { try {
match = expression.evaluate(editor, context, parent).toInsertableString(); match =
expression.evaluate(editor, injector.getExecutionContextManager().onEditor(editor, null), parent).toInsertableString();
} }
catch (Exception e) { catch (Exception e) {
exceptions.add((ExException)e); exceptions.add((ExException)e);
@ -992,9 +989,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
return new Pair<>(true, new Triple<>(regmatch, pattern, sp)); return new Pair<>(true, new Triple<>(regmatch, pattern, sp));
} }
private static @NotNull ReplaceConfirmationChoice confirmChoice(@NotNull Editor editor, private static @NotNull ReplaceConfirmationChoice confirmChoice(@NotNull Editor editor, @NotNull String match, @NotNull Caret caret, int startoff) {
@NotNull ExecutionContext context,
@NotNull String match, @NotNull Caret caret, int startoff) {
final Ref<ReplaceConfirmationChoice> result = Ref.create(ReplaceConfirmationChoice.QUIT); final Ref<ReplaceConfirmationChoice> result = Ref.create(ReplaceConfirmationChoice.QUIT);
final Function1<KeyStroke, Boolean> keyStrokeProcessor = key -> { final Function1<KeyStroke, Boolean> keyStrokeProcessor = key -> {
final ReplaceConfirmationChoice choice; final ReplaceConfirmationChoice choice;
@ -1028,6 +1023,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
else { else {
// XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for this method // XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for this method
final ExEntryPanel exEntryPanel = ExEntryPanel.getInstanceWithoutShortcuts(); 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); exEntryPanel.activate(editor, ((IjEditorExecutionContext)context).getContext(), MessageHelper.message("replace.with.0", match), "", 1);
new IjVimCaret(caret).moveToOffset(startoff); new IjVimCaret(caret).moveToOffset(startoff);
ModalEntry.INSTANCE.activate(new IjVimEditor(editor), keyStrokeProcessor); ModalEntry.INSTANCE.activate(new IjVimEditor(editor), keyStrokeProcessor);
@ -1085,9 +1081,9 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
private @Nullable TextRange findNextSearchForGn(@NotNull VimEditor editor, int count, boolean forwards) { private @Nullable TextRange findNextSearchForGn(@NotNull VimEditor editor, int count, boolean forwards) {
if (forwards) { if (forwards) {
final EnumSet<SearchOptions> searchOptions = EnumSet.of(SearchOptions.WRAP, SearchOptions.WHOLE_FILE); final EnumSet<SearchOptions> searchOptions = EnumSet.of(SearchOptions.WRAP, SearchOptions.WHOLE_FILE);
return VimInjectorKt.getInjector().getSearchHelper().findPattern(editor, getLastUsedPattern(), editor.primaryCaret().getOffset(), count, searchOptions); return VimInjectorKt.getInjector().getSearchHelper().findPattern(editor, getLastUsedPattern(), editor.primaryCaret().getOffset().getPoint(), count, searchOptions);
} else { } else {
return searchBackward(editor, editor.primaryCaret().getOffset(), count); return searchBackward(editor, editor.primaryCaret().getOffset().getPoint(), count);
} }
} }

View File

@ -117,7 +117,7 @@ internal object IdeaSelectionControl {
is Mode.VISUAL -> VimPlugin.getVisualMotion().enterVisualMode(editor.vim, mode.selectionType) is Mode.VISUAL -> VimPlugin.getVisualMotion().enterVisualMode(editor.vim, mode.selectionType)
is Mode.SELECT -> VimPlugin.getVisualMotion().enterSelectMode(editor.vim, mode.selectionType) is Mode.SELECT -> VimPlugin.getVisualMotion().enterSelectMode(editor.vim, mode.selectionType)
is Mode.INSERT -> VimPlugin.getChange() is Mode.INSERT -> VimPlugin.getChange()
.insertBeforeCursor(editor.vim, injector.executionContextManager.getEditorExecutionContext(editor.vim)) .insertBeforeCursor(editor.vim, injector.executionContextManager.onEditor(editor.vim))
is Mode.NORMAL -> Unit is Mode.NORMAL -> Unit
else -> error("Unexpected mode: $mode") else -> error("Unexpected mode: $mode")

View File

@ -337,7 +337,7 @@ internal abstract class VimKeyHandler(nextHandler: EditorActionHandler?) : Octop
override fun executeHandler(editor: Editor, caret: Caret?, dataContext: DataContext?) { override fun executeHandler(editor: Editor, caret: Caret?, dataContext: DataContext?) {
val enterKey = key(key) val enterKey = key(key)
val context = dataContext?.vim ?: injector.executionContextManager.getEditorExecutionContext(editor.vim) val context = injector.executionContextManager.onEditor(editor.vim, dataContext?.vim)
val keyHandler = KeyHandler.getInstance() val keyHandler = KeyHandler.getInstance()
keyHandler.handleKey(editor.vim, enterKey, context, keyHandler.keyHandlerState) keyHandler.handleKey(editor.vim, enterKey, context, keyHandler.keyHandlerState)
} }

View File

@ -90,8 +90,6 @@ private fun Editor.updatePrimaryCaretVisualAttributes() {
// Make sure the caret is visible as soon as it's set. It might be invisible while blinking // 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 // 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) { if (!ApplicationManager.getApplication().isUnitTestMode) {
(this as? EditorEx)?.setCaretVisible(true) (this as? EditorEx)?.setCaretVisible(true)
} }

View File

@ -11,8 +11,8 @@ package com.maddyhome.idea.vim.helper
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.Service import com.intellij.openapi.components.Service
import com.maddyhome.idea.vim.action.change.Extension 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.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.ui.ModalEntry import com.maddyhome.idea.vim.ui.ModalEntry
@ -23,7 +23,7 @@ import javax.swing.KeyStroke
@Service @Service
internal class CommandLineHelper : VimCommandLineHelper { internal class CommandLineHelper : VimCommandLineHelper {
override fun inputString(vimEditor: VimEditor, context: ExecutionContext, prompt: String, finishOn: Char?): String? { override fun inputString(vimEditor: VimEditor, prompt: String, finishOn: Char?): String? {
val editor = vimEditor.ij val editor = vimEditor.ij
if (vimEditor.vimStateMachine.isDotRepeatInProgress) { if (vimEditor.vimStateMachine.isDotRepeatInProgress) {
val input = Extension.consumeString() val input = Extension.consumeString()
@ -53,7 +53,7 @@ internal class CommandLineHelper : VimCommandLineHelper {
var text: String? = null var text: String? = null
// XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for input() // XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for input()
val exEntryPanel = ExEntryPanel.getInstanceWithoutShortcuts() val exEntryPanel = ExEntryPanel.getInstanceWithoutShortcuts()
exEntryPanel.activate(editor, context.ij, prompt.ifEmpty { " " }, "", 1) exEntryPanel.activate(editor, injector.executionContextManager.onEditor(editor.vim).ij, prompt.ifEmpty { " " }, "", 1)
ModalEntry.activate(editor.vim) { key: KeyStroke -> ModalEntry.activate(editor.vim) { key: KeyStroke ->
return@activate when { return@activate when {
key.isCloseKeyStroke() -> { key.isCloseKeyStroke() -> {

View File

@ -14,7 +14,6 @@ import com.intellij.openapi.editor.ex.util.EditorUtil
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.openapi.util.UserDataHolder 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( internal class EditorDataContext @Deprecated("Please use `init` method") constructor(
private val editor: Editor, private val editor: Editor,
private val editorContext: DataContext, private val editorContext: DataContext,

View File

@ -211,12 +211,15 @@ public class EditorHelper {
return injector.getEditorGroup().getEditors(new IjVimDocument(doc)).stream().findFirst().orElse(null); return injector.getEditorGroup().getEditors(new IjVimDocument(doc)).stream().findFirst().orElse(null);
} }
public static @NotNull String pad(final @NotNull Editor editor, int line, final int to) { public static @NotNull String pad(final @NotNull Editor editor,
@NotNull DataContext context,
int line,
final int to) {
final int len = EngineEditorHelperKt.lineLength(new IjVimEditor(editor), line); final int len = EngineEditorHelperKt.lineLength(new IjVimEditor(editor), line);
if (len >= to) return ""; if (len >= to) return "";
final int limit = to - len; final int limit = to - len;
return IndentConfig.create(editor).createIndentBySize(limit); return IndentConfig.create(editor, context).createIndentBySize(limit);
} }
/** /**

View File

@ -35,6 +35,7 @@ import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.NativeAction import com.maddyhome.idea.vim.api.NativeAction
import com.maddyhome.idea.vim.api.VimActionExecutor import com.maddyhome.idea.vim.api.VimActionExecutor
import com.maddyhome.idea.vim.api.VimEditor 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.command.OperatorArguments
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
import com.maddyhome.idea.vim.newapi.IjNativeAction import com.maddyhome.idea.vim.newapi.IjNativeAction
@ -78,7 +79,6 @@ internal class IjActionExecutor : VimActionExecutor {
val dataContext = DataContextWrapper(context.ij) val dataContext = DataContextWrapper(context.ij)
dataContext.putUserData(runFromVimKey, true) dataContext.putUserData(runFromVimKey, true)
val actionId = ActionManager.getInstance().getId(ijAction)
val event = AnActionEvent( val event = AnActionEvent(
null, null,
dataContext, dataContext,
@ -92,15 +92,8 @@ internal class IjActionExecutor : VimActionExecutor {
// because rider use async update method. See VIM-1819. // because rider use async update method. See VIM-1819.
// This method executes inside of lastUpdateAndCheckDumb // This method executes inside of lastUpdateAndCheckDumb
// Another related issue: VIM-2604 // Another related issue: VIM-2604
// 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) ijAction.beforeActionPerformedUpdate(event)
if (!event.presentation.isEnabled) return false if (!event.presentation.isEnabled) return false
} else {
if (!ActionUtil.lastUpdateAndCheckDumb(ijAction, event, false)) return false
}
if (ijAction is ActionGroup && !event.presentation.isPerformGroup) { if (ijAction is ActionGroup && !event.presentation.isPerformGroup) {
// Some ActionGroups should not be performed, but shown as a popup // Some ActionGroups should not be performed, but shown as a popup
val popup = JBPopupFactory.getInstance() val popup = JBPopupFactory.getInstance()
@ -224,7 +217,7 @@ internal class IjActionExecutor : VimActionExecutor {
CommandProcessor.getInstance() CommandProcessor.getInstance()
.executeCommand( .executeCommand(
editor.ij.project, editor.ij.project,
{ cmd.execute(editor, context, operatorArguments) }, { cmd.execute(editor, injector.executionContextManager.onEditor(editor, context), operatorArguments) },
cmd.id, cmd.id,
DocCommandGroupId.noneGroupId(editor.ij.document), DocCommandGroupId.noneGroupId(editor.ij.document),
UndoConfirmationPolicy.DEFAULT, UndoConfirmationPolicy.DEFAULT,

View File

@ -14,6 +14,7 @@ import com.intellij.openapi.editor.VisualPosition
import com.intellij.openapi.editor.actionSystem.EditorActionManager import com.intellij.openapi.editor.actionSystem.EditorActionManager
import com.intellij.openapi.editor.ex.util.EditorUtil import com.intellij.openapi.editor.ex.util.EditorUtil
import com.maddyhome.idea.vim.api.EngineEditorHelper 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.VimEditor
import com.maddyhome.idea.vim.api.VimVisualPosition import com.maddyhome.idea.vim.api.VimVisualPosition
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
@ -50,8 +51,8 @@ internal class IjEditorHelper : EngineEditorHelper {
return EditorHelper.getVisualLineAtBottomOfScreen(editor.ij) return EditorHelper.getVisualLineAtBottomOfScreen(editor.ij)
} }
override fun pad(editor: VimEditor, line: Int, to: Int): String { override fun pad(editor: VimEditor, context: ExecutionContext, line: Int, to: Int): String {
return EditorHelper.pad(editor.ij, line, to) return EditorHelper.pad(editor.ij, context.ij, line, to)
} }
override fun inlayAwareOffsetToVisualPosition(editor: VimEditor, offset: Int): VimVisualPosition { override fun inlayAwareOffsetToVisualPosition(editor: VimEditor, offset: Int): VimVisualPosition {

View File

@ -632,6 +632,113 @@ public class SearchHelper {
return new TextRange(bstart, bend + 1); 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, private static int findBlockLocation(@NotNull CharSequence chars,
char found, char found,
char match, char match,

View File

@ -10,13 +10,10 @@ package com.maddyhome.idea.vim.helper
import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.PlatformDataKeys import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.application.ApplicationInfo
import com.intellij.openapi.command.CommandProcessor import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.undo.UndoManager import com.intellij.openapi.command.undo.UndoManager
import com.intellij.openapi.components.Service import com.intellij.openapi.components.Service
import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider 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.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
@ -43,25 +40,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
val scrollingModel = editor.getScrollingModel() val scrollingModel = editor.getScrollingModel()
scrollingModel.accumulateViewportChanges() scrollingModel.accumulateViewportChanges()
// [VERSION UPDATE] 241+ remove this if if (injector.globalIjOptions().oldundo) {
if (ApplicationInfo.getInstance().build.baselineVersion >= 241) {
undoFor241plus(editor, undoManager, fileEditor)
} else {
undoForLessThan241(undoManager, fileEditor, editor)
}
scrollingModel.flushViewportChanges()
return true
}
return false
}
private fun undoForLessThan241(
undoManager: UndoManager,
fileEditor: TextEditor,
editor: VimEditor,
) {if (injector.globalIjOptions().oldundo) {
SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) } SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
restoreVisualMode(editor) restoreVisualMode(editor)
} else { } else {
@ -79,37 +58,12 @@ internal class UndoRedoHelper : UndoRedoBase() {
removeSelections(editor) removeSelections(editor)
} }
} }
}
scrollingModel.flushViewportChanges()
private fun undoFor241plus( return true
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)
}
} }
return false
} }
private fun hasSelection(editor: VimEditor): Boolean { private fun hasSelection(editor: VimEditor): Boolean {
@ -122,23 +76,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
val fileEditor = TextEditorProvider.getInstance().getTextEditor(editor.ij) val fileEditor = TextEditorProvider.getInstance().getTextEditor(editor.ij)
val undoManager = UndoManager.getInstance(project) val undoManager = UndoManager.getInstance(project)
if (undoManager.isRedoAvailable(fileEditor)) { if (undoManager.isRedoAvailable(fileEditor)) {
// [VERSION UPDATE] 241+ remove this if if (injector.globalIjOptions().oldundo) {
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) } SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
restoreVisualMode(editor) restoreVisualMode(editor)
} else { } else {
@ -160,40 +98,9 @@ internal class UndoRedoHelper : UndoRedoBase() {
removeSelections(editor) removeSelections(editor)
} }
} }
return true
} }
return false
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)
}
}
} }
private fun removeSelections(editor: VimEditor) { private fun removeSelections(editor: VimEditor) {
@ -207,17 +114,6 @@ 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) { private fun VimEditor.runWithChangeTracking(block: ChangeTracker.() -> Unit) {
val tracker = ChangeTracker(this) val tracker = ChangeTracker(this)
tracker.block() tracker.block()

View File

@ -19,7 +19,6 @@ import com.intellij.codeInsight.template.TemplateManagerListener
import com.intellij.codeInsight.template.impl.TemplateState import com.intellij.codeInsight.template.impl.TemplateState
import com.intellij.find.FindModelListener import com.intellij.find.FindModelListener
import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.AnActionResult import com.intellij.openapi.actionSystem.AnActionResult
@ -182,7 +181,7 @@ internal object IdeaSpecifics {
if (editor.vim.inNormalMode) { if (editor.vim.inNormalMode) {
VimPlugin.getChange().insertBeforeCursor( VimPlugin.getChange().insertBeforeCursor(
editor.vim, editor.vim,
injector.executionContextManager.getEditorExecutionContext(editor.vim), injector.executionContextManager.onEditor(editor.vim),
) )
KeyHandler.getInstance().reset(editor.vim) KeyHandler.getInstance().reset(editor.vim)
} }
@ -232,7 +231,5 @@ internal class FindActionIdAction : DumbAwareToggleAction() {
override fun setSelected(e: AnActionEvent, state: Boolean) { override fun setSelected(e: AnActionEvent, state: Boolean) {
injector.globalIjOptions().trackactionids = !injector.globalIjOptions().trackactionids injector.globalIjOptions().trackactionids = !injector.globalIjOptions().trackactionids
} }
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
} }
//endregion //endregion

View File

@ -35,7 +35,6 @@ import com.intellij.openapi.editor.ex.DocumentEx
import com.intellij.openapi.editor.ex.EditorEventMulticasterEx import com.intellij.openapi.editor.ex.EditorEventMulticasterEx
import com.intellij.openapi.editor.ex.FocusChangeListener import com.intellij.openapi.editor.ex.FocusChangeListener
import com.intellij.openapi.editor.impl.EditorComponentImpl 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.FileEditorManager
import com.intellij.openapi.fileEditor.FileEditorManagerEvent import com.intellij.openapi.fileEditor.FileEditorManagerEvent
import com.intellij.openapi.fileEditor.FileEditorManagerListener import com.intellij.openapi.fileEditor.FileEditorManagerListener
@ -46,14 +45,11 @@ import com.intellij.openapi.fileEditor.ex.FileEditorWithProvider
import com.intellij.openapi.fileEditor.impl.EditorComposite import com.intellij.openapi.fileEditor.impl.EditorComposite
import com.intellij.openapi.fileEditor.impl.EditorWindow import com.intellij.openapi.fileEditor.impl.EditorWindow
import com.intellij.openapi.project.ProjectManager 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.Disposer
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.openapi.util.removeUserData import com.intellij.openapi.util.removeUserData
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.ExceptionUtil import com.intellij.util.ExceptionUtil
import com.jetbrains.rd.util.lifetime.Lifetime
import com.maddyhome.idea.vim.EventFacade import com.maddyhome.idea.vim.EventFacade
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.KeyHandlerStateResetter import com.maddyhome.idea.vim.KeyHandlerStateResetter
@ -105,6 +101,7 @@ 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.macro.macroWidgetOptionListener
import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetListener import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetListener
import com.maddyhome.idea.vim.ui.widgets.mode.modeWidgetOptionListener import com.maddyhome.idea.vim.ui.widgets.mode.modeWidgetOptionListener
import com.maddyhome.idea.vim.vimDisposable
import java.awt.event.MouseAdapter import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent import java.awt.event.MouseEvent
import javax.swing.SwingUtilities import javax.swing.SwingUtilities
@ -267,10 +264,12 @@ internal object VimListenerManager {
// TODO: If the user changes the 'ideavimsupport' option, existing editors won't be initialised // TODO: If the user changes the 'ideavimsupport' option, existing editors won't be initialised
if (vimDisabled(editor)) return if (vimDisabled(editor)) return
val pluginLifetime = VimPlugin.getInstance().createLifetime() // As I understand, there is no need to pass a disposable that also disposes on editor close
val editorLifetime = (editor as EditorImpl).disposable.createLifetime() // because all editor resources will be garbage collected anyway on editor close
val disposable = // Note that this uses the plugin's main disposable, rather than VimPlugin.onOffDisposable, because we don't need
Lifetime.intersect(pluginLifetime, editorLifetime).createNestedDisposable("MyLifetimedDisposable") // to - we explicitly call VimListenerManager.removeAll from VimPlugin.turnOffPlugin, and this disposes each
// editor's disposable individually.
val disposable = editor.project?.vimDisposable ?: return
val listenersDisposable = Disposer.newDisposable(disposable) val listenersDisposable = Disposer.newDisposable(disposable)
editor.putUserData(editorListenersDisposableKey, listenersDisposable) editor.putUserData(editorListenersDisposableKey, listenersDisposable)
@ -539,15 +538,15 @@ internal object VimListenerManager {
// When starting on an empty line and dragging vertically upwards onto // When starting on an empty line and dragging vertically upwards onto
// another line, the selection should include the entirety of the empty line // another line, the selection should include the entirety of the empty line
caret.setSelection( caret.setSelection(
ijVimEditor.coerceOffset(endOffset + 1), ijVimEditor.coerceOffset(endOffset + 1).point,
ijVimEditor.coerceOffset(startOffset), ijVimEditor.coerceOffset(startOffset).point,
) )
} else if (lineEnd == startOffset + 1 && startOffset == endOffset) { } else if (lineEnd == startOffset + 1 && startOffset == endOffset) {
// When dragging left from EOL on a non-empty line, the selection // When dragging left from EOL on a non-empty line, the selection
// should include the last character on the line // should include the last character on the line
caret.setSelection( caret.setSelection(
ijVimEditor.coerceOffset(lineEnd), ijVimEditor.coerceOffset(lineEnd).point,
ijVimEditor.coerceOffset(lineEnd - 1), ijVimEditor.coerceOffset(lineEnd - 1).point,
) )
} }
} }

View File

@ -12,8 +12,16 @@ import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.openapi.util.UserDataHolder import com.intellij.openapi.util.UserDataHolder
import com.maddyhome.idea.vim.api.ExecutionContext 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 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
// This key is stored in data context when the action is started from vim // This key is stored in data context when the action is started from vim
internal val runFromVimKey = Key.create<Boolean>("RunFromVim") internal val runFromVimKey = Key.create<Boolean>("RunFromVim")

View File

@ -9,15 +9,23 @@
package com.maddyhome.idea.vim.newapi package com.maddyhome.idea.vim.newapi
import com.intellij.openapi.components.Service import com.intellij.openapi.components.Service
import com.intellij.openapi.editor.ex.util.EditorUtil import com.intellij.openapi.editor.actionSystem.CaretSpecificDataContext
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ExecutionContextManagerBase 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.api.VimEditor
import com.maddyhome.idea.vim.helper.EditorDataContext import com.maddyhome.idea.vim.helper.EditorDataContext
@Service @Service
internal class IjExecutionContextManager : ExecutionContextManagerBase() { internal class IjExecutionContextManager : ExecutionContextManagerBase() {
override fun getEditorExecutionContext(editor: VimEditor): ExecutionContext { override fun onEditor(editor: VimEditor, prevContext: ExecutionContext?): ExecutionContext.Editor {
return EditorUtil.getEditorDataContext(editor.ij).vim 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))
} }
} }

View File

@ -10,10 +10,12 @@ package com.maddyhome.idea.vim.newapi
import com.intellij.openapi.editor.RangeMarker import com.intellij.openapi.editor.RangeMarker
import com.maddyhome.idea.vim.common.LiveRange 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 { internal class IjLiveRange(val marker: RangeMarker) : LiveRange {
override val startOffset: Int override val startOffset: Offset
get() = marker.startOffset get() = marker.startOffset.offset
} }
public val RangeMarker.vim: LiveRange public val RangeMarker.vim: LiveRange

View File

@ -34,8 +34,4 @@ internal class IjNativeActionManager : NativeActionManager {
public val AnAction.vim: IjNativeAction public val AnAction.vim: IjNativeAction
get() = IjNativeAction(this) 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)"
}
}

View File

@ -21,7 +21,10 @@ import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimCaretBase import com.maddyhome.idea.vim.api.VimCaretBase
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimVisualPosition 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.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.group.visual.VisualChange
import com.maddyhome.idea.vim.helper.lastSelectionInfo import com.maddyhome.idea.vim.helper.lastSelectionInfo
import com.maddyhome.idea.vim.helper.markStorage import com.maddyhome.idea.vim.helper.markStorage
@ -75,8 +78,8 @@ internal class IjVimCaret(val caret: Caret) : VimCaretBase() {
} }
override val editor: VimEditor override val editor: VimEditor
get() = IjVimEditor(caret.editor) get() = IjVimEditor(caret.editor)
override val offset: Int override val offset: Offset
get() = caret.offset get() = caret.offset.offset
override var vimLastColumn: Int override var vimLastColumn: Int
get() = caret.vimLastColumn get() = caret.vimLastColumn
set(value) { set(value) {
@ -115,8 +118,8 @@ internal class IjVimCaret(val caret: Caret) : VimCaretBase() {
this.caret.moveToLogicalPosition(LogicalPosition(position.line, position.column, position.leansForward)) this.caret.moveToLogicalPosition(LogicalPosition(position.line, position.column, position.leansForward))
} }
override fun getLine(): Int { override fun getLine(): EditorLine.Pointer {
return caret.logicalPosition.line return EditorLine.Pointer.init(caret.logicalPosition.line, editor)
} }
override fun hasSelection(): Boolean { override fun hasSelection(): Boolean {
@ -161,8 +164,8 @@ internal class IjVimCaret(val caret: Caret) : VimCaretBase() {
return this return this
} }
override fun setSelection(start: Int, end: Int) { override fun setSelection(start: Offset, end: Offset) {
caret.setSelection(start, end) caret.setSelection(start.point, end.point)
} }
override fun removeSelection() { override fun removeSelection() {

View File

@ -14,6 +14,7 @@ import com.intellij.openapi.editor.event.DocumentListener
import com.maddyhome.idea.vim.api.VimDocument import com.maddyhome.idea.vim.api.VimDocument
import com.maddyhome.idea.vim.common.ChangesListener import com.maddyhome.idea.vim.common.ChangesListener
import com.maddyhome.idea.vim.common.LiveRange import com.maddyhome.idea.vim.common.LiveRange
import com.maddyhome.idea.vim.common.Offset
internal class IjVimDocument(val document: Document) : VimDocument { internal class IjVimDocument(val document: Document) : VimDocument {
@ -40,7 +41,7 @@ internal class IjVimDocument(val document: Document) : VimDocument {
document.removeDocumentListener(nativeListener) document.removeDocumentListener(nativeListener)
} }
override fun getOffsetGuard(offset: Int): LiveRange? { override fun getOffsetGuard(offset: Offset): LiveRange? {
return document.getOffsetGuard(offset)?.vim return document.getOffsetGuard(offset.point)?.vim
} }
} }

View File

@ -35,11 +35,13 @@ import com.maddyhome.idea.vim.api.VimScrollingModel
import com.maddyhome.idea.vim.api.VimSelectionModel import com.maddyhome.idea.vim.api.VimSelectionModel
import com.maddyhome.idea.vim.api.VimVisualPosition import com.maddyhome.idea.vim.api.VimVisualPosition
import com.maddyhome.idea.vim.api.VirtualFile 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.command.OperatorArguments
import com.maddyhome.idea.vim.common.EditorLine
import com.maddyhome.idea.vim.common.IndentConfig import com.maddyhome.idea.vim.common.IndentConfig
import com.maddyhome.idea.vim.common.LiveRange 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.TextRange
import com.maddyhome.idea.vim.common.offset
import com.maddyhome.idea.vim.group.visual.vimSetSystemBlockSelectionSilently import com.maddyhome.idea.vim.group.visual.vimSetSystemBlockSelectionSilently
import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.StrictMode import com.maddyhome.idea.vim.helper.StrictMode
@ -88,18 +90,18 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
return editor.document.lineCount return editor.document.lineCount
} }
override fun deleteRange(leftOffset: Int, rightOffset: Int) { override fun deleteRange(leftOffset: Offset, rightOffset: Offset) {
editor.document.deleteString(leftOffset, rightOffset) editor.document.deleteString(leftOffset.point, rightOffset.point)
} }
override fun addLine(atPosition: Int): Int { override fun addLine(atPosition: EditorLine.Offset): EditorLine.Pointer {
val offset: Int = if (atPosition < lineCount()) { val offset: Int = if (atPosition.line < lineCount()) {
// The new line character is inserted before the new line char of the previous line. So it works line an enter // 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 // 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. // \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 // https://youtrack.jetbrains.com/issue/IDEA-286587
val lineStart = (editor.document.getLineStartOffset(atPosition) - 1).coerceAtLeast(0) val lineStart = (editor.document.getLineStartOffset(atPosition.line) - 1).coerceAtLeast(0)
val guard = editor.document.getOffsetGuard(lineStart) val guard = editor.document.getOffsetGuard(lineStart)
if (guard != null && guard.endOffset == lineStart + 1) { if (guard != null && guard.endOffset == lineStart + 1) {
// Dancing around guarded blocks. It may happen that this concrete position is locked, but the next // Dancing around guarded blocks. It may happen that this concrete position is locked, but the next
@ -114,11 +116,11 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
fileSize().toInt() fileSize().toInt()
} }
editor.document.insertString(offset, "\n") editor.document.insertString(offset, "\n")
return atPosition return EditorLine.Pointer.init(atPosition.line, this)
} }
override fun insertText(atPosition: Int, text: CharSequence) { override fun insertText(atPosition: Offset, text: CharSequence) {
editor.document.insertString(atPosition, text) editor.document.insertString(atPosition.point, text)
} }
override fun replaceString(start: Int, end: Int, newString: String) { override fun replaceString(start: Int, end: Int, newString: String) {
@ -126,13 +128,13 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
} }
// TODO: 30.12.2021 Is end offset inclusive? // TODO: 30.12.2021 Is end offset inclusive?
override fun getLineRange(line: Int): Pair<Int, Int> { override fun getLineRange(line: EditorLine.Pointer): Pair<Offset, Offset> {
// TODO: 30.12.2021 getLineEndOffset returns the same value for "xyz" and "xyz\n" // TODO: 30.12.2021 getLineEndOffset returns the same value for "xyz" and "xyz\n"
return editor.document.getLineStartOffset(line) to editor.document.getLineEndOffset(line) return editor.document.getLineStartOffset(line.line).offset to editor.document.getLineEndOffset(line.line).offset
} }
override fun getLine(offset: Int): Int { override fun getLine(offset: Offset): EditorLine.Pointer {
return editor.offsetToLogicalPosition(offset).line return EditorLine.Pointer.init(editor.offsetToLogicalPosition(offset.point).line, this)
} }
override fun carets(): List<VimCaret> { override fun carets(): List<VimCaret> {
@ -201,15 +203,15 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
return editor.isOneLineMode return editor.isOneLineMode
} }
override fun getText(left: Int, right: Int): CharSequence { override fun getText(left: Offset, right: Offset): CharSequence {
return editor.document.charsSequence.subSequence(left, right) return editor.document.charsSequence.subSequence(left.point, right.point)
} }
override fun search( override fun search(
pair: Pair<Int, Int>, pair: Pair<Offset, Offset>,
editor: VimEditor, editor: VimEditor,
shiftType: LineDeleteShift, shiftType: LineDeleteShift,
): Pair<Pair<Int, Int>, LineDeleteShift>? { ): Pair<Pair<Offset, Offset>, LineDeleteShift>? {
val ijEditor = (editor as IjVimEditor).editor val ijEditor = (editor as IjVimEditor).editor
return when (shiftType) { return when (shiftType) {
LineDeleteShift.NO_NL -> if (pair.noGuard(ijEditor)) return pair to shiftType else null LineDeleteShift.NO_NL -> if (pair.noGuard(ijEditor)) return pair to shiftType else null
@ -356,10 +358,10 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
return EditorHelper.getVirtualFile(editor)?.getUrl()?.let { VirtualFileManager.extractProtocol(it) } return EditorHelper.getVirtualFile(editor)?.getUrl()?.let { VirtualFileManager.extractProtocol(it) }
} }
override val projectId = editor.project?.let { injector.file.getProjectId(it) } ?: DEFAULT_PROJECT_ID override val projectId = editor.project?.basePath ?: DEFAULT_PROJECT_ID
override fun visualPositionToOffset(position: VimVisualPosition): Int { override fun visualPositionToOffset(position: VimVisualPosition): Offset {
return editor.visualPositionToOffset(VisualPosition(position.line, position.column, position.leansRight)) return editor.visualPositionToOffset(VisualPosition(position.line, position.column, position.leansRight)).offset
} }
override fun exitInsertMode(context: ExecutionContext, operatorArguments: OperatorArguments) { override fun exitInsertMode(context: ExecutionContext, operatorArguments: OperatorArguments) {
@ -415,8 +417,8 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
return visualPosition.run { VimVisualPosition(line, column, leansRight) } return visualPosition.run { VimVisualPosition(line, column, leansRight) }
} }
override fun createLiveMarker(start: Int, end: Int): LiveRange { override fun createLiveMarker(start: Offset, end: Offset): LiveRange {
return editor.document.createRangeMarker(start, end).vim return editor.document.createRangeMarker(start.point, end.point).vim
} }
/** /**
@ -454,10 +456,10 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
ijFoldRegion.isExpanded = value ijFoldRegion.isExpanded = value
} }
} }
override val startOffset: Int override val startOffset: Offset
get() = ijFoldRegion.startOffset get() = Offset(ijFoldRegion.startOffset)
override val endOffset: Int override val endOffset: Offset
get() = ijFoldRegion.endOffset get() = Offset(ijFoldRegion.endOffset)
} }
} }
@ -466,17 +468,17 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
return caret return caret
} }
private fun Pair<Int, Int>.noGuard(editor: Editor): Boolean { private fun Pair<Offset, Offset>.noGuard(editor: Editor): Boolean {
return editor.document.getRangeGuard(this.first, this.second) == null return editor.document.getRangeGuard(this.first.point, this.second.point) == null
} }
private inline fun Pair<Int, Int>.shift( private inline fun Pair<Offset, Offset>.shift(
shiftStart: Int = 0, shiftStart: Int = 0,
shiftEnd: Int = 0, shiftEnd: Int = 0,
action: Pair<Int, Int>.() -> Unit, action: Pair<Offset, Offset>.() -> Unit,
) { ) {
val data = val data =
(this.first + shiftStart).coerceAtLeast(0) to (this.second + shiftEnd).coerceAtLeast(0) (this.first.point + shiftStart).coerceAtLeast(0).offset to (this.second.point + shiftEnd).coerceAtLeast(0).offset
data.action() data.action()
} }
@ -498,6 +500,3 @@ public val Editor.vim: VimEditor
get() = IjVimEditor(this) get() = IjVimEditor(this)
public val VimEditor.ij: Editor public val VimEditor.ij: Editor
get() = (this as IjVimEditor).editor get() = (this as IjVimEditor).editor
public val com.intellij.openapi.util.TextRange.vim: TextRange
get() = TextRange(this.startOffset, this.endOffset)

View File

@ -42,7 +42,6 @@ import com.maddyhome.idea.vim.api.VimMessages
import com.maddyhome.idea.vim.api.VimMotionGroup import com.maddyhome.idea.vim.api.VimMotionGroup
import com.maddyhome.idea.vim.api.VimOptionGroup import com.maddyhome.idea.vim.api.VimOptionGroup
import com.maddyhome.idea.vim.api.VimProcessGroup 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.VimRegexpService
import com.maddyhome.idea.vim.api.VimScrollGroup import com.maddyhome.idea.vim.api.VimScrollGroup
import com.maddyhome.idea.vim.api.VimSearchGroup import com.maddyhome.idea.vim.api.VimSearchGroup
@ -66,7 +65,6 @@ import com.maddyhome.idea.vim.group.FileGroup
import com.maddyhome.idea.vim.group.GlobalIjOptions import com.maddyhome.idea.vim.group.GlobalIjOptions
import com.maddyhome.idea.vim.group.HistoryGroup import com.maddyhome.idea.vim.group.HistoryGroup
import com.maddyhome.idea.vim.group.IjVimOptionGroup 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.MacroGroup
import com.maddyhome.idea.vim.group.MotionGroup import com.maddyhome.idea.vim.group.MotionGroup
import com.maddyhome.idea.vim.group.SearchGroup import com.maddyhome.idea.vim.group.SearchGroup
@ -149,8 +147,6 @@ internal class IjVimInjector : VimInjectorBase() {
get() = service<MacroGroup>() get() = service<MacroGroup>()
override val undo: VimUndoRedo override val undo: VimUndoRedo
get() = service<UndoRedoHelper>() get() = service<UndoRedoHelper>()
override val psiService: VimPsiService
get() = service<IjVimPsiService>()
override val commandLineHelper: VimCommandLineHelper override val commandLineHelper: VimCommandLineHelper
get() = service<CommandLineHelper>() get() = service<CommandLineHelper>()
override val nativeActionManager: NativeActionManager override val nativeActionManager: NativeActionManager

View File

@ -11,7 +11,6 @@ package com.maddyhome.idea.vim.newapi
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.util.Ref import com.intellij.openapi.util.Ref
import com.maddyhome.idea.vim.VimPlugin 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.Options
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
@ -27,7 +26,9 @@ import com.maddyhome.idea.vim.helper.shouldIgnoreCase
import com.maddyhome.idea.vim.helper.updateSearchHighlights import com.maddyhome.idea.vim.helper.updateSearchHighlights
import com.maddyhome.idea.vim.options.GlobalOptionChangeListener import com.maddyhome.idea.vim.options.GlobalOptionChangeListener
import com.maddyhome.idea.vim.ui.ModalEntry 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.model.functions.handlers.SubmatchFunctionHandler
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser.parseExpression
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import javax.swing.KeyStroke import javax.swing.KeyStroke
@ -81,7 +82,6 @@ public abstract class IjVimSearchGroup : VimSearchGroupBase() {
override fun confirmChoice( override fun confirmChoice(
editor: VimEditor, editor: VimEditor,
context: ExecutionContext,
match: String, match: String,
caret: VimCaret, caret: VimCaret,
startOffset: Int, startOffset: Int,
@ -121,6 +121,7 @@ public abstract class IjVimSearchGroup : VimSearchGroupBase() {
// XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for this method // 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 = val exEntryPanel: com.maddyhome.idea.vim.ui.ex.ExEntryPanel =
com.maddyhome.idea.vim.ui.ex.ExEntryPanel.getInstanceWithoutShortcuts() com.maddyhome.idea.vim.ui.ex.ExEntryPanel.getInstanceWithoutShortcuts()
val context = injector.executionContextManager.onEditor(editor, null)
exEntryPanel.activate( exEntryPanel.activate(
editor.ij, editor.ij,
(context as IjEditorExecutionContext).context, (context as IjEditorExecutionContext).context,
@ -135,6 +136,10 @@ public abstract class IjVimSearchGroup : VimSearchGroupBase() {
return result.get() return result.get()
} }
override fun parseVimScriptExpression(expressionString: String): Expression? {
return parseExpression(expressionString)
}
override fun addSubstitutionConfirmationHighlight(editor: VimEditor, startOffset: Int, endOffset: Int) { override fun addSubstitutionConfirmationHighlight(editor: VimEditor, startOffset: Int, endOffset: Int) {
val hl = addSubstitutionConfirmationHighlight( val hl = addSubstitutionConfirmationHighlight(
(editor as IjVimEditor).editor, (editor as IjVimEditor).editor,

View File

@ -13,31 +13,144 @@ import com.intellij.openapi.diagnostic.Logger
import com.maddyhome.idea.vim.api.ImmutableVimCaret import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimSearchHelperBase 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.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.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.PsiHelper
import com.maddyhome.idea.vim.helper.SearchHelper import com.maddyhome.idea.vim.helper.SearchHelper
import com.maddyhome.idea.vim.helper.SearchOptions 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.IntComparator
import it.unimi.dsi.fastutil.ints.IntComparators import it.unimi.dsi.fastutil.ints.IntComparators
import java.util.* import java.util.*
import java.util.function.Function
import java.util.regex.Pattern
import kotlin.math.abs
import kotlin.math.max
@Service @Service
internal class IjVimSearchHelper : VimSearchHelperBase() { internal class IjVimSearchHelper : VimSearchHelperBase() {
companion object { companion object {
private const val BLOCK_CHARS = "{}()[]<>"
private val logger = Logger.getInstance(IjVimSearchHelper::class.java.name) 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 { override fun findMethodEnd(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Int {
// TODO add it to PsiService
return PsiHelper.findMethodEnd(editor.ij, caret.ij.offset, count) return PsiHelper.findMethodEnd(editor.ij, caret.ij.offset, count)
} }
override fun findMethodStart(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Int { override fun findMethodStart(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Int {
// TODO add it to PsiService
return PsiHelper.findMethodStart(editor.ij, caret.ij.offset, count) 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( override fun findPattern(
editor: VimEditor, editor: VimEditor,
pattern: String?, pattern: String?,
@ -60,6 +173,525 @@ internal class IjVimSearchHelper : VimSearchHelperBase() {
else SearchHelper.findAll(editor.ij, pattern, startLine, endLine, ignoreCase) 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 { override fun findMisspelledWord(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Int {
val startOffset: Int val startOffset: Int
val endOffset: Int val endOffset: Int
@ -68,18 +700,17 @@ internal class IjVimSearchHelper : VimSearchHelperBase() {
if (count < 0) { if (count < 0) {
startOffset = 0 startOffset = 0
endOffset = caret.offset - 1 endOffset = caret.offset.point - 1
skipCount = -count - 1 skipCount = -count - 1
offsetOrdering = IntComparators.OPPOSITE_COMPARATOR offsetOrdering = IntComparators.OPPOSITE_COMPARATOR
} }
else { else {
startOffset = caret.offset + 1 startOffset = caret.offset.point + 1
endOffset = editor.ij.document.textLength endOffset = editor.ij.document.textLength
skipCount = count - 1 skipCount = count - 1
offsetOrdering = IntComparators.NATURAL_COMPARATOR offsetOrdering = IntComparators.NATURAL_COMPARATOR
} }
// TODO add it to PsiService
return SearchHelper.findMisspelledWords(editor.ij, startOffset, endOffset, skipCount, offsetOrdering) return SearchHelper.findMisspelledWords(editor.ij, startOffset, endOffset, skipCount, offsetOrdering)
} }
} }

View File

@ -307,7 +307,7 @@ public class ExOutputPanel extends JPanel {
KeyHandler.getInstance().getKeyStack().dump()); KeyHandler.getInstance().getKeyStack().dump());
} }
KeyHandler.getInstance().getKeyStack().addKeys(keys); KeyHandler.getInstance().getKeyStack().addKeys(keys);
ExecutionContext context = injector.getExecutionContextManager().getEditorExecutionContext(new IjVimEditor(myEditor)); ExecutionContext.Editor context = injector.getExecutionContextManager().onEditor(new IjVimEditor(myEditor), null);
VimPlugin.getMacro().playbackKeys(new IjVimEditor(myEditor), context, 1); VimPlugin.getMacro().playbackKeys(new IjVimEditor(myEditor), context, 1);
} }
}); });

View File

@ -10,6 +10,7 @@ package com.maddyhome.idea.vim.ui.ex
import com.intellij.openapi.diagnostic.debug import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
import java.awt.event.ActionEvent import java.awt.event.ActionEvent
@ -125,7 +126,12 @@ internal object ExEditorKit : DefaultEditorKit() {
val entry = ExEntryPanel.getInstance().entry val entry = ExEntryPanel.getInstance().entry
val editor = entry.editor val editor = entry.editor
val keyHandler = KeyHandler.getInstance() val keyHandler = KeyHandler.getInstance()
keyHandler.handleKey(editor.vim, key, entry.context.vim, keyHandler.keyHandlerState) keyHandler.handleKey(
editor.vim,
key,
injector.executionContextManager.onEditor(editor.vim, entry.context.vim),
keyHandler.keyHandlerState,
)
} else { } else {
val event = ActionEvent(e.source, e.id, c.toString(), e.getWhen(), e.modifiers) val event = ActionEvent(e.source, e.id, c.toString(), e.getWhen(), e.modifiers)
super.actionPerformed(event) super.actionPerformed(event)

View File

@ -13,6 +13,7 @@ import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.KeyboardShortcut import com.intellij.openapi.actionSystem.KeyboardShortcut
import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.DumbAwareAction
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import javax.swing.KeyStroke import javax.swing.KeyStroke
@ -36,12 +37,12 @@ internal class ExShortcutKeyAction(private val exEntryPanel: ExEntryPanel) : Dum
if (keyStroke != null) { if (keyStroke != null) {
val editor = exEntryPanel.entry.editor val editor = exEntryPanel.entry.editor
val keyHandler = KeyHandler.getInstance() val keyHandler = KeyHandler.getInstance()
keyHandler.handleKey(
// About the context: we use the context of the main editor to execute actions on it. editor.vim,
// e.dataContext will refer to the ex-entry editor and commands will be executed on it, keyStroke,
// thus it should not be used. For example, `:action EditorSelectWord` will not work with this context injector.executionContextManager.onEditor(editor.vim, e.dataContext.vim),
val mainEditorContext = exEntryPanel.entry.context.vim keyHandler.keyHandlerState
keyHandler.handleKey(editor.vim, keyStroke, mainEditorContext, keyHandler.keyHandlerState) )
} }
} }

View File

@ -74,7 +74,8 @@ internal class Executor : VimScriptExecutorBase() {
VimPlugin.indicateError() VimPlugin.indicateError()
} }
} catch (e: Exception) { } catch (e: Exception) {
logger.warn(e) logger.warn("Caught: ${e.message}")
logger.warn(e.stackTrace.toString())
if (injector.application.isUnitTest()) { if (injector.application.isUnitTest()) {
throw e throw e
} }

View File

@ -113,9 +113,9 @@ internal data class GlobalCommand(val ranges: Ranges, val argument: String, val
} }
if (globalBusy) { if (globalBusy) {
val match = regex.findInLine(editor, editor.currentCaret().getLine()) val match = regex.findInLine(editor, editor.currentCaret().getLine().line)
if (match is VimMatchResult.Success == !invert) { if (match is VimMatchResult.Success == !invert) {
globalExecuteOne(editor, context, editor.getLineStartOffset(editor.currentCaret().getLine()), cmd.toString()) globalExecuteOne(editor, context, editor.getLineStartOffset(editor.currentCaret().getLine().line), cmd.toString())
} }
} else { } else {
val line1 = range.startLine val line1 = range.startLine
@ -164,8 +164,8 @@ internal data class GlobalCommand(val ranges: Ranges, val argument: String, val
val searchcol = 0 val searchcol = 0
if (globalBusy) { if (globalBusy) {
val offset = editor.currentCaret().offset val offset = editor.currentCaret().offset
val lineStartOffset = editor.getLineStartForOffset(offset) val lineStartOffset = editor.getLineStartForOffset(offset.point)
match = sp.vim_regexec_multi(regmatch, editor, lcount, editor.currentCaret().getLine(), searchcol) match = sp.vim_regexec_multi(regmatch, editor, lcount, editor.currentCaret().getLine().line, searchcol)
if ((!invert && match > 0) || (invert && match <= 0)) { if ((!invert && match > 0) || (invert && match <= 0)) {
globalExecuteOne(editor, context, lineStartOffset, cmd.toString()) globalExecuteOne(editor, context, lineStartOffset, cmd.toString())
} }

View File

@ -136,7 +136,11 @@
<!-- IdeaVim extensions--> <!-- IdeaVim extensions-->
<extensions defaultExtensionNs="com.intellij"> <extensions defaultExtensionNs="com.intellij">
<applicationService serviceImplementation="com.maddyhome.idea.vim.extension.nerdtree.NerdTree$NerdDispatcher"/> <projectService serviceImplementation="com.maddyhome.idea.vim.extension.nerdtree.NerdTree$NerdDispatcher"/>
<applicationInitializedListener implementation="com.maddyhome.idea.vim.extension.nerdtree.NerdTreeApplicationListener"/> <postStartupActivity implementation="com.maddyhome.idea.vim.extension.nerdtree.NerdTree$NerdStartupActivity"/>
</extensions> </extensions>
<projectListeners>
<listener class="com.maddyhome.idea.vim.extension.nerdtree.NerdTree$ProjectViewListener"
topic="com.intellij.openapi.wm.ex.ToolWindowManagerListener"/>
</projectListeners>
</idea-plugin> </idea-plugin>

View File

@ -15,7 +15,6 @@ import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimBehaviorDiffers
import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@ -382,14 +381,10 @@ class MotionActionTest : VimTestCase() {
// VIM-1287 |d| |v_i{| // VIM-1287 |d| |v_i{|
@Test @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() { fun testBadlyNestedBlockInsideString() {
val before = "{\"{foo, ${c}bar\", baz}}" val before = "{\"{foo, ${c}bar\", baz}}"
val keys = listOf("di{") val keys = listOf("di{")
val after = "{}}" val after = "{\"{foo, ${c}bar\", baz}}"
doTest(keys, before, after, Mode.NORMAL()) doTest(keys, before, after, Mode.NORMAL())
} }
@ -411,14 +406,6 @@ class MotionActionTest : VimTestCase() {
doTest(keys, before, after, Mode.INSERT) 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{| // VIM-1008 |c| |v_i{|
@Test @Test
fun testDeleteInsideSingleQuotesSurroundedBlock() { fun testDeleteInsideSingleQuotesSurroundedBlock() {

View File

@ -42,10 +42,10 @@ class YankMotionActionTest : VimTestCase() {
""".trimIndent() """.trimIndent()
configureByText(file) configureByText(file)
val initialOffset = fixture.editor.caretModel val initialOffset = fixture.editor.caretModel.offset
typeText("yy") typeText("yy")
kotlin.test.assertEquals(initialOffset, fixture.editor.caretModel) kotlin.test.assertEquals(initialOffset, fixture.editor.caretModel.offset)
} }
@Suppress("DANGEROUS_CHARACTERS") @Suppress("DANGEROUS_CHARACTERS")

View File

@ -98,6 +98,15 @@ class MotionUnmatchedBraceOpenActionTest : VimTestCase() {
) )
} }
@VimBehaviorDiffers(
originalVimAfter = """
class Xxx $c{
int main() {
}
}
""",
)
@Test @Test
fun `test go to next next bracket with great count`() { fun `test go to next next bracket with great count`() {
doTest( doTest(
@ -110,9 +119,9 @@ class MotionUnmatchedBraceOpenActionTest : VimTestCase() {
} }
""".trimIndent(), """.trimIndent(),
""" """
class Xxx $c{ class Xxx {
int main() { int main() {
$c
} }
} }
""".trimIndent(), """.trimIndent(),

View File

@ -8,12 +8,10 @@
package org.jetbrains.plugins.ideavim.action.motion.updown package org.jetbrains.plugins.ideavim.action.motion.updown
import com.intellij.idea.TestFor
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
/** /**
@ -145,7 +143,6 @@ class MotionPercentOrMatchActionTest : VimTestCase() {
} }
@Test @Test
@Disabled("It will work after implementing all of the methods in VimPsiService")
fun `test motion outside text`() { fun `test motion outside text`() {
doTest( doTest(
"%", "%",
@ -210,45 +207,41 @@ class MotionPercentOrMatchActionTest : VimTestCase() {
} }
@Test @Test
@TestWithoutNeovim(SkipNeovimReason.BUG_IN_NEOVIM)
fun `test motion in text with escape (outer forward)`() { fun `test motion in text with escape (outer forward)`() {
doTest( doTest(
"%", "%",
""" debugPrint$c(\(var)) """, """ debugPrint$c(\(var)) """,
""" debugPrint(\(var)$c) """,
Mode.NORMAL(),
)
}
@Test
fun `test motion in text with escape (outer backward)`() {
doTest(
"%",
""" debugPrint(\(var)$c) """,
""" debugPrint$c(\(var)) """,
Mode.NORMAL(),
)
}
@Test
fun `test motion in text with escape (inner forward)`() {
doTest(
"%",
""" debugPrint(\$c(var)) """,
""" debugPrint(\(var$c)) """, """ debugPrint(\(var$c)) """,
Mode.NORMAL(), Mode.NORMAL(),
) )
} }
@Test @Test
@TestWithoutNeovim(SkipNeovimReason.BUG_IN_NEOVIM)
fun `test motion in text with escape (outer backward)`() {
doTest(
"%",
""" debugPrint(\(var)$c) """,
""" 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(\$c(var)) """,
Mode.NORMAL(),
)
}
@Test
@TestWithoutNeovim(SkipNeovimReason.BUG_IN_NEOVIM)
fun `test motion in text with escape (inner backward)`() { fun `test motion in text with escape (inner backward)`() {
doTest( doTest(
"%", "%",
""" debugPrint(\$c(var)) """, """ debugPrint(\$c(var)) """,
""" debugPrint(\$c(var)) """, """ debugPrint(\(var$c)) """,
Mode.NORMAL(), Mode.NORMAL(),
) )
} }
@ -339,28 +332,4 @@ class MotionPercentOrMatchActionTest : VimTestCase() {
) )
assertOffset(10) 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())
}
} }

View File

@ -10,6 +10,7 @@ package org.jetbrains.plugins.ideavim.common.editor
import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.command.WriteCommandAction
import com.maddyhome.idea.vim.common.offset
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim import org.jetbrains.plugins.ideavim.TestWithoutNeovim
@ -24,7 +25,7 @@ class VimEditorTest : VimTestCase() {
val vimEditor = IjVimEditor(fixture.editor) val vimEditor = IjVimEditor(fixture.editor)
WriteCommandAction.runWriteCommandAction(fixture.project) { WriteCommandAction.runWriteCommandAction(fixture.project) {
runWriteAction { runWriteAction {
vimEditor.deleteRange(0, 5) vimEditor.deleteRange(0.offset, 5.offset)
} }
} }
assertState("567890") assertState("567890")

View File

@ -8,7 +8,6 @@
package org.jetbrains.plugins.ideavim.extension.entiretextobj package org.jetbrains.plugins.ideavim.extension.entiretextobj
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim import org.jetbrains.plugins.ideavim.TestWithoutNeovim
@ -58,14 +57,6 @@ class VimTextObjEntireExtensionTest : VimTestCase() {
@Test @Test
fun testYankEntireBuffer() { fun testYankEntireBuffer() {
doTest("yae", poem, "<caret>$poemNoCaret") 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| // |gU| |ie|

View File

@ -203,36 +203,6 @@ class ReplaceWithRegisterTest : VimTestCase() {
assertEquals("one", VimPlugin.getRegister().lastRegister?.text) 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 -------------------------- // --------------------------------------- grr --------------------------
@Test @Test

View File

@ -322,7 +322,7 @@ class CaretVisualAttributesHelperTest : VimTestCase() {
) )
injector.actionExecutor.executeAction( injector.actionExecutor.executeAction(
"EditorCloneCaretBelow", "EditorCloneCaretBelow",
injector.executionContextManager.getEditorExecutionContext(fixture.editor.vim), injector.executionContextManager.onEditor(fixture.editor.vim),
) )
kotlin.test.assertEquals(2, fixture.editor.caretModel.caretCount) kotlin.test.assertEquals(2, fixture.editor.caretModel.caretCount)
assertCaretVisualAttributes(CaretVisualAttributes.Shape.BLOCK, 0f) assertCaretVisualAttributes(CaretVisualAttributes.Shape.BLOCK, 0f)

View File

@ -9,7 +9,6 @@ package org.jetbrains.plugins.ideavim.helper
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.TextRange 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.helper.checkInString
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
@ -247,7 +246,8 @@ class SearchHelperTest : VimTestCase() {
fun findBlockRange(testCase: FindBlockRangeTestCase) { fun findBlockRange(testCase: FindBlockRangeTestCase) {
val (_, text, type, count, isOuter, expected) = (testCase) val (_, text, type, count, isOuter, expected) = (testCase)
configureByText(text) configureByText(text)
val actual = findBlockRange(fixture.editor.vim, fixture.editor.vim.currentCaret(), type, count, isOuter) val actual =
injector.searchHelper.findBlockRange(fixture.editor.vim, fixture.editor.vim.currentCaret(), type, count, isOuter)
kotlin.test.assertEquals(expected, actual) 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("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("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("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 = TextRange(1, 6)), // Vim behavior differs, but we have some PSI magic and can resolve such cases 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 = TextRange(0, 7)), // 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 = null),
FindBlockRangeTestCase("inner match exclude end paren in string when caret at start of quote", "(${c}\"aa)\")", '(', 1, isOuter = false, expected = TextRange(1, 6)), 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("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)), FindBlockRangeTestCase("inner match exclude end paren in string when caret at end of quote", "(\"aa)${c}\")", '(', 1, isOuter = false, expected = TextRange(1, 6)),

View File

@ -79,7 +79,7 @@ class VimRegexEngineTest : VimTestCase() {
configureByText("Lor${c}em ${c}Ipsum") configureByText("Lor${c}em ${c}Ipsum")
val editor = fixture.editor.vim val editor = fixture.editor.vim
val mark = VimMark.create('m', 0, 0, editor.getPath(), editor.extractProtocol())!! val mark = VimMark.create('m', 0, 0, editor.getPath(), editor.extractProtocol())!!
val secondCaret = editor.carets().maxByOrNull { it.offset }!! val secondCaret = editor.carets().maxByOrNull { it.offset.point }!!
secondCaret.markStorage.setMark(mark) secondCaret.markStorage.setMark(mark)
val result = findAll("\\%>'m\\%#.") val result = findAll("\\%>'m\\%#.")

View File

@ -245,9 +245,6 @@ enum class SkipNeovimReason {
GUARDED_BLOCKS, GUARDED_BLOCKS,
CTRL_CODES, CTRL_CODES,
BUG_IN_NEOVIM,
PSI,
} }
fun LogicalPosition.toVimCoords(): VimCoords { fun LogicalPosition.toVimCoords(): VimCoords {

View File

@ -15,9 +15,7 @@ import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.PlatformCoreDataKeys
import com.intellij.openapi.actionSystem.ex.ActionUtil 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.PathManager
import com.intellij.openapi.application.WriteAction import com.intellij.openapi.application.WriteAction
import com.intellij.openapi.editor.CaretVisualAttributes import com.intellij.openapi.editor.CaretVisualAttributes
@ -27,7 +25,6 @@ import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.VisualPosition import com.intellij.openapi.editor.VisualPosition
import com.intellij.openapi.editor.colors.EditorColors import com.intellij.openapi.editor.colors.EditorColors
import com.intellij.openapi.editor.ex.EditorEx 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.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.fileTypes.FileType import com.intellij.openapi.fileTypes.FileType
import com.intellij.openapi.fileTypes.PlainTextFileType import com.intellij.openapi.fileTypes.PlainTextFileType
@ -67,6 +64,7 @@ import com.maddyhome.idea.vim.key.ToKeysMappingInfo
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.globalIjOptions 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.ijOptions
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.OptionAccessScope import com.maddyhome.idea.vim.options.OptionAccessScope
@ -454,11 +452,6 @@ abstract class VimTestCase {
assertEquals(expected, actual, "Wrong register contents") 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) { protected fun assertState(modeAfter: Mode) {
assertMode(modeAfter) assertMode(modeAfter)
assertCaretsVisualAttributes() assertCaretsVisualAttributes()
@ -762,13 +755,9 @@ abstract class VimTestCase {
val event = val event =
KeyEvent(editor.component, KeyEvent.KEY_PRESSED, Date().time, key.modifiers, key.keyCode, key.keyChar) 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( val e = AnActionEvent(
event, event,
context, injector.executionContextManager.onEditor(editor.vim).ij,
ActionPlaces.KEYBOARD_SHORTCUT, ActionPlaces.KEYBOARD_SHORTCUT,
VimShortcutKeyAction.instance.templatePresentation.clone(), VimShortcutKeyAction.instance.templatePresentation.clone(),
ActionManager.getInstance(), ActionManager.getInstance(),
@ -821,7 +810,7 @@ abstract class VimTestCase {
fun typeText(keys: List<KeyStroke?>, editor: Editor, project: Project?) { fun typeText(keys: List<KeyStroke?>, editor: Editor, project: Project?) {
val keyHandler = KeyHandler.getInstance() val keyHandler = KeyHandler.getInstance()
val dataContext = injector.executionContextManager.getEditorExecutionContext(editor.vim) val dataContext = injector.executionContextManager.onEditor(editor.vim)
TestInputModel.getInstance(editor).setKeyStrokes(keys.filterNotNull()) TestInputModel.getInstance(editor).setKeyStrokes(keys.filterNotNull())
runWriteCommand( runWriteCommand(
project, project,

View File

@ -8,7 +8,6 @@
package org.jetbrains.plugins.ideavim.action.motion.updown package org.jetbrains.plugins.ideavim.action.motion.updown
import com.intellij.idea.TestFor
import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimJavaTestCase import org.jetbrains.plugins.ideavim.VimJavaTestCase
@ -67,98 +66,4 @@ class MotionPercentOrMatchActionJavaTest : VimJavaTestCase() {
typeText("%") typeText("%")
assertState("/* foo $c */") 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())
}
} }

View File

@ -10,9 +10,7 @@ package ui
import com.automation.remarks.junit5.Video import com.automation.remarks.junit5.Video
import com.intellij.remoterobot.RemoteRobot import com.intellij.remoterobot.RemoteRobot
import com.intellij.remoterobot.fixtures.ComponentFixture
import com.intellij.remoterobot.fixtures.ContainerFixture import com.intellij.remoterobot.fixtures.ContainerFixture
import com.intellij.remoterobot.search.locators.byXpath
import com.intellij.remoterobot.steps.CommonSteps import com.intellij.remoterobot.steps.CommonSteps
import com.intellij.remoterobot.stepsProcessing.step import com.intellij.remoterobot.stepsProcessing.step
import com.intellij.remoterobot.utils.keyboard import com.intellij.remoterobot.utils.keyboard
@ -122,9 +120,6 @@ class UiTests {
Thread.sleep(5000) Thread.sleep(5000)
wrapWithIf(javaEditor) wrapWithIf(javaEditor)
testTrackActionId(javaEditor) testTrackActionId(javaEditor)
testActionGenerate(javaEditor)
testActionNewElementSamePlace(javaEditor)
testActionCopy(javaEditor)
} }
} }
@ -243,66 +238,6 @@ class UiTests {
vimExit() 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) { private fun IdeaFrame.createFile(fileName: String, remoteRobot: RemoteRobot) {
step("Create $fileName file") { step("Create $fileName file") {
with(projectViewTree) { with(projectViewTree) {

View File

@ -48,11 +48,7 @@ class PyCharmTest {
findAllText("Python Packages").isNotEmpty() && findAllText("Python Packages").isNotEmpty() &&
isSmartMode() 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) Thread.sleep(10_000)

View File

@ -55,7 +55,7 @@ dependencies {
compileOnly(project(":annotation-processors")) compileOnly(project(":annotation-processors"))
compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion") compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.3.1") testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
} }
tasks { tasks {

View File

@ -59,7 +59,7 @@ private fun changeCharacter(editor: VimEditor, caret: VimCaret, count: Int, ch:
val col = caret.getBufferPosition().column val col = caret.getBufferPosition().column
// TODO: Is this correct? Should we really use only current caret? We have a caret as an argument // 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 len = editor.lineLength(editor.currentCaret().getBufferPosition().line)
val offset = caret.offset val offset = caret.offset.point
if (len - col < count) { if (len - col < count) {
return false return false
} }

View File

@ -31,6 +31,6 @@ public class ChangeLastGlobalSearchReplaceAction : ChangeEditorActionHandler.Sin
): Boolean { ): Boolean {
val range = LineRange(0, editor.lineCount() - 1) val range = LineRange(0, editor.lineCount() - 1)
return injector.searchGroup return injector.searchGroup
.processSubstituteCommand(editor, editor.primaryCaret(), context, range, "s", "//~/&", Script(listOf())) .processSubstituteCommand(editor, editor.primaryCaret(), range, "s", "//~/&", Script(listOf()))
} }
} }

View File

@ -33,7 +33,7 @@ public class ChangeLastSearchReplaceAction : ChangeEditorActionHandler.SingleExe
for (caret in editor.carets()) { for (caret in editor.carets()) {
val line = caret.getBufferPosition().line val line = caret.getBufferPosition().line
if (!injector.searchGroup if (!injector.searchGroup
.processSubstituteCommand(editor, caret, context, LineRange(line, line), "s", "//~/", Script(listOf())) .processSubstituteCommand(editor, caret, LineRange(line, line), "s", "//~/", Script(listOf()))
) { ) {
result = false result = false
} }

View File

@ -75,7 +75,7 @@ private fun insertCharacterAroundCursor(editor: VimEditor, caret: VimCaret, dir:
vp = VimVisualPosition(vp.line + dir, vp.column, false) vp = VimVisualPosition(vp.line + dir, vp.column, false)
val len = editor.lineLength(editor.visualLineToBufferLine(vp.line)) val len = editor.lineLength(editor.visualLineToBufferLine(vp.line))
if (vp.column < len) { if (vp.column < len) {
val offset = editor.visualPositionToOffset(VimVisualPosition(vp.line, vp.column, false)) val offset = editor.visualPositionToOffset(VimVisualPosition(vp.line, vp.column, false)).point
val charsSequence = editor.text() val charsSequence = editor.text()
if (offset < charsSequence.length) { if (offset < charsSequence.length) {
val ch = charsSequence[offset] val ch = charsSequence[offset]

View File

@ -17,10 +17,11 @@ import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.OperatorArguments 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.common.TextRange
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.state.mode.SelectionType
import java.util.* import java.util.*
@CommandOrMotion(keys = ["<C-U>"], modes = [Mode.INSERT]) @CommandOrMotion(keys = ["<C-U>"], modes = [Mode.INSERT])
@ -56,13 +57,13 @@ private fun insertDeleteInsertedText(
var deleteTo = caret.vimInsertStart.startOffset var deleteTo = caret.vimInsertStart.startOffset
val offset = caret.offset val offset = caret.offset
if (offset == deleteTo) { if (offset == deleteTo) {
deleteTo = injector.motion.moveCaretToCurrentLineStartSkipLeading(editor, caret) deleteTo = Offset(injector.motion.moveCaretToCurrentLineStartSkipLeading(editor, caret))
} }
if (deleteTo != -1) { if (deleteTo.point != -1) {
injector.changeGroup.deleteRange( injector.changeGroup.deleteRange(
editor, editor,
caret, caret,
TextRange(deleteTo, offset), TextRange(deleteTo.point, offset.point),
SelectionType.CHARACTER_WISE, SelectionType.CHARACTER_WISE,
false, false,
operatorArguments, operatorArguments,

View File

@ -17,11 +17,11 @@ import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset
import com.maddyhome.idea.vim.helper.enumSetOf import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.state.mode.SelectionType
import java.util.* import java.util.*
@CommandOrMotion(keys = ["<C-W>"], modes = [Mode.INSERT]) @CommandOrMotion(keys = ["<C-W>"], modes = [Mode.INSERT])
@ -52,9 +52,9 @@ public class InsertDeletePreviousWordAction : ChangeEditorActionHandler.ForEachC
*/ */
private fun insertDeletePreviousWord(editor: VimEditor, caret: VimCaret, operatorArguments: OperatorArguments): Boolean { private fun insertDeletePreviousWord(editor: VimEditor, caret: VimCaret, operatorArguments: OperatorArguments): Boolean {
val deleteTo: Int = if (caret.getBufferPosition().column == 0) { val deleteTo: Int = if (caret.getBufferPosition().column == 0) {
caret.offset - 1 caret.offset.point - 1
} else { } else {
var pointer = caret.offset - 1 var pointer = caret.offset.point - 1
val chars = editor.text() val chars = editor.text()
while (pointer >= 0 && chars[pointer] == ' ' && chars[pointer] != '\n') { while (pointer >= 0 && chars[pointer] == ' ' && chars[pointer] != '\n') {
pointer-- pointer--
@ -73,7 +73,7 @@ private fun insertDeletePreviousWord(editor: VimEditor, caret: VimCaret, operato
if (deleteTo < 0) { if (deleteTo < 0) {
return false return false
} }
val range = TextRange(deleteTo, caret.offset) val range = TextRange(deleteTo, caret.offset.point)
injector.changeGroup.deleteRange(editor, caret, range, SelectionType.CHARACTER_WISE, true, operatorArguments) injector.changeGroup.deleteRange(editor, caret, range, SelectionType.CHARACTER_WISE, true, operatorArguments)
return true return true
} }

View File

@ -15,6 +15,7 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.Offset
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
@ -78,7 +79,11 @@ private fun insertNewLineAbove(editor: VimEditor, context: ExecutionContext) {
// Check if the "last character on previous line" has a guard // Check if the "last character on previous line" has a guard
// This is actively used in pycharm notebooks https://youtrack.jetbrains.com/issue/VIM-2495 // This is actively used in pycharm notebooks https://youtrack.jetbrains.com/issue/VIM-2495
val hasGuards = moves.stream().anyMatch { (_, second): Pair<VimCaret?, Int?> -> val hasGuards = moves.stream().anyMatch { (_, second): Pair<VimCaret?, Int?> ->
editor.document.getOffsetGuard(second!!) != null editor.document.getOffsetGuard(
Offset(
second!!,
),
) != null
} }
if (!hasGuards) { if (!hasGuards) {
for ((first, second) in moves) { for ((first, second) in moves) {

View File

@ -15,12 +15,12 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.ex.ExException import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.helper.RWLockLabel import com.maddyhome.idea.vim.helper.RWLockLabel
import com.maddyhome.idea.vim.put.PutData import com.maddyhome.idea.vim.put.PutData
import com.maddyhome.idea.vim.register.Register import com.maddyhome.idea.vim.register.Register
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.vimscript.model.Script import com.maddyhome.idea.vim.vimscript.model.Script
@CommandOrMotion(keys = ["<C-R>"], modes = [Mode.INSERT]) @CommandOrMotion(keys = ["<C-R>"], modes = [Mode.INSERT])
@ -40,7 +40,7 @@ public class InsertRegisterAction : VimActionHandler.SingleExecution() {
if (argument?.character == '=') { if (argument?.character == '=') {
injector.application.invokeLater { injector.application.invokeLater {
try { try {
val expression = readExpression(editor, context) val expression = readExpression(editor)
if (expression != null) { if (expression != null) {
if (expression.isNotEmpty()) { if (expression.isNotEmpty()) {
val expressionValue = val expressionValue =
@ -62,8 +62,8 @@ public class InsertRegisterAction : VimActionHandler.SingleExecution() {
} }
} }
private fun readExpression(editor: VimEditor, context: ExecutionContext): String? { private fun readExpression(editor: VimEditor): String? {
return injector.commandLineHelper.inputString(editor, context, "=", null) return injector.commandLineHelper.inputString(editor, "=", null)
} }
} }

View File

@ -20,8 +20,8 @@ import com.maddyhome.idea.vim.command.MotionType
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.Motion import com.maddyhome.idea.vim.handler.Motion
import com.maddyhome.idea.vim.handler.MotionActionHandler import com.maddyhome.idea.vim.handler.MotionActionHandler
import com.maddyhome.idea.vim.state.mode.inVisualMode
import com.maddyhome.idea.vim.state.mode.isInsertionAllowed import com.maddyhome.idea.vim.state.mode.isInsertionAllowed
import com.maddyhome.idea.vim.state.mode.inVisualMode
@CommandOrMotion(keys = ["g$", "g<End>"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING]) @CommandOrMotion(keys = ["g$", "g<End>"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
public class MotionLastScreenColumnAction : MotionActionHandler.ForEachCaret() { public class MotionLastScreenColumnAction : MotionActionHandler.ForEachCaret() {

View File

@ -34,7 +34,7 @@ public class MotionShiftLeftAction : ShiftedArrowKeyHandler(true) {
override fun motionWithoutKeyModel(editor: VimEditor, context: ExecutionContext, cmd: Command) { override fun motionWithoutKeyModel(editor: VimEditor, context: ExecutionContext, cmd: Command) {
val caret = editor.currentCaret() val caret = editor.currentCaret()
val newOffset = injector.motion.findOffsetOfNextWord(editor, caret.offset, -cmd.count, false) val newOffset = injector.motion.findOffsetOfNextWord(editor, caret.offset.point, -cmd.count, false)
caret.moveToMotion(newOffset) caret.moveToMotion(newOffset)
} }
} }

View File

@ -35,7 +35,7 @@ public class MotionShiftRightAction : ShiftedArrowKeyHandler(true) {
override fun motionWithoutKeyModel(editor: VimEditor, context: ExecutionContext, cmd: Command) { override fun motionWithoutKeyModel(editor: VimEditor, context: ExecutionContext, cmd: Command) {
val caret = editor.currentCaret() val caret = editor.currentCaret()
val newOffset = injector.motion.findOffsetOfNextWord(editor, caret.offset, cmd.count, false) val newOffset = injector.motion.findOffsetOfNextWord(editor, caret.offset.point, cmd.count, false)
if (newOffset is Motion.AbsoluteOffset) { if (newOffset is Motion.AbsoluteOffset) {
caret.moveToOffset(newOffset.offset) caret.moveToOffset(newOffset.offset)
} }

View File

@ -13,10 +13,10 @@ import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.TextObjectVisualType import com.maddyhome.idea.vim.command.TextObjectVisualType
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.group.findBlockRange
import com.maddyhome.idea.vim.handler.TextObjectActionHandler import com.maddyhome.idea.vim.handler.TextObjectActionHandler
import com.maddyhome.idea.vim.helper.enumSetOf import com.maddyhome.idea.vim.helper.enumSetOf
import java.util.* import java.util.*
@ -35,7 +35,7 @@ public class MotionInnerBlockAngleAction : TextObjectActionHandler() {
count: Int, count: Int,
rawCount: Int, rawCount: Int,
): TextRange? { ): TextRange? {
return findBlockRange(editor, caret, '<', count, false) return injector.searchHelper.findBlockRange(editor, caret, '<', count, false)
} }
} }
@ -53,7 +53,7 @@ public class MotionInnerBlockBraceAction : TextObjectActionHandler() {
count: Int, count: Int,
rawCount: Int, rawCount: Int,
): TextRange? { ): TextRange? {
return findBlockRange(editor, caret, '{', count, false) return injector.searchHelper.findBlockRange(editor, caret, '{', count, false)
} }
} }
@ -71,7 +71,7 @@ public class MotionInnerBlockBracketAction : TextObjectActionHandler() {
count: Int, count: Int,
rawCount: Int, rawCount: Int,
): TextRange? { ): TextRange? {
return findBlockRange(editor, caret, '[', count, false) return injector.searchHelper.findBlockRange(editor, caret, '[', count, false)
} }
} }
@ -89,7 +89,7 @@ public class MotionInnerBlockParenAction : TextObjectActionHandler() {
count: Int, count: Int,
rawCount: Int, rawCount: Int,
): TextRange? { ): TextRange? {
return findBlockRange(editor, caret, '(', count, false) return injector.searchHelper.findBlockRange(editor, caret, '(', count, false)
} }
} }
@ -107,7 +107,7 @@ public class MotionOuterBlockAngleAction : TextObjectActionHandler() {
count: Int, count: Int,
rawCount: Int, rawCount: Int,
): TextRange? { ): TextRange? {
return findBlockRange(editor, caret, '<', count, true) return injector.searchHelper.findBlockRange(editor, caret, '<', count, true)
} }
} }
@ -125,7 +125,7 @@ public class MotionOuterBlockBraceAction : TextObjectActionHandler() {
count: Int, count: Int,
rawCount: Int, rawCount: Int,
): TextRange? { ): TextRange? {
return findBlockRange(editor, caret, '{', count, true) return injector.searchHelper.findBlockRange(editor, caret, '{', count, true)
} }
} }
@ -143,7 +143,7 @@ public class MotionOuterBlockBracketAction : TextObjectActionHandler() {
count: Int, count: Int,
rawCount: Int, rawCount: Int,
): TextRange? { ): TextRange? {
return findBlockRange(editor, caret, '[', count, true) return injector.searchHelper.findBlockRange(editor, caret, '[', count, true)
} }
} }
@ -161,6 +161,6 @@ public class MotionOuterBlockParenAction : TextObjectActionHandler() {
count: Int, count: Int,
rawCount: Int, rawCount: Int,
): TextRange? { ): TextRange? {
return findBlockRange(editor, caret, '(', count, true) return injector.searchHelper.findBlockRange(editor, caret, '(', count, true)
} }
} }

View File

@ -13,10 +13,11 @@ import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.command.TextObjectVisualType import com.maddyhome.idea.vim.command.TextObjectVisualType
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.handler.TextObjectActionHandler import com.maddyhome.idea.vim.handler.TextObjectActionHandler
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.mode
@CommandOrMotion(keys = ["iW"], modes = [com.intellij.vim.annotations.Mode.VISUAL, com.intellij.vim.annotations.Mode.OP_PENDING]) @CommandOrMotion(keys = ["iW"], modes = [com.intellij.vim.annotations.Mode.VISUAL, com.intellij.vim.annotations.Mode.OP_PENDING])
public class MotionInnerBigWordAction : TextObjectActionHandler() { public class MotionInnerBigWordAction : TextObjectActionHandler() {
@ -92,10 +93,10 @@ private fun getWordRange(
var dir = 1 var dir = 1
var selection = false var selection = false
if (editor.mode is Mode.VISUAL) { if (editor.mode is Mode.VISUAL) {
if (caret.vimSelectionStart > caret.offset) { if (caret.vimSelectionStart > caret.offset.point) {
dir = -1 dir = -1
} }
if (caret.vimSelectionStart != caret.offset) { if (caret.vimSelectionStart != caret.offset.point) {
selection = true selection = true
} }
} }

View File

@ -39,7 +39,7 @@ public class SearchEntryFwdAction : MotionActionHandler.ForEachCaret() {
): Motion { ): Motion {
if (argument == null) return Motion.Error if (argument == null) return Motion.Error
return injector.searchGroup return injector.searchGroup
.processSearchCommand(editor, argument.string, caret.offset, Direction.FORWARDS).toMotionOrError() .processSearchCommand(editor, argument.string, caret.offset.point, Direction.FORWARDS).toMotionOrError()
} }
override val motionType: MotionType = MotionType.EXCLUSIVE override val motionType: MotionType = MotionType.EXCLUSIVE

View File

@ -39,7 +39,7 @@ public class SearchEntryRevAction : MotionActionHandler.ForEachCaret() {
): Motion { ): Motion {
if (argument == null) return Motion.Error if (argument == null) return Motion.Error
return injector.searchGroup return injector.searchGroup
.processSearchCommand(editor, argument.string, caret.offset, Direction.BACKWARDS).toMotionOrError() .processSearchCommand(editor, argument.string, caret.offset.point, Direction.BACKWARDS).toMotionOrError()
} }
override val motionType: MotionType = MotionType.EXCLUSIVE override val motionType: MotionType = MotionType.EXCLUSIVE

View File

@ -16,9 +16,9 @@ import com.maddyhome.idea.vim.api.getLineEndForOffset
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.state.mode.SelectionType
/** /**
* @author Alex Plate * @author Alex Plate
@ -36,10 +36,10 @@ public class SelectEnableBlockModeAction : VimActionHandler.SingleExecution() {
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Boolean { ): Boolean {
editor.removeSecondaryCarets() editor.removeSecondaryCarets()
val lineEnd = editor.getLineEndForOffset(editor.primaryCaret().offset) val lineEnd = editor.getLineEndForOffset(editor.primaryCaret().offset.point)
editor.primaryCaret().run { editor.primaryCaret().run {
vimSetSystemSelectionSilently(offset, (offset + 1).coerceAtMost(lineEnd)) vimSetSystemSelectionSilently(offset.point, (offset.point + 1).coerceAtMost(lineEnd))
moveToInlayAwareOffset((offset + 1).coerceAtMost(lineEnd)) moveToInlayAwareOffset((offset.point + 1).coerceAtMost(lineEnd))
} }
return injector.visualMotionGroup.enterSelectMode(editor, SelectionType.BLOCK_WISE) return injector.visualMotionGroup.enterSelectMode(editor, SelectionType.BLOCK_WISE)
} }

View File

@ -16,9 +16,9 @@ import com.maddyhome.idea.vim.api.getLineEndForOffset
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.state.mode.SelectionType
/** /**
* @author Alex Plate * @author Alex Plate
@ -35,11 +35,11 @@ public class SelectEnableCharacterModeAction : VimActionHandler.SingleExecution(
cmd: Command, cmd: Command,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Boolean { ): Boolean {
editor.nativeCarets().sortedByDescending { it.offset }.forEach { caret -> editor.nativeCarets().sortedByDescending { it.offset.point }.forEach { caret ->
val lineEnd = editor.getLineEndForOffset(caret.offset) val lineEnd = editor.getLineEndForOffset(caret.offset.point)
caret.run { caret.run {
vimSetSystemSelectionSilently(offset, (offset + 1).coerceAtMost(lineEnd)) vimSetSystemSelectionSilently(offset.point, (offset.point + 1).coerceAtMost(lineEnd))
moveToInlayAwareOffset((offset + 1).coerceAtMost(lineEnd)) moveToInlayAwareOffset((offset.point + 1).coerceAtMost(lineEnd))
} }
} }
return injector.visualMotionGroup.enterSelectMode(editor, SelectionType.CHARACTER_WISE) return injector.visualMotionGroup.enterSelectMode(editor, SelectionType.CHARACTER_WISE)

View File

@ -37,8 +37,8 @@ public class SelectEnableLineModeAction : VimActionHandler.SingleExecution() {
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Boolean { ): Boolean {
editor.nativeCarets().forEach { caret -> editor.nativeCarets().forEach { caret ->
val lineEnd = editor.getLineEndForOffset(caret.offset) val lineEnd = editor.getLineEndForOffset(caret.offset.point)
val lineStart = editor.getLineStartForOffset(caret.offset) val lineStart = editor.getLineStartForOffset(caret.offset.point)
caret.vimSetSystemSelectionSilently(lineStart, lineEnd) caret.vimSetSystemSelectionSilently(lineStart, lineEnd)
} }
return injector.visualMotionGroup.enterSelectMode(editor, SelectionType.LINE_WISE) return injector.visualMotionGroup.enterSelectMode(editor, SelectionType.LINE_WISE)

View File

@ -48,8 +48,8 @@ public class SelectToggleVisualMode : VimActionHandler.SingleExecution() {
editor.setSelectMode(myMode.selectionType) editor.setSelectMode(myMode.selectionType)
if (myMode.selectionType != SelectionType.LINE_WISE) { if (myMode.selectionType != SelectionType.LINE_WISE) {
editor.nativeCarets().forEach { editor.nativeCarets().forEach {
if (it.offset + injector.visualMotionGroup.selectionAdj == it.selectionEnd) { if (it.offset.point + injector.visualMotionGroup.selectionAdj == it.selectionEnd) {
it.moveToInlayAwareOffset(it.offset + injector.visualMotionGroup.selectionAdj) it.moveToInlayAwareOffset(it.offset.point + injector.visualMotionGroup.selectionAdj)
} }
} }
} }
@ -57,8 +57,8 @@ public class SelectToggleVisualMode : VimActionHandler.SingleExecution() {
editor.pushVisualMode(myMode.selectionType) editor.pushVisualMode(myMode.selectionType)
if (myMode.selectionType != SelectionType.LINE_WISE) { if (myMode.selectionType != SelectionType.LINE_WISE) {
editor.nativeCarets().forEach { editor.nativeCarets().forEach {
if (it.offset == it.selectionEnd && it.visualLineStart <= it.offset - injector.visualMotionGroup.selectionAdj) { if (it.offset.point == it.selectionEnd && it.visualLineStart <= it.offset.point - injector.visualMotionGroup.selectionAdj) {
it.moveToInlayAwareOffset(it.offset - injector.visualMotionGroup.selectionAdj) it.moveToInlayAwareOffset(it.offset.point - injector.visualMotionGroup.selectionAdj)
} }
} }
} }

View File

@ -48,7 +48,7 @@ public class SelectMotionLeftAction : MotionActionHandler.ForEachCaret() {
if (editor.isTemplateActive()) { if (editor.isTemplateActive()) {
logger.debug("Template is active. Activate insert mode") logger.debug("Template is active. Activate insert mode")
injector.changeGroup.insertBeforeCursor(editor, context) injector.changeGroup.insertBeforeCursor(editor, context)
if (caret.offset in startSelection..endSelection) { if (caret.offset.point in startSelection..endSelection) {
return startSelection.toMotion() return startSelection.toMotion()
} }
} }

View File

@ -48,11 +48,11 @@ public class SelectMotionRightAction : MotionActionHandler.ForEachCaret() {
if (editor.isTemplateActive()) { if (editor.isTemplateActive()) {
logger.debug("Template is active. Activate insert mode") logger.debug("Template is active. Activate insert mode")
injector.changeGroup.insertBeforeCursor(editor, context) injector.changeGroup.insertBeforeCursor(editor, context)
if (caret.offset in startSelection..endSelection) { if (caret.offset.point in startSelection..endSelection) {
return endSelection.toMotion() return endSelection.toMotion()
} }
} }
return caret.offset.toMotion() return caret.offset.point.toMotion()
} }
return injector.motion.getHorizontalMotion(editor, caret, operatorArguments.count1, false) return injector.motion.getHorizontalMotion(editor, caret, operatorArguments.count1, false)
} }

View File

@ -48,14 +48,14 @@ public sealed class WordEndAction(public val direction: Direction, public val bi
} }
private fun moveCaretToNextWordEnd(editor: VimEditor, caret: ImmutableVimCaret, count: Int, bigWord: Boolean): Motion { private fun moveCaretToNextWordEnd(editor: VimEditor, caret: ImmutableVimCaret, count: Int, bigWord: Boolean): Motion {
if (caret.offset == 0 && count < 0 || caret.offset >= editor.fileSize() - 1 && count > 0) { if (caret.offset.point == 0 && count < 0 || caret.offset.point >= editor.fileSize() - 1 && count > 0) {
return Motion.Error return Motion.Error
} }
// If we are doing this move as part of a change command (e.q. cw), we need to count the current end of // If we are doing this move as part of a change command (e.q. cw), we need to count the current end of
// word if the cursor happens to be on the end of a word already. If this is a normal move, we don't count // word if the cursor happens to be on the end of a word already. If this is a normal move, we don't count
// the current word. // the current word.
val pos = injector.searchHelper.findNextWordEnd(editor, caret.offset, count, bigWord, false) val pos = injector.searchHelper.findNextWordEnd(editor, caret.offset.point, count, bigWord, false)
return if (pos == -1) { return if (pos == -1) {
if (count < 0) { if (count < 0) {
AbsoluteOffset(injector.motion.moveCaretToLineStart(editor, 0)) AbsoluteOffset(injector.motion.moveCaretToLineStart(editor, 0))

View File

@ -28,7 +28,7 @@ public class MotionBigWordLeftAction : MotionActionHandler.ForEachCaret() {
argument: Argument?, argument: Argument?,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Motion { ): Motion {
return injector.motion.findOffsetOfNextWord(editor, caret.offset, -operatorArguments.count1, true) return injector.motion.findOffsetOfNextWord(editor, caret.offset.point, -operatorArguments.count1, true)
} }
override val motionType: MotionType = MotionType.EXCLUSIVE override val motionType: MotionType = MotionType.EXCLUSIVE

View File

@ -28,7 +28,7 @@ public class MotionBigWordRightAction : MotionActionHandler.ForEachCaret() {
argument: Argument?, argument: Argument?,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Motion { ): Motion {
return injector.motion.findOffsetOfNextWord(editor, caret.offset, operatorArguments.count1, true) return injector.motion.findOffsetOfNextWord(editor, caret.offset.point, operatorArguments.count1, true)
} }
override val motionType: MotionType = MotionType.EXCLUSIVE override val motionType: MotionType = MotionType.EXCLUSIVE

View File

@ -31,7 +31,7 @@ public class MotionCamelEndLeftAction : MotionActionHandler.ForEachCaret() {
argument: Argument?, argument: Argument?,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Motion { ): Motion {
return injector.searchHelper.findPreviousCamelEnd(editor.text(), caret.offset, operatorArguments.count1) return injector.searchHelper.findPreviousCamelEnd(editor.text(), caret.offset.point, operatorArguments.count1)
?.toMotionOrError() ?: Motion.Error ?.toMotionOrError() ?: Motion.Error
} }
} }
@ -47,7 +47,7 @@ public class MotionCamelEndRightAction : MotionActionHandler.ForEachCaret() {
argument: Argument?, argument: Argument?,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Motion { ): Motion {
return injector.searchHelper.findNextCamelEnd(editor.text(), caret.offset + 1, operatorArguments.count1) return injector.searchHelper.findNextCamelEnd(editor.text(), caret.offset.point + 1, operatorArguments.count1)
?.toMotionOrError() ?: Motion.Error ?.toMotionOrError() ?: Motion.Error
} }
} }

View File

@ -31,7 +31,7 @@ public class MotionCamelLeftAction : MotionActionHandler.ForEachCaret() {
argument: Argument?, argument: Argument?,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Motion { ): Motion {
return injector.searchHelper.findPreviousCamelStart(editor.text(), caret.offset, operatorArguments.count1) return injector.searchHelper.findPreviousCamelStart(editor.text(), caret.offset.point, operatorArguments.count1)
?.toMotionOrError() ?: Motion.Error ?.toMotionOrError() ?: Motion.Error
} }
} }
@ -47,7 +47,7 @@ public class MotionCamelRightAction : MotionActionHandler.ForEachCaret() {
argument: Argument?, argument: Argument?,
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Motion { ): Motion {
return injector.searchHelper.findNextCamelStart(editor.text(), caret.offset + 1, operatorArguments.count1) return injector.searchHelper.findNextCamelStart(editor.text(), caret.offset.point + 1, operatorArguments.count1)
?.toMotionOrError() ?: Motion.Error ?.toMotionOrError() ?: Motion.Error
} }
} }

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