Compare commits

..

No commits in common. "master" and "prepare-release" have entirely different histories.

20 changed files with 188 additions and 1042 deletions

View file

@ -1,9 +0,0 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
[*.{coffee,json}]
indent_style = space
indent_size = 2

View file

@ -1,23 +0,0 @@
# Ex-Mode Contributing Guidelines
Current Maintainers:
- [@jazzpi](https://github.com/jazzpi)
This project is accepting new maintainers. Interested parties should open a new issue titled `New Maintainer Request`.
## Pull Requests
- If the PR *fixes* or should result in the closure of any issues, use the `fixes #` or `closes #` syntax to ensure issue will
close when your PR is merged
- All pull-requests that fix a bug or add a new feature *must* have accompanying tests before they will be merged. If you want
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.
## Issues
- Be aware of the responsibilities of `ex-mode` vs `vim-mode`
- If you have identified a bug we would welcome any Pull Requests that either:
- Fix the issue
- Create failing tests to confirm the bug

View file

@ -1,15 +0,0 @@
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!))

View file

@ -1,45 +1,13 @@
## Project specific config ### language: objective-c
language: generic
env: env:
global: - APM_TEST_PACKAGES="vim-mode"
- 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: notifications:
email: email:
on_success: never on_success: never
on_failure: change on_failure: change
branches: script: 'curl -s https://raw.githubusercontent.com/atom/ci/master/build-package.sh | sh'
only:
- master
git: git:
depth: 10 depth: 10
sudo: false
addons:
apt:
packages:
- build-essential
- git
- libgnome-keyring-dev
- libsecret-1-dev
- fakeroot

View file

@ -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 ## 0.9.0
* Added support for yank commands, ex `:1,10y` (@posgarou) * Added support for yank commands, ex `:1,10y` (@posgarou)

33
README.md Executable file → Normal file
View file

@ -4,9 +4,7 @@ ex-mode for Atom's vim-mode
## Use ## Use
Install both [vim-mode-plus](https://github.com/t9md/atom-vim-mode-plus) (or Install both [vim-mode](https://github.com/atom/vim-mode) and ex-mode. Type `:` in command mode. Enter `w` or `write`.
the deprecated `vim-mode`) and ex-mode. Type `:` in command mode. Enter `w` or
`write`.
## Extend ## Extend
@ -30,34 +28,7 @@ atom.packages.onDidActivatePackage (pack) ->
Ex.registerAlias 'Wq', 'wq' Ex.registerAlias 'Wq', 'wq'
``` ```
## Existing commands See `lib/ex.coffee` for some examples commands. Contributions are very welcome!
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 <file>` | 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 <file>` | Save as |
| `wall/wa` | Save all tabs |
| `sp/split` | Split window |
| `sp/split <file>` | Open file in split window |
| `s/substitute` | Substitute regular expression in active line |
| `vsp/vsplit` | Vertical split window |
| `vsp/vsplit <file>` | Open file in vertical split window |
| `delete` | Cut active line |
| `yank` | Copy active line |
| `set <options>` | Set options |
| `sort` | Sort all lines in file |
| `sort <line range>` | Sort lines in line range |
See `lib/ex.coffee` for the implementations of these commands. Contributions are very welcome!
## Status ## Status

View file

@ -7,10 +7,5 @@
# For more detailed documentation see # For more detailed documentation see
# https://atom.io/docs/latest/advanced/keymaps # 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)': 'atom-text-editor.vim-mode:not(.insert-mode)':
':': 'ex-mode:open' ':': 'ex-mode:open'

View file

@ -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 []

View file

@ -5,47 +5,33 @@ CommandError = require './command-error'
class Command class Command
constructor: (@editor, @exState) -> constructor: (@editor, @exState) ->
@selections = @exState.getSelections() @viewModel = new ExViewModel(@)
@viewModel = new ExViewModel(@, Object.keys(@selections).length > 0)
parseAddr: (str, cursor) -> parseAddr: (str, curPos) ->
row = cursor.getBufferRow()
if str is '.' if str is '.'
addr = row addr = curPos.row
else if str is '$' else if str is '$'
# Lines are 0-indexed in Atom, but 1-indexed in vim. # 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 addr = @editor.getBuffer().lines.length - 1
# 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 ["+", "-"] else if str[0] in ["+", "-"]
addr = row + @parseOffset(str) addr = curPos.row + @parseOffset(str)
else if not isNaN(str) else if not isNaN(str)
addr = parseInt(str) - 1 addr = parseInt(str) - 1
else if str[0] is "'" # Parse Mark... else if str[0] is "'" # Parse Mark...
unless @vimState? unless @vimState?
throw new CommandError("Couldn't get access to vim-mode.") throw new CommandError("Couldn't get access to vim-mode.")
mark = @vimState.mark.marks[str[1]] mark = @vimState.marks[str[1]]
unless mark? unless mark?
throw new CommandError("Mark #{str} not set.") throw new CommandError("Mark #{str} not set.")
addr = mark.getEndBufferPosition().row addr = mark.bufferMarker.range.end.row
else if str[0] is "/" else if str[0] is "/"
str = str[1...] addr = Find.findNextInBuffer(@editor.buffer, curPos, str[1...-1])
if str[str.length-1] is "/" unless addr?
str = str[...-1] throw new CommandError("Pattern not found: #{str[1...-1]}")
addr = Find.scanEditor(str, @editor, cursor.getCurrentLineBufferRange().end)[0] else if str[0] is "?"
unless addr? addr = Find.findPreviousInBuffer(@editor.buffer, curPos, str[1...-1])
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]
unless addr? unless addr?
throw new CommandError("Pattern not found: #{str[1...-1]}") throw new CommandError("Pattern not found: #{str[1...-1]}")
addr = addr.start.row
return addr return addr
@ -77,9 +63,7 @@ class Command
return return
# Step 4: Address parsing # Step 4: Address parsing
# see comment in parseAddr about line length lastLine = @editor.getBuffer().lines.length - 1
buffer = @editor.getBuffer()
lastLine = (buffer.getLineCount?() ? buffer.lines.length) - 1
if cl[0] is '%' if cl[0] is '%'
range = [0, lastLine] range = [0, lastLine]
cl = cl[1..] cl = cl[1..]
@ -91,8 +75,8 @@ class Command
\$| # Last line \$| # Last line
\d+| # n-th line \d+| # n-th line
'[\[\]<>'`"^.(){}a-zA-Z]| # Marks '[\[\]<>'`"^.(){}a-zA-Z]| # Marks
/.*?(?:[^\\]/|$)| # Regex /.*?[^\\]/| # Regex
\?.*?(?:[^\\]\?|$)| # Backwards search \?.*?[^\\]\?| # Backwards search
[+-]\d* # Current line +/- a number of lines [+-]\d* # Current line +/- a number of lines
)((?:\s*[+-]\d*)*) # Line offset )((?:\s*[+-]\d*)*) # Line offset
)? )?
@ -111,38 +95,27 @@ class Command
[match, addr1, off1, addr2, off2] = cl.match(addrPattern) [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
else
runOverSelections = false
if addr1? if addr1?
address1 = @parseAddr(addr1, cursor) address1 = @parseAddr(addr1, curPos)
else else
# If no addr1 is given (,+3), assume it is '.' # If no addr1 is given (,+3), assume it is '.'
address1 = cursor.getBufferRow() address1 = curPos.row
if off1? if off1?
address1 += @parseOffset(off1) address1 += @parseOffset(off1)
address1 = 0 if address1 is -1 address1 = 0 if address1 is -1
address1 = lastLine if address1 > lastLine
if address1 < 0 if address1 < 0 or address1 > lastLine
throw new CommandError('Invalid range') throw new CommandError('Invalid range')
if addr2? if addr2?
address2 = @parseAddr(addr2, cursor) address2 = @parseAddr(addr2, curPos)
if off2? if off2?
address2 += @parseOffset(off2) address2 += @parseOffset(off2)
address2 = 0 if address2 is -1 if address2 < 0 or address2 > lastLine
address2 = lastLine if address2 > lastLine
if address2 < 0
throw new CommandError('Invalid range') throw new CommandError('Invalid range')
if address2 < address1 if address2 < address1
@ -176,7 +149,9 @@ class Command
[m, command, args] = cl.match(/^(\w+)(.*)/) [m, command, args] = cl.match(/^(\w+)(.*)/)
# If the command matches an existing one exactly, execute that one # 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 # Step 8: Match command against existing commands
matching = (name for name, val of Ex.singleton() when \ matching = (name for name, val of Ex.singleton() when \
name.indexOf(command) is 0) name.indexOf(command) is 0)
@ -186,14 +161,7 @@ class Command
command = matching[0] command = matching[0]
func = Ex.singleton()[command] func = Ex.singleton()[command]
if func? 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
func({ range, args, @vimState, @exState, @editor }) func({ range, args, @vimState, @exState, @editor })
else else
throw new CommandError("Not an editor command: #{input.characters}") throw new CommandError("Not an editor command: #{input.characters}")

View file

@ -35,28 +35,3 @@ module.exports = ExMode =
consumeVim: (vim) -> consumeVim: (vim) ->
@vim = vim @vim = vim
@globalExState.setVim(vim) @globalExState.setVim(vim)
consumeVimModePlus: (vim) ->
this.consumeVim(vim)
config:
splitbelow:
title: 'Split below'
description: 'when splitting, split from below'
type: 'boolean'
default: 'false'
splitright:
title: 'Split right'
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'

View file

@ -15,8 +15,7 @@ class ExCommandModeInputElement extends HTMLDivElement
@editorContainer.style.height = "0px" @editorContainer.style.height = "0px"
@editorElement = document.createElement "atom-text-editor" @editorElement = document.createElement "atom-text-editor"
@editorElement.classList.add('editor') # Consider this deprecated! @editorElement.classList.add('editor')
@editorElement.classList.add('ex-mode-editor')
@editorElement.getModel().setMini(true) @editorElement.getModel().setMini(true)
@editorElement.setAttribute('mini', '') @editorElement.setAttribute('mini', '')
@editorContainer.appendChild(@editorElement) @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:confirm', @confirm.bind(this))
atom.commands.add(@editorElement, 'core:cancel', @cancel.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)) atom.commands.add(@editorElement, 'blur', @cancel.bind(this))
backspace: -> backspace: ->

