mirror of
				https://github.com/chylex/IntelliJ-IdeaVim.git
				synced 2025-11-04 10:40:10 +01:00 
			
		
		
		
	Compare commits
	
		
			15 Commits
		
	
	
		
			customized
			...
			063ed0aa84
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						063ed0aa84
	
				 | 
					
					
						|||
| 
						
						
							
						
						fccfe0c2ea
	
				 | 
					
					
						|||
| 
						
						
							
						
						45ed79d865
	
				 | 
					
					
						|||
| 
						
						
							
						
						527eb4cbb3
	
				 | 
					
					
						|||
| 
						
						
							
						
						e32ac125b2
	
				 | 
					
					
						|||
| 
						
						
							
						
						4d1d3b697c
	
				 | 
					
					
						|||
| 
						
						
							
						
						3246832528
	
				 | 
					
					
						|||
| 
						
						
							
						
						6505bfc9aa
	
				 | 
					
					
						|||
| 
						
						
							
						
						0c63890e9d
	
				 | 
					
					
						|||
| 
						
						
							
						
						259958e702
	
				 | 
					
					
						|||
| 
						
						
							
						
						4916545b53
	
				 | 
					
					
						|||
| 
						
						
							
						
						b8a9bddfa9
	
				 | 
					
					
						|||
| 
						
						
							
						
						95688b33c8
	
				 | 
					
					
						|||
| 
						
						
							
						
						07f44f1c93
	
				 | 
					
					
						|||
| 
						
						
							
						
						2ce6239ad6
	
				 | 
					
					
						
@@ -14,7 +14,7 @@ ideaVersion=2024.1
 | 
			
		||||
ideaType=IC
 | 
			
		||||
downloadIdeaSources=true
 | 
			
		||||
instrumentPluginCode=true
 | 
			
		||||
version=chylex-1
 | 
			
		||||
version=chylex-32
 | 
			
		||||
javaVersion=17
 | 
			
		||||
remoteRobotVersion=0.11.22
 | 
			
		||||
antlrVersion=4.10.1
 | 
			
		||||
 
 | 
			
		||||
@@ -8,25 +8,21 @@
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.extension.nerdtree
 | 
			
		||||
 | 
			
		||||
import com.intellij.ide.projectView.ProjectView
 | 
			
		||||
import com.intellij.ide.projectView.impl.AbstractProjectViewPane
 | 
			
		||||
import com.intellij.ide.projectView.impl.ProjectViewImpl
 | 
			
		||||
import com.intellij.openapi.actionSystem.ActionManager
 | 
			
		||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
 | 
			
		||||
import com.intellij.openapi.actionSystem.AnActionEvent
 | 
			
		||||
import com.intellij.openapi.actionSystem.CommonDataKeys
 | 
			
		||||
import com.intellij.openapi.actionSystem.PlatformCoreDataKeys
 | 
			
		||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.openapi.diagnostic.logger
 | 
			
		||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
 | 
			
		||||
import com.intellij.openapi.project.DumbAwareAction
 | 
			
		||||
import com.intellij.openapi.project.Project
 | 
			
		||||
import com.intellij.openapi.project.ProjectManager
 | 
			
		||||
import com.intellij.openapi.startup.ProjectActivity
 | 
			
		||||
import com.intellij.openapi.wm.ToolWindow
 | 
			
		||||
import com.intellij.openapi.ui.getUserData
 | 
			
		||||
import com.intellij.openapi.ui.putUserData
 | 
			
		||||
import com.intellij.openapi.util.Key
 | 
			
		||||
import com.intellij.openapi.wm.ToolWindowId
 | 
			
		||||
import com.intellij.openapi.wm.ex.ToolWindowManagerEx
 | 
			
		||||
import com.intellij.openapi.wm.ex.ToolWindowManagerListener
 | 
			
		||||
import com.intellij.ui.KeyStrokeAdapter
 | 
			
		||||
import com.intellij.ui.TreeExpandCollapse
 | 
			
		||||
import com.intellij.ui.speedSearch.SpeedSearchSupply
 | 
			
		||||
@@ -53,6 +49,8 @@ import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
 | 
			
		||||
import java.awt.event.KeyEvent
 | 
			
		||||
import javax.swing.JComponent
 | 
			
		||||
import javax.swing.JTree
 | 
			
		||||
import javax.swing.KeyStroke
 | 
			
		||||
import javax.swing.SwingConstants
 | 
			
		||||
 | 
			
		||||
