diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..33224b4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +[*.{coffee,json}] +indent_style = space +indent_size = 2 diff --git a/.travis.yml b/.travis.yml index 3a85896..e8e09d4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,45 @@ -language: objective-c +## Project specific config ### +language: generic + env: - - APM_TEST_PACKAGES="vim-mode" + global: + - APM_TEST_PACKAGES="vim-mode-plus" + - ATOM_LINT_WITH_BUNDLED_NODE="true" + + matrix: + - ATOM_CHANNEL=stable + - ATOM_CHANNEL=beta + +os: + - linux + +dist: trusty + +### Generic setup follows ### +script: + - curl -s -O https://raw.githubusercontent.com/atom/ci/master/build-package.sh + - chmod u+x build-package.sh + - ./build-package.sh notifications: email: on_success: never on_failure: change -script: 'curl -s https://raw.githubusercontent.com/atom/ci/master/build-package.sh | sh' +branches: + only: + - master git: depth: 10 + +sudo: false + +addons: + apt: + packages: + - build-essential + - git + - libgnome-keyring-dev + - libsecret-1-dev + - fakeroot diff --git a/CHANGELOG.md b/CHANGELOG.md index 78d1819..30a0532 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,36 @@ +## (unpublished) + +* Fix `:enew` not working ([#215](https://github.com/lloeki/ex-mode/pull/215)) + +## 0.18.0 + +* Add Gdefault support ([#191](https://github.com/lloeki/ex-mode/pull/191)) +* Add :sort command ([#190](https://github.com/lloeki/ex-mode/pull/190)) + +## 0.17.0 + +* Add support for Atom 1.19 ([#185](https://github.com/lloeki/ex-mode/pull/185)) +* Added support for canceling ex-mode with Ctrl-C ([#186](https://github.com/lloeki/ex-mode/pull/186)) + +## 0.16.0 + +* Support for Atom 1.18 and 1.19 ([#184](https://github.com/lloeki/ex-mode/pull/184)) + +## 0.15.0 +* `vim-mode-plus` support! + - Add keybinding (@jmarianer) + - Support `:substitute` command (@mkiken) + - Support marks + - Use `vim-mode-plus` in specs + +## 0.14.0 +* Support `:tabonly` (@jmarianer) +* Fix `:x` closing Atom instead of the current pane + +## 0.13.1 +* Limit addresses to the last line +* Fix autocompleting a non existent directory (@mcnicholls) + ## 0.13.0 * Added basic support for visual marks (e.g. `:'<,'>s/foo/bar`) * Added `smartcase` option to `:set` diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 58bcc45..ea4a068 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ ex-mode for Atom's vim-mode ## Use -Install both [vim-mode](https://github.com/atom/vim-mode) and ex-mode. Type `:` in command mode. Enter `w` or `write`. +Install both [vim-mode-plus](https://github.com/t9md/atom-vim-mode-plus) (or +the deprecated `vim-mode`) and ex-mode. Type `:` in command mode. Enter `w` or +`write`. ## Extend @@ -28,7 +30,34 @@ atom.packages.onDidActivatePackage (pack) -> Ex.registerAlias 'Wq', 'wq' ``` -See `lib/ex.coffee` for some examples commands. Contributions are very welcome! +## Existing commands + +This is the baseline list of commands supported in `ex-mode`. + +| Command | Operation | +| --------------------------------------- | ---------------------------------- | +| `q/quit/tabc/tabclose` | Close active tab | +| `qall/quitall` | Close all tabs | +| `tabe/tabedit/tabnew` | Open new tab | +| `e/edit/tabe/tabedit/tabnew ` | Edit given file | +| `tabn/tabnext` | Go to next tab | +| `tabp/tabprevious` | Go to previous tab | +| `tabo/tabonly` | Close other tabs | +| `w/write` | Save active tab | +| `w/write/saveas ` | Save as | +| `wall/wa` | Save all tabs | +| `sp/split` | Split window | +| `sp/split ` | Open file in split window | +| `s/substitute` | Substitute regular expression in active line | +| `vsp/vsplit` | Vertical split window | +| `vsp/vsplit ` | Open file in vertical split window | +| `delete` | Cut active line | +| `yank` | Copy active line | +| `set ` | Set options | +| `sort` | Sort all lines in file | +| `sort ` | Sort lines in line range | + +See `lib/ex.coffee` for the implementations of these commands. Contributions are very welcome! ## Status diff --git a/keymaps/ex-mode.cson b/keymaps/ex-mode.cson index 5abddd0..a071efd 100644 --- a/keymaps/ex-mode.cson +++ b/keymaps/ex-mode.cson @@ -7,5 +7,10 @@ # For more detailed documentation see # https://atom.io/docs/latest/advanced/keymaps +'atom-text-editor.vim-mode-plus:not(.insert-mode)': + ':': 'ex-mode:open' +'atom-text-editor.ex-mode-editor': + 'ctrl-c': 'ex-mode:close' + 'ctrl-[': 'ex-mode:close' 'atom-text-editor.vim-mode:not(.insert-mode)': ':': 'ex-mode:open' diff --git a/lib/autocomplete.coffee b/lib/autocomplete.coffee index 8dcb910..6775bac 100644 --- a/lib/autocomplete.coffee +++ b/lib/autocomplete.coffee @@ -40,6 +40,7 @@ class AutoComplete if @completions.length == 0 @completions = completeFunc() + complete = '' if @completions.length complete = @completions[@autoCompleteIndex % @completions.length] @autoCompleteIndex++ @@ -48,7 +49,7 @@ class AutoComplete if complete.endsWith('/') && @completions.length == 1 @resetCompletion() - return complete + return complete getCommandCompletion: (command) -> return @filterByPrefix(@commands, command) @@ -63,12 +64,17 @@ class AutoComplete basePath = path.dirname(filePath) baseName = path.basename(filePath) - files = fs.readdirSync(basePath) - - return @filterByPrefix(files, baseName).map((f) => - filePath = path.join(basePath, f) - if fs.lstatSync(filePath).isDirectory() - return command + ' ' + filePath + path.sep - else - return command + ' ' + filePath - ) + try + basePathStat = fs.statSync(basePath) + if basePathStat.isDirectory() + files = fs.readdirSync(basePath) + return @filterByPrefix(files, baseName).map((f) => + filePath = path.join(basePath, f) + if fs.lstatSync(filePath).isDirectory() + return command + ' ' + filePath + path.sep + else + return command + ' ' + filePath + ) + return [] + catch err + return [] diff --git a/lib/command.coffee b/lib/command.coffee index 48e9616..15e74fe 100644 --- a/lib/command.coffee +++ b/lib/command.coffee @@ -14,7 +14,11 @@ class Command addr = row else if str is '$' # Lines are 0-indexed in Atom, but 1-indexed in vim. - addr = @editor.getBuffer().lines.length - 1 + # The two ways of getting length let us support Atom 1.19's new buffer + # implementation (https://github.com/atom/atom/pull/14435) and still + # support 1.18 and below + buffer = @editor.getBuffer() + addr = (buffer.getLineCount?() ? buffer.lines.length) - 1 else if str[0] in ["+", "-"] addr = row + @parseOffset(str) else if not isNaN(str) @@ -22,7 +26,7 @@ class Command else if str[0] is "'" # Parse Mark... unless @vimState? throw new CommandError("Couldn't get access to vim-mode.") - mark = @vimState.marks[str[1]] + mark = @vimState.mark.marks[str[1]] unless mark? throw new CommandError("Mark #{str} not set.") addr = mark.getEndBufferPosition().row @@ -73,7 +77,9 @@ class Command return # Step 4: Address parsing - lastLine = @editor.getBuffer().lines.length - 1 + # see comment in parseAddr about line length + buffer = @editor.getBuffer() + lastLine = (buffer.getLineCount?() ? buffer.lines.length) - 1 if cl[0] is '%' range = [0, lastLine] cl = cl[1..] @@ -123,8 +129,9 @@ class Command address1 += @parseOffset(off1) address1 = 0 if address1 is -1 + address1 = lastLine if address1 > lastLine - if address1 < 0 or address1 > lastLine + if address1 < 0 throw new CommandError('Invalid range') if addr2? @@ -132,7 +139,10 @@ class Command if off2? address2 += @parseOffset(off2) - if address2 < 0 or address2 > lastLine + address2 = 0 if address2 is -1 + address2 = lastLine if address2 > lastLine + + if address2 < 0 throw new CommandError('Invalid range') if address2 < address1 diff --git a/lib/ex-mode.coffee b/lib/ex-mode.coffee index 490003e..d8fc766 100644 --- a/lib/ex-mode.coffee +++ b/lib/ex-mode.coffee @@ -36,6 +36,9 @@ module.exports = ExMode = @vim = vim @globalExState.setVim(vim) + consumeVimModePlus: (vim) -> + this.consumeVim(vim) + config: splitbelow: title: 'Split below' @@ -47,3 +50,13 @@ module.exports = ExMode = description: 'when splitting, split from right' type: 'boolean' default: 'false' + gdefault: + title: 'Gdefault' + description: 'When on, the ":substitute" flag \'g\' is default on' + type: 'boolean' + default: 'false' + onlyCloseBuffers: + title: 'Only close buffers' + description: 'When on, quitall only closes all buffers, not entire Atom instance' + type: 'boolean' + default: 'false' diff --git a/lib/ex-normal-mode-input-element.coffee b/lib/ex-normal-mode-input-element.coffee index 73ca3da..915a5be 100644 --- a/lib/ex-normal-mode-input-element.coffee +++ b/lib/ex-normal-mode-input-element.coffee @@ -15,7 +15,8 @@ class ExCommandModeInputElement extends HTMLDivElement @editorContainer.style.height = "0px" @editorElement = document.createElement "atom-text-editor" - @editorElement.classList.add('editor') + @editorElement.classList.add('editor') # Consider this deprecated! + @editorElement.classList.add('ex-mode-editor') @editorElement.getModel().setMini(true) @editorElement.setAttribute('mini', '') @editorContainer.appendChild(@editorElement) @@ -40,6 +41,7 @@ class ExCommandModeInputElement extends HTMLDivElement atom.commands.add(@editorElement, 'core:confirm', @confirm.bind(this)) atom.commands.add(@editorElement, 'core:cancel', @cancel.bind(this)) + atom.commands.add(@editorElement, 'ex-mode:close', @cancel.bind(this)) atom.commands.add(@editorElement, 'blur', @cancel.bind(this)) backspace: -> diff --git a/lib/ex.coffee b/lib/ex.coffee index 7c9a6fa..b262023 100644 --- a/lib/ex.coffee +++ b/lib/ex.coffee @@ -3,6 +3,7 @@ CommandError = require './command-error' fs = require 'fs-plus' VimOption = require './vim-option' _ = require 'underscore-plus' +atom defer = () -> deferred = {} @@ -17,8 +18,13 @@ trySave = (func) -> deferred = defer() try - func() - deferred.resolve() + response = func() + + if response instanceof Promise + response.then -> + deferred.resolve() + else + deferred.resolve() catch error if error.message.endsWith('is a directory') atom.notifications.addWarning("Unable to save file: #{error.message}") @@ -127,7 +133,11 @@ class Ex atom.workspace.getActivePane().destroyActiveItem() quitall: -> - atom.close() + if !atom.config.get('ex-mode.onlyCloseBuffers') + atom.close() + else + atom.workspace.getTextEditors().forEach (editor) -> + editor.destroy() q: => @quit() @@ -163,6 +173,16 @@ class Ex tabp: => @tabprevious() + tabonly: -> + tabBar = atom.workspace.getPanes()[0] + tabBarElement = atom.views.getView(tabBar).querySelector(".tab-bar") + tabBarElement.querySelector(".right-clicked") && tabBarElement.querySelector(".right-clicked").classList.remove("right-clicked") + tabBarElement.querySelector(".active").classList.add("right-clicked") + atom.commands.dispatch(tabBarElement, 'tabs:close-other-tabs') + tabBarElement.querySelector(".active").classList.remove("right-clicked") + + tabo: => @tabonly() + edit: ({ range, args, editor }) -> filePath = args.trim() if filePath[0] is '!' @@ -191,9 +211,7 @@ class Ex e: (args) => @edit(args) enew: -> - buffer = atom.workspace.getActiveTextEditor().buffer - buffer.setPath(undefined) - buffer.load() + atom.workspace.open() write: ({ range, args, editor, saveas }) -> saveas ?= false @@ -211,29 +229,30 @@ class Ex deferred = defer() editor = atom.workspace.getActiveTextEditor() - saved = false + + # Case 1; path is provided if filePath.length isnt 0 - fullPath = getFullPath(filePath) - if editor.getPath()? and (not fullPath? or editor.getPath() == fullPath) - if saveas - throw new CommandError("Argument required") - else - # Use editor.save when no path is given or the path to the file is given - trySave(-> editor.save()).then(deferred.resolve) - saved = true - else if not fullPath? - fullPath = atom.showSaveDialogSync() + fullPath = getFullPath filePath - if not saved and fullPath? - if not force and fs.existsSync(fullPath) - throw new CommandError("File exists (add ! to override)") - if saveas or editor.getFileName() == null - editor = atom.workspace.getActiveTextEditor() - trySave(-> editor.saveAs(fullPath, editor)).then(deferred.resolve) - else - trySave(-> saveAs(fullPath, editor)).then(deferred.resolve) + # Only write when it does not exist or we have a force flag set. + if force or not fs.existsSync(fullPath) + editor.saveAs(fullPath) + return deferred.promise - deferred.promise + throw new CommandError("File exists (add ! to override)") + + # Case 2; no path provided, call editor save. + editor = atom.workspace.getActiveTextEditor() + + # Does the current buffer exist? + if editor.getPath()? and fs.existsSync(editor.getPath()) + trySave(-> editor.save()).then(deferred.promise) + else + # Cant see what the better API is but Pane.saveActiveItemAs() is the only call + # I could find that states it will ask the user. + trySave(-> atom.workspace.getActivePane().saveActiveItemAs()).then(deferred.promise) + + return deferred.promise wall: -> atom.workspace.saveAll() @@ -242,7 +261,7 @@ class Ex @write(args) wq: (args) => - @write(args).then => @quit() + @write(args).then(=> @quit()) wa: => @wall() @@ -266,6 +285,7 @@ class Ex xit: (args) => @wq(args) + x: (args) => @xit(args) split: ({ range, args }) -> args = args.trim() @@ -324,16 +344,30 @@ class Ex [pattern, substition, flags] = parsed if pattern is '' - pattern = vimState.getSearchHistoryItem() + if vimState.getSearchHistoryItem? + # vim-mode + pattern = vimState.getSearchHistoryItem() + else if vimState.searchHistory? + # vim-mode-plus + pattern = vimState.searchHistory.get('prev') + if not pattern? atom.beep() throw new CommandError('No previous regular expression') else - vimState.pushSearchHistory(pattern) + if vimState.pushSearchHistory? + # vim-mode + vimState.pushSearchHistory(pattern) + else if vimState.searchHistory? + # vim-mode-plus + vimState.searchHistory.save(pattern) try flagsObj = {} flags.split('').forEach((flag) -> flagsObj[flag] = true) + # gdefault option + if atom.config.get('ex-mode.gdefault') + flagsObj.g = !flagsObj.g patternRE = getSearchTerm(pattern, flagsObj) catch e if e.message.indexOf('Invalid flags supplied to RegExp constructor') is 0 @@ -415,4 +449,24 @@ class Ex throw new CommandError("No such option: #{option}") optionProcessor() + sort: ({ range }) => + editor = atom.workspace.getActiveTextEditor() + sortingRange = [[]] + + # If no range is provided, the entire file should be sorted. + isMultiLine = range[1] - range[0] > 1 + if isMultiLine + sortingRange = [[range[0], 0], [range[1] + 1, 0]] + else + sortingRange = [[0, 0], [editor.getLastBufferRow(), 0]] + + # Store every bufferedRow string in an array. + textLines = [] + for lineIndex in [sortingRange[0][0]..sortingRange[1][0] - 1] + textLines.push(editor.lineTextForBufferRow(lineIndex)) + + # Sort the array and join them together with newlines for writing back to the file. + sortedText = _.sortBy(textLines).join('\n') + '\n' + editor.buffer.setTextInRange(sortingRange, sortedText) + module.exports = Ex diff --git a/lib/vim-option.coffee b/lib/vim-option.coffee index ddf4688..223395f 100644 --- a/lib/vim-option.coffee +++ b/lib/vim-option.coffee @@ -56,4 +56,10 @@ class VimOption noscs: => @nosmartcase() + gdefault: => + atom.config.set("ex-mode.gdefault", true) + + nogdefault: => + atom.config.set("ex-mode.gdefault", false) + module.exports = VimOption diff --git a/package.json b/package.json index 8db31b3..cda6de9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ex-mode", "main": "./lib/ex-mode", - "version": "0.13.0", + "version": "0.18.0", "description": "Ex for Atom's vim-mode", "activationCommands": { "atom-workspace": "ex-mode:open" @@ -23,6 +23,11 @@ "versions": { "^0.1.0": "consumeVim" } + }, + "vim-mode-plus": { + "versions": { + "^0.1.0": "consumeVimModePlus" + } } }, "providedServices": { diff --git a/spec/autocomplete-spec.coffee b/spec/autocomplete-spec.coffee index 49b5a1e..000e9c2 100644 --- a/spec/autocomplete-spec.coffee +++ b/spec/autocomplete-spec.coffee @@ -10,6 +10,7 @@ describe "autocomplete functionality", -> beforeEach -> @autoComplete = new AutoComplete(['taba', 'tabb', 'tabc']) @testDir = path.join(os.tmpdir(), "atom-ex-mode-spec-#{uuid.v4()}") + @nonExistentTestDir = path.join(os.tmpdir(), "atom-ex-mode-spec-#{uuid.v4()}") @testFile1 = path.join(@testDir, "atom-ex-testfile-a.txt") @testFile2 = path.join(@testDir, "atom-ex-testfile-b.txt") @@ -80,3 +81,20 @@ describe "autocomplete functionality", -> it "lists files once", -> expect(@autoComplete.getFilePathCompletion.callCount).toBe(1) + + describe "autocomplete non existent directory", -> + beforeEach -> + @completed = @autoComplete.getAutocomplete('tabe ' + @nonExistentTestDir) + + it "returns no completions", -> + expected = ''; + expect(@completed).toEqual(expected) + + describe "autocomplete existing file as directory", -> + beforeEach -> + filePath = @testFile1 + path.sep + @completed = @autoComplete.getAutocomplete('tabe ' + filePath) + + it "returns no completions", -> + expected = ''; + expect(@completed).toEqual(expected) diff --git a/spec/ex-commands-spec.coffee b/spec/ex-commands-spec.coffee index bfee143..29f211e 100644 --- a/spec/ex-commands-spec.coffee +++ b/spec/ex-commands-spec.coffee @@ -11,7 +11,7 @@ describe "the commands", -> [editor, editorElement, vimState, exState, dir, dir2] = [] projectPath = (fileName) -> path.join(dir, fileName) beforeEach -> - vimMode = atom.packages.loadPackage('vim-mode') + vimMode = atom.packages.loadPackage('vim-mode-plus') exMode = atom.packages.loadPackage('ex-mode') waitsForPromise -> activationPromise = exMode.activate() @@ -42,7 +42,6 @@ describe "the commands", -> editor = editorElement.getModel() vimState = vimMode.mainModule.getEditorState(editor) exState = exMode.mainModule.exStates.get(editor) - vimState.activateNormalMode() vimState.resetNormalMode() editor.setText("abc\ndef\nabc\ndef") @@ -90,6 +89,33 @@ describe "the commands", -> submitNormalModeInputText '-2' expect(editor.getCursorBufferPosition()).toEqual [0, 0] + it "limits to the last line", -> + openEx() + submitNormalModeInputText '10' + expect(editor.getCursorBufferPosition()).toEqual [3, 0] + editor.setCursorBufferPosition([0, 0]) + + openEx() + submitNormalModeInputText '3,10' + expect(editor.getCursorBufferPosition()).toEqual [3, 0] + editor.setCursorBufferPosition([0, 0]) + + openEx() + submitNormalModeInputText '$+1000' + expect(editor.getCursorBufferPosition()).toEqual [3, 0] + editor.setCursorBufferPosition([0, 0]) + + it "goes to the first line with address 0", -> + editor.setCursorBufferPosition([2, 0]) + openEx() + submitNormalModeInputText '0' + expect(editor.getCursorBufferPosition()).toEqual [0, 0] + + editor.setCursorBufferPosition([2, 0]) + openEx() + submitNormalModeInputText '0,0' + expect(editor.getCursorBufferPosition()).toEqual [0, 0] + it "doesn't move when the address is the current line", -> openEx() submitNormalModeInputText '.' @@ -458,6 +484,13 @@ describe "the commands", -> submitNormalModeInputText('xit') expect(Ex.wq).toHaveBeenCalled() + describe ":x", -> + it "acts as an alias to :xit", -> + spyOn(Ex, 'xit') + openEx() + submitNormalModeInputText('x') + expect(Ex.xit).toHaveBeenCalled() + describe ":wqall", -> it "calls :wall, then :quitall", -> spyOn(Ex, 'wall') @@ -682,6 +715,17 @@ describe "the commands", -> submitNormalModeInputText(':%substitute/abc/ghi/ig') expect(editor.getText()).toEqual('ghiaghi\ndefdDEF\nghiaghi') + it "set gdefault option", -> + openEx() + atom.config.set('ex-mode.gdefault', true) + submitNormalModeInputText(':substitute/a/x') + expect(editor.getText()).toEqual('xbcxABC\ndefdDEF\nabcaABC') + + atom.commands.dispatch(editorElement, 'ex-mode:open') + atom.config.set('ex-mode.gdefault', true) + submitNormalModeInputText(':substitute/a/x/g') + expect(editor.getText()).toEqual('xbcaABC\ndefdDEF\nabcaABC') + describe ":yank", -> beforeEach -> editor.setText('abc\ndef\nghi\njkl') @@ -911,6 +955,14 @@ describe "the commands", -> submitNormalModeInputText(':set nosmartcase') expect(atom.config.get('vim-mode.useSmartcaseForSearch')).toBe(false) + it "sets (no)gdefault", -> + openEx() + submitNormalModeInputText(':set gdefault') + expect(atom.config.get('ex-mode.gdefault')).toBe(true) + atom.commands.dispatch(editorElement, 'ex-mode:open') + submitNormalModeInputText(':set nogdefault') + expect(atom.config.get('ex-mode.gdefault')).toBe(false) + describe "aliases", -> it "calls the aliased function without arguments", -> ExClass.registerAlias('W', 'w') @@ -950,3 +1002,18 @@ describe "the commands", -> expect(calls.length).toEqual 2 expect(calls[0].args[0].range).toEqual [0, 2] expect(calls[1].args[0].range).toEqual [3, 3] + + describe ':sort', -> + beforeEach -> + editor.setText('ghi\nabc\njkl\ndef\n142\nzzz\n91xfds9\n') + editor.setCursorBufferPosition([0, 0]) + + it "sorts entire file if range is not multi-line", -> + openEx() + submitNormalModeInputText('sort') + expect(editor.getText()).toEqual('142\n91xfds9\nabc\ndef\nghi\njkl\nzzz\n') + + it "sorts specific range if range is multi-line", -> + openEx() + submitNormalModeInputText('2,4sort') + expect(editor.getText()).toEqual('ghi\nabc\ndef\njkl\n142\nzzz\n91xfds9\n') diff --git a/spec/ex-input-spec.coffee b/spec/ex-input-spec.coffee index 3d076b9..cd7fbd0 100644 --- a/spec/ex-input-spec.coffee +++ b/spec/ex-input-spec.coffee @@ -2,7 +2,7 @@ helpers = require './spec-helper' describe "the input element", -> [editor, editorElement, vimState, exState] = [] beforeEach -> - vimMode = atom.packages.loadPackage('vim-mode') + vimMode = atom.packages.loadPackage('vim-mode-plus') exMode = atom.packages.loadPackage('ex-mode') waitsForPromise -> activationPromise = exMode.activate() @@ -26,7 +26,6 @@ describe "the input element", -> atom.commands.dispatch(getCommandEditor(), "core:cancel") vimState = vimMode.mainModule.getEditorState(editor) exState = exMode.mainModule.exStates.get(editor) - vimState.activateNormalMode() vimState.resetNormalMode() editor.setText("abc\ndef\nabc\ndef")