1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-09-17 22:24:47 +02:00

Compare commits

...

60 Commits

Author SHA1 Message Date
8de7a9f741 Set plugin version to chylex-27 2024-02-16 06:09:40 +01:00
5977840cef Disable speed search in Project tool window when NERDTree is enabled 2024-02-16 06:09:40 +01:00
d0fea229c4 Work around VIM-3306 (make paragraph-motion plugin go to the end of the line) 2024-02-16 06:09:40 +01:00
9eeac4a788 Apply scrolloff after executing native IDEA actions 2024-02-16 06:09:40 +01:00
8370661b0d Stay on same line after reindenting 2024-02-16 06:09:40 +01:00
3f88e64abf Implement motions to go to next/previous misspelled word 2024-02-16 06:09:39 +01:00
28e3a7c894 Update search register when using f/t 2024-02-16 06:09:39 +01:00
bc859c65f5 Automatically add unambiguous imports after running a macro 2024-02-16 06:09:39 +01:00
141fc8ed22 Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2024-02-16 06:09:39 +01:00
dec494639c Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2024-02-16 06:09:39 +01:00
d06688033b Fix(VIM-3166): Workaround to fix broken filtering of visual lines 2024-02-16 06:09:39 +01:00
98110b9132 Add support for count for visual and line motion surround 2024-02-16 06:09:39 +01:00
a0404b8664 Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2024-02-16 06:09:39 +01:00
a2a30d545e Fix(VIM-696) Restore visual mode after undo/redo, and disable incompatible actions 2024-02-16 06:09:38 +01:00
4f3bf039c9 Revert(VIM-2884): Fix moving lines to cursor 2024-02-15 08:30:02 +01:00
1f4b97fa00 Respect count with <Action> mappings 2024-02-15 08:30:02 +01:00
61301fdbf5 Add Matchit support for Java statements 2024-02-15 08:30:02 +01:00
c980250529 Change matchit plugin to use HTML patterns in unrecognized files 2024-02-15 08:30:02 +01:00
bdb4b02016 Reset insert mode when switching active editor 2024-02-15 08:30:02 +01:00
62039a00d3 Remove update checker 2024-02-15 08:30:02 +01:00
95c66fd4a6 Set custom plugin version 2024-02-15 08:30:01 +01:00
Alex Plate
fb9bfbaeeb Do not check the compatibility of the sneak plugin 2024-02-14 18:05:56 +02:00
Alex Plate
09668f4fcb Update gradle wrapper to version 8.6 2024-02-14 17:07:20 +02:00
Alex Plate
4c7a6165ed Fix incorrect logs location in UI tests 2024-02-13 19:26:58 +02:00
Alex Plate
12d0d2613f Allow sneak plugin to be registered with the original mappings from the sneak plugin 2024-02-13 19:20:41 +02:00
Alex Plate
42ee78cd3d Disable runIde task for test subprojects 2024-02-13 19:18:18 +02:00
Alex Plate
58d308c1ed Fix the logging reporting for UI tests 2024-02-13 18:02:55 +02:00
Alex Plate
29e1bcc53d Wait longer for the python console to intialize 2024-02-13 14:56:14 +02:00
Alex Plate
3531574e5e Remove intellij plugin dependency for UI tests 2024-02-13 12:59:13 +02:00
Alex Plate
b9721218ab Enable PyCharm for python UI tests 2024-02-13 12:42:33 +02:00
Alex Plate
a119ea6a29 Fix octopus UI test 2024-02-13 12:34:56 +02:00
Alex Plate
95ef5f5f32 Fix incorrect configuration for UI tests 2024-02-13 08:43:12 +02:00
Alex Plate
b81b18645b Disable publishPlugin task for tests 2024-02-13 08:34:51 +02:00
Alex Plate
ce591f1b43 Configure UI robot for the root project 2024-02-12 14:49:38 +02:00
Alex Plate
28afa4b3ce Change the gradle config for UI tests 2024-02-12 13:37:10 +02:00
IdeaVim Bot
89a24d71a6 Update changelog after merging PR 2024-02-10 18:07:26 +00:00
f69630b668 VIM-3238 Fix recording a macro that replays another macro 2024-02-10 20:05:47 +02:00
Alex Plate
a2d34a883b Fix verifyPlugin task 2024-02-09 16:45:57 +02:00
Alex Plate
5c79b887d8 Do not ask for license for py tests 2024-02-09 16:31:25 +02:00
Alex Plate
d0475bf659 Explicitly specify jupyter version 2024-02-09 16:27:22 +02:00
Alex Plate
85c9576699 Extract common UI test fixtures 2024-02-09 16:22:22 +02:00
Alex Plate
2483450a1f Rename ui tests for better consistency 2024-02-09 16:11:14 +02:00
Alex Plate
519d5eed06 Set up PyCharm UI test 2024-02-09 16:03:31 +02:00
Alex Plate
d87965775a Update version of robot 2024-02-08 16:48:03 +02:00
Alex Plate
8c6f81aa00 Fix incorrect location of the video 2024-02-08 16:46:03 +02:00
Alex Plate
6ea0ab0968 Print if neovim testing is enabled or not 2024-02-08 15:22:54 +02:00
Alex Plate
70ab3ecdbe Make a custom expand function for UI tests 2024-02-08 15:10:47 +02:00
Alex Plate
a24ae616df Fix the creation of the video for the UI tests 2024-02-08 15:10:36 +02:00
Alex Plate
cc838f614f Remove the testWithNeovim task
Now you can just run `gradlew test -Dnvim`
2024-02-08 14:53:43 +02:00
Alex Plate
ae62a9f378 Fix the incorrect test 2024-02-08 14:35:52 +02:00
Alex Plate
1b5778a58c Remove configurations for UI tests 2024-02-08 14:33:02 +02:00
Alex Plate
27a689e7b8 Extract UI tests into the separate module 2024-02-08 14:21:09 +02:00
Alex Plate
8e6c490c62 Reformat the config file 2024-02-08 09:50:13 +02:00
Alex Plate
ccda70fe53 Remove mentions of ktlint 2024-02-08 09:49:17 +02:00
Alex Plate
26c42e4f0d Turn off fixes for the gradle release with test search issues 2024-02-08 09:48:38 +02:00
Alex Plate
3244dd52eb The line with compilation error is disabled
In 2024.1 EAP this line causes compilation error due to platform conversion from java to kotlin
The fix is landed in the platform and it should work fine with the new EAP.
However, since our tests are fail now, I'll comment out this line and bring it back in one week.
2024-02-08 09:39:54 +02:00
Alex Plate
4c6807a0c2 Extract long running tests into a separate module 2024-02-08 09:28:26 +02:00
Alex Plate
03a6a2749a Clean up build.gradle.kts 2024-02-08 09:16:16 +02:00
Alex Plate
82f69456e9 Exclude propery tests into a separate project 2024-02-08 09:14:52 +02:00
filipp.vakhitov
6beda371fb TeamCity change in 'Ideavim / IdeaVim releases' project: VCS roots of 'Publish vim-engine' build configuration were updated 2024-02-07 19:42:16 +00:00
103 changed files with 1802 additions and 1073 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
* text=auto eol=lf

View File

@@ -11,12 +11,12 @@ jobs:
- uses: actions/checkout@v4
- name: Apply Patch
run: |
git apply src/test/java/ui/octopus.patch
git apply tests/ui-ij-tests/src/test/kotlin/ui/octopus.patch
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 11
java-version: 17
- name: Setup FFmpeg
uses: FedericoCarboni/setup-ffmpeg@v3
with:
@@ -30,7 +30,7 @@ jobs:
- name: Run Idea
run: |
mkdir -p build/reports
gradle :runIdeForUiTests > build/reports/idea.log &
gradle runIdeForUiTests > build/reports/idea.log &
- name: Wait for Idea started
uses: jtalk/url-health-check-action@v3
with:
@@ -38,10 +38,10 @@ jobs:
max-attempts: 20
retry-delay: 10s
- name: Tests
run: gradle :testUi
run: gradle :tests:ui-ij-tests:testUi
- name: Move video
if: always()
run: mv video build/reports
run: mv tests/ui-ij-tests/video build/reports
- name: Move sandbox logs
if: always()
run: mv build/idea-sandbox/system/log sandbox-idea-log

55
.github/workflows/runUiPyTests.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: Run UI PyCharm Tests
on:
workflow_dispatch:
schedule:
- cron: '0 12 * * *'
jobs:
build-for-ui-test-mac-os:
if: github.repository == 'JetBrains/ideavim'
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 17
- uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Setup FFmpeg
uses: FedericoCarboni/setup-ffmpeg@v3
with:
# Not strictly necessary, but it may prevent rate limit
# errors especially on GitHub-hosted macos machines.
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Gradle
uses: gradle/gradle-build-action@v2.4.2
- name: Build Plugin
run: gradle :buildPlugin
- name: Run Idea
run: |
mkdir -p build/reports
gradle :runIdeForUiTests -PideaType=PC > build/reports/idea.log &
- name: Wait for Idea started
uses: jtalk/url-health-check-action@v3
with:
url: http://127.0.0.1:8082
max-attempts: 20
retry-delay: 10s
- name: Tests
run: gradle :tests:ui-py-tests:testUi
- name: Move video
if: always()
run: mv tests/ui-py-tests/video build/reports
- name: Move sandbox logs
if: always()
run: mv build/idea-sandbox/system/log sandbox-idea-log
- name: Save report
if: always()
uses: actions/upload-artifact@v4
with:
name: ui-test-fails-report-mac
path: |
build/reports
sandbox-idea-log

