1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-08-17 16:31:45 +02:00

Compare commits

..

177 Commits

Author SHA1 Message Date
0fbe675373 Set plugin version to chylex-39 2024-08-11 17:49:09 +02:00
43f30cdf7c Add action to run last macro in all opened files 2024-08-11 17:49:08 +02:00
44ad2c9780 Stop macro execution after a failed search 2024-08-11 17:49:08 +02:00
7c10a19ad3 Revert per-caret registers 2024-08-11 16:28:49 +02:00
7478a2bf39 Revert "Factor disposable objects on editor opening"
This reverts commit 1fa78935
2024-08-11 16:28:49 +02:00
98fad582f2 Fix(VIM-3364): Exception with mapped Generate action 2024-08-11 16:28:49 +02:00
41250fcf5f Apply scrolloff after executing native IDEA actions 2024-08-11 16:28:49 +02:00
5679ac79f6 Stay on same line after reindenting 2024-08-11 16:28:48 +02:00
ce9dcb75ac Update search register when using f/t 2024-08-11 16:28:48 +02:00
458514697d Automatically add unambiguous imports after running a macro 2024-08-11 16:28:48 +02:00
643e359dd7 Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2024-08-11 16:28:48 +02:00
2d4ae3c72d Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2024-08-11 16:28:48 +02:00
2109d55d18 Add support for count for visual and line motion surround 2024-08-11 16:28:48 +02:00
7e4777dba3 Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2024-08-11 16:28:48 +02:00
3c1698720f Fix(VIM-696) Restore visual mode after undo/redo, and disable incompatible actions 2024-08-11 16:28:48 +02:00
e517af3a6e Respect count with <Action> mappings 2024-08-11 16:28:48 +02:00
46210112fa Change matchit plugin to use HTML patterns in unrecognized files 2024-08-11 16:28:48 +02:00
308589fec5 Reset insert mode when switching active editor 2024-08-11 16:28:48 +02:00
c55e43e2ae Disable switching to insert mode for some editors 2024-08-11 16:28:47 +02:00
fbe262826c Remove update checker 2024-08-11 16:28:47 +02:00
de52305556 Set custom plugin version 2024-08-11 16:28:47 +02:00
dependabot[bot]
fbbd1ebc0d Bump org.antlr:antlr4 from 4.13.1 to 4.13.2
Bumps [org.antlr:antlr4](https://github.com/antlr/antlr4) from 4.13.1 to 4.13.2.
- [Release notes](https://github.com/antlr/antlr4/releases)
- [Changelog](https://github.com/antlr/antlr4/blob/dev/CHANGES.txt)
- [Commits](https://github.com/antlr/antlr4/compare/4.13.1...4.13.2)

---
updated-dependencies:
- dependency-name: org.antlr:antlr4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-07 18:46:09 +03:00
Alex Plate
8d5df11372 Add a warning when the action is executed from the write action
Related: VIM-3376
2024-08-06 11:11:40 +03:00
Matt Ellis
dfebe542d8 Fix incsearch highlight in multiple Rider instances
Unlike other IDEs, Rider has multiple client sessions. The IDE itself is the "local" session, while the external ReSharper process is treated as a "frontend" process. The code to get local editors was erroneously getting `ALL` sessions, rather than just `LOCAL` sessions, and assuming that the first was the local session. In Rider, opening three instances would add three additional clients, and that would change the order.

I don't know why I changed `LOCAL` to `ALL` when previously changing this bit of code. AIUI, using `LOCAL` should work fine. If it turns out that CWM or remote dev require `ALL`, please document why.

Fixes VIM-3549
2024-08-05 18:58:45 +03:00
Matt Ellis
7fde47c08b Update README.md
Added links to POSIX spec
2024-08-01 14:04:58 +03:00
dependabot[bot]
629919f634 Bump org.jetbrains.intellij.platform from 2.0.0-rc1 to 2.0.0-rc2
Bumps org.jetbrains.intellij.platform from 2.0.0-rc1 to 2.0.0-rc2.

---
updated-dependencies:
- dependency-name: org.jetbrains.intellij.platform
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-31 17:27:09 +02:00
dependabot[bot]
43fff1c73e Bump com.google.devtools.ksp:symbol-processing-api
Bumps [com.google.devtools.ksp:symbol-processing-api](https://github.com/google/ksp) from 2.0.0-1.0.23 to 2.0.0-1.0.24.
- [Release notes](https://github.com/google/ksp/releases)
- [Commits](https://github.com/google/ksp/compare/2.0.0-1.0.23...2.0.0-1.0.24)

---
updated-dependencies:
- dependency-name: com.google.devtools.ksp:symbol-processing-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-31 17:26:53 +02:00
Alex Plate
d02f0e17ca Revert "Fix(VIM-3376): Refactor the way IdeaVim executes actions"
This reverts commit 24514039
2024-07-31 13:14:25 +03:00
Filipp Vakhitov
5dc860f61e Fix(VIM-3569): Pipe-symbol can no longer be used in remaps 2024-07-30 18:02:12 +03:00
Alex Plate
956e726c31 Add test 2024-07-29 17:48:07 +02:00
Alex Plate
446067e2fe Update formatting 2024-07-29 16:57:29 +02:00
Alex Plate
bd53a895c0 Return nullability of editor in action 2024-07-29 16:54:02 +02:00
Alex Plate
e61fed2467 Revert "Use installer in case of the snapshot dependency"
This reverts commit f5e125759f.
2024-07-29 16:51:25 +02:00
Alex Plate
f5e125759f Use installer in case of the snapshot dependency 2024-07-29 16:47:31 +02:00
Alex Plate
622eb887c8 Make style refactoring 2024-07-29 16:37:57 +02:00
Alex Plate
9fb614e16c Pass editor to the "execute" method of the NativeAction 2024-07-29 16:36:01 +02:00
Alex Plate
1e0fa07768 Fix problem with unsatisfied condition 2024-07-29 16:15:58 +02:00
Alex Plate
bfb1d5b7f5 Remove accidential return 2024-07-29 14:08:15 +02:00
Alex Plate
2fad8790a9 Reset key handler in property tests 2024-07-29 13:57:32 +02:00
Alex Plate
9719106a14 Fix incorrect assertions after migration to the new version of gradle plugin 2024-07-29 13:52:14 +02:00
Alex Plate
870a0da2a2 Update UI tests without octopus handler 2024-07-29 13:41:24 +02:00
Alex Plate
d3c315d299 Update logs location in UI tests 2024-07-29 13:39:53 +02:00
Alex Plate
2583b6e792 Print the list of available keys for debugging 2024-07-29 13:27:15 +02:00
Alex Plate
89417eb4f6 Comment out compatibility check for copilot because it's broken for the moment 2024-07-29 13:16:50 +02:00
Alex Plate
560700c9aa Pass editor to the execution of the action
Related: VIM-3376
2024-07-29 12:49:16 +02:00
Alex Plate
24514039e1 Fix(VIM-3376): Refactor the way IdeaVim executes actions
Now instead of a few hacks, we use a special function from the platform
2024-07-29 12:49:16 +02:00
Alex Plate
ff44596c1a Migrate to gradle plugin 2.0.0-rc1 2024-07-26 19:00:31 +03:00
Filipp Vakhitov
b001d63fd9 Do not fire VimInsertListener on every mode change 2024-07-26 17:30:08 +03:00
Filipp Vakhitov
5db96bef28 Fix compilation with IdeaVimExtension 2024-07-26 17:30:08 +03:00
Filipp Vakhitov
39c615cddd Do not cast VimStateMachine classes in vim-engine
It will break if we change implementation in Fleet
2024-07-26 17:30:08 +03:00
Filipp Vakhitov
961173a93b Fix tests 2024-07-26 17:30:08 +03:00
Filipp Vakhitov
92741c6356 Fix compilation 2024-07-26 17:30:08 +03:00
Filipp Vakhitov
643eb2a85f Add context to MappingProcessor 2024-07-26 17:30:08 +03:00
filipp
883744e4ee Move blockInsert to vim-engine 2024-07-26 17:30:08 +03:00
filipp
66173e03be Move reformatCodeRange to vim-engine 2024-07-26 17:30:08 +03:00
filipp
e455722758 Move more methods to vim-engine 2024-07-26 17:30:08 +03:00
filipp
823bdc1561 Replace VimYankGroup with implementation that works with VimListenersNotifier 2024-07-26 17:30:08 +03:00
filipp
f91fda2ca5 Remove VimInsertListener
It can be replaced by ModeChangeListener
2024-07-26 17:30:08 +03:00
filipp
92abd76615 Move more methods to engine 2024-07-26 17:30:08 +03:00
filipp
57c45ca153 Move more methods to engine 2024-07-26 17:30:08 +03:00
filipp
7c623ae4b5 Move case change methods to engine 2024-07-26 17:30:08 +03:00
filipp
f2ef92cdef Remove unused method 2024-07-26 17:30:08 +03:00
Filipp Vakhitov
e8e6eabe97 Fix tests
See https://youtrack.jetbrains.com/issue/VIM-3566/Unit-Tests-Test-UI-Caret-Attributes for more details.
2024-07-26 16:31:47 +03:00
Filipp Vakhitov
ef1c915264 Fix visual mode not being removed.
Steps to reproduce:
1. Select some text inside diff editor
2. Open 'Find in Files' window and search for file
3. Open the found file

Result: current mode is VISUAL
2024-07-26 14:24:01 +03:00
Filipp Vakhitov
a5e2168f7f Fix(VIM-3540): Caret icon is stuck in incorrect mode when having two or more simultaneous instances running
Some of the text input fields where Vim should not work at all had block carets.
It did not happen before, because previously we had a unique VimStateMachine for each editor and for newly created editors it was in INSERT mode. And we did call the updateSecondaryCaretsVisualAttributes method for editors that have nothing to do with Vim, but because of the INSERT mode it was looking OK.
However, now the VimStateMachine is global, and we can't rely on local INSERT anymore.
This commit forbids updating caret visual attributes for editors that do not support Vim.

NOTE: `isIdeaVimDisabledHere` is broken during editor creation handling, it always returns true. However, we do not trigger carets redraws on editor creation and do it on focus events, so it should work.
2024-07-26 00:44:35 +03:00
Filipp Vakhitov
83e5470b3a Fix(VIM-3563): Can't exit insert mode in Readonly File 2024-07-25 22:54:39 +03:00
dependabot[bot]
c446de8979 Bump org.jetbrains.kotlin:kotlin-stdlib from 1.9.24 to 1.9.25
Bumps [org.jetbrains.kotlin:kotlin-stdlib](https://github.com/JetBrains/kotlin) from 1.9.24 to 1.9.25.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/v1.9.25/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.24...v1.9.25)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-stdlib
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-24 19:02:14 +03:00
Matt Ellis
13426915f4 Remove special case handling for macro recording 2024-07-23 23:08:24 +03:00
Matt Ellis
d766c3b8ee Add test for nested insert literal action 2024-07-23 23:08:24 +03:00
Matt Ellis
844bc01537 Make functions protected 2024-07-23 23:08:24 +03:00
Matt Ellis
3d2d32b022 Move special case code to actions 2024-07-23 23:08:24 +03:00
Matt Ellis
a8677d3dd7 Refactor expected argument type
We no longer need to track a previous fallback argument type, since we don't support nested commands inside a command builder. We can just return the current argument type, or its fallback
2024-07-23 23:08:24 +03:00
Matt Ellis
7217fdf734 Remove unused special case check
This was to handle nested commands, e.g. inserting a digraph inside a search `d/foo<C-K>OK<CR>`. The command line now has its own command builder, so this check is no longer needed
2024-07-23 23:08:24 +03:00
Matt Ellis
0c867b3869 Make some CommandBuilder properties immutable 2024-07-23 23:08:24 +03:00
Matt Ellis
b3bcab4336 Refactor DigraphResult to sealed classes 2024-07-23 23:08:24 +03:00
Matt Ellis
fe1b48a9b3 Refactor digraph prompt handling 2024-07-23 23:08:24 +03:00
Matt Ellis
b5a0862520 The last command part is the current one 2024-07-23 23:08:24 +03:00
Matt Ellis
babc1f54e5 Return the last valid register if it exists
The last command part is not guaranteed to be a "select register" part. The user might have selected a register then typed an operator, and we might be waiting for a motion.
2024-07-23 23:08:24 +03:00
Matt Ellis
32b910a65b Remove unused functions and properties 2024-07-23 23:08:24 +03:00
Filipp Vakhitov
2aa71a0008 Add "Dracula" theme to mode widget 2024-07-23 19:36:07 +03:00
Filipp Vakhitov
c2c0c2aba2 Fix(VIM-3552): Undo undoes paste and insert in one keypress 2024-07-22 23:08:56 +03:00
Alex Plate
6a10cf5e0d Update the configuration for PY UI tests 2024-07-22 10:08:56 +03:00
Alex Plate
90474a3a4f Fix the mouse button issue 2024-07-19 20:25:45 +03:00
Alex Plate
be43f74bc6 Get rid of deprecated execute method in UI tests 2024-07-19 19:36:11 +03:00
Alex Plate
5916c42cd1 Disable cache for gradle UI tests 2024-07-19 18:52:58 +03:00
Alex Plate
a43c7ece32 Remove deprecated execute function in UI tests 2024-07-19 18:37:14 +03:00
Alex Plate
40c1070b1a Update path to logs in UI tests one more time 2024-07-19 17:52:54 +03:00
Alex Plate
75ccdb2a4d Update qodana after moving the vimscript to the engine 2024-07-19 17:47:49 +03:00
Alex Plate
3de7b0ca78 Disable configuration cache for gradle release task 2024-07-19 17:43:26 +03:00
Alex Plate
448e32a6cc Update AceJump dependency to the latest one 2024-07-19 17:27:02 +03:00
Alex Plate
4a85058ba2 Start using kotlin 2.0
Originally this is needed to update the dependency on AceJump, which uses the kotlin 2.0 compiler, and its classes are not compatible with the old compiler
2024-07-19 17:13:59 +03:00
filipp
7e28deb328 Better logic
In case that user will unsubscribe via setting UI and won't call IdeaVim's action
2024-07-19 16:29:15 +03:00
filipp
f3767b53b7 Subscribe IDE EAP users to IdeaVim EAP 2024-07-19 16:12:33 +03:00
Alex Plate
1026e27e64 Bring back the old version of AceJump dependency because the new one breaks the compilation 2024-07-19 14:58:20 +03:00
Alex Plate
18d653a9ae Update AceJump dependency to the latest one 2024-07-19 14:32:11 +03:00
Alex Plate
fcf4b44443 Save the whole sandbox after the UI test 2024-07-19 09:35:39 +03:00
Alex Plate
907e44b1d7 Change the gradle task for the UI tests 2024-07-19 09:05:11 +03:00
Alex Plate
6c9b39a623 Update gradle plugin and remote robot versions 2024-07-18 17:08:16 +03:00
dependabot[bot]
a3cb093b42 Bump org.jetbrains.intellij.platform from 2.0.0-beta8 to 2.0.0-beta9
Bumps org.jetbrains.intellij.platform from 2.0.0-beta8 to 2.0.0-beta9.

---
updated-dependencies:
- dependency-name: org.jetbrains.intellij.platform
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-17 15:50:17 +00:00
dependabot[bot]
524e854c61 Bump com.google.devtools.ksp:symbol-processing-api
Bumps [com.google.devtools.ksp:symbol-processing-api](https://github.com/google/ksp) from 2.0.0-1.0.22 to 2.0.0-1.0.23.
- [Release notes](https://github.com/google/ksp/releases)
- [Commits](https://github.com/google/ksp/compare/2.0.0-1.0.22...2.0.0-1.0.23)

---
updated-dependencies:
- dependency-name: com.google.devtools.ksp:symbol-processing-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-17 18:49:49 +03:00
Filipp Vakhitov
5588c27037 Attempt to fix VIM-3540
I'm not sure what causes the issue, but everything was working when we were updating visual attributes per each caret and... let update them per each caret
2024-07-17 14:45:50 +03:00
Filipp Vakhitov
90d36eea98 Make myInputInterceptor private 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
f4414de86c Safer getCurrentModalInput() 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
d46102ccaf Remove deprecated method from vim-engine 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
82347f5f0d Fix surround 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
c594f28acb Fix compilation 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
bf6517e58f Add comment 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
aec2f4c435 Remove isCancel argument
It was not used anywhere
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
8f905758d5 Remove typing in ModalInput
Typing is more suitable for command lines than a modal input, and most likely it should be used instead
It is still possible to support typing by properly implementing the handleKey method
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
80cc236f48 Remove modal input on click 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
e432a02a45 Remove confirmChoice method
It used secondary loop
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
d7894fa7f4 ProcessSubstituteCommand refactoring part 9
Finally, moved confirmation dialog to modal input
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
853d7032f0 ProcessSubstituteCommand refactoring part 8 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
5f9f57e1c0 ProcessSubstituteCommand refactoring part 7 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
f4381c8216 ProcessSubstituteCommand refactoring part 6 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
20eee7cae7 ProcessSubstituteCommand refactoring part 5 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
33392c2148 ProcessSubstituteCommand refactoring part 4 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
bb67564fbe ProcessSubstituteCommand refactoring part 3 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
61ccbcd788 ProcessSubstituteCommand refactoring part 2
We do not need the `doAll` condition (because the next code block with `doAll` adjusts line and column for the next match to be correct),
And the line2 can't be >= editor.lineCount()
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
1dbaa3be6d ProcessSubstituteCommand refactoring part 1 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
872bc22830 Remove shouldRecord from KeyConsumer
shouldRecord value was only updated in ModeInputConsumer when the key was not handled. But when the key is not handled, it is not passed to finishedCommandPreparation and the shouldRecord value is not used
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
ce23ed814c Move command line key handling logic to KeyConsumer 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
82cd534756 Remove startExEntry method
We may create a command line via the VimCommandLineService and forget (or do not know) about calling startExEntry necessary. So we move its logic inside the creation of the command line
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
673809d6b9 Move lastCommand to CmdFilterCommand 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
cdbaf73b1e Remove unused fields from VimProcessGroup 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
7f911b7e72 Fix VisualAreaMatcher
It looks for the last selection info in all conditions, not the ones that were specified
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
c03a2dfe7e Add support for finishOn in readInputAndProcess 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
75935ce4d1 Add readInputAndProcess to replace inputString and save us from using secondary loop 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
f0b203409e Replace cancelExEntry with close method in VimCommandLine 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
b3b369eb59 Remove unnecessary write action
`insertRegister` is self synchronized
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
96072982cf Replace deprecated inputString() with ModalInput 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
de016fc445 Always register shortcuts
No matter what kind of panel we have, we want VimShortcutKeyAction to be triggered and pass the shortcut keys (e.g. CTRL-C to close panel)
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
554d9b5f7b Create a Wrapper for ExEntryPanel
ExEntryPanel already extends JPanel, so we need a wrapper to make it extend VimModalInputBase
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
149edefad5 Delegate handling closing keystrokes to implementations
Sometimes it may mean aborting interception (CTRL-C), sometimes finishing it (CR)
2024-07-17 13:30:54 +03:00
Filipp Vakhitov
52d3840c83 Rename argument 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
793677d4fd Add typeText() method to VimModalInput 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
2376ee4877 Fix tests 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
68dcab6262 Introduce ModalInput to get rid of Swing's secondary loop 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
f38fd3512c Fix passing key to editor when the output panel is scrolled to its end 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
e515278ba3 Add label field to VimOutputPanel 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
9cc69e41ee Add KeyHandling logic to VimOutputPanel 2024-07-17 13:30:54 +03:00
Filipp Vakhitov
2109ff235c Fix tests 2024-07-16 20:32:11 +03:00
Filipp Vakhitov
d6910aa81d Fix(VIM-3538): Can't select a block in {} when the cursor is on commented Line 2024-07-16 19:23:43 +03:00
Filipp Vakhitov
8369391902 Fix(VIM-3530): "Yank inner block"/"Change inner block" uses wrong block 2024-07-16 18:47:58 +03:00
Alex Plate
f336807498 Bring back IU for copilot compatibility testing 2024-07-16 09:09:35 +03:00
Alex Plate
14ba5d7126 Convert Troubleshooter field to function to avoid early initialization 2024-07-15 13:54:20 +03:00
Alex Plate
288394d25f Update the notification text for undo 2024-07-15 13:19:03 +03:00
Alex Plate
fb08b5fd65 Prepare to changing the default value of the undo
A notification added to give the information about what happened

Related: VIM-547
2024-07-15 13:16:01 +03:00
Alex Plate
3465e11c3a Initialize the injector in multiple entry points of IntelliJ
Initially, injector was initialized in VimPlugin, assuming that almost every interaction with the plugin goes through it. However, with the plugin evolution, this class starts to be less used.
As IJ doesn't have any single entry point for the plugins, we initialize it in multiple places.
However, the architecture where the plugin might be not initizlied is considered as a bad acrhitecture and should be reviewed.

Related ticket: VIM-3369
2024-07-12 15:09:18 +03:00
Matt Ellis
e07a16863e Show possible IDs based on the action's shortcuts
Fixes VIM-3499
2024-07-12 15:09:02 +03:00
kun-codes
64f7532510 added rename functionality 2024-07-12 09:53:07 +03:00
kun-codes
dd892e77fb added paste functionality 2024-07-12 09:53:07 +03:00
kun-codes
65aeeba521 added copy functionality 2024-07-12 09:53:07 +03:00
dependabot[bot]
ca3e56d0d6 Bump org.mockito.kotlin:mockito-kotlin from 5.3.1 to 5.4.0
Bumps [org.mockito.kotlin:mockito-kotlin](https://github.com/mockito/mockito-kotlin) from 5.3.1 to 5.4.0.
- [Release notes](https://github.com/mockito/mockito-kotlin/releases)
- [Commits](https://github.com/mockito/mockito-kotlin/compare/5.3.1...5.4.0)

---
updated-dependencies:
- dependency-name: org.mockito.kotlin:mockito-kotlin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-10 18:21:34 +03:00
dependabot[bot]
bcbfb0dc32 Bump org.jetbrains.changelog from 2.2.0 to 2.2.1
Bumps org.jetbrains.changelog from 2.2.0 to 2.2.1.

---
updated-dependencies:
- dependency-name: org.jetbrains.changelog
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-10 18:21:20 +03:00
Alex Plate
46a4a10e63 Reformat code in tests 2024-07-10 10:06:27 +03:00
Alex Plate
d5bddd077f Disable Gradle configuration cache in CI tests
With gradle plugin 2.0 this cache fails on GitHub Actions. This solution is temporlly and generally the `integrationsTest` should be migrated from `doLast` to some other task approach
2024-07-10 09:02:26 +03:00
Alex Plate
e22fd263cc Test compatibility of copilot on PyCharm
The current execution reports the absence of python plugin. Probably there is a dependency on PyCharm there
2024-07-09 16:35:32 +03:00
Alex Plate
f3902d0ae0 Convert property to function to avoid extra service creation 2024-07-09 16:07:57 +03:00
Alex Plate
f9213ee45d Mute some tests that depend on oldundo option 2024-07-09 15:46:41 +03:00
Alex Plate
281bc2573e Bring back some methods to preserve the compatibility with other plugins 2024-07-09 15:38:50 +03:00
Alex Plate
ae8c7f6bfa Revert "Remove old compatibility layers"
This reverts commit 6cee04a4be.
2024-07-09 15:32:39 +03:00
filipp
c0419d6018 Fix project leak 2024-07-08 13:09:08 +03:00
filipp
ea98e50f65 Sometimes safety is a bad thing 2024-07-08 13:09:08 +03:00
filipp
168174e383 Add missing resetOpPending to KeyHandler.reset 2024-07-08 13:09:08 +03:00
filipp
53cd4e1b88 Remove KeyHandlerStateResetter
It's an oneliner that we can live without
2024-07-08 13:09:08 +03:00
filipp
27e3561bb8 Safer VimListenersNotifier 2024-07-08 13:09:08 +03:00
filipp
9bb9cb13e3 Fix possible bug in the ExOutputModel.show() method and add documentation 2024-07-08 13:09:08 +03:00
filipp
16455f7241 Remove the update() method from VimOutputPanel 2024-07-08 13:09:08 +03:00
filipp
82225aa519 Use new interface instead of the old one 2024-07-08 13:09:08 +03:00
Filipp Vakhitov
5f2baefc6c Introduce new interface for VimOutputPanel to support the output panel in Fleet
Why is the old interface bad?
- it is not obvious. You cannot create a new panel or check if it is already created. Only "getOrCreate" it
- output panel is bound to editor while in Vim it is global
- we have the `isActive` field and the `clear()` method at the same time, because interface implies that you store the same instance of the panel and reactivate it for each output and I don't like it. We also can forget to call `clear()` after reusing panel
- we cannot "build" output before showing to make the panel more efficient. With multiple carets we can only cal `output(oldText + newText)` for each caret, and it is bad. (imagine we have global command with a lot of matches and for each time we will need to call the `output(oldText + newText)`)
- the `output()` method shows panel, activates it and updates it
- there are more things that I do not like, but the points above should be already enough
2024-07-08 13:09:08 +03:00
Filipp Vakhitov
cedcf39723 Fix(VIM-3461): Focus regression 2024-07-08 13:09:08 +03:00
Filipp Vakhitov
4925d9aada Remove ideaglobalmode option
Mode is global now
2024-07-08 13:09:08 +03:00
Filipp Vakhitov
f3e6df32d0 Fix(VIM-3462): IdeaVim not responsive occasionally
This bug was caused by two reasons:
1. KeyHandler state is not longer per-editor and we can't reset it on editor creation
2. We do not need to do such things on editor creation. What actually matters is focusing the editor
2024-07-08 13:09:08 +03:00
Filipp Vakhitov
5aaa8752af Move to new API from deprecated one 2024-07-08 13:09:08 +03:00
Filipp Vakhitov
a1d214316c Make VimStateMachine global
It is global in Vim
2024-07-08 13:09:08 +03:00
filipp
8a1e3eb066 Move toggleInsertOverwrite() to VimEditor
If we execute it from VimStateMachine directly, then mode change listeners are not notified
2024-07-08 13:09:08 +03:00
216 changed files with 3353 additions and 2304 deletions

View File

@@ -27,7 +27,7 @@ jobs:
settings-path: ${{ github.workspace }} # location for the settings.xml file
- name: Run tests
run: ./gradlew integrationsTest
run: ./gradlew --no-configuration-cache integrationsTest
env:
YOUTRACK_TOKEN: ${{ secrets.YOUTRACK_TOKEN }}
GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -16,14 +16,14 @@ jobs:
java-version: 17
- name: Setup FFmpeg
run: brew install ffmpeg
- name: Setup Gradle
uses: gradle/gradle-build-action@v2.4.2
# - 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 -Doctopus.handler=false > build/reports/idea.log &
gradle --no-configuration-cache runIdeForUiTests -Doctopus.handler=false > build/reports/idea.log &
- name: Wait for Idea started
uses: jtalk/url-health-check-action@v3
with:
@@ -37,7 +37,7 @@ jobs:
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
run: mv build/idea-sandbox/IC-2024.1.2/log_runIdeForUiTests idea-sandbox-log
- name: Save report
if: always()
uses: actions/upload-artifact@v4
@@ -46,7 +46,7 @@ jobs:
path: |
build/reports
tests/ui-ij-tests/build/reports
sandbox-idea-log
idea-sandbox-log
# build-for-ui-test-linux:
# runs-on: ubuntu-latest
# steps:
@@ -78,4 +78,4 @@ jobs:
# with:
# name: ui-test-fails-report-linux
# path: |
# ui-test-example/build/reports
# ui-test-example/build/reports

View File

@@ -19,14 +19,14 @@ jobs:
python-version: '3.10'
- name: Setup FFmpeg
run: brew install ffmpeg
- name: Setup Gradle
uses: gradle/gradle-build-action@v2.4.2
# - 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 &
gradle --no-configuration-cache :runIdeForUiTests -PideaType=PC > build/reports/idea.log &
- name: Wait for Idea started
uses: jtalk/url-health-check-action@v3
with:
@@ -40,7 +40,7 @@ jobs:
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
run: mv build/idea-sandbox/PC-2024.1.2/log_runIdeForUiTests idea-sandbox-log
- name: Save report
if: always()
uses: actions/upload-artifact@v4
@@ -49,4 +49,4 @@ jobs:
path: |
build/reports
tests/ui-py-tests/build/reports
sandbox-idea-log
idea-sandbox-log

View File

@@ -16,14 +16,14 @@ jobs:
java-version: 17
- name: Setup FFmpeg
run: brew install ffmpeg
- name: Setup Gradle
uses: gradle/gradle-build-action@v2.4.2
# - 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 > build/reports/idea.log &
gradle --no-configuration-cache runIdeForUiTests > build/reports/idea.log &
- name: Wait for Idea started
uses: jtalk/url-health-check-action@v3
with:
@@ -37,7 +37,7 @@ jobs:
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
run: mv build/idea-sandbox/IC-2024.1.2/log_runIdeForUiTests idea-sandbox-log
- name: Save report
if: always()
uses: actions/upload-artifact@v4
@@ -46,7 +46,7 @@ jobs:
path: |
build/reports
tests/ui-ij-tests/build/reports
sandbox-idea-log
idea-sandbox-log
# build-for-ui-test-linux:
# runs-on: ubuntu-latest
# steps:
@@ -78,4 +78,4 @@ jobs:
# with:
# name: ui-test-fails-report-linux
# path: |
# ui-test-example/build/reports
# ui-test-example/build/reports

3
.gitignore vendored
View File

@@ -1,5 +1,6 @@
*.swp
/.gradle/
/.intellijPlatform/
/.idea/
!/.idea/scopes
@@ -32,3 +33,5 @@ vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated
# Created by github automation
settings.xml
.kotlin

View File

@@ -12,7 +12,7 @@
<option name="taskNames">
<list>
<option value="check" />
<option value="runPluginVerifier" />
<option value="verifyPlugin" />
</list>
</option>
<option name="vmOptions" value="" />
@@ -20,6 +20,7 @@
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>

View File

@@ -37,7 +37,7 @@ object Compatibility : IdeaVimBuildType({
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
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.github.copilot' [latest-IU] -team-city
# java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.github.copilot' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.github.dankinsoid.multicursor' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.joshestein.ideavim-quickscope' [latest-IU] -team-city
""".trimIndent()

View File

@@ -22,7 +22,7 @@ object PluginVerifier : IdeaVimBuildType({
steps {
gradle {
tasks = "clean runPluginVerifier"
tasks = "clean verifyPlugin"
buildFile = ""
enableStacktrace = true
}

View File

@@ -144,6 +144,7 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
gradle {
name = "Run Integrations"
tasks = "releaseActions"
gradleParams = "--no-configuration-cache"
}
// gradle {
// name = "Slack Notification"

View File

@@ -222,13 +222,13 @@ Ex commands or via `:map` command mappings:
* Execute an action by `{action_id}`. Works from Ex command line.
* Please don't use `:action` in mappings. Use `<Action>` instead.
### Finding action ids:
### Finding action IDs:
* IJ provides `IdeaVim: track action Ids` command to show the id of the executed actions.
* IJ provides `IdeaVim: track action IDs` command to show the id of the executed actions.
This command can be found in "Search everywhere" (double `shift`).
<details>
<summary><strong>"Track action Ids" Details</strong> (click to see)</summary>
<summary><strong>"Track action IDs" Details</strong> (click to see)</summary>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="assets/readme/track_action_dark.gif">
<img src="assets/readme/track_action_light.gif" alt="track action ids"/>
@@ -369,6 +369,8 @@ is the full list of synonyms.
- Fancy constants for [undolevels](https://vimhelp.org/options.txt.html#%27undolevels%27):
> The local value is set to -123456 when the global value is to be used.
- Vi (not Vim) is a POSIX standard, and [has a spec](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/vi.html)! Vim is mostly POSIX compliant when Vi compatibility is selected with the `'compatible'` option, but there are still some differences that can be changed with `'copoptions'`. The spec is interesting because it documents the behaviour of different commands in a stricter style than the user documentation, describing the current line and column after the command, for example. [More details can be found by reading `:help posix`](https://vimhelp.org/vi_diff.txt.html#posix).
License
-------

View File

@@ -8,7 +8,7 @@
plugins {
kotlin("jvm")
kotlin("plugin.serialization") version "1.9.22"
kotlin("plugin.serialization") version "2.0.0"
}
val kotlinxSerializationVersion: String by project
@@ -21,7 +21,7 @@ repositories {
}
dependencies {
compileOnly("com.google.devtools.ksp:symbol-processing-api:2.0.0-1.0.22")
compileOnly("com.google.devtools.ksp:symbol-processing-api:2.0.0-1.0.24")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion") {
// kotlin stdlib is provided by IJ, so there is no need to include it into the distribution
exclude("org.jetbrains.kotlin", "kotlin-stdlib")

View File

@@ -32,6 +32,8 @@ import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.RepositoryBuilder
import org.intellij.markdown.ast.getTextInNode
import org.jetbrains.changelog.Changelog
import org.jetbrains.intellij.platform.gradle.TestFrameworkType
import org.jetbrains.intellij.platform.gradle.tasks.aware.SplitModeAware
import org.kohsuke.github.GHUser
import java.net.HttpURLConnection
import java.net.URL
@@ -43,7 +45,7 @@ buildscript {
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.0")
classpath("com.github.AlexPl292:mark-down-to-slack:1.1.2")
classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
@@ -64,25 +66,23 @@ buildscript {
plugins {
java
kotlin("jvm") version "1.9.22"
kotlin("jvm") version "2.0.0"
application
id("java-test-fixtures")
id("org.jetbrains.intellij") version "1.17.3"
id("org.jetbrains.changelog") version "2.2.0"
id("org.jetbrains.intellij.platform") version "2.0.0-rc2"
id("org.jetbrains.changelog") version "2.2.1"
id("org.jetbrains.kotlinx.kover") version "0.6.1"
id("com.dorongold.task-tree") version "4.0.0"
id("com.google.devtools.ksp") version "1.9.22-1.0.17"
id("com.google.devtools.ksp") version "2.0.0-1.0.23"
}
val moduleSources by configurations.registering
// Import variables from gradle.properties file
val javaVersion: String by project
val kotlinVersion: String by project
val ideaVersion: String by project
val ideaType: String by project
val downloadIdeaSources: String by project
val instrumentPluginCode: String by project
val remoteRobotVersion: String by project
val splitModeVersion: String by project
@@ -97,7 +97,9 @@ val releaseType: String? by project
repositories {
mavenCentral()
maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
intellijPlatform {
defaultRepositories()
}
}
dependencies {
@@ -108,9 +110,27 @@ dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
compileOnly("org.jetbrains:annotations:24.1.0")
// --------- Test dependencies ----------
intellijPlatform {
// Note that it is also possible to use local("...") to compile against a locally installed IDE
// E.g. local("/Users/{user}/Applications/IntelliJ IDEA Ultimate.app")
// Or something like: intellijIdeaUltimate(ideaVersion)
create(ideaType, ideaVersion)
testImplementation(testFixtures(project(":")))
pluginVerifier()
zipSigner()
instrumentationTools()
testFramework(TestFrameworkType.Platform)
testFramework(TestFrameworkType.JUnit5)
// AceJump is an optional dependency. We use their SessionManager class to check if it's active
plugin("AceJump", "3.8.19")
plugin("com.intellij.classic.ui", "242.20224.159")
}
moduleSources(project(":vim-engine", "sourcesJarArtifacts"))
// --------- Test dependencies ----------
testApi("com.squareup.okhttp3:okhttp:4.12.0")
@@ -125,7 +145,7 @@ dependencies {
testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
testImplementation("org.mockito.kotlin:mockito-kotlin:5.3.1")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3")
@@ -133,6 +153,12 @@ dependencies {
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.3")
// Temp workaround suggested in https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-faq.html#junit5-test-framework-refers-to-junit4
// Can be removed when IJPL-159134 is fixed
// testRuntimeOnly("junit:junit:4.13.2")
testImplementation("org.junit.vintage:junit-vintage-engine:5.10.3")
// testFixturesImplementation("org.junit.vintage:junit-vintage-engine:5.10.3")
}
configurations {
@@ -172,60 +198,94 @@ tasks {
// See https://plugins.jetbrains.com/docs/intellij/using-kotlin.html#kotlin-standard-library
// For the list of bundled versions
apiVersion = "1.9"
freeCompilerArgs = listOf("-Xjvm-default=all-compatibility")
freeCompilerArgs = listOf(
"-Xjvm-default=all-compatibility",
// Needed to compile the AceJump which uses kotlin beta
// Without these two option compilation fails
"-Xskip-prerelease-check",
"-Xallow-unstable-dependencies",
)
// allWarningsAsErrors = true
}
}
compileTestKotlin {
enabled = false
kotlinOptions {
jvmTarget = javaVersion
apiVersion = "1.9"
// Needed to compile the AceJump which uses kotlin beta
// Without these two option compilation fails
freeCompilerArgs += listOf("-Xskip-prerelease-check", "-Xallow-unstable-dependencies")
// allWarningsAsErrors = true
}
}
// Note that this will run the plugin installed in the IDE specified in dependencies. To run in a different IDE, use
// a custom task (see below)
runIde {
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
}
downloadRobotServerPlugin {
version.set(remoteRobotVersion)
// Uncomment to run the plugin in a custom IDE, rather than the IDE specified as a compile target in dependencies
// Note that the version must be greater than the plugin's target version, for obvious reasons
// val runIdeCustom by intellijPlatformTesting.runIde.registering {
// type = IntelliJPlatformType.Rider
// version = "2024.1.2"
// }
// Uncomment to run the plugin in a locally installed IDE
// val runIdeLocal by intellijPlatformTesting.runIde.registering {
// localPath = file("/Users/{user}/Applications/WebStorm.app")
// }
val runIdeForUiTests by intellijPlatformTesting.runIde.registering {
task {
jvmArgumentProviders += CommandLineArgumentProvider {
listOf(
"-Drobot-server.port=8082",
"-Dide.mac.message.dialogs.as.sheets=false",
"-Djb.privacy.policy.text=<!--999.999-->",
"-Djb.consents.confirmation.enabled=false",
"-Dide.show.tips.on.startup.default.value=false",
"-Doctopus.handler=" + (System.getProperty("octopus.handler") ?: true),
)
}
}
plugins {
robotServerPlugin(remoteRobotVersion)
}
}
runIdeForUiTests {
systemProperty("robot-server.port", "8082")
systemProperty("ide.mac.message.dialogs.as.sheets", "false")
systemProperty("jb.privacy.policy.text", "<!--999.999-->")
systemProperty("jb.consents.confirmation.enabled", "false")
systemProperty("ide.show.tips.on.startup.default.value", "false")
val runIdeSplitMode by intellijPlatformTesting.runIde.registering {
splitMode = true
splitModeTarget = SplitModeAware.SplitModeTarget.FRONTEND
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
// Frontend split mode support requires 242+
// TODO: Remove this once IdeaVim targets 242, as the task will naturally use the target version to run
version.set(splitModeVersion)
}
// Add plugin open API sources to the plugin ZIP
val createOpenApiSourceJar by registering(Jar::class) {
// Java sources
from(sourceSets.main.get().java) {
include("**/com/maddyhome/idea/vim/**/*.java")
}
from(project(":vim-engine").sourceSets.main.get().java) {
include("**/com/maddyhome/idea/vim/**/*.java")
}
// Kotlin sources
from(kotlin.sourceSets.main.get().kotlin) {
include("**/com/maddyhome/idea/vim/**/*.kt")
}
from(project(":vim-engine").kotlin.sourceSets.main.get().kotlin) {
include("**/com/maddyhome/idea/vim/**/*.kt")
}
val sourcesJar by registering(Jar::class) {
dependsOn(moduleSources)
destinationDirectory.set(layout.buildDirectory.dir("libs"))
archiveClassifier.set("src")
archiveClassifier.set(DocsType.SOURCES)
from(sourceSets.main.map { it.kotlin })
from(provider {
moduleSources.map {
it.map { jarFile -> zipTree(jarFile) }
}
})
}
buildPlugin {
dependsOn(createOpenApiSourceJar)
from(createOpenApiSourceJar) { into("lib/src") }
dependsOn(sourcesJar)
from(sourcesJar) { into("lib/src") }
}
}
@@ -250,44 +310,51 @@ gradle.projectsEvaluated {
// --- Intellij plugin
intellij {
version.set(ideaVersion)
type.set(ideaType)
pluginName.set("IdeaVim")
intellijPlatform {
pluginConfiguration {
name = "IdeaVim"
changeNotes.set(
"""
Undo in IdeaVim now works like in Vim<br/>
Caret movement is no longer a separate undo step, and full insert is undoable in one step.<br/>
<a href="https://youtrack.jetbrains.com/issue/VIM-547/Undo-splits-Insert-mode-edits-into-separate-undo-chunks">Share Feedback</a>
<br/>
<br/>
<a href="https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20Fix%20versions:%20${version.get()}">Changelog</a>
""".trimIndent()
)
updateSinceUntilBuild.set(false)
ideaVersion {
// Let the Gradle plugin set the since-build version. It defaults to the version of the IDE we're building against
// specified as two components, `{branch}.{build}` (e.g., "241.15989"). There is no third component specified.
// The until-build version defaults to `{branch}.*`, but we want to support _all_ future versions, so we set it
// with a null provider (the provider is important).
// By letting the Gradle plugin handle this, the Plugin DevKit IntelliJ plugin cannot help us with the "Usage of
// IntelliJ API not available in older IDEs" inspection. However, since our since-build is the version we compile
// against, we can never get an API that's newer - it would be an unresolved symbol.
untilBuild.set(provider { null })
}
}
downloadSources.set(downloadIdeaSources.toBoolean())
instrumentCode.set(instrumentPluginCode.toBoolean())
intellijRepository.set("https://www.jetbrains.com/intellij-repository")
plugins.set(listOf("AceJump:3.8.11"))
}
tasks {
publishPlugin {
publishing {
channels.set(publishChannels.split(","))
token.set(publishToken)
}
signPlugin {
signing {
certificateChain.set(providers.environmentVariable("CERTIFICATE_CHAIN"))
privateKey.set(providers.environmentVariable("PRIVATE_KEY"))
password.set(providers.environmentVariable("PRIVATE_KEY_PASSWORD"))
}
runPluginVerifier {
downloadDir.set("${project.buildDir}/pluginVerifier/ides")
teamCityOutputFormat.set(true)
verifyPlugin {
teamCityOutputFormat = true
ides {
recommended()
}
}
patchPluginXml {
// Don't forget to update plugin.xml
sinceBuild.set("241.15989.150")
changeNotes.set(
"""<a href="https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20Fix%20versions:%20${version.get()}">Changelog</a>"""
)
}
instrumentCode.set(instrumentPluginCode.toBoolean())
}
ksp {
@@ -891,12 +958,12 @@ fun changes(): List<Change> {
println("Start changes processing")
for (message in messages) {
println("Processing '$message'...")
val lowercaseMessage = message.toLowerCase()
val lowercaseMessage = message.lowercase()
val regex = "^fix\\((vim-\\d+)\\):".toRegex()
val findResult = regex.find(lowercaseMessage)
if (findResult != null) {
println("Message matches")
val value = findResult.groups[1]!!.value.toUpperCase()
val value = findResult.groups[1]!!.value.uppercase()
val shortMessage = message.drop(findResult.range.last + 1).trim()
newFixes += Change(value, shortMessage)
} else {

View File

@@ -5,9 +5,9 @@ Using actions from external plugins is the same, as tracking and reusing any IDE
1. Install the plugin via Marketplace
2. Enable action tracking. You can enable it by one of the following ways:
* Execute `:set trackactionids` ex command or just `:set tai`
* Open the "Find actions" window by pressing `Ctrl-Shift-A` and search for "Track Action Ids" to find the toggle that enables and disables action tracking
* Open the "Find actions" window by pressing `Ctrl-Shift-A` and search for "Track Action IDs" to find the toggle that enables and disables action tracking
3. Execute the plugin action the way intended by plugin author "See Edit menu or use ⇧ + ⌥ + U / Shift + Alt + U" or just find the `Toggle Camel Case` action in the "Find actions" window (`Ctrl-Shift-A`). If you action tracking is on, you will see this notification in your right bottom corner:
<img alt="Notification" src="images/action-id-notification.png"/>
4. Copy the action id from the notification to create the following mapping in your .ideavimrc
```map <leader>t <Action>(de.netnexus.CamelCasePlugin.ToggleCamelCase)```
```map <leader>t <Action>(de.netnexus.CamelCasePlugin.ToggleCamelCase)```

View File

@@ -16,14 +16,13 @@
# https://data.services.jetbrains.com/products?code=IC
# Maven releases are here: https://www.jetbrains.com/intellij-repository/releases
# And snapshots: https://www.jetbrains.com/intellij-repository/snapshots
ideaVersion=2024.1.1
ideaVersion=2024.2
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
ideaType=IC
downloadIdeaSources=true
instrumentPluginCode=true
version=chylex-36
version=chylex-39
javaVersion=17
remoteRobotVersion=0.11.22
remoteRobotVersion=0.11.23
antlrVersion=4.10.1
# [VERSION UPDATE] 2024.2 - remove when IdeaVim targets 2024.2
@@ -35,7 +34,7 @@ splitModeVersion=242-EAP-SNAPSHOT
# Please don't forget to update kotlin version in buildscript section
# Also update kotlinxSerializationVersion version
kotlinVersion=1.9.22
kotlinVersion=2.0.0
publishToken=token
publishChannels=eap

View File

@@ -19,9 +19,9 @@ exclude:
- src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/JavaText.kt
- src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/LoremText.kt
- src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/SimpleText.kt
- src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated
- src/main/java/com/maddyhome/idea/vim/package-info.java
- vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated
- vim-engine/src/main/java/com/maddyhome/idea/vim/parser/generated
- src/main/java/com/maddyhome/idea/vim/group/SearchGroup.java
- tests/ui-fixtures
dependencyIgnores:

View File

@@ -20,7 +20,7 @@ repositories {
}
dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.24")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.25")
implementation("io.ktor:ktor-client-core:2.3.12")
implementation("io.ktor:ktor-client-cio:2.3.10")

View File

@@ -8,14 +8,19 @@
package com.maddyhome.idea.vim
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManagerListener
import com.intellij.openapi.startup.ProjectActivity
import com.intellij.openapi.updateSettings.impl.UpdateSettings
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.initInjector
import com.maddyhome.idea.vim.ui.JoinEap
import com.maddyhome.idea.vim.ui.JoinEap.EAP_LINK
/**
* @author Alex Plate
@@ -28,6 +33,11 @@ internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
if (firstInitializationOccurred) return
firstInitializationOccurred = true
if (!VimPlugin.getVimState().wasSubscibedToEAPAutomatically && ApplicationManager.getApplication().isEAP && !JoinEap.eapActive()) {
VimPlugin.getVimState().wasSubscibedToEAPAutomatically = true
UpdateSettings.getInstance().storedPluginHosts += EAP_LINK
}
// This code should be executed once
VimPlugin.getInstance().initialize()
}
@@ -36,6 +46,7 @@ internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
// This is a temporal workaround for VIM-2487
internal class PyNotebooksCloseWorkaround : ProjectManagerListener {
override fun projectClosingBeforeSave(project: Project) {
initInjector()
// TODO: Confirm context in CWM scenario
if (injector.globalIjOptions().closenotebooks) {
injector.editorGroup.getEditors().forEach { vimEditor ->

View File

@@ -37,6 +37,7 @@ import com.maddyhome.idea.vim.group.visual.VisualMotionGroup;
import com.maddyhome.idea.vim.helper.MacKeyRepeat;
import com.maddyhome.idea.vim.listener.VimListenerManager;
import com.maddyhome.idea.vim.newapi.IjVimInjector;
import com.maddyhome.idea.vim.newapi.IjVimInjectorKt;
import com.maddyhome.idea.vim.newapi.IjVimSearchGroup;
import com.maddyhome.idea.vim.ui.StatusBarIconFactory;
import com.maddyhome.idea.vim.vimscript.services.VariableService;
@@ -67,7 +68,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
private static final Logger LOG = Logger.getInstance(VimPlugin.class);
static {
VimInjectorKt.setInjector(new IjVimInjector());
IjVimInjectorKt.initInjector();
}
private final @NotNull VimState state = new VimState();

View File

@@ -18,7 +18,7 @@ import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
internal class VimProjectService(val project: Project) : Disposable {
override fun dispose() {
// Not sure if this is a best solution
ExEntryPanel.getInstance().editor = null
ExEntryPanel.getInstance().setEditor(null)
}
companion object {

View File

@@ -0,0 +1,52 @@
package com.maddyhome.idea.vim.action
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.command.UndoConfirmationPolicy
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.project.DumbAwareAction
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
class VimRunLastMacroInOpenFiles : DumbAwareAction() {
override fun update(e: AnActionEvent) {
val lastRegister = injector.macro.lastRegister
val isEnabled = lastRegister != 0.toChar()
e.presentation.isEnabled = isEnabled
e.presentation.text = if (isEnabled) "Run Macro '${lastRegister}' in Open Files" else "Run Last Macro in Open Files"
}
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.EDT
}
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
val fileEditorManager = FileEditorManagerEx.getInstanceExIfCreated(project) ?: return
val editors = fileEditorManager.allEditors.filterIsInstance<TextEditor>()
WriteCommandAction.writeCommandAction(project)
.withName(e.presentation.text)
.withGlobalUndo()
.withUndoConfirmationPolicy(UndoConfirmationPolicy.REQUEST_CONFIRMATION)
.run<RuntimeException> {
val reg = injector.macro.lastRegister
for (editor in editors) {
fileEditorManager.openFile(editor.file, true)
val vimEditor = editor.editor.vim
vimEditor.mode = Mode.NORMAL()
KeyHandler.getInstance().reset(vimEditor)
injector.macro.playbackRegister(vimEditor, IjEditorExecutionContext(e.dataContext), reg, 1)
}
}
}
}

View File

@@ -43,6 +43,7 @@ import com.maddyhome.idea.vim.key.ShortcutOwnerInfo
import com.maddyhome.idea.vim.listener.AceJumpService
import com.maddyhome.idea.vim.listener.AppCodeTemplates.appCodeTemplateCaptured
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.initInjector
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
import com.maddyhome.idea.vim.ui.ex.ExTextField
@@ -61,10 +62,13 @@ import javax.swing.KeyStroke
* way to get ideavim keys for this plugin. See VIM-3085
*/
class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
init {
initInjector()
}
private val traceTime: Boolean
get() {
// Make sure the injector is initialized
VimPlugin.getInstance()
return injector.globalOptions().ideatracetime
}
@@ -257,7 +261,7 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
private fun getEditor(e: AnActionEvent): Editor? {
return e.getData(PlatformDataKeys.EDITOR)
?: if (e.getData(PlatformDataKeys.CONTEXT_COMPONENT) is ExTextField) {
ExEntryPanel.getInstance().editor
ExEntryPanel.getInstance().ijEditor
} else {
null
}

View File

@@ -28,7 +28,7 @@ import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.helper.inRepeatMode
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
@@ -102,7 +102,7 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
val argument = cmd.argument ?: return false
if (!editor.vimStateMachine.isDotRepeatInProgress) {
if (!editor.inRepeatMode) {
argumentCaptured = argument
}
val range = getMotionRange(editor, context, argument, operatorArguments)

View File

@@ -17,7 +17,6 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.ij
@CommandOrMotion(keys = ["."], modes = [Mode.NORMAL])
@@ -25,7 +24,7 @@ internal class RepeatChangeAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_WRITABLE
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
val state = editor.vimStateMachine
val state = injector.vimState
val lastCommand = VimRepeater.lastChangeCommand
if (lastCommand == null && Extension.lastExtensionHandler == null) return false

View File

@@ -79,7 +79,7 @@ internal class VimQuickJavaDoc : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_READONLY
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
injector.actionExecutor.executeAction(IdeActions.ACTION_QUICK_JAVADOC, context)
injector.actionExecutor.executeAction(editor, IdeActions.ACTION_QUICK_JAVADOC, context)
return true
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.command
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.state.VimStateMachine
/**
* COMPATIBILITY-LAYER: Additional class
* Please see: https://jb.gg/zo8n0r
*/
@Deprecated("Use `injector.vimState`")
class CommandState(private val machine: VimStateMachine) {
val mode: Mode
get() {
val myMode = machine.mode
return when (myMode) {
is com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> Mode.CMD_LINE
com.maddyhome.idea.vim.state.mode.Mode.INSERT -> Mode.INSERT
is com.maddyhome.idea.vim.state.mode.Mode.NORMAL -> Mode.COMMAND
is com.maddyhome.idea.vim.state.mode.Mode.OP_PENDING -> Mode.OP_PENDING
com.maddyhome.idea.vim.state.mode.Mode.REPLACE -> Mode.REPLACE
is com.maddyhome.idea.vim.state.mode.Mode.SELECT -> Mode.SELECT
is com.maddyhome.idea.vim.state.mode.Mode.VISUAL -> Mode.VISUAL
}
}
@Deprecated("Use `KeyHandler.keyHandlerState.commandBuilder", ReplaceWith(
"KeyHandler.getInstance().keyHandlerState.commandBuilder",
"com.maddyhome.idea.vim.KeyHandler"
)
)
val commandBuilder: CommandBuilder
get() = KeyHandler.getInstance().keyHandlerState.commandBuilder
@Deprecated("Use `KeyHandler.keyHandlerState.mappingState", ReplaceWith(
"KeyHandler.getInstance().keyHandlerState.mappingState",
"com.maddyhome.idea.vim.KeyHandler"
)
)
val mappingState: MappingState
get() = KeyHandler.getInstance().keyHandlerState.mappingState
enum class Mode {
// Basic modes
COMMAND, VISUAL, SELECT, INSERT, CMD_LINE, /*EX*/
// Additional modes
OP_PENDING, REPLACE /*, VISUAL_REPLACE*/, INSERT_NORMAL, INSERT_VISUAL, INSERT_SELECT
}
enum class SubMode {
NONE, VISUAL_CHARACTER, VISUAL_LINE, VISUAL_BLOCK
}
companion object {
@JvmStatic
@Deprecated("Use `injector.vimState`")
fun getInstance(editor: Editor): CommandState {
return CommandState(injector.vimState)
}
}
}

View File

@@ -12,17 +12,17 @@ import com.intellij.application.options.CodeStyle
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.codeStyle.CommonCodeStyleSettings.IndentOptions
import com.maddyhome.idea.vim.api.VimIndentConfig
internal class IndentConfig private constructor(indentOptions: IndentOptions) {
internal class IndentConfig private constructor(indentOptions: IndentOptions): VimIndentConfig {
private val indentSize = indentOptions.INDENT_SIZE
private val tabSize = indentOptions.TAB_SIZE
private val isUseTabs = indentOptions.USE_TAB_CHARACTER
fun getTotalIndent(count: Int): Int = indentSize * count
override fun getIndentSize(depth: Int): Int = indentSize * depth
override fun createIndentByDepth(depth: Int): String = createIndentBySize(getIndentSize(depth))
fun createIndentByCount(count: Int): String = createIndentBySize(getTotalIndent(count))
fun createIndentBySize(size: Int): String {
override fun createIndentBySize(size: Int): String {
val tabCount: Int
val spaceCount: Int
if (isUseTabs) {

View File

@@ -18,6 +18,8 @@ import kotlin.reflect.KProperty
internal class VimState {
var isIdeaJoinNotified by StateProperty("idea-join")
var isIdeaPutNotified by StateProperty("idea-put")
var isNewUndoNotified by StateProperty("idea-undo")
var wasSubscibedToEAPAutomatically by StateProperty("was-automatically-subscribed-to-eap")
fun readData(element: Element) {
val notifications = element.getChild("notifications")

View File

@@ -9,51 +9,124 @@ package com.maddyhome.idea.vim.ex
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.api.VimExOutputPanel
import com.maddyhome.idea.vim.api.VimOutputPanelBase
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.vimExOutput
import com.maddyhome.idea.vim.ui.ExOutputPanel
import java.lang.ref.WeakReference
import javax.swing.KeyStroke
// TODO: We need a nicer way to handle output, especially wrt testing, appending + clearing
class ExOutputModel private constructor(private val myEditor: Editor) : VimExOutputPanel {
class ExOutputModel(private val myEditor: WeakReference<Editor>) : VimOutputPanelBase() {
private var isActiveInTestMode = false
override val isActive: Boolean
val editor get() = myEditor.get()
val isActive: Boolean
get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.isPanelActive(myEditor)
editor?.let { ExOutputPanel.getNullablePanel(it) }?.myActive ?: false
} else {
isActiveInTestMode
}
override var text: String? = null
override fun addText(text: String, isNewLine: Boolean) {
if (this.text.isNotEmpty() && isNewLine) this.text += "\n$text" else this.text += text
}
override fun show() {
if (editor == null) return
val currentPanel = injector.outputPanel.getCurrentOutputPanel()
if (currentPanel != null && currentPanel != this) currentPanel.close()
editor!!.vimExOutput = this
val exOutputPanel = ExOutputPanel.getInstance(editor!!)
if (!exOutputPanel.myActive) {
if (ApplicationManager.getApplication().isUnitTestMode) {
isActiveInTestMode = true
} else {
exOutputPanel.activate()
}
}
}
override fun scrollPage() {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.scrollPage()
}
override fun scrollHalfPage() {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.scrollHalfPage()
}
override fun scrollLine() {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.scrollLine()
}
override var text: String = ""
get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).text
editor?.let { ExOutputPanel.getInstance(it).text } ?: ""
} else {
// ExOutputPanel always returns a non-null string
field ?: ""
field
}
set(value) {
// ExOutputPanel will strip a trailing newline. We'll do it now so that tests have the same behaviour. We also
// never pass null to ExOutputPanel, but we do store it for tests, so we know if we're active or not
val newValue = value?.removeSuffix("\n")
val newValue = value.removeSuffix("\n")
if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).setText(newValue ?: "")
editor?.let { ExOutputPanel.getInstance(it).setText(newValue) }
} else {
field = newValue
isActiveInTestMode = !newValue.isNullOrEmpty()
isActiveInTestMode = newValue.isNotEmpty()
}
}
override var label: String
get() {
val notNullEditor = editor ?: return ""
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return ""
return panel.myLabel.text
}
set(value) {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.myLabel.text = value
}
override fun output(text: String) {
fun output(text: String) {
this.text = text
}
override fun clear() {
text = null
fun clear() {
text = ""
}
override val atEnd: Boolean
get() {
val notNullEditor = editor ?: return false
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return false
return panel.isAtEnd()
}
override fun onBadKey() {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.onBadKey()
}
override fun close(key: KeyStroke?) {
val notNullEditor = editor ?: return
val panel = ExOutputPanel.getNullablePanel(notNullEditor) ?: return
panel.close(key)
}
override fun close() {
if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).close()
editor?.let { ExOutputPanel.getInstance(it).close() }
}
else {
isActiveInTestMode = false
@@ -65,7 +138,7 @@ class ExOutputModel private constructor(private val myEditor: Editor) : VimExOut
fun getInstance(editor: Editor): ExOutputModel {
var model = editor.vimExOutput
if (model == null) {
model = ExOutputModel(editor)
model = ExOutputModel(WeakReference(editor))
editor.vimExOutput = model
}
return model

View File

@@ -24,13 +24,14 @@ import com.maddyhome.idea.vim.common.CommandAlias
import com.maddyhome.idea.vim.common.CommandAliasHandler
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.helper.TestInputModel
import com.maddyhome.idea.vim.helper.inRepeatMode
import com.maddyhome.idea.vim.helper.noneOfEnum
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.key.MappingOwner
import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.ui.ModalEntry
import com.maddyhome.idea.vim.ui.ex.ExEntryPanelService
import com.maddyhome.idea.vim.vimscript.model.Executable
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
import com.maddyhome.idea.vim.vimscript.model.VimLContext
@@ -151,7 +152,7 @@ object VimExtensionFacade {
/** Returns a single key stroke from the user input similar to 'getchar()'. */
@JvmStatic
fun inputKeyStroke(editor: Editor): KeyStroke {
if (editor.vim.vimStateMachine.isDotRepeatInProgress) {
if (editor.vim.inRepeatMode) {
val input = Extension.consumeKeystroke()
LOG.trace("inputKeyStroke: dot repeat in progress. Input: $input")
return input ?: error("Not enough keystrokes saved: ${Extension.lastExtensionHandler}")
@@ -183,7 +184,7 @@ object VimExtensionFacade {
/** Returns a string typed in the input box similar to 'input()'. */
@JvmStatic
fun inputString(editor: Editor, context: DataContext, prompt: String, finishOn: Char?): String {
return injector.commandLine.inputString(editor.vim, context.vim, prompt, finishOn) ?: ""
return (injector.commandLine as ExEntryPanelService).inputString(editor.vim, context.vim, prompt, finishOn) ?: ""
}
/** Get the current contents of the given register similar to 'getreg()'. */

View File

@@ -46,7 +46,6 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissin
import com.maddyhome.idea.vim.extension.exportOperatorFunction
import com.maddyhome.idea.vim.handler.TextObjectActionHandler
import com.maddyhome.idea.vim.helper.PsiHelper
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
@@ -65,7 +64,7 @@ internal class CommentaryExtension : VimExtension {
selectionType: SelectionType,
resetCaret: Boolean = true,
): Boolean {
val mode = editor.vimStateMachine.mode
val mode = editor.mode
if (mode !is Mode.VISUAL) {
editor.ij.selectionModel.setSelection(range.startOffset, range.endOffset)
}
@@ -80,11 +79,12 @@ internal class CommentaryExtension : VimExtension {
val project = editor.ij.project!!
val callback = { afterCommenting(mode, editor, resetCaret, range) }
actions.any { executeActionWithCallbackOnSuccess(it, project, context, callback) }
actions.any { executeActionWithCallbackOnSuccess(editor, it, project, context, callback) }
}
}
private fun executeActionWithCallbackOnSuccess(
editor: VimEditor,
action: String,
project: Project,
context: ExecutionContext,
@@ -93,7 +93,7 @@ internal class CommentaryExtension : VimExtension {
val res = Ref.create<Boolean>(false)
AsyncActionExecutionService.getInstance(project).withExecutionAfterAction(
action,
{ res.set(injector.actionExecutor.executeAction(action, context)) },
{ res.set(injector.actionExecutor.executeAction(editor, name = action, context = context)) },
{ if (res.get()) callback() })
return res.get()
}

View File

@@ -23,13 +23,15 @@ import com.intellij.util.Alarm
import com.intellij.util.Alarm.ThreadToUse
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.ModeChangeListener
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.common.VimYankListener
import com.maddyhome.idea.vim.extension.VimExtension
import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.helper.VimNlsSafe
import com.maddyhome.idea.vim.listener.VimInsertListener
import com.maddyhome.idea.vim.listener.VimYankListener
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import org.jetbrains.annotations.NonNls
@@ -75,7 +77,7 @@ internal class HighlightColorResetter : LafManagerListener {
*
* When a new text is yanked or user starts editing, the old highlighting would be deleted.
*/
internal class VimHighlightedYank : VimExtension, VimYankListener, VimInsertListener {
internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeListener {
private val highlightHandler = HighlightHandler()
private var initialised = false
@@ -83,8 +85,8 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, VimInsertList
override fun init() {
// Note that these listeners will still be registered when IdeaVim is disabled. However, they'll never get called
VimPlugin.getYank().addListener(this)
VimPlugin.getChange().addInsertListener(this)
injector.listenersNotifier.modeChangeListeners.add(this)
injector.listenersNotifier.yankListeners.add(this)
// Register our own disposable to remove highlights when IdeaVim is disabled. Somehow, we need to re-register when
// IdeaVim is re-enabled. We don't get a call back for that, but because the listeners are active until the
@@ -105,8 +107,8 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, VimInsertList
override fun dispose() {
// Called when the extension is disabled with `:set no{extension}` or if the plugin owning the extension unloads
VimPlugin.getYank().removeListener(this)
VimPlugin.getChange().removeInsertListener(this)
injector.listenersNotifier.modeChangeListeners.remove(this)
injector.listenersNotifier.yankListeners.remove(this)
highlightHandler.clearYankHighlighters()
initialised = false
@@ -117,7 +119,8 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, VimInsertList
highlightHandler.highlightYankRange(editor.ij, range)
}
override fun insertModeStarted(editor: Editor) {
override fun modeChanged(editor: VimEditor, oldMode: Mode) {
if (editor.mode !is Mode.INSERT) return
ensureInitialised()
highlightHandler.clearYankHighlighters()
}

View File

@@ -481,6 +481,9 @@ internal class NerdTree : VimExtension {
registerCommand("NERDTreeMapNewFile", "n", NerdAction.ToIj("NewFile"))
registerCommand("NERDTreeMapNewDir", "N", NerdAction.ToIj("NewDir"))
registerCommand("NERDTreeMapDelete", "d", NerdAction.ToIj("\$Delete"))
registerCommand("NERDTreeMapCopy", "y", NerdAction.ToIj("\$Copy"))
registerCommand("NERDTreeMapPaste", "v", NerdAction.ToIj("\$Paste"))
registerCommand("NERDTreeMapRename", "<C-r>", NerdAction.ToIj("RenameElement"))
registerCommand("NERDTreeMapRefreshRoot", "R", NerdAction.ToIj("Synchronize"))
registerCommand("NERDTreeMapMenu", "m", NerdAction.ToIj("ShowPopupMenu"))
registerCommand("NERDTreeMapQuit", "q", NerdAction.ToIj("HideActiveWindow"))

View File

@@ -36,6 +36,7 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissin
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
import com.maddyhome.idea.vim.extension.exportOperatorFunction
import com.maddyhome.idea.vim.group.findBlockRange
import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimCaret
@@ -50,6 +51,7 @@ import com.maddyhome.idea.vim.state.mode.selectionType
import org.jetbrains.annotations.NonNls
import java.awt.event.KeyEvent
import javax.swing.KeyStroke
import com.maddyhome.idea.vim.state.mode.returnTo
/**
* Port of vim-surround.
@@ -115,6 +117,9 @@ internal class VimSurroundExtension : VimExtension {
// it.moveToOffset(lineStartOffset)
}
// Jump back to start
if (editor.mode !is Mode.NORMAL) {
editor.mode = Mode.NORMAL()
}
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
}
@@ -136,7 +141,7 @@ internal class VimSurroundExtension : VimExtension {
}
runWriteAction {
// Leave visual mode
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
editor.exitVisualMode()
}
}
}
@@ -306,8 +311,10 @@ internal class VimSurroundExtension : VimExtension {
private fun getSurroundRange(caret: VimCaret): TextRange? {
val editor = caret.editor
val ijEditor = editor.ij
return when (ijEditor.vim.mode) {
if (editor.mode is Mode.CMD_LINE) {
editor.mode = (editor.mode as Mode.CMD_LINE).returnTo()
}
return when (editor.mode) {
is Mode.NORMAL -> injector.markService.getChangeMarks(caret)
is Mode.VISUAL -> caret.run { TextRange(selectionStart, selectionEnd) }
else -> null
@@ -350,6 +357,9 @@ private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_
private fun inputTagPair(editor: Editor, context: DataContext): Pair<String, String>? {
val tagInput = inputString(editor, context, "<", '>')
if (editor.vim.mode is Mode.CMD_LINE) {
editor.vim.mode = editor.vim.mode.returnTo()
}
val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
return if (matcher.find()) {
val tagName = matcher.group(1)
@@ -366,6 +376,9 @@ private fun inputFunctionName(
withInternalSpaces: Boolean,
): Pair<String, String>? {
val functionNameInput = inputString(editor, context, "function: ", null)
if (editor.vim.mode is Mode.CMD_LINE) {
editor.vim.mode = editor.vim.mode.returnTo()
}
if (functionNameInput.isEmpty()) return null
return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
}

View File

@@ -15,77 +15,35 @@ import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.UndoConfirmationPolicy
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.actions.EnterAction
import com.intellij.openapi.editor.event.EditorMouseEvent
import com.intellij.openapi.editor.event.EditorMouseListener
import com.intellij.openapi.editor.impl.TextRangeInterval
import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.util.PsiUtilBase
import com.intellij.util.containers.ContainerUtil
import com.maddyhome.idea.vim.EventFacade
import com.maddyhome.idea.vim.api.BufferPosition
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimChangeGroupBase
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimMotionGroupBase
import com.maddyhome.idea.vim.api.anyNonWhitespace
import com.maddyhome.idea.vim.api.getLineEndForOffset
import com.maddyhome.idea.vim.api.getLineEndOffset
import com.maddyhome.idea.vim.api.getLineStartForOffset
import com.maddyhome.idea.vim.api.getText
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.lineLength
import com.maddyhome.idea.vim.api.normalizeOffset
import com.maddyhome.idea.vim.api.options
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.IndentConfig.Companion.create
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.ex.ranges.LineRange
import com.maddyhome.idea.vim.group.MotionGroup.Companion.getMotionRange2
import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
import com.maddyhome.idea.vim.handler.Motion
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset
import com.maddyhome.idea.vim.handler.commandContinuation
import com.maddyhome.idea.vim.helper.CharacterHelper
import com.maddyhome.idea.vim.helper.CharacterHelper.changeCase
import com.maddyhome.idea.vim.helper.CharacterHelper.charType
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.NumberType
import com.maddyhome.idea.vim.helper.endOffsetInclusive
import com.maddyhome.idea.vim.helper.findNumberUnderCursor
import com.maddyhome.idea.vim.helper.findNumbersInRange
import com.maddyhome.idea.vim.helper.inInsertMode
import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
import com.maddyhome.idea.vim.key.KeyHandlerKeeper.Companion.getInstance
import com.maddyhome.idea.vim.listener.VimInsertListener
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.regexp.VimRegex
import com.maddyhome.idea.vim.regexp.match.VimMatchResult
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.vimscript.model.commands.SortOption
import org.jetbrains.annotations.TestOnly
import java.math.BigInteger
import java.util.*
import java.util.function.Consumer
import kotlin.math.max
/**
* Provides all the insert/replace related functionality
*/
class ChangeGroup : VimChangeGroupBase() {
private val insertListeners = ContainerUtil.createLockFreeCopyOnWriteList<VimInsertListener>()
private val listener: EditorMouseListener = object : EditorMouseListener {
override fun mouseClicked(event: EditorMouseEvent) {
val editor = event.editor
@@ -145,163 +103,6 @@ class ChangeGroup : VimChangeGroupBase() {
}
}
override fun getDeleteRangeAndType2(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
argument: Argument,
isChange: Boolean,
operatorArguments: OperatorArguments,
): Pair<TextRange, SelectionType>? {
val range = getMotionRange2(
(editor as IjVimEditor).editor,
(caret as IjVimCaret).caret,
(context as IjEditorExecutionContext).context,
argument,
operatorArguments
)
?: return null
// Delete motion commands that are not linewise become linewise if all the following are true:
// 1) The range is across multiple lines
// 2) There is only whitespace before the start of the range
// 3) There is only whitespace after the end of the range
var type: SelectionType
type = if (argument.motion.isLinewiseMotion()) {
SelectionType.LINE_WISE
} else {
SelectionType.CHARACTER_WISE
}
val motion = argument.motion
if (!isChange && !motion.isLinewiseMotion()) {
val start = editor.offsetToBufferPosition(range.startOffset)
val end = editor.offsetToBufferPosition(range.endOffset)
if (start.line != end.line) {
val offset1 = range.startOffset
if (!editor.anyNonWhitespace(offset1, -1)) {
val offset = range.endOffset
if (!editor.anyNonWhitespace(offset, 1)) {
type = SelectionType.LINE_WISE
}
}
}
}
return Pair(range, type)
}
/**
* Toggles the case of count characters
*
* @param editor The editor to change
* @param caret The caret on which the operation is performed
* @param count The number of characters to change
* @return true if able to change count characters
*/
override fun changeCaseToggleCharacter(editor: VimEditor, caret: VimCaret, count: Int): Boolean {
val allowWrap = injector.options(editor).whichwrap.contains("~")
var motion = injector.motion.getHorizontalMotion(editor, caret, count, true, allowWrap)
if (motion is Motion.Error) return false
changeCase(editor, caret, caret.offset, (motion as AbsoluteOffset).offset, CharacterHelper.CASE_TOGGLE)
motion = injector.motion.getHorizontalMotion(
editor,
caret,
count,
false,
allowWrap
) // same but without allow end because we can change till end, but can't move caret there
if (motion is AbsoluteOffset) {
caret.moveToOffset(editor.normalizeOffset(motion.offset, false))
}
return true
}
override fun blockInsert(
editor: VimEditor,
context: ExecutionContext,
range: TextRange,
append: Boolean,
operatorArguments: OperatorArguments,
): Boolean {
val lines = getLinesCountInVisualBlock(editor, range)
val startPosition = editor.offsetToBufferPosition(range.startOffset)
val mode = operatorArguments.mode
val visualBlockMode = mode is VISUAL && mode.selectionType === SelectionType.BLOCK_WISE
for (caret in editor.carets()) {
val line = startPosition.line
var column = startPosition.column
if (!visualBlockMode) {
column = 0
} else if (append) {
column += range.maxLength
if (caret.vimLastColumn == VimMotionGroupBase.LAST_COLUMN) {
column = VimMotionGroupBase.LAST_COLUMN
}
}
val lineLength = editor.lineLength(line)
if (column < VimMotionGroupBase.LAST_COLUMN && lineLength < column) {
val pad = EditorHelper.pad((editor as IjVimEditor).editor, line, column)
val offset = editor.getLineEndOffset(line)
insertText(editor, caret, offset, pad)
}
if (visualBlockMode || !append) {
(caret as IjVimCaret).caret.moveToInlayAwareLogicalPosition(LogicalPosition(line, column))
}
if (visualBlockMode) {
setInsertRepeat(lines, column, append)
}
}
if (visualBlockMode || !append) {
insertBeforeCursor(editor, context)
} else {
insertAfterCursor(editor, context)
}
return true
}
/**
* Changes the case of all the characters in the range
*
* @param editor The editor to change
* @param caret The caret to be moved
* @param range The range to change
* @param type The case change type (TOGGLE, UPPER, LOWER)
* @return true if able to delete the text, false if not
*/
override fun changeCaseRange(editor: VimEditor, caret: VimCaret, range: TextRange, type: Char): Boolean {
val starts = range.startOffsets
val ends = range.endOffsets
for (i in ends.indices.reversed()) {
changeCase(editor, caret, starts[i], ends[i], type)
}
caret.moveToOffset(range.startOffset)
return true
}
/**
* This performs the actual case change.
*
* @param editor The editor to change
* @param start The start offset to change
* @param end The end offset to change
* @param type The type of change (TOGGLE, UPPER, LOWER)
*/
private fun changeCase(editor: VimEditor, caret: VimCaret, start: Int, end: Int, type: Char) {
var start = start
var end = end
if (start > end) {
val t = end
end = start
start = t
}
end = editor.normalizeOffset(end, true)
val chars = editor.text()
val sb = StringBuilder()
for (i in start until end) {
sb.append(changeCase(chars[i], type))
}
replaceText(editor, caret, start, end, sb.toString())
}
private fun restoreCursor(editor: VimEditor, caret: VimCaret, startLine: Int) {
if (caret != editor.primaryCaret()) {
(editor as IjVimEditor).editor.caretModel.addCaret(
@@ -310,88 +111,13 @@ class ChangeGroup : VimChangeGroupBase() {
}
}
/**
* Changes the case of all the character moved over by the motion argument.
*
* @param editor The editor to change
* @param caret The caret on which motion pretends to be performed
* @param context The data context
* @param type The case change type (TOGGLE, UPPER, LOWER)
* @param argument The motion command
* @return true if able to delete the text, false if not
*/
override fun changeCaseMotion(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext?,
type: Char,
argument: Argument,
operatorArguments: OperatorArguments,
): Boolean {
val range = injector.motion.getMotionRange(
editor, caret, context!!, argument,
operatorArguments
)
return range != null && changeCaseRange(editor, caret, range, type)
}
override fun reformatCodeMotion(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
argument: Argument,
operatorArguments: OperatorArguments,
): Boolean {
val range = injector.motion.getMotionRange(
editor, caret, context, argument,
operatorArguments
)
return range != null && reformatCodeRange(editor, caret, range)
}
override fun reformatCodeSelection(editor: VimEditor, caret: VimCaret, range: VimSelection) {
val textRange = range.toVimTextRange(true)
reformatCodeRange(editor, caret, textRange)
}
private fun reformatCodeRange(editor: VimEditor, caret: VimCaret, range: TextRange): Boolean {
val starts = range.startOffsets
val ends = range.endOffsets
val firstLine = editor.offsetToBufferPosition(range.startOffset).line
for (i in ends.indices.reversed()) {
val startOffset = editor.getLineStartForOffset(starts[i])
val offset = ends[i] - if (startOffset == ends[i]) 0 else 1
val endOffset = editor.getLineEndForOffset(offset)
reformatCode(editor, startOffset, endOffset)
}
val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine)
caret.moveToOffset(newOffset)
return true
}
private fun reformatCode(editor: VimEditor, start: Int, end: Int) {
override fun reformatCode(editor: VimEditor, start: Int, end: Int) {
val project = (editor as IjVimEditor).editor.project ?: return
val file = PsiUtilBase.getPsiFileInEditor(editor.editor, project) ?: return
val textRange = com.intellij.openapi.util.TextRange.create(start, end)
CodeStyleManager.getInstance(project).reformatText(file, listOf(textRange))
}
override fun autoIndentMotion(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
argument: Argument,
operatorArguments: OperatorArguments,
) {
val range = injector.motion.getMotionRange(editor, caret, context, argument, operatorArguments)
if (range != null) {
autoIndentRange(
editor, caret, context,
TextRange(range.startOffset, range.endOffsetInclusive)
)
}
}
override fun autoIndentRange(
editor: VimEditor,
caret: VimCaret,
@@ -446,361 +172,14 @@ class ChangeGroup : VimChangeGroupBase() {
}
}
override fun indentLines(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
lines: Int,
dir: Int,
operatorArguments: OperatorArguments,
) {
val start = caret.offset
val end = injector.motion.moveCaretToRelativeLineEnd(editor, caret, lines - 1, true)
indentRange(editor, caret, context, TextRange(start, end), 1, dir, operatorArguments)
}
override fun indentMotion(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
argument: Argument,
dir: Int,
operatorArguments: OperatorArguments,
) {
val range = injector.motion.getMotionRange(editor, caret, context, argument, operatorArguments)
if (range != null) {
indentRange(editor, caret, context, range, 1, dir, operatorArguments)
}
}
override fun indentRange(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
range: TextRange,
count: Int,
dir: Int,
operatorArguments: OperatorArguments,
) {
if (logger.isDebugEnabled) {
logger.debug("count=$count")
}
// Remember the current caret column
val intendedColumn = caret.vimLastColumn
val indentConfig = create((editor as IjVimEditor).editor)
val sline = editor.offsetToBufferPosition(range.startOffset).line
val endLogicalPosition = editor.offsetToBufferPosition(range.endOffset)
val eline = if (endLogicalPosition.column == 0) max((endLogicalPosition.line - 1).toDouble(), 0.0)
.toInt() else endLogicalPosition.line
if (range.isMultiple) {
val from = editor.offsetToBufferPosition(range.startOffset).column
if (dir == 1) {
// Right shift blockwise selection
val indent = indentConfig.createIndentByCount(count)
for (l in sline..eline) {
val len = editor.lineLength(l)
if (len > from) {
val spos = BufferPosition(l, from, false)
insertText(editor, caret, spos, indent)
}
}
} else {
// Left shift blockwise selection
val chars = editor.text()
for (l in sline..eline) {
val len = editor.lineLength(l)
if (len > from) {
val spos = BufferPosition(l, from, false)
val epos = BufferPosition(l, from + indentConfig.getTotalIndent(count) - 1, false)
val wsoff = editor.bufferPositionToOffset(spos)
val weoff = editor.bufferPositionToOffset(epos)
var pos: Int
pos = wsoff
while (pos <= weoff) {
if (charType(editor, chars[pos], false) !== CharacterHelper.CharacterType.WHITESPACE) {
break
}
pos++
}
if (pos > wsoff) {
deleteText(editor, TextRange(wsoff, pos), null, caret, operatorArguments, true)
}
}
}
}
} else {
// Shift non-blockwise selection
for (l in sline..eline) {
val soff = editor.getLineStartOffset(l)
val eoff = editor.getLineEndOffset(l, true)
val woff = injector.motion.moveCaretToLineStartSkipLeading(editor, l)
val col = editor.offsetToBufferPosition(woff).column
val limit = max(0.0, (col + dir * indentConfig.getTotalIndent(count)).toDouble())
.toInt()
if (col > 0 || soff != eoff) {
val indent = indentConfig.createIndentBySize(limit)
replaceText(editor, caret, soff, woff, indent)
}
}
}
if (!editor.editor.inInsertMode) {
if (!range.isMultiple) {
// The caret has moved, so reset the intended column before trying to get the expected offset
val newCaret = caret.setVimLastColumnAndGetCaret(intendedColumn)
val offset = injector.motion.moveCaretToLineWithStartOfLineOption(editor, sline, caret)
newCaret.moveToOffset(offset)
} else {
caret.moveToOffset(range.startOffset)
}
}
}
/**
* Sort range of text with a given comparator
*
* @param editor The editor to replace text in
* @param range The range to sort
* @param lineComparator The comparator to use to sort
* @param sortOptions The option to sort the range
* @return true if able to sort the text, false if not
*/
override fun sortRange(
editor: VimEditor, caret: VimCaret, range: LineRange, lineComparator: Comparator<String>,
sortOptions: SortOption,
): Boolean {
val startLine = range.startLine
val endLine = range.endLine
val count = range.size
if (count < 2) {
return false
}
val startOffset = editor.getLineStartOffset(startLine)
val endOffset = editor.getLineEndOffset(endLine)
val selectedText = (editor as IjVimEditor).editor.document.getText(TextRangeInterval(startOffset, endOffset))
val lines = selectedText.split("\n")
val modifiedLines = sortOptions.pattern?.let {
if (sortOptions.sortOnPattern) {
extractPatternFromLines(editor, lines, startLine, it)
} else {
deletePatternFromLines(editor, lines, startLine, it)
}
} ?: lines
val sortedLines = lines.zip(modifiedLines)
.sortedWith { l1, l2 -> lineComparator.compare(l1.second, l2.second) }
.map {it.first}
.toMutableList()
if (sortOptions.unique) {
val iterator = sortedLines.iterator()
var previous: String? = null
while (iterator.hasNext()) {
val current = iterator.next()
if (current == previous || sortOptions.ignoreCase && current.equals(previous, ignoreCase = true)) {
iterator.remove()
} else {
previous = current
}
}
}
if (sortedLines.isEmpty()) {
return false
}
replaceText(editor, caret, startOffset, endOffset, StringUtil.join(sortedLines, "\n"))
return true
}
private fun extractPatternFromLines(editor: VimEditor, lines: List<String>, startLine: Int, pattern: String): List<String> {
val regex = VimRegex(pattern)
return lines.mapIndexed { i: Int, line: String ->
val result = regex.findInLine(editor, startLine + i, 0)
when (result) {
is VimMatchResult.Success -> result.value
is VimMatchResult.Failure -> line
}
}
}
private fun deletePatternFromLines(editor: VimEditor, lines: List<String>, startLine: Int, pattern: String): List<String> {
val regex = VimRegex(pattern)
return lines.mapIndexed { i: Int, line: String ->
val result = regex.findInLine(editor, startLine + i, 0)
when (result) {
is VimMatchResult.Success -> line.substring(result.value.length, line.length)
is VimMatchResult.Failure -> line
}
}
}
/**
* Perform increment and decrement for numbers in visual mode
*
*
* Flag [avalanche] marks if increment (or decrement) should be performed in avalanche mode
* (for v_g_Ctrl-A and v_g_Ctrl-X commands)
*
* @return true
*/
override fun changeNumberVisualMode(
editor: VimEditor,
caret: VimCaret,
selectedRange: TextRange,
count: Int,
avalanche: Boolean,
): Boolean {
val nf: List<String> = injector.options(editor).nrformats
val alpha = nf.contains("alpha")
val hex = nf.contains("hex")
val octal = nf.contains("octal")
val numberRanges = findNumbersInRange((editor as IjVimEditor).editor, selectedRange, alpha, hex, octal)
val newNumbers: MutableList<String?> = ArrayList()
for (i in numberRanges.indices) {
val numberRange = numberRanges[i]
val iCount = if (avalanche) (i + 1) * count else count
val newNumber = changeNumberInRange(editor, numberRange, iCount, alpha, hex, octal)
newNumbers.add(newNumber)
}
for (i in newNumbers.indices.reversed()) {
// Replace text bottom up. In other direction ranges will be desynchronized after inc numbers like 99
val (first) = numberRanges[i]
val newNumber = newNumbers[i]
replaceText(editor, caret, first.startOffset, first.endOffset, newNumber!!)
}
(caret as IjVimCaret).caret.moveToInlayAwareOffset(selectedRange.startOffset)
return true
}
override fun changeNumber(editor: VimEditor, caret: VimCaret, count: Int): Boolean {
val nf: List<String> = injector.options(editor).nrformats
val alpha = nf.contains("alpha")
val hex = nf.contains("hex")
val octal = nf.contains("octal")
val range = findNumberUnderCursor((editor as IjVimEditor).editor, (caret as IjVimCaret).caret, alpha, hex, octal)
if (range == null) {
logger.debug("no number on line")
return false
}
val newNumber = changeNumberInRange(editor, range, count, alpha, hex, octal)
return if (newNumber == null) {
false
} else {
replaceText(editor, caret, range.first.startOffset, range.first.endOffset, newNumber)
caret.caret.moveToInlayAwareOffset(range.first.startOffset + newNumber.length - 1)
true
}
}
override fun reset() {
strokes.clear()
repeatCharsCount = 0
if (lastStrokes != null) {
lastStrokes!!.clear()
}
}
override fun saveStrokes(newStrokes: String?) {
val chars = newStrokes!!.toCharArray()
strokes.add(chars)
}
private fun changeNumberInRange(
editor: VimEditor,
range: Pair<TextRange, NumberType>,
count: Int,
alpha: Boolean,
hex: Boolean,
octal: Boolean,
): String? {
val text = editor.getText(range.first)
val numberType = range.second
if (logger.isDebugEnabled) {
logger.debug("found range $range")
logger.debug("text=$text")
}
var number = text
if (text.isEmpty()) {
return null
}
var ch = text[0]
if (hex && NumberType.HEX == numberType) {
if (!text.lowercase(Locale.getDefault()).startsWith(HEX_START)) {
throw RuntimeException("Hex number should start with 0x: $text")
}
for (i in text.length - 1 downTo 2) {
val index = "abcdefABCDEF".indexOf(text[i])
if (index >= 0) {
lastLower = index < 6
break
}
}
var num = BigInteger(text.substring(2), 16)
num = num.add(BigInteger.valueOf(count.toLong()))
if (num.compareTo(BigInteger.ZERO) < 0) {
num = BigInteger(MAX_HEX_INTEGER, 16).add(BigInteger.ONE).add(num)
}
number = num.toString(16)
number = number.padStart(text.length - 2, '0')
if (!lastLower) {
number = number.uppercase(Locale.getDefault())
}
number = text.substring(0, 2) + number
} else if (octal && NumberType.OCT == numberType && text.length > 1) {
if (!text.startsWith("0")) throw RuntimeException("Oct number should start with 0: $text")
var num = BigInteger(text, 8).add(BigInteger.valueOf(count.toLong()))
if (num.compareTo(BigInteger.ZERO) < 0) {
num = BigInteger("1777777777777777777777", 8).add(BigInteger.ONE).add(num)
}
number = num.toString(8)
number = "0" + number.padStart(text.length - 1, '0')
} else if (alpha && NumberType.ALPHA == numberType) {
if (!Character.isLetter(ch)) throw RuntimeException("Not alpha number : $text")
ch += count.toChar().code
if (Character.isLetter(ch)) {
number = ch.toString()
}
} else if (NumberType.DEC == numberType) {
if (ch != '-' && !Character.isDigit(ch)) throw RuntimeException("Not dec number : $text")
var pad = ch == '0'
var len = text.length
if (ch == '-' && text[1] == '0') {
pad = true
len--
}
var num = BigInteger(text)
num = num.add(BigInteger.valueOf(count.toLong()))
number = num.toString()
if (!octal && pad) {
var neg = false
if (number[0] == '-') {
neg = true
number = number.substring(1)
}
number = number.padStart(len, '0')
if (neg) {
number = "-$number"
}
}
}
return number
}
@Deprecated(message = "Please use listenersNotifier", replaceWith = ReplaceWith("injector.listenersNotifier.modeChangeListeners.add", imports = ["import com.maddyhome.idea.vim.api.injector"]))
fun addInsertListener(listener: VimInsertListener) {
insertListeners.add(listener)
injector.listenersNotifier.modeChangeListeners.add(listener)
}
@Deprecated(message = "Please use listenersNotifier", replaceWith = ReplaceWith("injector.listenersNotifier.modeChangeListeners.remove", imports = ["import com.maddyhome.idea.vim.api.injector"]))
fun removeInsertListener(listener: VimInsertListener) {
insertListeners.remove(listener)
}
override fun notifyListeners(editor: VimEditor) {
insertListeners.forEach(Consumer { listener: VimInsertListener -> listener.insertModeStarted((editor as IjVimEditor).editor) })
}
@TestOnly
override fun resetRepeat() {
setInsertRepeat(0, 0, false)
injector.listenersNotifier.modeChangeListeners.remove(listener)
}
private companion object {

View File

@@ -8,9 +8,7 @@
package com.maddyhome.idea.vim.group;
import com.intellij.execution.impl.ConsoleViewImpl;
import com.intellij.find.EditorSearchSession;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.client.ClientAppSession;
import com.intellij.openapi.client.ClientKind;
import com.intellij.openapi.client.ClientSessionsManager;
@@ -23,12 +21,10 @@ import com.intellij.openapi.editor.event.CaretListener;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
import com.intellij.openapi.project.Project;
import com.maddyhome.idea.vim.KeyHandler;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.*;
import com.maddyhome.idea.vim.ex.ExOutputModel;
import com.maddyhome.idea.vim.helper.CaretVisualAttributesHelperKt;
import com.maddyhome.idea.vim.helper.CommandStateHelper;
import com.maddyhome.idea.vim.helper.EditorHelper;
import com.maddyhome.idea.vim.helper.UserDataManager;
import com.maddyhome.idea.vim.newapi.IjVimDocument;
@@ -218,46 +214,9 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
editorEx.addPropertyChangeListener(FontSizeChangeListener.INSTANCE);
}
// We add Vim bindings to all opened editors, including editors used as UI controls rather than just project file
// editors. This includes editors used as part of the UI, such as the VCS commit message, or used as read-only
// viewers for text output, such as log files in run configurations or the Git Console tab. And editors are used for
// interactive stdin/stdout for console-based run configurations.
// We want to provide an intuitive experience for working with these additional editors, so we automatically switch
// to INSERT mode if they are interactive editors. Recognising these can be a bit tricky.
// These additional interactive editors are not file-based, but must have a writable document. However, log output
// documents are also writable (the IDE is writing new content as it becomes available) just not user-editable. So
// we must also check that the editor is not in read-only "viewer" mode (this includes "rendered" mode, which is
// read-only and also hides the caret).
// Furthermore, interactive stdin/stdout console output in run configurations is hosted in a read-only editor, but
// it can still be edited. The `ConsoleViewImpl` class installs a typing handler that ignores the editor's
// `isViewer` property and allows typing if the associated process (if any) is still running. We can get the
// editor's console view and check this ourselves, but we have to wait until the editor has finished initialising
// before it's available in user data.
// Finally, we have a special check for diff windows. If we compare against clipboard, we get a diff editor that is
// not file based, is writable, and not a viewer, but we don't want to treat this as an interactive editor.
// Note that we need a similar check in `VimEditor.isWritable` to allow Escape to work to exit insert mode. We need
// to know that a read-only editor that is hosting a console view with a running process can be treated as writable.
Runnable switchToInsertMode = () -> {
ExecutionContext context = injector.getExecutionContextManager().getEditorExecutionContext(new IjVimEditor(editor));
VimPlugin.getChange().insertBeforeCursor(new IjVimEditor(editor), context);
KeyHandler.getInstance().reset(new IjVimEditor(editor));
};
if (!editor.isViewer() &&
!EditorHelper.isFileEditor(editor) &&
editor.getDocument().isWritable() &&
!CommandStateHelper.inInsertMode(editor) &&
editor.getEditorKind() != EditorKind.DIFF) {
switchToInsertMode.run();
if (injector.getApplication().isUnitTest()) {
updateCaretsVisualAttributes(new IjVimEditor(editor));
}
ApplicationManager.getApplication().invokeLater(
() -> {
if (editor.isDisposed()) return;
ConsoleViewImpl consoleView = editor.getUserData(ConsoleViewImpl.CONSOLE_VIEW_IN_EDITOR_VIEW);
if (consoleView != null && consoleView.isRunning() && !CommandStateHelper.inInsertMode(editor)) {
switchToInsertMode.run();
}
});
updateCaretsVisualAttributes(new IjVimEditor(editor));
}
public void editorDeinit(@NotNull Editor editor) {
@@ -279,9 +238,8 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
VimPlugin.getNotifications(project).notifyAboutIdeaJoin(editor);
}
@Nullable
@Override
public Element getState() {
public @Nullable Element getState() {
Element element = new Element("editor");
saveData(element);
return element;
@@ -357,18 +315,16 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
.collect(Collectors.toList());
}
@NotNull
@Override
public Collection<VimEditor> getEditors() {
public @NotNull Collection<VimEditor> getEditors() {
return getLocalEditors()
.filter(UserDataManager::getVimInitialised)
.map(IjVimEditor::new)
.collect(Collectors.toList());
}
@NotNull
@Override
public Collection<VimEditor> getEditors(@NotNull VimDocument buffer) {
public @NotNull Collection<VimEditor> getEditors(@NotNull VimDocument buffer) {
final Document document = ((IjVimDocument)buffer).getDocument();
return getLocalEditors()
.filter(editor -> UserDataManager.getVimInitialised(editor) && editor.getDocument().equals(document))
@@ -388,7 +344,7 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
// events such as document change (to update search highlights), and these can come from CWM guests, and we'd get
// the remote editors.
// This invocation will always get local editors, regardless of the current context.
List<ClientAppSession> appSessions = ClientSessionsManager.getAppSessions(ClientKind.ALL);
List<ClientAppSession> appSessions = ClientSessionsManager.getAppSessions(ClientKind.LOCAL);
if (!appSessions.isEmpty()) {
ClientAppSession localSession = appSessions.get(0);
return localSession.getService(ClientEditorManager.class).editors();
@@ -416,12 +372,16 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
// Note that IDE scale is handled by LafManager.lookAndFeelChanged
VimCommandLine activeCommandLine = injector.getCommandLine().getActiveCommandLine();
if (activeCommandLine != null) {
injector.getProcessGroup().cancelExEntry(new IjVimEditor(editor), false);
activeCommandLine.close(true, false);
}
ExOutputModel exOutputModel = ExOutputModel.tryGetInstance(editor);
if (exOutputModel != null) {
exOutputModel.close();
}
VimModalInput modalInput = injector.getModalInput().getCurrentModalInput();
if (modalInput != null) {
modalInput.deactivate(true, false);
}
}
}
}

View File

@@ -173,20 +173,20 @@ class FileGroup : VimFileBase() {
/**
* Saves specific file in the project.
*/
override fun saveFile(context: ExecutionContext) {
override fun saveFile(editor: VimEditor, context: ExecutionContext) {
val action = if (injector.globalIjOptions().ideawrite.contains(IjOptionConstants.ideawrite_all)) {
injector.nativeActionManager.saveAll
} else {
injector.nativeActionManager.saveCurrent
}
action.execute(context)
action.execute(editor, context)
}
/**
* Saves all files in the project.
*/
override fun saveFiles(context: ExecutionContext) {
injector.nativeActionManager.saveAll.execute(context)
override fun saveFiles(editor: VimEditor, context: ExecutionContext) {
injector.nativeActionManager.saveAll.execute(editor, context)
}
/**
@@ -271,7 +271,7 @@ class FileGroup : VimFileBase() {
val msg = StringBuilder()
val doc = editor.document
if (getInstance(IjVimEditor(editor)).mode !is VISUAL) {
if (injector.vimState.mode !is VISUAL) {
val lp = editor.caretModel.logicalPosition
val col = editor.caretModel.offset - doc.getLineStartOffset(lp.line)
var endoff = doc.getLineEndOffset(lp.line)

View File

@@ -26,7 +26,7 @@ class IjVimPsiService: VimPsiService {
val psiFile = PsiHelper.getFile(editor.ij) ?: return null
val psiElement = psiFile.findElementAt(pos) ?: return null
val language = psiElement.language
val commenter = LanguageCommenters.INSTANCE.forLanguage(language)
val commenter = LanguageCommenters.INSTANCE.forLanguage(language) ?: return null
val psiComment = PsiTreeUtil.getParentOfType(psiElement, PsiComment::class.java, false) ?: return null
val commentText = psiComment.text

View File

@@ -47,12 +47,14 @@ import com.maddyhome.idea.vim.helper.getNormalizedScrollOffset
import com.maddyhome.idea.vim.helper.getNormalizedSideScrollOffset
import com.maddyhome.idea.vim.helper.isEndAllowed
import com.maddyhome.idea.vim.helper.vimLastColumn
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl
import com.maddyhome.idea.vim.listener.AppCodeTemplates
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.ReturnTo
import com.maddyhome.idea.vim.state.mode.returnTo
import org.jetbrains.annotations.Range
import kotlin.math.max
import kotlin.math.min
@@ -307,19 +309,32 @@ internal class MotionGroup : VimMotionGroupBase() {
val editor = fileEditor.editor
if (!editor.isDisposed) {
editor.vim.let { vimEditor ->
when (vimEditor.vimStateMachine.mode) {
when (vimEditor.mode) {
is Mode.VISUAL -> {
vimEditor.exitVisualMode()
KeyHandler.getInstance().reset(vimEditor)
}
is Mode.CMD_LINE -> {
injector.processGroup.cancelExEntry(vimEditor, false)
val commandLine = injector.commandLine.getActiveCommandLine() ?: return
commandLine.close(refocusOwningEditor = false, resetCaret = false)
ExOutputModel.tryGetInstance(editor)?.close()
}
else -> {}
}
}
}
} else {
val state = injector.vimState as VimStateMachineImpl
if (state.mode is Mode.VISUAL) {
val returnTo = state.mode.returnTo
when (returnTo) {
ReturnTo.INSERT -> state.mode = Mode.INSERT
ReturnTo.REPLACE -> state.mode = Mode.REPLACE
null -> state.mode = Mode.NORMAL()
}
}
val keyHandler = KeyHandler.getInstance()
KeyHandler.getInstance().reset(keyHandler.keyHandlerState, state.mode)
}
}
}

View File

@@ -62,6 +62,25 @@ internal class NotificationService(private val project: Project?) {
@Suppress("unused")
constructor() : this(null)
fun notifyAboutNewUndo() {
val notification = Notification(
IDEAVIM_NOTIFICATION_ID,
"Undo in IdeaVim now works like in Vim",
"""
Caret movement is no longer a separate undo step, and full insert is undoable in one step.
""".trimIndent(),
NotificationType.INFORMATION,
)
notification.addAction(object : DumbAwareAction("Share Feedback") {
override fun actionPerformed(p0: AnActionEvent) {
BrowserUtil.browse("https://youtrack.jetbrains.com/issue/VIM-547/Undo-splits-Insert-mode-edits-into-separate-undo-chunks")
}
})
notification.notify(project)
}
fun notifyAboutIdeaPut() {
val notification = Notification(
IDEAVIM_NOTIFICATION_ID,
@@ -182,8 +201,8 @@ internal class NotificationService(private val project: Project?) {
).notify(project)
}
fun notifyActionId(id: String?) {
ActionIdNotifier.notifyActionId(id, project)
fun notifyActionId(id: String?, candidates: List<String>? = null) {
ActionIdNotifier.notifyActionId(id, project, candidates)
}
fun notifyKeymapIssues(issues: ArrayList<KeyMapIssue>) {
@@ -259,20 +278,31 @@ internal class NotificationService(private val project: Project?) {
object ActionIdNotifier {
private var notification: Notification? = null
private const val NO_ID = "<i>Cannot detect action id</i>"
fun notifyActionId(id: String?, project: Project?) {
fun notifyActionId(id: String?, project: Project?, candidates: List<String>? = null) {
notification?.expire()
val content = if (id != null) "Action id: $id" else NO_ID
Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, content, NotificationType.INFORMATION).let {
notification = it
it.whenExpired { notification = null }
it.setContent(it.content + "<br><br><small>Use ${ActionCenter.getToolwindowName()} to see previous ids</small>")
val possibleIDs = candidates?.distinct()?.sorted()
val content = when {
id != null -> "Action ID: <code>$id</code><br><br>"
possibleIDs.isNullOrEmpty() -> "<i>Cannot detect action ID</i><br><br>"
possibleIDs.size == 1 -> "Possible action ID: <code>${possibleIDs[0]}</code><br><br>"
else -> {
buildString {
append("<p>Multiple possible action IDs. Candidates include:<ul>")
possibleIDs.forEach { append("<li><code>$it</code></li>") }
append("</ul></p>")
}
}
} + "<small>See the ${ActionCenter.getToolwindowName()} tool window for previous IDs</small>"
notification = Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, content, NotificationType.INFORMATION).also {
it.whenExpired { notification = null }
it.addAction(StopTracking())
if (id != null) it.addAction(CopyActionId(id, project))
if (id != null || possibleIDs?.size == 1) {
it.addAction(CopyActionId(id ?: possibleIDs?.get(0), project))
}
it.notify(project)
}

View File

@@ -95,8 +95,6 @@ class ProcessGroup : VimProcessGroupBase() {
val progressIndicator = ProgressIndicatorProvider.getInstance().progressIndicator
val output = handler.runProcessWithProgressIndicator(progressIndicator)
lastCommand = command
if (output.isCancelled) {
// TODO: Vim will use whatever text has already been written to stdout
// For whatever reason, we're not getting any here, so just throw an exception

View File

@@ -14,6 +14,8 @@ import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.diagnostic.Logger;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.VimInjectorKt;
import com.maddyhome.idea.vim.newapi.IjVimInjectorKt;
import com.maddyhome.idea.vim.register.Register;
import com.maddyhome.idea.vim.register.VimRegisterGroupBase;
import com.maddyhome.idea.vim.state.mode.SelectionType;
@@ -35,6 +37,10 @@ import java.util.List;
})
public class RegisterGroup extends VimRegisterGroupBase implements PersistentStateComponent<Element> {
static {
IjVimInjectorKt.initInjector();
}
private static final Logger logger = Logger.getInstance(RegisterGroup.class);
public RegisterGroup() {

View File

@@ -25,6 +25,7 @@ import com.maddyhome.idea.vim.mark.Jump
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.initInjector
import org.jdom.Element
@State(name = "VimJumpsSettings", storages = [Storage(value = "\$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)])
@@ -65,6 +66,7 @@ internal class VimJumpServiceImpl : VimJumpServiceBase(), PersistentStateCompone
}
override fun loadState(state: Element) {
initInjector()
val projectElements = state.getChildren("project")
for (projectElement in projectElements) {
val jumps = mutableListOf<Jump>()
@@ -89,6 +91,7 @@ internal class VimJumpServiceImpl : VimJumpServiceBase(), PersistentStateCompone
internal class JumpsListener(val project: Project) : RecentPlacesListener {
override fun recentPlaceAdded(changePlace: PlaceInfo, isChanged: Boolean) {
initInjector()
if (!injector.globalIjOptions().unifyjumps) return
val jumpService = injector.jumpService

View File

@@ -46,6 +46,7 @@ import com.maddyhome.idea.vim.put.PutData
import com.maddyhome.idea.vim.put.VimPasteProvider
import com.maddyhome.idea.vim.put.VimPutBase
import com.maddyhome.idea.vim.register.RegisterConstants
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.isBlock
import com.maddyhome.idea.vim.state.mode.isChar
@@ -83,7 +84,7 @@ internal class PutGroup : VimPutBase() {
val editor = (vimEditor as IjVimEditor).editor
val context = vimContext.context as DataContext
val carets: MutableMap<Caret, RangeMarker> = mutableMapOf()
if (editor.isInsertMode) {
if (injector.vimState.mode is Mode.INSERT) {
val undo = injector.undo
val nanoTime = System.nanoTime()
vimEditor.forEachCaret { undo.startInsertSequence(it, it.offset, nanoTime) }

View File

@@ -16,6 +16,7 @@ import com.intellij.openapi.keymap.ex.KeymapManagerEx
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.ProjectActivity
import com.maddyhome.idea.vim.api.key
import com.maddyhome.idea.vim.newapi.initInjector
/**
* Logs the chain of handlers for esc and enter
@@ -34,6 +35,8 @@ internal class EditorHandlersChainLogger : ProjectActivity {
private val editorHandlers = ExtensionPointName<EditorActionHandlerBean>("com.intellij.editorActionHandler")
override suspend fun execute(project: Project) {
initInjector()
if (!enableOctopus) return
val escHandlers = editorHandlers.extensionList

View File

@@ -19,7 +19,7 @@ import com.maddyhome.idea.vim.command.OperatorArguments
*/
internal abstract class IdeActionHandler(private val actionName: String) : VimActionHandler.SingleExecution() {
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
injector.actionExecutor.executeAction(actionName, context)
injector.actionExecutor.executeAction(editor, name = actionName, context = context)
injector.scroll.scrollCaretIntoView(editor)
return true
}

View File

@@ -19,6 +19,7 @@ import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.EditorListener
import com.maddyhome.idea.vim.common.IsReplaceCharListener
import com.maddyhome.idea.vim.common.ModeChangeListener
import com.maddyhome.idea.vim.newapi.IjVimEditor
@@ -73,7 +74,7 @@ internal object GuicursorChangeListener : EffectiveOptionValueChangeListener {
}
private fun Editor.guicursorMode(): GuiCursorMode {
return GuiCursorMode.fromMode(vim.mode, vim.vimStateMachine.isReplaceCharacter)
return GuiCursorMode.fromMode(vim.mode, injector.vimState.isReplaceCharacter)
}
/**
@@ -86,6 +87,7 @@ private fun isBlockCursorOverride() = EditorSettingsExternalizable.getInstance()
private fun Editor.updatePrimaryCaretVisualAttributes() {
if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
if (isIdeaVimDisabledHere) return
caretModel.primaryCaret.visualAttributes = AttributesCache.getCaretVisualAttributes(this)
// Make sure the caret is visible as soon as it's set. It might be invisible while blinking
@@ -99,6 +101,7 @@ private fun Editor.updatePrimaryCaretVisualAttributes() {
private fun Editor.updateSecondaryCaretsVisualAttributes() {
if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
if (isIdeaVimDisabledHere) return
// IntelliJ simulates visual block with multiple carets with selections. Do our best to hide them
val attributes = if (this.vim.inBlockSelection) HIDDEN else AttributesCache.getCaretVisualAttributes(this)
this.caretModel.allCarets.forEach {
@@ -144,7 +147,7 @@ private object AttributesCache {
@TestOnly
internal fun getGuiCursorMode(editor: Editor) = editor.guicursorMode()
class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener {
class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener, EditorListener {
override fun isReplaceCharChanged(editor: VimEditor) {
updateCaretsVisual(editor)
}
@@ -153,21 +156,19 @@ class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener
updateCaretsVisual(editor)
}
override fun focusGained(editor: VimEditor) {
updateCaretsVisual(editor)
}
private fun updateCaretsVisual(editor: VimEditor) {
if (injector.globalOptions().ideaglobalmode) {
updateAllEditorsCaretsVisual()
} else {
val ijEditor = (editor as IjVimEditor).editor
ijEditor.updateCaretsVisualAttributes()
ijEditor.updateCaretsVisualPosition()
}
val ijEditor = (editor as IjVimEditor).editor
ijEditor.updateCaretsVisualAttributes()
ijEditor.updateCaretsVisualPosition()
}
fun updateAllEditorsCaretsVisual() {
injector.editorGroup.getEditors().forEach { editor ->
val ijEditor = (editor as IjVimEditor).editor
ijEditor.updateCaretsVisualAttributes()
ijEditor.updateCaretsVisualPosition()
updateCaretsVisual(editor)
}
}
}

View File

@@ -34,4 +34,4 @@ val Editor.inVisualMode: Boolean
@get:JvmName("inExMode")
internal val Editor.inExMode
get() = this.vim.vimStateMachine.mode is Mode.CMD_LINE
get() = this.vim.mode is Mode.CMD_LINE

View File

@@ -13,23 +13,23 @@ import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.AnActionResult
import com.intellij.openapi.actionSystem.DataContextWrapper
import com.intellij.openapi.actionSystem.EmptyAction
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.actionSystem.ex.ActionManagerEx
import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.actionSystem.ex.ActionUtil.performDumbAwareWithCallbacks
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
import com.intellij.openapi.actionSystem.impl.Utils
import com.intellij.openapi.application.ex.ApplicationManagerEx
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.UndoConfirmationPolicy
import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.editor.actionSystem.DocCommandGroupId
import com.intellij.openapi.project.IndexNotReadyException
import com.intellij.openapi.progress.util.ProgressIndicatorUtils
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.util.NlsContexts
import com.intellij.util.SlowOperations
import com.maddyhome.idea.vim.RegisterActions
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.NativeAction
@@ -69,6 +69,12 @@ internal class IjActionExecutor : VimActionExecutor {
* @param context The context to run it in
*/
override fun executeAction(editor: VimEditor?, action: NativeAction, context: ExecutionContext): Boolean {
val applicationEx = ApplicationManagerEx.getApplicationEx()
if (ProgressIndicatorUtils.isWriteActionRunningOrPending(applicationEx)) {
// This is needed for VIM-3376 and it should turn into error at soeme moment
thisLogger().warn(RuntimeException("Actions cannot be updated when write-action is running or pending", ))
}
val ijAction = (action as IjNativeAction).action
/**
@@ -121,51 +127,15 @@ internal class IjActionExecutor : VimActionExecutor {
}
}
// This is taken directly from ActionUtil.performActionDumbAwareWithCallbacks
// But with one check removed. With this check some actions (like `:w` doesn't work)
// https://youtrack.jetbrains.com/issue/VIM-2691/File-is-not-saved-on-w
private fun performDumbAwareWithCallbacks(
action: AnAction,
event: AnActionEvent,
performRunnable: Runnable,
) {
val project = event.project
var indexError: IndexNotReadyException? = null
val manager = ActionManagerEx.getInstanceEx()
manager.fireBeforeActionPerformed(action, event)
var result: AnActionResult? = null
try {
SlowOperations.allowSlowOperations(SlowOperations.ACTION_PERFORM).use {
performRunnable.run()
result = AnActionResult.PERFORMED
}
} catch (ex: IndexNotReadyException) {
indexError = ex
result = AnActionResult.failed(ex)
} catch (ex: RuntimeException) {
result = AnActionResult.failed(ex)
throw ex
} catch (ex: Error) {
result = AnActionResult.failed(ex)
throw ex
} finally {
if (result == null) result = AnActionResult.failed(Throwable())
manager.fireAfterActionPerformed(action, event, result!!)
}
if (indexError != null) {
ActionUtil.showDumbModeWarning(project, action, event)
}
}
/**
* Execute an action by name
*
* @param name The name of the action to execute
* @param context The context to run it in
*/
override fun executeAction(name: @NonNls String, context: ExecutionContext): Boolean {
override fun executeAction(editor: VimEditor, name: @NonNls String, context: ExecutionContext): Boolean {
val action = getAction(name, context)
return action != null && executeAction(null, IjNativeAction(action), context)
return action != null && executeAction(editor, IjNativeAction(action), context)
}
private fun getAction(name: String, context: ExecutionContext): AnAction? {
@@ -211,8 +181,8 @@ internal class IjActionExecutor : VimActionExecutor {
CommandProcessor.getInstance().executeCommand(editor?.ij?.project, runnable, name, groupId)
}
override fun executeEsc(context: ExecutionContext): Boolean {
return executeAction(IdeActions.ACTION_EDITOR_ESCAPE, context)
override fun executeEsc(editor: VimEditor, context: ExecutionContext): Boolean {
return executeAction(editor, IdeActions.ACTION_EDITOR_ESCAPE, context)
}
override fun executeVimAction(

View File

@@ -14,6 +14,7 @@ import com.intellij.openapi.editor.VisualPosition
import com.intellij.openapi.editor.actionSystem.EditorActionManager
import com.intellij.openapi.editor.ex.util.EditorUtil
import com.maddyhome.idea.vim.api.EngineEditorHelper
import com.maddyhome.idea.vim.api.EngineEditorHelperBase
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimRangeMarker
import com.maddyhome.idea.vim.api.VimVisualPosition
@@ -22,7 +23,7 @@ import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
@Service
internal class IjEditorHelper : EngineEditorHelper {
internal class IjEditorHelper : EngineEditorHelperBase() {
override fun amountOfInlaysBeforeVisualPosition(editor: VimEditor, pos: VimVisualPosition): Int {
return (editor as IjVimEditor).editor.amountOfInlaysBeforeVisualPosition(
VisualPosition(
@@ -51,10 +52,6 @@ internal class IjEditorHelper : EngineEditorHelper {
return EditorHelper.getVisualLineAtBottomOfScreen(editor.ij)
}
override fun pad(editor: VimEditor, line: Int, to: Int): String {
return EditorHelper.pad(editor.ij, line, to)
}
override fun inlayAwareOffsetToVisualPosition(editor: VimEditor, offset: Int): VimVisualPosition {
return EditorUtil.inlayAwareOffsetToVisualPosition(editor.ij, offset).vim
}

View File

@@ -31,7 +31,7 @@ import com.maddyhome.idea.vim.state.mode.returnTo
internal fun Editor.exitSelectMode(adjustCaretPosition: Boolean) {
if (!this.vim.inSelectMode) return
val returnTo = this.vim.vimStateMachine.mode.returnTo
val returnTo = this.vim.mode.returnTo
when (returnTo) {
ReturnTo.INSERT -> {
this.vim.mode = Mode.INSERT
@@ -64,7 +64,7 @@ internal fun Editor.exitSelectMode(adjustCaretPosition: Boolean) {
internal fun VimEditor.exitSelectMode(adjustCaretPosition: Boolean) {
if (!this.inSelectMode) return
val returnTo = this.vimStateMachine.mode.returnTo
val returnTo = this.mode.returnTo
when (returnTo) {
ReturnTo.INSERT -> {
this.mode = Mode.INSERT

View File

@@ -180,7 +180,7 @@ internal object ScrollViewHelper {
}
private fun getScrollJump(editor: VimEditor, height: Int): Int {
val flags = VimStateMachine.getInstance(editor).executingCommandFlags
val flags = injector.vimState.executingCommandFlags
val scrollJump = !flags.contains(CommandFlags.FLAG_IGNORE_SCROLL_JUMP)
// Default value is 1. Zero is a valid value, but we normalise to 1 - we always want to scroll at least one line
@@ -203,7 +203,7 @@ internal object ScrollViewHelper {
val caretColumn = position.column
val halfWidth = getApproximateScreenWidth(editor) / 2
val scrollOffset = getNormalizedSideScrollOffset(editor)
val flags = VimStateMachine.getInstance(vimEditor).executingCommandFlags
val flags = injector.vimState.executingCommandFlags
val allowSidescroll = !flags.contains(CommandFlags.FLAG_IGNORE_SIDE_SCROLL_JUMP)
val sidescroll = injector.options(vimEditor).sidescroll
val offsetLeft = caretColumn - (currentVisualLeftColumn + scrollOffset)

View File

@@ -16,18 +16,15 @@ import com.intellij.openapi.editor.Editor
import com.intellij.spellchecker.SpellCheckerSeveritiesProvider
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.getLineEndOffset
import com.maddyhome.idea.vim.api.getText
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.helper.CharacterHelper.charType
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.vim
import it.unimi.dsi.fastutil.ints.IntComparator
import it.unimi.dsi.fastutil.ints.IntIterator
import it.unimi.dsi.fastutil.ints.IntRBTreeSet
import it.unimi.dsi.fastutil.ints.IntSortedSet
import java.util.*
/**
* Check ignorecase and smartcase options to see if a case insensitive search should be performed with the given pattern.
@@ -97,210 +94,6 @@ fun countWords(
return CountPosition(count, position)
}
fun findNumbersInRange(
editor: Editor,
textRange: TextRange,
alpha: Boolean,
hex: Boolean,
octal: Boolean,
): List<Pair<TextRange, NumberType>> {
val result: MutableList<Pair<TextRange, NumberType>> = ArrayList()
for (i in 0 until textRange.size()) {
val startOffset = textRange.startOffsets[i]
val end = textRange.endOffsets[i]
val text: String = editor.vim.getText(startOffset, end)
val textChunks = text.split("\\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
var chunkStart = 0
for (chunk in textChunks) {
val number = findNumberInText(chunk, 0, alpha, hex, octal)
if (number != null) {
result.add(
Pair(
TextRange(
number.first.startOffset + startOffset + chunkStart,
number.first.endOffset + startOffset + chunkStart
),
number.second
)
)
}
chunkStart += 1 + chunk.length
}
}
return result
}
fun findNumberUnderCursor(
editor: Editor,
caret: Caret,
alpha: Boolean,
hex: Boolean,
octal: Boolean,
): Pair<TextRange, NumberType>? {
val lline = caret.logicalPosition.line
val text = IjVimEditor(editor).getLineText(lline).lowercase(Locale.getDefault())
val startLineOffset = IjVimEditor(editor).getLineStartOffset(lline)
val posOnLine = caret.offset - startLineOffset
val numberTextRange = findNumberInText(text, posOnLine, alpha, hex, octal) ?: return null
return Pair(
TextRange(
numberTextRange.first.startOffset + startLineOffset,
numberTextRange.first.endOffset + startLineOffset
),
numberTextRange.second
)
}
/**
* Search for number in given text from start position
*
* @param textInRange - text to search in
* @param startPosOnLine - start offset to search
* @return - text range with number
*/
fun findNumberInText(
textInRange: String,
startPosOnLine: Int,
alpha: Boolean,
hex: Boolean,
octal: Boolean,
): Pair<TextRange, NumberType>? {
if (logger.isDebugEnabled) {
logger.debug("text=$textInRange")
}
var pos = startPosOnLine
val lineEndOffset = textInRange.length
while (true) {
// Skip over current whitespace if any
while (pos < lineEndOffset && !isNumberChar(textInRange[pos], alpha, hex, octal, true)) {
pos++
}
if (logger.isDebugEnabled) logger.debug("pos=$pos")
if (pos >= lineEndOffset) {
logger.debug("no number char on line")
return null
}
val isHexChar = "abcdefABCDEF".indexOf(textInRange[pos]) >= 0
if (hex) {
// Ox and OX handling
if (textInRange[pos] == '0' && pos < lineEndOffset - 1 && "xX".indexOf(textInRange[pos + 1]) >= 0) {
pos += 2
} else if ("xX".indexOf(textInRange[pos]) >= 0 && pos > 0 && textInRange[pos - 1] == '0') {
pos++
}
logger.debug("checking hex")
val range = findRange(textInRange, pos, false, true, false, false)
val start = range.first
val end = range.second
// Ox and OX
if (start >= 2 && textInRange.substring(start - 2, start).equals("0x", ignoreCase = true)) {
logger.debug("found hex")
return Pair(TextRange(start - 2, end), NumberType.HEX)
}
if (!isHexChar || alpha) {
break
} else {
pos++
}
} else {
break
}
}
if (octal) {
logger.debug("checking octal")
val range = findRange(textInRange, pos, false, false, true, false)
val start = range.first
val end = range.second
if (end - start == 1 && textInRange[start] == '0') {
return Pair(TextRange(start, end), NumberType.DEC)
}
if (textInRange[start] == '0' && end > start &&
!(start > 0 && isNumberChar(textInRange[start - 1], false, false, false, true))
) {
logger.debug("found octal")
return Pair(TextRange(start, end), NumberType.OCT)
}
}
if (alpha) {
if (logger.isDebugEnabled) logger.debug("checking alpha for " + textInRange[pos])
if (isNumberChar(textInRange[pos], true, false, false, false)) {
if (logger.isDebugEnabled) logger.debug("found alpha at $pos")
return Pair(TextRange(pos, pos + 1), NumberType.ALPHA)
}
}
val range = findRange(textInRange, pos, false, false, false, true)
var start = range.first
val end = range.second
if (start > 0 && textInRange[start - 1] == '-') {
start--
}
return Pair(TextRange(start, end), NumberType.DEC)
}
/**
* Searches for digits block that matches parameters
*/
private fun findRange(
text: String,
pos: Int,
alpha: Boolean,
hex: Boolean,
octal: Boolean,
decimal: Boolean,
): Pair<Int, Int> {
var end = pos
while (end < text.length && isNumberChar(text[end], alpha, hex, octal, decimal || octal)) {
end++
}
var start = pos
while (start >= 0 && isNumberChar(text[start], alpha, hex, octal, decimal || octal)) {
start--
}
if (start < end &&
(start == -1 ||
0 <= start && start < text.length &&
!isNumberChar(text[start], alpha, hex, octal, decimal || octal))
) {
start++
}
if (octal) {
for (i in start until end) {
if (!isNumberChar(text[i], false, false, true, false)) return Pair(0, 0)
}
}
return Pair(start, end)
}
private fun isNumberChar(ch: Char, alpha: Boolean, hex: Boolean, octal: Boolean, decimal: Boolean): Boolean {
return if (alpha && ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))) {
true
} else if (octal && (ch >= '0' && ch <= '7')) {
true
} else if (hex && ((ch >= '0' && ch <= '9') || "abcdefABCDEF".indexOf(ch) >= 0)) {
true
} else {
decimal && (ch >= '0' && ch <= '9')
}
}
/**
* Find the word under the cursor or the next word to the right of the cursor on the current line.
*

View File

@@ -16,6 +16,7 @@ import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.registry.Registry
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
@@ -23,8 +24,8 @@ import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.ChangesListener
import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.common.InsertSequence
import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.SelectionType
@@ -70,6 +71,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
restoreVisualMode(editor)
}
} else {
notifyAboutNewUndo(editor.ij.project)
runWithBooleanRegistryOption("ide.undo.transparent.caret.movement", true) {
var nextUndoNanoTime = undoManager.getNextUndoNanoTime(fileEditor)
val insertInfo = (editor.primaryCaret() as IjVimCaret).getInsertSequenceForTime(nextUndoNanoTime)
@@ -90,6 +92,13 @@ internal class UndoRedoHelper : UndoRedoBase() {
}
}
private fun notifyAboutNewUndo(project: Project?) {
if (VimPlugin.getVimState().isNewUndoNotified) return
VimPlugin.getVimState().isNewUndoNotified = true
VimPlugin.getNotifications(project).notifyAboutNewUndo()
}
private fun hasSelection(editor: VimEditor): Boolean {
return editor.primaryCaret().ij.hasSelection()
}

View File

@@ -103,7 +103,6 @@ internal var Editor.vimInitialised: Boolean by userDataOr { false }
// ------------------ Editor
internal fun unInitializeEditor(editor: Editor) {
editor.vimLastSelectionType = null
editor.vimStateMachine = null
editor.vimMorePanel = null
editor.vimExOutput = null
editor.vimLastHighlighters = null
@@ -118,7 +117,6 @@ internal var Editor.vimIncsearchCurrentMatchOffset: Int? by userData()
* @see :help visualmode()
*/
internal var Editor.vimLastSelectionType: SelectionType? by userData()
internal var Editor.vimStateMachine: VimStateMachine? by userData()
internal var Editor.vimEditorGroup: Boolean by userDataOr { false }
internal var Editor.vimHasRelativeLineNumbersInstalled: Boolean by userDataOr { false }
internal var Editor.vimMorePanel: ExOutputPanel? by userData()

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2003-2024 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.listener
import com.intellij.execution.impl.ConsoleViewImpl
import com.intellij.openapi.application.ApplicationManager
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.EditorListener
import com.maddyhome.idea.vim.helper.inInsertMode
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.Mode
/**
* This listener is similar to the one we introduce in vim-engine to handle focus change,
* However, in IJ we would like to start editing in some editors in INSERT mode (e.g., consoles)
* It is different to we had previously. Now we go to INSERT mode not only when we focus on the console the first time, but every time.
* Going to INSERT on every focus is easier to implement and more consistent (behavior is always the same, you don't have to remember if you are focusing a console the first time or not)
*/
class IJEditorFocusListener : EditorListener {
override fun focusGained(editor: VimEditor) {
val editorInFocus = KeyHandler.getInstance().editorInFocus
if (editorInFocus != null && editorInFocus.ij == editor.ij) return
KeyHandler.getInstance().editorInFocus = editor
// We add Vim bindings to all opened editors, including editors used as UI controls rather than just project file
// editors. This includes editors used as part of the UI, such as the VCS commit message, or used as read-only
// viewers for text output, such as log files in run configurations or the Git Console tab. And editors are used for
// interactive stdin/stdout for console-based run configurations.
// We want to provide an intuitive experience for working with these additional editors, so we automatically switch
// to INSERT mode if they are interactive editors. Recognising these can be a bit tricky.
// These additional interactive editors are not file-based, but must have a writable document. However, log output
// documents are also writable (the IDE is writing new content as it becomes available) just not user-editable. So
// we must also check that the editor is not in read-only "viewer" mode (this includes "rendered" mode, which is
// read-only and also hides the caret).
// Furthermore, interactive stdin/stdout console output in run configurations is hosted in a read-only editor, but
// it can still be edited. The `ConsoleViewImpl` class installs a typing handler that ignores the editor's
// `isViewer` property and allows typing if the associated process (if any) is still running. We can get the
// editor's console view and check this ourselves, but we have to wait until the editor has finished initialising
// before it's available in user data.
// Finally, we have a special check for diff windows. If we compare against clipboard, we get a diff editor that is
// not file based, is writable, and not a viewer, but we don't want to treat this as an interactive editor.
// Note that we need a similar check in `VimEditor.isWritable` to allow Escape to work to exit insert mode. We need
// to know that a read-only editor that is hosting a console view with a running process can be treated as writable.
val ijEditor = editor.ij
val switchToInsertMode = Runnable {
val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor)
VimPlugin.getChange().insertBeforeCursor(editor, context)
}
if (!ijEditor.document.isWritable) {
val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor)
val mode = injector.vimState.mode
when (mode) {
is Mode.INSERT -> editor.exitInsertMode(context, OperatorArguments(false, 0, mode))
else -> {}
}
}
ApplicationManager.getApplication().invokeLater {
if (ijEditor.isDisposed) return@invokeLater
val consoleView: ConsoleViewImpl? = ijEditor.getUserData(ConsoleViewImpl.CONSOLE_VIEW_IN_EDITOR_VIEW)
if (consoleView != null && consoleView.isRunning && !ijEditor.inInsertMode) {
switchToInsertMode.run()
}
}
KeyHandler.getInstance().reset(editor)
}
}

View File

@@ -29,6 +29,7 @@ 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.keymap.KeymapManager
import com.intellij.openapi.project.DumbAwareToggleAction
import com.intellij.openapi.util.TextRange
import com.maddyhome.idea.vim.KeyHandler
@@ -37,8 +38,8 @@ import com.maddyhome.idea.vim.action.VimShortcutKeyAction
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.group.NotificationService
import com.maddyhome.idea.vim.helper.isIdeaVimDisabledHere
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.initInjector
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inNormalMode
@@ -61,6 +62,7 @@ internal object IdeaSpecifics {
private var caretOffset = -1
private var completionPrevDocumentLength: Int? = null
private var completionPrevDocumentOffset: Int? = null
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
if (VimPlugin.isNotEnabled()) return
@@ -74,7 +76,24 @@ internal object IdeaSpecifics {
if (!isVimAction && injector.globalIjOptions().trackactionids) {
if (action !is NotificationService.ActionIdNotifier.CopyActionId && action !is NotificationService.ActionIdNotifier.StopTracking) {
val id: String? = ActionManager.getInstance().getId(action) ?: (action.shortcutSet as? ProxyShortcutSet)?.actionId
VimPlugin.getNotifications(event.dataContext.getData(CommonDataKeys.PROJECT)).notifyActionId(id)
val candidates = if (id == null) {
// Some actions are specific to the component they're registered for, and are copies of a global action,
// reusing the action ID and shortcuts (e.g. `NextTab` is different for editor tabs and tool window tabs).
// Unfortunately, ActionManager doesn't know about these "local" actions, so can't return the action ID.
// However, the new "local" action does copy the shortcuts of the global template action, so we can look up
// all actions with matching shortcuts. We might return more action IDs than expected, so this is a list of
// candidates, not a definite match of the action being executed, but the list should include our target
// action. Note that we might return duplicate IDs because the keymap might have multiple shortcuts mapped
// to the same action. The notifier will handle de-duplication and sorting as a presentation detail.
action.shortcutSet.shortcuts.flatMap { KeymapManager.getInstance().activeKeymap.getActionIdList(it) }
}
else {
emptyList()
}
// We can still get empty ID and empty candidates. Notably, for the tool window toggle buttons on the new UI.
// We could filter out action events with `place == ActionPlaces.TOOLWINDOW_TOOLBAR_BAR`
VimPlugin.getNotifications(event.dataContext.getData(CommonDataKeys.PROJECT)).notifyActionId(id, candidates)
}
}
@@ -128,7 +147,6 @@ internal object IdeaSpecifics {
}
) {
editor?.let {
val commandState = it.vim.vimStateMachine
it.vim.mode = Mode.NORMAL()
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
KeyHandler.getInstance().reset(it.vim)
@@ -227,9 +245,13 @@ internal object IdeaSpecifics {
//region Find action ID
internal class FindActionIdAction : DumbAwareToggleAction() {
override fun isSelected(e: AnActionEvent): Boolean = injector.globalIjOptions().trackactionids
override fun isSelected(e: AnActionEvent): Boolean {
initInjector()
return injector.globalIjOptions().trackactionids
}
override fun setSelected(e: AnActionEvent, state: Boolean) {
initInjector()
injector.globalIjOptions().trackactionids = !injector.globalIjOptions().trackactionids
}

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
@@ -9,7 +9,18 @@
package com.maddyhome.idea.vim.listener
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.common.ModeChangeListener
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.Mode
@Deprecated(message = "Please use ModeChangeListener", replaceWith = ReplaceWith("ModeChangeListener", imports = ["import com.maddyhome.idea.vim.common.ModeChangeListener"]))
interface VimInsertListener : ModeChangeListener {
override fun modeChanged(editor: VimEditor, oldMode: Mode) {
if (editor.mode == Mode.INSERT) {
insertModeStarted(editor.ij)
}
}
interface VimInsertListener {
fun insertModeStarted(editor: Editor)
}

View File

@@ -56,7 +56,6 @@ import com.intellij.util.ExceptionUtil
import com.jetbrains.rd.util.lifetime.Lifetime
import com.maddyhome.idea.vim.EventFacade
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.KeyHandlerStateResetter
import com.maddyhome.idea.vim.VimKeyListener
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.VimTypedActionHandler
@@ -95,8 +94,8 @@ import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
import com.maddyhome.idea.vim.helper.vimDisabled
import com.maddyhome.idea.vim.helper.vimInitialised
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.InsertTimeRecorder
import com.maddyhome.idea.vim.newapi.IjVimSearchGroup
import com.maddyhome.idea.vim.newapi.InsertTimeRecorder
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
@@ -162,6 +161,7 @@ internal object VimListenerManager {
check(keyCheckRequests.tryEmit(Unit))
val caretVisualAttributesListener = CaretVisualAttributesListener()
injector.listenersNotifier.myEditorListeners.add(caretVisualAttributesListener)
injector.listenersNotifier.modeChangeListeners.add(caretVisualAttributesListener)
injector.listenersNotifier.isReplaceCharListeners.add(caretVisualAttributesListener)
caretVisualAttributesListener.updateAllEditorsCaretsVisual()
@@ -178,7 +178,7 @@ internal object VimListenerManager {
injector.listenersNotifier.macroRecordingListeners.add(macroWidgetListener)
injector.listenersNotifier.vimPluginListeners.add(macroWidgetListener)
injector.listenersNotifier.myEditorListeners.add(KeyHandlerStateResetter())
injector.listenersNotifier.myEditorListeners.add(IJEditorFocusListener())
injector.listenersNotifier.myEditorListeners.add(ShowCmdWidgetUpdater())
}
@@ -395,6 +395,7 @@ internal object VimListenerManager {
editor.vim.mode = Mode.NORMAL()
KeyHandler.getInstance().reset(editor.vim)
}
injector.scroll.scrollCaretIntoView(editor.vim)
}
MotionGroup.fileEditorManagerSelectionChangedCallback(event)
@@ -744,10 +745,8 @@ internal object VimListenerManager {
if (event.area == EditorMouseEventArea.EDITING_AREA) {
val editor = event.editor
val commandLine = injector.commandLine.getActiveCommandLine()
if (commandLine != null) {
injector.processGroup.cancelExEntry(editor.vim, false)
}
injector.commandLine.getActiveCommandLine()?.close(refocusOwningEditor = true, resetCaret = false)
injector.modalInput.getCurrentModalInput()?.deactivate(refocusOwningEditor = true, resetCaret = false)
ExOutputModel.tryGetInstance(editor)?.close()
@@ -775,10 +774,8 @@ internal object VimListenerManager {
event.area != EditorMouseEventArea.FOLDING_OUTLINE_AREA &&
event.mouseEvent.button != MouseEvent.BUTTON3
) {
val commandLine = injector.commandLine.getActiveCommandLine()
if (commandLine != null) {
injector.processGroup.cancelExEntry(event.editor.vim, false)
}
injector.commandLine.getActiveCommandLine()?.close(refocusOwningEditor = true, resetCaret = false)
injector.modalInput.getCurrentModalInput()?.deactivate(refocusOwningEditor = true, resetCaret = false)
ExOutputModel.getInstance(event.editor).close()
}

View File

@@ -11,12 +11,13 @@ package com.maddyhome.idea.vim.newapi
import com.intellij.openapi.editor.VisualPosition
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.NativeAction
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimVisualPosition
import com.maddyhome.idea.vim.api.injector
internal fun NativeAction?.execute(context: ExecutionContext) {
internal fun NativeAction?.execute(editor: VimEditor, context: ExecutionContext) {
if (this == null) return
injector.actionExecutor.executeAction(null, this, context)
injector.actionExecutor.executeAction(editor, this, context)
}
internal val VisualPosition.vim: VimVisualPosition

View File

@@ -30,7 +30,9 @@ import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimCaretListener
import com.maddyhome.idea.vim.api.VimDocument
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimEditorBase
import com.maddyhome.idea.vim.api.VimFoldRegion
import com.maddyhome.idea.vim.api.VimIndentConfig
import com.maddyhome.idea.vim.api.VimScrollingModel
import com.maddyhome.idea.vim.api.VimSelectionModel
import com.maddyhome.idea.vim.api.VimVisualPosition
@@ -38,6 +40,7 @@ import com.maddyhome.idea.vim.api.VirtualFile
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.common.IndentConfig
import com.maddyhome.idea.vim.common.IndentConfig.Companion.create
import com.maddyhome.idea.vim.common.LiveRange
import com.maddyhome.idea.vim.common.ModeChangeListener
import com.maddyhome.idea.vim.common.TextRange
@@ -52,6 +55,7 @@ import com.maddyhome.idea.vim.helper.inExMode
import com.maddyhome.idea.vim.helper.isTemplateActive
import com.maddyhome.idea.vim.helper.vimChangeActionSwitchMode
import com.maddyhome.idea.vim.helper.vimLastSelectionType
import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.inBlockSelection
@@ -59,7 +63,7 @@ import org.jetbrains.annotations.ApiStatus
import java.lang.System.identityHashCode
@ApiStatus.Internal
internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase() {
companion object {
// For cases where Editor does not have a project (for some reason)
// It's something IJ Platform related and stored here because of this reason
@@ -72,12 +76,22 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
val editor = editor.getTopLevelEditor()
val originalEditor = editor
override fun updateMode(mode: Mode) {
(injector.vimState as VimStateMachineImpl).mode = mode
}
override fun updateIsReplaceCharacter(isReplaceCharacter: Boolean) {
(injector.vimState as VimStateMachineImpl).isReplaceCharacter = isReplaceCharacter
}
override val lfMakesNewLine: Boolean = true
override var vimChangeActionSwitchMode: Mode?
get() = editor.vimChangeActionSwitchMode
set(value) {
editor.vimChangeActionSwitchMode = value
}
override val indentConfig: VimIndentConfig
get() = create(editor)
override fun fileSize(): Long = editor.fileSize.toLong()
@@ -119,7 +133,7 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
}
override fun insertText(caret: VimCaret, atPosition: Int, text: CharSequence) {
if (editor.isInsertMode) {
if (injector.vimState.mode is Mode.INSERT) {
injector.undo.startInsertSequence(caret, atPosition, System.nanoTime())
}
editor.document.insertString(atPosition, text)
@@ -151,7 +165,7 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
return editor.caretModel.allCarets.map { IjVimCaret(it) }
}
override var isFirstCaret = false
override var isFirstCaret = true
override var isReversingCarets = false
@Suppress("ideavimRunForEachCaret")
@@ -159,7 +173,6 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
if (editor.vim.inBlockSelection) {
action(IjVimCaret(editor.caretModel.primaryCaret))
} else {
isFirstCaret = true
try {
editor.caretModel.runForEachCaret({
if (it.isValid) {
@@ -168,13 +181,12 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
}
}, false)
} finally {
isFirstCaret = false
isFirstCaret = true
}
}
}
override fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean) {
isFirstCaret = true
isReversingCarets = reverse
try {
editor.caretModel.runForEachCaret({
@@ -182,7 +194,7 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
isFirstCaret = false
}, reverse)
} finally {
isFirstCaret = false
isFirstCaret = true
isReversingCarets = false
}
}
@@ -452,7 +464,7 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
// This is faster than simply calling Editor#logicalToVisualPosition
return editor.offsetToVisualLine(editor.document.getLineStartOffset(line))
}
return super.bufferLineToVisualLine(line)
return super<MutableLinearEditor>.bufferLineToVisualLine(line)
}
override var insertMode: Boolean

View File

@@ -11,7 +11,6 @@ package com.maddyhome.idea.vim.newapi
import com.intellij.openapi.components.service
import com.intellij.openapi.components.serviceIfCreated
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.textarea.TextComponentEditorImpl
import com.maddyhome.idea.vim.api.EngineEditorHelper
import com.maddyhome.idea.vim.api.ExecutionContextManager
@@ -23,13 +22,12 @@ import com.maddyhome.idea.vim.api.VimApplication
import com.maddyhome.idea.vim.api.VimChangeGroup
import com.maddyhome.idea.vim.api.VimClipboardManager
import com.maddyhome.idea.vim.api.VimCommandGroup
import com.maddyhome.idea.vim.api.VimCommandLine
import com.maddyhome.idea.vim.api.VimCommandLineService
import com.maddyhome.idea.vim.api.VimDigraphGroup
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimEditorGroup
import com.maddyhome.idea.vim.api.VimEnabler
import com.maddyhome.idea.vim.api.VimExOutputPanel
import com.maddyhome.idea.vim.api.VimExOutputPanelService
import com.maddyhome.idea.vim.api.VimExtensionRegistrator
import com.maddyhome.idea.vim.api.VimFile
import com.maddyhome.idea.vim.api.VimInjector
@@ -39,8 +37,10 @@ import com.maddyhome.idea.vim.api.VimKeyGroup
import com.maddyhome.idea.vim.api.VimLookupManager
import com.maddyhome.idea.vim.api.VimMarkService
import com.maddyhome.idea.vim.api.VimMessages
import com.maddyhome.idea.vim.api.VimModalInputService
import com.maddyhome.idea.vim.api.VimMotionGroup
import com.maddyhome.idea.vim.api.VimOptionGroup
import com.maddyhome.idea.vim.api.VimOutputPanelService
import com.maddyhome.idea.vim.api.VimProcessGroup
import com.maddyhome.idea.vim.api.VimPsiService
import com.maddyhome.idea.vim.api.VimRedrawService
@@ -58,8 +58,9 @@ import com.maddyhome.idea.vim.api.VimrcFileState
import com.maddyhome.idea.vim.api.VimscriptExecutor
import com.maddyhome.idea.vim.api.VimscriptFunctionService
import com.maddyhome.idea.vim.api.VimscriptParser
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.isInjectorInitialized
import com.maddyhome.idea.vim.diagnostic.VimLogger
import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.extension.VimExtensionRegistrar
import com.maddyhome.idea.vim.group.CommandGroup
import com.maddyhome.idea.vim.group.EditorGroup
@@ -79,14 +80,13 @@ import com.maddyhome.idea.vim.helper.IjActionExecutor
import com.maddyhome.idea.vim.helper.IjEditorHelper
import com.maddyhome.idea.vim.helper.IjVimStringParser
import com.maddyhome.idea.vim.helper.UndoRedoHelper
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.history.VimHistory
import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl
import com.maddyhome.idea.vim.macro.VimMacro
import com.maddyhome.idea.vim.put.VimPut
import com.maddyhome.idea.vim.register.VimRegisterGroup
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.ui.VimRcFileState
import com.maddyhome.idea.vim.ui.ex.ExEntryPanelService
import com.maddyhome.idea.vim.undo.VimUndoRedo
import com.maddyhome.idea.vim.vimscript.Executor
import com.maddyhome.idea.vim.vimscript.services.VariableService
@@ -94,6 +94,16 @@ import com.maddyhome.idea.vim.yank.VimYankGroup
import com.maddyhome.idea.vim.yank.YankGroupBase
import javax.swing.JTextArea
/**
* Currently, injector has to be initialized in all "entry points" from the IJ platform.
* This means Project Activities, listeners, status bar widgets, statistic collectors, etc.
* This is a bad pattern and we need to find a solution where the plugin doesn't have an "uninitialized state"
*/
internal fun initInjector() {
if (isInjectorInitialized()) return
injector = IjVimInjector()
}
internal class IjVimInjector : VimInjectorBase() {
override fun <T : Any> getLogger(clazz: Class<T>): VimLogger = IjVimLogger(Logger.getInstance(clazz))
@@ -105,12 +115,6 @@ internal class IjVimInjector : VimInjectorBase() {
override val actionExecutor: VimActionExecutor
get() = service<IjActionExecutor>()
override val exOutputPanel: VimExOutputPanelService
get() = object : VimExOutputPanelService {
override fun getPanel(editor: VimEditor): VimExOutputPanel {
return ExOutputModel.getInstance(editor.ij)
}
}
override val historyGroup: VimHistory
get() = service<HistoryGroup>()
override val extensionRegistrator: VimExtensionRegistrator
@@ -193,6 +197,10 @@ internal class IjVimInjector : VimInjectorBase() {
get() = com.maddyhome.idea.vim.vimscript.parser.VimscriptParser
override val commandLine: VimCommandLineService
get() = service()
override val modalInput: VimModalInputService
get() = commandLine as ExEntryPanelService
override val outputPanel: VimOutputPanelService
get() = service()
override val optionGroup: VimOptionGroup
get() = service()
@@ -206,21 +214,14 @@ internal class IjVimInjector : VimInjectorBase() {
override val redrawService: VimRedrawService
get() = service()
@Deprecated("Please use VimInjector.vimState", replaceWith = ReplaceWith("vimState"))
override fun commandStateFor(editor: VimEditor): VimStateMachine {
var res = editor.ij.vimStateMachine
if (res == null) {
res = VimStateMachineImpl()
editor.ij.vimStateMachine = res
}
return res
return vimState
}
@Deprecated("Please use VimInjector.vimState", replaceWith = ReplaceWith("vimState"))
override fun commandStateFor(editor: Any): VimStateMachine {
return when (editor) {
is VimEditor -> this.commandStateFor(editor)
is Editor -> this.commandStateFor(IjVimEditor(editor))
else -> error("Unexpected type: $editor")
}
return vimState
}
override val engineEditorHelper: EngineEditorHelper

View File

@@ -97,59 +97,6 @@ open class IjVimSearchGroup : VimSearchGroupBase(), PersistentStateComponent<Ele
updateSearchHighlights(getLastUsedPattern(), lastIgnoreSmartCase, showSearchHighlight, true)
}
override fun confirmChoice(
editor: VimEditor,
context: ExecutionContext,
match: String,
caret: VimCaret,
startOffset: Int,
): ReplaceConfirmationChoice {
val result: Ref<ReplaceConfirmationChoice> = Ref.create(ReplaceConfirmationChoice.QUIT)
val keyStrokeProcessor: Function1<KeyStroke, Boolean> = label@{ key: KeyStroke ->
val choice: ReplaceConfirmationChoice
val c = key.keyChar
choice = if (key.isCloseKeyStroke() || c == 'q') {
ReplaceConfirmationChoice.QUIT
} else if (c == 'y') {
ReplaceConfirmationChoice.SUBSTITUTE_THIS
} else if (c == 'l') {
ReplaceConfirmationChoice.SUBSTITUTE_LAST
} else if (c == 'n') {
ReplaceConfirmationChoice.SKIP
} else if (c == 'a') {
ReplaceConfirmationChoice.SUBSTITUTE_ALL
} else {
return@label true
}
// TODO: Handle <C-E> and <C-Y>
result.set(choice)
false
}
if (ApplicationManager.getApplication().isUnitTestMode) {
caret.moveToOffset(startOffset)
val inputModel = getInstance(editor.ij)
var key = inputModel.nextKeyStroke()
while (key != null) {
if (!keyStrokeProcessor.invoke(key)) {
break
}
key = inputModel.nextKeyStroke()
}
} else {
// XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for this method
val exEntryPanel = injector.commandLine.createWithoutShortcuts(
editor,
context,
MessageHelper.message("replace.with.0", match),
"",
)
caret.moveToOffset(startOffset)
ModalEntry.activate(editor, keyStrokeProcessor)
exEntryPanel.deactivate(refocusOwningEditor = true, resetCaret = false)
}
return result.get()
}
override fun addSubstitutionConfirmationHighlight(
editor: VimEditor,
startOffset: Int,

View File

@@ -19,11 +19,16 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.group.IjOptionConstants
import com.maddyhome.idea.vim.group.IjOptions
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.initInjector
import com.maddyhome.idea.vim.options.OptionAccessScope
import com.maddyhome.idea.vim.options.OptionConstants
internal class OptionsState : ApplicationUsagesCollector() {
init {
initInjector()
}
override fun getGroup(): EventLogGroup = GROUP
override fun getMetrics(): Set<MetricEvent> {

View File

@@ -13,11 +13,16 @@ import com.intellij.internal.statistic.eventLog.EventLogGroup
import com.intellij.internal.statistic.eventLog.events.EventFields
import com.intellij.internal.statistic.eventLog.events.VarargEventId
import com.intellij.internal.statistic.service.fus.collectors.ApplicationUsagesCollector
import com.maddyhome.idea.vim.newapi.initInjector
import com.maddyhome.idea.vim.statistic.PluginState.Util.extensionNames
import com.maddyhome.idea.vim.vimscript.services.VimRcService
internal class VimscriptState : ApplicationUsagesCollector() {
init {
initInjector()
}
override fun getGroup(): EventLogGroup = GROUP
override fun getMetrics(): Set<MetricEvent> {

View File

@@ -44,7 +44,7 @@ internal class Troubleshooter {
}
companion object {
val instance = service<Troubleshooter>()
fun getInstance() = service<Troubleshooter>()
}
}

View File

@@ -19,6 +19,7 @@ import com.maddyhome.idea.vim.KeyHandler;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.ExecutionContext;
import com.maddyhome.idea.vim.api.VimEditor;
import com.maddyhome.idea.vim.api.VimOutputPanel;
import com.maddyhome.idea.vim.diagnostic.VimLogger;
import com.maddyhome.idea.vim.helper.MessageHelper;
import com.maddyhome.idea.vim.helper.UiHelper;
@@ -46,19 +47,18 @@ import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
public class ExOutputPanel extends JPanel {
private final @NotNull Editor myEditor;
private final @NotNull JLabel myLabel = new JLabel("more");
public final @NotNull JLabel myLabel = new JLabel("more");
private final @NotNull JTextArea myText = new JTextArea();
private final @NotNull JScrollPane myScrollPane =
new JBScrollPane(myText, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
private final @NotNull ComponentAdapter myAdapter;
private boolean myAtEnd = false;
private int myLineHeight = 0;
private @Nullable JComponent myOldGlass = null;
private @Nullable LayoutManager myOldLayout = null;
private boolean myWasOpaque = false;
private boolean myActive = false;
public boolean myActive = false;
private static final VimLogger LOG = injector.getLogger(ExOutputPanel.class);
@@ -90,12 +90,16 @@ public class ExOutputPanel extends JPanel {
updateUI();
}
public static @Nullable ExOutputPanel getNullablePanel(@NotNull Editor editor) {
return UserDataManager.getVimMorePanel(editor);
}
public static boolean isPanelActive(@NotNull Editor editor) {
return UserDataManager.getVimMorePanel(editor) != null;
return getNullablePanel(editor) != null;
}
public static @NotNull ExOutputPanel getInstance(@NotNull Editor editor) {
ExOutputPanel panel = UserDataManager.getVimMorePanel(editor);
ExOutputPanel panel = getNullablePanel(editor);
if (panel == null) {
panel = new ExOutputPanel(editor);
UserDataManager.setVimMorePanel(editor, panel);
@@ -192,7 +196,7 @@ public class ExOutputPanel extends JPanel {
/**
* Turns on the more window for the given editor
*/
private void activate() {
public void activate() {
JRootPane root = SwingUtilities.getRootPane(myEditor.getContentComponent());
myOldGlass = (JComponent)root.getGlassPane();
if (myOldGlass != null) {
@@ -220,42 +224,31 @@ public class ExOutputPanel extends JPanel {
myLabel.setFont(UiHelper.selectEditorFont(myEditor, myLabel.getText()));
}
private void scrollLine() {
public void scrollLine() {
scrollOffset(myLineHeight);
}
private void scrollPage() {
public void scrollPage() {
scrollOffset(myScrollPane.getVerticalScrollBar().getVisibleAmount());
}
private void scrollHalfPage() {
public void scrollHalfPage() {
double sa = myScrollPane.getVerticalScrollBar().getVisibleAmount() / 2.0;
double offset = Math.ceil(sa / myLineHeight) * myLineHeight;
scrollOffset((int)offset);
}
private void handleEnter() {
if (myAtEnd) {
close();
}
else {
scrollLine();
}
}
private void badKey() {
public void onBadKey() {
myLabel.setText(MessageHelper.message("more.ret.line.space.page.d.half.page.q.quit"));
myLabel.setFont(UiHelper.selectEditorFont(myEditor, myLabel.getText()));
}
private void scrollOffset(int more) {
myAtEnd = false;
int val = myScrollPane.getVerticalScrollBar().getValue();
myScrollPane.getVerticalScrollBar().setValue(val + more);
myScrollPane.getHorizontalScrollBar().setValue(0);
if (val + more >=
myScrollPane.getVerticalScrollBar().getMaximum() - myScrollPane.getVerticalScrollBar().getVisibleAmount()) {
myAtEnd = true;
myLabel.setText(MessageHelper.message("hit.enter.or.type.command.to.continue"));
}
else {
@@ -264,6 +257,11 @@ public class ExOutputPanel extends JPanel {
myLabel.setFont(UiHelper.selectEditorFont(myEditor, myLabel.getText()));
}
public boolean isAtEnd() {
int val = myScrollPane.getVerticalScrollBar().getValue();
return val >= myScrollPane.getVerticalScrollBar().getMaximum() - myScrollPane.getVerticalScrollBar().getVisibleAmount();
}
private void positionPanel() {
final JComponent contentComponent = myEditor.getContentComponent();
Container scroll = SwingUtilities.getAncestorOfClass(JScrollPane.class, contentComponent);
@@ -304,14 +302,13 @@ public class ExOutputPanel extends JPanel {
close(null);
}
private void close(final @Nullable KeyEvent e) {
public void close(final @Nullable KeyStroke key) {
ApplicationManager.getApplication().invokeLater(() -> {
deactivate(true);
final Project project = myEditor.getProject();
if (project != null && e != null && e.getKeyChar() != '\n') {
final KeyStroke key = KeyStroke.getKeyStrokeForEvent(e);
if (project != null && key != null && key.getKeyChar() != '\n') {
final List<KeyStroke> keys = new ArrayList<>(1);
keys.add(key);
if (LOG.isTrace()) {
@@ -320,7 +317,9 @@ public class ExOutputPanel extends JPanel {
}
KeyHandler.getInstance().getKeyStack().addKeys(keys);
ExecutionContext context = injector.getExecutionContextManager().getEditorExecutionContext(new IjVimEditor(myEditor));
VimPlugin.getMacro().playbackKeys(new IjVimEditor(myEditor), context, 1);
injector.getApplication().runWriteAction(() -> { VimPlugin.getMacro().playbackKeys(new IjVimEditor(myEditor), context, 1);
return null;
});
}
});
}
@@ -337,40 +336,14 @@ public class ExOutputPanel extends JPanel {
*/
@Override
public void keyTyped(@NotNull KeyEvent e) {
if (myExOutputPanel.myAtEnd) {
myExOutputPanel.close(e);
}
else {
switch (e.getKeyChar()) {
case ' ':
myExOutputPanel.scrollPage();
break;
case 'd':
myExOutputPanel.scrollHalfPage();
break;
case 'q':
case '\u001b':
myExOutputPanel.close();
break;
case '\n':
myExOutputPanel.handleEnter();
break;
case KeyEvent.CHAR_UNDEFINED: {
switch (e.getKeyCode()) {
case KeyEvent.VK_ENTER:
myExOutputPanel.handleEnter();
break;
case KeyEvent.VK_ESCAPE:
myExOutputPanel.close();
break;
default:
myExOutputPanel.badKey();
}
}
default:
myExOutputPanel.badKey();
}
}
VimOutputPanel currentPanel = injector.getOutputPanel().getCurrentOutputPanel();
if (currentPanel == null) return;
int keyCode = e.getKeyCode();
Character keyChar = e.getKeyChar();
int modifiers = e.getModifiersEx();
KeyStroke keyStroke = (keyChar == KeyEvent.CHAR_UNDEFINED) ? KeyStroke.getKeyStroke(keyCode, modifiers) : KeyStroke.getKeyStroke(keyChar, modifiers);
currentPanel.handleKey(keyStroke);
}
}

View File

@@ -150,7 +150,7 @@ internal class ReloadVimRc : DumbAwareAction() {
override fun actionPerformed(e: AnActionEvent) {
val editor = e.getData(PlatformDataKeys.EDITOR) ?: return
injector.keyGroup.removeKeyMapping(MappingOwner.IdeaVim.InitScript)
Troubleshooter.instance.removeByType("old-action-notation-in-mappings")
Troubleshooter.getInstance().removeByType("old-action-notation-in-mappings")
// Reload the ideavimrc in the context of the current window, as though we had called `:source ~/.ideavimrc`
executeIdeaVimRc(editor.vim)

View File

@@ -20,7 +20,6 @@ import com.intellij.openapi.wm.WindowManager
import com.intellij.openapi.wm.impl.status.EditorBasedWidget
import com.intellij.util.Consumer
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
@@ -28,6 +27,7 @@ import com.maddyhome.idea.vim.common.EditorListener
import com.maddyhome.idea.vim.helper.EngineStringHelper
import com.maddyhome.idea.vim.helper.VimNlsSafe
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.initInjector
import com.maddyhome.idea.vim.options.GlobalOptionChangeListener
import org.jetbrains.annotations.NonNls
import java.awt.Component
@@ -72,6 +72,11 @@ internal object ShowCmdOptionChangeListener : GlobalOptionChangeListener {
}
internal class ShowCmdStatusBarWidgetFactory : StatusBarWidgetFactory/*, LightEditCompatible*/ {
init {
initInjector()
}
override fun getId() = ShowCmd.ID
override fun getDisplayName(): String = ShowCmd.displayName
@@ -81,7 +86,6 @@ internal class ShowCmdStatusBarWidgetFactory : StatusBarWidgetFactory/*, LightEd
}
override fun isAvailable(project: Project): Boolean {
VimPlugin.getInstance()
return injector.globalOptions().showcmd
}

View File

@@ -267,7 +267,7 @@ private object ShortcutConflictsSettings : DumbAwareAction(MessageHelper.message
}
internal object JoinEap : DumbAwareAction()/*, LightEditCompatible*/ {
private const val EAP_LINK = "https://plugins.jetbrains.com/plugins/eap/ideavim"
const val EAP_LINK = "https://plugins.jetbrains.com/plugins/eap/ideavim"
fun eapActive() = EAP_LINK in UpdateSettings.getInstance().storedPluginHosts

View File

@@ -10,6 +10,7 @@ package com.maddyhome.idea.vim.ui.ex
import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.diagnostic.logger
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.vim
import org.jetbrains.annotations.NonNls
import java.awt.event.ActionEvent
@@ -119,7 +120,8 @@ internal object ExEditorKit : DefaultEditorKit() {
val c = key.keyChar
if (c.code > 0) {
if (target.useHandleKeyFromEx) {
val entry = ExEntryPanel.getInstance().entry
val panel = ((injector.commandLine.getActiveCommandLine() as? ExEntryPanel) ?: (injector.modalInput.getCurrentModalInput() as? WrappedAsModalInputExEntryPanel)?.exEntryPanel) ?: return
val entry = panel.entry
val editor = entry.editor
val keyHandler = KeyHandler.getInstance()
keyHandler.handleKey(editor!!.vim, key, entry.context.vim, keyHandler.keyHandlerState)

View File

@@ -25,10 +25,12 @@ import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.action.VimShortcutKeyAction;
import com.maddyhome.idea.vim.api.VimCommandLine;
import com.maddyhome.idea.vim.api.VimCommandLineCaret;
import com.maddyhome.idea.vim.api.VimEditor;
import com.maddyhome.idea.vim.api.VimKeyGroupBase;
import com.maddyhome.idea.vim.ex.ranges.LineRange;
import com.maddyhome.idea.vim.helper.SearchHighlightsHelper;
import com.maddyhome.idea.vim.helper.UiHelper;
import com.maddyhome.idea.vim.key.interceptors.VimInputInterceptor;
import com.maddyhome.idea.vim.newapi.IjVimCaret;
import com.maddyhome.idea.vim.newapi.IjVimEditor;
import com.maddyhome.idea.vim.ui.ExPanelBorder;
@@ -36,6 +38,8 @@ import com.maddyhome.idea.vim.vimscript.model.commands.Command;
import com.maddyhome.idea.vim.vimscript.model.commands.GlobalCommand;
import com.maddyhome.idea.vim.vimscript.model.commands.SubstituteCommand;
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -62,6 +66,10 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
private boolean isReplaceMode = false;
private WeakReference<Editor> weakEditor = null;
private VimInputInterceptor myInputInterceptor = null;
public Function1<String, Unit> inputProcessing = null;
public Character finishOn = null;
private ExEntryPanel(boolean enableShortcuts) {
label = new JLabel(" ");
entry = new ExTextField();
@@ -79,15 +87,13 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
layout.setConstraints(entry, gbc);
add(entry);
if (enableShortcuts) {
// This does not need to be unregistered, it's registered as a custom UI property on this
EventFacade.getInstance().registerCustomShortcutSet(
VimShortcutKeyAction.getInstance(),
toShortcutSet(((VimKeyGroupBase) injector.getKeyGroup()).getRequiredShortcutKeys()),
entry
);
new ExShortcutKeyAction(this).registerCustomShortcutSet();
}
// This does not need to be unregistered, it's registered as a custom UI property on this
EventFacade.getInstance().registerCustomShortcutSet(
VimShortcutKeyAction.getInstance(),
toShortcutSet(((VimKeyGroupBase) injector.getKeyGroup()).getRequiredShortcutKeys()),
entry
);
new ExShortcutKeyAction(this).registerCustomShortcutSet();
updateUI();
}
@@ -127,10 +133,18 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
}
}
public @Nullable Editor getEditor() {
public @Nullable Editor getIjEditor() {
return weakEditor != null ? weakEditor.get() : null;
}
public @NotNull VimEditor getEditor() {
Editor editor = getIjEditor();
if (editor == null) {
throw new RuntimeException("Editor was disposed for active command line");
}
return new IjVimEditor(editor);
}
public void setEditor(@Nullable Editor editor) {
if (editor == null) {
weakEditor = null;
@@ -139,6 +153,14 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
}
}
/**
* @deprecated use {@link ExEntryPanel#activate(Editor, DataContext, String, String)}
*/
@Deprecated(forRemoval = true)
public void activate(@NotNull Editor editor, DataContext context, @NotNull String label, String initText, int count) {
activate(editor, context, label, initText);
}
/**
* Turns on the ex entry field for the given editor
*
@@ -249,6 +271,9 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
// We have this in the end, because `entry.deactivate()` communicates with active panel during deactivation
active = false;
finishOn = null;
myInputInterceptor = null;
inputProcessing = null;
}
private void reset() {
@@ -273,7 +298,7 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
@Override
protected void textChanged(@NotNull DocumentEvent e) {
String text = entry.getText();
Font newFont = UiHelper.selectEditorFont(getEditor(), text);
Font newFont = UiHelper.selectEditorFont(getIjEditor(), text);
if (newFont != entry.getFont()) {
entry.setFont(newFont);
}
@@ -326,7 +351,7 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
// coerced to at least 1.
int count1 = KeyHandler.getInstance().getKeyHandlerState().getEditorCommandBuilder().getAggregatedUncommittedCount();
if (labelText.equals("/") || labelText.equals("?") || searchCommand) {
if ((labelText.equals("/") || labelText.equals("?") || searchCommand) && !injector.getMacro().isExecutingMacro()) {
final boolean forwards = !labelText.equals("?"); // :s, :g, :v are treated as forwards
int pattenEnd = injector.getSearchGroup().findEndOfPattern(searchText, separator, 0);
final String pattern = searchText.substring(0, pattenEnd);
@@ -392,6 +417,14 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
return active;
}
/**
* @deprecated Use getVisibleText()
*/
@Deprecated(forRemoval = true)
public @NotNull String getText() {
return entry.getText();
}
@Override
public @NotNull String getVisibleText() {
return entry.getText();
@@ -409,6 +442,10 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
@Override
public void handleKey(@NotNull KeyStroke stroke) {
entry.handleKey(stroke);
if (finishOn != null && stroke.getKeyChar() == finishOn && inputProcessing != null) {
inputProcessing.invoke(getActualText());
close(true, true);
}
}
// Called automatically when the LAF is changed and the component is visible, and manually by the LAF listener handler
@@ -446,8 +483,8 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
}
private void setFontForElements() {
label.setFont(UiHelper.selectEditorFont(getEditor(), label.getText()));
entry.setFont(UiHelper.selectEditorFont(getEditor(), getVisibleText()));
label.setFont(UiHelper.selectEditorFont(getIjEditor(), label.getText()));
entry.setFont(UiHelper.selectEditorFont(getIjEditor(), getVisibleText()));
}
private void positionPanel() {
@@ -535,6 +572,32 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
IdeFocusManager.findInstance().requestFocus(entry, true);
}
@Nullable
public VimInputInterceptor<?> getInputInterceptor() {
return myInputInterceptor;
}
@Override
public void insertText(int offset, @NotNull String string) {
VimCommandLine.super.insertText(offset, string);
}
public void setInputInterceptor(@Nullable VimInputInterceptor<?> vimInputInterceptor) {
myInputInterceptor = vimInputInterceptor;
}
@Nullable
@Override
public Function1<String, Unit> getInputProcessing() {
return inputProcessing;
}
@Nullable
@Override
public Character getFinishOn() {
return finishOn;
}
public static class LafListener implements LafManagerListener {
@Override
public void lookAndFeelChanged(@NotNull LafManager source) {

View File

@@ -11,27 +11,35 @@ import com.intellij.openapi.application.ApplicationManager
import com.maddyhome.idea.vim.action.change.Extension
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCommandLine
import com.maddyhome.idea.vim.api.VimCommandLineService
import com.maddyhome.idea.vim.api.VimCommandLineCaret
import com.maddyhome.idea.vim.api.VimCommandLineServiceBase
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimModalInput
import com.maddyhome.idea.vim.api.VimModalInputBase
import com.maddyhome.idea.vim.api.VimModalInputService
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.TestInputModel
import com.maddyhome.idea.vim.helper.inRepeatMode
import com.maddyhome.idea.vim.helper.isCloseKeyStroke
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.key.interceptors.VimInputInterceptor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd
import com.maddyhome.idea.vim.ui.ModalEntry
import java.awt.event.KeyEvent
import javax.swing.KeyStroke
class ExEntryPanelService : VimCommandLineService {
class ExEntryPanelService : VimCommandLineServiceBase(), VimModalInputService {
override fun getActiveCommandLine(): VimCommandLine? {
val instance = ExEntryPanel.instance ?: ExEntryPanel.instanceWithoutShortcuts ?: return null
val instance = ExEntryPanel.instance ?: return null
return if (instance.isActive) instance else null
}
override fun inputString(vimEditor: VimEditor, context: ExecutionContext, prompt: String, finishOn: Char?): String? {
@Deprecated("Please use readInputAndProcess")
fun inputString(vimEditor: VimEditor, context: ExecutionContext, prompt: String, finishOn: Char?): String? {
val editor = vimEditor.ij
if (vimEditor.vimStateMachine.isDotRepeatInProgress) {
if (vimEditor.inRepeatMode) {
val input = Extension.consumeString()
return input ?: error("Not enough strings saved: ${Extension.lastExtensionHandler}")
}
@@ -58,7 +66,7 @@ class ExEntryPanelService : VimCommandLineService {
} else {
var text: String? = null
// XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for input()
val commandLine = injector.commandLine.create(vimEditor, context, prompt.ifEmpty { " " }, "")
val commandLine = injector.commandLine.createSearchPrompt(vimEditor, context, prompt.ifEmpty { " " }, "")
ModalEntry.activate(editor.vim) { key: KeyStroke ->
return@activate when {
key.isCloseKeyStroke() -> {
@@ -89,7 +97,40 @@ class ExEntryPanelService : VimCommandLineService {
}
}
override fun create(editor: VimEditor, context: ExecutionContext, label: String, initText: String): VimCommandLine {
override fun readInputAndProcess(
editor: VimEditor,
context: ExecutionContext,
prompt: String,
finishOn: Char?,
processing: (String) -> Unit,
) {
val currentMode = editor.mode
check(currentMode is ReturnableFromCmd) {
"Cannot enable cmd mode from current mode $currentMode"
}
// Make sure the Visual selection marks are up to date before we use them.
injector.markService.setVisualSelectionMarks(editor)
// Note that we should remove selection and reset caret offset before we switch back to Normal mode and then enter
// Command-line mode. However, some IdeaVim commands can handle multiple carets, including multiple carets with
// selection (which might or might not be a block selection). Unfortunately, because we're just entering
// Command-line mode, we don't know which command is going to be entered, so we can't remove selection here.
// Therefore, we switch to Normal and then Command-line even though we might still have a Visual selection...
// On the plus side, it means we still show selection while editing the command line, which Vim also does
// (Normal then Command-line is not strictly necessary, but done for completeness and autocmd)
// Caret selection is finally handled in Command.execute
editor.mode = Mode.NORMAL()
editor.mode = Mode.CMD_LINE(currentMode)
val panel = ExEntryPanel.getInstance()
panel as ExEntryPanel
panel.finishOn = finishOn
panel.inputProcessing = processing
panel.activate(editor.ij, context.ij, prompt, "")
}
override fun createPanel(editor: VimEditor, context: ExecutionContext, label: String, initText: String): VimCommandLine {
val panel = ExEntryPanel.getInstance()
panel.activate(editor.ij, context.ij, label, initText)
return panel
@@ -104,4 +145,31 @@ class ExEntryPanelService : VimCommandLineService {
override fun fullReset() {
ExEntryPanel.fullReset()
}
override fun getCurrentModalInput(): VimModalInput? {
return ExEntryPanel.getInstanceWithoutShortcuts()?.takeIf { it.isActive && it.inputInterceptor != null }?.let { WrappedAsModalInputExEntryPanel(it) }
}
override fun create(editor: VimEditor, context: ExecutionContext, label: String, inputInterceptor: VimInputInterceptor<*>): VimModalInput {
val panel = ExEntryPanel.getInstanceWithoutShortcuts()
panel.inputInterceptor = inputInterceptor
panel.activate(editor.ij, context.ij, label, "")
return WrappedAsModalInputExEntryPanel(panel)
}
}
internal class WrappedAsModalInputExEntryPanel(internal val exEntryPanel: ExEntryPanel) : VimModalInputBase() {
override var inputInterceptor: VimInputInterceptor<*>
get() = exEntryPanel.inputInterceptor!!
set(value) { exEntryPanel.inputInterceptor = value }
override val caret: VimCommandLineCaret = exEntryPanel.caret
override val label: String = exEntryPanel.label
override fun deactivate(refocusOwningEditor: Boolean, resetCaret: Boolean) {
exEntryPanel.deactivate(refocusOwningEditor, resetCaret)
}
override fun focus() {
exEntryPanel.focus()
}
}

View File

@@ -16,6 +16,7 @@ import com.intellij.util.ui.JBUI;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.VimCommandLine;
import com.maddyhome.idea.vim.api.VimCommandLineCaret;
import com.maddyhome.idea.vim.api.VimEditor;
import com.maddyhome.idea.vim.helper.UiHelper;
import com.maddyhome.idea.vim.history.HistoryConstants;
import com.maddyhome.idea.vim.history.HistoryEntry;
@@ -211,7 +212,7 @@ public class ExTextField extends JTextField {
}
public @Nullable Editor getEditor() {
return ExEntryPanel.getInstance().getEditor();
return ExEntryPanel.getInstance().getIjEditor();
}
public DataContext getContext() {
@@ -291,9 +292,9 @@ public class ExTextField extends JTextField {
*/
void cancel() {
clearCurrentAction();
Editor editor = ExEntryPanel.instance.getEditor();
if (editor != null) {
VimPlugin.getProcess().cancelExEntry(new IjVimEditor(editor), true);
VimCommandLine commandLine = injector.getCommandLine().getActiveCommandLine();
if (commandLine != null) {
commandLine.close(true, true);
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2003-2024 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.ui.ex
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimOutputPanel
import com.maddyhome.idea.vim.api.VimOutputPanelServiceBase
import com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.newapi.ij
import java.lang.ref.WeakReference
class IjOutputPanelService : VimOutputPanelServiceBase() {
private var activeOutputPanel: VimOutputPanel? = null
override fun getCurrentOutputPanel(): VimOutputPanel? {
return activeOutputPanel?.takeIf {
(it as ExOutputModel)
it.isActive && it.editor != null
}
}
override fun create(editor: VimEditor, context: ExecutionContext): VimOutputPanel {
val panel = ExOutputModel(WeakReference(editor.ij))
activeOutputPanel = panel
return panel
}
}

View File

@@ -352,7 +352,8 @@ class ModeWidgetPopup : AnAction() {
enum class ModeWidgetTheme(private var value: String) {
TERM("Term"),
COLORLESS("Colorless");
COLORLESS("Colorless"),
DRACULA("Dracula");
override fun toString(): String {
return value

View File

@@ -31,6 +31,16 @@ fun getModeBackground(mode: Mode?): Color {
is Mode.OP_PENDING, null -> UIUtil.getPanelBackground()
}
}
ModeWidgetTheme.DRACULA -> {
return when (mode) {
Mode.INSERT -> Color.decode("#50FA7B")
Mode.REPLACE -> Color.decode("#FF5555")
is Mode.NORMAL -> Color.decode("#BD93F9")
is Mode.CMD_LINE -> Color.decode("#FFB86C")
is Mode.VISUAL, is Mode.SELECT -> Color.decode("#F1FA8C")
is Mode.OP_PENDING, null -> UIUtil.getPanelBackground()
}
}
ModeWidgetTheme.COLORLESS -> {
return UIUtil.getPanelBackground()
}
@@ -83,6 +93,7 @@ fun getModeForeground(mode: Mode?): Color {
val theme = ModeWidgetTheme.parseString(themeString) ?: ModeWidgetTheme.getDefaultTheme()
return when (theme) {
ModeWidgetTheme.TERM -> if (isLight) Color.WHITE else Color.BLACK
ModeWidgetTheme.DRACULA -> Color.BLACK
ModeWidgetTheme.COLORLESS -> UIUtil.getLabelForeground()
}
} else {

View File

@@ -39,7 +39,7 @@ internal data class CmdFilterCommand(val range: Range, val argument: String) : C
argument.forEach { c ->
when {
!inBackslash && c == '!' -> {
val last = VimPlugin.getProcess().lastCommand
val last = lastCommand
if (last.isNullOrEmpty()) {
VimPlugin.showMessage(MessageHelper.message("e_noprev"))
return ExecutionResult.Error
@@ -76,6 +76,7 @@ internal data class CmdFilterCommand(val range: Range, val argument: String) : C
VimPlugin.getProcess().executeCommand(editor, command, null, workingDirectory)?.let {
ExOutputModel.getInstance(editor.ij).output(it)
}
lastCommand = command
ExecutionResult.Success
} else {
// Filter
@@ -92,6 +93,7 @@ internal data class CmdFilterCommand(val range: Range, val argument: String) : C
}
}
}
lastCommand = command
ExecutionResult.Success
}
} catch (e: ProcessCanceledException) {
@@ -103,5 +105,6 @@ internal data class CmdFilterCommand(val range: Range, val argument: String) : C
companion object {
private val logger = Logger.getInstance(CmdFilterCommand::class.java.name)
private var lastCommand: String? = null
}
}

View File

@@ -24,6 +24,8 @@
serviceInterface="com.maddyhome.idea.vim.api.VimProcessGroup"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.ui.ex.ExEntryPanelService"
serviceInterface="com.maddyhome.idea.vim.api.VimCommandLineService"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.ui.ex.IjOutputPanelService"
serviceInterface="com.maddyhome.idea.vim.api.VimOutputPanelService"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.DigraphGroup"
serviceInterface="com.maddyhome.idea.vim.api.VimDigraphGroup"/>
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.HistoryGroup"/>

View File

@@ -125,10 +125,12 @@
<xi:include href="/META-INF/includes/VimListeners.xml" xpointer="xpointer(/idea-plugin/*)"/>
<actions resource-bundle="messages.IdeaVimBundle">
<action id="VimPluginToggle" class="com.maddyhome.idea.vim.action.VimPluginToggleAction">
<group id="com.chylex.intellij.vim" text="Vim" popup="true">
<add-to-group group-id="ToolsMenu" anchor="last"/>
</action>
<action id="VimPluginToggle" class="com.maddyhome.idea.vim.action.VimPluginToggleAction"/>
<action id="VimRunLastMacroInOpenFiles" class="com.maddyhome.idea.vim.action.VimRunLastMacroInOpenFiles"/>
</group>
<!-- Internal -->
<!--suppress PluginXmlI18n -->
<action id="VimInternalAddBlockInlays" class="com.maddyhome.idea.vim.action.internal.AddBlockInlaysAction" text="Add Test Block Inlays | IdeaVim Internal" internal="true"/>

View File

@@ -100,8 +100,8 @@ action.not.found.0=Action not found: {0}
action.CustomizeModeWidget.text=Mode Widget Settings
action.VimFindActionIdAction.text=IdeaVim: Track Action Ids
action.VimFindActionIdAction.description=Starts tracking ids of executed actions
action.VimFindActionIdAction.text=IdeaVim: Track Action IDs
action.VimFindActionIdAction.description=Starts tracking IDs of executed actions
ex.show.all.actions.0.1=--- Actions ---{0}{1}

View File

@@ -62,7 +62,7 @@ class ChangeActionTest : VimTestCase() {
listOf("i", "<C-O>", "v", "<esc>"),
"12${c}345",
"12${c}345",
Mode.INSERT,
Mode.INSERT,
)
}
@@ -73,7 +73,7 @@ Mode.INSERT,
listOf("i", "<C-O>", "v", "d"),
"12${c}345",
"12${c}45",
Mode.INSERT,
Mode.INSERT,
)
}
@@ -109,7 +109,7 @@ Mode.INSERT,
listOf("i", "<C-O>", "gh", "<esc>"),
"12${c}345",
"123${c}45",
Mode.INSERT,
Mode.INSERT,
)
}
@@ -122,7 +122,7 @@ Mode.INSERT,
listOf("i", "<C-O>", "gh", "d"),
"12${c}345",
"12d${c}45",
Mode.INSERT,
Mode.INSERT,
)
}
@@ -133,7 +133,7 @@ Mode.INSERT,
listOf("i", "def", "<C-O>", "d2h", "x"),
"abc$c.\n",
"abcdx.\n",
Mode.INSERT,
Mode.INSERT,
)
}
@@ -222,7 +222,7 @@ Mode.INSERT,
four
""".trimIndent(),
Mode.INSERT,
Mode.INSERT,
)
}
@@ -405,7 +405,7 @@ Mode.INSERT,
listOf("A", ", ", "<C-R>", "a", "!"),
"${c}Hello\n",
"Hello, World!\n",
Mode.INSERT,
Mode.INSERT,
)
}
@@ -416,7 +416,7 @@ Mode.INSERT,
listOf("O", "bar"),
"fo${c}o\n",
"bar\nfoo\n",
Mode.INSERT,
Mode.INSERT,
)
}

View File

@@ -10,7 +10,6 @@ package org.jetbrains.plugins.ideavim.action
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import org.jetbrains.plugins.ideavim.SkipNeovimReason
@@ -73,8 +72,6 @@ class MotionActionTest : VimTestCase() {
""".trimIndent()
doTest(listOf("12", "<Esc>"), content, content, Mode.NORMAL())
assertPluginError(false)
val vimCommandState = fixture.editor.vimStateMachine
kotlin.test.assertNotNull(vimCommandState)
assertEmpty(KeyHandler.getInstance().keyHandlerState.commandBuilder.keys.toList())
}
@@ -741,7 +738,7 @@ class MotionActionTest : VimTestCase() {
@Test
fun testDeleteToEndOfSearchResultInclusive() {
val before = "Lorem ${c}ipsum dolor sit amet, consectetur adipiscing elit"
val after = "Lorem ${c} amet, consectetur adipiscing elit"
val after = "Lorem $c amet, consectetur adipiscing elit"
doTest("d/sit/e<CR>", before, after)
}
@@ -769,6 +766,11 @@ class MotionActionTest : VimTestCase() {
doTest(keys, before, after, Mode.NORMAL())
}
@Test
fun testDeleteToLiteral() {
doTest("d/<C-V>333<CR>", "ab${c}cdōef", "abōef")
}
@Test
fun testDeleteBackwardsToSearchResult() {
val before = "Lorem ipsum dolor sit amet, ${c}consectetur adipiscing elit"

View File

@@ -9,8 +9,11 @@
package org.jetbrains.plugins.ideavim.action.change
import com.intellij.idea.TestFor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Assumptions.assumeTrue
import org.junit.jupiter.api.Test
class UndoActionTest : VimTestCase() {
@@ -109,6 +112,7 @@ class UndoActionTest : VimTestCase() {
@Test
@TestFor(issues = ["VIM-547"])
fun `test typed text requires one undo`() {
assumeTrue(!injector.globalIjOptions().oldundo)
configureByText("Lorem ipsu${c}m")
typeText("a dolor sit amet,<CR>consectetur adipiscing elit<Esc>")
assertState("Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit")
@@ -119,6 +123,7 @@ class UndoActionTest : VimTestCase() {
@Test
@TestFor(issues = ["VIM-547"])
fun `test breaking insert sequence`() {
assumeTrue(!injector.globalIjOptions().oldundo)
configureByText("Lorem ipsu${c}m")
typeText("a dolor sit amet,<CR>consectetur <C-G>uadipiscing elit<Esc>")
assertState("Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit")
@@ -129,6 +134,7 @@ class UndoActionTest : VimTestCase() {
@Test
@TestFor(issues = ["VIM-547"])
fun `test moving caret breaks insert sequence`() {
assumeTrue(!injector.globalIjOptions().oldundo)
configureByText("Lorem ipsu${c}m")
typeText("a dolor sit amet,<CR>consectetur <Left>adipiscing elit<Esc>")
assertState("Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit ")
@@ -140,7 +146,17 @@ class UndoActionTest : VimTestCase() {
@TestFor(issues = ["VIM-547"])
fun `test change action is included in insert sequence`() {
configureByText("Lorem ${c}ipsum dolor sit amet")
typeText("celorem")
typeText("celorem<Esc>")
assertState("Lorem lore${c}m dolor sit amet")
typeText("u")
assertState("Lorem ${c}ipsum dolor sit amet")
}
@Test
@TestFor(issues = ["VIM-3552"])
fun `test paste is not part of undo sequence`() {
configureByText("test")
typeText("yypoxxxu")
configureByText("Lorem lorem${c} dolor sit amet")
typeText("u")
configureByText("Lorem ${c}ipsum dolor sit amet")

View File

@@ -174,8 +174,8 @@ class PutTestAfterCursorActionTest : VimTestCase() {
override fun collectTransferableData(
file: PsiFile,
editor: Editor,
startOffsets: IntArray?,
endOffsets: IntArray?,
startOffsets: IntArray,
endOffsets: IntArray,
): List<TextBlockTransferableData> {
return emptyList()
}

View File

@@ -18,7 +18,6 @@ import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimBehaviorDiffers
import org.jetbrains.plugins.ideavim.VimTestCase
import org.jetbrains.plugins.ideavim.annotations.TestWithoutPrimaryClipboard
import org.junit.Ignore
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
@@ -261,9 +260,9 @@ class PutVisualTextMoveCursorActionTest : VimTestCase() {
assertState(after)
}
@Suppress("unused")
@Ignore
fun `ingoretest put visual block visual line mode`() {
@Disabled
@Test
fun `test put visual block visual line mode`() {
val before = """
qw${c}e
asd
@@ -315,8 +314,6 @@ class PutVisualTextMoveCursorActionTest : VimTestCase() {
assertState(after)
}
@Suppress("unused")
@Ignore
@Test
@Disabled
fun `test put visual text multicaret`() {

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2003-2024 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package org.jetbrains.plugins.ideavim.action.motion.updown
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
class MotionUpCtrlPAction : VimTestCase() {
@Test
@Disabled("This one should be fixed")
fun `test last column empty`() {
val keys = "o<Esc><End><C-P>"
val before = """
${c}Lorem Ipsumm dolor sit amet, consectetur adipiscing elit. Morbi gravida commodo orci, egestas placerat purus rhoncus non. Donec efficitur placerat lorem, non ullamcorper nisl. Aliquam vestibulum, purus a pretium sodales, lorem libero placerat tortor, ut gravida est arcu nec purus. Suspendisse luctus euismod mi, at consectetur sapien facilisis sed. Duis eu magna id nisi lacinia vehicula in quis mauris. Donec tincidunt, erat in euismod placerat, tortor eros efficitur ligula, non finibus metus enim in ex. Nam commodo libero quis vestibulum congue. Vivamus sit amet tincidunt orci, in luctus tortor. Ut aliquam porttitor pharetra. Sed vel mi lacinia, auctor eros vel, condimentum eros. Fusce suscipit auctor venenatis. Aliquam elit risus, eleifend quis mollis eu, venenatis quis ex. Nunc varius consectetur eros sit amet efficitur. Donec a elit rutrum, tristique est in, maximus sem. Donec eleifend magna vitae suscipit viverra. Phasellus luctus aliquam tellus viverra consequat.
""".trimIndent()
val after = """
Lorem Ipsumm dolor sit amet, consectetur adipiscing elit. Morbi gravida commodo orci, egestas placerat purus rhoncus non. Donec efficitur placerat lorem, non ullamcorper nisl. Aliquam vestibulum, purus a pretium sodales, lorem libero placerat tortor, ut gravida est arcu nec purus. Suspendisse luctus euismod mi, at consectetur sapien facilisis sed. Duis eu magna id nisi lacinia vehicula in quis mauris. Donec tincidunt, erat in euismod placerat, tortor eros efficitur ligula, non finibus metus enim in ex. Nam commodo libero quis vestibulum congue. Vivamus sit amet tincidunt orci, in luctus tortor. Ut aliquam porttitor pharetra. Sed vel mi lacinia, auctor eros vel, condimentum eros. Fusce suscipit auctor venenatis. Aliquam elit risus, eleifend quis mollis eu, venenatis quis ex. Nunc varius consectetur eros sit amet efficitur. Donec a elit rutrum, tristique est in, maximus sem. Donec eleifend magna vitae suscipit viverra. Phasellus luctus aliquam tellus viverra consequat${c}.
""".trimIndent()
doTest(keys, before, after)
}
}

View File

@@ -298,6 +298,15 @@ class MapCommandTest : VimTestCase() {
assertState("I found it in ${c}a legendary land")
}
@Test
@TestFor(issues = ["VIM-3569"])
fun `test bar in mapping`() {
configureByText("${c}I found it in a legendary land")
typeText(commandToKeys("nmap <leader>\\| dw"))
typeText("<leader>|")
assertState("${c}found it in a legendary land")
}
// VIM-676 |:map|
@TestWithoutNeovim(reason = SkipNeovimReason.VIM_SCRIPT)
@Test

View File

@@ -154,6 +154,8 @@ class SetCommandTest : VimTestCase() {
// 'fileencoding' defaults to "", but is automatically detected as UTF-8
enterCommand("set number relativenumber scrolloff nrformats")
assertExOutput(" nrformats=hex scrolloff=0")
injector.outputPanel.getCurrentOutputPanel()?.close()
assertCommandOutput("set",
"""
|--- Options ---
@@ -169,21 +171,20 @@ class SetCommandTest : VimTestCase() {
assertCommandOutput("set all",
"""
|--- Options ---
|noargtextobj ideamarks scrolljump=1 notextobj-indent
|nobomb ideawrite=all scrolloff=0 textwidth=0
|nobreakindent noignorecase selectmode= timeout
| colorcolumn= noincsearch shellcmdflag=-x timeoutlen=1000
|nocommentary nolist shellxescape=@ notrackactionids
|nocursorline nomatchit shellxquote={ undolevels=1000
|nodigraph maxmapdepth=20 showcmd virtualedit=
|noexchange more showmode novisualbell
| fileformat=unix nomultiple-cursors sidescroll=0 visualdelay=100
|nogdefault noNERDTree sidescrolloff=0 whichwrap=b,s
|nohighlightedyank nrformats=hex nosmartcase wrap
| history=50 nonumber nosneak wrapscan
|nohlsearch operatorfunc= startofline
|noideaglobalmode norelativenumber nosurround
|noideajoin scroll=0 notextobj-entire
|noargtextobj ideamarks scroll=0 nosurround
|nobomb ideawrite=all scrolljump=1 notextobj-entire
|nobreakindent noignorecase scrolloff=0 notextobj-indent
| colorcolumn= noincsearch selectmode= textwidth=0
|nocommentary nolist shellcmdflag=-x timeout
|nocursorline nomatchit shellxescape=@ timeoutlen=1000
|nodigraph maxmapdepth=20 shellxquote={ notrackactionids
|noexchange more showcmd undolevels=1000
| fileformat=unix nomultiple-cursors showmode virtualedit=
|nogdefault noNERDTree sidescroll=0 novisualbell
|nohighlightedyank nrformats=hex sidescrolloff=0 visualdelay=100
| history=50 nonumber nosmartcase whichwrap=b,s
|nohlsearch operatorfunc= nosneak wrap
|noideajoin norelativenumber startofline wrapscan
| clipboard=ideaput,autoselect,exclude:cons\|linux
| fileencoding=utf-8
| guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175
@@ -220,6 +221,8 @@ class SetCommandTest : VimTestCase() {
// 'fileencoding' defaults to "", but is automatically detected as UTF-8
enterCommand("set number relativenumber scrolloff nrformats")
assertExOutput(" nrformats=hex scrolloff=0")
injector.outputPanel.getCurrentOutputPanel()?.close()
assertCommandOutput("set!",
"""
|--- Options ---
@@ -254,7 +257,6 @@ class SetCommandTest : VimTestCase() {
|nohlsearch
| ide=IntelliJ IDEA Community Edition
|noideacopypreprocess
|noideaglobalmode
|noideajoin
| ideamarks
| idearefactormode=select

View File

@@ -435,21 +435,21 @@ class SetglobalCommandTest : VimTestCase() {
setOsSpecificOptionsToSafeValues()
assertCommandOutput("setglobal all", """
|--- Global option values ---
|noargtextobj noideajoin scroll=0 notextobj-entire
|nobomb ideamarks scrolljump=1 notextobj-indent
|nobreakindent ideawrite=all scrolloff=0 textwidth=0
| colorcolumn= noignorecase selectmode= timeout
|nocommentary noincsearch shellcmdflag=-x timeoutlen=1000
|nocursorline nolist shellxescape=@ notrackactionids
|nodigraph nomatchit shellxquote={ undolevels=1000
|noexchange maxmapdepth=20 showcmd virtualedit=
| fileencoding= more showmode novisualbell
| fileformat=unix nomultiple-cursors sidescroll=0 visualdelay=100
|nogdefault noNERDTree sidescrolloff=0 whichwrap=b,s
|nohighlightedyank nrformats=hex nosmartcase wrap
| history=50 nonumber nosneak wrapscan
|nohlsearch operatorfunc= startofline
|noideaglobalmode norelativenumber nosurround
|noargtextobj ideamarks scrolljump=1 notextobj-indent
|nobomb ideawrite=all scrolloff=0 textwidth=0
|nobreakindent noignorecase selectmode= timeout
| colorcolumn= noincsearch shellcmdflag=-x timeoutlen=1000
|nocommentary nolist shellxescape=@ notrackactionids
|nocursorline nomatchit shellxquote={ undolevels=1000
|nodigraph maxmapdepth=20 showcmd virtualedit=
|noexchange more showmode novisualbell
| fileencoding= nomultiple-cursors sidescroll=0 visualdelay=100
| fileformat=unix noNERDTree sidescrolloff=0 whichwrap=b,s
|nogdefault nrformats=hex nosmartcase wrap
|nohighlightedyank nonumber nosneak wrapscan
| history=50 operatorfunc= startofline
|nohlsearch norelativenumber nosurround
|noideajoin scroll=0 notextobj-entire
| clipboard=ideaput,autoselect,exclude:cons\|linux
| guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175
| ide=IntelliJ IDEA Community Edition
@@ -523,7 +523,6 @@ class SetglobalCommandTest : VimTestCase() {
|nohlsearch
| ide=IntelliJ IDEA Community Edition
|noideacopypreprocess
|noideaglobalmode
|noideajoin
| ideamarks
| idearefactormode=select

View File

@@ -489,21 +489,20 @@ class SetlocalCommandTest : VimTestCase() {
setOsSpecificOptionsToSafeValues()
assertCommandOutput("setlocal all", """
|--- Local option values ---
|noargtextobj ideamarks scroll=0 notextobj-entire
|nobomb idearefactormode= scrolljump=1 notextobj-indent
|nobreakindent ideawrite=all scrolloff=-1 textwidth=0
| colorcolumn= noignorecase selectmode= timeout
|nocommentary noincsearch shellcmdflag=-x timeoutlen=1000
|nocursorline nolist shellxescape=@ notrackactionids
|nodigraph nomatchit shellxquote={ virtualedit=
|noexchange maxmapdepth=20 showcmd novisualbell
| fileformat=unix more showmode visualdelay=100
|nogdefault nomultiple-cursors sidescroll=0 whichwrap=b,s
|nohighlightedyank noNERDTree sidescrolloff=-1 wrap
| history=50 nrformats=hex nosmartcase wrapscan
|nohlsearch nonumber nosneak
|noideaglobalmode operatorfunc= startofline
|--ideajoin norelativenumber nosurround
|noargtextobj ideamarks norelativenumber startofline
|nobomb idearefactormode= scroll=0 nosurround
|nobreakindent ideawrite=all scrolljump=1 notextobj-entire
| colorcolumn= noignorecase scrolloff=-1 notextobj-indent
|nocommentary noincsearch selectmode= textwidth=0
|nocursorline nolist shellcmdflag=-x timeout
|nodigraph nomatchit shellxescape=@ timeoutlen=1000
|noexchange maxmapdepth=20 shellxquote={ notrackactionids
| fileformat=unix more showcmd virtualedit=
|nogdefault nomultiple-cursors showmode novisualbell
|nohighlightedyank noNERDTree sidescroll=0 visualdelay=100
| history=50 nrformats=hex sidescrolloff=-1 whichwrap=b,s
|nohlsearch nonumber nosmartcase wrap
|--ideajoin operatorfunc= nosneak wrapscan
| clipboard=ideaput,autoselect,exclude:cons\|linux
| fileencoding=utf-8
| guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175
@@ -577,7 +576,6 @@ class SetlocalCommandTest : VimTestCase() {
|nohlsearch
| ide=IntelliJ IDEA Community Edition
|--ideacopypreprocess
|noideaglobalmode
|--ideajoin
| ideamarks
| idearefactormode=

View File

@@ -8,6 +8,7 @@
package org.jetbrains.plugins.ideavim.ex.implementation.statements
import com.maddyhome.idea.vim.api.injector
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
@@ -55,6 +56,7 @@ class FunctionDeclarationTest : VimTestCase() {
)
typeText(commandToKeys("echo F1()"))
assertExOutput("5550")
injector.outputPanel.getCurrentOutputPanel()?.close()
typeText(commandToKeys("echo F2()"))
assertExOutput("555")
@@ -203,10 +205,13 @@ class FunctionDeclarationTest : VimTestCase() {
),
)
typeText(commandToKeys("echo F1()"))
injector.outputPanel.getCurrentOutputPanel()?.close()
typeText(commandToKeys("echo F2()"))
assertExOutput("1")
injector.outputPanel.getCurrentOutputPanel()?.close()
typeText(commandToKeys("echo F2()"))
assertExOutput("2")
injector.outputPanel.getCurrentOutputPanel()?.close()
typeText(commandToKeys("echo F2()"))
assertExOutput("3")
@@ -230,9 +235,11 @@ class FunctionDeclarationTest : VimTestCase() {
),
)
typeText(commandToKeys("echo F1()"))
injector.outputPanel.getCurrentOutputPanel()?.close()
typeText(commandToKeys("echo F2()"))
assertExOutput("1")
typeText(commandToKeys("delf! F1"))
injector.outputPanel.getCurrentOutputPanel()?.close()
typeText(commandToKeys("echo F2()"))
assertExOutput("2")
@@ -260,10 +267,12 @@ class FunctionDeclarationTest : VimTestCase() {
assertPluginError(true)
assertPluginErrorMessageContains("E121: Undefined variable: x")
injector.outputPanel.getCurrentOutputPanel()?.close()
typeText(commandToKeys("echo F2()"))
assertExOutput("10")
assertPluginError(false)
injector.outputPanel.getCurrentOutputPanel()?.close()
typeText(commandToKeys("echo F1()"))
assertPluginError(true)
assertPluginErrorMessageContains("E121: Undefined variable: x")
@@ -289,6 +298,7 @@ class FunctionDeclarationTest : VimTestCase() {
assertPluginError(true)
assertPluginErrorMessageContains("E121: Undefined variable: unknownVar")
injector.outputPanel.getCurrentOutputPanel()?.close()
typeText(commandToKeys("echo x"))
assertExOutput("10")
assertPluginError(false)
@@ -474,7 +484,12 @@ class FunctionDeclarationTest : VimTestCase() {
)
typeText(commandToKeys("1,4call F1()"))
assertPluginError(false)
assertExOutput("1:4")
assertExOutput("""
1:4
1:4
1:4
1:4
""".trimIndent())
assertState(
"""
-----

View File

@@ -8,7 +8,6 @@
package org.jetbrains.plugins.ideavim.extension.replacewithregister
import com.intellij.testFramework.UsefulTestCase.assertContainsElements
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.vim
@@ -24,6 +23,7 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInfo
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class ReplaceWithRegisterTest : VimTestCase() {
@@ -525,7 +525,7 @@ class ReplaceWithRegisterTest : VimTestCase() {
fun `test multiple carets`() {
// Behaviour of pasting a full line with multiple carets is undefined in Vim and has different implementation in
// IdeaVim depending on if ideaput is specified in 'clipboard' or not
assertContainsElements(optionsNoEditor().clipboard, OptionConstants.clipboard_ideaput)
assertTrue(OptionConstants.clipboard_ideaput in optionsNoEditor().clipboard)
enableExtensions("multiple-cursors")
val text = """

View File

@@ -321,8 +321,9 @@ class CaretVisualAttributesHelperTest : VimTestCase() {
""".trimMargin(),
)
injector.actionExecutor.executeAction(
"EditorCloneCaretBelow",
injector.executionContextManager.getEditorExecutionContext(fixture.editor.vim),
fixture.editor.vim,
name = "EditorCloneCaretBelow",
context = injector.executionContextManager.getEditorExecutionContext(fixture.editor.vim),
)
kotlin.test.assertEquals(2, fixture.editor.caretModel.caretCount)
assertCaretVisualAttributes(CaretVisualAttributes.Shape.BLOCK, 0f)

View File

@@ -7,7 +7,6 @@
*/
package org.jetbrains.plugins.ideavim.option
import com.intellij.testFramework.UsefulTestCase.assertDoesntContain
import com.maddyhome.idea.vim.helper.CharacterHelper
import com.maddyhome.idea.vim.helper.CharacterHelper.charType
import com.maddyhome.idea.vim.newapi.vim
@@ -92,14 +91,7 @@ class KeywordOptionTest : VimTestCase() {
setKeyword("b-a")
assertPluginError(true)
assertPluginErrorMessageContains("E474: Invalid argument: iskeyword=b-a")
assertDoesntContain(
values,
object : ArrayList<String?>() {
init {
add("b-a")
}
},
)
assertTrue("b-a" !in values)
}
@Test
@@ -107,14 +99,7 @@ class KeywordOptionTest : VimTestCase() {
setKeyword("ab")
assertPluginError(true)
assertPluginErrorMessageContains("E474: Invalid argument: iskeyword=ab")
assertDoesntContain(
values,
object : ArrayList<String?>() {
init {
add("ab")
}
},
)
assertTrue("ab" !in values)
}
@Test

View File

@@ -16,7 +16,6 @@ import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.LogicalPosition
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.common.CharacterPosition
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.register.RegisterConstants.ALTERNATE_BUFFER_REGISTER
import com.maddyhome.idea.vim.register.RegisterConstants.BLACK_HOLE_REGISTER
@@ -28,8 +27,8 @@ import com.maddyhome.idea.vim.register.RegisterConstants.LAST_SEARCH_REGISTER
import com.maddyhome.idea.vim.register.RegisterConstants.VALID_REGISTERS
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.toVimNotation
import org.junit.Assert.assertEquals
import org.junit.jupiter.api.TestInfo
import kotlin.test.assertEquals
object NeovimTesting {
private lateinit var neovimApi: NeovimApi
@@ -104,7 +103,7 @@ object NeovimTesting {
singleCaret
}
fun isNeovimTestingEnabled(): Boolean {
private fun isNeovimTestingEnabled(): Boolean {
val property = System.getProperty("ideavim.nvim.test", "false")
val neovimTestingEnabled = if (property.isBlank()) true else property.toBoolean()
return neovimTestingEnabled
@@ -168,7 +167,7 @@ object NeovimTesting {
fun vimMode() = neovimApi.mode.get().mode
private fun assertMode(editor: Editor) {
val ideavimState = editor.vim.vimStateMachine.mode.toVimNotation()
val ideavimState = editor.vim.mode.toVimNotation()
val neovimState = vimMode()
assertEquals(neovimState, ideavimState)
}
@@ -189,7 +188,7 @@ object NeovimTesting {
val neovimRegister = getRegister(register)
val vimPluginRegister = VimPlugin.getRegister().getRegister(register)
val ideavimRegister = vimPluginRegister?.text ?: ""
assertEquals("Register '$register'", neovimRegister, ideavimRegister)
assertEquals(neovimRegister, ideavimRegister, "Register '$register'")
if (neovimRegister.isNotEmpty()) {
val neovimRegisterType = neovimApi.callFunction("getregtype", listOf(register)).get().toString()
@@ -202,7 +201,7 @@ object NeovimTesting {
// We take only the first char because neovim returns width for block selection
val neovimChar = neovimRegisterType.getOrNull(0)?.toString() ?: ""
assertEquals("Register '$register'", expectedType, neovimChar)
assertEquals(expectedType, neovimChar, "Register '$register'")
}
}
}

View File

@@ -33,7 +33,6 @@ class TestOptionConstants {
const val whichwrap = "whichwrap"
// IdeaVim specific
const val ideaglobalmode = "ideaglobalmode"
const val ideatracetime = "ideatracetime"
}
}

View File

@@ -88,7 +88,6 @@ 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
@@ -128,6 +127,7 @@ abstract class VimTestCase {
KeyHandler.getInstance().fullReset(editor.vim)
}
KeyHandler.getInstance().keyHandlerState.reset(Mode.NORMAL())
injector.vimState.reset()
resetAllOptions()
VimPlugin.getKey().resetKeyMappings()
VimPlugin.getSearch().resetState()
@@ -221,6 +221,8 @@ abstract class VimTestCase {
VimPlugin.getChange().resetRepeat()
VimPlugin.getKey().savedShortcutConflicts.clear()
assertTrue(KeyHandler.getInstance().keyStack.isEmpty())
injector.outputPanel.getCurrentOutputPanel()?.close()
injector.modalInput.getCurrentModalInput()?.deactivate(refocusOwningEditor = false, resetCaret = false)
// Tear down neovim
NeovimTesting.tearDown(testInfo)
@@ -887,12 +889,6 @@ 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.getEditorExecutionContext(editor.vim)

View File

@@ -156,7 +156,6 @@ private class OptionsVerificator : BeforeTestExecutionCallback, AfterTestExecuti
val LOG by lazy { vimLogger<OptionsVerificator>() }
private val ignored = setOf(
TestOptionConstants.guicursor,
TestOptionConstants.ideaglobalmode,
TestOptionConstants.ideatracetime,
TestIjOptionConstants.ideavimsupport,
TestOptionConstants.maxmapdepth,

View File

@@ -27,6 +27,12 @@ class TestInjector(val injector: VimInjector) : VimInjector by injector {
tracers[key] = collector
}
override val modalInput
get() = injector.modalInput
override val vimState
get() = injector.vimState
override val optionGroup: VimOptionGroup
get() {
val tracer = tracers[OptionsTracer] as? OptionsTraceCollector

View File

@@ -1,3 +1,5 @@
import org.jetbrains.intellij.platform.gradle.TestFrameworkType
/*
* Copyright 2003-2024 The IdeaVim authors
*
@@ -9,16 +11,20 @@
plugins {
id("java")
kotlin("jvm")
id("org.jetbrains.intellij")
id("org.jetbrains.intellij.platform.module")
}
val kotlinVersion: String by project
val ideaType: String by project
val ideaVersion: String by project
val javaVersion: String by project
repositories {
mavenCentral()
maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
intellijPlatform {
defaultRepositories()
}
}
dependencies {
@@ -26,35 +32,25 @@ dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
testImplementation(testFixtures(project(":"))) // The root project
testImplementation("org.junit.vintage:junit-vintage-engine:5.10.3")
intellijPlatform {
create(ideaType, ideaVersion)
testFramework(TestFrameworkType.Platform)
testFramework(TestFrameworkType.JUnit5)
bundledPlugins("com.intellij.java", "org.jetbrains.plugins.yaml")
instrumentationTools()
}
}
intellijPlatform {
buildSearchableOptions = false
}
tasks {
test {
useJUnitPlatform()
}
verifyPlugin {
enabled = false
}
publishPlugin {
enabled = false
}
runIde {
enabled = false
}
runPluginVerifier {
enabled = false
}
}
intellij {
version.set(ideaVersion)
type.set("IC")
// Yaml is only used for testing. It's part of the IdeaIC distribution, but needs to be included as a reference
plugins.set(listOf("java", "yaml"))
}
java {

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