@@ -132,7 +130,6 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
 | 
			
		||||
    synchronized(Util.monitor) {
 | 
			
		||||
      Util.commandsRegistered = true
 | 
			
		||||
      ProjectManager.getInstance().openProjects.forEach { project -> installDispatcher(project) }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -164,39 +161,8 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  class ProjectViewListener(private val project: Project) : ToolWindowManagerListener {
 | 
			
		||||
    override fun toolWindowShown(toolWindow: ToolWindow) {
 | 
			
		||||
      if (ToolWindowId.PROJECT_VIEW != toolWindow.id) return
 | 
			
		||||
 | 
			
		||||
      val dispatcher = NerdDispatcher.getInstance(project)
 | 
			
		||||
      if (dispatcher.speedSearchListenerInstalled) return
 | 
			
		||||
 | 
			
		||||
      // I specify nullability explicitly as we've got a lot of exceptions saying this property is null
 | 
			
		||||
      val currentProjectViewPane: AbstractProjectViewPane? = ProjectView.getInstance(project).currentProjectViewPane
 | 
			
		||||
      val tree = currentProjectViewPane?.tree ?: return
 | 
			
		||||
      val supply = SpeedSearchSupply.getSupply(tree, true) ?: return
 | 
			
		||||
 | 
			
		||||
      // NB: Here might be some issues with concurrency, but it's not really bad, I think
 | 
			
		||||
      dispatcher.speedSearchListenerInstalled = true
 | 
			
		||||
      supply.addChangeListener {
 | 
			
		||||
        dispatcher.waitForSearch = false
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // TODO I'm not sure is this activity runs at all? Should we use [RunOnceUtil] instead?
 | 
			
		||||
  class NerdStartupActivity : ProjectActivity {
 | 
			
		||||
    override suspend fun execute(project: Project) {
 | 
			
		||||
      synchronized(Util.monitor) {
 | 
			
		||||
        if (!Util.commandsRegistered) return
 | 
			
		||||
        installDispatcher(project)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  class NerdDispatcher : DumbAwareAction() {
 | 
			
		||||
    internal var waitForSearch = false
 | 
			
		||||
    internal var speedSearchListenerInstalled = false
 | 
			
		||||
 | 
			
		||||
    override fun actionPerformed(e: AnActionEvent) {
 | 
			
		||||
      var keyStroke = getKeyStroke(e) ?: return
 | 
			
		||||
@@ -244,10 +210,6 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
      fun getInstance(project: Project): NerdDispatcher {
 | 
			
		||||
        return project.getService(NerdDispatcher::class.java)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      private const val ESCAPE_KEY_CODE = 27
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -283,19 +245,14 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
    registerCommand(
 | 
			
		||||
      "NERDTreeMapActivateNode",
 | 
			
		||||
      "o",
 | 
			
		||||
      NerdAction.Code { project, dataContext, _ ->
 | 
			
		||||
        val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
 | 
			
		||||
      NerdAction.Code { _, dataContext, e ->
 | 
			
		||||
        val tree = getTree(e) ?: return@Code
 | 
			
		||||
 | 
			
		||||
        val array = CommonDataKeys.NAVIGATABLE_ARRAY.getData(dataContext)?.filter { it.canNavigateToSource() }
 | 
			
		||||
        if (array.isNullOrEmpty()) {
 | 
			
		||||
          val row = tree.selectionRows?.getOrNull(0) ?: return@Code
 | 
			
		||||
          if (tree.isExpanded(row)) {
 | 
			
		||||
            tree.collapseRow(row)
 | 
			
		||||
          } else {
 | 
			
		||||
            tree.expandRow(row)
 | 
			
		||||
          }
 | 
			
		||||
        val row = tree.selectionRows?.getOrNull(0) ?: return@Code
 | 
			
		||||
        if (tree.isExpanded(row)) {
 | 
			
		||||
          tree.collapseRow(row)
 | 
			
		||||
        } else {
 | 
			
		||||
          array.forEach { it.navigate(true) }
 | 
			
		||||
          tree.expandRow(row)
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
    )
 | 
			
		||||
@@ -374,8 +331,8 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
    registerCommand(
 | 
			
		||||
      "NERDTreeMapOpenRecursively",
 | 
			
		||||
      "O",
 | 
			
		||||
      NerdAction.Code { project, _, _ ->
 | 
			
		||||
        val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
 | 
			
		||||
      NerdAction.Code { _, _, e ->
 | 
			
		||||
        val tree = getTree(e) ?: return@Code
 | 
			
		||||
        TreeExpandCollapse.expandAll(tree)
 | 
			
		||||
        tree.selectionPath?.let {
 | 
			
		||||
          TreeUtil.scrollToVisible(tree, it, false)
 | 
			
		||||
@@ -385,8 +342,8 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
    registerCommand(
 | 
			
		||||
      "NERDTreeMapCloseChildren",
 | 
			
		||||
      "X",
 | 
			
		||||
      NerdAction.Code { project, _, _ ->
 | 
			
		||||
        val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
 | 
			
		||||
      NerdAction.Code { _, _, e ->
 | 
			
		||||
        val tree = getTree(e) ?: return@Code
 | 
			
		||||
        TreeExpandCollapse.collapse(tree)
 | 
			
		||||
        tree.selectionPath?.let {
 | 
			
		||||
          TreeUtil.scrollToVisible(tree, it, false)
 | 
			
		||||
@@ -396,8 +353,8 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
    registerCommand(
 | 
			
		||||
      "NERDTreeMapCloseDir",
 | 
			
		||||
      "x",
 | 
			
		||||
      NerdAction.Code { project, _, _ ->
 | 
			
		||||
        val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
 | 
			
		||||
      NerdAction.Code { _, _, e ->
 | 
			
		||||
        val tree = getTree(e) ?: return@Code
 | 
			
		||||
        val currentPath = tree.selectionPath ?: return@Code
 | 
			
		||||
        if (tree.isExpanded(currentPath)) {
 | 
			
		||||
          tree.collapsePath(currentPath)
 | 
			
		||||
@@ -415,8 +372,8 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
    registerCommand(
 | 
			
		||||
      "NERDTreeMapJumpParent",
 | 
			
		||||
      "p",
 | 
			
		||||
      NerdAction.Code { project, _, _ ->
 | 
			
		||||
        val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
 | 
			
		||||
      NerdAction.Code { _, _, e ->
 | 
			
		||||
        val tree = getTree(e) ?: return@Code
 | 
			
		||||
        val currentPath = tree.selectionPath ?: return@Code
 | 
			
		||||
        val parentPath = currentPath.parentPath ?: return@Code
 | 
			
		||||
        if (parentPath.parentPath != null) {
 | 
			
		||||
@@ -429,8 +386,8 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
    registerCommand(
 | 
			
		||||
      "NERDTreeMapJumpFirstChild",
 | 
			
		||||
      "K",
 | 
			
		||||
      NerdAction.Code { project, _, _ ->
 | 
			
		||||
        val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
 | 
			
		||||
      NerdAction.Code { _, _, e ->
 | 
			
		||||
        val tree = getTree(e) ?: return@Code
 | 
			
		||||
        val currentPath = tree.selectionPath ?: return@Code
 | 
			
		||||
        val parent = currentPath.parentPath ?: return@Code
 | 
			
		||||
        val row = tree.getRowForPath(parent)
 | 
			
		||||
@@ -442,8 +399,8 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
    registerCommand(
 | 
			
		||||
      "NERDTreeMapJumpLastChild",
 | 
			
		||||
      "J",
 | 
			
		||||
      NerdAction.Code { project, _, _ ->
 | 
			
		||||
        val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
 | 
			
		||||
      NerdAction.Code { _, _, e ->
 | 
			
		||||
        val tree = getTree(e) ?: return@Code
 | 
			
		||||
        val currentPath = tree.selectionPath ?: return@Code
 | 
			
		||||
 | 
			
		||||
        val currentPathCount = currentPath.pathCount
 | 
			
		||||
@@ -488,20 +445,26 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
 | 
			
		||||
    registerCommand(
 | 
			
		||||
      "/",
 | 
			
		||||
      NerdAction.Code { project, _, _ ->
 | 
			
		||||
        NerdDispatcher.getInstance(project).waitForSearch = true
 | 
			
		||||
      NerdAction.Code { _, _, e ->
 | 
			
		||||
        val tree = getTree(e) ?: return@Code
 | 
			
		||||
        tree.getUserData(KEY)?.waitForSearch = true
 | 
			
		||||
      },
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    registerCommand(
 | 
			
		||||
      "<ESC>",
 | 
			
		||||
      NerdAction.Code { project, _, _ ->
 | 
			
		||||
        val instance = NerdDispatcher.getInstance(project)
 | 
			
		||||
        if (instance.waitForSearch) {
 | 
			
		||||
          instance.waitForSearch = false
 | 
			
		||||
        }
 | 
			
		||||
      NerdAction.Code { _, _, e ->
 | 
			
		||||
        val tree = getTree(e) ?: return@Code
 | 
			
		||||
        tree.getUserData(KEY)?.waitForSearch = false
 | 
			
		||||
      },
 | 
			
		||||
    )
 | 
			
		||||
    
 | 
			
		||||
    for (c in ('a'..'z') + ('A'..'Z')) {
 | 
			
		||||
      val ks = KeyStroke.getKeyStroke(c)
 | 
			
		||||
      if (ks !in actionsRoot) {
 | 
			
		||||
        registerCommand(c.toString(), NerdAction.Code { _, _, _ -> })
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  object Util {
 | 
			
		||||
@@ -526,6 +489,21 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
  companion object {
 | 
			
		||||
    const val pluginName = "NERDTree"
 | 
			
		||||
    private val LOG = logger<NerdTree>()
 | 
			
		||||
    private val KEY = Key.create<NerdDispatcher>("IdeaVim-NerdTree-Dispatcher")
 | 
			
		||||
 | 
			
		||||
    fun installDispatcher(component: JComponent) {
 | 
			
		||||
      if (component.getUserData(KEY) != null) return
 | 
			
		||||
 | 
			
		||||
      val dispatcher = NerdDispatcher()
 | 
			
		||||
      component.putUserData(KEY, dispatcher)
 | 
			
		||||
 | 
			
		||||
      val shortcuts = collectShortcuts(actionsRoot).map { RequiredShortcut(it, MappingOwner.Plugin.get(pluginName)) }
 | 
			
		||||
      dispatcher.registerCustomShortcutSet(KeyGroup.toShortcutSet(shortcuts), component)
 | 
			
		||||
 | 
			
		||||
      SpeedSearchSupply.getSupply(component, true)?.addChangeListener {
 | 
			
		||||
        dispatcher.waitForSearch = false
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -560,12 +538,6 @@ private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun installDispatcher(project: Project) {
 | 
			
		||||
  val dispatcher = NerdTree.NerdDispatcher.getInstance(project)
 | 
			
		||||
  val shortcuts =
 | 
			
		||||
    collectShortcuts(actionsRoot).map { RequiredShortcut(it, MappingOwner.Plugin.get(NerdTree.pluginName)) }
 | 
			
		||||
  dispatcher.registerCustomShortcutSet(
 | 
			
		||||
    KeyGroup.toShortcutSet(shortcuts),
 | 
			
		||||
    (ProjectView.getInstance(project) as ProjectViewImpl).component,
 | 
			
		||||
  )
 | 
			
		||||
private fun getTree(e: AnActionEvent): JTree? {
 | 
			
		||||
  return e.dataContext.getData(PlatformCoreDataKeys.CONTEXT_COMPONENT) as? JTree
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
package com.maddyhome.idea.vim.extension.nerdtree
 | 
			
		||||
 | 
			
		||||
import com.intellij.ide.ApplicationInitializedListener
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.util.ui.StartupUiUtil
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import java.awt.AWTEvent
 | 
			
		||||
import java.awt.event.FocusEvent
 | 
			
		||||
import javax.swing.JTree
 | 
			
		||||
 | 
			
		||||
@Suppress("UnstableApiUsage")
 | 
			
		||||
internal class NerdTreeApplicationListener : ApplicationInitializedListener {
 | 
			
		||||
  override suspend fun execute(asyncScope: CoroutineScope) {
 | 
			
		||||
    StartupUiUtil.addAwtListener(::handleEvent, AWTEvent.FOCUS_EVENT_MASK, ApplicationManager.getApplication().getService(NerdTreeDisposableService::class.java))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun handleEvent(event: AWTEvent) {
 | 
			
		||||
    if (event is FocusEvent && event.id == FocusEvent.FOCUS_GAINED) {
 | 
			
		||||
      val source = event.source
 | 
			
		||||
      if (source is JTree) {
 | 
			
		||||
        NerdTree.installDispatcher(source)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,9 @@
 | 
			
		||||
package com.maddyhome.idea.vim.extension.nerdtree
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.Disposable
 | 
			
		||||
import com.intellij.openapi.components.Service
 | 
			
		||||
 | 
			
		||||
@Service
 | 
			
		||||
internal class NerdTreeDisposableService : Disposable {
 | 
			
		||||
  override fun dispose() {}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,30 @@
 | 
			
		||||
package com.maddyhome.idea.vim.extension.surround
 | 
			
		||||
 | 
			
		||||
import com.intellij.util.text.CharSequenceSubSequence
 | 
			
		||||
 | 
			
		||||
internal data class RepeatedCharSequence(val text: CharSequence, val count: Int) : CharSequence {
 | 
			
		||||
  override val length = text.length * count
 | 
			
		||||
 | 
			
		||||
  override fun get(index: Int): Char {
 | 
			
		||||
    if (index < 0 || index >= length) throw IndexOutOfBoundsException()
 | 
			
		||||
    return text[index % text.length]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
 | 
			
		||||
    return CharSequenceSubSequence(this, startIndex, endIndex)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun toString(): String {
 | 
			
		||||
    return text.repeat(count)
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  companion object {
 | 
			
		||||
    fun of(text: CharSequence, count: Int): CharSequence {
 | 
			
		||||
      return when (count) {
 | 
			
		||||
        0 -> ""
 | 
			
		||||
        1 -> text
 | 
			
		||||
        else -> RepeatedCharSequence(text, count)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -14,6 +14,7 @@ import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimChangeGroup
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.endsWithNewLine
 | 
			
		||||
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
 | 
			
		||||
@@ -35,7 +36,10 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissin
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
 | 
			
		||||
import com.maddyhome.idea.vim.extension.exportOperatorFunction
 | 
			
		||||
import com.maddyhome.idea.vim.group.findBlockRange
 | 
			
		||||
import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
 | 
			
		||||
import com.maddyhome.idea.vim.key.OperatorFunction
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
 | 
			
		||||
@@ -78,7 +82,7 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
      putKeyMappingIfMissing(MappingMode.XO, injector.parser.parseKeys("S"), owner, injector.parser.parseKeys("<Plug>VSurround"), true)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator())
 | 
			
		||||
    VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator(supportsMultipleCursors = false, count = 1)) // TODO
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private class YSurroundHandler : ExtensionHandler {
 | 
			
		||||
@@ -106,7 +110,7 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
        val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset)
 | 
			
		||||
        if (lastNonWhiteSpaceOffset != null) {
 | 
			
		||||
          val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1)
 | 
			
		||||
          performSurround(pair, range, it)
 | 
			
		||||
          performSurround(pair, range, it, count = operatorArguments.count1)
 | 
			
		||||
        }
 | 
			
		||||
//        it.moveToOffset(lineStartOffset)
 | 
			
		||||
      }
 | 
			
		||||
@@ -126,15 +130,13 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
 | 
			
		||||
  private class VSurroundHandler : ExtensionHandler {
 | 
			
		||||
    override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
			
		||||
      val selectionStart = editor.ij.caretModel.primaryCaret.selectionStart
 | 
			
		||||
      // NB: Operator ignores SelectionType anyway
 | 
			
		||||
      if (!Operator().apply(editor, context, editor.mode.selectionType)) {
 | 
			
		||||
      if (!Operator(supportsMultipleCursors = true, count = operatorArguments.count1).apply(editor, context, editor.mode.selectionType)) {
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
      runWriteAction {
 | 
			
		||||
        // Leave visual mode
 | 
			
		||||
        executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
 | 
			
		||||
        editor.ij.caretModel.moveToOffset(selectionStart)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -155,6 +157,10 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
      fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
 | 
			
		||||
        editor.ij.runWithEveryCaretAndRestore { changeAtCaret(editor, context, charFrom, newSurround) }
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      fun changeAtCaret(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
 | 
			
		||||
        // Save old register values for carets
 | 
			
		||||
        val surroundings = editor.sortedCarets()
 | 
			
		||||
          .map {
 | 
			
		||||
@@ -262,21 +268,42 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private class Operator : OperatorFunction {
 | 
			
		||||
    override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
 | 
			
		||||
      val ijEditor = editor.ij
 | 
			
		||||
  private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction {
 | 
			
		||||
    override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
 | 
			
		||||
      val ijEditor = vimEditor.ij
 | 
			
		||||
      val c = getChar(ijEditor)
 | 
			
		||||
      if (c.code == 0) return true
 | 
			
		||||
 | 
			
		||||
      val pair = getOrInputPair(c, ijEditor, context.ij) ?: return false
 | 
			
		||||
      // XXX: Will it work with line-wise or block-wise selections?
 | 
			
		||||
      val range = getSurroundRange(editor.currentCaret()) ?: return false
 | 
			
		||||
      performSurround(pair, range, editor.currentCaret(), selectionType == SelectionType.LINE_WISE)
 | 
			
		||||
      // Jump back to start
 | 
			
		||||
      executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
 | 
			
		||||
 | 
			
		||||
      runWriteAction {
 | 
			
		||||
        val change = VimPlugin.getChange()
 | 
			
		||||
        if (supportsMultipleCursors) {
 | 
			
		||||
          ijEditor.runWithEveryCaretAndRestore {
 | 
			
		||||
            applyOnce(ijEditor, change, pair, count)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          applyOnce(ijEditor, change, pair, count)
 | 
			
		||||
          // Jump back to start
 | 
			
		||||
          executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: Pair<String, String>, count: Int) {
 | 
			
		||||
      // XXX: Will it work with line-wise or block-wise selections?
 | 
			
		||||
      val primaryCaret = editor.caretModel.primaryCaret
 | 
			
		||||
      val range = getSurroundRange(primaryCaret.vim)
 | 
			
		||||
      if (range != null) {
 | 
			
		||||
        val start = RepeatedCharSequence.of(pair.first, count)
 | 
			
		||||
        val end = RepeatedCharSequence.of(pair.second, count)
 | 
			
		||||
        change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, start)
 | 
			
		||||
        change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.endOffset + start.length, end)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getSurroundRange(caret: VimCaret): TextRange? {
 | 
			
		||||
      val editor = caret.editor
 | 
			
		||||
      val ijEditor = editor.ij
 | 
			
		||||
@@ -362,15 +389,15 @@ private fun getChar(editor: Editor): Char {
 | 
			
		||||
  return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, tagsOnNewLines: Boolean = false) {
 | 
			
		||||
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
 | 
			
		||||
  runWriteAction {
 | 
			
		||||
    val editor = caret.editor
 | 
			
		||||
    val change = VimPlugin.getChange()
 | 
			
		||||
    val leftSurround = pair.first + if (tagsOnNewLines) "\n" else ""
 | 
			
		||||
    val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count)
 | 
			
		||||
 | 
			
		||||
    val isEOF = range.endOffset == editor.text().length
 | 
			
		||||
    val hasNewLine = editor.endsWithNewLine()
 | 
			
		||||
    val rightSurround = if (tagsOnNewLines) {
 | 
			
		||||
    val rightSurround = (if (tagsOnNewLines) {
 | 
			
		||||
      if (isEOF && !hasNewLine) {
 | 
			
		||||
        "\n" + pair.second
 | 
			
		||||
      } else {
 | 
			
		||||
@@ -378,7 +405,7 @@ private fun performSurround(pair: Pair<String, String>, range: TextRange, caret:
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      pair.second
 | 
			
		||||
    }
 | 
			
		||||
    }).let { RepeatedCharSequence.of(it, count) }
 | 
			
		||||
 | 
			
		||||
    change.insertText(editor, caret, range.startOffset, leftSurround)
 | 
			
		||||
    change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)
 | 
			
		||||
 
 | 
			
		||||
@@ -79,7 +79,6 @@ import java.math.BigInteger
 | 
			
		||||
import java.util.*
 | 
			
		||||
import java.util.function.Consumer
 | 
			
		||||
import kotlin.math.max
 | 
			
		||||
import kotlin.math.min
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides all the insert/replace related functionality
 | 
			
		||||
@@ -395,6 +394,7 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
    context: ExecutionContext,
 | 
			
		||||
    range: TextRange,
 | 
			
		||||
  ) {
 | 
			
		||||
    val startPos = editor.offsetToBufferPosition(caret.offset)
 | 
			
		||||
    val startOffset = editor.getLineStartForOffset(range.startOffset)
 | 
			
		||||
    val endOffset = editor.getLineEndForOffset(range.endOffset)
 | 
			
		||||
    val ijEditor = (editor as IjVimEditor).editor
 | 
			
		||||
@@ -419,11 +419,7 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    val afterAction = {
 | 
			
		||||
      val firstLine = editor.offsetToBufferPosition(
 | 
			
		||||
        min(startOffset.toDouble(), endOffset.toDouble()).toInt()
 | 
			
		||||
      ).line
 | 
			
		||||
      val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine)
 | 
			
		||||
      caret.moveToOffset(newOffset)
 | 
			
		||||
      caret.moveToOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, startPos.line))
 | 
			
		||||
      restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
 | 
			
		||||
    }
 | 
			
		||||
    if (project != null) {
 | 
			
		||||
 
 | 
			
		||||
@@ -85,7 +85,7 @@ public object IjOptions {
 | 
			
		||||
  public val closenotebooks: ToggleOption = addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true))
 | 
			
		||||
  public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true))
 | 
			
		||||
  public val exCommandAnnotation: ToggleOption = addOption(ToggleOption("excommandannotation", GLOBAL, "excommandannotation", true, isHidden = true))
 | 
			
		||||
  public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", false, isHidden = true))
 | 
			
		||||
  public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isHidden = true))
 | 
			
		||||
  public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true))
 | 
			
		||||
  public val useNewRegex: ToggleOption = addOption(ToggleOption("usenewregex", GLOBAL, "usenewregex", true, isHidden = true))
 | 
			
		||||
  public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isHidden = true))
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,68 @@
 | 
			
		||||
package com.maddyhome.idea.vim.group
 | 
			
		||||
 | 
			
		||||
import com.intellij.codeInsight.daemon.ReferenceImporter
 | 
			
		||||
import com.intellij.openapi.actionSystem.CommonDataKeys
 | 
			
		||||
import com.intellij.openapi.actionSystem.DataContext
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.openapi.application.ReadAction
 | 
			
		||||
import com.intellij.openapi.command.WriteCommandAction
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.intellij.openapi.fileEditor.FileDocumentManager
 | 
			
		||||
import com.intellij.openapi.progress.ProgressIndicator
 | 
			
		||||
import com.intellij.openapi.progress.ProgressManager
 | 
			
		||||
import com.intellij.openapi.progress.Task
 | 
			
		||||
import com.intellij.psi.PsiDocumentManager
 | 
			
		||||
import com.intellij.psi.PsiElement
 | 
			
		||||
import com.intellij.psi.PsiRecursiveElementWalkingVisitor
 | 
			
		||||
import java.util.function.BooleanSupplier
 | 
			
		||||
 | 
			
		||||
internal object MacroAutoImport {
 | 
			
		||||
  fun run(editor: Editor, dataContext: DataContext) {
 | 
			
		||||
    val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return
 | 
			
		||||
    val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: return
 | 
			
		||||
 | 
			
		||||
    if (!FileDocumentManager.getInstance().requestWriting(editor.document, project)) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val importers = ReferenceImporter.EP_NAME.extensionList
 | 
			
		||||
    if (importers.isEmpty()) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Auto import", true) {
 | 
			
		||||
      override fun run(indicator: ProgressIndicator) {
 | 
			
		||||
        val fixes = ReadAction.nonBlocking<List<BooleanSupplier>> {
 | 
			
		||||
          val fixes = mutableListOf<BooleanSupplier>()
 | 
			
		||||
 | 
			
		||||
          file.accept(object : PsiRecursiveElementWalkingVisitor() {
 | 
			
		||||
            override fun visitElement(element: PsiElement) {
 | 
			
		||||
              for (reference in element.references) {
 | 
			
		||||
                if (reference.resolve() != null) {
 | 
			
		||||
                  continue
 | 
			
		||||
                }
 | 
			
		||||
                for (importer in importers) {
 | 
			
		||||
                  importer.computeAutoImportAtOffset(editor, file, element.textRange.startOffset, true)
 | 
			
		||||
                    ?.let(fixes::add)
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
              super.visitElement(element)
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
 | 
			
		||||
          return@nonBlocking fixes
 | 
			
		||||
        }.executeSynchronously()
 | 
			
		||||
 | 
			
		||||
        ApplicationManager.getApplication().invokeAndWait {
 | 
			
		||||
          WriteCommandAction.writeCommandAction(project)
 | 
			
		||||
            .withName("Auto Import")
 | 
			
		||||
            .withGroupId("IdeaVimAutoImportAfterMacro")
 | 
			
		||||
            .shouldRecordActionForActiveDocument(true)
 | 
			
		||||
            .run<RuntimeException> {
 | 
			
		||||
              fixes.forEach { it.asBoolean }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -22,6 +22,7 @@ import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.helper.MessageHelper.message
 | 
			
		||||
import com.maddyhome.idea.vim.macro.VimMacroBase
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Used to handle playback of macros
 | 
			
		||||
@@ -93,6 +94,9 @@ internal class MacroGroup : VimMacroBase() {
 | 
			
		||||
        } finally {
 | 
			
		||||
          keyStack.removeFirst()
 | 
			
		||||
        }
 | 
			
		||||
        if (!isInternalMacro) {
 | 
			
		||||
          MacroAutoImport.run(editor.ij, context.ij)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (isInternalMacro) {
 | 
			
		||||
 
 | 
			
		||||
@@ -210,8 +210,8 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
 | 
			
		||||
   * @param patternOffset   The pattern offset, e.g. `/{pattern}/{offset}`
 | 
			
		||||
   * @param direction       The direction to search
 | 
			
		||||
   */
 | 
			
		||||
  @TestOnly
 | 
			
		||||
  public void setLastSearchState(@SuppressWarnings("unused") @NotNull Editor editor, @NotNull String pattern,
 | 
			
		||||
  @Override
 | 
			
		||||
  public void setLastSearchState(@SuppressWarnings("unused") @NotNull VimEditor editor, @NotNull String pattern,
 | 
			
		||||
                                 @NotNull String patternOffset, Direction direction) {
 | 
			
		||||
    if (globalIjOptions(injector).getUseNewRegex()) {
 | 
			
		||||
      super.setLastSearchState(pattern, patternOffset, direction);
 | 
			
		||||
 
 | 
			
		||||
@@ -323,7 +323,7 @@ public class EditorHelper {
 | 
			
		||||
 | 
			
		||||
    final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
 | 
			
		||||
    @NotNull final VimEditor editor1 = new IjVimEditor(editor);
 | 
			
		||||
    final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
 | 
			
		||||
    final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) + editor.getSettings().getAdditionalLinesCount();
 | 
			
		||||
    final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine);
 | 
			
		||||
 | 
			
		||||
    // For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen.
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ package com.maddyhome.idea.vim.helper
 | 
			
		||||
 | 
			
		||||
import com.intellij.codeWithMe.ClientId
 | 
			
		||||
import com.intellij.openapi.editor.Caret
 | 
			
		||||
import com.intellij.openapi.editor.CaretState
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.intellij.openapi.editor.ex.util.EditorUtil
 | 
			
		||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
 | 
			
		||||
@@ -20,6 +21,8 @@ import com.maddyhome.idea.vim.api.StringListOptionValue
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.group.IjOptionConstants
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.inBlockSelection
 | 
			
		||||
import java.awt.Component
 | 
			
		||||
import javax.swing.JComponent
 | 
			
		||||
import javax.swing.JTable
 | 
			
		||||
@@ -96,3 +99,41 @@ internal val Caret.vimLine: Int
 | 
			
		||||
 */
 | 
			
		||||
internal val Editor.vimLine: Int
 | 
			
		||||
  get() = this.caretModel.currentCaret.vimLine
 | 
			
		||||
 | 
			
		||||
internal inline fun Editor.runWithEveryCaretAndRestore(action: () -> Unit) {
 | 
			
		||||
  val caretModel = this.caretModel
 | 
			
		||||
  val carets = if (this.vim.inBlockSelection) null else caretModel.allCarets
 | 
			
		||||
  if (carets == null || carets.size == 1) {
 | 
			
		||||
    action()
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    var initialDocumentSize = this.document.textLength
 | 
			
		||||
    var documentSizeDifference = 0
 | 
			
		||||
 | 
			
		||||
    val caretOffsets = carets.map { it.selectionStart to it.selectionEnd }
 | 
			
		||||
    val restoredCarets = mutableListOf<CaretState>()
 | 
			
		||||
 | 
			
		||||
    caretModel.removeSecondaryCarets()
 | 
			
		||||
    
 | 
			
		||||
    for ((selectionStart, selectionEnd) in caretOffsets) {
 | 
			
		||||
      if (selectionStart == selectionEnd) {
 | 
			
		||||
        caretModel.primaryCaret.moveToOffset(selectionStart + documentSizeDifference)
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        caretModel.primaryCaret.setSelection(
 | 
			
		||||
          selectionStart + documentSizeDifference,
 | 
			
		||||
          selectionEnd + documentSizeDifference
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      action()
 | 
			
		||||
      restoredCarets.add(caretModel.caretsAndSelections.single())
 | 
			
		||||
 | 
			
		||||
      val documentLength = this.document.textLength
 | 
			
		||||
      documentSizeDifference += documentLength - initialDocumentSize
 | 
			
		||||
      initialDocumentSize = documentLength
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    caretModel.caretsAndSelections = restoredCarets
 | 
			
		||||
  } 
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ import com.intellij.openapi.actionSystem.PlatformDataKeys
 | 
			
		||||
import com.intellij.openapi.actionSystem.ex.ActionManagerEx
 | 
			
		||||
import com.intellij.openapi.actionSystem.ex.ActionUtil
 | 
			
		||||
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
 | 
			
		||||
import com.intellij.openapi.actionSystem.impl.Utils
 | 
			
		||||
import com.intellij.openapi.command.CommandProcessor
 | 
			
		||||
import com.intellij.openapi.command.UndoConfirmationPolicy
 | 
			
		||||
import com.intellij.openapi.components.Service
 | 
			
		||||
@@ -86,6 +87,7 @@ internal class IjActionExecutor : VimActionExecutor {
 | 
			
		||||
      ActionManager.getInstance(),
 | 
			
		||||
      0,
 | 
			
		||||
    )
 | 
			
		||||
    Utils.initUpdateSession(event)
 | 
			
		||||
    // beforeActionPerformedUpdate should be called to update the action. It fixes some rider-specific problems.
 | 
			
		||||
    //   because rider use async update method. See VIM-1819.
 | 
			
		||||
    // This method executes inside of lastUpdateAndCheckDumb
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,7 @@ internal object ScrollViewHelper {
 | 
			
		||||
    // that this needs to be replaced as a more or less dumb line for line rewrite.
 | 
			
		||||
    val topLine = getVisualLineAtTopOfScreen(editor)
 | 
			
		||||
    val bottomLine = getVisualLineAtBottomOfScreen(editor)
 | 
			
		||||
    val lastLine = vimEditor.getVisualLineCount() - 1
 | 
			
		||||
    val lastLine = vimEditor.getVisualLineCount() + editor.settings.additionalLinesCount
 | 
			
		||||
 | 
			
		||||
    // We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred
 | 
			
		||||
    val scrollOffset = injector.options(vimEditor).scrolloff
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ import com.intellij.openapi.components.Service
 | 
			
		||||
import com.intellij.openapi.fileEditor.TextEditor
 | 
			
		||||
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
 | 
			
		||||
import com.intellij.openapi.util.registry.Registry
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
@@ -24,6 +25,8 @@ import com.maddyhome.idea.vim.common.ChangesListener
 | 
			
		||||
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.inVisualMode
 | 
			
		||||
import com.maddyhome.idea.vim.undo.UndoRedoBase
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -58,13 +61,13 @@ internal class UndoRedoHelper : UndoRedoBase() {
 | 
			
		||||
    undoManager: UndoManager,
 | 
			
		||||
    fileEditor: TextEditor,
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
  ) {
 | 
			
		||||
    if (injector.globalIjOptions().oldundo) {
 | 
			
		||||
      SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
 | 
			
		||||
    } else {
 | 
			
		||||
      // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
 | 
			
		||||
      editor.runWithChangeTracking {
 | 
			
		||||
        undoManager.undo(fileEditor)
 | 
			
		||||
  ) {if (injector.globalIjOptions().oldundo) {
 | 
			
		||||
        SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
 | 
			
		||||
        restoreVisualMode(editor)
 | 
			
		||||
      } else {
 | 
			
		||||
        // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
 | 
			
		||||
        editor.runWithChangeTracking {
 | 
			
		||||
          undoManager.undo(fileEditor)
 | 
			
		||||
 | 
			
		||||
        // We execute undo one more time if the previous one just restored selection
 | 
			
		||||
        if (!hasChanges && hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
 | 
			
		||||
@@ -135,17 +138,17 @@ internal class UndoRedoHelper : UndoRedoBase() {
 | 
			
		||||
    undoManager: UndoManager,
 | 
			
		||||
    fileEditor: TextEditor,
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
  ) {
 | 
			
		||||
    if (injector.globalIjOptions().oldundo) {
 | 
			
		||||
      SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
 | 
			
		||||
    } else {
 | 
			
		||||
      undoManager.redo(fileEditor)
 | 
			
		||||
      CommandProcessor.getInstance().runUndoTransparentAction {
 | 
			
		||||
        editor.carets().forEach { it.ij.removeSelection() }
 | 
			
		||||
      }
 | 
			
		||||
      // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
 | 
			
		||||
      editor.runWithChangeTracking {
 | 
			
		||||
  ) {if (injector.globalIjOptions().oldundo) {
 | 
			
		||||
        SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
 | 
			
		||||
        restoreVisualMode(editor)
 | 
			
		||||
      } else {
 | 
			
		||||
        undoManager.redo(fileEditor)
 | 
			
		||||
        CommandProcessor.getInstance().runUndoTransparentAction {
 | 
			
		||||
          editor.carets().forEach { it.ij.removeSelection() }
 | 
			
		||||
        }
 | 
			
		||||
        // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
 | 
			
		||||
        editor.runWithChangeTracking {
 | 
			
		||||
          undoManager.redo(fileEditor)
 | 
			
		||||
 | 
			
		||||
        // We execute undo one more time if the previous one just restored selection
 | 
			
		||||
        if (!hasChanges && hasSelection(editor) && undoManager.isRedoAvailable(fileEditor)) {
 | 
			
		||||
@@ -237,4 +240,21 @@ internal class UndoRedoHelper : UndoRedoBase() {
 | 
			
		||||
    val hasChanges: Boolean
 | 
			
		||||
      get() = changeListener.hasChanged || initialPath != editor.getPath()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun restoreVisualMode(editor: VimEditor) {
 | 
			
		||||
    if (!editor.inVisualMode && editor.getSelectionModel().hasSelection()) {
 | 
			
		||||
      val detectedMode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor)
 | 
			
		||||
      
 | 
			
		||||
      // Visual block selection is restored into multiple carets, so multi-carets that form a block are always
 | 
			
		||||
      // identified as visual block mode, leading to false positives.
 | 
			
		||||
      // Since I use visual block mode much less often than multi-carets, this is a judgment call to never restore
 | 
			
		||||
      // visual block mode.
 | 
			
		||||
      val wantedMode = if (detectedMode == SelectionType.BLOCK_WISE)
 | 
			
		||||
        SelectionType.CHARACTER_WISE
 | 
			
		||||
      else
 | 
			
		||||
        detectedMode
 | 
			
		||||
      
 | 
			
		||||
      VimPlugin.getVisualMotion().enterVisualMode(editor, wantedMode)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@ import com.intellij.openapi.actionSystem.CommonDataKeys
 | 
			
		||||
import com.intellij.openapi.actionSystem.ex.AnActionListener
 | 
			
		||||
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.intellij.openapi.editor.impl.ScrollingModelImpl
 | 
			
		||||
import com.intellij.openapi.project.DumbAwareToggleAction
 | 
			
		||||
import com.intellij.openapi.util.TextRange
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler
 | 
			
		||||
@@ -57,6 +58,7 @@ internal object IdeaSpecifics {
 | 
			
		||||
    private val surrounderAction =
 | 
			
		||||
      "com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction"
 | 
			
		||||
    private var editor: Editor? = null
 | 
			
		||||
    private var caretOffset = -1
 | 
			
		||||
    private var completionPrevDocumentLength: Int? = null
 | 
			
		||||
    private var completionPrevDocumentOffset: Int? = null
 | 
			
		||||
    override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
 | 
			
		||||
@@ -65,6 +67,7 @@ internal object IdeaSpecifics {
 | 
			
		||||
      val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
 | 
			
		||||
      if (hostEditor != null) {
 | 
			
		||||
        editor = hostEditor
 | 
			
		||||
        caretOffset = hostEditor.caretModel.offset
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction
 | 
			
		||||
@@ -96,43 +99,58 @@ internal object IdeaSpecifics {
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
 | 
			
		||||
      val editor = editor
 | 
			
		||||
      if (editor != null && action is ChooseItemAction && injector.registerGroup.isRecording) {
 | 
			
		||||
        val prevDocumentLength = completionPrevDocumentLength
 | 
			
		||||
        val prevDocumentOffset = completionPrevDocumentOffset
 | 
			
		||||
      if (editor != null) {
 | 
			
		||||
        if (action is ChooseItemAction && injector.registerGroup.isRecording) {
 | 
			
		||||
          val prevDocumentLength = completionPrevDocumentLength
 | 
			
		||||
          val prevDocumentOffset = completionPrevDocumentOffset
 | 
			
		||||
 | 
			
		||||
        if (prevDocumentLength != null && prevDocumentOffset != null) {
 | 
			
		||||
          val register = VimPlugin.getRegister()
 | 
			
		||||
          val addedTextLength = editor.document.textLength - prevDocumentLength
 | 
			
		||||
          val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
 | 
			
		||||
          val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
 | 
			
		||||
          if (prevDocumentLength != null && prevDocumentOffset != null) {
 | 
			
		||||
            val register = VimPlugin.getRegister()
 | 
			
		||||
            val addedTextLength = editor.document.textLength - prevDocumentLength
 | 
			
		||||
            val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
 | 
			
		||||
            val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
 | 
			
		||||
 | 
			
		||||
          register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength)))
 | 
			
		||||
          repeat(caretShift.coerceAtLeast(0)) {
 | 
			
		||||
            register.recordKeyStroke(leftArrow)
 | 
			
		||||
            register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength)))
 | 
			
		||||
            repeat(caretShift.coerceAtLeast(0)) {
 | 
			
		||||
              register.recordKeyStroke(leftArrow)
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          this.completionPrevDocumentLength = null
 | 
			
		||||
          this.completionPrevDocumentOffset = null
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        //region Enter insert mode after surround with if
 | 
			
		||||
        if (surrounderAction == action.javaClass.name && surrounderItems.any {
 | 
			
		||||
            action.templatePresentation.text.endsWith(
 | 
			
		||||
              it,
 | 
			
		||||
            )
 | 
			
		||||
          }
 | 
			
		||||
        ) {
 | 
			
		||||
          editor?.let {
 | 
			
		||||
            val commandState = it.vim.vimStateMachine
 | 
			
		||||
            it.vim.mode = Mode.NORMAL()
 | 
			
		||||
            VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
 | 
			
		||||
            KeyHandler.getInstance().reset(it.vim)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        //endregion
 | 
			
		||||
 | 
			
		||||
        this.completionPrevDocumentLength = null
 | 
			
		||||
        this.completionPrevDocumentOffset = null
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      //region Enter insert mode after surround with if
 | 
			
		||||
      if (surrounderAction == action.javaClass.name && surrounderItems.any {
 | 
			
		||||
          action.templatePresentation.text.endsWith(
 | 
			
		||||
            it,
 | 
			
		||||
          )
 | 
			
		||||
        }
 | 
			
		||||
      ) {
 | 
			
		||||
        editor?.let {
 | 
			
		||||
          val commandState = it.vim.vimStateMachine
 | 
			
		||||
          it.vim.mode = Mode.NORMAL()
 | 
			
		||||
          VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
 | 
			
		||||
          KeyHandler.getInstance().reset(it.vim)
 | 
			
		||||
        if (caretOffset != -1 && caretOffset != editor.caretModel.offset) {
 | 
			
		||||
          val scrollModel = editor.scrollingModel as ScrollingModelImpl
 | 
			
		||||
          if (scrollModel.isScrollingNow) {
 | 
			
		||||
            val v = scrollModel.verticalScrollOffset
 | 
			
		||||
            val h = scrollModel.horizontalScrollOffset
 | 
			
		||||
            scrollModel.finishAnimation()
 | 
			
		||||
            scrollModel.scroll(h, v)
 | 
			
		||||
            scrollModel.finishAnimation()
 | 
			
		||||
          }
 | 
			
		||||
          injector.scroll.scrollCaretIntoView(editor.vim)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      //endregion
 | 
			
		||||
 | 
			
		||||
      this.editor = null
 | 
			
		||||
      this.caretOffset = -1
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,7 @@ import com.intellij.openapi.editor.ex.DocumentEx
 | 
			
		||||
import com.intellij.openapi.editor.ex.EditorEventMulticasterEx
 | 
			
		||||
import com.intellij.openapi.editor.ex.FocusChangeListener
 | 
			
		||||
import com.intellij.openapi.editor.impl.EditorComponentImpl
 | 
			
		||||
import com.intellij.openapi.editor.impl.EditorImpl
 | 
			
		||||
import com.intellij.openapi.fileEditor.FileEditorManager
 | 
			
		||||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
 | 
			
		||||
import com.intellij.openapi.fileEditor.FileEditorManagerListener
 | 
			
		||||
@@ -45,11 +46,14 @@ import com.intellij.openapi.fileEditor.ex.FileEditorWithProvider
 | 
			
		||||
import com.intellij.openapi.fileEditor.impl.EditorComposite
 | 
			
		||||
import com.intellij.openapi.fileEditor.impl.EditorWindow
 | 
			
		||||
import com.intellij.openapi.project.ProjectManager
 | 
			
		||||
import com.intellij.openapi.rd.createLifetime
 | 
			
		||||
import com.intellij.openapi.rd.createNestedDisposable
 | 
			
		||||
import com.intellij.openapi.util.Disposer
 | 
			
		||||
import com.intellij.openapi.util.Key
 | 
			
		||||
import com.intellij.openapi.util.removeUserData
 | 
			
		||||
import com.intellij.openapi.vfs.VirtualFile
 | 
			
		||||
import com.intellij.util.ExceptionUtil
 | 
			
		||||
import com.jetbrains.rd.util.lifetime.Lifetime
 | 
			
		||||
import com.maddyhome.idea.vim.EventFacade
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandlerStateResetter
 | 
			
		||||
@@ -101,7 +105,6 @@ import com.maddyhome.idea.vim.ui.widgets.macro.MacroWidgetListener
 | 
			
		||||
import com.maddyhome.idea.vim.ui.widgets.macro.macroWidgetOptionListener
 | 
			
		||||
import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetListener
 | 
			
		||||
import com.maddyhome.idea.vim.ui.widgets.mode.modeWidgetOptionListener
 | 
			
		||||
import com.maddyhome.idea.vim.vimDisposable
 | 
			
		||||
import java.awt.event.MouseAdapter
 | 
			
		||||
import java.awt.event.MouseEvent
 | 
			
		||||
import javax.swing.SwingUtilities
 | 
			
		||||
@@ -264,12 +267,10 @@ internal object VimListenerManager {
 | 
			
		||||
      // TODO: If the user changes the 'ideavimsupport' option, existing editors won't be initialised
 | 
			
		||||
      if (vimDisabled(editor)) return
 | 
			
		||||
 | 
			
		||||
      // As I understand, there is no need to pass a disposable that also disposes on editor close
 | 
			
		||||
      //   because all editor resources will be garbage collected anyway on editor close
 | 
			
		||||
      // Note that this uses the plugin's main disposable, rather than VimPlugin.onOffDisposable, because we don't need
 | 
			
		||||
      // to - we explicitly call VimListenerManager.removeAll from VimPlugin.turnOffPlugin, and this disposes each
 | 
			
		||||
      // editor's disposable individually.
 | 
			
		||||
      val disposable = editor.project?.vimDisposable ?: return
 | 
			
		||||
      val pluginLifetime = VimPlugin.getInstance().createLifetime()
 | 
			
		||||
      val editorLifetime = (editor as EditorImpl).disposable.createLifetime()
 | 
			
		||||
      val disposable =
 | 
			
		||||
        Lifetime.intersect(pluginLifetime, editorLifetime).createNestedDisposable("MyLifetimedDisposable")
 | 
			
		||||
 | 
			
		||||
      val listenersDisposable = Disposer.newDisposable(disposable)
 | 
			
		||||
      editor.putUserData(editorListenersDisposableKey, listenersDisposable)
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@ import com.maddyhome.idea.vim.vimscript.model.functions.handlers.SubmatchFunctio
 | 
			
		||||
import org.jetbrains.annotations.TestOnly
 | 
			
		||||
import javax.swing.KeyStroke
 | 
			
		||||
 | 
			
		||||
public open class IjVimSearchGroup : VimSearchGroupBase() {
 | 
			
		||||
public abstract class IjVimSearchGroup : VimSearchGroupBase() {
 | 
			
		||||
 | 
			
		||||
  init {
 | 
			
		||||
    // TODO: Investigate migrating these listeners to use the effective value change listener
 | 
			
		||||
 
 | 
			
		||||
@@ -136,11 +136,7 @@
 | 
			
		||||
 | 
			
		||||
  <!--  IdeaVim extensions-->
 | 
			
		||||
  <extensions defaultExtensionNs="com.intellij">
 | 
			
		||||
    <projectService serviceImplementation="com.maddyhome.idea.vim.extension.nerdtree.NerdTree$NerdDispatcher"/>
 | 
			
		||||
    <postStartupActivity implementation="com.maddyhome.idea.vim.extension.nerdtree.NerdTree$NerdStartupActivity"/>
 | 
			
		||||
    <applicationService serviceImplementation="com.maddyhome.idea.vim.extension.nerdtree.NerdTree$NerdDispatcher"/>
 | 
			
		||||
    <applicationInitializedListener implementation="com.maddyhome.idea.vim.extension.nerdtree.NerdTreeApplicationListener"/>
 | 
			
		||||
  </extensions>
 | 
			
		||||
  <projectListeners>
 | 
			
		||||
    <listener class="com.maddyhome.idea.vim.extension.nerdtree.NerdTree$ProjectViewListener"
 | 
			
		||||
              topic="com.intellij.openapi.wm.ex.ToolWindowManagerListener"/>
 | 
			
		||||
  </projectListeners>
 | 
			
		||||
</idea-plugin>
 | 
			
		||||
 
 | 
			
		||||
@@ -151,5 +151,6 @@
 | 
			
		||||
    </group>
 | 
			
		||||
 | 
			
		||||
    <action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/>
 | 
			
		||||
    <action id="VimJumpToSource" class="com.intellij.diff.actions.impl.OpenInEditorAction" />
 | 
			
		||||
  </actions>
 | 
			
		||||
</idea-plugin>
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ package org.jetbrains.plugins.ideavim.action.motion.gn
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.common.Direction
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
			
		||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
			
		||||
@@ -85,7 +86,7 @@ class GnNextTextObjectTest : VimTestCase() {
 | 
			
		||||
 | 
			
		||||
  private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) {
 | 
			
		||||
    configureByText(before)
 | 
			
		||||
    VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
 | 
			
		||||
    VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
 | 
			
		||||
    typeText(keys)
 | 
			
		||||
    assertState(after)
 | 
			
		||||
    assertState(Mode.NORMAL())
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ package org.jetbrains.plugins.ideavim.action.motion.gn
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.common.Direction
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
			
		||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
			
		||||
@@ -63,7 +64,7 @@ class GnPreviousTextObjectTest : VimTestCase() {
 | 
			
		||||
 | 
			
		||||
  private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) {
 | 
			
		||||
    configureByText(before)
 | 
			
		||||
    VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
 | 
			
		||||
    VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
 | 
			
		||||
    typeText(keys)
 | 
			
		||||
    assertState(after)
 | 
			
		||||
    assertState(Mode.NORMAL())
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.common.Direction
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
			
		||||
@@ -57,7 +58,7 @@ class VisualSelectNextSearchTest : VimTestCase() {
 | 
			
		||||
  @Test
 | 
			
		||||
  fun testWithoutSpaces() {
 | 
			
		||||
    configureByText("test<caret>test")
 | 
			
		||||
    VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
 | 
			
		||||
    VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
 | 
			
		||||
    typeText(injector.parser.parseKeys("gn"))
 | 
			
		||||
    assertOffset(7)
 | 
			
		||||
    assertSelection("test")
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.common.Direction
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
			
		||||
@@ -54,7 +55,7 @@ class VisualSelectPreviousSearchTest : VimTestCase() {
 | 
			
		||||
  @Test
 | 
			
		||||
  fun testWithoutSpaces() {
 | 
			
		||||
    configureByText("tes<caret>ttest")
 | 
			
		||||
    VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
 | 
			
		||||
    VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
 | 
			
		||||
    typeText(injector.parser.parseKeys("gN"))
 | 
			
		||||
    assertOffset(0)
 | 
			
		||||
    assertSelection("test")
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ package org.jetbrains.plugins.ideavim.action.motion.search
 | 
			
		||||
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.common.Direction
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
			
		||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
			
		||||
import org.jetbrains.plugins.ideavim.VimTestCase
 | 
			
		||||
@@ -167,7 +168,7 @@ class SearchAgainPreviousActionTest : VimTestCase() {
 | 
			
		||||
 | 
			
		||||
  private fun doTestWithSearch(keys: String, before: String, after: String) {
 | 
			
		||||
    doTest(keys, before, after) {
 | 
			
		||||
      VimPlugin.getSearch().setLastSearchState(it, "all", "", Direction.FORWARDS)
 | 
			
		||||
      VimPlugin.getSearch().setLastSearchState(it.vim, "all", "", Direction.FORWARDS)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.command.Command
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.handler.VimActionHandler
 | 
			
		||||
 | 
			
		||||
@CommandOrMotion(keys = ["<C-R>"], modes = [Mode.NORMAL])
 | 
			
		||||
@CommandOrMotion(keys = ["U", "<C-R>"], modes = [Mode.NORMAL, Mode.VISUAL])
 | 
			
		||||
public class RedoAction : VimActionHandler.SingleExecution() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.command.Command
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.handler.VimActionHandler
 | 
			
		||||
 | 
			
		||||
@CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL])
 | 
			
		||||
@CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL, Mode.VISUAL])
 | 
			
		||||
public class UndoAction : VimActionHandler.SingleExecution() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@
 | 
			
		||||
package com.maddyhome.idea.vim.action.change.change
 | 
			
		||||
 | 
			
		||||
import com.intellij.vim.annotations.CommandOrMotion
 | 
			
		||||
import com.intellij.vim.annotations.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
@@ -22,7 +21,7 @@ import com.maddyhome.idea.vim.helper.CharacterHelper
 | 
			
		||||
/**
 | 
			
		||||
 * @author vlan
 | 
			
		||||
 */
 | 
			
		||||
@CommandOrMotion(keys = ["u"], modes = [Mode.VISUAL])
 | 
			
		||||
@CommandOrMotion(keys = [], modes = [])
 | 
			
		||||
public class ChangeCaseLowerVisualAction : VisualOperatorActionHandler.ForEachCaret() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.CHANGE
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@
 | 
			
		||||
package com.maddyhome.idea.vim.action.change.change
 | 
			
		||||
 | 
			
		||||
import com.intellij.vim.annotations.CommandOrMotion
 | 
			
		||||
import com.intellij.vim.annotations.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
@@ -22,7 +21,7 @@ import com.maddyhome.idea.vim.helper.CharacterHelper
 | 
			
		||||
/**
 | 
			
		||||
 * @author vlan
 | 
			
		||||
 */
 | 
			
		||||
@CommandOrMotion(keys = ["U"], modes = [Mode.VISUAL])
 | 
			
		||||
@CommandOrMotion(keys = [], modes = [])
 | 
			
		||||
public class ChangeCaseUpperVisualAction : VisualOperatorActionHandler.ForEachCaret() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.CHANGE
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,7 @@ public class FilterVisualLinesAction : VimActionHandler.SingleExecution() {
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MOT_LINEWISE)
 | 
			
		||||
 | 
			
		||||
  override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
 | 
			
		||||
    injector.markService.setVisualSelectionMarks(editor)
 | 
			
		||||
    injector.processGroup.startFilterCommand(editor, context, cmd)
 | 
			
		||||
    editor.exitVisualMode()
 | 
			
		||||
    return true
 | 
			
		||||
 
 | 
			
		||||
@@ -82,6 +82,13 @@ public sealed class TillCharacterMotion(
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
    injector.motion.setLastFTCmd(tillCharacterMotionType, argument.character)
 | 
			
		||||
    
 | 
			
		||||
    val offset = if (!finishBeforeCharacter) ""
 | 
			
		||||
    else if (direction == Direction.FORWARDS) "s-1"
 | 
			
		||||
    else "s+1"
 | 
			
		||||
    
 | 
			
		||||
    injector.searchGroup.setLastSearchState(editor, argument.character.let { if (it == '.') "\\." else it.toString() }, offset, direction)
 | 
			
		||||
    
 | 
			
		||||
    return res.toMotionOrError()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -146,7 +146,7 @@ public interface VimChangeGroup {
 | 
			
		||||
    operatorArguments: OperatorArguments,
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  public fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: String): VimCaret
 | 
			
		||||
  public fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: CharSequence): VimCaret
 | 
			
		||||
 | 
			
		||||
  public fun insertText(editor: VimEditor, caret: VimCaret, str: String): VimCaret
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -201,7 +201,7 @@ public abstract class VimChangeGroupBase : VimChangeGroup {
 | 
			
		||||
   * @param caret  The caret to start insertion in
 | 
			
		||||
   * @param str    The text to insert
 | 
			
		||||
   */
 | 
			
		||||
  override fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: String): VimCaret {
 | 
			
		||||
  override fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: CharSequence): VimCaret {
 | 
			
		||||
    (editor as MutableVimEditor).insertText(offset, str)
 | 
			
		||||
    val newCaret = caret.moveToInlayAwareOffset(offset + str.length)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ public interface VimSearchGroup {
 | 
			
		||||
   * Last used pattern to perform a substitution.
 | 
			
		||||
   */
 | 
			
		||||
  public var lastSubstitutePattern: String?
 | 
			
		||||
 | 
			
		||||
  public fun setLastSearchState(editor: VimEditor, pattern: String, patternOffset: String, direction: Direction?)
 | 
			
		||||
  public fun searchBackward(editor: VimEditor, offset: Int, count: Int): TextRange?
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user