diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 33224b4..0000000 --- a/.editorconfig +++ /dev/null @@ -1,9 +0,0 @@ -root = true - -[*] -end_of_line = lf -insert_final_newline = true - -[*.{coffee,json}] -indent_style = space -indent_size = 2 diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 2231100..31132f0 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -3,8 +3,13 @@ Current Maintainers: - [@jazzpi](https://github.com/jazzpi) +- [@LongLiveCHIEF](https://github.com/LongLiveCHIEF) -This project is accepting new maintainers. Interested parties should open a new issue titled `New Maintainer Request`. +This project is accepting new maintainers. Interested parties should + +1. Open a new issue, titled: `New Maintainer Request` +2. Assign the issue to [@lloeki](https://github.com/lloeki) +3. The last line of your request should `/cc @jazzpi @LongLiveCHIEF` ## Pull Requests @@ -14,6 +19,7 @@ close when your PR is merged to speed up the merge of your PR, please contribute these tests - *note*: if you submit a PR but are unsure how to write tests, please begin your PR title with `[needs tests]` - Please use the [pull request template](PULL_REQUEST_TEMPLATE.md) as a guide for submitting your PR. +- Include a `/cc` for @LongLiveCHIEF and @jazzpi the current maintainers ## Issues diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 1b3c90e..ae22d79 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,15 +1,14 @@ Fixes # . Changes Proposed in this Pull Request: - -- foo -- bar -- baz +- +- +- I have written tests for: -[](Remove the `[]()` to uncomment the appropriate lines) +- [ ] New features introduced +- [ ] Bugs fixed +- [ ] Neither (I'm just enhancing tests!) -[](- New features introduced) -[](- Bugs fixed) -[](- Neither (I'm just enhancing tests!)) +@jazzpi @LongLiveCHIEF diff --git a/.travis.yml b/.travis.yml index e8e09d4..3a85896 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,45 +1,13 @@ -## Project specific config ### -language: generic - +language: objective-c env: - 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 + - APM_TEST_PACKAGES="vim-mode" notifications: email: on_success: never on_failure: change -branches: - only: - - master +script: 'curl -s https://raw.githubusercontent.com/atom/ci/master/build-package.sh | sh' 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 30a0532..334f338 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,51 +1,3 @@ -## (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` -* Fixed using marks as addresses (e.g. `:'a,.delete`) -* Fixed search not working without a closing delimiter (e.g. `:/foo`) - -## 0.12.0 -* Added file and command autocomplete (@stuartquin) -* Added `splitbelow` and `splitright` options to `:set` -* Fixed the editor not updating when saving a new file with `:w` or `:saveas` - -## 0.11.0 - -* Stop using non-standard Promise.defer (fixes issue with `:w`) (@AsaAyers) - ## 0.9.0 * Added support for yank commands, ex `:1,10y` (@posgarou) diff --git a/README.md b/README.md old mode 100755 new mode 100644 index ea4a068..58bcc45 --- a/README.md +++ b/README.md @@ -4,9 +4,7 @@ ex-mode for Atom's vim-mode ## Use -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`. +Install both [vim-mode](https://github.com/atom/vim-mode) and ex-mode. Type `:` in command mode. Enter `w` or `write`. ## Extend @@ -30,34 +28,7 @@ atom.packages.onDidActivatePackage (pack) -> Ex.registerAlias 'Wq', 'wq' ``` -## 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! +See `lib/ex.coffee` for some examples commands. Contributions are very welcome! ## Status diff --git a/keymaps/ex-mode.cson b/keymaps/ex-mode.cson index a071efd..5abddd0 100644 --- a/keymaps/ex-mode.cson +++ b/keymaps/ex-mode.cson @@ -7,10 +7,5 @@ # 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 deleted file mode 100644 index 6775bac..0000000 --- a/lib/autocomplete.coffee +++ /dev/null @@ -1,80 +0,0 @@ -fs = require 'fs' -path = require 'path' -os = require 'os' -Ex = require './ex' - -module.exports = -class AutoComplete - constructor: (commands) -> - @commands = commands - @resetCompletion() - - resetCompletion: () -> - @autoCompleteIndex = 0 - @autoCompleteText = null - @completions = [] - - expandTilde: (filePath) -> - if filePath.charAt(0) == '~' - return os.homedir() + filePath.slice(1) - else - return filePath - - getAutocomplete: (text) -> - if !@autoCompleteText - @autoCompleteText = text - - parts = @autoCompleteText.split(' ') - cmd = parts[0] - - if parts.length > 1 - filePath = parts.slice(1).join(' ') - return @getCompletion(() => @getFilePathCompletion(cmd, filePath)) - else - return @getCompletion(() => @getCommandCompletion(cmd)) - - filterByPrefix: (commands, prefix) -> - commands.sort().filter((f) => f.startsWith(prefix)) - - getCompletion: (completeFunc) -> - if @completions.length == 0 - @completions = completeFunc() - - complete = '' - if @completions.length - complete = @completions[@autoCompleteIndex % @completions.length] - @autoCompleteIndex++ - - # Only one result so lets return this directory - if complete.endsWith('/') && @completions.length == 1 - @resetCompletion() - - return complete - - getCommandCompletion: (command) -> - return @filterByPrefix(@commands, command) - - getFilePathCompletion: (command, filePath) -> - filePath = @expandTilde(filePath) - - if filePath.endsWith(path.sep) - basePath = path.dirname(filePath + '.') - baseName = '' - else - basePath = path.dirname(filePath) - baseName = path.basename(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 15e74fe..172cefb 100644 --- a/lib/command.coffee +++ b/lib/command.coffee @@ -5,47 +5,33 @@ CommandError = require './command-error' class Command constructor: (@editor, @exState) -> - @selections = @exState.getSelections() - @viewModel = new ExViewModel(@, Object.keys(@selections).length > 0) + @viewModel = new ExViewModel(@) - parseAddr: (str, cursor) -> - row = cursor.getBufferRow() + parseAddr: (str, curPos) -> if str is '.' - addr = row + addr = curPos.row else if str is '$' # Lines are 0-indexed in Atom, but 1-indexed in vim. - # 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 + addr = @editor.getBuffer().lines.length - 1 else if str[0] in ["+", "-"] - addr = row + @parseOffset(str) + addr = curPos.row + @parseOffset(str) else if not isNaN(str) addr = parseInt(str) - 1 else if str[0] is "'" # Parse Mark... unless @vimState? throw new CommandError("Couldn't get access to vim-mode.") - mark = @vimState.mark.marks[str[1]] + mark = @vimState.marks[str[1]] unless mark? throw new CommandError("Mark #{str} not set.") - addr = mark.getEndBufferPosition().row + addr = mark.bufferMarker.range.end.row else if str[0] is "/" - str = str[1...] - if str[str.length-1] is "/" - str = str[...-1] - addr = Find.scanEditor(str, @editor, cursor.getCurrentLineBufferRange().end)[0] - unless addr? - throw new CommandError("Pattern not found: #{str}") - addr = addr.start.row - else if str[0] is "?" - str = str[1...] - if str[str.length-1] is "?" - str = str[...-1] - addr = Find.scanEditor(str, @editor, cursor.getCurrentLineBufferRange().start, true)[0] + addr = Find.findNextInBuffer(@editor.buffer, curPos, str[1...-1]) + unless addr? + throw new CommandError("Pattern not found: #{str[1...-1]}") + else if str[0] is "?" + addr = Find.findPreviousInBuffer(@editor.buffer, curPos, str[1...-1]) unless addr? throw new CommandError("Pattern not found: #{str[1...-1]}") - addr = addr.start.row return addr @@ -77,9 +63,7 @@ class Command return # Step 4: Address parsing - # see comment in parseAddr about line length - buffer = @editor.getBuffer() - lastLine = (buffer.getLineCount?() ? buffer.lines.length) - 1 + lastLine = @editor.getBuffer().lines.length - 1 if cl[0] is '%' range = [0, lastLine] cl = cl[1..] @@ -91,8 +75,8 @@ class Command \$| # Last line \d+| # n-th line '[\[\]<>'`"^.(){}a-zA-Z]| # Marks - /.*?(?:[^\\]/|$)| # Regex - \?.*?(?:[^\\]\?|$)| # Backwards search + /.*?[^\\]/| # Regex + \?.*?[^\\]\?| # Backwards search [+-]\d* # Current line +/- a number of lines )((?:\s*[+-]\d*)*) # Line offset )? @@ -111,45 +95,34 @@ class Command [match, addr1, off1, addr2, off2] = cl.match(addrPattern) - cursor = @editor.getLastCursor() + curPos = @editor.getCursorBufferPosition() - # Special case: run command on selection. This can't be handled by simply - # parsing the mark since vim-mode doesn't set it (and it would be fairly - # useless with multiple selections) - if addr1 is "'<" and addr2 is "'>" - runOverSelections = true + if addr1? + address1 = @parseAddr(addr1, curPos) else - runOverSelections = false - if addr1? - address1 = @parseAddr(addr1, cursor) - else - # If no addr1 is given (,+3), assume it is '.' - address1 = cursor.getBufferRow() - if off1? - address1 += @parseOffset(off1) + # If no addr1 is given (,+3), assume it is '.' + address1 = curPos.row + if off1? + address1 += @parseOffset(off1) - address1 = 0 if address1 is -1 - address1 = lastLine if address1 > lastLine + address1 = 0 if address1 is -1 - if address1 < 0 - throw new CommandError('Invalid range') + if address1 < 0 or address1 > lastLine + throw new CommandError('Invalid range') - if addr2? - address2 = @parseAddr(addr2, cursor) - if off2? - address2 += @parseOffset(off2) + if addr2? + address2 = @parseAddr(addr2, curPos) + if off2? + address2 += @parseOffset(off2) - address2 = 0 if address2 is -1 - address2 = lastLine if address2 > lastLine + if address2 < 0 or address2 > lastLine + throw new CommandError('Invalid range') - if address2 < 0 - throw new CommandError('Invalid range') - - if address2 < address1 - throw new CommandError('Backwards range given') + if address2 < address1 + throw new CommandError('Backwards range given') range = [address1, if address2? then address2 else address1] - cl = cl[match?.length..] + cl = cl[match?.length..] # Step 5: Leading blanks are ignored cl = cl.trimLeft() @@ -176,7 +149,9 @@ class Command [m, command, args] = cl.match(/^(\w+)(.*)/) # If the command matches an existing one exactly, execute that one - unless (func = Ex.singleton()[command])? + if (func = Ex.singleton()[command])? + func({ range, args, @vimState, @exState, @editor }) + else # Step 8: Match command against existing commands matching = (name for name, val of Ex.singleton() when \ name.indexOf(command) is 0) @@ -186,16 +161,9 @@ class Command command = matching[0] func = Ex.singleton()[command] - - if func? - if runOverSelections - for id, selection of @selections - bufferRange = selection.getBufferRange() - range = [bufferRange.start.row, bufferRange.end.row] - func({ range, args, @vimState, @exState, @editor }) - else + if func? func({ range, args, @vimState, @exState, @editor }) - else - throw new CommandError("Not an editor command: #{input.characters}") + else + throw new CommandError("Not an editor command: #{input.characters}") module.exports = Command diff --git a/lib/ex-mode.coffee b/lib/ex-mode.coffee index d8fc766..490003e 100644 --- a/lib/ex-mode.coffee +++ b/lib/ex-mode.coffee @@ -36,9 +36,6 @@ module.exports = ExMode = @vim = vim @globalExState.setVim(vim) - consumeVimModePlus: (vim) -> - this.consumeVim(vim) - config: splitbelow: title: 'Split below' @@ -50,13 +47,3 @@ 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 915a5be..73ca3da 100644 --- a/lib/ex-normal-mode-input-element.coffee +++ b/lib/ex-normal-mode-input-element.coffee @@ -15,8 +15,7 @@ class ExCommandModeInputElement extends HTMLDivElement @editorContainer.style.height = "0px" @editorElement = document.createElement "atom-text-editor" - @editorElement.classList.add('editor') # Consider this deprecated! - @editorElement.classList.add('ex-mode-editor') + @editorElement.classList.add('editor') @editorElement.getModel().setMini(true) @editorElement.setAttribute('mini', '') @editorContainer.appendChild(@editorElement) @@ -41,7 +40,6 @@ 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-state.coffee b/lib/ex-state.coffee index 89fbeeb..7c0f37c 100644 --- a/lib/ex-state.coffee +++ b/lib/ex-state.coffee @@ -60,13 +60,4 @@ class ExState @clearOpStack() @emitter.emit('processed-op-stack') - # Returns all non-empty selections - getSelections: -> - filtered = {} - for id, selection of @editor.getSelections() - unless selection.isEmpty() - filtered[id] = selection - - return filtered - module.exports = ExState diff --git a/lib/ex-view-model.coffee b/lib/ex-view-model.coffee index 08dd9f6..0c99be9 100644 --- a/lib/ex-view-model.coffee +++ b/lib/ex-view-model.coffee @@ -1,41 +1,20 @@ {ViewModel, Input} = require './view-model' -AutoComplete = require './autocomplete' -Ex = require './ex' module.exports = class ExViewModel extends ViewModel - constructor: (@exCommand, withSelection) -> + constructor: (@exCommand) -> super(@exCommand, class: 'command') @historyIndex = -1 - if withSelection - @view.editorElement.getModel().setText("'<,'>") - - @view.editorElement.addEventListener('keydown', @tabAutocomplete) atom.commands.add(@view.editorElement, 'core:move-up', @increaseHistoryEx) atom.commands.add(@view.editorElement, 'core:move-down', @decreaseHistoryEx) - @autoComplete = new AutoComplete(Ex.getCommands()) - restoreHistory: (index) -> @view.editorElement.getModel().setText(@history(index).value) history: (index) -> @exState.getExHistoryItem(index) - tabAutocomplete: (event) => - if event.keyCode == 9 - event.stopPropagation() - event.preventDefault() - - completed = @autoComplete.getAutocomplete(@view.editorElement.getModel().getText()) - if completed - @view.editorElement.getModel().setText(completed) - - return false - else - @autoComplete.resetCompletion() - increaseHistoryEx: => if @history(@historyIndex + 1)? @historyIndex += 1 diff --git a/lib/ex.coffee b/lib/ex.coffee index b262023..c91beb7 100644 --- a/lib/ex.coffee +++ b/lib/ex.coffee @@ -3,28 +3,13 @@ CommandError = require './command-error' fs = require 'fs-plus' VimOption = require './vim-option' _ = require 'underscore-plus' -atom - -defer = () -> - deferred = {} - deferred.promise = new Promise((resolve, reject) -> - deferred.resolve = resolve - deferred.reject = reject - ) - return deferred - trySave = (func) -> - deferred = defer() + deferred = Promise.defer() try - response = func() - - if response instanceof Promise - response.then -> - deferred.resolve() - else - deferred.resolve() + func() + deferred.resolve() catch error if error.message.endsWith('is a directory') atom.notifications.addWarning("Unable to save file: #{error.message}") @@ -124,20 +109,11 @@ class Ex @registerAlias: (alias, name) => @singleton()[alias] = (args) => @singleton()[name](args) - @getCommands: () => - Object.keys(Ex.singleton()).concat(Object.keys(Ex.prototype)).filter((cmd, index, list) -> - list.indexOf(cmd) == index - ) - quit: -> atom.workspace.getActivePane().destroyActiveItem() quitall: -> - if !atom.config.get('ex-mode.onlyCloseBuffers') - atom.close() - else - atom.workspace.getTextEditors().forEach (editor) -> - editor.destroy() + atom.close() q: => @quit() @@ -173,16 +149,6 @@ 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 '!' @@ -211,7 +177,9 @@ class Ex e: (args) => @edit(args) enew: -> - atom.workspace.open() + buffer = atom.workspace.getActiveTextEditor().buffer + buffer.setPath(undefined) + buffer.load() write: ({ range, args, editor, saveas }) -> saveas ?= false @@ -226,33 +194,32 @@ class Ex if filePath.indexOf(' ') isnt -1 throw new CommandError('Only one file name allowed') - deferred = defer() + deferred = Promise.defer() editor = atom.workspace.getActiveTextEditor() - - # Case 1; path is provided + saved = false if filePath.length isnt 0 - fullPath = getFullPath filePath + 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() - # 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 + if not saved and fullPath? + if not force and fs.existsSync(fullPath) + throw new CommandError("File exists (add ! to override)") + if saveas + editor = atom.workspace.getActiveTextEditor() + trySave(-> editor.saveAs(fullPath, editor)).then(deferred.resolve) + else + trySave(-> saveAs(fullPath, editor)).then(deferred.resolve) - 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 + deferred.promise wall: -> atom.workspace.saveAll() @@ -261,7 +228,7 @@ class Ex @write(args) wq: (args) => - @write(args).then(=> @quit()) + @write(args).then => @quit() wa: => @wall() @@ -285,7 +252,6 @@ class Ex xit: (args) => @wq(args) - x: (args) => @xit(args) split: ({ range, args }) -> args = args.trim() @@ -344,30 +310,16 @@ class Ex [pattern, substition, flags] = parsed if pattern is '' - if vimState.getSearchHistoryItem? - # vim-mode - pattern = vimState.getSearchHistoryItem() - else if vimState.searchHistory? - # vim-mode-plus - pattern = vimState.searchHistory.get('prev') - + pattern = vimState.getSearchHistoryItem() if not pattern? atom.beep() throw new CommandError('No previous regular expression') else - if vimState.pushSearchHistory? - # vim-mode - vimState.pushSearchHistory(pattern) - else if vimState.searchHistory? - # vim-mode-plus - vimState.searchHistory.save(pattern) + vimState.pushSearchHistory(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 @@ -449,24 +401,4 @@ 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/find.coffee b/lib/find.coffee index 60e98b8..53dffc5 100644 --- a/lib/find.coffee +++ b/lib/find.coffee @@ -1,40 +1,3 @@ -_ = require 'underscore-plus' - -getSearchTerm = (term, modifiers = {'g': true}) -> - - escaped = false - hasc = false - hasC = false - term_ = term - term = '' - for char in term_ - if char is '\\' and not escaped - escaped = true - term += char - else - if char is 'c' and escaped - hasc = true - term = term[...-1] - else if char is 'C' and escaped - hasC = true - term = term[...-1] - else if char isnt '\\' - term += char - escaped = false - - if hasC - modifiers['i'] = false - if (not hasC and not term.match('[A-Z]') and \ - atom.config.get("vim-mode:useSmartcaseForSearch")) or hasc - modifiers['i'] = true - - modFlags = Object.keys(modifiers).filter((key) -> modifiers[key]).join('') - - try - new RegExp(term, modFlags) - catch - new RegExp(_.escapeRegExp(term), modFlags) - module.exports = { findInBuffer : (buffer, pattern) -> found = [] @@ -60,26 +23,4 @@ module.exports = { return found[found.length - 1].start.row else return null - - # Returns an array of ranges of all occurences of `term` in `editor`. - # The array is sorted so that the first occurences after the cursor come - # first (and the search wraps around). If `reverse` is true, the array is - # reversed so that the first occurence before the cursor comes first. - scanEditor: (term, editor, position, reverse = false) -> - [rangesBefore, rangesAfter] = [[], []] - editor.scan getSearchTerm(term), ({range}) -> - if reverse - isBefore = range.start.compare(position) < 0 - else - isBefore = range.start.compare(position) <= 0 - - if isBefore - rangesBefore.push(range) - else - rangesAfter.push(range) - - if reverse - rangesAfter.concat(rangesBefore).reverse() - else - rangesAfter.concat(rangesBefore) } diff --git a/lib/vim-option.coffee b/lib/vim-option.coffee index 223395f..2ee056c 100644 --- a/lib/vim-option.coffee +++ b/lib/vim-option.coffee @@ -20,46 +20,4 @@ class VimOption nonu: => @nonumber() - splitright: => - atom.config.set("ex-mode.splitright", true) - - spr: => - @splitright() - - nosplitright: => - atom.config.set("ex-mode.splitright", false) - - nospr: => - @nosplitright() - - splitbelow: => - atom.config.set("ex-mode.splitbelow", true) - - sb: => - @splitbelow() - - nosplitbelow: => - atom.config.set("ex-mode.splitbelow", false) - - nosb: => - @nosplitbelow() - - smartcase: => - atom.config.set("vim-mode.useSmartcaseForSearch", true) - - scs: => - @smartcase() - - nosmartcase: => - atom.config.set("vim-mode.useSmartcaseForSearch", false) - - 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 cda6de9..05fa7bc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ex-mode", "main": "./lib/ex-mode", - "version": "0.18.0", + "version": "0.10.0", "description": "Ex for Atom's vim-mode", "activationCommands": { "atom-workspace": "ex-mode:open" @@ -23,11 +23,6 @@ "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 deleted file mode 100644 index 000e9c2..0000000 --- a/spec/autocomplete-spec.coffee +++ /dev/null @@ -1,100 +0,0 @@ -fs = require 'fs-plus' -path = require 'path' -os = require 'os' -uuid = require 'node-uuid' - -helpers = require './spec-helper' -AutoComplete = require '../lib/autocomplete' - -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") - - runs => - fs.makeTreeSync(@testDir) - fs.closeSync(fs.openSync(@testFile1, 'w')); - fs.closeSync(fs.openSync(@testFile2, 'w')); - spyOn(@autoComplete, 'resetCompletion').andCallThrough() - spyOn(@autoComplete, 'getFilePathCompletion').andCallThrough() - spyOn(@autoComplete, 'getCommandCompletion').andCallThrough() - - afterEach -> - fs.removeSync(@testDir) - - describe "autocomplete commands", -> - beforeEach -> - @completed = @autoComplete.getAutocomplete('tab') - - it "returns taba", -> - expect(@completed).toEqual('taba') - - it "calls command function", -> - expect(@autoComplete.getCommandCompletion.callCount).toBe(1) - - describe "autocomplete commands, then autoComplete again", -> - beforeEach -> - @completed = @autoComplete.getAutocomplete('tab') - @completed = @autoComplete.getAutocomplete('tab') - - it "returns tabb", -> - expect(@completed).toEqual('tabb') - - it "calls command function", -> - expect(@autoComplete.getCommandCompletion.callCount).toBe(1) - - describe "autocomplete directory", -> - beforeEach -> - filePath = path.join(os.tmpdir(), 'atom-ex-mode-spec-') - @completed = @autoComplete.getAutocomplete('tabe ' + filePath) - - it "returns testDir", -> - expected = 'tabe ' + @testDir + path.sep - expect(@completed).toEqual(expected) - - it "clears autocomplete", -> - expect(@autoComplete.resetCompletion.callCount).toBe(1) - - describe "autocomplete directory, then autocomplete again", -> - beforeEach -> - filePath = path.join(os.tmpdir(), 'atom-ex-mode-spec-') - @completed = @autoComplete.getAutocomplete('tabe ' + filePath) - @completed = @autoComplete.getAutocomplete(@completed) - - it "returns test file 1", -> - expect(@completed).toEqual('tabe ' + @testFile1) - - it "lists files twice", -> - expect(@autoComplete.getFilePathCompletion.callCount).toBe(2) - - describe "autocomplete full directory, then autocomplete again", -> - beforeEach -> - filePath = path.join(@testDir, 'a') - @completed = @autoComplete.getAutocomplete('tabe ' + filePath) - @completed = @autoComplete.getAutocomplete(@completed) - - it "returns test file 2", -> - expect(@completed).toEqual('tabe ' + @testFile2) - - 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 29f211e..08237e3 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-plus') + vimMode = atom.packages.loadPackage('vim-mode') exMode = atom.packages.loadPackage('ex-mode') waitsForPromise -> activationPromise = exMode.activate() @@ -36,12 +36,12 @@ describe "the commands", -> helpers.getEditorElement (element) -> atom.commands.dispatch(element, "ex-mode:open") - atom.commands.dispatch(element.getModel().normalModeInputView.editorElement, - "core:cancel") + keydown('escape') editorElement = element editor = editorElement.getModel() vimState = vimMode.mainModule.getEditorState(editor) exState = exMode.mainModule.exStates.get(editor) + vimState.activateNormalMode() vimState.resetNormalMode() editor.setText("abc\ndef\nabc\ndef") @@ -61,99 +61,6 @@ describe "the commands", -> commandEditor.getModel().setText(text) atom.commands.dispatch(commandEditor, "core:confirm") - openEx = -> - atom.commands.dispatch(editorElement, "ex-mode:open") - - describe "as a motion", -> - beforeEach -> - editor.setCursorBufferPosition([0, 0]) - - it "moves the cursor to a specific line", -> - openEx() - submitNormalModeInputText '2' - - expect(editor.getCursorBufferPosition()).toEqual [1, 0] - - it "moves to the second address", -> - openEx() - submitNormalModeInputText '1,3' - - expect(editor.getCursorBufferPosition()).toEqual [2, 0] - - it "works with offsets", -> - openEx() - submitNormalModeInputText '2+1' - expect(editor.getCursorBufferPosition()).toEqual [2, 0] - - openEx() - 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 '.' - expect(editor.getCursorBufferPosition()).toEqual [0, 0] - - openEx() - submitNormalModeInputText ',' - expect(editor.getCursorBufferPosition()).toEqual [0, 0] - - it "moves to the last line", -> - openEx() - submitNormalModeInputText '$' - expect(editor.getCursorBufferPosition()).toEqual [3, 0] - - it "moves to a mark's line", -> - keydown('l') - keydown('m') - normalModeInputKeydown 'a' - keydown('j') - openEx() - submitNormalModeInputText "'a" - expect(editor.getCursorBufferPosition()).toEqual [0, 0] - - it "moves to a specified search", -> - openEx() - submitNormalModeInputText '/def' - expect(editor.getCursorBufferPosition()).toEqual [1, 0] - - editor.setCursorBufferPosition([2, 0]) - openEx() - submitNormalModeInputText '?def' - expect(editor.getCursorBufferPosition()).toEqual [1, 0] - - editor.setCursorBufferPosition([3, 0]) - openEx() - submitNormalModeInputText '/ef' - expect(editor.getCursorBufferPosition()).toEqual [1, 0] - describe ":write", -> describe "when editing a new file", -> beforeEach -> @@ -161,23 +68,22 @@ describe "the commands", -> it "opens the save dialog", -> spyOn(atom, 'showSaveDialogSync') - openEx() + keydown(':') submitNormalModeInputText('write') expect(atom.showSaveDialogSync).toHaveBeenCalled() it "saves when a path is specified in the save dialog", -> filePath = projectPath('write-from-save-dialog') spyOn(atom, 'showSaveDialogSync').andReturn(filePath) - openEx() + keydown(':') submitNormalModeInputText('write') expect(fs.existsSync(filePath)).toBe(true) expect(fs.readFileSync(filePath, 'utf-8')).toEqual('abc\ndef') - expect(editor.isModified()).toBe(false) it "saves when a path is specified in the save dialog", -> spyOn(atom, 'showSaveDialogSync').andReturn(undefined) spyOn(fs, 'writeFileSync') - openEx() + keydown(':') submitNormalModeInputText('write') expect(fs.writeFileSync.calls.length).toBe(0) @@ -193,7 +99,7 @@ describe "the commands", -> it "saves the file", -> editor.setText('abc') - openEx() + keydown(':') submitNormalModeInputText('write') expect(fs.readFileSync(filePath, 'utf-8')).toEqual('abc') expect(editor.isModified()).toBe(false) @@ -204,7 +110,7 @@ describe "the commands", -> beforeEach -> newPath = path.relative(dir, "#{filePath}.new") editor.getBuffer().setText('abc') - openEx() + keydown(':') afterEach -> submitNormalModeInputText("write #{newPath}") @@ -226,7 +132,7 @@ describe "the commands", -> newPath = path.join('~', newPath) it "throws an error with more than one path", -> - openEx() + keydown(':') submitNormalModeInputText('write path1 path2') expect(atom.notifications.notifications[0].message).toEqual( 'Command error: Only one file name allowed' @@ -243,7 +149,7 @@ describe "the commands", -> fs.removeSync(existsPath) it "throws an error if the file already exists", -> - openEx() + keydown(':') submitNormalModeInputText("write #{existsPath}") expect(atom.notifications.notifications[0].message).toEqual( 'Command error: File exists (add ! to override)' @@ -251,7 +157,7 @@ describe "the commands", -> expect(fs.readFileSync(existsPath, 'utf-8')).toEqual('abc') it "writes if forced with :write!", -> - openEx() + keydown(':') submitNormalModeInputText("write! #{existsPath}") expect(atom.notifications.notifications).toEqual([]) expect(fs.readFileSync(existsPath, 'utf-8')).toEqual('abc\ndef') @@ -259,7 +165,7 @@ describe "the commands", -> describe ":wall", -> it "saves all", -> spyOn(atom.workspace, 'saveAll') - openEx() + keydown(':') submitNormalModeInputText('wall') expect(atom.workspace.saveAll).toHaveBeenCalled() @@ -270,14 +176,14 @@ describe "the commands", -> it "opens the save dialog", -> spyOn(atom, 'showSaveDialogSync') - openEx() + keydown(':') submitNormalModeInputText('saveas') expect(atom.showSaveDialogSync).toHaveBeenCalled() it "saves when a path is specified in the save dialog", -> filePath = projectPath('saveas-from-save-dialog') spyOn(atom, 'showSaveDialogSync').andReturn(filePath) - openEx() + keydown(':') submitNormalModeInputText('saveas') expect(fs.existsSync(filePath)).toBe(true) expect(fs.readFileSync(filePath, 'utf-8')).toEqual('abc\ndef') @@ -285,7 +191,7 @@ describe "the commands", -> it "saves when a path is specified in the save dialog", -> spyOn(atom, 'showSaveDialogSync').andReturn(undefined) spyOn(fs, 'writeFileSync') - openEx() + keydown(':') submitNormalModeInputText('saveas') expect(fs.writeFileSync.calls.length).toBe(0) @@ -301,7 +207,7 @@ describe "the commands", -> it "complains if no path given", -> editor.setText('abc') - openEx() + keydown(':') submitNormalModeInputText('saveas') expect(atom.notifications.notifications[0].message).toEqual( 'Command error: Argument required' @@ -313,7 +219,7 @@ describe "the commands", -> beforeEach -> newPath = path.relative(dir, "#{filePath}.new") editor.getBuffer().setText('abc') - openEx() + keydown(':') afterEach -> submitNormalModeInputText("saveas #{newPath}") @@ -335,7 +241,7 @@ describe "the commands", -> newPath = path.join('~', newPath) it "throws an error with more than one path", -> - openEx() + keydown(':') submitNormalModeInputText('saveas path1 path2') expect(atom.notifications.notifications[0].message).toEqual( 'Command error: Only one file name allowed' @@ -352,7 +258,7 @@ describe "the commands", -> fs.removeSync(existsPath) it "throws an error if the file already exists", -> - openEx() + keydown(':') submitNormalModeInputText("saveas #{existsPath}") expect(atom.notifications.notifications[0].message).toEqual( 'Command error: File exists (add ! to override)' @@ -360,7 +266,7 @@ describe "the commands", -> expect(fs.readFileSync(existsPath, 'utf-8')).toEqual('abc') it "writes if forced with :saveas!", -> - openEx() + keydown(':') submitNormalModeInputText("saveas! #{existsPath}") expect(atom.notifications.notifications).toEqual([]) expect(fs.readFileSync(existsPath, 'utf-8')).toEqual('abc\ndef') @@ -374,7 +280,7 @@ describe "the commands", -> atom.workspace.open() it "closes the active pane item if not modified", -> - openEx() + keydown(':') submitNormalModeInputText('quit') expect(pane.destroyActiveItem).toHaveBeenCalled() expect(pane.getItems().length).toBe(1) @@ -385,14 +291,14 @@ describe "the commands", -> it "opens the prompt to save", -> spyOn(pane, 'promptToSaveItem') - openEx() + keydown(':') submitNormalModeInputText('quit') expect(pane.promptToSaveItem).toHaveBeenCalled() describe ":quitall", -> it "closes Atom", -> spyOn(atom, 'close') - openEx() + keydown(':') submitNormalModeInputText('quitall') expect(atom.close).toHaveBeenCalled() @@ -400,7 +306,7 @@ describe "the commands", -> it "acts as an alias to :quit", -> spyOn(Ex, 'tabclose').andCallThrough() spyOn(Ex, 'quit').andCallThrough() - openEx() + keydown(':') submitNormalModeInputText('tabclose') expect(Ex.quit).toHaveBeenCalledWith(Ex.tabclose.calls[0].args...) @@ -414,13 +320,13 @@ describe "the commands", -> it "switches to the next tab", -> pane.activateItemAtIndex(1) - openEx() + keydown(':') submitNormalModeInputText('tabnext') expect(pane.getActiveItemIndex()).toBe(2) it "wraps around", -> pane.activateItemAtIndex(pane.getItems().length - 1) - openEx() + keydown(':') submitNormalModeInputText('tabnext') expect(pane.getActiveItemIndex()).toBe(0) @@ -434,13 +340,13 @@ describe "the commands", -> it "switches to the previous tab", -> pane.activateItemAtIndex(1) - openEx() + keydown(':') submitNormalModeInputText('tabprevious') expect(pane.getActiveItemIndex()).toBe(0) it "wraps around", -> pane.activateItemAtIndex(0) - openEx() + keydown(':') submitNormalModeInputText('tabprevious') expect(pane.getActiveItemIndex()).toBe(pane.getItems().length - 1) @@ -451,7 +357,7 @@ describe "the commands", -> it "writes the file, then quits", -> spyOn(atom, 'showSaveDialogSync').andReturn(projectPath('wq-1')) - openEx() + keydown(':') submitNormalModeInputText('wq') expect(Ex.write).toHaveBeenCalled() # Since `:wq` only calls `:quit` after `:write` is finished, we need to @@ -460,7 +366,7 @@ describe "the commands", -> it "doesn't quit when the file is new and no path is specified in the save dialog", -> spyOn(atom, 'showSaveDialogSync').andReturn(undefined) - openEx() + keydown(':') submitNormalModeInputText('wq') expect(Ex.write).toHaveBeenCalled() wasNotCalled = false @@ -470,7 +376,7 @@ describe "the commands", -> waitsFor((-> wasNotCalled), 100) it "passes the file name", -> - openEx() + keydown(':') submitNormalModeInputText('wq wq-2') expect(Ex.write) .toHaveBeenCalled() @@ -480,22 +386,15 @@ describe "the commands", -> describe ":xit", -> it "acts as an alias to :wq", -> spyOn(Ex, 'wq') - openEx() + keydown(':') 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') spyOn(Ex, 'quitall') - openEx() + keydown(':') submitNormalModeInputText('wqall') expect(Ex.wall).toHaveBeenCalled() expect(Ex.quitall).toHaveBeenCalled() @@ -507,7 +406,7 @@ describe "the commands", -> editor.getBuffer().setText('abc') editor.saveAs(filePath) fs.writeFileSync(filePath, 'def') - openEx() + keydown(':') submitNormalModeInputText('edit') # Reloading takes a bit waitsFor((-> editor.getText() is 'def'), @@ -519,7 +418,7 @@ describe "the commands", -> editor.saveAs(filePath) editor.getBuffer().setText('abcd') fs.writeFileSync(filePath, 'def') - openEx() + keydown(':') submitNormalModeInputText('edit') expect(atom.notifications.notifications[0].message).toEqual( 'Command error: No write since last change (add ! to override)') @@ -533,7 +432,7 @@ describe "the commands", -> editor.saveAs(filePath) editor.getBuffer().setText('abcd') fs.writeFileSync(filePath, 'def') - openEx() + keydown(':') submitNormalModeInputText('edit!') expect(atom.notifications.notifications.length).toBe(0) waitsFor((-> editor.getText() is 'def') @@ -541,7 +440,7 @@ describe "the commands", -> it "throws an error when editing a new file", -> editor.getBuffer().reload() - openEx() + keydown(':') submitNormalModeInputText('edit') expect(atom.notifications.notifications[0].message).toEqual( 'Command error: No file name') @@ -557,18 +456,18 @@ describe "the commands", -> it "opens the specified path", -> filePath = projectPath('edit-new-test') - openEx() + keydown(':') submitNormalModeInputText("edit #{filePath}") expect(atom.workspace.open).toHaveBeenCalledWith(filePath) it "opens a relative path", -> - openEx() + keydown(':') submitNormalModeInputText('edit edit-relative-test') expect(atom.workspace.open).toHaveBeenCalledWith( projectPath('edit-relative-test')) it "throws an error if trying to open more than one file", -> - openEx() + keydown(':') submitNormalModeInputText('edit edit-new-test-1 edit-new-test-2') expect(atom.workspace.open.callCount).toBe(0) expect(atom.notifications.notifications[0].message).toEqual( @@ -578,14 +477,14 @@ describe "the commands", -> it "acts as an alias to :edit if supplied with a path", -> spyOn(Ex, 'tabedit').andCallThrough() spyOn(Ex, 'edit') - openEx() + keydown(':') submitNormalModeInputText('tabedit tabedit-test') expect(Ex.edit).toHaveBeenCalledWith(Ex.tabedit.calls[0].args...) it "acts as an alias to :tabnew if not supplied with a path", -> spyOn(Ex, 'tabedit').andCallThrough() spyOn(Ex, 'tabnew') - openEx() + keydown(':') submitNormalModeInputText('tabedit ') expect(Ex.tabnew) .toHaveBeenCalledWith(Ex.tabedit.calls[0].args...) @@ -593,14 +492,14 @@ describe "the commands", -> describe ":tabnew", -> it "opens a new tab", -> spyOn(atom.workspace, 'open') - openEx() + keydown(':') submitNormalModeInputText('tabnew') expect(atom.workspace.open).toHaveBeenCalled() it "opens a new tab for editing when provided an argument", -> spyOn(Ex, 'tabnew').andCallThrough() spyOn(Ex, 'tabedit') - openEx() + keydown(':') submitNormalModeInputText('tabnew tabnew-test') expect(Ex.tabedit) .toHaveBeenCalledWith(Ex.tabnew.calls[0].args...) @@ -612,14 +511,14 @@ describe "the commands", -> spyOn(pane, 'splitDown').andCallThrough() filePath = projectPath('split') editor.saveAs(filePath) - openEx() + keydown(':') submitNormalModeInputText('split') expect(pane.splitDown).toHaveBeenCalled() else spyOn(pane, 'splitUp').andCallThrough() filePath = projectPath('split') editor.saveAs(filePath) - openEx() + keydown(':') submitNormalModeInputText('split') expect(pane.splitUp).toHaveBeenCalled() # FIXME: Should test whether the new pane contains a TextEditor @@ -632,7 +531,7 @@ describe "the commands", -> spyOn(pane, 'splitRight').andCallThrough() filePath = projectPath('vsplit') editor.saveAs(filePath) - openEx() + keydown(':') submitNormalModeInputText('vsplit') expect(pane.splitLeft).toHaveBeenCalled() else @@ -640,7 +539,7 @@ describe "the commands", -> spyOn(pane, 'splitLeft').andCallThrough() filePath = projectPath('vsplit') editor.saveAs(filePath) - openEx() + keydown(':') submitNormalModeInputText('vsplit') expect(pane.splitLeft).toHaveBeenCalled() # FIXME: Should test whether the new pane contains a TextEditor @@ -652,31 +551,32 @@ describe "the commands", -> editor.setCursorBufferPosition([2, 0]) it "deletes the current line", -> - openEx() + keydown(':') submitNormalModeInputText('delete') expect(editor.getText()).toEqual('abc\ndef\njkl') it "copies the deleted text", -> - openEx() + keydown(':') submitNormalModeInputText('delete') expect(atom.clipboard.read()).toEqual('ghi\n') it "deletes the lines in the given range", -> processedOpStack = false exState.onDidProcessOpStack -> processedOpStack = true - openEx() + keydown(':') submitNormalModeInputText('1,2delete') expect(editor.getText()).toEqual('ghi\njkl') waitsFor -> processedOpStack editor.setText('abc\ndef\nghi\njkl') editor.setCursorBufferPosition([1, 1]) + # For some reason, keydown(':') doesn't work here :/ atom.commands.dispatch(editorElement, 'ex-mode:open') submitNormalModeInputText(',/k/delete') expect(editor.getText()).toEqual('abc\n') it "undos deleting several lines at once", -> - openEx() + keydown(':') submitNormalModeInputText('-1,.delete') expect(editor.getText()).toEqual('abc\njkl') atom.commands.dispatch(editorElement, 'core:undo') @@ -688,17 +588,17 @@ describe "the commands", -> editor.setCursorBufferPosition([0, 0]) it "replaces a character on the current line", -> - openEx() + keydown(':') submitNormalModeInputText(':substitute /a/x') expect(editor.getText()).toEqual('xbcaABC\ndefdDEF\nabcaABC') it "doesn't need a space before the arguments", -> - openEx() + keydown(':') submitNormalModeInputText(':substitute/a/x') expect(editor.getText()).toEqual('xbcaABC\ndefdDEF\nabcaABC') it "respects modifiers passed to it", -> - openEx() + keydown(':') submitNormalModeInputText(':substitute/a/x/g') expect(editor.getText()).toEqual('xbcxABC\ndefdDEF\nabcaABC') @@ -707,7 +607,7 @@ describe "the commands", -> expect(editor.getText()).toEqual('xbcxxBC\ndefdDEF\nabcaABC') it "replaces on multiple lines", -> - openEx() + keydown(':') submitNormalModeInputText(':%substitute/abc/ghi') expect(editor.getText()).toEqual('ghiaABC\ndefdDEF\nghiaABC') @@ -715,35 +615,24 @@ 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') editor.setCursorBufferPosition([2, 0]) it "yanks the current line", -> - openEx() + keydown(':') submitNormalModeInputText('yank') expect(atom.clipboard.read()).toEqual('ghi\n') it "yanks the lines in the given range", -> - openEx() + keydown(':') submitNormalModeInputText('1,2yank') expect(atom.clipboard.read()).toEqual('abc\ndef\n') describe "illegal delimiters", -> test = (delim) -> - openEx() + keydown(':') submitNormalModeInputText(":substitute #{delim}a#{delim}x#{delim}gi") expect(atom.notifications.notifications[0].message).toEqual( "Command error: Regular expressions can't be delimited by alphanumeric characters, '\\', '\"' or '|'") @@ -760,12 +649,12 @@ describe "the commands", -> editor.setText('abcabc\nabcabc') it "removes the pattern without modifiers", -> - openEx() + keydown(':') submitNormalModeInputText(":substitute/abc//") expect(editor.getText()).toEqual('abc\nabcabc') it "removes the pattern with modifiers", -> - openEx() + keydown(':') submitNormalModeInputText(":substitute/abc//g") expect(editor.getText()).toEqual('\nabcabc') @@ -774,7 +663,7 @@ describe "the commands", -> editor.setText('abc,def,ghi') test = (escapeChar, escaped) -> - openEx() + keydown(':') submitNormalModeInputText(":substitute/,/\\#{escapeChar}/g") expect(editor.getText()).toEqual("abc#{escaped}def#{escaped}ghi") @@ -789,26 +678,26 @@ describe "the commands", -> it "uses case sensitive search if smartcase is off and the pattern is lowercase", -> atom.config.set('vim-mode.useSmartcaseForSearch', false) - openEx() + keydown(':') submitNormalModeInputText(':substitute/abc/ghi/g') expect(editor.getText()).toEqual('ghiaABC\ndefdDEF\nabcaABC') it "uses case sensitive search if smartcase is off and the pattern is uppercase", -> editor.setText('abcaABC\ndefdDEF\nabcaABC') - openEx() + keydown(':') submitNormalModeInputText(':substitute/ABC/ghi/g') expect(editor.getText()).toEqual('abcaghi\ndefdDEF\nabcaABC') it "uses case insensitive search if smartcase is on and the pattern is lowercase", -> editor.setText('abcaABC\ndefdDEF\nabcaABC') atom.config.set('vim-mode.useSmartcaseForSearch', true) - openEx() + keydown(':') submitNormalModeInputText(':substitute/abc/ghi/g') expect(editor.getText()).toEqual('ghiaghi\ndefdDEF\nabcaABC') it "uses case sensitive search if smartcase is on and the pattern is uppercase", -> editor.setText('abcaABC\ndefdDEF\nabcaABC') - openEx() + keydown(':') submitNormalModeInputText(':substitute/ABC/ghi/g') expect(editor.getText()).toEqual('abcaghi\ndefdDEF\nabcaABC') @@ -818,37 +707,37 @@ describe "the commands", -> it "uses case insensitive search if smartcase is off and \c is in the pattern", -> atom.config.set('vim-mode.useSmartcaseForSearch', false) - openEx() + keydown(':') submitNormalModeInputText(':substitute/abc\\c/ghi/g') expect(editor.getText()).toEqual('ghiaghi\ndefdDEF\nabcaABC') it "doesn't matter where in the pattern \\c is", -> atom.config.set('vim-mode.useSmartcaseForSearch', false) - openEx() + keydown(':') submitNormalModeInputText(':substitute/a\\cbc/ghi/g') expect(editor.getText()).toEqual('ghiaghi\ndefdDEF\nabcaABC') it "uses case sensitive search if smartcase is on, \\C is in the pattern and the pattern is lowercase", -> atom.config.set('vim-mode.useSmartcaseForSearch', true) - openEx() + keydown(':') submitNormalModeInputText(':substitute/a\\Cbc/ghi/g') expect(editor.getText()).toEqual('ghiaABC\ndefdDEF\nabcaABC') it "overrides \\C with \\c if \\C comes first", -> atom.config.set('vim-mode.useSmartcaseForSearch', true) - openEx() + keydown(':') submitNormalModeInputText(':substitute/a\\Cb\\cc/ghi/g') expect(editor.getText()).toEqual('ghiaghi\ndefdDEF\nabcaABC') it "overrides \\C with \\c if \\c comes first", -> atom.config.set('vim-mode.useSmartcaseForSearch', true) - openEx() + keydown(':') submitNormalModeInputText(':substitute/a\\cb\\Cc/ghi/g') expect(editor.getText()).toEqual('ghiaghi\ndefdDEF\nabcaABC') it "overrides an appended /i flag with \\C", -> atom.config.set('vim-mode.useSmartcaseForSearch', true) - openEx() + keydown(':') submitNormalModeInputText(':substitute/ab\\Cc/ghi/gi') expect(editor.getText()).toEqual('ghiaABC\ndefdDEF\nabcaABC') @@ -857,23 +746,23 @@ describe "the commands", -> editor.setText('abcaABC\ndefdDEF\nabcaABC') it "replaces \\1 with the first group", -> - openEx() + keydown(':') submitNormalModeInputText(':substitute/bc(.{2})/X\\1X') expect(editor.getText()).toEqual('aXaAXBC\ndefdDEF\nabcaABC') it "replaces multiple groups", -> - openEx() + keydown(':') submitNormalModeInputText(':substitute/a([a-z]*)aA([A-Z]*)/X\\1XY\\2Y') expect(editor.getText()).toEqual('XbcXYBCY\ndefdDEF\nabcaABC') it "replaces \\0 with the entire match", -> - openEx() + keydown(':') submitNormalModeInputText(':substitute/ab(ca)AB/X\\0X') expect(editor.getText()).toEqual('XabcaABXC\ndefdDEF\nabcaABC') describe ":set", -> it "throws an error without a specified option", -> - openEx() + keydown(':') submitNormalModeInputText(':set') expect(atom.notifications.notifications[0].message).toEqual( 'Command error: No option specified') @@ -881,7 +770,7 @@ describe "the commands", -> it "sets multiple options at once", -> atom.config.set('editor.showInvisibles', false) atom.config.set('editor.showLineNumbers', false) - openEx() + keydown(':') submitNormalModeInputText(':set list number') expect(atom.config.get('editor.showInvisibles')).toBe(true) expect(atom.config.get('editor.showLineNumbers')).toBe(true) @@ -892,7 +781,7 @@ describe "the commands", -> atom.config.set('editor.showLineNumbers', false) it "sets (no)list", -> - openEx() + keydown(':') submitNormalModeInputText(':set list') expect(atom.config.get('editor.showInvisibles')).toBe(true) atom.commands.dispatch(editorElement, 'ex-mode:open') @@ -900,7 +789,7 @@ describe "the commands", -> expect(atom.config.get('editor.showInvisibles')).toBe(false) it "sets (no)nu(mber)", -> - openEx() + keydown(':') submitNormalModeInputText(':set nu') expect(atom.config.get('editor.showLineNumbers')).toBe(true) atom.commands.dispatch(editorElement, 'ex-mode:open') @@ -913,61 +802,11 @@ describe "the commands", -> submitNormalModeInputText(':set nonumber') expect(atom.config.get('editor.showLineNumbers')).toBe(false) - it "sets (no)sp(lit)r(ight)", -> - openEx() - submitNormalModeInputText(':set spr') - expect(atom.config.get('ex-mode.splitright')).toBe(true) - atom.commands.dispatch(editorElement, 'ex-mode:open') - submitNormalModeInputText(':set nospr') - expect(atom.config.get('ex-mode.splitright')).toBe(false) - atom.commands.dispatch(editorElement, 'ex-mode:open') - submitNormalModeInputText(':set splitright') - expect(atom.config.get('ex-mode.splitright')).toBe(true) - atom.commands.dispatch(editorElement, 'ex-mode:open') - submitNormalModeInputText(':set nosplitright') - expect(atom.config.get('ex-mode.splitright')).toBe(false) - - it "sets (no)s(plit)b(elow)", -> - openEx() - submitNormalModeInputText(':set sb') - expect(atom.config.get('ex-mode.splitbelow')).toBe(true) - atom.commands.dispatch(editorElement, 'ex-mode:open') - submitNormalModeInputText(':set nosb') - expect(atom.config.get('ex-mode.splitbelow')).toBe(false) - atom.commands.dispatch(editorElement, 'ex-mode:open') - submitNormalModeInputText(':set splitbelow') - expect(atom.config.get('ex-mode.splitbelow')).toBe(true) - atom.commands.dispatch(editorElement, 'ex-mode:open') - submitNormalModeInputText(':set nosplitbelow') - expect(atom.config.get('ex-mode.splitbelow')).toBe(false) - - it "sets (no)s(mart)c(a)s(e)", -> - openEx() - submitNormalModeInputText(':set scs') - expect(atom.config.get('vim-mode.useSmartcaseForSearch')).toBe(true) - openEx() - submitNormalModeInputText(':set noscs') - expect(atom.config.get('vim-mode.useSmartcaseForSearch')).toBe(false) - openEx() - submitNormalModeInputText(':set smartcase') - expect(atom.config.get('vim-mode.useSmartcaseForSearch')).toBe(true) - openEx() - 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') spyOn(Ex, 'write') - openEx() + keydown(':') submitNormalModeInputText('W') expect(Ex.write).toHaveBeenCalled() @@ -975,45 +814,8 @@ describe "the commands", -> ExClass.registerAlias('W', 'write') spyOn(Ex, 'W').andCallThrough() spyOn(Ex, 'write') - openEx() + keydown(':') submitNormalModeInputText('W') WArgs = Ex.W.calls[0].args[0] writeArgs = Ex.write.calls[0].args[0] expect(WArgs).toBe writeArgs - - describe "with selections", -> - it "executes on the selected range", -> - spyOn(Ex, 's') - editor.setCursorBufferPosition([0, 0]) - editor.selectToBufferPosition([2, 1]) - atom.commands.dispatch(editorElement, 'ex-mode:open') - submitNormalModeInputText("'<,'>s/abc/def") - expect(Ex.s.calls[0].args[0].range).toEqual [0, 2] - - it "calls the functions multiple times if there are multiple selections", -> - spyOn(Ex, 's') - editor.setCursorBufferPosition([0, 0]) - editor.selectToBufferPosition([2, 1]) - editor.addCursorAtBufferPosition([3, 0]) - editor.selectToBufferPosition([3, 2]) - atom.commands.dispatch(editorElement, 'ex-mode:open') - submitNormalModeInputText("'<,'>s/abc/def") - calls = Ex.s.calls - 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 cd7fbd0..4555c6f 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-plus') + vimMode = atom.packages.loadPackage('vim-mode') exMode = atom.packages.loadPackage('ex-mode') waitsForPromise -> activationPromise = exMode.activate() @@ -26,6 +26,7 @@ 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") @@ -69,7 +70,6 @@ describe "the input element", -> expect(getVisibility()).toBe true commandEditor = getCommandEditor() model = commandEditor.getModel() - expect(model.getText()).toBe '' model.setText('abc') atom.commands.dispatch(commandEditor, "core:backspace") expect(getVisibility()).toBe true @@ -82,11 +82,3 @@ describe "the input element", -> expect(model.getText()).toBe '' atom.commands.dispatch(commandEditor, "core:backspace") expect(getVisibility()).toBe false - - it "contains '<,'> when opened while there are selections", -> - editor.setCursorBufferPosition([0, 0]) - editor.selectToBufferPosition([0, 1]) - editor.addCursorAtBufferPosition([2, 0]) - editor.selectToBufferPosition([2, 1]) - atom.commands.dispatch(editorElement, "ex-mode:open") - expect(getCommandEditor().getModel().getText()).toBe "'<,'>"