mirror of
				https://github.com/chylex/IntelliJ-IdeaVim.git
				synced 2025-11-04 10:40:10 +01: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,11 +120,19 @@ 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
 | 
				
			||||||
 | 
					        setRegister(REGISTER, null)
 | 
				
			||||||
        // Extract the inner value
 | 
					        // Extract the inner value
 | 
				
			||||||
        perform("di" + pick(charFrom), editor)
 | 
					        perform("di" + pick(charFrom), editor)
 | 
				
			||||||
        val innerValue: MutableList<KeyStroke> = getRegister(REGISTER)?.toMutableList() ?: mutableListOf()
 | 
					        val innerValue: MutableList<KeyStroke> = getRegister(REGISTER)?.toMutableList() ?: mutableListOf()
 | 
				
			||||||
 | 
					        // If the surrounding characters were not found, the register will be empty
 | 
				
			||||||
 | 
					        if (innerValue.isNotEmpty()) {
 | 
				
			||||||
          // Delete the surrounding
 | 
					          // Delete the surrounding
 | 
				
			||||||
          perform("da" + pick(charFrom), editor)
 | 
					          perform("da" + pick(charFrom), editor)
 | 
				
			||||||
          // Insert the surrounding characters and paste
 | 
					          // Insert the surrounding characters and paste
 | 
				
			||||||
@@ -133,11 +141,12 @@ class VimSurroundExtension : VimExtension {
 | 
				
			|||||||
            innerValue.addAll(injector.parser.parseKeys(newSurround.second))
 | 
					            innerValue.addAll(injector.parser.parseKeys(newSurround.second))
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          pasteSurround(innerValue, editor)
 | 
					          pasteSurround(innerValue, editor)
 | 
				
			||||||
        // Restore the old value
 | 
					 | 
				
			||||||
        setRegister(REGISTER, oldValue)
 | 
					 | 
				
			||||||
          // Jump back to start
 | 
					          // Jump back to start
 | 
				
			||||||
          executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor)
 | 
					          executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        // Restore the old value
 | 
				
			||||||
 | 
					        setRegister(REGISTER, oldValue)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      private fun perform(sequence: String, editor: Editor) {
 | 
					      private fun perform(sequence: String, editor: Editor) {
 | 
				
			||||||
        ClipboardOptionHelper.IdeaputDisabler()
 | 
					        ClipboardOptionHelper.IdeaputDisabler()
 | 
				
			||||||
@@ -179,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)
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else {
 | 
				
			||||||
 | 
					          applyOnce(editor, change, pair)
 | 
				
			||||||
          // Jump back to start
 | 
					          // Jump back to start
 | 
				
			||||||
          executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor)
 | 
					          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)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -313,6 +313,17 @@ class VimSurroundExtensionTest : VimTestCase() {
 | 
				
			|||||||
    doTest(listOf("dsb"), before, after, CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
 | 
					    doTest(listOf("dsb"), before, after, CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // VIM-2227
 | 
				
			||||||
 | 
					  @TestWithoutNeovim(SkipNeovimReason.PLUGIN)
 | 
				
			||||||
 | 
					  fun testDeleteInvalidSurroundingCharacter() {
 | 
				
			||||||
 | 
					    val text = "if (${c}condition) {"
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    doTest("yibds]", text, text, CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
 | 
				
			||||||
 | 
					    doTest("yibds[", text, text, CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
 | 
				
			||||||
 | 
					    doTest("yibds}", text, text, CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
 | 
				
			||||||
 | 
					    doTest("yibds{", text, text, CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @TestWithoutNeovim(SkipNeovimReason.PLUGIN)
 | 
					  @TestWithoutNeovim(SkipNeovimReason.PLUGIN)
 | 
				
			||||||
  fun testRepeatDeleteSurroundParens() {
 | 
					  fun testRepeatDeleteSurroundParens() {
 | 
				
			||||||
    val before = "if ((${c}condition)) {\n}\n"
 | 
					    val before = "if ((${c}condition)) {\n}\n"
 | 
				
			||||||
@@ -371,6 +382,17 @@ class VimSurroundExtensionTest : VimTestCase() {
 | 
				
			|||||||
    doTest(listOf("csbrE."), before, after, CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
 | 
					    doTest(listOf("csbrE."), before, after, CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // VIM-2227
 | 
				
			||||||
 | 
					  @TestWithoutNeovim(SkipNeovimReason.PLUGIN)
 | 
				
			||||||
 | 
					  fun testChangeInvalidSurroundingCharacter() {
 | 
				
			||||||
 | 
					    val text = "if (${c}condition) {"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    doTest("yibcs]}", text, text, CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
 | 
				
			||||||
 | 
					    doTest("yibcs[}", text, text, CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
 | 
				
			||||||
 | 
					    doTest("yibcs}]", text, text, CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
 | 
				
			||||||
 | 
					    doTest("yibcs{]", text, text, CommandState.Mode.COMMAND, CommandState.SubMode.NONE)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @VimBehaviorDiffers(
 | 
					  @VimBehaviorDiffers(
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
      <h1>Title</h1>
 | 
					      <h1>Title</h1>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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