diff --git a/src/main/java/com/maddyhome/idea/vim/extension/surround/VimSurroundExtension.kt b/src/main/java/com/maddyhome/idea/vim/extension/surround/VimSurroundExtension.kt
index f13128548..2ed9f9d12 100644
--- a/src/main/java/com/maddyhome/idea/vim/extension/surround/VimSurroundExtension.kt
+++ b/src/main/java/com/maddyhome/idea/vim/extension/surround/VimSurroundExtension.kt
@@ -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
@@ -36,7 +37,10 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
 import com.maddyhome.idea.vim.extension.exportOperatorFunction
 import com.maddyhome.idea.vim.group.findBlockRange
 import com.maddyhome.idea.vim.helper.exitVisualMode
+import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
 import com.maddyhome.idea.vim.key.OperatorFunction
+import com.maddyhome.idea.vim.newapi.IjVimCaret
+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
@@ -79,7 +83,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)) // TODO
   }
 
   private class YSurroundHandler : ExtensionHandler {
@@ -130,15 +134,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).apply(editor, context, editor.mode.selectionType)) {
         return
       }
       runWriteAction {
         // Leave visual mode
         editor.exitVisualMode()
-        editor.ij.caretModel.moveToOffset(selectionStart)
       }
     }
   }
@@ -159,6 +161,10 @@ internal class VimSurroundExtension : VimExtension {
 
     companion object {
       fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: SurroundPair?) {
+        editor.ij.runWithEveryCaretAndRestore { changeAtCaret(editor, context, charFrom, newSurround) }
+      }
+      
+      fun changeAtCaret(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: SurroundPair?) {
         // Save old register values for carets
         val surroundings = editor.sortedCarets()
           .map {
@@ -272,20 +278,44 @@ 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) : 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)
+          }
+        }
+        else {
+          applyOnce(ijEditor, change, pair)
+          // Jump back to start
+          executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
+        }
+      }
       return true
     }
+    
+    private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: SurroundPair) {
+      // XXX: Will it work with line-wise or block-wise selections?
+      val primaryCaret = editor.caretModel.primaryCaret
+      val range = getSurroundRange(primaryCaret.vim)
+      if (range != null) {
+        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(caret: VimCaret): TextRange? {
       val editor = caret.editor
diff --git a/src/main/java/com/maddyhome/idea/vim/helper/EditorHelper.kt b/src/main/java/com/maddyhome/idea/vim/helper/EditorHelper.kt
index 2db097b80..52c39423a 100644
--- a/src/main/java/com/maddyhome/idea/vim/helper/EditorHelper.kt
+++ b/src/main/java/com/maddyhome/idea/vim/helper/EditorHelper.kt
@@ -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
@@ -21,6 +22,8 @@ import com.maddyhome.idea.vim.api.injector
 import com.maddyhome.idea.vim.group.IjOptionConstants
 import com.maddyhome.idea.vim.key.IdeaVimDisablerExtensionPoint
 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
@@ -97,4 +100,42 @@ internal val Caret.vimLine: Int
  * Get current caret line in vim notation (1-based)
  */
 internal val Editor.vimLine: Int
-  get() = this.caretModel.currentCaret.vimLine
\ No newline at end of file
+  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
+  } 
+}