View file

@ -60,13 +60,4 @@ class ExState
@clearOpStack() @clearOpStack()
@emitter.emit('processed-op-stack') @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 module.exports = ExState

View file

@ -1,41 +1,20 @@
{ViewModel, Input} = require './view-model' {ViewModel, Input} = require './view-model'
AutoComplete = require './autocomplete'
Ex = require './ex'
module.exports = module.exports =
class ExViewModel extends ViewModel class ExViewModel extends ViewModel
constructor: (@exCommand, withSelection) -> constructor: (@exCommand) ->
super(@exCommand, class: 'command') super(@exCommand, class: 'command')
@historyIndex = -1 @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-up', @increaseHistoryEx)
atom.commands.add(@view.editorElement, 'core:move-down', @decreaseHistoryEx) atom.commands.add(@view.editorElement, 'core:move-down', @decreaseHistoryEx)
@autoComplete = new AutoComplete(Ex.getCommands())
restoreHistory: (index) -> restoreHistory: (index) ->
@view.editorElement.getModel().setText(@history(index).value) @view.editorElement.getModel().setText(@history(index).value)
history: (index) -> history: (index) ->
@exState.getExHistoryItem(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: => increaseHistoryEx: =>
if @history(@historyIndex + 1)? if @history(@historyIndex + 1)?
@historyIndex += 1 @historyIndex += 1

View file

@ -3,27 +3,12 @@ CommandError = require './command-error'
fs = require 'fs-plus' fs = require 'fs-plus'
VimOption = require './vim-option' VimOption = require './vim-option'
_ = require 'underscore-plus' _ = require 'underscore-plus'
atom
defer = () ->
deferred = {}
deferred.promise = new Promise((resolve, reject) ->
deferred.resolve = resolve
deferred.reject = reject
)
return deferred
trySave = (func) -> trySave = (func) ->
deferred = defer() deferred = Promise.defer()
try try
response = func() func()
if response instanceof Promise
response.then ->
deferred.resolve()
else
deferred.resolve() deferred.resolve()
catch error catch error
if error.message.endsWith('is a directory') if error.message.endsWith('is a directory')
@ -124,20 +109,11 @@ class Ex
@registerAlias: (alias, name) => @registerAlias: (alias, name) =>
@singleton()[alias] = (args) => @singleton()[name](args) @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: -> quit: ->
atom.workspace.getActivePane().destroyActiveItem() atom.workspace.getActivePane().destroyActiveItem()
quitall: -> quitall: ->
if !atom.config.get('ex-mode.onlyCloseBuffers')
atom.close() atom.close()
else
atom.workspace.getTextEditors().forEach (editor) ->
editor.destroy()
q: => @quit() q: => @quit()
@ -151,11 +127,11 @@ class Ex
tabe: (args) => @tabedit(args) tabe: (args) => @tabedit(args)
tabnew: (args) => tabnew: ({ range, args }) =>
if args.args.trim() is '' if args.trim() is ''
atom.workspace.open() atom.workspace.open()
else else
@tabedit(args) @tabedit(range, args)
tabclose: (args) => @quit(args) tabclose: (args) => @quit(args)
@ -173,16 +149,6 @@ class Ex
tabp: => @tabprevious() 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 }) -> edit: ({ range, args, editor }) ->
filePath = args.trim() filePath = args.trim()
if filePath[0] is '!' if filePath[0] is '!'
@ -211,7 +177,9 @@ class Ex
e: (args) => @edit(args) e: (args) => @edit(args)
enew: -> enew: ->
atom.workspace.open() buffer = atom.workspace.getActiveTextEditor().buffer
buffer.setPath(undefined)
buffer.load()
write: ({ range, args, editor, saveas }) -> write: ({ range, args, editor, saveas }) ->
saveas ?= false saveas ?= false
@ -226,33 +194,32 @@ class Ex
if filePath.indexOf(' ') isnt -1 if filePath.indexOf(' ') isnt -1
throw new CommandError('Only one file name allowed') throw new CommandError('Only one file name allowed')
deferred = defer() deferred = Promise.defer()
editor = atom.workspace.getActiveTextEditor() editor = atom.workspace.getActiveTextEditor()
saved = false
# Case 1; path is provided
if filePath.length isnt 0 if filePath.length isnt 0
fullPath = getFullPath filePath fullPath = getFullPath(filePath)
if editor.getPath()? and (not fullPath? or editor.getPath() == fullPath)
# Only write when it does not exist or we have a force flag set. if saveas
if force or not fs.existsSync(fullPath) throw new CommandError("Argument required")
editor.saveAs(fullPath)
return 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 else
# Cant see what the better API is but Pane.saveActiveItemAs() is the only call # Use editor.save when no path is given or the path to the file is given
# I could find that states it will ask the user. trySave(-> editor.save()).then(deferred.resolve)
trySave(-> atom.workspace.getActivePane().saveActiveItemAs()).then(deferred.promise) saved = true
else if not fullPath?
fullPath = atom.showSaveDialogSync()
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)
deferred.promise
wall: -> wall: ->
atom.workspace.saveAll() atom.workspace.saveAll()
@ -261,7 +228,7 @@ class Ex
@write(args) @write(args)
wq: (args) => wq: (args) =>
@write(args).then(=> @quit()) @write(args).then => @quit()
wa: => wa: =>
@wall() @wall()
@ -285,22 +252,12 @@ class Ex
xit: (args) => @wq(args) xit: (args) => @wq(args)
x: (args) => @xit(args)
split: ({ range, args }) -> split: ({ range, args }) ->
args = args.trim() args = args.trim()
filePaths = args.split(' ') filePaths = args.split(' ')
filePaths = undefined if filePaths.length is 1 and filePaths[0] is '' filePaths = undefined if filePaths.length is 1 and filePaths[0] is ''
pane = atom.workspace.getActivePane() pane = atom.workspace.getActivePane()
if atom.config.get('ex-mode.splitbelow')
if filePaths? and filePaths.length > 0
newPane = pane.splitDown()
for file in filePaths
do ->
atom.workspace.openURIInPane file, newPane
else
pane.splitDown(copyActiveItem: true)
else
if filePaths? and filePaths.length > 0 if filePaths? and filePaths.length > 0
newPane = pane.splitUp() newPane = pane.splitUp()
for file in filePaths for file in filePaths
@ -309,7 +266,6 @@ class Ex
else else
pane.splitUp(copyActiveItem: true) pane.splitUp(copyActiveItem: true)
sp: (args) => @split(args) sp: (args) => @split(args)
substitute: ({ range, args, editor, vimState }) -> substitute: ({ range, args, editor, vimState }) ->
@ -344,30 +300,16 @@ class Ex
[pattern, substition, flags] = parsed [pattern, substition, flags] = parsed
if pattern is '' if pattern is ''
if vimState.getSearchHistoryItem?
# vim-mode
pattern = vimState.getSearchHistoryItem() pattern = vimState.getSearchHistoryItem()
else if vimState.searchHistory?
# vim-mode-plus
pattern = vimState.searchHistory.get('prev')
if not pattern? if not pattern?
atom.beep() atom.beep()
throw new CommandError('No previous regular expression') throw new CommandError('No previous regular expression')
else else
if vimState.pushSearchHistory?
# vim-mode
vimState.pushSearchHistory(pattern) vimState.pushSearchHistory(pattern)
else if vimState.searchHistory?
# vim-mode-plus
vimState.searchHistory.save(pattern)
try try
flagsObj = {} flagsObj = {}
flags.split('').forEach((flag) -> flagsObj[flag] = true) flags.split('').forEach((flag) -> flagsObj[flag] = true)
# gdefault option
if atom.config.get('ex-mode.gdefault')
flagsObj.g = !flagsObj.g
patternRE = getSearchTerm(pattern, flagsObj) patternRE = getSearchTerm(pattern, flagsObj)
catch e catch e
if e.message.indexOf('Invalid flags supplied to RegExp constructor') is 0 if e.message.indexOf('Invalid flags supplied to RegExp constructor') is 0
@ -393,15 +335,6 @@ class Ex
filePaths = args.split(' ') filePaths = args.split(' ')
filePaths = undefined if filePaths.length is 1 and filePaths[0] is '' filePaths = undefined if filePaths.length is 1 and filePaths[0] is ''
pane = atom.workspace.getActivePane() pane = atom.workspace.getActivePane()
if atom.config.get('ex-mode.splitright')
if filePaths? and filePaths.length > 0
newPane = pane.splitRight()
for file in filePaths
do ->
atom.workspace.openURIInPane file, newPane
else
pane.splitRight(copyActiveItem: true)
else
if filePaths? and filePaths.length > 0 if filePaths? and filePaths.length > 0
newPane = pane.splitLeft() newPane = pane.splitLeft()
for file in filePaths for file in filePaths
@ -449,24 +382,4 @@ class Ex
throw new CommandError("No such option: #{option}") throw new CommandError("No such option: #{option}")
optionProcessor() 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 module.exports = Ex

