Compare commits

..

154 commits

Author SHA1 Message Date
Edvin Hultberg
30ad82671b
Merge pull request #225 from sarangjo/master
Add existing command information to README
2019-02-25 15:52:09 +01:00
Sarang Joshi
59cafd6b99 Add existing command information to README 2018-12-13 14:39:51 -08:00
Edvin Hultberg
ef4bb8a6ac
Merge pull request #216 from lloeki/eh-refactor-write
Refactor :write command
2018-05-31 12:21:44 +02:00
Edvin Hultberg
744ec7351e Refactor :write command
Update the logic to simplify it.

Also fix #208
2018-05-29 23:55:46 +02:00
Edvin Hultberg
23dde8c7ee 📝 update CHANGELOG
[ci skip]
2018-05-29 23:14:45 +02:00
Edvin Hultberg
a887128b9f
Merge pull request #215 from lloeki/eh-fix-enew
Fix :enew not working
2018-05-29 23:13:19 +02:00
Edvin Hultberg
a33959f829 Fix :enew not working
Fixes #214
2018-05-29 23:09:50 +02:00
Edvin Hultberg
b9b3ba3e9f
Merge pull request #209 from awilkins/only-close-buffers
Only close buffers on quitall
2018-05-03 21:20:16 +02:00
Adrian Wilkins
15038d7b0c Only close buffers on quitall 2018-03-26 13:24:05 +01:00
Edvin Hultberg
2b7e6346a5 Merge pull request #201 from bl/add-ctrl-left-bracket-to-close
Support Ctrl-[ to close ex-mode
2017-10-18 18:19:52 +02:00
Bernard Laveaux
4f1ebf8a1a Support Ctrl-[ to close ex-mode
This is merely a suggestion to also default the `ctrl-[` keymap to
close ex-mode. This behaviour is very similar to vim's default
behaviour:

```
CTRL-[          *c_CTRL-[* *c_<Esc>* *c_Esc*
<Esc>		When typed and 'x' not present in 'cpoptions', quit
		Command-line mode without executing.  In macros or when 'x'
		present in 'cpoptions', start entered command.
		Note: If your <Esc> key is hard to hit on your keyboard, train
		yourself to use CTRL-[.
```

Is very similar to the currently supported `ctrl-c`

```
CTRL-C          *c_CTRL-C*
                quit command-line without executing
```
2017-10-17 16:31:54 -04:00
Edvin Hultberg
653d62ec15 Prepare 0.18.0 release 2017-08-19 09:44:09 +02:00
Edvin Hultberg
93d0af041f update changelog 2017-08-19 09:43:43 +02:00
Edvin Hultberg
146d832e14 Update CHANGELOG.md
[ci skip]
2017-08-15 21:33:13 +02:00
Edvin Hultberg
14f0c83261 Merge pull request #191 from mkiken/gdefault
Supports Vim's gdefault option.
2017-08-15 21:32:26 +02:00
mkiken
c75395174f add gdefault option implementation test 2017-08-13 17:18:26 +09:00
mkiken
d0059a7bb2 gdefault option implementation test 2017-08-13 16:57:11 +09:00
mkiken
1a515fcb05 gdefault option set test 2017-08-13 16:47:28 +09:00
mkiken
964813a0b0 implement gdefault option 2017-08-13 16:38:06 +09:00
mkiken
2fa4584eb4 add gdefault option 2017-08-13 16:08:28 +09:00
Edvin Hultberg
23be6cc862 Update CHANGELOG.md 2017-08-08 21:12:55 +02:00
Edvin Hultberg
195396b47e Merge pull request #190 from RobertPaul01/sort
Adds :sort feature
2017-08-08 21:11:25 +02:00
Robert Paul
791c62a3ba Adds clarification comments 2017-08-08 14:02:29 -05:00
Robby
d76940dabc Adds another unit test 2017-08-06 23:40:06 -05:00
Robby
4312777508 Modifies sort function and adds a unit test 2017-08-06 23:22:13 -05:00
Robby
15296ff369 Removes accidental newline 2017-08-06 21:24:22 -05:00
Robby
117d7439ad Adds save ex-mode command 2017-08-06 21:23:41 -05:00
Edvin Hultberg
8590f5a678
⬆️ 1.17.0 2017-07-29 17:49:40 +02:00
Edvin Hultberg
3283b72394
📝 update changelog 2017-07-29 17:41:38 +02:00
Edvin Hultberg
91f748f85f
fix indenting 2017-07-29 17:40:59 +02:00
Edvin Hultberg
5301f4a5d4 Merge pull request #185 from lloeki/eh-1.19
Support Promise response in trySave
2017-07-29 17:39:11 +02:00
Edvin Hultberg
daddcf8d0f
add editorconfig 2017-07-29 17:38:07 +02:00
Edvin Hultberg
9f1a767fec 📝 update changeling 2017-07-28 14:28:03 +02:00
Edvin Hultberg
afaf152432 Merge pull request #186 from lloeki/eh-ctrl-c
Support Ctrl-C to cancel ex-mode
2017-07-28 14:27:20 +02:00
Edvin Hultberg
26ac7c50b1 Support Ctrl-C to cancel ex-mode 2017-07-27 16:13:48 +02:00
Edvin Hultberg
b5cb054b39 Support Promise response in trySave
In Atom 1.19, TextBuffer.save returns a Promise. This commit adds
support to catch this and resolve our internal callbacks when promise
resolves.
2017-07-27 15:58:50 +02:00
Edvin Hultberg
4747bcf5e8 ⬆️ version 2017-07-27 15:22:18 +02:00
Edvin Hultberg
eb7a23717a Revert back versions, apm publish strugles.. 2017-07-27 15:20:56 +02:00
Edvin Hultberg
f2ee5516e0 Prepare 0.18.0 release 2017-07-27 15:20:06 +02:00
Edvin Hultberg
3dbcf76ff7
Prepare 0.17.0 release 2017-07-27 15:18:20 +02:00
Edvin Hultberg
d0e7afe164
Prepare 0.16.0 release 2017-07-27 15:17:45 +02:00
Edvin Hultberg
78a479bf9b Update CHANGELOG.md 2017-07-27 15:16:12 +02:00
Edvin Hultberg
dfbbdadfbc
📝 update changelog 2017-07-27 15:14:39 +02:00
Edvin Hultberg
516b722f38 Merge pull request #184 from sophaskins/sophaskins-jump-to-line-1.19
use Atom 1.19 buffer API for finding the length of a buffer
2017-07-27 15:11:30 +02:00
Sophie Haskins
abb5cd207f use Atom 1.19 buffer API for finding the length of a buffer 2017-06-29 10:32:17 -04:00
jazzpi
545f13294e Prepare 0.15.0 release 2017-05-25 01:45:07 +02:00
jazzpi
9b62ba70ca Add Changelog for v0.15.0 2017-05-25 01:43:49 +02:00
Jasper v. B
def663b9cc Merge pull request #180 from jazzpi/vim-mode-plus-marks-specs
vim-mode-plus marks & specs
2017-05-25 01:38:12 +02:00
jazzpi
546aa9f95c Use vim-mode-plus in ex-input-spec as well 2017-05-25 01:33:11 +02:00
jazzpi
99dd953370 Use vim-mode-plus on Travis
Also use Linux on Travis because there are more machines available
2017-05-25 01:27:09 +02:00
jazzpi
708aa94eb0 Use vim-mode-plus in specs 2017-05-25 01:08:38 +02:00
jazzpi
02ab74465c Support vim-mode-plus marks 2017-05-25 01:08:21 +02:00
Jasper v. B
ed3417c842 Merge pull request #178 from mkiken/fix-substitute-vim-mode-plus
Support vim-mode-plus substitute command
2017-05-25 01:06:58 +02:00
Jasper v. B
daaf8b3a2d Merge pull request #173 from jmarianer/vim-mode-plus
vim-mode-plus keybinding
2017-05-25 01:05:41 +02:00
mkiken
378cf6cff4 comment refactoring. 2017-03-20 14:07:44 +09:00
mkiken
0abb61fcb8 add consumeVimModePlus. 2017-03-20 14:02:05 +09:00
mkiken
50f1beb1e9 switch searchHistory 2017-03-20 13:49:33 +09:00
mkiken
f4eb1aef7d test package.json change. 2017-03-20 12:03:35 +09:00
jazzpi
28b451f33d Prepare 0.14.0 release 2017-03-15 15:01:12 +01:00
jazzpi
cf3e250ea0 Add changelog for v0.13.1 and v0.14.0 2017-03-15 14:59:12 +01:00
Jasper v. B
26781dc9a4 Merge pull request #177 from jazzpi/fix-x-closing-atom
Fix `:x` closing Atom instead of the current pane
2017-03-15 14:52:36 +01:00
jazzpi
ccf6aa22f8 Fix :x closing Atom instead of the current pane
`x` was being matched with `xall` instead of `xit`, so add an alias.
2017-03-15 14:03:25 +01:00
Jasper v. B
e2c42a6eeb Merge pull request #172 from jmarianer/tabonly
[needs tests] Support :tabonly. Code shamelessly copied from the close-other-tabs extension.
2017-01-19 21:51:01 +01:00
Joey Marianer
ba01f8a1b1 ex-mode supports vim-mode-plus 2017-01-08 17:05:30 -08:00
Joey Marianer
cd80a163cb Support :tabonly. Code shamelessly copied from the close-other-tabs extension. 2017-01-08 14:30:44 -08:00
jazzpi
d3c3603eb4 Prepare 0.13.1 release 2016-11-04 13:07:34 +01:00
Jasper v. B
02d62547c7 Merge pull request #171 from jazzpi/limit-to-last-line
Limit addresses to the last line
2016-11-04 13:05:01 +01:00
jazzpi
d5acbd3f53 Limit addresses to the last line 2016-11-04 12:58:03 +01:00
Jasper v. B
12bb28ceda Merge pull request #166 from mcnicholls/autocomplete-non-existant-directory-fix
Autocomplete non existant directory fix
2016-08-24 12:10:09 +02:00
Michael Nicholls
0441b24c21 Fix autocompleting a non existent directory 2016-08-23 07:22:01 +01:00
Michael Nicholls
fd0aa7a6c3 Add tests for autocompleting a non existent directory
Also add tests for autocompleting a file as a directory
2016-08-23 07:21:53 +01:00
jazzpi
03a36d11cd Prepare 0.13.0 release 2016-08-16 14:42:03 +02:00
jazzpi
17be8f025a Update changelog for v0.12.0 and v0.13.0 2016-08-16 13:29:31 +02:00
Jasper v. B
8e13c77a1a Merge pull request #165 from jazzpi/fix-search-without-close
Fix search not working without a closing delimiter
2016-08-16 13:16:02 +02:00
jazzpi
0f91ab5ae0 Fix search not working without a closing delimiter 2016-08-16 13:09:05 +02:00
Jasper v. B
f22ae13cfd Merge pull request #164 from jazzpi/fix-mark-address
Fix mark addresses and add specs for addresses
2016-08-16 12:37:47 +02:00
jazzpi
31ac3a98ed Fix spec for movement to mark 2016-08-16 12:33:00 +02:00
jazzpi
3c952ccbfe Fix mark addresses and add specs for addresses
Enables properly parsing marks as addresses. Also adds some specs for
addresses by checking how ex-mode behaves when used as a motion.

Fixes #70.
2016-08-16 00:36:05 +02:00
Jasper v. B
1799706e95 Merge pull request #163 from jazzpi/smartcase-option
Add 'smartcase' option
2016-08-16 00:32:56 +02:00
jazzpi
c6efc0d46c Add 'smartcase' option 2016-08-16 00:26:37 +02:00
Jasper v. B
6bb9c45793 Merge pull request #162 from jazzpi/update-github-meta
Update GitHub Meta Files
2016-08-16 00:25:09 +02:00
Jasper v. B
6c22b3b701 Merge pull request #160 from jazzpi/visual-marks
Basic support for visual marks ('<,'>)
2016-08-16 00:24:58 +02:00
Jasper v. B
a450f5853f Update contributing guidelines
LongLiveCHIEF no longer maintains this repository and I'm watching it, so the @mentions are unnecessary. Also, only collaborators can assign issues so step #2 doesn't work anyways.
2016-08-15 23:31:08 +02:00
Jasper v. B
773c3c733d Update PR template
Remove the @mentions because I'm watching the repository and LongLiveCHIEF no longer maintains it.
2016-08-15 23:24:32 +02:00
jazzpi
5f56b62b7b Fix specs
Apparently `keydown(':')` was never actually opening the Ex line (and
`keydown('escape')` wasn't closing it so it wasn't noticeable), so now
we open it directly with `ex-mode:open`.
2016-08-15 21:21:52 +02:00
jazzpi
1b8f6238c2 Add basic support for visual marks < and >
Only works with range '<,'>. If there are multiple selections, run the
command for each one.

Closes #31.
2016-08-15 21:13:32 +02:00
jazzpi
3a104fe061 Prepare 0.12.0 release 2016-08-14 21:54:02 +02:00
Jasper v. B
aca7d3b990 Merge pull request #159 from jazzpi/split-options-set
Add splitbelow and splitright options to `:set`
2016-08-14 21:52:32 +02:00
Jasper v. B
683a592979 Merge pull request #158 from jazzpi/update-new-file-save
Update editor when saving a new file with `:w` or `:saveas`
2016-08-14 21:52:22 +02:00
Jasper v. B
25ce6b59fd Merge pull request #155 from stuartquin/tab-autocomplete
Issue #29 Command and file autocomplete
2016-08-14 21:51:43 +02:00
Stuart Quin
d609005810 Get Ex commands from instance and prototype 2016-08-11 12:55:24 +01:00
Stuart Quin
70a1987cf7 Sort commands before autocomplete 2016-08-10 08:28:33 +01:00
jazzpi
7bec719a6f Add splitbelow and splitright options to :set 2016-08-10 02:01:01 +02:00
jazzpi
2b9b2f26e5 Update editor when saving a new file with :w or :saveas
Fixes #156
2016-08-10 01:29:21 +02:00
Stuart Quin
6f03cd8bc7 Add support for ~ expansion 2016-08-03 21:34:49 +01:00
Stuart Quin
7e1e03284a Issue #29 Command and file autocomplete 2016-08-03 18:41:13 +01:00
jazzpi
a59a6f9364 Prepare 0.11.0 release 2016-08-03 12:24:34 +02:00
jazzpi
74451db75f Wrong version in CHANGELOG 2016-08-03 12:24:04 +02:00
jazzpi
ccf4c99b2b Update CHANGELOG 2016-08-03 12:21:51 +02:00
Jasper v. B
b869a49e02 Merge pull request #154 from AsaAyers/patch-1
Stop using non-standard Promise.defer()
2016-08-03 12:18:05 +02:00
Asa Ayers
959ad08591 Stop using non-standard Promise.defer()
Fixes #147
2016-08-02 15:17:41 -07:00
Brian Vanderbusch
dfa44b5fa2 Prepare 0.10.0 release 2016-06-17 18:27:37 -05:00
Brian Vanderbusch
e4462b6584 Merge branch 'prepare-release' 2016-06-17 18:10:08 -05:00
Brian Vanderbusch
1c9d6a2fea prepare release v0.9.0 2016-06-17 18:09:24 -05:00
Jasper v. B
96bdf4143c Merge pull request #148 from brebory/bugfix/brebory/tabnew-with-args
Fix tabnew with arguments
2016-06-17 00:12:20 +02:00
Brendon Roberto
4424eec4cc Fix issue with tabnew forwarding args to tabedit 2016-06-16 15:21:20 -04:00
Brendon Roberto
cd4fb6f359 Add tests for tabnew command with arguments 2016-06-16 15:21:11 -04:00
Brian Vanderbusch
a405147fc5 bump version 2016-05-18 10:32:50 -05:00
Brian Vanderbusch
e6ab4167c9 Contributing guidelines (#140)
* Create Pull Request Template

* added contributing guidelines

- includes a pull request template

* cleanup duped PR docs

* finish fix of duped PR template

* updated contributing guidelines per #140

* comma cleanup, needs tests info update
2016-05-18 10:29:22 -05:00
Brian Vanderbusch
bca120d98d added #133 features to changelog 2016-04-30 13:35:16 -05:00
Xiaolong Wang
bad84d8c51 Merge branch 'master' of github.com:dragonxlwang/ex-mode
add test
2016-04-24 15:40:30 -05:00
Xiaolong Wang
286db320a8 add test 2016-04-24 15:39:57 -05:00
Xiaolong Wang
b763104cb2 adding support for splitright and splitbelow 2016-04-24 15:39:18 -05:00
Brian Vanderbusch
a6c979e0a4 changelog for 0.9.0 release 2016-04-24 14:37:45 -05:00
Ryan Mitchell
2a5fe2c382 Feature/yanking (#138)
* Support yanking

* Remove unneeded code from yank spec
2016-04-24 13:56:22 -05:00
Brian Vanderbusch
0de4c800ea Merge pull request #139 from posgarou/bugfix/copy_on_deletion
Copy to text to clipboard on delete
2016-04-24 13:56:03 -05:00
Ryan Mitchell
f8396fb4e4 Copy to text to clipboard on delete 2016-04-23 21:33:04 -04:00
Xiaolong Wang
fdd8b36e62 adding support for splitright and splitbelow 2016-03-18 14:09:14 -05:00
jazzpi
145446b8de Make aliasing more prominent 2016-02-18 18:12:33 +01:00
jazzpi
ded67a40b5 Prepare 0.8.0 release 2016-01-03 13:33:14 +01:00
jazzpi
79a4a8986c Update changelog for 0.8.0 2016-01-03 13:29:18 +01:00
jazzpi
86570f76cc Remove "looking for new maintainer" disclaimer 2016-01-03 13:22:36 +01:00
jazzpi
c16576f08b Merge pull request #120 from caiocutrim:master
Add :wall, :quitall and :wqall commands
2016-01-03 13:16:41 +01:00
jazzpi
e17a6e5533 Add specs for :wall, :quitall and :wqall 2016-01-03 13:15:14 +01:00
jazzpi
059719bee4 Fix :quitall 2015-12-28 14:04:19 +01:00
jazzpi
d6afe394ef Merge branch 'master' of https://github.com/caiocutrim/ex-mode into caiocutrim-master 2015-12-28 13:14:50 +01:00
jazzpi
b55d2857b0 Merge pull request #113 from GertjanReynaert:master
Add option to register alias keys in atom init config
2015-12-28 13:02:23 +01:00
jazzpi
31875cff79 Add specs for aliases 2015-12-28 13:01:56 +01:00
jazzpi
701f27130f Make Ex.registerAlias accessible from the outside 2015-12-28 12:46:26 +01:00
jazzpi
155ffcaa5a Merge branch 'master' of https://github.com/GertjanReynaert/ex-mode into GertjanReynaert-master 2015-12-28 12:39:47 +01:00
jazzpi
299f83983d Add specs for the input element 2015-12-28 12:38:00 +01:00
Jasper v. B
d2d66f5260 Merge pull request #109 from shamrin/issue108
backspace over empty `:` now cancels ex-mode
2015-12-22 12:46:30 +01:00
jazzpi
19f1a74812 Merge pull request #118 from jazzpi/rework-commands
Rework command calling, improve :substitute
2015-12-08 13:13:11 +01:00
Jasper v. B
94ff33716f Merge pull request #121 from bakert/saveas
:saveas command and spec test.
2015-12-08 12:54:51 +01:00
Thomas David Baker
3c78f9c985 :saveas command and spec test. 2015-12-07 11:20:57 -08:00
Caio Cutrim
10fbdfe969 fixed saveAllThenQuit function 2015-11-26 17:16:24 -03:00
Caio Cutrim
4ec1c56077 I added :wa, :qa, :waq shortcuts commands 2015-11-26 12:20:32 -03:00
jazzpi
ddbdb861fb Improve :substitute
Rework the parsing algorithm so that it works (mostly)
without using RegEx's. This allows for replacing with an
empty string and escape sequences (\t, \n, \r).

Fixes #71, #93, #117
2015-11-21 15:51:42 +01:00
jazzpi
af0ba7c01c Improve format for calling commands
Commands (from the Ex class) are now called with an object
containing the range, arguments, vim state, ex state and
editor instead of a long list of arguments.
2015-11-21 14:48:13 +01:00
472ec2140e looking for new maintainer 2015-11-19 14:42:29 +01:00
Gertjan Reynaert
14d234d182 Add option to register aliasses 2015-11-17 16:39:21 +01:00
Alexey Shamrin
e0ee339bf6 backspace over empty : now cancels ex-mode
fixes #108
2015-10-31 05:33:06 +03:00
5773c8f47b Merge pull request #102 from jacwah/sub-sep
Don't allow :s delimiters not allowed by vim
2015-09-23 20:30:39 +02:00
Jacob Wahlgren
77d3fa46d5 Refactor illegal delimiters specs 2015-09-22 00:50:21 +02:00
Jacob Wahlgren
e2841dc26c Don't allow :s delimiters not allowed by vim
"Instead of the '/' which surrounds the pattern and replacement string,
you can use any other single-byte character, but not an alphanumeric
character, '\', '"'' or '|'."
- http://vimdoc.sourceforge.net/htmldoc/change.html#:substitute
2015-09-22 00:35:36 +02:00
728ccaa5f9 Prepare 0.7.0 release 2015-08-03 12:21:31 +02:00
c0c220c22e distinguish prefix from input 2015-08-03 12:21:01 +02:00
72d80ed4e9 prefix vim-mode's search 2015-08-03 12:20:26 +02:00
2a2669f46f Merge pull request #89 from Po1o/master
Make cmd-line in ex-mode look like in vim-mode
2015-08-03 12:10:58 +02:00
Polo
d0cbbb5d15 Make cmd-line in ex-mode look like in vim-mode
Changed the style of .command-mode-input so that it looks like
.normal-mode-input from vim-mode.
This makes ex-mode more consistent with vim-mode
2015-08-01 15:37:02 +02:00
962e4a35ba travis: install vim-mode 2015-07-30 08:55:14 +02:00
59fb0ddf1f enable Travis CI 2015-07-30 08:50:53 +02:00
edea63a575 Merge pull request #83 from jazzpi/specs
Add specs; minor changes to some commands
2015-07-30 08:49:20 +02:00
jazzpi
42a44ee9e1 Add specs; minor changes to some commands
`:tabedit` now works as an alias to `:edit` with a path and
  as an alias to `:tabnew` without.
`:tabnew` is a new command that opens a new tab with a new file if used
 without a path and works as an alias to `:tabedit` with one.
`:tabclose` now works as a proper alias to `:quit` (i.e. passes the arguments)
`:edit` now works more like before - it opens a given path in a new tab.
 It also doesn't do anything if the file was modified since the last commit,
 unless forced by using `:edit!`
`:write` works properly again and doesn't overwrite files, unless forced by
 using `:write!`
`:xit` is now called `:xit` and not just `:x`
`:substitute` now properly replaces multiple groups (`:s/(a)b(c)/X\1\2X\0`)
2015-07-29 19:13:13 +02:00
25 changed files with 2161 additions and 244 deletions

9
.editorconfig Normal file
View file

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

23
.github/CONTRIBUTING.md vendored Normal file
View file

@ -0,0 +1,23 @@
# 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

15
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

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

45
.travis.yml Normal file
View file

@ -0,0 +1,45 @@
## Project specific config ###
language: generic
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
notifications:
email:
on_success: never
on_failure: change
branches:
only:
- master
git:
depth: 10
sudo: false
addons:
apt:
packages:
- build-essential
- git
- libgnome-keyring-dev
- libsecret-1-dev
- fakeroot

View file

@ -1,3 +1,69 @@
## (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)
* Added contributor guidelines, including a pull request template
* Added ability to control splitting with `splitright`, and `splitbelow` (@dragonxwang)
### Fixes
* delete commands now add text to clipboard, ex `:1,4d`
## 0.8.0
* Don't allow :s delimiters not allowed by vim (@jacwah)
* Backspace over empty `:` now cancels ex-mode (@shamrin)
* Added option to register alias keys in atom init config (@GertjanReynaert)
* Allow `:substitute` to replace empty with an empty string and replacing the last search item (@jazzpi)
* Added `:wall`, `:quitall` and `:wqall` commands (@caiocutrim)
* Added `:saveas` command (@bakert)
## 0.6.0 ## 0.6.0
* No project/multiple projects paths (uses first one) * No project/multiple projects paths (uses first one)
* Support for :set * Support for :set

43
README.md Normal file → Executable file
View file

@ -4,7 +4,9 @@ ex-mode for Atom's vim-mode
## Use ## Use
Install both [vim-mode](https://github.com/atom/vim-mode) and ex-mode. Type `:` in command mode. Enter `w` or `write`. Install both [vim-mode-plus](https://github.com/t9md/atom-vim-mode-plus) (or
the deprecated `vim-mode`) and ex-mode. Type `:` in command mode. Enter `w` or
`write`.
## Extend ## Extend
@ -18,7 +20,44 @@ atom.packages.onDidActivatePackage (pack) ->
Ex.registerCommand 'z', -> console.log("Zzzzzz...") Ex.registerCommand 'z', -> console.log("Zzzzzz...")
``` ```
See `lib/ex.coffee` for some examples commands. Contributions are very welcome! You can also add aliases:
```coffee
atom.packages.onDidActivatePackage (pack) ->
if pack.name == 'ex-mode'
Ex = pack.mainModule.provideEx()
Ex.registerAlias 'WQ', 'wq'
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 <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,5 +7,10 @@
# 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'

80
lib/autocomplete.coffee Normal file
View file

@ -0,0 +1,80 @@
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,33 +5,47 @@ CommandError = require './command-error'
class Command class Command
constructor: (@editor, @exState) -> constructor: (@editor, @exState) ->
@viewModel = new ExViewModel(@) @selections = @exState.getSelections()
@viewModel = new ExViewModel(@, Object.keys(@selections).length > 0)
parseAddr: (str, curPos) -> parseAddr: (str, cursor) ->
row = cursor.getBufferRow()
if str is '.' if str is '.'
addr = curPos.row addr = 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.
addr = @editor.getBuffer().lines.length - 1 # The two ways of getting length let us support Atom 1.19's new buffer
# implementation (https://github.com/atom/atom/pull/14435) and still
# support 1.18 and below
buffer = @editor.getBuffer()
addr = (buffer.getLineCount?() ? buffer.lines.length) - 1
else if str[0] in ["+", "-"] else if str[0] in ["+", "-"]
addr = curPos.row + @parseOffset(str) addr = 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.marks[str[1]] mark = @vimState.mark.marks[str[1]]
unless mark? unless mark?
throw new CommandError("Mark #{str} not set.") throw new CommandError("Mark #{str} not set.")
addr = mark.bufferMarker.range.end.row addr = mark.getEndBufferPosition().row
else if str[0] is "/" else if str[0] is "/"
addr = Find.findNextInBuffer(@editor.buffer, curPos, str[1...-1]) str = str[1...]
if str[str.length-1] is "/"
str = str[...-1]
addr = Find.scanEditor(str, @editor, cursor.getCurrentLineBufferRange().end)[0]
unless addr? unless addr?
throw new CommandError("Pattern not found: #{str[1...-1]}") throw new CommandError("Pattern not found: #{str}")
addr = addr.start.row
else if str[0] is "?" else if str[0] is "?"
addr = Find.findPreviousInBuffer(@editor.buffer, curPos, str[1...-1]) 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
@ -63,7 +77,9 @@ class Command
return return
# Step 4: Address parsing # Step 4: Address parsing
lastLine = @editor.getBuffer().lines.length - 1 # see comment in parseAddr about line length
buffer = @editor.getBuffer()
lastLine = (buffer.getLineCount?() ? buffer.lines.length) - 1
if cl[0] is '%' if cl[0] is '%'
range = [0, lastLine] range = [0, lastLine]
cl = cl[1..] cl = cl[1..]
@ -75,8 +91,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
)? )?
@ -95,34 +111,45 @@ class Command
[match, addr1, off1, addr2, off2] = cl.match(addrPattern) [match, addr1, off1, addr2, off2] = cl.match(addrPattern)
curPos = @editor.getCursorBufferPosition() cursor = @editor.getLastCursor()
if addr1? # Special case: run command on selection. This can't be handled by simply
address1 = @parseAddr(addr1, curPos) # 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 else
# If no addr1 is given (,+3), assume it is '.' runOverSelections = false
address1 = curPos.row if addr1?
if off1? address1 = @parseAddr(addr1, cursor)
address1 += @parseOffset(off1) else
# If no addr1 is given (,+3), assume it is '.'
address1 = cursor.getBufferRow()
if off1?
address1 += @parseOffset(off1)
address1 = 0 if address1 is -1 address1 = 0 if address1 is -1
address1 = lastLine if address1 > lastLine
if address1 < 0 or address1 > lastLine if address1 < 0
throw new CommandError('Invalid range') throw new CommandError('Invalid range')
if addr2? if addr2?
address2 = @parseAddr(addr2, curPos) address2 = @parseAddr(addr2, cursor)
if off2? if off2?
address2 += @parseOffset(off2) address2 += @parseOffset(off2)
if address2 < 0 or address2 > lastLine address2 = 0 if address2 is -1
throw new CommandError('Invalid range') address2 = lastLine if address2 > lastLine
if address2 < address1 if address2 < 0
throw new CommandError('Backwards range given') throw new CommandError('Invalid range')
if address2 < address1
throw new CommandError('Backwards range given')
range = [address1, if address2? then address2 else address1] range = [address1, if address2? then address2 else address1]
cl = cl[match?.length..] cl = cl[match?.length..]
# Step 5: Leading blanks are ignored # Step 5: Leading blanks are ignored
cl = cl.trimLeft() cl = cl.trimLeft()
@ -149,9 +176,7 @@ 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
if (func = Ex.singleton()[command])? unless (func = Ex.singleton()[command])?
func(range, args)
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)
@ -161,9 +186,16 @@ class Command
command = matching[0] command = matching[0]
func = Ex.singleton()[command] func = Ex.singleton()[command]
if func?
func(range, args) 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 else
throw new CommandError("Not an editor command: #{input.characters}") func({ range, args, @vimState, @exState, @editor })
else
throw new CommandError("Not an editor command: #{input.characters}")
module.exports = Command module.exports = Command

View file

@ -30,7 +30,33 @@ module.exports = ExMode =
provideEx: -> provideEx: ->
registerCommand: Ex.registerCommand.bind(Ex) registerCommand: Ex.registerCommand.bind(Ex)
registerAlias: Ex.registerAlias.bind(Ex)
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,7 +15,8 @@ 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') @editorElement.classList.add('editor') # Consider this deprecated!
@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)
@ -36,11 +37,17 @@ class ExCommandModeInputElement extends HTMLDivElement
@confirm() if e.newText @confirm() if e.newText
else else
atom.commands.add(@editorElement, 'editor:newline', @confirm.bind(this)) atom.commands.add(@editorElement, 'editor:newline', @confirm.bind(this))
atom.commands.add(@editorElement, 'core:backspace', @backspace.bind(this))
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: ->
# pressing backspace over empty `:` should cancel ex-mode
@cancel() unless @editorElement.getModel().getText().length
confirm: -> confirm: ->
@value = @editorElement.getModel().getText() or @defaultText @value = @editorElement.getModel().getText() or @defaultText
@viewModel.confirm(this) @viewModel.confirm(this)

View file

@ -34,6 +34,9 @@ class ExState
onDidFailToExecute: (fn) -> onDidFailToExecute: (fn) ->
@emitter.on('failed-to-execute', fn) @emitter.on('failed-to-execute', fn)
onDidProcessOpStack: (fn) ->
@emitter.on('processed-op-stack', fn)
pushOperations: (operations) -> pushOperations: (operations) ->
@opStack.push operations @opStack.push operations
@ -55,5 +58,15 @@ class ExState
else else
throw e throw e
@clearOpStack() @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 module.exports = ExState

View file

@ -1,20 +1,41 @@
{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) -> constructor: (@exCommand, withSelection) ->
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

@ -2,13 +2,29 @@ path = require 'path'
CommandError = require './command-error' CommandError = require './command-error'
fs = require 'fs-plus' fs = require 'fs-plus'
VimOption = require './vim-option' 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) -> trySave = (func) ->
deferred = Promise.defer() deferred = defer()
try try
func() response = func()
deferred.resolve()
if response instanceof Promise
response.then ->
deferred.resolve()
else
deferred.resolve()
catch error catch error
if error.message.endsWith('is a directory') if error.message.endsWith('is a directory')
atom.notifications.addWarning("Unable to save file: #{error.message}") atom.notifications.addWarning("Unable to save file: #{error.message}")
@ -32,36 +48,71 @@ trySave = (func) ->
deferred.promise deferred.promise
saveAs = (filePath) -> saveAs = (filePath, editor) ->
editor = atom.workspace.getActiveTextEditor()
fs.writeFileSync(filePath, editor.getText()) fs.writeFileSync(filePath, editor.getText())
getFullPath = (filePath) -> getFullPath = (filePath) ->
filePath = fs.normalize(filePath)
if path.isAbsolute(filePath) if path.isAbsolute(filePath)
fullPath = filePath filePath
else if atom.project.getPaths().length == 0 else if atom.project.getPaths().length == 0
fullPath = path.join('~', filePath) path.join(fs.normalize('~'), filePath)
else else
fullPath = path.join(atom.project.getPaths()[0], filePath) path.join(atom.project.getPaths()[0], filePath)
return fs.normalize(fullPath) replaceGroups = (groups, string) ->
replaced = ''
escaped = false
while (char = string[0])?
string = string[1..]
if char is '\\' and not escaped
escaped = true
else if /\d/.test(char) and escaped
escaped = false
group = groups[parseInt(char)]
group ?= ''
replaced += group
else
escaped = false
replaced += char
replaceGroups = (groups, replString) -> replaced
arr = replString.split('')
offset = 0
cdiff = 0
while (m = replString.match(/(?:[^\\]|^)\\(\d)/))? getSearchTerm = (term, modifiers = {'g': true}) ->
group = groups[m[1]] or ''
i = replString.indexOf(m[0])
l = m[0].length
replString = replString.slice(i + l)
arr[i + offset...i + offset + l] = (if l is 2 then '' else m[0][0]) +
group
arr = arr.join('').split ''
offset += i + l - group.length
return arr.join('').replace(/\\\\(\d)/, '\\$1') 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)
class Ex class Ex
@singleton: => @singleton: =>
@ -70,26 +121,43 @@ class Ex
@registerCommand: (name, func) => @registerCommand: (name, func) =>
@singleton()[name] = func @singleton()[name] = func
@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: -> quit: ->
atom.workspace.getActivePane().destroyActiveItem() atom.workspace.getActivePane().destroyActiveItem()
quitall: ->
if !atom.config.get('ex-mode.onlyCloseBuffers')
atom.close()
else
atom.workspace.getTextEditors().forEach (editor) ->
editor.destroy()
q: => @quit() q: => @quit()
tabedit: (range, args) -> qall: => @quitall()
args = args.trim()
filePaths = args.split(' ') tabedit: (args) =>
pane = atom.workspace.getActivePane() if args.args.trim() isnt ''
if filePaths? and filePaths.length > 0 @edit(args)
for file in filePaths
do -> atom.workspace.openURIInPane file, pane
else else
atom.workspace.openURIInPane('', pane) @tabnew(args)
tabe: (args...) => @tabedit(args...) tabe: (args) => @tabedit(args)
tabnew: (args...) => @tabedit(args...) tabnew: (args) =>
if args.args.trim() is ''
atom.workspace.open()
else
@tabedit(args)
tabclose: => @quit() tabclose: (args) => @quit(args)
tabc: => @tabclose() tabc: => @tabclose()
@ -105,148 +173,260 @@ class Ex
tabp: => @tabprevious() tabp: => @tabprevious()
edit: (range, filePath) -> tabonly: ->
filePath = fs.normalize(filePath.trim()) 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 '!'
force = true
filePath = filePath[1..].trim()
else
force = false
if editor.isModified() and not force
throw new CommandError('No write since last change (add ! to override)')
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')
buffer = atom.workspace.getActiveTextEditor().buffer
if buffer.isModified()
throw new CommandError('Unsaved file')
if filePath is ''
filePath = buffer.getPath()
buffer.setPath(getFullPath(filePath))
buffer.load()
e: (args...) => @edit(args...) if filePath.length isnt 0
fullPath = getFullPath(filePath)
if fullPath is editor.getPath()
editor.getBuffer().reload()
else
atom.workspace.open(fullPath)
else
if editor.getPath()?
editor.getBuffer().reload()
else
throw new CommandError('No file name')
e: (args) => @edit(args)
enew: -> enew: ->
buffer = atom.workspace.getActiveTextEditor().buffer atom.workspace.open()
buffer.setPath(undefined)
buffer.load()
write: (range, filePath) -> write: ({ range, args, editor, saveas }) ->
filePath = filePath.trim() saveas ?= false
deferred = Promise.defer() filePath = args
if filePath[0] is '!'
pane = atom.workspace.getActivePane() force = true
editor = atom.workspace.getActiveTextEditor() filePath = filePath[1..]
if editor.getPath()?
if filePath.length > 0
editorPath = editor.getPath()
fullPath = getFullPath(filePath)
trySave(-> saveAs(fullPath))
.then editor.buffer.setPath(editorPath)
.then deferred.resolve
else
trySave(-> editor.save())
.then deferred.resolve
else else
if filePath.length > 0 force = false
fullPath = getFullPath(filePath)
trySave(-> saveAs(fullPath))
.then -> editor.buffer.setPath(fullPath)
.then deferred.resolve
else
fullPath = atom.showSaveDialogSync()
if fullPath?
trySave(-> editor.saveAs(fullPath))
.then -> editor.buffer.setPath(fullPath)
.then deferred.resolve
deferred.promise filePath = filePath.trim()
if filePath.indexOf(' ') isnt -1
throw new CommandError('Only one file name allowed')
w: (args...) => deferred = defer()
@write(args...)
wq: (args...) => editor = atom.workspace.getActiveTextEditor()
@write(args...).then => @quit()
x: (args...) => @wq(args...) # Case 1; path is provided
if filePath.length isnt 0
fullPath = getFullPath filePath
wa: -> # 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
throw new CommandError("File exists (add ! to override)")
# Case 2; no path provided, call editor save.
editor = atom.workspace.getActiveTextEditor()
# Does the current buffer exist?
if editor.getPath()? and fs.existsSync(editor.getPath())
trySave(-> editor.save()).then(deferred.promise)
else
# Cant see what the better API is but Pane.saveActiveItemAs() is the only call
# I could find that states it will ask the user.
trySave(-> atom.workspace.getActivePane().saveActiveItemAs()).then(deferred.promise)
return deferred.promise
wall: ->
atom.workspace.saveAll() atom.workspace.saveAll()
split: (range, args) -> w: (args) =>
@write(args)
wq: (args) =>
@write(args).then(=> @quit())
wa: =>
@wall()
wqall: =>
@wall()
@quitall()
wqa: =>
@wqall()
xall: =>
@wqall()
xa: =>
@wqall()
saveas: (args) =>
args.saveas = true
@write(args)
xit: (args) => @wq(args)
x: (args) => @xit(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 filePaths? and filePaths.length > 0 if atom.config.get('ex-mode.splitbelow')
newPane = pane.splitUp() if filePaths? and filePaths.length > 0
for file in filePaths newPane = pane.splitDown()
do -> for file in filePaths
atom.workspace.openURIInPane file, newPane do ->
atom.workspace.openURIInPane file, newPane
else
pane.splitDown(copyActiveItem: true)
else else
pane.splitUp(copyActiveItem: true) if filePaths? and filePaths.length > 0
newPane = pane.splitUp()
for file in filePaths
do ->
atom.workspace.openURIInPane file, newPane
else
pane.splitUp(copyActiveItem: true)
sp: (args...) => @split(args...)
substitute: (range, args) -> sp: (args) => @split(args)
args = args.trimLeft()
delim = args[0] substitute: ({ range, args, editor, vimState }) ->
if /[a-z]/i.test(delim) args_ = args.trimLeft()
delim = args_[0]
if /[a-z1-9\\"|]/i.test(delim)
throw new CommandError( throw new CommandError(
"Regular expressions can't be delimited by letters") "Regular expressions can't be delimited by alphanumeric characters, '\\', '\"' or '|'")
delimRE = new RegExp("[^\\\\]#{delim}") args_ = args_[1..]
spl = [] escapeChars = {t: '\t', n: '\n', r: '\r'}
args_ = args[1..] parsed = ['', '', '']
while (i = args_.search(delimRE)) isnt -1 parsing = 0
spl.push args_[..i] escaped = false
args_ = args_[i + 2..] while (char = args_[0])?
if args_.length is 0 and spl.length is 3 args_ = args_[1..]
throw new CommandError('Trailing characters') if char is delim
else if args_.length isnt 0 if not escaped
spl.push args_ parsing++
if spl.length > 3 if parsing > 2
throw new CommandError('Trailing characters') throw new CommandError('Trailing characters')
spl[1] ?= '' else
spl[2] ?= '' parsed[parsing] = parsed[parsing][...-1]
notDelimRE = new RegExp("\\\\#{delim}", 'g') else if char is '\\' and not escaped
spl[0] = spl[0].replace(notDelimRE, delim) parsed[parsing] += char
spl[1] = spl[1].replace(notDelimRE, delim) escaped = true
else if parsing == 1 and escaped and escapeChars[char]?
parsed[parsing] += escapeChars[char]
escaped = false
else
escaped = false
parsed[parsing] += char
[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')
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)
try try
pattern = new RegExp(spl[0], spl[2]) 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 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
# vim only says 'Trailing characters', but let's be more descriptive
throw new CommandError("Invalid flags: #{e.message[45..]}") throw new CommandError("Invalid flags: #{e.message[45..]}")
else if e.message.indexOf('Invalid regular expression: ') is 0 else if e.message.indexOf('Invalid regular expression: ') is 0
throw new CommandError("Invalid RegEx: #{e.message[27..]}") throw new CommandError("Invalid RegEx: #{e.message[27..]}")
else else
throw e throw e
buffer = atom.workspace.getActiveTextEditor().buffer editor.transact ->
atom.workspace.getActiveTextEditor().transact ->
for line in [range[0]..range[1]] for line in [range[0]..range[1]]
buffer.scanInRange(pattern, editor.scanInBufferRange(
[[line, 0], [line, buffer.lines[line].length]], patternRE,
({match, matchText, range, stop, replace}) -> [[line, 0], [line + 1, 0]],
replace(replaceGroups(match[..], spl[1])) ({match, replace}) ->
replace(replaceGroups(match[..], substition))
) )
s: (args...) => @substitute(args...) s: (args) => @substitute(args)
vsplit: (range, args) -> vsplit: ({ 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 filePaths? and filePaths.length > 0 if atom.config.get('ex-mode.splitright')
newPane = pane.splitLeft() if filePaths? and filePaths.length > 0
for file in filePaths newPane = pane.splitRight()
do -> for file in filePaths
atom.workspace.openURIInPane file, newPane do ->
atom.workspace.openURIInPane file, newPane
else
pane.splitRight(copyActiveItem: true)
else else
pane.splitLeft(copyActiveItem: true) if filePaths? and filePaths.length > 0
newPane = pane.splitLeft()
for file in filePaths
do ->
atom.workspace.openURIInPane file, newPane
else
pane.splitLeft(copyActiveItem: true)
vsp: (args...) => @vsplit(args...) vsp: (args) => @vsplit(args)
delete: (range) -> delete: ({ range }) ->
range = [[range[0], 0], [range[1] + 1, 0]] range = [[range[0], 0], [range[1] + 1, 0]]
atom.workspace.getActiveTextEditor().buffer.setTextInRange(range, '') editor = atom.workspace.getActiveTextEditor()
set: (range, args) -> text = editor.getTextInBufferRange(range)
atom.clipboard.write(text)
editor.buffer.setTextInRange(range, '')
yank: ({ range }) ->
range = [[range[0], 0], [range[1] + 1, 0]]
txt = atom.workspace.getActiveTextEditor().getTextInBufferRange(range)
atom.clipboard.write(txt);
set: ({ range, args }) ->
args = args.trim() args = args.trim()
if args == "" if args == ""
throw new CommandError("No option specified") throw new CommandError("No option specified")
@ -269,4 +449,24 @@ 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,3 +1,40 @@
_ = 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 = []
@ -23,4 +60,26 @@ 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

@ -1,11 +1,11 @@
ExCommandModeInputElement = require './ex-command-mode-input-element' ExNormalModeInputElement = require './ex-normal-mode-input-element'
class ViewModel class ViewModel
constructor: (@command, opts={}) -> constructor: (@command, opts={}) ->
{@editor, @exState} = @command {@editor, @exState} = @command
@view = new ExCommandModeInputElement().initialize(@, opts) @view = new ExNormalModeInputElement().initialize(@, opts)
@editor.commandModeInputView = @view @editor.normalModeInputView = @view
@exState.onDidFailToExecute => @view.remove() @exState.onDidFailToExecute => @view.remove()
@done = false @done = false

View file

@ -20,4 +20,46 @@ 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.6.2", "version": "0.18.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,6 +23,11 @@
"versions": { "versions": {
"^0.1.0": "consumeVim" "^0.1.0": "consumeVim"
} }
},
"vim-mode-plus": {
"versions": {
"^0.1.0": "consumeVimModePlus"
}
} }
}, },
"providedServices": { "providedServices": {
@ -32,5 +37,8 @@
"0.20.0": "provideEx" "0.20.0": "provideEx"
} }
} }
},
"devDependencies": {
"node-uuid": "^1.4.2"
} }
} }

View file

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

1019
spec/ex-commands-spec.coffee Normal file

File diff suppressed because it is too large Load diff

92
spec/ex-input-spec.coffee Normal file
View file

@ -0,0 +1,92 @@
helpers = require './spec-helper'
describe "the input element", ->
[editor, editorElement, vimState, exState] = []
beforeEach ->
vimMode = atom.packages.loadPackage('vim-mode-plus')
exMode = atom.packages.loadPackage('ex-mode')
waitsForPromise ->
activationPromise = exMode.activate()
helpers.activateExMode()
activationPromise
runs ->
spyOn(exMode.mainModule.globalExState, 'setVim').andCallThrough()
waitsForPromise ->
vimMode.activate()
waitsFor ->
exMode.mainModule.globalExState.setVim.calls.length > 0
runs ->
helpers.getEditorElement (element) ->
atom.commands.dispatch(element, "ex-mode:open")
editorElement = element
editor = editorElement.getModel()
atom.commands.dispatch(getCommandEditor(), "core:cancel")
vimState = vimMode.mainModule.getEditorState(editor)
exState = exMode.mainModule.exStates.get(editor)
vimState.resetNormalMode()
editor.setText("abc\ndef\nabc\ndef")
afterEach ->
atom.commands.dispatch(getCommandEditor(), "core:cancel")
getVisibility = () ->
editor.normalModeInputView.panel.visible
getCommandEditor = () ->
editor.normalModeInputView.editorElement
it "opens with 'ex-mode:open'", ->
atom.commands.dispatch(editorElement, "ex-mode:open")
expect(getVisibility()).toBe true
it "closes with 'core:cancel'", ->
atom.commands.dispatch(editorElement, "ex-mode:open")
expect(getVisibility()).toBe true
atom.commands.dispatch(getCommandEditor(), "core:cancel")
expect(getVisibility()).toBe false
it "closes when opening and then pressing backspace", ->
atom.commands.dispatch(editorElement, "ex-mode:open")
expect(getVisibility()).toBe true
atom.commands.dispatch(getCommandEditor(), "core:backspace")
expect(getVisibility()).toBe false
it "doesn't close when there is text and pressing backspace", ->
atom.commands.dispatch(editorElement, "ex-mode:open")
expect(getVisibility()).toBe true
commandEditor = getCommandEditor()
model = commandEditor.getModel()
model.setText('abc')
atom.commands.dispatch(commandEditor, "core:backspace")
expect(getVisibility()).toBe true
expect(model.getText()).toBe 'ab'
it "closes when there is text and pressing backspace multiple times", ->
atom.commands.dispatch(editorElement, "ex-mode:open")
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
expect(model.getText()).toBe 'ab'
atom.commands.dispatch(commandEditor, "core:backspace")
expect(getVisibility()).toBe true
expect(model.getText()).toBe 'a'
atom.commands.dispatch(commandEditor, "core:backspace")
expect(getVisibility()).toBe true
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 "'<,'>"

View file

@ -1,62 +0,0 @@
ExMode = require '../lib/ex-mode'
# Use the command `window:run-package-specs` (cmd-alt-ctrl-p) to run specs.
#
# To run a specific `it` or `describe` block add an `f` to the front (e.g. `fit`
# or `fdescribe`). Remove the `f` to unfocus the block.
describe "ExMode", ->
[workspaceElement, activationPromise] = []
beforeEach ->
workspaceElement = atom.views.getView(atom.workspace)
activationPromise = atom.packages.activatePackage('ex-mode')
describe "when the ex-mode:toggle event is triggered", ->
it "hides and shows the modal panel", ->
# Before the activation event the view is not on the DOM, and no panel
# has been created
expect(workspaceElement.querySelector('.ex-mode')).not.toExist()
# This is an activation event, triggering it will cause the package to be
# activated.
atom.commands.dispatch workspaceElement, 'ex-mode:toggle'
waitsForPromise ->
activationPromise
runs ->
expect(workspaceElement.querySelector('.ex-mode')).toExist()
exModeElement = workspaceElement.querySelector('.ex-mode')
expect(exModeElement).toExist()
exModePanel = atom.workspace.panelForItem(exModeElement)
expect(exModePanel.isVisible()).toBe true
atom.commands.dispatch workspaceElement, 'ex-mode:toggle'
expect(exModePanel.isVisible()).toBe false
it "hides and shows the view", ->
# This test shows you an integration test testing at the view level.
# Attaching the workspaceElement to the DOM is required to allow the
# `toBeVisible()` matchers to work. Anything testing visibility or focus
# requires that the workspaceElement is on the DOM. Tests that attach the
# workspaceElement to the DOM are generally slower than those off DOM.
jasmine.attachToDOM(workspaceElement)
expect(workspaceElement.querySelector('.ex-mode')).not.toExist()
# This is an activation event, triggering it causes the package to be
# activated.
atom.commands.dispatch workspaceElement, 'ex-mode:toggle'
waitsForPromise ->
activationPromise
runs ->
# Now we can test for view visibility
exModeElement = workspaceElement.querySelector('.ex-mode')
expect(exModeElement).toBeVisible()
atom.commands.dispatch workspaceElement, 'ex-mode:toggle'
expect(exModeElement).not.toBeVisible()

View file

@ -1,5 +0,0 @@
ExModeView = require '../lib/ex-mode-view'
describe "ExModeView", ->
it "has one valid test", ->
expect("life").toBe "easy"

65
spec/spec-helper.coffee Normal file
View file

@ -0,0 +1,65 @@
ExState = require '../lib/ex-state'
GlobalExState = require '../lib/global-ex-state'
beforeEach ->
atom.workspace ||= {}
activateExMode = ->
atom.workspace.open().then ->
atom.commands.dispatch(atom.views.getView(atom.workspace), 'ex-mode:open')
keydown('escape')
atom.workspace.getActivePane().destroyActiveItem()
getEditorElement = (callback) ->
textEditor = null
waitsForPromise ->
atom.workspace.open().then (e) ->
textEditor = e
runs ->
# element = document.createElement("atom-text-editor")
# element.setModel(textEditor)
# element.classList.add('vim-mode')
# element.exState = new ExState(element, new GlobalExState)
#
# element.addEventListener "keydown", (e) ->
# atom.keymaps.handleKeyboardEvent(e)
element = atom.views.getView(textEditor)
callback(element)
dispatchKeyboardEvent = (target, eventArgs...) ->
e = document.createEvent('KeyboardEvent')
e.initKeyboardEvent(eventArgs...)
# 0 is the default, and it's valid ASCII, but it's wrong.
Object.defineProperty(e, 'keyCode', get: -> undefined) if e.keyCode is 0
target.dispatchEvent e
dispatchTextEvent = (target, eventArgs...) ->
e = document.createEvent('TextEvent')
e.initTextEvent(eventArgs...)
target.dispatchEvent e
keydown = (key, {element, ctrl, shift, alt, meta, raw}={}) ->
key = "U+#{key.charCodeAt(0).toString(16)}" unless key is 'escape' or raw?
element ||= document.activeElement
eventArgs = [
true, # bubbles
true, # cancelable
null, # view
key, # key
0, # location
ctrl, alt, shift, meta
]
canceled = not dispatchKeyboardEvent(element, 'keydown', eventArgs...)
dispatchKeyboardEvent(element, 'keypress', eventArgs...)
if not canceled
if dispatchTextEvent(element, 'textInput', eventArgs...)
element.value += key
dispatchKeyboardEvent(element, 'keyup', eventArgs...)
module.exports = {keydown, getEditorElement, activateExMode}

View file

@ -9,4 +9,22 @@
div[is=ex-command-mode-input] atom-text-editor[mini]::before { div[is=ex-command-mode-input] atom-text-editor[mini]::before {
content: ":"; content: ":";
opacity: 0.5;
}
div[is=vim-normal-mode-input] atom-text-editor[mini]::before {
content: "/";
opacity: 0.5;
}
.command-mode-input atom-text-editor[mini] {
background-color: inherit;
border: none;
width: 100%;
font-weight: normal;
color: @text-color;
line-height: 1.28;
cursor: default;
white-space: nowrap;
padding-left: 10px;
} }