View File

@@ -13,7 +13,7 @@ jobs:
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 11
java-version: 17
- name: Setup FFmpeg
uses: FedericoCarboni/setup-ffmpeg@v3
with:
@@ -27,7 +27,7 @@ jobs:
- name: Run Idea
run: |
mkdir -p build/reports
gradle :runIdeForUiTests > build/reports/idea.log &
gradle runIdeForUiTests > build/reports/idea.log &
- name: Wait for Idea started
uses: jtalk/url-health-check-action@v3
with:
@@ -35,10 +35,10 @@ jobs:
max-attempts: 20
retry-delay: 10s
- name: Tests
run: gradle :testUi
run: gradle :tests:ui-ij-tests:testUi
- name: Move video
if: always()
run: mv video build/reports
run: mv tests/ui-ij-tests/video build/reports
- name: Move sandbox logs
if: always()
run: mv build/idea-sandbox/system/log sandbox-idea-log

View File

@@ -6,6 +6,7 @@
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</value>
</option>
<option name="LINE_SEPARATOR" value="&#10;" />
<JavaCodeStyleSettings>
<option name="FIELD_NAME_PREFIX" value="my" />
<option name="STATIC_FIELD_NAME_PREFIX" value="our" />

View File

@@ -34,7 +34,6 @@ object Compatibility : IdeaVimBuildType({
java --version
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}org.jetbrains.IdeaVim-EasyMotion' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}io.github.mishkun.ideavimsneak' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}eu.theblob42.idea.whichkey' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}IdeaVimExtension' [latest-IU] -team-city
# Outdated java -jar verifier/verifier-cli-dev-all.jar check-plugin '${'$'}github.zgqq.intellij-enhance' [latest-IU] -team-city

View File