View file

@ -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 = { module.exports = {
findInBuffer : (buffer, pattern) -> findInBuffer : (buffer, pattern) ->
found = [] found = []
@ -60,26 +23,4 @@ module.exports = {
return found[found.length - 1].start.row return found[found.length - 1].start.row
else else
return null 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)
} }

View file

@ -20,46 +20,4 @@ class VimOption
nonu: => nonu: =>
@nonumber() @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 module.exports = VimOption

View file

@ -1,7 +1,7 @@
{ {
"name": "ex-mode", "name": "ex-mode",
"main": "./lib/ex-mode", "main": "./lib/ex-mode",
"version": "0.18.0", "version": "0.9.0",
"description": "Ex for Atom's vim-mode", "description": "Ex for Atom's vim-mode",
"activationCommands": { "activationCommands": {
"atom-workspace": "ex-mode:open" "atom-workspace": "ex-mode:open"
@ -23,11 +23,6 @@
"versions": { "versions": {
"^0.1.0": "consumeVim" "^0.1.0": "consumeVim"
} }
},
"vim-mode-plus": {
"versions": {
"^0.1.0": "consumeVimModePlus"
}
} }
}, },
"providedServices": { "providedServices": {

View file

@ -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)

View file

@ -11,7 +11,7 @@ describe "the commands", ->
[editor, editorElement, vimState, exState, dir, dir2] = [] [editor, editorElement, vimState, exState, dir, dir2] = []
projectPath = (fileName) -> path.join(dir, fileName) projectPath = (fileName) -> path.join(dir, fileName)
beforeEach -> beforeEach ->
vimMode = atom.packages.loadPackage('vim-mode-plus') vimMode = atom.packages.loadPackage('vim-mode')
exMode = atom.packages.loadPackage('ex-mode') exMode = atom.packages.loadPackage('ex-mode')
waitsForPromise -> waitsForPromise ->
activationPromise = exMode.activate() activationPromise = exMode.activate()
@ -36,12 +36,12 @@ describe "the commands", ->
helpers.getEditorElement (element) -> helpers.getEditorElement (element) ->
atom.commands.dispatch(element, "ex-mode:open") atom.commands.dispatch(element, "ex-mode:open")
atom.commands.dispatch(element.getModel().normalModeInputView.editorElement, keydown('escape')
"core:cancel")
editorElement = element editorElement = element
editor = editorElement.getModel() editor = editorElement.getModel()
vimState = vimMode.mainModule.getEditorState(editor) vimState = vimMode.mainModule.getEditorState(editor)
exState = exMode.mainModule.exStates.get(editor) exState = exMode.mainModule.exStates.get(editor)
vimState.activateNormalMode()
vimState.resetNormalMode() vimState.resetNormalMode()
editor.setText("abc\ndef\nabc\ndef") editor.setText("abc\ndef\nabc\ndef")
@ -61,99 +61,6 @@ describe "the commands", ->
commandEditor.getModel().setText(text) commandEditor.getModel().setText(text)
atom.commands.dispatch(commandEditor, "core:confirm") 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 ":write", ->
describe "when editing a new file", -> describe "when editing a new file", ->
beforeEach -> beforeEach ->
@ -161,23 +68,22 @@ describe "the commands", ->
it "opens the save dialog", -> it "opens the save dialog", ->
spyOn(atom, 'showSaveDialogSync') spyOn(atom, 'showSaveDialogSync')
openEx() keydown(':')
submitNormalModeInputText('write') submitNormalModeInputText('write')
expect(atom.showSaveDialogSync).toHaveBeenCalled() expect(atom.showSaveDialogSync).toHaveBeenCalled()
it "saves when a path is specified in the save dialog", -> it "saves when a path is specified in the save dialog", ->
filePath = projectPath('write-from-save-dialog') filePath = projectPath('write-from-save-dialog')
spyOn(atom, 'showSaveDialogSync').andReturn(filePath) spyOn(atom, 'showSaveDialogSync').andReturn(filePath)
openEx() keydown(':')
submitNormalModeInputText('write') submitNormalModeInputText('write')
expect(fs.existsSync(filePath)).toBe(true) expect(fs.existsSync(filePath)).toBe(true)
expect(fs.readFileSync(filePath, 'utf-8')).toEqual('abc\ndef') 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", -> it "saves when a path is specified in the save dialog", ->
spyOn(atom, 'showSaveDialogSync').andReturn(undefined) spyOn(atom, 'showSaveDialogSync').andReturn(undefined)
spyOn(fs, 'writeFileSync') spyOn(fs, 'writeFileSync')
openEx() keydown(':')
submitNormalModeInputText('write') submitNormalModeInputText('write')
expect(fs.writeFileSync.calls.length).toBe(0) expect(fs.writeFileSync.calls.length).toBe(0)
@ -193,7 +99,7 @@ describe "the commands", ->
it "saves the file", -> it "saves the file", ->
editor.setText('abc') editor.setText('abc')
openEx() keydown(':')
submitNormalModeInputText('write') submitNormalModeInputText('write')
expect(fs.readFileSync(filePath, 'utf-8')).toEqual('abc') expect(fs.readFileSync(filePath, 'utf-8')).toEqual('abc')
expect(editor.isModified()).toBe(false) expect(editor.isModified()).toBe(false)
@ -204,7 +110,7 @@ describe "the commands", ->
beforeEach -> beforeEach ->
newPath = path.relative(dir, "#{filePath}.new") newPath = path.relative(dir, "#{filePath}.new")
editor.getBuffer().setText('abc') editor.getBuffer().setText('abc')
openEx() keydown(':')
afterEach -> afterEach ->
submitNormalModeInputText("write #{newPath}") submitNormalModeInputText("write #{newPath}")
@ -226,7 +132,7 @@ describe "the commands", ->
newPath = path.join('~', newPath) newPath = path.join('~', newPath)
it "throws an error with more than one path", -> it "throws an error with more than one path", ->
openEx() keydown(':')
submitNormalModeInputText('write path1 path2') submitNormalModeInputText('write path1 path2')
expect(atom.notifications.notifications[0].message).toEqual( expect(atom.notifications.notifications[0].message).toEqual(
'Command error: Only one file name allowed' 'Command error: Only one file name allowed'
@ -243,7 +149,7 @@ describe "the commands", ->
fs.removeSync(existsPath) fs.removeSync(existsPath)
it "throws an error if the file already exists", -> it "throws an error if the file already exists", ->
openEx() keydown(':')
submitNormalModeInputText("write #{existsPath}") submitNormalModeInputText("write #{existsPath}")
expect(atom.notifications.notifications[0].message).toEqual( expect(atom.notifications.notifications[0].message).toEqual(
'Command error: File exists (add ! to override)' 'Command error: File exists (add ! to override)'
@ -251,7 +157,7 @@ describe "the commands", ->
expect(fs.readFileSync(existsPath, 'utf-8')).toEqual('abc') expect(fs.readFileSync(existsPath, 'utf-8')).toEqual('abc')
it "writes if forced with :write!", -> it "writes if forced with :write!", ->
openEx() keydown(':')
submitNormalModeInputText("write! #{existsPath}") submitNormalModeInputText("write! #{existsPath}")
expect(atom.notifications.notifications).toEqual([]) expect(atom.notifications.notifications).toEqual([])
expect(fs.readFileSync(existsPath, 'utf-8')).toEqual('abc\ndef') expect(fs.readFileSync(existsPath, 'utf-8')).toEqual('abc\ndef')
@ -259,7 +165,7 @@ describe "the commands", ->
describe ":wall", -> describe ":wall", ->
it "saves all", -> it "saves all", ->
spyOn(atom.workspace, 'saveAll') spyOn(atom.workspace, 'saveAll')
openEx() keydown(':')
submitNormalModeInputText('wall') submitNormalModeInputText('wall')
expect(atom.workspace.saveAll).toHaveBeenCalled() expect(atom.workspace.saveAll).toHaveBeenCalled()
@ -270,14 +176,14 @@ describe "the commands", ->
it "opens the save dialog", -> it "opens the save dialog", ->
spyOn(atom, 'showSaveDialogSync') spyOn(atom, 'showSaveDialogSync')
openEx() keydown(':')
submitNormalModeInputText('saveas') submitNormalModeInputText('saveas')
expect(atom.showSaveDialogSync).toHaveBeenCalled() expect(atom.showSaveDialogSync).toHaveBeenCalled()
it "saves when a path is specified in the save dialog", -> it "saves when a path is specified in the save dialog", ->
filePath = projectPath('saveas-from-save-dialog') filePath = projectPath('saveas-from-save-dialog')
spyOn(atom, 'showSaveDialogSync').andReturn(filePath) spyOn(atom, 'showSaveDialogSync').andReturn(filePath)
openEx() keydown(':')
submitNormalModeInputText('saveas') submitNormalModeInputText('saveas')
expect(fs.existsSync(filePath)).toBe(true) expect(fs.existsSync(filePath)).toBe(true)
expect(fs.readFileSync(filePath, 'utf-8')).toEqual('abc\ndef') 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", -> it "saves when a path is specified in the save dialog", ->
spyOn(atom, 'showSaveDialogSync').andReturn(undefined) spyOn(atom, 'showSaveDialogSync').andReturn(undefined)
spyOn(fs, 'writeFileSync') spyOn(fs, 'writeFileSync')
openEx() keydown(':')
submitNormalModeInputText('saveas') submitNormalModeInputText('saveas')
expect(fs.writeFileSync.calls.length).toBe(0) expect(fs.writeFileSync.calls.length).toBe(0)
@ -301,7 +207,7 @@ describe "the commands", ->
it "complains if no path given", -> it "complains if no path given", ->
editor.setText('abc') editor.setText('abc')
openEx() keydown(':')
submitNormalModeInputText('saveas') submitNormalModeInputText('saveas')
expect(atom.notifications.notifications[0].message).toEqual( expect(atom.notifications.notifications[0].message).toEqual(
'Command error: Argument required' 'Command error: Argument required'
@ -313,7 +219,7 @@ describe "the commands", ->
beforeEach -> beforeEach ->
newPath = path.relative(dir, "#{filePath}.new") newPath = path.relative(dir, "#{filePath}.new")
editor.getBuffer().setText('abc') editor.getBuffer().setText('abc')
openEx() keydown(':')
afterEach -> afterEach ->
submitNormalModeInputText("saveas #{newPath}") submitNormalModeInputText("saveas #{newPath}")
@ -335,7 +241,7 @@ describe "the commands", ->
newPath = path.join('~', newPath) newPath = path.join('~', newPath)
it "throws an error with more than one path", -> it "throws an error with more than one path", ->
openEx() keydown(':')
submitNormalModeInputText('saveas path1 path2') submitNormalModeInputText('saveas path1 path2')
expect(atom.notifications.notifications[0].message).toEqual( expect(atom.notifications.notifications[0].message).toEqual(
'Command error: Only one file name allowed' 'Command error: Only one file name allowed'
@ -352,7 +258,7 @@ describe "the commands", ->
fs.removeSync(existsPath) fs.removeSync(existsPath)
it "throws an error if the file already exists", -> it "throws an error if the file already exists", ->
openEx() keydown(':')
submitNormalModeInputText("saveas #{existsPath}") submitNormalModeInputText("saveas #{existsPath}")
expect(atom.notifications.notifications[0].message).toEqual( expect(atom.notifications.notifications[0].message).toEqual(
'Command error: File exists (add ! to override)' 'Command error: File exists (add ! to override)'
@ -360,7 +266,7 @@ describe "the commands", ->
expect(fs.readFileSync(existsPath, 'utf-8')).toEqual('abc') expect(fs.readFileSync(existsPath, 'utf-8')).toEqual('abc')
it "writes if forced with :saveas!", -> it "writes if forced with :saveas!", ->
openEx() keydown(':')
submitNormalModeInputText("saveas! #{existsPath}") submitNormalModeInputText("saveas! #{existsPath}")
expect(atom.notifications.notifications).toEqual([]) expect(atom.notifications.notifications).toEqual([])
expect(fs.readFileSync(existsPath, 'utf-8')).toEqual('abc\ndef') expect(fs.readFileSync(existsPath, 'utf-8')).toEqual('abc\ndef')
@ -374,7 +280,7 @@ describe "the commands", ->
atom.workspace.open() atom.workspace.open()
it "closes the active pane item if not modified", -> it "closes the active pane item if not modified", ->
openEx() keydown(':')
submitNormalModeInputText('quit') submitNormalModeInputText('quit')
expect(pane.destroyActiveItem).toHaveBeenCalled() expect(pane.destroyActiveItem).toHaveBeenCalled()
expect(pane.getItems().length).toBe(1) expect(pane.getItems().length).toBe(1)
@ -385,14 +291,14 @@ describe "the commands", ->
it "opens the prompt to save", -> it "opens the prompt to save", ->
spyOn(pane, 'promptToSaveItem') spyOn(pane, 'promptToSaveItem')
openEx() keydown(':')
submitNormalModeInputText('quit') submitNormalModeInputText('quit')
expect(pane.promptToSaveItem).toHaveBeenCalled() expect(pane.promptToSaveItem).toHaveBeenCalled()
describe ":quitall", -> describe ":quitall", ->
it "closes Atom", -> it "closes Atom", ->
spyOn(atom, 'close') spyOn(atom, 'close')
openEx() keydown(':')
submitNormalModeInputText('quitall') submitNormalModeInputText('quitall')
expect(atom.close).toHaveBeenCalled() expect(atom.close).toHaveBeenCalled()
@ -400,7 +306,7 @@ describe "the commands", ->
it "acts as an alias to :quit", -> it "acts as an alias to :quit", ->
spyOn(Ex, 'tabclose').andCallThrough() spyOn(Ex, 'tabclose').andCallThrough()
spyOn(Ex, 'quit').andCallThrough() spyOn(Ex, 'quit').andCallThrough()
openEx() keydown(':')
submitNormalModeInputText('tabclose') submitNormalModeInputText('tabclose')
expect(Ex.quit).toHaveBeenCalledWith(Ex.tabclose.calls[0].args...) expect(Ex.quit).toHaveBeenCalledWith(Ex.tabclose.calls[0].args...)
@ -414,13 +320,13 @@ describe "the commands", ->
it "switches to the next tab", -> it "switches to the next tab", ->
pane.activateItemAtIndex(1) pane.activateItemAtIndex(1)
openEx() keydown(':')
submitNormalModeInputText('tabnext') submitNormalModeInputText('tabnext')
expect(pane.getActiveItemIndex()).toBe(2) expect(pane.getActiveItemIndex()).toBe(2)
it "wraps around", -> it "wraps around", ->
pane.activateItemAtIndex(pane.getItems().length - 1) pane.activateItemAtIndex(pane.getItems().length - 1)
openEx() keydown(':')
submitNormalModeInputText('tabnext') submitNormalModeInputText('tabnext')
expect(pane.getActiveItemIndex()).toBe(0) expect(pane.getActiveItemIndex()).toBe(0)
@ -434,13 +340,13 @@ describe "the commands", ->
it "switches to the previous tab", -> it "switches to the previous tab", ->
pane.activateItemAtIndex(1) pane.activateItemAtIndex(1)
openEx() keydown(':')
submitNormalModeInputText('tabprevious') submitNormalModeInputText('tabprevious')
expect(pane.getActiveItemIndex()).toBe(0) expect(pane.getActiveItemIndex()).toBe(0)
it "wraps around", -> it "wraps around", ->
pane.activateItemAtIndex(0) pane.activateItemAtIndex(0)
openEx() keydown(':')
submitNormalModeInputText('tabprevious') submitNormalModeInputText('tabprevious')
expect(pane.getActiveItemIndex()).toBe(pane.getItems().length - 1) expect(pane.getActiveItemIndex()).toBe(pane.getItems().length - 1)
@ -451,7 +357,7 @@ describe "the commands", ->
it "writes the file, then quits", -> it "writes the file, then quits", ->
spyOn(atom, 'showSaveDialogSync').andReturn(projectPath('wq-1')) spyOn(atom, 'showSaveDialogSync').andReturn(projectPath('wq-1'))
openEx() keydown(':')
submitNormalModeInputText('wq') submitNormalModeInputText('wq')
expect(Ex.write).toHaveBeenCalled() expect(Ex.write).toHaveBeenCalled()
# Since `:wq` only calls `:quit` after `:write` is finished, we need to # 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", -> it "doesn't quit when the file is new and no path is specified in the save dialog", ->
spyOn(atom, 'showSaveDialogSync').andReturn(undefined) spyOn(atom, 'showSaveDialogSync').andReturn(undefined)
openEx() keydown(':')
submitNormalModeInputText('wq') submitNormalModeInputText('wq')
expect(Ex.write).toHaveBeenCalled() expect(Ex.write).toHaveBeenCalled()
wasNotCalled = false wasNotCalled = false
@ -470,7 +376,7 @@ describe "the commands", ->
waitsFor((-> wasNotCalled), 100) waitsFor((-> wasNotCalled), 100)
it "passes the file name", -> it "passes the file name", ->
openEx() keydown(':')
submitNormalModeInputText('wq wq-2') submitNormalModeInputText('wq wq-2')
expect(Ex.write) expect(Ex.write)
.toHaveBeenCalled() .toHaveBeenCalled()
@ -480,22 +386,15 @@ describe "the commands", ->
describe ":xit", -> describe ":xit", ->
it "acts as an alias to :wq", -> it "acts as an alias to :wq", ->
spyOn(Ex, 'wq') spyOn(Ex, 'wq')
openEx() keydown(':')
submitNormalModeInputText('xit') submitNormalModeInputText('xit')
expect(Ex.wq).toHaveBeenCalled() 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", -> describe ":wqall", ->
it "calls :wall, then :quitall", -> it "calls :wall, then :quitall", ->
spyOn(Ex, 'wall') spyOn(Ex, 'wall')
spyOn(Ex, 'quitall') spyOn(Ex, 'quitall')
openEx() keydown(':')
submitNormalModeInputText('wqall') submitNormalModeInputText('wqall')
expect(Ex.wall).toHaveBeenCalled() expect(Ex.wall).toHaveBeenCalled()
expect(Ex.quitall).toHaveBeenCalled() expect(Ex.quitall).toHaveBeenCalled()
@ -507,7 +406,7 @@ describe "the commands", ->
editor.getBuffer().setText('abc') editor.getBuffer().setText('abc')
editor.saveAs(filePath) editor.saveAs(filePath)
fs.writeFileSync(filePath, 'def') fs.writeFileSync(filePath, 'def')
openEx() keydown(':')
submitNormalModeInputText('edit') submitNormalModeInputText('edit')
# Reloading takes a bit # Reloading takes a bit
waitsFor((-> editor.getText() is 'def'), waitsFor((-> editor.getText() is 'def'),
@ -519,7 +418,7 @@ describe "the commands", ->
editor.saveAs(filePath) editor.saveAs(filePath)
editor.getBuffer().setText('abcd') editor.getBuffer().setText('abcd')
fs.writeFileSync(filePath, 'def') fs.writeFileSync(filePath, 'def')
openEx() keydown(':')
submitNormalModeInputText('edit') submitNormalModeInputText('edit')
expect(atom.notifications.notifications[0].message).toEqual( expect(atom.notifications.notifications[0].message).toEqual(
'Command error: No write since last change (add ! to override)') 'Command error: No write since last change (add ! to override)')
@ -533,7 +432,7 @@ describe "the commands", ->
editor.saveAs(filePath) editor.saveAs(filePath)
editor.getBuffer().setText('abcd') editor.getBuffer().setText('abcd')
fs.writeFileSync(filePath, 'def') fs.writeFileSync(filePath, 'def')
openEx() keydown(':')
submitNormalModeInputText('edit!') submitNormalModeInputText('edit!')
expect(atom.notifications.notifications.length).toBe(0) expect(atom.notifications.notifications.length).toBe(0)
waitsFor((-> editor.getText() is 'def') waitsFor((-> editor.getText() is 'def')
@ -541,7 +440,7 @@ describe "the commands", ->
it "throws an error when editing a new file", -> it "throws an error when editing a new file", ->
editor.getBuffer().reload() editor.getBuffer().reload()
openEx() keydown(':')
submitNormalModeInputText('edit') submitNormalModeInputText('edit')
expect(atom.notifications.notifications[0].message).toEqual( expect(atom.notifications.notifications[0].message).toEqual(
'Command error: No file name') 'Command error: No file name')
@ -557,18 +456,18 @@ describe "the commands", ->
it "opens the specified path", -> it "opens the specified path", ->
filePath = projectPath('edit-new-test') filePath = projectPath('edit-new-test')
openEx() keydown(':')
submitNormalModeInputText("edit #{filePath}") submitNormalModeInputText("edit #{filePath}")
expect(atom.workspace.open).toHaveBeenCalledWith(filePath) expect(atom.workspace.open).toHaveBeenCalledWith(filePath)
it "opens a relative path", -> it "opens a relative path", ->
openEx() keydown(':')
submitNormalModeInputText('edit edit-relative-test') submitNormalModeInputText('edit edit-relative-test')
expect(atom.workspace.open).toHaveBeenCalledWith( expect(atom.workspace.open).toHaveBeenCalledWith(
projectPath('edit-relative-test')) projectPath('edit-relative-test'))
it "throws an error if trying to open more than one file", -> it "throws an error if trying to open more than one file", ->
openEx() keydown(':')
submitNormalModeInputText('edit edit-new-test-1 edit-new-test-2') submitNormalModeInputText('edit edit-new-test-1 edit-new-test-2')
expect(atom.workspace.open.callCount).toBe(0) expect(atom.workspace.open.callCount).toBe(0)
expect(atom.notifications.notifications[0].message).toEqual( 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", -> it "acts as an alias to :edit if supplied with a path", ->
spyOn(Ex, 'tabedit').andCallThrough() spyOn(Ex, 'tabedit').andCallThrough()
spyOn(Ex, 'edit') spyOn(Ex, 'edit')
openEx() keydown(':')
submitNormalModeInputText('tabedit tabedit-test') submitNormalModeInputText('tabedit tabedit-test')
expect(Ex.edit).toHaveBeenCalledWith(Ex.tabedit.calls[0].args...) expect(Ex.edit).toHaveBeenCalledWith(Ex.tabedit.calls[0].args...)
it "acts as an alias to :tabnew if not supplied with a path", -> it "acts as an alias to :tabnew if not supplied with a path", ->
spyOn(Ex, 'tabedit').andCallThrough() spyOn(Ex, 'tabedit').andCallThrough()
spyOn(Ex, 'tabnew') spyOn(Ex, 'tabnew')
openEx() keydown(':')
submitNormalModeInputText('tabedit ') submitNormalModeInputText('tabedit ')
expect(Ex.tabnew) expect(Ex.tabnew)
.toHaveBeenCalledWith(Ex.tabedit.calls[0].args...) .toHaveBeenCalledWith(Ex.tabedit.calls[0].args...)
@ -593,54 +492,29 @@ describe "the commands", ->
describe ":tabnew", -> describe ":tabnew", ->
it "opens a new tab", -> it "opens a new tab", ->
spyOn(atom.workspace, 'open') spyOn(atom.workspace, 'open')
openEx() keydown(':')
submitNormalModeInputText('tabnew') submitNormalModeInputText('tabnew')
expect(atom.workspace.open).toHaveBeenCalled() expect(atom.workspace.open).toHaveBeenCalled()
it "opens a new tab for editing when provided an argument", ->
spyOn(Ex, 'tabnew').andCallThrough()
spyOn(Ex, 'tabedit')
openEx()
submitNormalModeInputText('tabnew tabnew-test')
expect(Ex.tabedit)
.toHaveBeenCalledWith(Ex.tabnew.calls[0].args...)
describe ":split", -> describe ":split", ->
it "splits the current file upwards/downward", -> it "splits the current file upwards", ->
pane = atom.workspace.getActivePane() pane = atom.workspace.getActivePane()
if atom.config.get('ex-mode.splitbelow')
spyOn(pane, 'splitDown').andCallThrough()
filePath = projectPath('split')
editor.saveAs(filePath)
openEx()
submitNormalModeInputText('split')
expect(pane.splitDown).toHaveBeenCalled()
else
spyOn(pane, 'splitUp').andCallThrough() spyOn(pane, 'splitUp').andCallThrough()
filePath = projectPath('split') filePath = projectPath('split')
editor.saveAs(filePath) editor.saveAs(filePath)
openEx() keydown(':')
submitNormalModeInputText('split') submitNormalModeInputText('split')
expect(pane.splitUp).toHaveBeenCalled() expect(pane.splitUp).toHaveBeenCalled()
# FIXME: Should test whether the new pane contains a TextEditor # FIXME: Should test whether the new pane contains a TextEditor
# pointing to the same path # pointing to the same path
describe ":vsplit", -> describe ":vsplit", ->
it "splits the current file to the left/right", -> it "splits the current file to the left", ->
if atom.config.get('ex-mode.splitright')
pane = atom.workspace.getActivePane()
spyOn(pane, 'splitRight').andCallThrough()
filePath = projectPath('vsplit')
editor.saveAs(filePath)
openEx()
submitNormalModeInputText('vsplit')
expect(pane.splitLeft).toHaveBeenCalled()
else
pane = atom.workspace.getActivePane() pane = atom.workspace.getActivePane()
spyOn(pane, 'splitLeft').andCallThrough() spyOn(pane, 'splitLeft').andCallThrough()
filePath = projectPath('vsplit') filePath = projectPath('vsplit')
editor.saveAs(filePath) editor.saveAs(filePath)
openEx() keydown(':')
submitNormalModeInputText('vsplit') submitNormalModeInputText('vsplit')
expect(pane.splitLeft).toHaveBeenCalled() expect(pane.splitLeft).toHaveBeenCalled()
# FIXME: Should test whether the new pane contains a TextEditor # FIXME: Should test whether the new pane contains a TextEditor
@ -652,31 +526,32 @@ describe "the commands", ->
editor.setCursorBufferPosition([2, 0]) editor.setCursorBufferPosition([2, 0])
it "deletes the current line", -> it "deletes the current line", ->
openEx() keydown(':')
submitNormalModeInputText('delete') submitNormalModeInputText('delete')
expect(editor.getText()).toEqual('abc\ndef\njkl') expect(editor.getText()).toEqual('abc\ndef\njkl')
it "copies the deleted text", -> it "copies the deleted text", ->
openEx() keydown(':')
submitNormalModeInputText('delete') submitNormalModeInputText('delete')
expect(atom.clipboard.read()).toEqual('ghi\n') expect(atom.clipboard.read()).toEqual('ghi\n')
it "deletes the lines in the given range", -> it "deletes the lines in the given range", ->
processedOpStack = false processedOpStack = false
exState.onDidProcessOpStack -> processedOpStack = true exState.onDidProcessOpStack -> processedOpStack = true
openEx() keydown(':')
submitNormalModeInputText('1,2delete') submitNormalModeInputText('1,2delete')
expect(editor.getText()).toEqual('ghi\njkl') expect(editor.getText()).toEqual('ghi\njkl')
waitsFor -> processedOpStack waitsFor -> processedOpStack
editor.setText('abc\ndef\nghi\njkl') editor.setText('abc\ndef\nghi\njkl')
editor.setCursorBufferPosition([1, 1]) editor.setCursorBufferPosition([1, 1])
# For some reason, keydown(':') doesn't work here :/
atom.commands.dispatch(editorElement, 'ex-mode:open') atom.commands.dispatch(editorElement, 'ex-mode:open')
submitNormalModeInputText(',/k/delete') submitNormalModeInputText(',/k/delete')
expect(editor.getText()).toEqual('abc\n') expect(editor.getText()).toEqual('abc\n')
it "undos deleting several lines at once", -> it "undos deleting several lines at once", ->
openEx() keydown(':')
submitNormalModeInputText('-1,.delete') submitNormalModeInputText('-1,.delete')
expect(editor.getText()).toEqual('abc\njkl') expect(editor.getText()).toEqual('abc\njkl')
atom.commands.dispatch(editorElement, 'core:undo') atom.commands.dispatch(editorElement, 'core:undo')
@ -688,17 +563,17 @@ describe "the commands", ->
editor.setCursorBufferPosition([0, 0]) editor.setCursorBufferPosition([0, 0])
it "replaces a character on the current line", -> it "replaces a character on the current line", ->
openEx() keydown(':')
submitNormalModeInputText(':substitute /a/x') submitNormalModeInputText(':substitute /a/x')
expect(editor.getText()).toEqual('xbcaABC\ndefdDEF\nabcaABC') expect(editor.getText()).toEqual('xbcaABC\ndefdDEF\nabcaABC')
it "doesn't need a space before the arguments", -> it "doesn't need a space before the arguments", ->
openEx() keydown(':')
submitNormalModeInputText(':substitute/a/x') submitNormalModeInputText(':substitute/a/x')
expect(editor.getText()).toEqual('xbcaABC\ndefdDEF\nabcaABC') expect(editor.getText()).toEqual('xbcaABC\ndefdDEF\nabcaABC')
it "respects modifiers passed to it", -> it "respects modifiers passed to it", ->
openEx() keydown(':')
submitNormalModeInputText(':substitute/a/x/g') submitNormalModeInputText(':substitute/a/x/g')
expect(editor.getText()).toEqual('xbcxABC\ndefdDEF\nabcaABC') expect(editor.getText()).toEqual('xbcxABC\ndefdDEF\nabcaABC')
@ -707,7 +582,7 @@ describe "the commands", ->
expect(editor.getText()).toEqual('xbcxxBC\ndefdDEF\nabcaABC') expect(editor.getText()).toEqual('xbcxxBC\ndefdDEF\nabcaABC')
it "replaces on multiple lines", -> it "replaces on multiple lines", ->
openEx() keydown(':')
submitNormalModeInputText(':%substitute/abc/ghi') submitNormalModeInputText(':%substitute/abc/ghi')
expect(editor.getText()).toEqual('ghiaABC\ndefdDEF\nghiaABC') expect(editor.getText()).toEqual('ghiaABC\ndefdDEF\nghiaABC')
@ -715,35 +590,24 @@ describe "the commands", ->
submitNormalModeInputText(':%substitute/abc/ghi/ig') submitNormalModeInputText(':%substitute/abc/ghi/ig')
expect(editor.getText()).toEqual('ghiaghi\ndefdDEF\nghiaghi') 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", -> describe ":yank", ->
beforeEach -> beforeEach ->
editor.setText('abc\ndef\nghi\njkl') editor.setText('abc\ndef\nghi\njkl')
editor.setCursorBufferPosition([2, 0]) editor.setCursorBufferPosition([2, 0])
it "yanks the current line", -> it "yanks the current line", ->
openEx() keydown(':')
submitNormalModeInputText('yank') submitNormalModeInputText('yank')
expect(atom.clipboard.read()).toEqual('ghi\n') expect(atom.clipboard.read()).toEqual('ghi\n')
it "yanks the lines in the given range", -> it "yanks the lines in the given range", ->
openEx() keydown(':')
submitNormalModeInputText('1,2yank') submitNormalModeInputText('1,2yank')
expect(atom.clipboard.read()).toEqual('abc\ndef\n') expect(atom.clipboard.read()).toEqual('abc\ndef\n')
describe "illegal delimiters", -> describe "illegal delimiters", ->
test = (delim) -> test = (delim) ->
openEx() keydown(':')
submitNormalModeInputText(":substitute #{delim}a#{delim}x#{delim}gi") submitNormalModeInputText(":substitute #{delim}a#{delim}x#{delim}gi")
expect(atom.notifications.notifications[0].message).toEqual( expect(atom.notifications.notifications[0].message).toEqual(
"Command error: Regular expressions can't be delimited by alphanumeric characters, '\\', '\"' or '|'") "Command error: Regular expressions can't be delimited by alphanumeric characters, '\\', '\"' or '|'")
@ -760,12 +624,12 @@ describe "the commands", ->
editor.setText('abcabc\nabcabc') editor.setText('abcabc\nabcabc')
it "removes the pattern without modifiers", -> it "removes the pattern without modifiers", ->
openEx() keydown(':')
submitNormalModeInputText(":substitute/abc//") submitNormalModeInputText(":substitute/abc//")
expect(editor.getText()).toEqual('abc\nabcabc') expect(editor.getText()).toEqual('abc\nabcabc')
it "removes the pattern with modifiers", -> it "removes the pattern with modifiers", ->
openEx() keydown(':')
submitNormalModeInputText(":substitute/abc//g") submitNormalModeInputText(":substitute/abc//g")
expect(editor.getText()).toEqual('\nabcabc') expect(editor.getText()).toEqual('\nabcabc')
@ -774,7 +638,7 @@ describe "the commands", ->
editor.setText('abc,def,ghi') editor.setText('abc,def,ghi')
test = (escapeChar, escaped) -> test = (escapeChar, escaped) ->
openEx() keydown(':')
submitNormalModeInputText(":substitute/,/\\#{escapeChar}/g") submitNormalModeInputText(":substitute/,/\\#{escapeChar}/g")
expect(editor.getText()).toEqual("abc#{escaped}def#{escaped}ghi") expect(editor.getText()).toEqual("abc#{escaped}def#{escaped}ghi")
@ -789,26 +653,26 @@ describe "the commands", ->
it "uses case sensitive search if smartcase is off and the pattern is lowercase", -> it "uses case sensitive search if smartcase is off and the pattern is lowercase", ->
atom.config.set('vim-mode.useSmartcaseForSearch', false) atom.config.set('vim-mode.useSmartcaseForSearch', false)
openEx() keydown(':')
submitNormalModeInputText(':substitute/abc/ghi/g') submitNormalModeInputText(':substitute/abc/ghi/g')
expect(editor.getText()).toEqual('ghiaABC\ndefdDEF\nabcaABC') expect(editor.getText()).toEqual('ghiaABC\ndefdDEF\nabcaABC')
it "uses case sensitive search if smartcase is off and the pattern is uppercase", -> it "uses case sensitive search if smartcase is off and the pattern is uppercase", ->
editor.setText('abcaABC\ndefdDEF\nabcaABC') editor.setText('abcaABC\ndefdDEF\nabcaABC')
openEx() keydown(':')
submitNormalModeInputText(':substitute/ABC/ghi/g') submitNormalModeInputText(':substitute/ABC/ghi/g')
expect(editor.getText()).toEqual('abcaghi\ndefdDEF\nabcaABC') expect(editor.getText()).toEqual('abcaghi\ndefdDEF\nabcaABC')
it "uses case insensitive search if smartcase is on and the pattern is lowercase", -> it "uses case insensitive search if smartcase is on and the pattern is lowercase", ->
editor.setText('abcaABC\ndefdDEF\nabcaABC') editor.setText('abcaABC\ndefdDEF\nabcaABC')
atom.config.set('vim-mode.useSmartcaseForSearch', true) atom.config.set('vim-mode.useSmartcaseForSearch', true)
openEx() keydown(':')
submitNormalModeInputText(':substitute/abc/ghi/g') submitNormalModeInputText(':substitute/abc/ghi/g')
expect(editor.getText()).toEqual('ghiaghi\ndefdDEF\nabcaABC') expect(editor.getText()).toEqual('ghiaghi\ndefdDEF\nabcaABC')
it "uses case sensitive search if smartcase is on and the pattern is uppercase", -> it "uses case sensitive search if smartcase is on and the pattern is uppercase", ->
editor.setText('abcaABC\ndefdDEF\nabcaABC') editor.setText('abcaABC\ndefdDEF\nabcaABC')
openEx() keydown(':')
submitNormalModeInputText(':substitute/ABC/ghi/g') submitNormalModeInputText(':substitute/ABC/ghi/g')
expect(editor.getText()).toEqual('abcaghi\ndefdDEF\nabcaABC') expect(editor.getText()).toEqual('abcaghi\ndefdDEF\nabcaABC')
@ -818,37 +682,37 @@ describe "the commands", ->
it "uses case insensitive search if smartcase is off and \c is in the pattern", -> it "uses case insensitive search if smartcase is off and \c is in the pattern", ->
atom.config.set('vim-mode.useSmartcaseForSearch', false) atom.config.set('vim-mode.useSmartcaseForSearch', false)
openEx() keydown(':')
submitNormalModeInputText(':substitute/abc\\c/ghi/g') submitNormalModeInputText(':substitute/abc\\c/ghi/g')
expect(editor.getText()).toEqual('ghiaghi\ndefdDEF\nabcaABC') expect(editor.getText()).toEqual('ghiaghi\ndefdDEF\nabcaABC')
it "doesn't matter where in the pattern \\c is", -> it "doesn't matter where in the pattern \\c is", ->
atom.config.set('vim-mode.useSmartcaseForSearch', false) atom.config.set('vim-mode.useSmartcaseForSearch', false)
openEx() keydown(':')
submitNormalModeInputText(':substitute/a\\cbc/ghi/g') submitNormalModeInputText(':substitute/a\\cbc/ghi/g')
expect(editor.getText()).toEqual('ghiaghi\ndefdDEF\nabcaABC') 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", -> 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) atom.config.set('vim-mode.useSmartcaseForSearch', true)
openEx() keydown(':')
submitNormalModeInputText(':substitute/a\\Cbc/ghi/g') submitNormalModeInputText(':substitute/a\\Cbc/ghi/g')
expect(editor.getText()).toEqual('ghiaABC\ndefdDEF\nabcaABC') expect(editor.getText()).toEqual('ghiaABC\ndefdDEF\nabcaABC')
it "overrides \\C with \\c if \\C comes first", -> it "overrides \\C with \\c if \\C comes first", ->
atom.config.set('vim-mode.useSmartcaseForSearch', true) atom.config.set('vim-mode.useSmartcaseForSearch', true)
openEx() keydown(':')
submitNormalModeInputText(':substitute/a\\Cb\\cc/ghi/g') submitNormalModeInputText(':substitute/a\\Cb\\cc/ghi/g')
expect(editor.getText()).toEqual('ghiaghi\ndefdDEF\nabcaABC') expect(editor.getText()).toEqual('ghiaghi\ndefdDEF\nabcaABC')
it "overrides \\C with \\c if \\c comes first", -> it "overrides \\C with \\c if \\c comes first", ->
atom.config.set('vim-mode.useSmartcaseForSearch', true) atom.config.set('vim-mode.useSmartcaseForSearch', true)
openEx() keydown(':')
submitNormalModeInputText(':substitute/a\\cb\\Cc/ghi/g') submitNormalModeInputText(':substitute/a\\cb\\Cc/ghi/g')
expect(editor.getText()).toEqual('ghiaghi\ndefdDEF\nabcaABC') expect(editor.getText()).toEqual('ghiaghi\ndefdDEF\nabcaABC')
it "overrides an appended /i flag with \\C", -> it "overrides an appended /i flag with \\C", ->
atom.config.set('vim-mode.useSmartcaseForSearch', true) atom.config.set('vim-mode.useSmartcaseForSearch', true)
openEx() keydown(':')
submitNormalModeInputText(':substitute/ab\\Cc/ghi/gi') submitNormalModeInputText(':substitute/ab\\Cc/ghi/gi')
expect(editor.getText()).toEqual('ghiaABC\ndefdDEF\nabcaABC') expect(editor.getText()).toEqual('ghiaABC\ndefdDEF\nabcaABC')
@ -857,23 +721,23 @@ describe "the commands", ->
editor.setText('abcaABC\ndefdDEF\nabcaABC') editor.setText('abcaABC\ndefdDEF\nabcaABC')
it "replaces \\1 with the first group", -> it "replaces \\1 with the first group", ->
openEx() keydown(':')
submitNormalModeInputText(':substitute/bc(.{2})/X\\1X') submitNormalModeInputText(':substitute/bc(.{2})/X\\1X')
expect(editor.getText()).toEqual('aXaAXBC\ndefdDEF\nabcaABC') expect(editor.getText()).toEqual('aXaAXBC\ndefdDEF\nabcaABC')
it "replaces multiple groups", -> it "replaces multiple groups", ->
openEx() keydown(':')
submitNormalModeInputText(':substitute/a([a-z]*)aA([A-Z]*)/X\\1XY\\2Y') submitNormalModeInputText(':substitute/a([a-z]*)aA([A-Z]*)/X\\1XY\\2Y')
expect(editor.getText()).toEqual('XbcXYBCY\ndefdDEF\nabcaABC') expect(editor.getText()).toEqual('XbcXYBCY\ndefdDEF\nabcaABC')
it "replaces \\0 with the entire match", -> it "replaces \\0 with the entire match", ->
openEx() keydown(':')
submitNormalModeInputText(':substitute/ab(ca)AB/X\\0X') submitNormalModeInputText(':substitute/ab(ca)AB/X\\0X')
expect(editor.getText()).toEqual('XabcaABXC\ndefdDEF\nabcaABC') expect(editor.getText()).toEqual('XabcaABXC\ndefdDEF\nabcaABC')
describe ":set", -> describe ":set", ->
it "throws an error without a specified option", -> it "throws an error without a specified option", ->
openEx() keydown(':')
submitNormalModeInputText(':set') submitNormalModeInputText(':set')
expect(atom.notifications.notifications[0].message).toEqual( expect(atom.notifications.notifications[0].message).toEqual(
'Command error: No option specified') 'Command error: No option specified')
@ -881,7 +745,7 @@ describe "the commands", ->
it "sets multiple options at once", -> it "sets multiple options at once", ->
atom.config.set('editor.showInvisibles', false) atom.config.set('editor.showInvisibles', false)
atom.config.set('editor.showLineNumbers', false) atom.config.set('editor.showLineNumbers', false)
openEx() keydown(':')
submitNormalModeInputText(':set list number') submitNormalModeInputText(':set list number')
expect(atom.config.get('editor.showInvisibles')).toBe(true) expect(atom.config.get('editor.showInvisibles')).toBe(true)
expect(atom.config.get('editor.showLineNumbers')).toBe(true) expect(atom.config.get('editor.showLineNumbers')).toBe(true)
@ -892,7 +756,7 @@ describe "the commands", ->
atom.config.set('editor.showLineNumbers', false) atom.config.set('editor.showLineNumbers', false)
it "sets (no)list", -> it "sets (no)list", ->
openEx() keydown(':')
submitNormalModeInputText(':set list') submitNormalModeInputText(':set list')
expect(atom.config.get('editor.showInvisibles')).toBe(true) expect(atom.config.get('editor.showInvisibles')).toBe(true)
atom.commands.dispatch(editorElement, 'ex-mode:open') atom.commands.dispatch(editorElement, 'ex-mode:open')
@ -900,7 +764,7 @@ describe "the commands", ->
expect(atom.config.get('editor.showInvisibles')).toBe(false) expect(atom.config.get('editor.showInvisibles')).toBe(false)
it "sets (no)nu(mber)", -> it "sets (no)nu(mber)", ->
openEx() keydown(':')
submitNormalModeInputText(':set nu') submitNormalModeInputText(':set nu')
expect(atom.config.get('editor.showLineNumbers')).toBe(true) expect(atom.config.get('editor.showLineNumbers')).toBe(true)
atom.commands.dispatch(editorElement, 'ex-mode:open') atom.commands.dispatch(editorElement, 'ex-mode:open')
@ -913,61 +777,11 @@ describe "the commands", ->
submitNormalModeInputText(':set nonumber') submitNormalModeInputText(':set nonumber')
expect(atom.config.get('editor.showLineNumbers')).toBe(false) 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", -> describe "aliases", ->
it "calls the aliased function without arguments", -> it "calls the aliased function without arguments", ->
ExClass.registerAlias('W', 'w') ExClass.registerAlias('W', 'w')
spyOn(Ex, 'write') spyOn(Ex, 'write')
openEx() keydown(':')
submitNormalModeInputText('W') submitNormalModeInputText('W')
expect(Ex.write).toHaveBeenCalled() expect(Ex.write).toHaveBeenCalled()
@ -975,45 +789,8 @@ describe "the commands", ->
ExClass.registerAlias('W', 'write') ExClass.registerAlias('W', 'write')
spyOn(Ex, 'W').andCallThrough() spyOn(Ex, 'W').andCallThrough()
spyOn(Ex, 'write') spyOn(Ex, 'write')
openEx() keydown(':')
submitNormalModeInputText('W') submitNormalModeInputText('W')
WArgs = Ex.W.calls[0].args[0] WArgs = Ex.W.calls[0].args[0]
writeArgs = Ex.write.calls[0].args[0] writeArgs = Ex.write.calls[0].args[0]
expect(WArgs).toBe writeArgs 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')

View file

@ -2,7 +2,7 @@ helpers = require './spec-helper'
describe "the input element", -> describe "the input element", ->
[editor, editorElement, vimState, exState] = [] [editor, editorElement, vimState, exState] = []
beforeEach -> beforeEach ->
vimMode = atom.packages.loadPackage('vim-mode-plus') vimMode = atom.packages.loadPackage('vim-mode')
exMode = atom.packages.loadPackage('ex-mode') exMode = atom.packages.loadPackage('ex-mode')
waitsForPromise -> waitsForPromise ->
activationPromise = exMode.activate() activationPromise = exMode.activate()
@ -26,6 +26,7 @@ describe "the input element", ->
atom.commands.dispatch(getCommandEditor(), "core:cancel") atom.commands.dispatch(getCommandEditor(), "core:cancel")
vimState = vimMode.mainModule.getEditorState(editor) vimState = vimMode.mainModule.getEditorState(editor)
exState = exMode.mainModule.exStates.get(editor) exState = exMode.mainModule.exStates.get(editor)
vimState.activateNormalMode()
vimState.resetNormalMode() vimState.resetNormalMode()
editor.setText("abc\ndef\nabc\ndef") editor.setText("abc\ndef\nabc\ndef")
@ -69,7 +70,6 @@ describe "the input element", ->
expect(getVisibility()).toBe true expect(getVisibility()).toBe true
commandEditor = getCommandEditor() commandEditor = getCommandEditor()
model = commandEditor.getModel() model = commandEditor.getModel()
expect(model.getText()).toBe ''
model.setText('abc') model.setText('abc')
atom.commands.dispatch(commandEditor, "core:backspace") atom.commands.dispatch(commandEditor, "core:backspace")
expect(getVisibility()).toBe true expect(getVisibility()).toBe true
@ -82,11 +82,3 @@ describe "the input element", ->
expect(model.getText()).toBe '' expect(model.getText()).toBe ''
atom.commands.dispatch(commandEditor, "core:backspace") atom.commands.dispatch(commandEditor, "core:backspace")
expect(getVisibility()).toBe false 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 "'<,'>"