mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-08-23 13:01:04 +02:00
Compare commits
17 Commits
VIM-2227
...
surround-m
Author | SHA1 | Date | |
---|---|---|---|
d6076c719f | |||
![]() |
a3ca1b965b | ||
![]() |
dd20b480a7 | ||
![]() |
38292e97af | ||
![]() |
46ea752164 | ||
![]() |
194b744361 | ||
b50197f7ce | |||
![]() |
c00703d1d0 | ||
![]() |
6e12377116 | ||
![]() |
b0c4391ad8 | ||
![]() |
f43ac2538a | ||
![]() |
9eaf8b5d2d | ||
![]() |
e365d0b07c | ||
![]() |
69c273c4a5 | ||
![]() |
f7950e7adb | ||
![]() |
7c1ae9812e | ||
![]() |
5c794ac40e |
@@ -55,6 +55,7 @@ usual beta standards.
|
|||||||
* [VIM-696](https://youtrack.jetbrains.com/issue/VIM-696/vim-selection-issue-after-undo) Fix selection after undo
|
* [VIM-696](https://youtrack.jetbrains.com/issue/VIM-696/vim-selection-issue-after-undo) Fix selection after undo
|
||||||
* [VIM-744](https://youtrack.jetbrains.com/issue/VIM-744/Use-undoredo-with-count-modifier) Add count to undo/redo
|
* [VIM-744](https://youtrack.jetbrains.com/issue/VIM-744/Use-undoredo-with-count-modifier) Add count to undo/redo
|
||||||
* [VIM-1862](https://youtrack.jetbrains.com/issue/VIM-1862/Ex-commands-executed-in-keymaps-and-macros-are-added-to-the-command-history) Fix command history
|
* [VIM-1862](https://youtrack.jetbrains.com/issue/VIM-1862/Ex-commands-executed-in-keymaps-and-macros-are-added-to-the-command-history) Fix command history
|
||||||
|
* [VIM-2227](https://youtrack.jetbrains.com/issue/VIM-2227) Wrong behavior when deleting / changing surround with invalid character
|
||||||
|
|
||||||
### Merged PRs:
|
### Merged PRs:
|
||||||
* [468](https://github.com/JetBrains/ideavim/pull/468) by [Thomas Schouten](https://github.com/PHPirates): Implement UserDataHolder for EditorDataContext
|
* [468](https://github.com/JetBrains/ideavim/pull/468) by [Thomas Schouten](https://github.com/PHPirates): Implement UserDataHolder for EditorDataContext
|
||||||
@@ -63,6 +64,7 @@ usual beta standards.
|
|||||||
* [493](https://github.com/JetBrains/ideavim/pull/493) by [Matt Ellis](https://github.com/citizenmatt): Improvements to Commentary extension
|
* [493](https://github.com/JetBrains/ideavim/pull/493) by [Matt Ellis](https://github.com/citizenmatt): Improvements to Commentary extension
|
||||||
* [494](https://github.com/JetBrains/ideavim/pull/494) by [Matt Ellis](https://github.com/citizenmatt): Cleanup pre-212 CaretVisualAttributes compatibility code
|
* [494](https://github.com/JetBrains/ideavim/pull/494) by [Matt Ellis](https://github.com/citizenmatt): Cleanup pre-212 CaretVisualAttributes compatibility code
|
||||||
* [504](https://github.com/JetBrains/ideavim/pull/504) by [Matt Ellis](https://github.com/citizenmatt): Minor bug fixes
|
* [504](https://github.com/JetBrains/ideavim/pull/504) by [Matt Ellis](https://github.com/citizenmatt): Minor bug fixes
|
||||||
|
* [519](https://github.com/JetBrains/ideavim/pull/519) by [chylex](https://github.com/chylex): Fix(VIM-2227): Wrong behavior when deleting / changing surround with invalid character
|
||||||
|
|
||||||
## 1.10.0, 2022-02-17
|
## 1.10.0, 2022-02-17
|
||||||
|
|
||||||
|
21
qodana.yaml
21
qodana.yaml
@@ -1,6 +1,8 @@
|
|||||||
version: 1.0
|
version: 1.0
|
||||||
profile:
|
profile:
|
||||||
name: Qodana
|
name: Qodana
|
||||||
|
include:
|
||||||
|
- name: CheckDependencyLicenses
|
||||||
exclude:
|
exclude:
|
||||||
- name: MoveVariableDeclarationIntoWhen
|
- name: MoveVariableDeclarationIntoWhen
|
||||||
- name: PluginXmlValidity
|
- name: PluginXmlValidity
|
||||||
@@ -9,12 +11,15 @@ exclude:
|
|||||||
- name: UnusedReturnValue
|
- name: UnusedReturnValue
|
||||||
- name: All
|
- name: All
|
||||||
paths:
|
paths:
|
||||||
- build.gradle
|
- build.gradle.kts
|
||||||
- gradle/wrapper/gradle-wrapper.properties
|
- gradle/wrapper/gradle-wrapper.properties
|
||||||
- resources/icons/youtrack.svg
|
- src/main/resources/icons/youtrack.svg
|
||||||
- src/com/maddyhome/idea/vim/ex/vimscript/VimScriptCommandHandler.java
|
- src/main/java/com/maddyhome/idea/vim/helper/SearchHelper.java
|
||||||
- src/com/maddyhome/idea/vim/helper/SearchHelper.java
|
- src/main/java/com/maddyhome/idea/vim/regexp/RegExp.kt
|
||||||
- src/com/maddyhome/idea/vim/regexp/RegExp.java
|
- src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/JavaText.kt
|
||||||
- test/org/jetbrains/plugins/ideavim/propertybased/samples/JavaText.kt
|
- src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/LoremText.kt
|
||||||
- test/org/jetbrains/plugins/ideavim/propertybased/samples/LoremText.kt
|
- src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/SimpleText.kt
|
||||||
- test/org/jetbrains/plugins/ideavim/propertybased/samples/SimpleText.kt
|
- src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated/VimscriptListener.java
|
||||||
|
- src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated/VimscriptLexer.java
|
||||||
|
- src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated/VimscriptParser.java
|
||||||
|
- src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated/VimscriptVisitor.java
|
@@ -146,7 +146,7 @@ rShift: GREATER+;
|
|||||||
|
|
||||||
letCommands:
|
letCommands:
|
||||||
(WS | COLON)* range? (WS | COLON)* LET WS+ expr WS*
|
(WS | COLON)* range? (WS | COLON)* LET WS+ expr WS*
|
||||||
assignmentOperator = (ASSIGN | PLUS_ASSIGN | MINUS_ASSIGN | STAR_ASSIGN | DIV_ASSIGN | MOD_ASSIGN | DOT_ASSIGN)
|
assignmentOperator
|
||||||
WS* expr WS* ((inline_comment NEW_LINE) | (NEW_LINE | BAR)+)
|
WS* expr WS* ((inline_comment NEW_LINE) | (NEW_LINE | BAR)+)
|
||||||
#Let1Command|
|
#Let1Command|
|
||||||
|
|
||||||
@@ -154,6 +154,21 @@ letCommands:
|
|||||||
#Let2Command
|
#Let2Command
|
||||||
;
|
;
|
||||||
|
|
||||||
|
assignmentOperator:
|
||||||
|
ASSIGN | plusAssign | minusAssign | startAssign | divAssign | modAssign | dotAssign;
|
||||||
|
plusAssign:
|
||||||
|
PLUS ASSIGN;
|
||||||
|
minusAssign:
|
||||||
|
MINUS ASSIGN;
|
||||||
|
startAssign:
|
||||||
|
STAR ASSIGN;
|
||||||
|
divAssign:
|
||||||
|
DIV ASSIGN;
|
||||||
|
modAssign:
|
||||||
|
MOD ASSIGN;
|
||||||
|
dotAssign:
|
||||||
|
DOT ASSIGN;
|
||||||
|
|
||||||
shortRange:
|
shortRange:
|
||||||
((QUESTION (~QUESTION)* QUESTION?) | (DIV (~DIV)* DIV?));
|
((QUESTION (~QUESTION)* QUESTION?) | (DIV (~DIV)* DIV?));
|
||||||
range:
|
range:
|
||||||
@@ -778,12 +793,12 @@ IS_NOT_CS: 'isnot#';
|
|||||||
|
|
||||||
// Assignment operators
|
// Assignment operators
|
||||||
ASSIGN: '=';
|
ASSIGN: '=';
|
||||||
PLUS_ASSIGN: '+=';
|
//PLUS_ASSIGN: '+=';
|
||||||
MINUS_ASSIGN: '-=';
|
//MINUS_ASSIGN: '-=';
|
||||||
STAR_ASSIGN: '*=';
|
//STAR_ASSIGN: '*=';
|
||||||
DIV_ASSIGN: '/=';
|
//DIV_ASSIGN: '/=';
|
||||||
MOD_ASSIGN: '%=';
|
//MOD_ASSIGN: '%=';
|
||||||
DOT_ASSIGN: '.=';
|
//DOT_ASSIGN: '.=';
|
||||||
|
|
||||||
// Escaped chars
|
// Escaped chars
|
||||||
ESCAPED_QUESTION: '\\?';
|
ESCAPED_QUESTION: '\\?';
|
||||||
|
@@ -22,6 +22,7 @@ import com.intellij.openapi.application.runWriteAction
|
|||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
|
import com.maddyhome.idea.vim.api.VimChangeGroup
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.command.CommandState
|
import com.maddyhome.idea.vim.command.CommandState
|
||||||
@@ -40,6 +41,7 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegister
|
|||||||
import com.maddyhome.idea.vim.extension.VimExtensionHandler
|
import com.maddyhome.idea.vim.extension.VimExtensionHandler
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||||
import com.maddyhome.idea.vim.helper.mode
|
import com.maddyhome.idea.vim.helper.mode
|
||||||
|
import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
|
||||||
import com.maddyhome.idea.vim.key.OperatorFunction
|
import com.maddyhome.idea.vim.key.OperatorFunction
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimCaret
|
import com.maddyhome.idea.vim.newapi.IjVimCaret
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||||
@@ -84,22 +86,20 @@ class VimSurroundExtension : VimExtension {
|
|||||||
override val isRepeatable = true
|
override val isRepeatable = true
|
||||||
|
|
||||||
override fun execute(editor: VimEditor, context: ExecutionContext) {
|
override fun execute(editor: VimEditor, context: ExecutionContext) {
|
||||||
setOperatorFunction(Operator())
|
setOperatorFunction(Operator(supportsMultipleCursors = false)) // TODO
|
||||||
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
|
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class VSurroundHandler : VimExtensionHandler {
|
private class VSurroundHandler : VimExtensionHandler {
|
||||||
override fun execute(editor: VimEditor, context: ExecutionContext) {
|
override fun execute(editor: VimEditor, context: ExecutionContext) {
|
||||||
val selectionStart = editor.ij.caretModel.primaryCaret.selectionStart
|
|
||||||
// NB: Operator ignores SelectionType anyway
|
// NB: Operator ignores SelectionType anyway
|
||||||
if (!Operator().apply(editor.ij, context.ij, SelectionType.CHARACTER_WISE)) {
|
if (!Operator(supportsMultipleCursors = true).apply(editor.ij, context.ij, SelectionType.CHARACTER_WISE)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
runWriteAction {
|
runWriteAction {
|
||||||
// Leave visual mode
|
// Leave visual mode
|
||||||
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
|
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
|
||||||
editor.ij.caretModel.moveToOffset(selectionStart)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,6 +120,10 @@ class VimSurroundExtension : VimExtension {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun change(editor: Editor, charFrom: Char, newSurround: Pair<String, String>?) {
|
fun change(editor: Editor, charFrom: Char, newSurround: Pair<String, String>?) {
|
||||||
|
editor.runWithEveryCaretAndRestore { changeAtCaret(editor, charFrom, newSurround) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun changeAtCaret(editor: Editor, charFrom: Char, newSurround: Pair<String, String>?) {
|
||||||
// We take over the " register, so preserve it
|
// We take over the " register, so preserve it
|
||||||
val oldValue: List<KeyStroke>? = getRegister(REGISTER)
|
val oldValue: List<KeyStroke>? = getRegister(REGISTER)
|
||||||
// Empty the " register
|
// Empty the " register
|
||||||
@@ -184,26 +188,44 @@ class VimSurroundExtension : VimExtension {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Operator : OperatorFunction {
|
private class Operator(private val supportsMultipleCursors: Boolean) : OperatorFunction {
|
||||||
override fun apply(editor: Editor, context: DataContext, selectionType: SelectionType): Boolean {
|
override fun apply(editor: Editor, context: DataContext, selectionType: SelectionType): Boolean {
|
||||||
val c = getChar(editor)
|
val c = getChar(editor)
|
||||||
if (c.code == 0) return true
|
if (c.code == 0) return true
|
||||||
|
|
||||||
val pair = getOrInputPair(c, editor) ?: return false
|
val pair = getOrInputPair(c, editor) ?: return false
|
||||||
// XXX: Will it work with line-wise or block-wise selections?
|
|
||||||
val range = getSurroundRange(editor) ?: return false
|
|
||||||
runWriteAction {
|
runWriteAction {
|
||||||
val change = VimPlugin.getChange()
|
val change = VimPlugin.getChange()
|
||||||
val leftSurround = pair.first
|
if (supportsMultipleCursors) {
|
||||||
val primaryCaret = editor.caretModel.primaryCaret
|
editor.runWithEveryCaretAndRestore {
|
||||||
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, leftSurround)
|
applyOnce(editor, change, pair)
|
||||||
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.endOffset + leftSurround.length, pair.second)
|
}
|
||||||
// Jump back to start
|
}
|
||||||
executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor)
|
else {
|
||||||
|
applyOnce(editor, change, pair)
|
||||||
|
// Jump back to start
|
||||||
|
executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: Pair<String, String>) {
|
||||||
|
// XXX: Will it work with line-wise or block-wise selections?
|
||||||
|
val range = getSurroundRange(editor)
|
||||||
|
if (range != null) {
|
||||||
|
val primaryCaret = editor.caretModel.primaryCaret
|
||||||
|
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, pair.first)
|
||||||
|
change.insertText(
|
||||||
|
IjVimEditor(editor),
|
||||||
|
IjVimCaret(primaryCaret),
|
||||||
|
range.endOffset + pair.first.length,
|
||||||
|
pair.second
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getSurroundRange(editor: Editor): TextRange? = when (editor.mode) {
|
private fun getSurroundRange(editor: Editor): TextRange? = when (editor.mode) {
|
||||||
CommandState.Mode.COMMAND -> VimPlugin.getMark().getChangeMarks(editor.vim)
|
CommandState.Mode.COMMAND -> VimPlugin.getMark().getChangeMarks(editor.vim)
|
||||||
CommandState.Mode.VISUAL -> editor.caretModel.primaryCaret.run { TextRange(selectionStart, selectionEnd) }
|
CommandState.Mode.VISUAL -> editor.caretModel.primaryCaret.run { TextRange(selectionStart, selectionEnd) }
|
||||||
|
@@ -39,7 +39,6 @@ import com.intellij.psi.codeStyle.CodeStyleManager;
|
|||||||
import com.intellij.psi.util.PsiUtilBase;
|
import com.intellij.psi.util.PsiUtilBase;
|
||||||
import com.intellij.util.containers.ContainerUtil;
|
import com.intellij.util.containers.ContainerUtil;
|
||||||
import com.maddyhome.idea.vim.EventFacade;
|
import com.maddyhome.idea.vim.EventFacade;
|
||||||
import com.maddyhome.idea.vim.RegisterActions;
|
|
||||||
import com.maddyhome.idea.vim.VimPlugin;
|
import com.maddyhome.idea.vim.VimPlugin;
|
||||||
import com.maddyhome.idea.vim.api.*;
|
import com.maddyhome.idea.vim.api.*;
|
||||||
import com.maddyhome.idea.vim.command.*;
|
import com.maddyhome.idea.vim.command.*;
|
||||||
@@ -123,10 +122,10 @@ public class ChangeGroup extends VimChangeGroupBase {
|
|||||||
* @param col The column to indent to
|
* @param col The column to indent to
|
||||||
*/
|
*/
|
||||||
private void insertNewLineBelow(@NotNull VimEditor editor, @NotNull VimCaret caret, int col) {
|
private void insertNewLineBelow(@NotNull VimEditor editor, @NotNull VimCaret caret, int col) {
|
||||||
if (((IjVimEditor) editor).getEditor().isOneLineMode()) return;
|
if (editor.isOneLineMode()) return;
|
||||||
|
|
||||||
injector.getMotion().moveCaret(editor, caret, VimPlugin.getMotion().moveCaretToLineEnd(editor, caret));
|
caret.moveToOffset(injector.getMotion().moveCaretToLineEnd(editor, caret));
|
||||||
UserDataManager.setVimChangeActionSwitchMode(((IjVimEditor) editor).getEditor(), CommandState.Mode.INSERT);
|
editor.setVimChangeActionSwitchMode(CommandState.Mode.INSERT);
|
||||||
insertText(editor, caret, "\n" + IndentConfig.create(((IjVimEditor) editor).getEditor()).createIndentBySize(col));
|
insertText(editor, caret, "\n" + IndentConfig.create(((IjVimEditor) editor).getEditor()).createIndentBySize(col));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,41 +164,6 @@ public class ChangeGroup extends VimChangeGroupBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable Pair<@NotNull TextRange, @NotNull SelectionType> getDeleteRangeAndType(@NotNull VimEditor editor,
|
|
||||||
@NotNull VimCaret caret,
|
|
||||||
@NotNull ExecutionContext context,
|
|
||||||
final @NotNull Argument argument,
|
|
||||||
boolean isChange,
|
|
||||||
@NotNull OperatorArguments operatorArguments) {
|
|
||||||
final TextRange range =
|
|
||||||
injector.getMotion().getMotionRange(editor, caret, context, argument, operatorArguments);
|
|
||||||
if (range == null) 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
|
|
||||||
SelectionType type;
|
|
||||||
if (argument.getMotion().isLinewiseMotion()) {
|
|
||||||
type = SelectionType.LINE_WISE;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
type = SelectionType.CHARACTER_WISE;
|
|
||||||
}
|
|
||||||
final Command motion = argument.getMotion();
|
|
||||||
if (!isChange && !motion.isLinewiseMotion()) {
|
|
||||||
VimLogicalPosition start = editor.offsetToLogicalPosition(range.getStartOffset());
|
|
||||||
VimLogicalPosition end = editor.offsetToLogicalPosition(range.getEndOffset());
|
|
||||||
if (start.getLine() != end.getLine()) {
|
|
||||||
if (!SearchHelper.anyNonWhitespace(((IjVimEditor) editor).getEditor(), range.getStartOffset(), -1) &&
|
|
||||||
!SearchHelper.anyNonWhitespace(((IjVimEditor) editor).getEditor(), range.getEndOffset(), 1)) {
|
|
||||||
type = SelectionType.LINE_WISE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Pair<>(range, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable Pair<@NotNull TextRange, @NotNull SelectionType> getDeleteRangeAndType2(@NotNull VimEditor editor,
|
public @Nullable Pair<@NotNull TextRange, @NotNull SelectionType> getDeleteRangeAndType2(@NotNull VimEditor editor,
|
||||||
@@ -236,60 +200,6 @@ public class ChangeGroup extends VimChangeGroupBase {
|
|||||||
return new Pair<>(range, type);
|
return new Pair<>(range, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete the range of text.
|
|
||||||
*
|
|
||||||
* @param editor The editor to delete the text from
|
|
||||||
* @param caret The caret to be moved after deletion
|
|
||||||
* @param range The range to delete
|
|
||||||
* @param type The type of deletion
|
|
||||||
* @param isChange Is from a change action
|
|
||||||
* @return true if able to delete the text, false if not
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean deleteRange(@NotNull VimEditor editor,
|
|
||||||
@NotNull VimCaret caret,
|
|
||||||
@NotNull TextRange range,
|
|
||||||
@Nullable SelectionType type,
|
|
||||||
boolean isChange) {
|
|
||||||
|
|
||||||
// Update the last column before we delete, or we might be retrieving the data for a line that no longer exists
|
|
||||||
UserDataManager.setVimLastColumn(((IjVimCaret) caret).getCaret(), InlayHelperKt.getInlayAwareVisualColumn(((IjVimCaret) caret).getCaret()));
|
|
||||||
|
|
||||||
boolean removeLastNewLine = removeLastNewLine(editor, range, type);
|
|
||||||
final boolean res = deleteText(editor, range, type);
|
|
||||||
if (removeLastNewLine) {
|
|
||||||
int textLength = ((IjVimEditor) editor).getEditor().getDocument().getTextLength();
|
|
||||||
((IjVimEditor) editor).getEditor().getDocument().deleteString(textLength - 1, textLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res) {
|
|
||||||
int pos = EditorHelper.normalizeOffset(((IjVimEditor) editor).getEditor(), range.getStartOffset(), isChange);
|
|
||||||
if (type == SelectionType.LINE_WISE) {
|
|
||||||
pos = VimPlugin.getMotion()
|
|
||||||
.moveCaretToLineWithStartOfLineOption(editor, editor.offsetToLogicalPosition(pos).getLine(),
|
|
||||||
caret);
|
|
||||||
}
|
|
||||||
injector.getMotion().moveCaret(editor, caret, pos);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean removeLastNewLine(@NotNull VimEditor editor, @NotNull TextRange range, @Nullable SelectionType type) {
|
|
||||||
int endOffset = range.getEndOffset();
|
|
||||||
int fileSize = EditorHelperRt.getFileSize(((IjVimEditor) editor).getEditor());
|
|
||||||
if (endOffset > fileSize) {
|
|
||||||
if (injector.getOptionService().isSet(OptionScope.GLOBAL.INSTANCE, OptionConstants.ideastrictmodeName, OptionConstants.ideastrictmodeName)) {
|
|
||||||
throw new IllegalStateException("Incorrect offset. File size: " + fileSize + ", offset: " + endOffset);
|
|
||||||
}
|
|
||||||
endOffset = fileSize;
|
|
||||||
}
|
|
||||||
return type == SelectionType.LINE_WISE &&
|
|
||||||
range.getStartOffset() != 0 &&
|
|
||||||
((IjVimEditor) editor).getEditor().getDocument().getCharsSequence().charAt(endOffset - 1) != '\n' &&
|
|
||||||
endOffset == fileSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void insertLineAround(@NotNull VimEditor editor, @NotNull ExecutionContext context, int shift) {
|
public void insertLineAround(@NotNull VimEditor editor, @NotNull ExecutionContext context, int shift) {
|
||||||
com.maddyhome.idea.vim.newapi.ChangeGroupKt.insertLineAround(editor, context, shift);
|
com.maddyhome.idea.vim.newapi.ChangeGroupKt.insertLineAround(editor, context, shift);
|
||||||
@@ -303,48 +213,6 @@ public class ChangeGroup extends VimChangeGroupBase {
|
|||||||
return com.maddyhome.idea.vim.newapi.ChangeGroupKt.deleteRange(editor, caret, range, type);
|
return com.maddyhome.idea.vim.newapi.ChangeGroupKt.deleteRange(editor, caret, range, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete count characters and then enter insert mode
|
|
||||||
*
|
|
||||||
* @param editor The editor to change
|
|
||||||
* @param caret The caret to be moved
|
|
||||||
* @param count The number of characters to change
|
|
||||||
* @return true if able to delete count characters, false if not
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean changeCharacters(@NotNull VimEditor editor, @NotNull VimCaret caret, int count) {
|
|
||||||
int len = EditorHelper.getLineLength(((IjVimEditor) editor).getEditor());
|
|
||||||
int col = ((IjVimCaret) caret).getCaret().getLogicalPosition().column;
|
|
||||||
if (col + count >= len) {
|
|
||||||
return changeEndOfLine(editor, caret, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean res = deleteCharacter(editor, caret, count, true);
|
|
||||||
if (res) {
|
|
||||||
UserDataManager.setVimChangeActionSwitchMode(((IjVimEditor) editor).getEditor(), CommandState.Mode.INSERT);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete from the cursor to the end of count - 1 lines down and enter insert mode
|
|
||||||
*
|
|
||||||
* @param editor The editor to change
|
|
||||||
* @param caret The caret to perform action on
|
|
||||||
* @param count The number of lines to change
|
|
||||||
* @return true if able to delete count lines, false if not
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean changeEndOfLine(@NotNull VimEditor editor, @NotNull VimCaret caret, int count) {
|
|
||||||
boolean res = deleteEndOfLine(editor, caret, count);
|
|
||||||
if (res) {
|
|
||||||
injector.getMotion().moveCaret(editor, caret, VimPlugin.getMotion().moveCaretToLineEnd(editor, caret));
|
|
||||||
UserDataManager.setVimChangeActionSwitchMode(((IjVimEditor) editor).getEditor(), CommandState.Mode.INSERT);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete the text covered by the motion command argument and enter insert mode
|
* Delete the text covered by the motion command argument and enter insert mode
|
||||||
@@ -368,9 +236,9 @@ public class ChangeGroup extends VimChangeGroupBase {
|
|||||||
String id = motion.getAction().getId();
|
String id = motion.getAction().getId();
|
||||||
boolean kludge = false;
|
boolean kludge = false;
|
||||||
boolean bigWord = id.equals(VIM_MOTION_BIG_WORD_RIGHT);
|
boolean bigWord = id.equals(VIM_MOTION_BIG_WORD_RIGHT);
|
||||||
final CharSequence chars = ((IjVimEditor) editor).getEditor().getDocument().getCharsSequence();
|
final CharSequence chars = editor.text();
|
||||||
final int offset = ((IjVimCaret) caret).getCaret().getOffset();
|
final int offset = caret.getOffset().getPoint();
|
||||||
int fileSize = EditorHelperRt.getFileSize(((IjVimEditor) editor).getEditor());
|
int fileSize = ((int)editor.fileSize());
|
||||||
if (fileSize > 0 && offset < fileSize) {
|
if (fileSize > 0 && offset < fileSize) {
|
||||||
final CharacterHelper.CharacterType charType = CharacterHelper.charType(chars.charAt(offset), bigWord);
|
final CharacterHelper.CharacterType charType = CharacterHelper.charType(chars.charAt(offset), bigWord);
|
||||||
if (charType != CharacterHelper.CharacterType.WHITESPACE) {
|
if (charType != CharacterHelper.CharacterType.WHITESPACE) {
|
||||||
@@ -379,24 +247,24 @@ public class ChangeGroup extends VimChangeGroupBase {
|
|||||||
if (wordMotions.contains(id) && lastWordChar && motion.getCount() == 1) {
|
if (wordMotions.contains(id) && lastWordChar && motion.getCount() == 1) {
|
||||||
final boolean res = deleteCharacter(editor, caret, 1, true);
|
final boolean res = deleteCharacter(editor, caret, 1, true);
|
||||||
if (res) {
|
if (res) {
|
||||||
UserDataManager.setVimChangeActionSwitchMode(((IjVimEditor) editor).getEditor(), CommandState.Mode.INSERT);
|
editor.setVimChangeActionSwitchMode(CommandState.Mode.INSERT);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case VIM_MOTION_WORD_RIGHT:
|
case VIM_MOTION_WORD_RIGHT:
|
||||||
kludge = true;
|
kludge = true;
|
||||||
motion.setAction(RegisterActions.findActionOrDie(VIM_MOTION_WORD_END_RIGHT));
|
motion.setAction(injector.getActionExecutor().findVimActionOrDie(VIM_MOTION_WORD_END_RIGHT));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case VIM_MOTION_BIG_WORD_RIGHT:
|
case VIM_MOTION_BIG_WORD_RIGHT:
|
||||||
kludge = true;
|
kludge = true;
|
||||||
motion.setAction(RegisterActions.findActionOrDie(VIM_MOTION_BIG_WORD_END_RIGHT));
|
motion.setAction(injector.getActionExecutor().findVimActionOrDie(VIM_MOTION_BIG_WORD_END_RIGHT));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case VIM_MOTION_CAMEL_RIGHT:
|
case VIM_MOTION_CAMEL_RIGHT:
|
||||||
kludge = true;
|
kludge = true;
|
||||||
motion.setAction(RegisterActions.findActionOrDie(VIM_MOTION_CAMEL_END_RIGHT));
|
motion.setAction(injector.getActionExecutor().findVimActionOrDie(VIM_MOTION_CAMEL_END_RIGHT));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -405,8 +273,8 @@ public class ChangeGroup extends VimChangeGroupBase {
|
|||||||
|
|
||||||
if (kludge) {
|
if (kludge) {
|
||||||
int cnt = operatorArguments.getCount1() * motion.getCount();
|
int cnt = operatorArguments.getCount1() * motion.getCount();
|
||||||
int pos1 = SearchHelper.findNextWordEnd(chars, offset, fileSize, cnt, bigWord, false);
|
int pos1 = injector.getSearchHelper().findNextWordEnd(chars, offset, fileSize, cnt, bigWord, false);
|
||||||
int pos2 = SearchHelper.findNextWordEnd(chars, pos1, fileSize, -cnt, bigWord, false);
|
int pos2 = injector.getSearchHelper().findNextWordEnd(chars, pos1, fileSize, -cnt, bigWord, false);
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("pos=" + offset);
|
logger.debug("pos=" + offset);
|
||||||
logger.debug("pos1=" + pos1);
|
logger.debug("pos1=" + pos1);
|
||||||
@@ -427,11 +295,11 @@ public class ChangeGroup extends VimChangeGroupBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (VimPlugin.getOptionService().isSet(OptionScope.GLOBAL.INSTANCE, OptionConstants.experimentalapiName, OptionConstants.experimentalapiName)) {
|
if (injector.getOptionService().isSet(OptionScope.GLOBAL.INSTANCE, OptionConstants.experimentalapiName, OptionConstants.experimentalapiName)) {
|
||||||
Pair<TextRange, SelectionType> deleteRangeAndType =
|
Pair<TextRange, SelectionType> deleteRangeAndType =
|
||||||
getDeleteRangeAndType2(editor, caret, context, argument, true, operatorArguments.withCount0(count0));
|
getDeleteRangeAndType2(editor, caret, context, argument, true, operatorArguments.withCount0(count0));
|
||||||
if (deleteRangeAndType == null) return false;
|
if (deleteRangeAndType == null) return false;
|
||||||
ChangeGroupKt.changeRange(((IjVimEditor) editor).getEditor(), ((IjVimCaret) caret).getCaret(), deleteRangeAndType.getFirst(), deleteRangeAndType.getSecond(), ((IjExecutionContext) context).getContext());
|
//ChangeGroupKt.changeRange(((IjVimEditor) editor).getEditor(), ((IjVimCaret) caret).getCaret(), deleteRangeAndType.getFirst(), deleteRangeAndType.getSecond(), ((IjExecutionContext) context).getContext());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -442,23 +310,6 @@ public class ChangeGroup extends VimChangeGroupBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Counts number of lines in the visual block.
|
|
||||||
* <p>
|
|
||||||
* The result includes empty and short lines which does not have explicit start position (caret).
|
|
||||||
*
|
|
||||||
* @param editor The editor the block was selected in
|
|
||||||
* @param range The range corresponding to the selected block
|
|
||||||
* @return total number of lines
|
|
||||||
*/
|
|
||||||
public static int getLinesCountInVisualBlock(@NotNull VimEditor editor, @NotNull TextRange range) {
|
|
||||||
final int[] startOffsets = range.getStartOffsets();
|
|
||||||
if (startOffsets.length == 0) return 0;
|
|
||||||
final VimLogicalPosition firstStart = editor.offsetToLogicalPosition(startOffsets[0]);
|
|
||||||
final VimLogicalPosition lastStart = editor.offsetToLogicalPosition(startOffsets[range.size() - 1]);
|
|
||||||
return lastStart.getLine() - firstStart.getLine() + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles the case of count characters
|
* Toggles the case of count characters
|
||||||
*
|
*
|
||||||
@@ -484,7 +335,7 @@ public class ChangeGroup extends VimChangeGroupBase {
|
|||||||
@NotNull TextRange range,
|
@NotNull TextRange range,
|
||||||
boolean append,
|
boolean append,
|
||||||
@NotNull OperatorArguments operatorArguments) {
|
@NotNull OperatorArguments operatorArguments) {
|
||||||
final int lines = getLinesCountInVisualBlock(editor, range);
|
final int lines = VimChangeGroupBase.Companion.getLinesCountInVisualBlock(editor, range);
|
||||||
final VimLogicalPosition startPosition = editor.offsetToLogicalPosition(range.getStartOffset());
|
final VimLogicalPosition startPosition = editor.offsetToLogicalPosition(range.getStartOffset());
|
||||||
|
|
||||||
boolean visualBlockMode = operatorArguments.getMode() == CommandState.Mode.VISUAL &&
|
boolean visualBlockMode = operatorArguments.getMode() == CommandState.Mode.VISUAL &&
|
||||||
@@ -589,24 +440,24 @@ public class ChangeGroup extends VimChangeGroupBase {
|
|||||||
int col = 0;
|
int col = 0;
|
||||||
int lines = 0;
|
int lines = 0;
|
||||||
if (type == SelectionType.BLOCK_WISE) {
|
if (type == SelectionType.BLOCK_WISE) {
|
||||||
lines = getLinesCountInVisualBlock(editor, range);
|
lines = VimChangeGroupBase.Companion.getLinesCountInVisualBlock(editor, range);
|
||||||
col = editor.offsetToLogicalPosition(range.getStartOffset()).getColumn();
|
col = editor.offsetToLogicalPosition(range.getStartOffset()).getColumn();
|
||||||
if (UserDataManager.getVimLastColumn(((IjVimCaret) caret).getCaret()) == VimMotionGroupBase.LAST_COLUMN) {
|
if (caret.getVimLastColumn() == VimMotionGroupBase.LAST_COLUMN) {
|
||||||
col = VimMotionGroupBase.LAST_COLUMN;
|
col = VimMotionGroupBase.LAST_COLUMN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
boolean after = range.getEndOffset() >= EditorHelperRt.getFileSize(((IjVimEditor) editor).getEditor());
|
boolean after = range.getEndOffset() >= editor.fileSize();
|
||||||
|
|
||||||
final VimLogicalPosition lp = editor.offsetToLogicalPosition(VimPlugin.getMotion().moveCaretToLineStartSkipLeading(editor, caret));
|
final VimLogicalPosition lp = editor.offsetToLogicalPosition(injector.getMotion().moveCaretToLineStartSkipLeading(editor, caret));
|
||||||
|
|
||||||
boolean res = deleteRange(editor, caret, range, type, true);
|
boolean res = deleteRange(editor, caret, range, type, true);
|
||||||
if (res) {
|
if (res) {
|
||||||
if (type == SelectionType.LINE_WISE) {
|
if (type == SelectionType.LINE_WISE) {
|
||||||
// Please don't use `getDocument().getText().isEmpty()` because it converts CharSequence into String
|
// Please don't use `getDocument().getText().isEmpty()` because it converts CharSequence into String
|
||||||
if (((IjVimEditor) editor).getEditor().getDocument().getTextLength() == 0) {
|
if (editor.fileSize() == 0) {
|
||||||
insertBeforeCursor(editor, context);
|
insertBeforeCursor(editor, context);
|
||||||
}
|
}
|
||||||
else if (after && !EditorHelperRt.endsWithNewLine(((IjVimEditor) editor).getEditor())) {
|
else if (after && !EngineEditorHelperKt.endsWithNewLine(editor)) {
|
||||||
insertNewLineBelow(editor, caret, lp.getColumn());
|
insertNewLineBelow(editor, caret, lp.getColumn());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@@ -890,6 +890,10 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
@Override
|
@Override
|
||||||
public void setLastSearchPattern(@Nullable String lastSearchPattern) {
|
public void setLastSearchPattern(@Nullable String lastSearchPattern) {
|
||||||
this.lastSearch = lastSearchPattern;
|
this.lastSearch = lastSearchPattern;
|
||||||
|
if (showSearchHighlight) {
|
||||||
|
resetIncsearchHighlights();
|
||||||
|
updateSearchHighlights();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -22,6 +22,7 @@ package com.maddyhome.idea.vim.helper
|
|||||||
|
|
||||||
import com.intellij.codeWithMe.ClientId
|
import com.intellij.codeWithMe.ClientId
|
||||||
import com.intellij.openapi.editor.Caret
|
import com.intellij.openapi.editor.Caret
|
||||||
|
import com.intellij.openapi.editor.CaretState
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.editor.ex.util.EditorUtil
|
import com.intellij.openapi.editor.ex.util.EditorUtil
|
||||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
|
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
|
||||||
@@ -106,3 +107,41 @@ val Caret.vimLine: Int
|
|||||||
*/
|
*/
|
||||||
val Editor.vimLine: Int
|
val Editor.vimLine: Int
|
||||||
get() = this.caretModel.currentCaret.vimLine
|
get() = this.caretModel.currentCaret.vimLine
|
||||||
|
|
||||||
|
inline fun Editor.runWithEveryCaretAndRestore(action: () -> Unit) {
|
||||||
|
val caretModel = this.caretModel
|
||||||
|
val carets = if (this.inBlockSubMode) null else caretModel.allCarets
|
||||||
|
if (carets == null || carets.size == 1) {
|
||||||
|
action()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var initialDocumentSize = this.document.textLength
|
||||||
|
var documentSizeDifference = 0
|
||||||
|
|
||||||
|
val caretOffsets = carets.map { it.selectionStart to it.selectionEnd }
|
||||||
|
val restoredCarets = mutableListOf<CaretState>()
|
||||||
|
|
||||||
|
caretModel.removeSecondaryCarets()
|
||||||
|
|
||||||
|
for ((selectionStart, selectionEnd) in caretOffsets) {
|
||||||
|
if (selectionStart == selectionEnd) {
|
||||||
|
caretModel.primaryCaret.moveToOffset(selectionStart + documentSizeDifference)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
caretModel.primaryCaret.setSelection(
|
||||||
|
selectionStart + documentSizeDifference,
|
||||||
|
selectionEnd + documentSizeDifference
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
action()
|
||||||
|
restoredCarets.add(caretModel.caretsAndSelections.single())
|
||||||
|
|
||||||
|
val documentLength = this.document.textLength
|
||||||
|
documentSizeDifference += documentLength - initialDocumentSize
|
||||||
|
initialDocumentSize = documentLength
|
||||||
|
}
|
||||||
|
|
||||||
|
caretModel.caretsAndSelections = restoredCarets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -21,18 +21,24 @@ package com.maddyhome.idea.vim.helper
|
|||||||
import com.intellij.openapi.actionSystem.ActionGroup
|
import com.intellij.openapi.actionSystem.ActionGroup
|
||||||
import com.intellij.openapi.actionSystem.ActionManager
|
import com.intellij.openapi.actionSystem.ActionManager
|
||||||
import com.intellij.openapi.actionSystem.ActionPlaces
|
import com.intellij.openapi.actionSystem.ActionPlaces
|
||||||
|
import com.intellij.openapi.actionSystem.AnAction
|
||||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||||
|
import com.intellij.openapi.actionSystem.AnActionResult
|
||||||
import com.intellij.openapi.actionSystem.DataContext
|
import com.intellij.openapi.actionSystem.DataContext
|
||||||
import com.intellij.openapi.actionSystem.IdeActions
|
import com.intellij.openapi.actionSystem.IdeActions
|
||||||
|
import com.intellij.openapi.actionSystem.PlatformCoreDataKeys
|
||||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
|
import com.intellij.openapi.actionSystem.PlatformDataKeys
|
||||||
import com.intellij.openapi.actionSystem.Presentation
|
import com.intellij.openapi.actionSystem.Presentation
|
||||||
|
import com.intellij.openapi.actionSystem.ex.ActionManagerEx
|
||||||
import com.intellij.openapi.actionSystem.ex.ActionUtil
|
import com.intellij.openapi.actionSystem.ex.ActionUtil
|
||||||
import com.intellij.openapi.command.CommandProcessor
|
import com.intellij.openapi.command.CommandProcessor
|
||||||
import com.intellij.openapi.command.UndoConfirmationPolicy
|
import com.intellij.openapi.command.UndoConfirmationPolicy
|
||||||
import com.intellij.openapi.components.Service
|
import com.intellij.openapi.components.Service
|
||||||
import com.intellij.openapi.editor.actionSystem.DocCommandGroupId
|
import com.intellij.openapi.editor.actionSystem.DocCommandGroupId
|
||||||
|
import com.intellij.openapi.project.IndexNotReadyException
|
||||||
import com.intellij.openapi.ui.popup.JBPopupFactory
|
import com.intellij.openapi.ui.popup.JBPopupFactory
|
||||||
import com.intellij.openapi.util.NlsContexts
|
import com.intellij.openapi.util.NlsContexts
|
||||||
|
import com.intellij.util.SlowOperations
|
||||||
import com.maddyhome.idea.vim.RegisterActions
|
import com.maddyhome.idea.vim.RegisterActions
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.api.NativeAction
|
import com.maddyhome.idea.vim.api.NativeAction
|
||||||
@@ -95,11 +101,48 @@ class IjActionExecutor : VimActionExecutor {
|
|||||||
popup.showInFocusCenter()
|
popup.showInFocusCenter()
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
ActionUtil.performActionDumbAwareWithCallbacks(ijAction, event)
|
performDumbAwareWithCallbacks(ijAction, event) { ijAction.actionPerformed(event) }
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
val component = event.getData(PlatformCoreDataKeys.CONTEXT_COMPONENT)
|
||||||
|
var result: AnActionResult? = null
|
||||||
|
try {
|
||||||
|
SlowOperations.allowSlowOperations(SlowOperations.ACTION_PERFORM).use { ignore ->
|
||||||
|
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, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun canBePerformed(event: AnActionEvent, action: ActionGroup, context: DataContext): Boolean {
|
private fun canBePerformed(event: AnActionEvent, action: ActionGroup, context: DataContext): Boolean {
|
||||||
val presentation = event.presentation
|
val presentation = event.presentation
|
||||||
return try {
|
return try {
|
||||||
@@ -155,6 +198,10 @@ class IjActionExecutor : VimActionExecutor {
|
|||||||
return RegisterActions.findAction(id)
|
return RegisterActions.findAction(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun findVimActionOrDie(id: String): EditorActionHandlerBase {
|
||||||
|
return RegisterActions.findActionOrDie(id)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getAction(actionId: String): NativeAction? {
|
override fun getAction(actionId: String): NativeAction? {
|
||||||
return ActionManager.getInstance().getAction(actionId)?.let { IjNativeAction(it) }
|
return ActionManager.getInstance().getAction(actionId)?.let { IjNativeAction(it) }
|
||||||
}
|
}
|
||||||
|
@@ -143,4 +143,8 @@ class IjEditorHelper : EngineEditorHelper {
|
|||||||
override fun getLeadingWhitespace(editor: VimEditor, line: Int): String {
|
override fun getLeadingWhitespace(editor: VimEditor, line: Int): String {
|
||||||
return EditorHelper.getLeadingWhitespace(editor.ij, line)
|
return EditorHelper.getLeadingWhitespace(editor.ij, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun anyNonWhitespace(editor: VimEditor, offset: Int, dir: Int): Boolean {
|
||||||
|
return SearchHelper.anyNonWhitespace(editor.ij, offset, dir)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -99,5 +99,6 @@ class UndoRedoHelper : UndoRedoBase() {
|
|||||||
while (check() && !changeListener.hasChanged) {
|
while (check() && !changeListener.hasChanged) {
|
||||||
action.run()
|
action.run()
|
||||||
}
|
}
|
||||||
|
vimDocument.removeChangeListener(changeListener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -32,6 +32,7 @@ import com.intellij.openapi.actionSystem.AnActionEvent
|
|||||||
import com.intellij.openapi.actionSystem.CommonDataKeys
|
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||||
import com.intellij.openapi.actionSystem.DataContext
|
import com.intellij.openapi.actionSystem.DataContext
|
||||||
import com.intellij.openapi.actionSystem.ex.AnActionListener
|
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.Editor
|
||||||
import com.intellij.openapi.project.DumbAwareToggleAction
|
import com.intellij.openapi.project.DumbAwareToggleAction
|
||||||
import com.maddyhome.idea.vim.KeyHandler
|
import com.maddyhome.idea.vim.KeyHandler
|
||||||
@@ -70,7 +71,7 @@ object IdeaSpecifics {
|
|||||||
//region Track action id
|
//region Track action id
|
||||||
if (VimPlugin.getOptionService().isSet(OptionScope.GLOBAL, OptionConstants.trackactionidsName)) {
|
if (VimPlugin.getOptionService().isSet(OptionScope.GLOBAL, OptionConstants.trackactionidsName)) {
|
||||||
if (action !is NotificationService.ActionIdNotifier.CopyActionId && action !is NotificationService.ActionIdNotifier.StopTracking) {
|
if (action !is NotificationService.ActionIdNotifier.CopyActionId && action !is NotificationService.ActionIdNotifier.StopTracking) {
|
||||||
val id: String? = ActionManager.getInstance().getId(action)
|
val id: String? = ActionManager.getInstance().getId(action) ?: (action.shortcutSet as? ProxyShortcutSet)?.actionId
|
||||||
VimPlugin.getNotifications(dataContext.getData(CommonDataKeys.PROJECT)).notifyActionId(id)
|
VimPlugin.getNotifications(dataContext.getData(CommonDataKeys.PROJECT)).notifyActionId(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,6 +28,7 @@ import com.maddyhome.idea.vim.VimPlugin
|
|||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.api.LineDeleteShift
|
import com.maddyhome.idea.vim.api.LineDeleteShift
|
||||||
import com.maddyhome.idea.vim.api.VimCaret
|
import com.maddyhome.idea.vim.api.VimCaret
|
||||||
|
import com.maddyhome.idea.vim.api.VimChangeGroupBase
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.VimMotionGroupBase
|
import com.maddyhome.idea.vim.api.VimMotionGroupBase
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
@@ -40,7 +41,6 @@ import com.maddyhome.idea.vim.common.TextRange
|
|||||||
import com.maddyhome.idea.vim.common.VimRange
|
import com.maddyhome.idea.vim.common.VimRange
|
||||||
import com.maddyhome.idea.vim.common.including
|
import com.maddyhome.idea.vim.common.including
|
||||||
import com.maddyhome.idea.vim.common.offset
|
import com.maddyhome.idea.vim.common.offset
|
||||||
import com.maddyhome.idea.vim.group.ChangeGroup
|
|
||||||
import com.maddyhome.idea.vim.group.MotionGroup
|
import com.maddyhome.idea.vim.group.MotionGroup
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||||
import com.maddyhome.idea.vim.helper.inlayAwareVisualColumn
|
import com.maddyhome.idea.vim.helper.inlayAwareVisualColumn
|
||||||
@@ -62,7 +62,7 @@ fun changeRange(
|
|||||||
var col = 0
|
var col = 0
|
||||||
var lines = 0
|
var lines = 0
|
||||||
if (type === SelectionType.BLOCK_WISE) {
|
if (type === SelectionType.BLOCK_WISE) {
|
||||||
lines = ChangeGroup.getLinesCountInVisualBlock(IjVimEditor(editor), range)
|
lines = VimChangeGroupBase.getLinesCountInVisualBlock(IjVimEditor(editor), range)
|
||||||
col = editor.offsetToLogicalPosition(range.startOffset).column
|
col = editor.offsetToLogicalPosition(range.startOffset).column
|
||||||
if (caret.vimLastColumn == VimMotionGroupBase.LAST_COLUMN) {
|
if (caret.vimLastColumn == VimMotionGroupBase.LAST_COLUMN) {
|
||||||
col = VimMotionGroupBase.LAST_COLUMN
|
col = VimMotionGroupBase.LAST_COLUMN
|
||||||
|
@@ -110,6 +110,17 @@ class IjVimSearchHelper : VimSearchHelper {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun findNextWordEnd(
|
||||||
|
chars: CharSequence,
|
||||||
|
pos: Int,
|
||||||
|
size: Int,
|
||||||
|
count: Int,
|
||||||
|
bigWord: Boolean,
|
||||||
|
spaceWords: Boolean,
|
||||||
|
): Int {
|
||||||
|
return SearchHelper.findNextWordEnd(chars, pos, size, count, bigWord, spaceWords)
|
||||||
|
}
|
||||||
|
|
||||||
override fun findNextWord(editor: VimEditor, searchFrom: Int, count: Int, bigWord: Boolean): Int {
|
override fun findNextWord(editor: VimEditor, searchFrom: Int, count: Int, bigWord: Boolean): Int {
|
||||||
return SearchHelper.findNextWord(
|
return SearchHelper.findNextWord(
|
||||||
(editor as IjVimEditor).editor,
|
(editor as IjVimEditor).editor,
|
||||||
|
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||||
|
* Copyright (C) 2003-2022 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.maddyhome.idea.vim.vimscript.model.functions.handlers
|
||||||
|
|
||||||
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
|
import com.maddyhome.idea.vim.ex.ExException
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.VimLContext
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimList
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.functions.FunctionHandler
|
||||||
|
|
||||||
|
class JoinFunctionHandler : FunctionHandler() {
|
||||||
|
override val name: String = "join"
|
||||||
|
override val minimumNumberOfArguments: Int = 1
|
||||||
|
override val maximumNumberOfArguments: Int = 2
|
||||||
|
|
||||||
|
override fun doFunction(
|
||||||
|
argumentValues: List<Expression>,
|
||||||
|
editor: VimEditor,
|
||||||
|
context: ExecutionContext,
|
||||||
|
vimContext: VimLContext,
|
||||||
|
): VimDataType {
|
||||||
|
val firstArgument = argumentValues[0].evaluate(editor, context, vimContext)
|
||||||
|
if (firstArgument !is VimList) {
|
||||||
|
throw ExException("E714: List required")
|
||||||
|
}
|
||||||
|
val secondArgument = argumentValues.getOrNull(1)?.evaluate(editor, context, vimContext) ?: VimString(" ")
|
||||||
|
return VimString(firstArgument.values.joinToString(secondArgument.asString()) { it.toString() })
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||||
|
* Copyright (C) 2003-2022 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.maddyhome.idea.vim.vimscript.model.functions.handlers
|
||||||
|
|
||||||
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.VimLContext
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.functions.FunctionHandler
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class TolowerFunctionHandler : FunctionHandler() {
|
||||||
|
override val name: String = "tolower"
|
||||||
|
override val minimumNumberOfArguments: Int = 1
|
||||||
|
override val maximumNumberOfArguments: Int = 1
|
||||||
|
|
||||||
|
override fun doFunction(
|
||||||
|
argumentValues: List<Expression>,
|
||||||
|
editor: VimEditor,
|
||||||
|
context: ExecutionContext,
|
||||||
|
vimContext: VimLContext,
|
||||||
|
): VimDataType {
|
||||||
|
val argumentString = argumentValues[0].evaluate(editor, context, vimContext).asString()
|
||||||
|
return VimString(argumentString.lowercase(Locale.getDefault()))
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||||
|
* Copyright (C) 2003-2022 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.maddyhome.idea.vim.vimscript.model.functions.handlers
|
||||||
|
|
||||||
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.VimLContext
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.functions.FunctionHandler
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class ToupperFunctionHandler : FunctionHandler() {
|
||||||
|
override val name: String = "toupper"
|
||||||
|
override val minimumNumberOfArguments: Int = 1
|
||||||
|
override val maximumNumberOfArguments: Int = 1
|
||||||
|
|
||||||
|
override fun doFunction(
|
||||||
|
argumentValues: List<Expression>,
|
||||||
|
editor: VimEditor,
|
||||||
|
context: ExecutionContext,
|
||||||
|
vimContext: VimLContext,
|
||||||
|
): VimDataType {
|
||||||
|
val argumentString = argumentValues[0].evaluate(editor, context, vimContext).asString()
|
||||||
|
return VimString(argumentString.uppercase(Locale.getDefault()))
|
||||||
|
}
|
||||||
|
}
|
@@ -180,7 +180,7 @@ object CommandVisitor : VimscriptBaseVisitor<Command>() {
|
|||||||
override fun visitLet1Command(ctx: VimscriptParser.Let1CommandContext): Command {
|
override fun visitLet1Command(ctx: VimscriptParser.Let1CommandContext): Command {
|
||||||
val ranges: Ranges = parseRanges(ctx.range())
|
val ranges: Ranges = parseRanges(ctx.range())
|
||||||
val variable: Expression = expressionVisitor.visit(ctx.expr(0))
|
val variable: Expression = expressionVisitor.visit(ctx.expr(0))
|
||||||
val operator = getByValue(ctx.assignmentOperator.text)
|
val operator = getByValue(ctx.assignmentOperator().text)
|
||||||
val expression: Expression = expressionVisitor.visit(ctx.expr(1))
|
val expression: Expression = expressionVisitor.visit(ctx.expr(1))
|
||||||
return LetCommand(ranges, variable, operator, expression, true)
|
return LetCommand(ranges, variable, operator, expression, true)
|
||||||
}
|
}
|
||||||
|
@@ -11,5 +11,8 @@
|
|||||||
<vimLibraryFunction implementation="com.maddyhome.idea.vim.vimscript.model.functions.handlers.FuncrefFunctionHandler" name="funcref"/>
|
<vimLibraryFunction implementation="com.maddyhome.idea.vim.vimscript.model.functions.handlers.FuncrefFunctionHandler" name="funcref"/>
|
||||||
<vimLibraryFunction implementation="com.maddyhome.idea.vim.vimscript.model.functions.handlers.HasFunctionHandler" name="has"/>
|
<vimLibraryFunction implementation="com.maddyhome.idea.vim.vimscript.model.functions.handlers.HasFunctionHandler" name="has"/>
|
||||||
<vimLibraryFunction implementation="com.maddyhome.idea.vim.vimscript.model.functions.handlers.SubmatchFunctionHandler" name="submatch"/>
|
<vimLibraryFunction implementation="com.maddyhome.idea.vim.vimscript.model.functions.handlers.SubmatchFunctionHandler" name="submatch"/>
|
||||||
|
<vimLibraryFunction implementation="com.maddyhome.idea.vim.vimscript.model.functions.handlers.TolowerFunctionHandler" name="tolower"/>
|
||||||
|
<vimLibraryFunction implementation="com.maddyhome.idea.vim.vimscript.model.functions.handlers.ToupperFunctionHandler" name="toupper"/>
|
||||||
|
<vimLibraryFunction implementation="com.maddyhome.idea.vim.vimscript.model.functions.handlers.JoinFunctionHandler" name="join"/>
|
||||||
</extensions>
|
</extensions>
|
||||||
</idea-plugin>
|
</idea-plugin>
|
@@ -88,7 +88,7 @@ class UndoActionTest : VimTestCase() {
|
|||||||
val after = """
|
val after = """
|
||||||
A Discovery
|
A Discovery
|
||||||
|
|
||||||
I1 found${c} it in a legendary land
|
I1 found$c it in a legendary land
|
||||||
all rocks and lavender and tufted grass,
|
all rocks and lavender and tufted grass,
|
||||||
where it was settled on some sodden sand
|
where it was settled on some sodden sand
|
||||||
hard by the torrent of a mountain pass.
|
hard by the torrent of a mountain pass.
|
||||||
|
@@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||||
|
* Copyright (C) 2003-2022 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.plugins.ideavim.ex.implementation
|
||||||
|
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
|
import org.junit.Ignore
|
||||||
|
|
||||||
|
class LongerFunctionTest : VimTestCase() {
|
||||||
|
val script = """
|
||||||
|
function! IsUppercase(char)
|
||||||
|
return a:char >=# 'A' && a:char <=# 'Z'
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! IsCaps(word)
|
||||||
|
return a:word ==# toupper(a:word)
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! Capitalize(word)
|
||||||
|
return toupper(a:word[0]) .. a:word[1:]
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! Split(text, delimeter)
|
||||||
|
let parts = []
|
||||||
|
let part = ''
|
||||||
|
for char in a:text
|
||||||
|
if char ==? a:delimeter
|
||||||
|
let parts += [part]
|
||||||
|
let part = ''
|
||||||
|
else
|
||||||
|
let part .= char
|
||||||
|
endif
|
||||||
|
endfor
|
||||||
|
let parts += [part]
|
||||||
|
return parts
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! ToCamelCase()
|
||||||
|
let capitalize = 0
|
||||||
|
|
||||||
|
normal gv"wy
|
||||||
|
let wordUnderCaret = @w
|
||||||
|
let parts = Split(wordUnderCaret, '_')
|
||||||
|
let result = tolower(parts[0])
|
||||||
|
let counter = 1
|
||||||
|
while counter < len(parts)
|
||||||
|
let result .= Capitalize(tolower(parts[counter]))
|
||||||
|
let counter += 1
|
||||||
|
endwhile
|
||||||
|
execute 'normal gvc' .. result
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! ToSnakeCase()
|
||||||
|
normal gv"wy
|
||||||
|
let wordUnderCaret = @w
|
||||||
|
|
||||||
|
let parts = []
|
||||||
|
let subword = ''
|
||||||
|
let atWordStart = 1
|
||||||
|
for char in wordUnderCaret
|
||||||
|
if IsUppercase(char) && !atWordStart
|
||||||
|
let parts += [toupper(subword)]
|
||||||
|
let subword = char
|
||||||
|
else
|
||||||
|
let subword .= char
|
||||||
|
endif
|
||||||
|
|
||||||
|
let atWordStart = char ==? ' '
|
||||||
|
endfor
|
||||||
|
let parts += [toupper(subword)]
|
||||||
|
execute 'normal gvc' .. join(parts, '_')
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
vnoremap u :<C-u>call ToCamelCase()<CR>
|
||||||
|
vnoremap U :<C-u>call ToSnakeCase()<CR>
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
// todo normal required
|
||||||
|
// fun `test 1`() {
|
||||||
|
// configureByText("""
|
||||||
|
// const val ${c}VERY_IMPORTANT_VALUE = 42
|
||||||
|
// """.trimIndent())
|
||||||
|
// injector.vimscriptExecutor.execute(script)
|
||||||
|
// typeText(injector.parser.parseKeys("veu"))
|
||||||
|
// assertState("""
|
||||||
|
// const val veryImportantValue${c} = 42
|
||||||
|
// """.trimIndent())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// todo normal required
|
||||||
|
// fun `test 2`() {
|
||||||
|
// configureByText("""
|
||||||
|
// val ${c}myCamelCaseValue = "Hi, I'm a simple value"
|
||||||
|
// """.trimIndent())
|
||||||
|
// injector.vimscriptExecutor.execute(script)
|
||||||
|
// typeText(injector.parser.parseKeys("veU"))
|
||||||
|
// assertState("""
|
||||||
|
// val MY_CAMEL_CASE_VALUE${c} = "Hi, I'm a simple value"
|
||||||
|
// """.trimIndent())
|
||||||
|
// }
|
||||||
|
}
|
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||||
|
* Copyright (C) 2003-2022 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.plugins.ideavim.ex.implementation.functions
|
||||||
|
|
||||||
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
|
|
||||||
|
class BasicStringFunctions : VimTestCase() {
|
||||||
|
|
||||||
|
fun `test toupper`() {
|
||||||
|
configureByText("\n")
|
||||||
|
typeText(commandToKeys("echo toupper('Vim is awesome')"))
|
||||||
|
assertExOutput("VIM IS AWESOME\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test tolower`() {
|
||||||
|
configureByText("\n")
|
||||||
|
typeText(commandToKeys("echo toupper('Vim is awesome')"))
|
||||||
|
assertExOutput("vim is awesome\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test join`() {
|
||||||
|
configureByText("\n")
|
||||||
|
typeText(commandToKeys("echo join(['Vim', 'is', 'awesome'], '_')"))
|
||||||
|
assertExOutput("Vim_is_awesome\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test join without second argument`() {
|
||||||
|
configureByText("\n")
|
||||||
|
typeText(commandToKeys("echo join(['Vim', 'is', 'awesome'])"))
|
||||||
|
assertExOutput("Vim is awesome\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun `test join with wrong first argument type`() {
|
||||||
|
configureByText("\n")
|
||||||
|
typeText(commandToKeys("echo join('Vim is awesome')"))
|
||||||
|
assertPluginError(true)
|
||||||
|
assertPluginErrorMessageContains("E714: List required")
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||||
|
* Copyright (C) 2003-2022 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.plugins.ideavim.ex.parser.commands
|
||||||
|
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.commands.LetCommand
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.expressions.Register
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.expressions.SimpleExpression
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.expressions.operators.AssignmentOperator
|
||||||
|
import junit.framework.TestCase.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class LetCommandTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `let with register is parsed correctly`() {
|
||||||
|
val script = injector.vimscriptParser.parse("let @+=5")
|
||||||
|
assertEquals(1, script.units.size)
|
||||||
|
val command = script.units.first()
|
||||||
|
assertTrue(command is LetCommand)
|
||||||
|
command as LetCommand
|
||||||
|
assertEquals(Register('+'), command.variable)
|
||||||
|
assertEquals(AssignmentOperator.ASSIGNMENT, command.operator)
|
||||||
|
assertEquals(SimpleExpression(5), command.expression)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `let with register is parsed correctly 2`() {
|
||||||
|
val script = injector.vimscriptParser.parse("let @--=42")
|
||||||
|
assertEquals(1, script.units.size)
|
||||||
|
val command = script.units.first()
|
||||||
|
assertTrue(command is LetCommand)
|
||||||
|
command as LetCommand
|
||||||
|
assertEquals(Register('-'), command.variable)
|
||||||
|
assertEquals(AssignmentOperator.SUBTRACTION, command.operator)
|
||||||
|
assertEquals(SimpleExpression(42), command.expression)
|
||||||
|
}
|
||||||
|
}
|
@@ -23,11 +23,12 @@ import com.maddyhome.idea.vim.api.injector
|
|||||||
import com.maddyhome.idea.vim.command.Argument
|
import com.maddyhome.idea.vim.command.Argument
|
||||||
import com.maddyhome.idea.vim.command.Command
|
import com.maddyhome.idea.vim.command.Command
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
|
import com.maddyhome.idea.vim.command.SelectionType
|
||||||
import com.maddyhome.idea.vim.ex.ExException
|
import com.maddyhome.idea.vim.ex.ExException
|
||||||
import com.maddyhome.idea.vim.handler.VimActionHandler
|
import com.maddyhome.idea.vim.handler.VimActionHandler
|
||||||
|
import com.maddyhome.idea.vim.put.PutData
|
||||||
import com.maddyhome.idea.vim.register.Register
|
import com.maddyhome.idea.vim.register.Register
|
||||||
import com.maddyhome.idea.vim.vimscript.model.Script
|
import com.maddyhome.idea.vim.vimscript.model.Script
|
||||||
import javax.swing.KeyStroke
|
|
||||||
|
|
||||||
class InsertRegisterAction : VimActionHandler.SingleExecution() {
|
class InsertRegisterAction : VimActionHandler.SingleExecution() {
|
||||||
override val type: Command.Type = Command.Type.INSERT
|
override val type: Command.Type = Command.Type.INSERT
|
||||||
@@ -84,10 +85,10 @@ class InsertRegisterAction : VimActionHandler.SingleExecution() {
|
|||||||
private fun insertRegister(editor: VimEditor, context: ExecutionContext, key: Char): Boolean {
|
private fun insertRegister(editor: VimEditor, context: ExecutionContext, key: Char): Boolean {
|
||||||
val register: Register? = injector.registerGroup.getRegister(key)
|
val register: Register? = injector.registerGroup.getRegister(key)
|
||||||
if (register != null) {
|
if (register != null) {
|
||||||
val keys: List<KeyStroke> = register.keys
|
val text = register.rawText ?: injector.parser.toPrintableString(register.keys)
|
||||||
for (k in keys) {
|
val textData = PutData.TextData(text, SelectionType.CHARACTER_WISE, emptyList())
|
||||||
injector.changeGroup.processKey(editor, context, k)
|
val putData = PutData(textData, null, 1, insertTextBeforeCaret = true, rawIndent = true, caretAfterInsertedText = true)
|
||||||
}
|
injector.put.putText(editor, context, putData)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@@ -33,4 +33,11 @@ interface EngineEditorHelper {
|
|||||||
fun inlayAwareOffsetToVisualPosition(editor: VimEditor, offset: Int): VimVisualPosition
|
fun inlayAwareOffsetToVisualPosition(editor: VimEditor, offset: Int): VimVisualPosition
|
||||||
fun getVisualLineLength(editor: VimEditor, line: Int): Int
|
fun getVisualLineLength(editor: VimEditor, line: Int): Int
|
||||||
fun getLeadingWhitespace(editor: VimEditor, line: Int): String
|
fun getLeadingWhitespace(editor: VimEditor, line: Int): String
|
||||||
|
fun anyNonWhitespace(editor: VimEditor, offset: Int, dir: Int): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
fun VimEditor.endsWithNewLine(): Boolean {
|
||||||
|
val textLength = this.fileSize().toInt()
|
||||||
|
if (textLength == 0) return false
|
||||||
|
return this.text()[textLength - 1] == '\n'
|
||||||
}
|
}
|
@@ -65,6 +65,7 @@ interface VimActionExecutor {
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun findVimAction(id: String): EditorActionHandlerBase?
|
fun findVimAction(id: String): EditorActionHandlerBase?
|
||||||
|
fun findVimActionOrDie(id: String): EditorActionHandlerBase
|
||||||
|
|
||||||
fun getAction(actionId: String): NativeAction?
|
fun getAction(actionId: String): NativeAction?
|
||||||
fun getActionIdList(idPrefix: String): List<String>
|
fun getActionIdList(idPrefix: String): List<String>
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package com.maddyhome.idea.vim.api
|
package com.maddyhome.idea.vim.api
|
||||||
|
|
||||||
import com.maddyhome.idea.vim.KeyHandler
|
import com.maddyhome.idea.vim.KeyHandler
|
||||||
|
import com.maddyhome.idea.vim.command.Argument
|
||||||
import com.maddyhome.idea.vim.command.Command
|
import com.maddyhome.idea.vim.command.Command
|
||||||
import com.maddyhome.idea.vim.command.CommandFlags
|
import com.maddyhome.idea.vim.command.CommandFlags
|
||||||
import com.maddyhome.idea.vim.command.CommandState
|
import com.maddyhome.idea.vim.command.CommandState
|
||||||
@@ -24,6 +25,8 @@ import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
|
|||||||
import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_END
|
import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_END
|
||||||
import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_POS
|
import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_POS
|
||||||
import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_START
|
import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_START
|
||||||
|
import com.maddyhome.idea.vim.options.OptionConstants
|
||||||
|
import com.maddyhome.idea.vim.options.OptionScope
|
||||||
import com.maddyhome.idea.vim.register.RegisterConstants.LAST_INSERTED_TEXT_REGISTER
|
import com.maddyhome.idea.vim.register.RegisterConstants.LAST_INSERTED_TEXT_REGISTER
|
||||||
import java.awt.event.KeyEvent
|
import java.awt.event.KeyEvent
|
||||||
import javax.swing.KeyStroke
|
import javax.swing.KeyStroke
|
||||||
@@ -798,6 +801,134 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getDeleteRangeAndType(
|
||||||
|
editor: VimEditor,
|
||||||
|
caret: VimCaret,
|
||||||
|
context: ExecutionContext,
|
||||||
|
argument: Argument,
|
||||||
|
isChange: Boolean,
|
||||||
|
operatorArguments: OperatorArguments,
|
||||||
|
): Pair<TextRange, SelectionType>? {
|
||||||
|
val range = injector.motion.getMotionRange(editor, caret, 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 = if (argument.motion.isLinewiseMotion()) {
|
||||||
|
SelectionType.LINE_WISE
|
||||||
|
} else {
|
||||||
|
SelectionType.CHARACTER_WISE
|
||||||
|
}
|
||||||
|
val motion = argument.motion
|
||||||
|
if (!isChange && !motion.isLinewiseMotion()) {
|
||||||
|
val start = editor.offsetToLogicalPosition(range.startOffset)
|
||||||
|
val end = editor.offsetToLogicalPosition(range.endOffset)
|
||||||
|
if (start.line != end.line) {
|
||||||
|
if (!injector.engineEditorHelper.anyNonWhitespace(editor, range.startOffset, -1) &&
|
||||||
|
!injector.engineEditorHelper.anyNonWhitespace(editor, range.endOffset, 1)
|
||||||
|
) {
|
||||||
|
type = SelectionType.LINE_WISE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Pair(range, type)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the range of text.
|
||||||
|
*
|
||||||
|
* @param editor The editor to delete the text from
|
||||||
|
* @param caret The caret to be moved after deletion
|
||||||
|
* @param range The range to delete
|
||||||
|
* @param type The type of deletion
|
||||||
|
* @param isChange Is from a change action
|
||||||
|
* @return true if able to delete the text, false if not
|
||||||
|
*/
|
||||||
|
override fun deleteRange(
|
||||||
|
editor: VimEditor,
|
||||||
|
caret: VimCaret,
|
||||||
|
range: TextRange,
|
||||||
|
type: SelectionType?,
|
||||||
|
isChange: Boolean,
|
||||||
|
): Boolean {
|
||||||
|
|
||||||
|
// Update the last column before we delete, or we might be retrieving the data for a line that no longer exists
|
||||||
|
caret.vimLastColumn = caret.inlayAwareVisualColumn
|
||||||
|
val removeLastNewLine = removeLastNewLine(editor, range, type)
|
||||||
|
val res = deleteText(editor, range, type)
|
||||||
|
if (removeLastNewLine) {
|
||||||
|
val textLength = editor.fileSize().toInt()
|
||||||
|
editor.deleteString(TextRange(textLength - 1, textLength))
|
||||||
|
}
|
||||||
|
if (res) {
|
||||||
|
var pos = injector.engineEditorHelper.normalizeOffset(editor, range.startOffset, isChange)
|
||||||
|
if (type === SelectionType.LINE_WISE) {
|
||||||
|
pos = injector.motion
|
||||||
|
.moveCaretToLineWithStartOfLineOption(
|
||||||
|
editor, editor.offsetToLogicalPosition(pos).line,
|
||||||
|
caret
|
||||||
|
)
|
||||||
|
}
|
||||||
|
injector.motion.moveCaret(editor, caret, pos)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeLastNewLine(editor: VimEditor, range: TextRange, type: SelectionType?): Boolean {
|
||||||
|
var endOffset = range.endOffset
|
||||||
|
val fileSize = editor.fileSize().toInt()
|
||||||
|
if (endOffset > fileSize) {
|
||||||
|
check(
|
||||||
|
!injector.optionService.isSet(
|
||||||
|
OptionScope.GLOBAL,
|
||||||
|
OptionConstants.ideastrictmodeName,
|
||||||
|
OptionConstants.ideastrictmodeName
|
||||||
|
)
|
||||||
|
) { "Incorrect offset. File size: $fileSize, offset: $endOffset" }
|
||||||
|
endOffset = fileSize
|
||||||
|
}
|
||||||
|
return (type === SelectionType.LINE_WISE) && range.startOffset != 0 && editor.text()[endOffset - 1] != '\n' && endOffset == fileSize
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete from the cursor to the end of count - 1 lines down and enter insert mode
|
||||||
|
*
|
||||||
|
* @param editor The editor to change
|
||||||
|
* @param caret The caret to perform action on
|
||||||
|
* @param count The number of lines to change
|
||||||
|
* @return true if able to delete count lines, false if not
|
||||||
|
*/
|
||||||
|
override fun changeEndOfLine(editor: VimEditor, caret: VimCaret, count: Int): Boolean {
|
||||||
|
val res = deleteEndOfLine(editor, caret, count)
|
||||||
|
if (res) {
|
||||||
|
caret.moveToOffset(injector.motion.moveCaretToLineEnd(editor, caret))
|
||||||
|
editor.vimChangeActionSwitchMode = CommandState.Mode.INSERT
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete count characters and then enter insert mode
|
||||||
|
*
|
||||||
|
* @param editor The editor to change
|
||||||
|
* @param caret The caret to be moved
|
||||||
|
* @param count The number of characters to change
|
||||||
|
* @return true if able to delete count characters, false if not
|
||||||
|
*/
|
||||||
|
override fun changeCharacters(editor: VimEditor, caret: VimCaret, count: Int): Boolean {
|
||||||
|
val len = injector.engineEditorHelper.getLineLength(editor)
|
||||||
|
val col = caret.getLogicalPosition().column
|
||||||
|
if (col + count >= len) {
|
||||||
|
return changeEndOfLine(editor, caret, 1)
|
||||||
|
}
|
||||||
|
val res = deleteCharacter(editor, caret, count, true)
|
||||||
|
if (res) {
|
||||||
|
editor.vimChangeActionSwitchMode = CommandState.Mode.INSERT
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears all the keystrokes from the current insert command
|
* Clears all the keystrokes from the current insert command
|
||||||
*
|
*
|
||||||
@@ -871,6 +1002,24 @@ abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
companion object {
|
companion object {
|
||||||
private const val MAX_REPEAT_CHARS_COUNT = 10000
|
private const val MAX_REPEAT_CHARS_COUNT = 10000
|
||||||
private val logger = vimLogger<VimChangeGroupBase>()
|
private val logger = vimLogger<VimChangeGroupBase>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts number of lines in the visual block.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The result includes empty and short lines which does not have explicit start position (caret).
|
||||||
|
*
|
||||||
|
* @param editor The editor the block was selected in
|
||||||
|
* @param range The range corresponding to the selected block
|
||||||
|
* @return total number of lines
|
||||||
|
*/
|
||||||
|
fun getLinesCountInVisualBlock(editor: VimEditor, range: TextRange): Int {
|
||||||
|
val startOffsets = range.startOffsets
|
||||||
|
if (startOffsets.isEmpty()) return 0
|
||||||
|
val firstStart = editor.offsetToLogicalPosition(startOffsets[0])
|
||||||
|
val lastStart = editor.offsetToLogicalPosition(startOffsets[range.size() - 1])
|
||||||
|
return lastStart.line - firstStart.line + 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -75,6 +75,15 @@ interface VimSearchHelper {
|
|||||||
bigWord: Boolean,
|
bigWord: Boolean,
|
||||||
): Int
|
): Int
|
||||||
|
|
||||||
|
fun findNextWordEnd(
|
||||||
|
chars: CharSequence,
|
||||||
|
pos: Int,
|
||||||
|
size: Int,
|
||||||
|
count: Int,
|
||||||
|
bigWord: Boolean,
|
||||||
|
spaceWords: Boolean,
|
||||||
|
): Int
|
||||||
|
|
||||||
fun findNextWord(editor: VimEditor, searchFrom: Int, count: Int, bigWord: Boolean): Int
|
fun findNextWord(editor: VimEditor, searchFrom: Int, count: Int, bigWord: Boolean): Int
|
||||||
|
|
||||||
fun findPattern(
|
fun findPattern(
|
||||||
|
@@ -12,6 +12,7 @@ import com.maddyhome.idea.vim.command.isChar
|
|||||||
import com.maddyhome.idea.vim.command.isLine
|
import com.maddyhome.idea.vim.command.isLine
|
||||||
import com.maddyhome.idea.vim.common.TextRange
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
import com.maddyhome.idea.vim.helper.firstOrNull
|
import com.maddyhome.idea.vim.helper.firstOrNull
|
||||||
|
import com.maddyhome.idea.vim.helper.mode
|
||||||
import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_POS
|
import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_POS
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
@@ -140,7 +141,9 @@ abstract class VimPutBase : VimPut {
|
|||||||
}
|
}
|
||||||
"postEndOffset" -> caret.moveToOffset(endOffset + 1)
|
"postEndOffset" -> caret.moveToOffset(endOffset + 1)
|
||||||
"preLineEndOfEndOffset" -> {
|
"preLineEndOfEndOffset" -> {
|
||||||
val pos = min(endOffset, editor.getLineEndForOffset(endOffset - 1) - 1)
|
var rightestPosition = editor.getLineEndForOffset(endOffset - 1)
|
||||||
|
if (editor.mode != CommandState.Mode.INSERT) --rightestPosition // it's not possible to place a caret at the end of the line in any mode except insert
|
||||||
|
val pos = min(endOffset, rightestPosition)
|
||||||
caret.moveToOffset(pos)
|
caret.moveToOffset(pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -48,6 +48,11 @@ interface VimRegisterGroup {
|
|||||||
isDelete: Boolean,
|
isDelete: Boolean,
|
||||||
): Boolean
|
): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores text to any writable register (used for the let command)
|
||||||
|
*/
|
||||||
|
fun storeText(register: Char, text: String): Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores text, character wise, in the given special register
|
* Stores text, character wise, in the given special register
|
||||||
*
|
*
|
||||||
|
@@ -18,6 +18,7 @@ import com.maddyhome.idea.vim.register.RegisterConstants.RECORDABLE_REGISTERS
|
|||||||
import com.maddyhome.idea.vim.register.RegisterConstants.SMALL_DELETION_REGISTER
|
import com.maddyhome.idea.vim.register.RegisterConstants.SMALL_DELETION_REGISTER
|
||||||
import com.maddyhome.idea.vim.register.RegisterConstants.UNNAMED_REGISTER
|
import com.maddyhome.idea.vim.register.RegisterConstants.UNNAMED_REGISTER
|
||||||
import com.maddyhome.idea.vim.register.RegisterConstants.VALID_REGISTERS
|
import com.maddyhome.idea.vim.register.RegisterConstants.VALID_REGISTERS
|
||||||
|
import com.maddyhome.idea.vim.register.RegisterConstants.WRITABLE_REGISTERS
|
||||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
||||||
import javax.swing.KeyStroke
|
import javax.swing.KeyStroke
|
||||||
|
|
||||||
@@ -292,6 +293,24 @@ abstract class VimRegisterGroupBase : VimRegisterGroup {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun storeText(register: Char, text: String): Boolean {
|
||||||
|
if (!WRITABLE_REGISTERS.contains(register)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
logger.debug { "register '$register' contains: \"$text\"" }
|
||||||
|
val textToStore = if (register.isUpperCase()) {
|
||||||
|
(getRegister(register.lowercaseChar())?.rawText ?: "") + text
|
||||||
|
} else {
|
||||||
|
text
|
||||||
|
}
|
||||||
|
val reg = Register(register, SelectionType.CHARACTER_WISE, textToStore, ArrayList())
|
||||||
|
saveRegister(register, reg)
|
||||||
|
if (register == '/') {
|
||||||
|
injector.searchGroup.lastSearchPattern = text // todo we should not have this field if we have the "/" register
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private fun guessSelectionType(text: String): SelectionType {
|
private fun guessSelectionType(text: String): SelectionType {
|
||||||
return if (text.endsWith("\n")) SelectionType.LINE_WISE else SelectionType.CHARACTER_WISE
|
return if (text.endsWith("\n")) SelectionType.LINE_WISE else SelectionType.CHARACTER_WISE
|
||||||
}
|
}
|
||||||
|
@@ -21,9 +21,11 @@ package com.maddyhome.idea.vim.vimscript.model.commands
|
|||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||||
import com.maddyhome.idea.vim.ex.ExException
|
import com.maddyhome.idea.vim.ex.ExException
|
||||||
import com.maddyhome.idea.vim.ex.ranges.Ranges
|
import com.maddyhome.idea.vim.ex.ranges.Ranges
|
||||||
import com.maddyhome.idea.vim.options.OptionScope
|
import com.maddyhome.idea.vim.options.OptionScope
|
||||||
|
import com.maddyhome.idea.vim.register.RegisterConstants
|
||||||
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
|
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
|
||||||
import com.maddyhome.idea.vim.vimscript.model.Script
|
import com.maddyhome.idea.vim.vimscript.model.Script
|
||||||
import com.maddyhome.idea.vim.vimscript.model.VimLContext
|
import com.maddyhome.idea.vim.vimscript.model.VimLContext
|
||||||
@@ -56,8 +58,12 @@ data class LetCommand(
|
|||||||
val isSyntaxSupported: Boolean,
|
val isSyntaxSupported: Boolean,
|
||||||
) : Command.SingleExecution(ranges) {
|
) : Command.SingleExecution(ranges) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = vimLogger<LetCommand>()
|
||||||
|
}
|
||||||
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
|
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
|
||||||
|
|
||||||
|
|
||||||
@Throws(ExException::class)
|
@Throws(ExException::class)
|
||||||
override fun processCommand(editor: VimEditor, context: ExecutionContext): ExecutionResult {
|
override fun processCommand(editor: VimEditor, context: ExecutionContext): ExecutionResult {
|
||||||
if (!isSyntaxSupported) return ExecutionResult.Error
|
if (!isSyntaxSupported) return ExecutionResult.Error
|
||||||
@@ -194,13 +200,19 @@ data class LetCommand(
|
|||||||
is EnvVariableExpression -> TODO()
|
is EnvVariableExpression -> TODO()
|
||||||
|
|
||||||
is Register -> {
|
is Register -> {
|
||||||
if (!(variable.char.isLetter() || variable.char.isDigit() || variable.char == '"')) {
|
if (RegisterConstants.WRITABLE_REGISTERS.contains(variable.char)) {
|
||||||
throw ExException("Let command supports only 0-9a-zA-Z\" registers at the moment")
|
val result = injector.registerGroup.storeText(variable.char, expression.evaluate(editor, context, vimContext).asString())
|
||||||
|
if (!result) {
|
||||||
|
logger.error("""
|
||||||
|
Error during `let ${variable.originalString} ${operator.value} ${expression.originalString}` command execution.
|
||||||
|
Could not set register value
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
} else if (RegisterConstants.VALID_REGISTERS.contains(variable.char)) {
|
||||||
|
throw ExException("E354: Invalid register name: '${variable.char}'")
|
||||||
|
} else {
|
||||||
|
throw ExException("E18: Unexpected characters in :let")
|
||||||
}
|
}
|
||||||
|
|
||||||
injector.registerGroup.startRecording(editor, variable.char)
|
|
||||||
injector.registerGroup.recordText(expression.evaluate(editor, context, vimContext).asString())
|
|
||||||
injector.registerGroup.finishRecording(editor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> throw ExException("E121: Undefined variable")
|
else -> throw ExException("E121: Undefined variable")
|
||||||
|
@@ -61,7 +61,7 @@ data class DefinedFunctionHandler(val function: FunctionDeclaration) : FunctionH
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
initializeFunctionVariables(argumentValues, editor, context)
|
initializeFunctionVariables(argumentValues, editor, context, vimContext)
|
||||||
|
|
||||||
if (function.flags.contains(FunctionFlag.RANGE)) {
|
if (function.flags.contains(FunctionFlag.RANGE)) {
|
||||||
val line = (injector.variableService.getNonNullVariableValue(Variable(Scope.FUNCTION_VARIABLE, "firstline"), editor, context, function) as VimInt).value
|
val line = (injector.variableService.getNonNullVariableValue(Variable(Scope.FUNCTION_VARIABLE, "firstline"), editor, context, function) as VimInt).value
|
||||||
@@ -131,12 +131,12 @@ data class DefinedFunctionHandler(val function: FunctionDeclaration) : FunctionH
|
|||||||
return returnValue
|
return returnValue
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initializeFunctionVariables(argumentValues: List<Expression>, editor: VimEditor, context: ExecutionContext) {
|
private fun initializeFunctionVariables(argumentValues: List<Expression>, editor: VimEditor, context: ExecutionContext, functionCallContext: VimLContext) {
|
||||||
// non-optional function arguments
|
// non-optional function arguments
|
||||||
for ((index, name) in function.args.withIndex()) {
|
for ((index, name) in function.args.withIndex()) {
|
||||||
injector.variableService.storeVariable(
|
injector.variableService.storeVariable(
|
||||||
Variable(Scope.FUNCTION_VARIABLE, name),
|
Variable(Scope.FUNCTION_VARIABLE, name),
|
||||||
argumentValues[index].evaluate(editor, context, function.vimContext),
|
argumentValues[index].evaluate(editor, context, functionCallContext),
|
||||||
editor,
|
editor,
|
||||||
context,
|
context,
|
||||||
function
|
function
|
||||||
@@ -147,7 +147,7 @@ data class DefinedFunctionHandler(val function: FunctionDeclaration) : FunctionH
|
|||||||
val expressionToStore = if (index + function.args.size < argumentValues.size) argumentValues[index + function.args.size] else function.defaultArgs[index].second
|
val expressionToStore = if (index + function.args.size < argumentValues.size) argumentValues[index + function.args.size] else function.defaultArgs[index].second
|
||||||
injector.variableService.storeVariable(
|
injector.variableService.storeVariable(
|
||||||
Variable(Scope.FUNCTION_VARIABLE, function.defaultArgs[index].first),
|
Variable(Scope.FUNCTION_VARIABLE, function.defaultArgs[index].first),
|
||||||
expressionToStore.evaluate(editor, context, function.vimContext),
|
expressionToStore.evaluate(editor, context, functionCallContext),
|
||||||
editor,
|
editor,
|
||||||
context,
|
context,
|
||||||
function
|
function
|
||||||
@@ -158,7 +158,7 @@ data class DefinedFunctionHandler(val function: FunctionDeclaration) : FunctionH
|
|||||||
val remainingArgs = if (function.args.size + function.defaultArgs.size < argumentValues.size) {
|
val remainingArgs = if (function.args.size + function.defaultArgs.size < argumentValues.size) {
|
||||||
VimList(
|
VimList(
|
||||||
argumentValues.subList(function.args.size + function.defaultArgs.size, argumentValues.size)
|
argumentValues.subList(function.args.size + function.defaultArgs.size, argumentValues.size)
|
||||||
.map { it.evaluate(editor, context, function.vimContext) }.toMutableList()
|
.map { it.evaluate(editor, context, functionCallContext) }.toMutableList()
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
VimList(mutableListOf())
|
VimList(mutableListOf())
|
||||||
|
Reference in New Issue
Block a user