@@ -25,7 +25,7 @@ object LongRunning : IdeaVimBuildType({
steps {
gradle {
tasks = "clean testLongRunning"
tasks = "clean :tests:long-running-tests:testLongRunning"
buildFile = ""
enableStacktrace = true
}

View File

@@ -39,7 +39,7 @@ object Nvim : IdeaVimBuildType({
""".trimIndent()
}
gradle {
tasks = "clean testWithNeovim"
tasks = "clean test -Dnvim"
buildFile = ""
enableStacktrace = true
}

View File

@@ -24,7 +24,7 @@ object PropertyBased : IdeaVimBuildType({
steps {
gradle {
tasks = "clean testPropertyBased"
tasks = "clean :tests:property-tests:testPropertyBased"
buildFile = ""
enableStacktrace = true
}

View File

@@ -0,0 +1,19 @@
package patches.buildTypes
import jetbrains.buildServer.configs.kotlin.v2019_2.*
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.*
/*
This patch script was generated by TeamCity on settings change in UI.
To apply the patch, change the buildType with id = 'PublishVimEngine'
accordingly, and delete the patch script.
*/
changeBuildType(RelativeId("PublishVimEngine")) {
vcs {
check(branchFilter == "+:<default>") {
"Unexpected option value: branchFilter = $branchFilter"
}
branchFilter = "+:fleet"
}
}

View File

@@ -30,6 +30,7 @@ usual beta standards.
### Merged PRs:
* [725](https://github.com/JetBrains/ideavim/pull/725) by [Emanuel Gestosa](https://github.com/emanuelgestosa): Regex
* [805](https://github.com/JetBrains/ideavim/pull/805) by [chylex](https://github.com/chylex): VIM-3238 Fix recording a macro that replays another macro
## 2.8.0, 2024-01-30

File diff suppressed because it is too large Load Diff

View File

@@ -8,16 +8,17 @@
# suppress inspection "UnusedProperty" for whole file
ideaVersion=2023.3.2
ideaVersion=2023.3.3
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
ideaType=IC
downloadIdeaSources=true
instrumentPluginCode=true
version=SNAPSHOT
version=chylex-27
javaVersion=17
remoteRobotVersion=0.11.21
remoteRobotVersion=0.11.22
antlrVersion=4.10.1
kotlin.incremental.useClasspathSnapshot=false
# Please don't forget to update kotlin version in buildscript section
# Also update kotlinxSerializationVersion version
@@ -39,4 +40,4 @@ org.gradle.jvmargs='-Dfile.encoding=UTF-8'
kotlin.stdlib.default.dependency=false
# Disable incremental annotation processing
ksp.incremental=false
ksp.incremental=false

Binary file not shown.

View File

@@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

5
gradlew vendored
View File

@@ -130,10 +130,13 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.

View File

@@ -13,4 +13,8 @@ include 'vim-engine'
include 'scripts'
include 'annotation-processors'
include 'tests:java-tests'
include 'tests:property-tests'
include 'tests:long-running-tests'
include 'tests:ui-ij-tests'
include 'tests:ui-py-tests'
include 'tests:ui-fixtures'

View File

@@ -217,6 +217,8 @@ private object FileTypePatterns {
return if (fileTypeName in htmlLikeFileTypes) {
this.htmlPatterns
} else if (fileTypeName == "JAVA" || fileExtension == "java") {
this.javaPatterns
} else if (fileTypeName == "Ruby" || fileExtension == "rb") {
this.rubyPatterns
} else if (fileTypeName == "RHTML" || fileExtension == "erb") {
@@ -231,7 +233,7 @@ private object FileTypePatterns {
} else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") {
this.cMakePatterns
} else {
return null
this.htmlPatterns
}
}
@@ -242,6 +244,7 @@ private object FileTypePatterns {
)
private val htmlPatterns = createHtmlPatterns()
private val javaPatterns = createJavaPatterns()
private val rubyPatterns = createRubyPatterns()
private val rubyAndHtmlPatterns = rubyPatterns + htmlPatterns
private val phpPatterns = createPhpPatterns()
@@ -270,6 +273,14 @@ private object FileTypePatterns {
LanguagePatterns(linkedMapOf(openingTagPattern to htmlSearchPair), linkedMapOf(closingTagPattern to htmlSearchPair))
)
}
private fun createJavaPatterns(): LanguagePatterns {
return (
LanguagePatterns("\\b(?<!else\\s+)if\\b", "\\belse\\s+if\\b", "\\belse(?!\\s+if)\\b") +
LanguagePatterns("\\bdo\\b", "\\bwhile\\b") +
LanguagePatterns("\\btry\\b", "\\bcatch\\b", "\\bfinally\\b")
)
}
private fun createRubyPatterns(): LanguagePatterns {
// Original patterns: https://github.com/vim/vim/blob/master/runtime/ftplugin/ruby.vim

View File

@@ -502,6 +502,13 @@ internal class NerdTree : VimExtension {
}
},
)
for (c in ('a'..'z') + ('A'..'Z')) {
val ks = KeyStroke.getKeyStroke(c)
if (ks !in actionsRoot) {
registerCommand(c.toString(), NerdAction.Code { _, _, _ -> })
}
}
}
companion object {

View File

@@ -11,8 +11,8 @@ package com.maddyhome.idea.vim.extension.paragraphmotion
import com.intellij.openapi.editor.Caret
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.getLineEndForOffset
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.normalizeOffset
import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.extension.ExtensionHandler
@@ -45,8 +45,7 @@ internal class ParagraphMotion : VimExtension {
}
fun moveCaretToNextParagraph(editor: VimEditor, caret: Caret, count: Int): Int? {
return injector.searchHelper.findNextParagraph(editor, caret.vim, count, true)
?.let { editor.normalizeOffset(it, true) }
return injector.searchHelper.findNextParagraph(editor, caret.vim, count, true)?.let(editor::getLineEndForOffset)
}
}
}

View File

@@ -9,7 +9,6 @@
package com.maddyhome.idea.vim.extension.sneak
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ScrollType
import com.intellij.openapi.editor.colors.EditorColors
@@ -19,6 +18,7 @@ import com.intellij.openapi.editor.markup.HighlighterTargetArea
import com.intellij.openapi.editor.markup.RangeHighlighter
import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.util.Disposer
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.VimProjectService
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
@@ -30,16 +30,16 @@ import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.extension.VimExtensionFacade
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
import com.maddyhome.idea.vim.extension.VimExtensionHandler
import com.maddyhome.idea.vim.helper.StrictMode
import com.maddyhome.idea.vim.newapi.ij
import java.awt.Font
import java.awt.event.KeyEvent
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import javax.swing.Timer
private const val DEFAULT_HIGHLIGHT_DURATION_SNEAK: Long = 300
private const val DEFAULT_HIGHLIGHT_DURATION_SNEAK = 300
// By [Mikhail Levchenko](https://github.com/Mishkun)
// Original repository with the plugin: https://github.com/Mishkun/ideavim-sneak
@@ -250,11 +250,13 @@ internal class IdeaVimSneakExtension : VimExtension {
}
private fun setClearHighlightRangeTimer(highlighter: RangeHighlighter) {
Executors.newSingleThreadScheduledExecutor().schedule({
ApplicationManager.getApplication().invokeLater {
editor?.markupModel?.removeHighlighter(highlighter) ?: StrictMode.fail("Highlighters without an editor")
val timer = Timer(DEFAULT_HIGHLIGHT_DURATION_SNEAK) {
if (editor?.isDisposed != true) {
editor?.markupModel?.removeHighlighter(highlighter)
}
}, DEFAULT_HIGHLIGHT_DURATION_SNEAK, TimeUnit.MILLISECONDS)
}
timer.isRepeats = false
timer.start()
}
private fun getHighlightTextAttributes() = TextAttributes(
@@ -279,13 +281,41 @@ private fun VimExtension.mapToFunctionAndProvideKeys(keys: String, handler: Exte
handler,
false
)
VimExtensionFacade.putKeyMapping(
VimExtensionFacade.putExtensionHandlerMapping(
MappingMode.NXO,
injector.parser.parseKeys(keys),
injector.parser.parseKeys(commandFromOriginalPlugin(keys)),
owner,
injector.parser.parseKeys(command(keys)),
handler,
false
)
// This is a combination to meet the following requirements:
// - Now we should support mappings from sneak `Sneak_s` and mappings from the previous version of the plugin `(sneak-s)`
// - The shortcut should not be registered if any of these mappings is overridden in .ideavimrc
// - The shortcut should not be registered if some other shortcut for this key exists
val fromKeys = injector.parser.parseKeys(keys)
val filteredModes = MappingMode.NXO.filterNotTo(HashSet()) {
VimPlugin.getKey().hasmapto(it, injector.parser.parseKeys(command(keys)))
}
val filteredModes2 = MappingMode.NXO.filterNotTo(HashSet()) {
VimPlugin.getKey().hasmapto(it, injector.parser.parseKeys(commandFromOriginalPlugin(keys)))
}
val filteredFromModes = MappingMode.NXO.filterNotTo(HashSet()) {
injector.keyGroup.hasmapfrom(it, fromKeys)
}
val doubleFiltered = MappingMode.NXO
.filter { it in filteredModes2 && it in filteredModes && it in filteredFromModes }
.toSet()
putKeyMapping(doubleFiltered, fromKeys, owner, injector.parser.parseKeys(command(keys)), true)
putKeyMapping(
doubleFiltered,
fromKeys,
owner,
injector.parser.parseKeys(commandFromOriginalPlugin(keys)),
true
)
}
private fun command(keys: String) = "<Plug>(sneak-$keys)"
private fun commandFromOriginalPlugin(keys: String) = "<Plug>Sneak_$keys"

View File

@@ -0,0 +1,30 @@
package com.maddyhome.idea.vim.extension.surround
import com.intellij.util.text.CharSequenceSubSequence
internal data class RepeatedCharSequence(val text: CharSequence, val count: Int) : CharSequence {
override val length = text.length * count
override fun get(index: Int): Char {
if (index < 0 || index >= length) throw IndexOutOfBoundsException()
return text[index % text.length]
}
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
return CharSequenceSubSequence(this, startIndex, endIndex)
}
override fun toString(): String {
return text.repeat(count)
}
companion object {
fun of(text: CharSequence, count: Int): CharSequence {
return when (count) {
0 -> ""
1 -> text
else -> RepeatedCharSequence(text, count)
}
}
}
}

View File

@@ -13,6 +13,7 @@ import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimChangeGroup
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.endsWithNewLine
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
@@ -31,7 +32,10 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMa
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
@@ -80,7 +84,7 @@ internal class VimSurroundExtension : VimExtension {
override val isRepeatable = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
setOperatorFunction(Operator())
setOperatorFunction(Operator(supportsMultipleCursors = false, count = 1)) // TODO
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
}
}
@@ -101,7 +105,7 @@ internal class VimSurroundExtension : VimExtension {
val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset)
if (lastNonWhiteSpaceOffset != null) {
val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1)
performSurround(pair, range, it)
performSurround(pair, range, it, count = operatorArguments.count1)
}
// it.moveToOffset(lineStartOffset)
}
@@ -121,15 +125,13 @@ internal class VimSurroundExtension : VimExtension {
private class VSurroundHandler : ExtensionHandler {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val selectionStart = editor.ij.caretModel.primaryCaret.selectionStart
// NB: Operator ignores SelectionType anyway
if (!Operator().apply(editor, context, editor.mode.selectionType)) {
if (!Operator(supportsMultipleCursors = true, count = operatorArguments.count1).apply(editor, context, editor.mode.selectionType)) {
return
}
runWriteAction {
// Leave visual mode
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
editor.ij.caretModel.moveToOffset(selectionStart)
}
}
}
@@ -150,6 +152,10 @@ internal class VimSurroundExtension : VimExtension {
companion object {
fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
editor.ij.runWithEveryCaretAndRestore { changeAtCaret(editor, context, charFrom, newSurround) }
}
fun changeAtCaret(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
// Save old register values for carets
val surroundings = editor.sortedCarets()
.map {
@@ -257,20 +263,41 @@ internal class VimSurroundExtension : VimExtension {
}
}
private class Operator : OperatorFunction {
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val ijEditor = editor.ij
val c = getChar(ijEditor)
private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction {
override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val editor = vimEditor.ij
val c = getChar(editor)
if (c.code == 0) return true
val pair = getOrInputPair(c, ijEditor) ?: return false
// XXX: Will it work with line-wise or block-wise selections?
val range = getSurroundRange(editor.currentCaret()) ?: return false
performSurround(pair, range, editor.currentCaret(), selectionType == SelectionType.LINE_WISE)
// Jump back to start
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
val pair = getOrInputPair(c, editor) ?: return false
runWriteAction {
val change = VimPlugin.getChange()
if (supportsMultipleCursors) {
editor.runWithEveryCaretAndRestore {
applyOnce(editor, change, pair, count)
}
}
else {
applyOnce(editor, change, pair, count)
// Jump back to start
executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor)
}
}
return true
}
private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: Pair<String, String>, count: Int) {
// XXX: Will it work with line-wise or block-wise selections?
val primaryCaret = editor.caretModel.primaryCaret
val range = getSurroundRange(primaryCaret.vim)
if (range != null) {
val start = RepeatedCharSequence.of(pair.first, count)
val end = RepeatedCharSequence.of(pair.second, count)
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, start)
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.endOffset + start.length, end)
}
}
private fun getSurroundRange(caret: VimCaret): TextRange? {
val editor = caret.editor
@@ -354,15 +381,15 @@ private fun getChar(editor: Editor): Char {
return res
}
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, tagsOnNewLines: Boolean = false) {
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
runWriteAction {
val editor = caret.editor
val change = VimPlugin.getChange()
val leftSurround = pair.first + if (tagsOnNewLines) "\n" else ""
val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count)
val isEOF = range.endOffset == editor.text().length
val hasNewLine = editor.endsWithNewLine()
val rightSurround = if (tagsOnNewLines) {
val rightSurround = (if (tagsOnNewLines) {
if (isEOF && !hasNewLine) {
"\n" + pair.second
} else {
@@ -370,7 +397,7 @@ private fun performSurround(pair: Pair<String, String>, range: TextRange, caret:
}
} else {
pair.second
}
}).let { RepeatedCharSequence.of(it, count) }
change.insertText(editor, caret, range.startOffset, leftSurround)
change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)

View File

@@ -78,7 +78,6 @@ import java.math.BigInteger
import java.util.*
import java.util.function.Consumer
import kotlin.math.max
import kotlin.math.min
/**
* Provides all the insert/replace related functionality
@@ -395,6 +394,7 @@ public class ChangeGroup : VimChangeGroupBase() {
context: ExecutionContext,
range: TextRange,
) {
val startPos = editor.offsetToBufferPosition(caret.offset.point)
val startOffset = editor.getLineStartForOffset(range.startOffset)
val endOffset = editor.getLineEndForOffset(range.endOffset)
val ijEditor = (editor as IjVimEditor).editor
@@ -419,11 +419,7 @@ public class ChangeGroup : VimChangeGroupBase() {
}
}
val afterAction = {
val firstLine = editor.offsetToBufferPosition(
min(startOffset.toDouble(), endOffset.toDouble()).toInt()
).line
val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine)
caret.moveToOffset(newOffset)
caret.moveToOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, startPos.line))
restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
}
if (project != null) {

View File

@@ -83,7 +83,7 @@ public object IjOptions {
public val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false))
public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true))
public val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100))
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", false, isTemporary = true))
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isTemporary = true))
public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isTemporary = true))
public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isTemporary = true))
public val showmodewidget: ToggleOption = addOption(ToggleOption("showmodewidget", GLOBAL, "showmodewidget", false, isTemporary = true))
@@ -92,4 +92,4 @@ public object IjOptions {
// This needs to be Option<out VimDataType> so that it can work with derived option types, such as NumberOption, which
// derives from Option<VimInt>
private fun <T : Option<out VimDataType>> addOption(option: T) = option.also { Options.addOption(option) }
}
}

View File

@@ -0,0 +1,68 @@
package com.maddyhome.idea.vim.group
import com.intellij.codeInsight.daemon.ReferenceImporter
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiRecursiveElementWalkingVisitor
import java.util.function.BooleanSupplier
internal object MacroAutoImport {
fun run(editor: Editor, dataContext: DataContext) {
val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return
val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: return
if (!FileDocumentManager.getInstance().requestWriting(editor.document, project)) {
return
}
val importers = ReferenceImporter.EP_NAME.extensionList
if (importers.isEmpty()) {
return
}
ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Auto import", true) {
override fun run(indicator: ProgressIndicator) {
val fixes = ReadAction.nonBlocking<List<BooleanSupplier>> {
val fixes = mutableListOf<BooleanSupplier>()
file.accept(object : PsiRecursiveElementWalkingVisitor() {
override fun visitElement(element: PsiElement) {
for (reference in element.references) {
if (reference.resolve() != null) {
continue
}
for (importer in importers) {
importer.computeAutoImportAtOffset(editor, file, element.textRange.startOffset, true)
?.let(fixes::add)
}
}
super.visitElement(element)
}
})
return@nonBlocking fixes
}.executeSynchronously()
ApplicationManager.getApplication().invokeAndWait {
WriteCommandAction.writeCommandAction(project)
.withName("Auto Import")
.withGroupId("IdeaVimAutoImportAfterMacro")
.shouldRecordActionForActiveDocument(true)
.run<RuntimeException> {
fixes.forEach { it.asBoolean }
}
}
}
})
}
}

View File

@@ -7,8 +7,6 @@
*/
package com.maddyhome.idea.vim.group
import com.intellij.codeInsight.completion.CompletionPhase
import com.intellij.codeInsight.completion.impl.CompletionServiceImpl
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.ProcessCanceledException
@@ -21,6 +19,7 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.MessageHelper.message
import com.maddyhome.idea.vim.macro.VimMacroBase
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
/**
* Used to handle playback of macros
@@ -78,7 +77,7 @@ internal class MacroGroup : VimMacroBase() {
ProgressManager.getInstance().executeNonCancelableSection {
// Prevent autocompletion during macros.
// See https://github.com/JetBrains/ideavim/pull/772 for details
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion)
// CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion)
getInstance().handleKey(editor, key, context)
}
if (injector.messages.isError()) return@runnable
@@ -90,6 +89,9 @@ internal class MacroGroup : VimMacroBase() {
} finally {
keyStack.removeFirst()
}
if (!isInternalMacro) {
MacroAutoImport.run(editor.ij, context.ij)
}
}
if (isInternalMacro) {

View File

@@ -214,8 +214,8 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
* @param patternOffset The pattern offset, e.g. `/{pattern}/{offset}`
* @param direction The direction to search
*/
@TestOnly
public void setLastSearchState(@SuppressWarnings("unused") @NotNull Editor editor, @NotNull String pattern,
@Override
public void setLastSearchState(@SuppressWarnings("unused") @NotNull VimEditor editor, @NotNull String pattern,
@NotNull String patternOffset, Direction direction) {
if (globalIjOptions(injector).getUseNewRegex()) {
super.setLastSearchState(pattern, patternOffset, direction);

View File

@@ -329,7 +329,7 @@ public class EditorHelper {
final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
@NotNull final VimEditor editor1 = new IjVimEditor(editor);
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) + editor.getSettings().getAdditionalLinesCount();
final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine);
// For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen.

View File

@@ -12,6 +12,7 @@ package com.maddyhome.idea.vim.helper
import com.intellij.codeWithMe.ClientId
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.CaretState
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ex.util.EditorUtil
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
@@ -19,6 +20,8 @@ import com.intellij.util.ui.table.JBTableRowEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.group.IjOptionConstants
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.inBlockSelection
import java.awt.Component
import javax.swing.JComponent
import javax.swing.JTable
@@ -93,3 +96,41 @@ internal val Caret.vimLine: Int
*/
internal val Editor.vimLine: Int
get() = this.caretModel.currentCaret.vimLine
internal inline fun Editor.runWithEveryCaretAndRestore(action: () -> Unit) {
val caretModel = this.caretModel
val carets = if (this.vim.inBlockSelection) null else caretModel.allCarets
if (carets == null || carets.size == 1) {
action()
}
else {
var initialDocumentSize = this.document.textLength
var documentSizeDifference = 0
val caretOffsets = carets.map { it.selectionStart to it.selectionEnd }
val restoredCarets = mutableListOf<CaretState>()
caretModel.removeSecondaryCarets()
for ((selectionStart, selectionEnd) in caretOffsets) {
if (selectionStart == selectionEnd) {
caretModel.primaryCaret.moveToOffset(selectionStart + documentSizeDifference)
}
else {
caretModel.primaryCaret.setSelection(
selectionStart + documentSizeDifference,
selectionEnd + documentSizeDifference
)
}
action()
restoredCarets.add(caretModel.caretsAndSelections.single())
val documentLength = this.document.textLength
documentSizeDifference += documentLength - initialDocumentSize
initialDocumentSize = documentLength
}
caretModel.caretsAndSelections = restoredCarets
}
}

View File

@@ -14,7 +14,6 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.normalizeVisualColumn
import com.maddyhome.idea.vim.api.options
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenHeight
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenWidth
import com.maddyhome.idea.vim.helper.EditorHelper.getNonNormalizedVisualLineAtBottomOfScreen
@@ -29,6 +28,7 @@ import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToBottomOfScre
import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToMiddleOfScreen
import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToTopOfScreen
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.VimStateMachine
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
@@ -56,7 +56,7 @@ internal object ScrollViewHelper {
// that this needs to be replaced as a more or less dumb line for line rewrite.
val topLine = getVisualLineAtTopOfScreen(editor)
val bottomLine = getVisualLineAtBottomOfScreen(editor)
val lastLine = vimEditor.getVisualLineCount() - 1
val lastLine = vimEditor.getVisualLineCount() + editor.settings.additionalLinesCount
// We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred
val scrollOffset = injector.options(vimEditor).scrolloff

View File

@@ -9,6 +9,7 @@
package com.maddyhome.idea.vim.helper;
import com.google.common.collect.Lists;
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx;
import com.intellij.lang.CodeDocumentationAwareCommenter;
import com.intellij.lang.Commenter;
import com.intellij.lang.Language;
@@ -16,22 +17,30 @@ import com.intellij.lang.LanguageCommenters;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.spellchecker.SpellCheckerSeveritiesProvider;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
import com.maddyhome.idea.vim.api.VimEditor;
import com.maddyhome.idea.vim.regexp.*;
import com.maddyhome.idea.vim.regexp.match.VimMatchResult;
import com.maddyhome.idea.vim.state.mode.Mode;
import com.maddyhome.idea.vim.state.VimStateMachine;
import com.maddyhome.idea.vim.common.CharacterPosition;
import com.maddyhome.idea.vim.common.Direction;
import com.maddyhome.idea.vim.common.TextRange;
import com.maddyhome.idea.vim.newapi.IjVimCaret;
import com.maddyhome.idea.vim.newapi.IjVimEditor;
import com.maddyhome.idea.vim.regexp.CharPointer;
import com.maddyhome.idea.vim.regexp.RegExp;
import com.maddyhome.idea.vim.state.VimStateMachine;
import com.maddyhome.idea.vim.state.mode.Mode;
import it.unimi.dsi.fastutil.ints.IntComparator;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntRBTreeSet;
import it.unimi.dsi.fastutil.ints.IntSortedSet;
import kotlin.Pair;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
@@ -1573,6 +1582,42 @@ public class SearchHelper {
return PsiHelper.findMethodEnd(editor, caret.getOffset(), count);
}
public static int findMisspelledWords(@NotNull Editor editor,
int startOffset,
int endOffset,
int skipCount,
IntComparator offsetOrdering) {
Project project = editor.getProject();
if (project == null) {
return -1;
}
IntSortedSet offsets = new IntRBTreeSet(offsetOrdering);
DaemonCodeAnalyzerEx.processHighlights(editor.getDocument(), project, SpellCheckerSeveritiesProvider.TYPO,
startOffset, endOffset, highlight -> {
if (highlight.getSeverity() == SpellCheckerSeveritiesProvider.TYPO) {
int offset = highlight.getStartOffset();
if (offset >= startOffset && offset <= endOffset) {
offsets.add(offset);
}
}
return true;
});
if (offsets.isEmpty()) {
return -1;
}
if (skipCount >= offsets.size()) {
return offsets.lastInt();
}
else {
IntIterator offsetIterator = offsets.iterator();
offsetIterator.skip(skipCount);
return offsetIterator.nextInt();
}
}
private static @NotNull String parseMatchPairsOption(final VimEditor vimEditor) {
List<String> pairs = options(injector, vimEditor).getMatchpairs();
StringBuilder res = new StringBuilder();

View File

@@ -14,6 +14,7 @@ import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.undo.UndoManager
import com.intellij.openapi.components.Service
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
@@ -21,6 +22,8 @@ import com.maddyhome.idea.vim.common.ChangesListener
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.inVisualMode
import com.maddyhome.idea.vim.undo.UndoRedoBase
/**
@@ -39,6 +42,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
if (injector.globalIjOptions().oldundo) {
SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
restoreVisualMode(editor)
} else {
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
editor.runWithChangeTracking {
@@ -74,6 +78,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
if (undoManager.isRedoAvailable(fileEditor)) {
if (injector.globalIjOptions().oldundo) {
SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
restoreVisualMode(editor)
} else {
undoManager.redo(fileEditor)
CommandProcessor.getInstance().runUndoTransparentAction {
@@ -131,4 +136,21 @@ internal class UndoRedoHelper : UndoRedoBase() {
val hasChanges: Boolean
get() = changeListener.hasChanged || initialPath != editor.getPath()
}
private fun restoreVisualMode(editor: VimEditor) {
if (!editor.inVisualMode && editor.getSelectionModel().hasSelection()) {
val detectedMode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor)
// Visual block selection is restored into multiple carets, so multi-carets that form a block are always
// identified as visual block mode, leading to false positives.
// Since I use visual block mode much less often than multi-carets, this is a judgment call to never restore
// visual block mode.
val wantedMode = if (detectedMode == SelectionType.BLOCK_WISE)
SelectionType.CHARACTER_WISE
else
detectedMode
VimPlugin.getVisualMotion().enterVisualMode(editor, wantedMode)
}
}
}

View File

@@ -1,32 +0,0 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.helper
import com.intellij.ide.plugins.StandalonePluginUpdateChecker
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.group.NotificationService
import com.maddyhome.idea.vim.icons.VimIcons
@Service(Service.Level.APP)
internal class VimStandalonePluginUpdateChecker : StandalonePluginUpdateChecker(
VimPlugin.getPluginId(),
updateTimestampProperty = PROPERTY_NAME,
NotificationService.IDEAVIM_STICKY_GROUP,
VimIcons.IDEAVIM,
) {
override fun skipUpdateCheck(): Boolean = VimPlugin.isNotEnabled() || "dev" in VimPlugin.getVersion()
companion object {
private const val PROPERTY_NAME = "ideavim.statistics.timestamp"
val instance: VimStandalonePluginUpdateChecker = service()
}
}

View File

@@ -27,6 +27,7 @@ import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.ex.AnActionListener
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.impl.ScrollingModelImpl
import com.intellij.openapi.project.DumbAwareToggleAction
import com.intellij.openapi.util.TextRange
import com.maddyhome.idea.vim.KeyHandler
@@ -56,6 +57,7 @@ internal object IdeaSpecifics {
private val surrounderAction =
"com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction"
private var editor: Editor? = null
private var caretOffset = -1
private var completionPrevDocumentLength: Int? = null
private var completionPrevDocumentOffset: Int? = null
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
@@ -64,6 +66,7 @@ internal object IdeaSpecifics {
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
if (hostEditor != null) {
editor = hostEditor
caretOffset = hostEditor.caretModel.offset
}
val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction
@@ -95,43 +98,56 @@ internal object IdeaSpecifics {
if (VimPlugin.isNotEnabled()) return
val editor = editor
if (editor != null && action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) {
val prevDocumentLength = completionPrevDocumentLength
val prevDocumentOffset = completionPrevDocumentOffset
if (editor != null) {
if (action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) {
val prevDocumentLength = completionPrevDocumentLength
val prevDocumentOffset = completionPrevDocumentOffset
if (prevDocumentLength != null && prevDocumentOffset != null) {
val register = VimPlugin.getRegister()
val addedTextLength = editor.document.textLength - prevDocumentLength
val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
if (prevDocumentLength != null && prevDocumentOffset != null) {
val register = VimPlugin.getRegister()
val addedTextLength = editor.document.textLength - prevDocumentLength
val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength)))
repeat(caretShift.coerceAtLeast(0)) {
register.recordKeyStroke(leftArrow)
register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength)))
repeat(caretShift.coerceAtLeast(0)) {
register.recordKeyStroke(leftArrow)
}
}
}
this.completionPrevDocumentLength = null
this.completionPrevDocumentOffset = null
}
//region Enter insert mode after surround with if
if (surrounderAction == action.javaClass.name && surrounderItems.any {
action.templatePresentation.text.endsWith(
it,
)
this.completionPrevDocumentLength = null
this.completionPrevDocumentOffset = null
}
) {
editor?.let {
val commandState = it.vim.vimStateMachine
//region Enter insert mode after surround with if
if (surrounderAction == action.javaClass.name && surrounderItems.any {
action.templatePresentation.text.endsWith(
it,
)
}
) {
val commandState = editor.vim.vimStateMachine
commandState.mode = Mode.NORMAL()
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
KeyHandler.getInstance().reset(it.vim)
VimPlugin.getChange().insertBeforeCursor(editor.vim, event.dataContext.vim)
KeyHandler.getInstance().reset(editor.vim)
}
//endregion
if (caretOffset != -1 && caretOffset != editor.caretModel.offset) {
val scrollModel = editor.scrollingModel as ScrollingModelImpl
if (scrollModel.isScrollingNow) {
val v = scrollModel.verticalScrollOffset
val h = scrollModel.horizontalScrollOffset
scrollModel.finishAnimation()
scrollModel.scroll(h, v)
scrollModel.finishAnimation()
}
injector.scroll.scrollCaretIntoView(editor.vim)
}
}
//endregion
this.editor = null
this.caretOffset = -1
}
}

View File

@@ -75,7 +75,6 @@ import com.maddyhome.idea.vim.handler.correctorRequester
import com.maddyhome.idea.vim.handler.keyCheckRequests
import com.maddyhome.idea.vim.helper.GuicursorChangeListener
import com.maddyhome.idea.vim.helper.StrictMode
import com.maddyhome.idea.vim.helper.VimStandalonePluginUpdateChecker
import com.maddyhome.idea.vim.helper.exitSelectMode
import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.forceBarCursor
@@ -92,6 +91,8 @@ import com.maddyhome.idea.vim.listener.MouseEventsDataHolder.skipNDragEvents
import com.maddyhome.idea.vim.listener.VimListenerManager.EditorListeners.add
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inSelectMode
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.state.mode.selectionType
@@ -305,6 +306,16 @@ internal object VimListenerManager {
class VimFileEditorManagerListener : FileEditorManagerListener {
override fun selectionChanged(event: FileEditorManagerEvent) {
if (VimPlugin.isNotEnabled()) return
val newEditor = event.newEditor
if (newEditor is TextEditor) {
val editor = newEditor.editor
if (editor.isInsertMode) {
VimStateMachine.getInstance(editor).mode = Mode.NORMAL()
KeyHandler.getInstance().reset(editor.vim)
}
}
MotionGroup.fileEditorManagerSelectionChangedCallback(event)
FileGroup.fileEditorManagerSelectionChangedCallback(event)
VimPlugin.getSearch().fileEditorManagerSelectionChangedCallback(event)
@@ -369,8 +380,6 @@ internal object VimListenerManager {
event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused))
}
VimStandalonePluginUpdateChecker.instance.pluginUsed()
}
override fun editorReleased(event: EditorFactoryEvent) {

View File

@@ -32,7 +32,7 @@ import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser.parseExpression
import org.jetbrains.annotations.TestOnly
import javax.swing.KeyStroke
public open class IjVimSearchGroup : VimSearchGroupBase() {
public abstract class IjVimSearchGroup : VimSearchGroupBase() {
init {
// TODO: Investigate migrating these listeners to use the effective value change listener
@@ -178,4 +178,4 @@ public open class IjVimSearchGroup : VimSearchGroupBase() {
showSearchHighlight = false
updateSearchHighlights(false)
}
}
}

View File

@@ -29,6 +29,8 @@ import com.maddyhome.idea.vim.helper.checkInString
import com.maddyhome.idea.vim.helper.fileSize
import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
import it.unimi.dsi.fastutil.ints.IntComparator
import it.unimi.dsi.fastutil.ints.IntComparators
import java.util.*
import java.util.function.Function
import java.util.regex.Pattern
@@ -689,4 +691,26 @@ internal class IjVimSearchHelper : VimSearchHelperBase() {
// End offset exclusive
return TextRange(bstart, bend + 1)
}
override fun findMisspelledWord(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Int {
val startOffset: Int
val endOffset: Int
val skipCount: Int
val offsetOrdering: IntComparator
if (count < 0) {
startOffset = 0
endOffset = caret.offset.point - 1
skipCount = -count - 1
offsetOrdering = IntComparators.OPPOSITE_COMPARATOR
}
else {
startOffset = caret.offset.point + 1
endOffset = editor.ij.document.textLength
skipCount = count - 1
offsetOrdering = IntComparators.NATURAL_COMPARATOR
}
return SearchHelper.findMisspelledWords(editor.ij, startOffset, endOffset, skipCount, offsetOrdering)
}
}

View File

@@ -333,7 +333,7 @@
* |[m| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodPreviousStartAction}
* |[p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
* |[p| {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction}
* |[s| TO BE IMPLEMENTED
* |[s| {@link com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordPreviousAction}
* |[z| TO BE IMPLEMENTED
* |[{| {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceOpenAction}
* |]_CTRL-D| TO BE IMPLEMENTED
@@ -358,7 +358,7 @@
* |]m| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodNextStartAction}
* |]p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
* |]p| {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction}
* |]s| TO BE IMPLEMENTED
* |]s| {@link com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordNextAction}
* |]z| TO BE IMPLEMENTED
* |]}| {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceCloseAction}
*

View File

@@ -1,12 +1,4 @@
<!--
~ Copyright 2003-2023 The IdeaVim authors
~
~ Use of this source code is governed by an MIT-style
~ license that can be found in the LICENSE.txt file or at
~ https://opensource.org/licenses/MIT.
-->
<idea-plugin url="https://plugins.jetbrains.com/plugin/164" xmlns:xi="http://www.w3.org/2001/XInclude">
<idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
<name>IdeaVim</name>
<id>IdeaVIM</id>
<description><![CDATA[
@@ -21,13 +13,13 @@
<li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li>
</ul>
]]></description>
<version>SNAPSHOT</version>
<version>chylex</version>
<vendor>JetBrains</vendor>
<!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version -->
<!-- Check for [Version Update] tag in YouTrack as well -->
<!-- Also, please update the value in build.gradle.kts file-->
<idea-version since-build="233.11799.30"/>
<idea-version since-build="232"/>
<!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) -->
<depends>com.intellij.modules.platform</depends>
@@ -158,5 +150,6 @@
</group>
<action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/>
<action id="VimJumpToSource" class="com.intellij.diff.actions.impl.OpenInEditorAction" />
</actions>
</idea-plugin>

View File

@@ -47,10 +47,7 @@ class MacroActionTest : VimTestCase() {
val editor = typeTextInFile(injector.parser.parseKeys("qa" + "3l" + "q"), "on<caret>e two three\n")
val commandState = editor.vim.vimStateMachine
kotlin.test.assertFalse(commandState.isRecording)
val registerGroup = VimPlugin.getRegister()
val register = registerGroup.getRegister('a')
assertNotNull<Any>(register)
kotlin.test.assertEquals("3l", register.text)
assertRegister('a', "3l")
}
@Test
@@ -58,9 +55,7 @@ class MacroActionTest : VimTestCase() {
configureByText("")
enterCommand("imap pp hello")
typeText(injector.parser.parseKeys("qa" + "i" + "pp<Esc>" + "q"))
val register = VimPlugin.getRegister().getRegister('a')
assertNotNull<Any>(register)
kotlin.test.assertEquals("ipp<Esc>", injector.parser.toKeyNotation(register.keys))
assertRegister('a', "ipp<Esc>")
}
@Test
@@ -68,7 +63,7 @@ class MacroActionTest : VimTestCase() {
typeTextInFile(injector.parser.parseKeys("qa" + "i" + "<C-K>OK<Esc>" + "q"), "")
val register = VimPlugin.getRegister().getRegister('a')
assertNotNull<Any>(register)
kotlin.test.assertEquals("i<C-K>OK<Esc>", injector.parser.toKeyNotation(register.keys))
assertRegister('a', "i<C-K>OK<Esc>")
}
@Test
@@ -141,8 +136,8 @@ class MacroActionTest : VimTestCase() {
assertState("4\n5\n")
}
// Broken, see the resulting text
fun `ignore test macro with macro`() {
@Test
fun `test macro with macro`() {
val content = """
Lorem Ipsum
@@ -152,16 +147,55 @@ class MacroActionTest : VimTestCase() {
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
configureByText(content)
typeText(injector.parser.parseKeys("qa" + "l" + "q" + "qb" + "10@a" + "q" + "2@b"))
typeText(
injector.parser.parseKeys(
"qa" + "l" + "q" +
"qb" + "6@a" + "q" +
"^" + "3@b"
)
)
val startOffset = content.rangeOf("rocks").startOffset
assertRegister('b', "6@a")
assertState("""
Lorem Ipsum
waitAndAssert {
println(fixture.editor.caretModel.offset)
println(startOffset)
println()
startOffset == fixture.editor.caretModel.offset
}
Lorem ipsum dolor ${c}sit amet,
consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent())
}
@Test
fun `test macro with macro with macro`() {
val content = """
Lorem Ipsum
${c}Lorem ipsum dolor sit amet,
consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
configureByText(content)
typeText(
injector.parser.parseKeys(
"qa" + "l" + "q" +
"qb" + "3@a" + "q" +
"qc" + "2@b" + "q" +
"^" + "3@c"
)
)
assertRegister('b', "3@a")
assertRegister('c', "2@b")
assertState("""
Lorem Ipsum
Lorem ipsum dolor ${c}sit amet,
consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent())
}
@Test

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -23,7 +23,7 @@ class BasicStringFunctions : VimTestCase() {
@Test
fun `test tolower`() {
configureByText("\n")
typeText(commandToKeys("echo toupper('Vim is awesome')"))
typeText(commandToKeys("echo tolower('Vim is awesome')"))
assertExOutput("vim is awesome\n")
}

View File

@@ -8,6 +8,7 @@
package org.jetbrains.plugins.ideavim.extension.sneak
import com.maddyhome.idea.vim.api.keys
import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
@@ -162,4 +163,26 @@ class IdeaVimSneakTest : VimTestCase() {
doTest("sa<ESC>sdw", before, after, Mode.NORMAL())
}
@Test
fun testSneakForwardFromMapping() {
val before = "som${c}e text"
val after = "some te${c}xt"
doTest("fxt", before, after, Mode.NORMAL()) {
// This should be fixed, but now we process `<Plug>` as a single key
typeText(keys(":map f <") + keys("Plug>Sneak_s<CR>"))
}
}
@Test
fun testSneakForwardFromMappingWithOldMappings() {
val before = "som${c}e text"
val after = "some te${c}xt"
doTest("fxt", before, after, Mode.NORMAL()) {
// This should be fixed, but now we process `<Plug>` as a single key
typeText(keys(":map f <") + keys("Plug>(sneak-s)<CR>"))
}
}
}

View File

@@ -1,71 +0,0 @@
/*
* Copyright 2003-2024 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package ui
import com.automation.remarks.junit5.Video
import com.intellij.remoterobot.steps.CommonSteps
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import java.nio.file.Files
import java.nio.file.Path
class PyCharmTest {
init {
// StepsLogger.init()
}
private lateinit var commonSteps: CommonSteps
companion object {
private var ideaProcess: Process? = null
private var tmpDir: Path = Files.createTempDirectory("launcher")
@BeforeAll
@JvmStatic
fun beforeAll() {
// val client = OkHttpClient()
// val ideDownloader = IdeDownloader(client)
// val downloadedIdePath = ideDownloader.downloadAndExtractLatestEap(Ide.PYCHARM, tmpDir)
// // This hack doesn't work because it breaks .app. Waiting for the new version of robot with the fix.
//// if (Os.hostOS() == Os.MAC) {
//// // Hack for the problem of double vmoptions file
//// // The ides now have two vmoptions files: one for ide itself and one for jetbrains client
//// // Because of some reason, when ide detects more than one vmoptions file, it tries to find one that ends on
//// // "64". And since it doesn't find one, the robot fails.
//// val dataPath = downloadedIdePath.resolve("Contents/bin")
//// Files.list(dataPath).filter {
//// it.fileName.toString().endsWith("jetbrains_client.vmoptions")
//// }.forEach { Files.delete(it) }
//// }
// ideaProcess = IdeLauncher.launchIde(
// downloadedIdePath,
// mapOf("robot-server.port" to 8083),
// emptyList(),
// listOf(ideDownloader.downloadRobotPlugin(tmpDir)),
// tmpDir
// )
}
@AfterAll
@JvmStatic
fun cleanUp() {
// ideaProcess?.destroy()
// tmpDir.toFile().deleteRecursively()
}
}
@Test
@Video
@Disabled("Waiting for the new version of the robot with fixes")
fun run() {
println("Hey")
}
}

View File

@@ -31,7 +31,7 @@ import com.maddyhome.idea.vim.state.mode.toVimNotation
import org.junit.Assert.assertEquals
import org.junit.jupiter.api.TestInfo
internal object NeovimTesting {
object NeovimTesting {
private lateinit var neovimApi: NeovimApi
private lateinit var neovim: Process
@@ -88,7 +88,7 @@ internal object NeovimTesting {
val noBehaviourDiffers = !method.isAnnotationPresent(VimBehaviorDiffers::class.java)
val noTestingWithoutNeovim = !method.isAnnotationPresent(TestWithoutNeovim::class.java) &&
!test.javaClass.isAnnotationPresent(TestWithoutNeovim::class.java)
val neovimTestingEnabled = System.getProperty("ideavim.nvim.test", "false")!!.toBoolean()
val neovimTestingEnabled = isNeovimTestingEnabled()
val notParserTest = "org.jetbrains.plugins.ideavim.ex.parser" !in test.javaClass.packageName
val notScriptImplementation = "org.jetbrains.plugins.ideavim.ex.implementation" !in test.javaClass.packageName
val notExtension = "org.jetbrains.plugins.ideavim.extension" !in test.javaClass.packageName
@@ -104,6 +104,12 @@ internal object NeovimTesting {
singleCaret
}
fun isNeovimTestingEnabled(): Boolean {
val property = System.getProperty("ideavim.nvim.test", "false")
val neovimTestingEnabled = if (property.isBlank()) true else property.toBoolean()
return neovimTestingEnabled
}
fun setupEditor(editor: Editor, test: TestInfo) {
if (!neovimEnabled(test, editor)) return
neovimApi.currentBuffer.get().setLines(0, -1, false, editor.document.text.split("\n")).get()

View File

@@ -80,6 +80,7 @@ import com.maddyhome.idea.vim.vimscript.model.datatypes.VimFuncref
import com.maddyhome.idea.vim.vimscript.parser.errors.IdeavimErrorListener
import org.jetbrains.annotations.ApiStatus
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.TestInfo
import org.junit.jupiter.api.assertThrows
@@ -105,7 +106,7 @@ import kotlin.test.assertTrue
abstract class VimTestCase {
protected lateinit var fixture: CodeInsightTestFixture
internal lateinit var testInfo: TestInfo
lateinit var testInfo: TestInfo
@BeforeEach
open fun setUp(testInfo: TestInfo) {
@@ -445,6 +446,11 @@ abstract class VimTestCase {
return NeovimTesting.getMark(char)
}
protected fun assertRegister(char: Char, expected: String?) {
val actual = injector.registerGroup.getRegister(char)?.keys?.let(injector.parser::toKeyNotation)
assertEquals(expected, actual, "Wrong register contents")
}
protected fun assertState(modeAfter: Mode) {
assertMode(modeAfter)
assertCaretsVisualAttributes()
@@ -795,6 +801,12 @@ abstract class VimTestCase {
const val s = EditorTestUtil.SELECTION_START_TAG
const val se = EditorTestUtil.SELECTION_END_TAG
@BeforeAll
@JvmStatic
fun beforeAll() {
println("Neovim testing: ${NeovimTesting.isNeovimTestingEnabled()}")
}
fun typeText(keys: List<KeyStroke?>, editor: Editor, project: Project?) {
val keyHandler = KeyHandler.getInstance()
val dataContext = injector.executionContextManager.onEditor(editor.vim)

View File

@@ -12,13 +12,6 @@ plugins {
id("org.jetbrains.intellij")
}
group = "org.example"
version = "SNAPSHOT"
repositories {
mavenCentral()
}
val kotlinVersion: String by project
val ideaVersion: String by project
val javaVersion: String by project
@@ -29,15 +22,28 @@ repositories {
}
dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
testImplementation(platform("org.junit:junit-bom:5.10.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
testImplementation(testFixtures(project(":"))) // The root project
}
tasks.test {
useJUnitPlatform()
tasks {
test {
useJUnitPlatform()
}
verifyPlugin {
enabled = false
}
publishPlugin {
enabled = false
}
runIde {
enabled = false
}
}
intellij {

View File

@@ -0,0 +1,65 @@
plugins {
java
kotlin("jvm")
id("org.jetbrains.intellij")
}
repositories {
mavenCentral()
maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
}
val kotlinVersion: String by project
val ideaVersion: String by project
val javaVersion: String by project
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
testImplementation(testFixtures(project(":"))) // The root project
}
tasks {
// This task is disabled because it should be excluded from `gradle test` run (because it's slow)
// I didn't find a better way to exclude except disabling and defining a new task with a different name
test {
enabled = false
useJUnitPlatform()
}
register<Test>("testLongRunning") {
group = "verification"
useJUnitPlatform()
}
verifyPlugin {
enabled = false
}
publishPlugin {
enabled = false
}
runIde {
enabled = false
}
}
intellij {
version.set(ideaVersion)
type.set("IC")
plugins.set(listOf("java"))
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion))
}
}
kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion))
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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
@@ -16,7 +16,6 @@ import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
class MacroTest : VimTestCase() {
// was a problem on revision affec9bb61ea5e1e635673a0041d61f7af3722b2
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
@Test

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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
@@ -15,7 +15,6 @@ import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
class MapCommandTest : VimTestCase() {
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
@Test
fun `test double recursion`() {

View File

@@ -0,0 +1,65 @@
plugins {
java
kotlin("jvm")
id("org.jetbrains.intellij")
}
repositories {
mavenCentral()
maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
}
val kotlinVersion: String by project
val ideaVersion: String by project
val javaVersion: String by project
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
testImplementation(testFixtures(project(":"))) // The root project
}
tasks {
// This task is disabled because it should be excluded from `gradle test` run (because it's slow)
// I didn't find a better way to exclude except disabling and defining a new task with a different name
test {
useJUnitPlatform()
enabled = false
}
register<Test>("testPropertyBased") {
group = "verification"
useJUnitPlatform()
}
verifyPlugin {
enabled = false
}
publishPlugin {
enabled = false
}
runIde {
enabled = false
}
}
intellij {
version.set(ideaVersion)
type.set("IC")
plugins.set(listOf("java"))
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion))
}
}
kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion))
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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
@@ -11,7 +11,6 @@ package org.jetbrains.plugins.ideavim.propertybased
import com.intellij.ide.IdeEventQueue
import com.intellij.openapi.editor.Editor
import com.intellij.testFramework.PlatformTestUtil
import com.maddyhome.idea.vim.action.change.LazyVimCommand
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.key.CommandNode
@@ -20,8 +19,8 @@ import org.jetbrains.jetCheck.Generator
import org.jetbrains.jetCheck.ImperativeCommand
import org.jetbrains.jetCheck.PropertyChecker
import org.jetbrains.plugins.ideavim.VimTestCase
import org.jetbrains.plugins.ideavim.propertybased.samples.javaText
import org.jetbrains.plugins.ideavim.propertybased.samples.loremText
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import java.awt.event.KeyEvent
import javax.swing.KeyStroke
@@ -63,19 +62,18 @@ class RandomActionsPropertyTest : VimPropertyTestBase() {
}
@Test
@Disabled
fun testRandomActionsOnJavaCode() {
// PropertyChecker.checkScenarios {
// ImperativeCommand { env ->
// val editor = configureByJavaText(javaText)
// try {
// moveCaretToRandomPlace(env, editor)
// env.executeCommands(Generator.sampledFrom(AvailableActions(editor)))
// } finally {
// reset(editor)
// }
// }
// }
PropertyChecker.checkScenarios {
ImperativeCommand { env ->
val editor = configureByJavaText(javaText)
try {
moveCaretToRandomPlace(env, editor)
env.executeCommands(Generator.sampledFrom(AvailableActions(editor)))
} finally {
reset(editor)
}
}
}
}
companion object {
@@ -100,7 +98,7 @@ private class AvailableActions(private val editor: Editor) : ImperativeCommand {
val usedKey = env.generateValue(keyGenerator, null)
val node = currentNode[usedKey]
env.logMessage("Use command: ${injector.parser.toKeyNotation(usedKey)}. ${if (node is CommandNode) "Action: ${(node.actionHolder as LazyVimCommand).actionId}" else ""}")
env.logMessage("Use command: ${injector.parser.toKeyNotation(usedKey)}. ${if (node is CommandNode) "Action: ${node.actionHolder.actionId}" else ""}")
VimTestCase.typeText(listOf(usedKey), editor, editor.project)
IdeEventQueue.getInstance().flushQueue()

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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
@@ -8,6 +8,7 @@
package org.jetbrains.plugins.ideavim.propertybased
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
@@ -35,4 +36,6 @@ abstract class VimPropertyTestBase : VimTestCase() {
VimPlugin.getSearch().resetState()
VimPlugin.getChange().reset()
}
protected fun configureByJavaText(content: String) = configureByText(JavaFileType.INSTANCE, content)
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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

View File

@@ -0,0 +1,47 @@
plugins {
java
kotlin("jvm")
id("java-test-fixtures")
}
repositories {
mavenCentral()
maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
}
val kotlinVersion: String by project
val ideaVersion: String by project
val javaVersion: String by project
val remoteRobotVersion: String by project
dependencies {
testFixturesImplementation("org.junit.jupiter:junit-jupiter:5.10.1")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
testFixturesImplementation(testFixtures(project(":"))) // The root project
testFixturesImplementation("com.intellij.remoterobot:remote-robot:$remoteRobotVersion")
testFixturesImplementation("com.intellij.remoterobot:remote-fixtures:$remoteRobotVersion")
testFixturesImplementation("com.intellij.remoterobot:ide-launcher:$remoteRobotVersion")
}
tasks {
// This task is disabled because it should be excluded from `gradle test` run (because it's slow)
// I didn't find a better way to exclude except disabling and defining a new task with a different name
test {
useJUnitPlatform()
enabled = false
}
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion))
}
}
kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion))
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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

View File

@@ -0,0 +1,57 @@
plugins {
java
kotlin("jvm")
}
repositories {
mavenCentral()
maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
}
val kotlinVersion: String by project
val ideaVersion: String by project
val javaVersion: String by project
val remoteRobotVersion: String by project
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
testImplementation(testFixtures(project(":"))) // The root project
testImplementation(testFixtures(project(":tests:ui-fixtures"))) // The root project
testImplementation("com.intellij.remoterobot:remote-robot:$remoteRobotVersion")
testImplementation("com.intellij.remoterobot:remote-fixtures:$remoteRobotVersion")
testImplementation("com.automation-remarks:video-recorder-junit5:2.0")
}
tasks {
// This task is disabled because it should be excluded from `gradle test` run (because it's slow)
// I didn't find a better way to exclude except disabling and defining a new task with a different name
test {
useJUnitPlatform()
enabled = false
}
register<Test>("testUi") {
group = "verification"
useJUnitPlatform()
// This is needed for the robot to access the message of the exception
// Usually these opens are provided by the intellij gradle plugin
// https://github.com/JetBrains/gradle-intellij-plugin/blob/b21e3f382e9885948a6427001d5e64234c602613/src/main/kotlin/org/jetbrains/intellij/utils/OpenedPackages.kt#L26
jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED")
}
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion))
}
}
kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion))
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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

View File

@@ -0,0 +1,58 @@
plugins {
java
kotlin("jvm")
}
repositories {
mavenCentral()
maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
}
val kotlinVersion: String by project
val ideaVersion: String by project
val javaVersion: String by project
val remoteRobotVersion: String by project
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
testImplementation(testFixtures(project(":"))) // The root project
testImplementation(testFixtures(project(":tests:ui-fixtures")))
testImplementation("com.intellij.remoterobot:remote-robot:$remoteRobotVersion")
testImplementation("com.intellij.remoterobot:remote-fixtures:$remoteRobotVersion")
testImplementation("com.intellij.remoterobot:ide-launcher:$remoteRobotVersion")
testImplementation("com.automation-remarks:video-recorder-junit5:2.0")
}
tasks {
// This task is disabled because it should be excluded from `gradle test` run (because it's slow)
// I didn't find a better way to exclude except disabling and defining a new task with a different name
test {
useJUnitPlatform()
enabled = false
}
register<Test>("testUi") {
group = "verification"
useJUnitPlatform()
// This is needed for the robot to access the message of the exception
// Usually these opens are provided by the intellij gradle plugin
// https://github.com/JetBrains/gradle-intellij-plugin/blob/b21e3f382e9885948a6427001d5e64234c602613/src/main/kotlin/org/jetbrains/intellij/utils/OpenedPackages.kt#L26
jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED")
}
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion))
}
}
kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion))
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.
*/
import com.automation.remarks.junit5.Video
import com.intellij.remoterobot.RemoteRobot
import com.intellij.remoterobot.steps.CommonSteps
import com.intellij.remoterobot.utils.keyboard
import com.intellij.remoterobot.utils.waitFor
import org.junit.jupiter.api.Test
import ui.pages.IdeaFrame
import ui.pages.idea
import ui.pages.welcomeFrame
import ui.utils.StepsLogger
import ui.utils.uiTest
class PyCharmTest {
init {
StepsLogger.init()
}
private lateinit var commonSteps: CommonSteps
@Test
@Video
fun run() = uiTest("ideaVimTest") {
commonSteps = CommonSteps(this)
startNewProject()
Thread.sleep(1000)
idea {
waitSmartMode()
testEnterWorksInPyConsole()
}
}
private fun IdeaFrame.testEnterWorksInPyConsole() {
findText("Python Console").click()
Thread.sleep(10_000)
keyboard {
enterText("print(123 + 321)")
enter()
}
waitFor {
findAllText("444").isNotEmpty()
}
}
private fun RemoteRobot.startNewProject() {
welcomeFrame {
createNewProjectLink.click()
button("Create").click()
}
}
}

View File

@@ -163,10 +163,10 @@ public class KeyHandler {
}
}
}
finishedCommandPreparation(editor, context, editorState, commandBuilder, key, shouldRecord)
} finally {
handleKeyRecursionCount--
}
finishedCommandPreparation(editor, context, editorState, commandBuilder, key, shouldRecord)
}
internal fun finishedCommandPreparation(

View File

@@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler
@CommandOrMotion(keys = ["<C-R>"], modes = [Mode.NORMAL])
@CommandOrMotion(keys = ["U", "<C-R>"], modes = [Mode.NORMAL, Mode.VISUAL])
public class RedoAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED

View File

@@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler
@CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL])
@CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL, Mode.VISUAL])
public class UndoAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED

View File

@@ -8,7 +8,6 @@
package com.maddyhome.idea.vim.action.change.change
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
@@ -22,7 +21,7 @@ import com.maddyhome.idea.vim.helper.CharacterHelper
/**
* @author vlan
*/
@CommandOrMotion(keys = ["u"], modes = [Mode.VISUAL])
@CommandOrMotion(keys = [], modes = [])
public class ChangeCaseLowerVisualAction : VisualOperatorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE

View File

@@ -8,7 +8,6 @@
package com.maddyhome.idea.vim.action.change.change
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
@@ -22,7 +21,7 @@ import com.maddyhome.idea.vim.helper.CharacterHelper
/**
* @author vlan
*/
@CommandOrMotion(keys = ["U"], modes = [Mode.VISUAL])
@CommandOrMotion(keys = [], modes = [])
public class ChangeCaseUpperVisualAction : VisualOperatorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE

View File

@@ -30,6 +30,7 @@ public class FilterVisualLinesAction : VimActionHandler.SingleExecution() {
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MOT_LINEWISE)
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
injector.markService.setVisualSelectionMarks(editor)
injector.processGroup.startFilterCommand(editor, context, cmd)
editor.exitVisualMode()
return true

View File

@@ -82,6 +82,13 @@ public sealed class TillCharacterMotion(
)
}
injector.motion.setLastFTCmd(tillCharacterMotionType, argument.character)
val offset = if (!finishBeforeCharacter) ""
else if (direction == Direction.FORWARDS) "s-1"
else "s+1"
injector.searchGroup.setLastSearchState(editor, argument.character.let { if (it == '.') "\\." else it.toString() }, offset, direction)
return res.toMotionOrError()
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.action.motion.text
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.command.MotionType
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.Motion
import com.maddyhome.idea.vim.handler.MotionActionHandler
import com.maddyhome.idea.vim.handler.toMotionOrError
import com.maddyhome.idea.vim.helper.enumSetOf
import java.util.*
@CommandOrMotion(keys = ["]s"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
public class MotionMisspelledWordNextAction : MotionActionHandler.ForEachCaret() {
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_JUMP)
override fun getOffset(
editor: VimEditor,
caret: ImmutableVimCaret,
context: ExecutionContext,
argument: Argument?,
operatorArguments: OperatorArguments,
): Motion {
return injector.searchHelper.findMisspelledWord(editor, caret, operatorArguments.count1).toMotionOrError()
}
override val motionType: MotionType = MotionType.EXCLUSIVE
}
@CommandOrMotion(keys = ["[s"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
public class MotionMisspelledWordPreviousAction : MotionActionHandler.ForEachCaret() {
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_JUMP)
override fun getOffset(
editor: VimEditor,
caret: ImmutableVimCaret,
context: ExecutionContext,
argument: Argument?,
operatorArguments: OperatorArguments,
): Motion {
return injector.searchHelper.findMisspelledWord(editor, caret, -operatorArguments.count1).toMotionOrError()
}
override val motionType: MotionType = MotionType.EXCLUSIVE
}

View File

@@ -145,7 +145,7 @@ public interface VimChangeGroup {
operatorArguments: OperatorArguments,
)
public fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: String): VimCaret
public fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: CharSequence): VimCaret
public fun insertText(editor: VimEditor, caret: VimCaret, str: String): VimCaret

View File

@@ -203,7 +203,7 @@ public abstract class VimChangeGroupBase : VimChangeGroup {
* @param caret The caret to start insertion in
* @param str The text to insert
*/
override fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: String): VimCaret {
override fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: CharSequence): VimCaret {
(editor as MutableVimEditor).insertText(Offset(offset), str)
val newCaret = caret.moveToInlayAwareOffset(offset + str.length)

View File

@@ -62,6 +62,7 @@ public interface VimKeyGroup {
public fun unregisterCommandActions()
public fun resetKeyMappings()
public fun hasmapto(mode: MappingMode, toKeys: List<KeyStroke>): Boolean
public fun hasmapfrom(mode: MappingMode, fromKeys: List<KeyStroke>): Boolean
public val shortcutConflicts: MutableMap<KeyStroke, ShortcutOwnerInfo>
public val savedShortcutConflicts: MutableMap<KeyStroke, ShortcutOwnerInfo>

View File

@@ -48,6 +48,10 @@ public abstract class VimKeyGroupBase : VimKeyGroup {
return this.getKeyMapping(mode).hasmapto(toKeys)
}
override fun hasmapfrom(mode: MappingMode, fromKeys: List<KeyStroke>): Boolean {
return this.getKeyMapping(mode).hasmapfrom(fromKeys)
}
override fun getKeyMapping(mode: MappingMode): KeyMapping {
return keyMappings.getOrPut(mode) { KeyMapping() }
}

View File

@@ -25,7 +25,7 @@ public interface VimSearchGroup {
* Last used pattern to perform a substitution.
*/
public var lastSubstitutePattern: String?
public fun setLastSearchState(editor: VimEditor, pattern: String, patternOffset: String, direction: Direction?)
public fun searchBackward(editor: VimEditor, offset: Int, count: Int): TextRange?
/**

View File

@@ -239,4 +239,10 @@ public interface VimSearchHelper {
count: Int,
isOuter: Boolean,
): TextRange?
public fun findMisspelledWord(
editor: VimEditor,
caret: ImmutableVimCaret,
count: Int,
): Int
}

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