Compare commits

..

235 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
b5e9df10b4 Prepare 0.6.2 release 2015-07-29 16:38:05 +02:00
1a117bddf9 Clean up save logic (fixes #75) 2015-07-29 16:36:55 +02:00
84c548a444 don't :e an unsaved file 2015-07-29 16:31:42 +02:00
da8405b387 Prepare 0.6.1 release 2015-07-28 12:03:04 +02:00
8fd1fe14c8 Added ~ support
Thanks to @romgrk in PR #48
2015-07-28 12:02:03 +02:00
cdf65d6e27 Prepare 0.6.0 release 2015-07-28 11:57:07 +02:00
ea919ada29 update changelog 2015-07-28 11:56:45 +02:00
add34853ac Merge pull request #61 from ardrigh/patch-1
Display ':' at start of the command input line
2015-07-28 11:34:05 +02:00
a187e68497 Merge pull request #74 from mleeds95/fix-write
Simplify how :write works and make it work when no projects are open.
2015-07-28 11:30:51 +02:00
e679604c21 Merge pull request #69 from nzyuzin/set_options
Add support for :set [option]
2015-07-28 11:30:35 +02:00
7450d05e64 Merge pull request #55 from jazzpi/fix-54
Replace `\#{delimiter}` with `#{delimiter}` in :s
2015-07-28 11:30:03 +02:00
Matthew Leeds
656ed90f7e Simplify how :write works and make it work when no projects are open. 2015-07-27 16:52:47 -05:00
Nikita Zyuzin
0f38fd195a Add support for :set [option] 2015-07-13 23:06:03 +04:00
13c5c84688 Merge pull request #59 from jazzpi/use-texteditor-transact
Use TextEditor.transact
2015-06-28 19:03:49 +02:00
Dagan McGregor
44f296999b Update ex-mode.less
Change to div to avoid conflict with vim-mode search
2015-06-23 18:12:02 +12:00
Dagan McGregor
928e5626fd Update ex-mode.less
Display ':' at start of the command input
2015-06-21 03:29:18 +12:00
jazzpi
7c202faefa Use TextEditor.transact
This fixes #57 and atom/atom#703
2015-06-18 18:52:24 +02:00
jazzpi
4cccef79a5 Replace \#{delimiter} with #{delimiter} in :s 2015-06-10 18:19:59 +02:00
bf0f492740 Merge pull request #45 from jazzpi/fix-write
Fix :write behaviour
2015-05-28 09:12:18 +02:00
jazzpi
5ee749cb0b Fix :write behaviour 2015-05-27 17:29:37 +02:00
e33dc15392 Prepare 0.5.1 release 2015-05-24 15:57:13 +02:00
c43b2658ef stop spurious cancels 2015-05-24 15:56:53 +02:00
3c17b6e670 history even bad commands (easing fixups) 2015-05-24 15:56:11 +02:00
cb2d79a1e3 get some fresh air 2015-05-24 15:55:24 +02:00
a32c04e898 Prepare 0.5.0 release 2015-05-24 11:51:55 +02:00
a0121dc1cc update changelog for next version 2015-05-24 11:49:49 +02:00
3f75ca3a30 comply with new Atom API 2015-05-24 11:49:29 +02:00
1c064ec13a debug logging removed 2015-05-24 11:47:46 +02:00
8eeb2dc57e Merge pull request #38 from jazzpi/fix-x
Fix `:x` throwing an error
2015-05-15 09:07:35 +02:00
jazzpi
5d06604d41 Fix :x throwing an error 2015-04-18 12:37:45 +02:00
be8bac62f1 Merge pull request #35 from jazzpi/implement-d
Implement d
2015-04-11 19:33:07 +02:00
jazzpi
8087ca3aeb Implement d 2015-04-06 13:18:31 +02:00
jazzpi
657a92e84e handle range starting at 0 (fixes #30) 2015-03-30 14:12:21 +02:00
e97964dd5c Prepare 0.4.1 release 2015-03-30 10:42:39 +02:00
6e0ef96e06 changelog 2015-03-30 10:42:25 +02:00
5b62897712 Merge branch 'fix_length_error' 2015-03-30 10:22:05 +02:00
jazzpi
f2508d7be2 Fix #23 2015-03-30 10:21:25 +02:00
470935ddc9 Prepare 0.4.0 release 2015-03-30 10:13:09 +02:00
4da6f2d987 Merge pull request #26 from jazzpi/patch-3
Implement parsing following ex spec and :s, fix behaviour of :e
2015-03-30 10:11:17 +02:00
jazzpi
241abdb9d0 Fix :s behaviour 2015-03-26 15:35:14 +01:00
jazzpi
822d5fcf97 Stop debug logging 2015-03-22 23:30:01 +01:00
jazzpi
f662a96afd Implement :s 2015-03-22 23:14:56 +01:00
jazzpi
161c55c94f Style 2015-03-22 15:19:41 +01:00
jazzpi
477b2a6b6b Fix double comprehension 2015-03-22 14:34:37 +01:00
jazzpi
433b1dd6ac Yes we can 2015-03-22 14:17:43 +01:00
jazzpi
d1295587ee Update commands 2015-03-22 02:56:48 +01:00
jazzpi
8b658db090 Update commands 2015-03-22 01:11:34 +01:00
jazzpi
054473a3d1 Fix 2015-03-22 01:11:01 +01:00
jazzpi
e5e944656c is instead of == 2015-03-22 00:58:53 +01:00
jazzpi
5fd7a8b6dd Fix line length 2015-03-22 00:53:26 +01:00
jazzpi
548f9b75c4 Add actual command calling 2015-03-21 23:36:08 +01:00
jazzpi
a7504aa590 Address parsing 2015-03-21 19:03:58 +01:00
jazzpi
1ec4589831 Provide the vim-mode service globally 2015-03-21 19:02:00 +01:00
jazzpi
32af5b18f5 Fix consumeVim version so we can use it 2015-03-21 18:54:44 +01:00
85114dd0a3 Prepare 0.3.1 release 2015-03-16 09:39:16 +01:00
b54ba5e635 stop logging vim 2015-03-16 09:35:54 +01:00
0e5150baeb Merge pull request #21 from jazzpi/patch-1
Fix consumeVim version so we can use it
2015-03-16 09:34:50 +01:00
Jasper v. B.
d88aeb71e6 Fix consumeVim version so we can use it
https://github.com/atom/vim-mode only provides provideVimMode in version 0.1.0.
2015-03-09 11:45:07 +01:00
1d0559fb16 Merge pull request #18 from jazzpi/master
Use space-pen for the input view
2015-03-08 14:18:06 +01:00
jazzpi
817bdf0f74 Use space-pen for the input view 2015-03-07 17:34:33 +01:00
b86e4064ea Merge pull request #17 from jazzpi/patch-1
Add :x
2015-03-01 13:09:46 +01:00
Jasper v. B.
3f355d3133 Add :x 2015-03-01 11:31:38 +01:00
65b254a241 consume vim-mode service 2015-02-24 10:54:11 +01:00
4adc1c168a Added :wq 2015-02-23 16:22:23 +01:00
209c19b9db feedback on command error 2015-02-23 16:22:05 +01:00
1c574d84d4 Prepare 0.3.0 release 2015-02-23 15:04:09 +01:00
42da2ce126 update changelog 2015-02-23 15:03:40 +01:00
4f2e9d6fd7 extensibility via services 2015-02-23 14:59:33 +01:00
d2bf0c5502 cleanup 2015-02-23 12:09:55 +01:00
dd42115c42 edit commands (fixes #12) 2015-02-23 12:08:59 +01:00
76014791ef tab commands (fixes #13) 2015-02-23 12:08:59 +01:00
838b43ffcd Prepare 0.2.0 release 2015-02-23 11:43:38 +01:00
cff3567317 update changelog 2015-02-23 11:42:59 +01:00
860a9f4e0b Update README.md 2015-02-23 11:37:59 +01:00
55bb9f6319 Merge branch 'quit_tabs_and_args'.
Conflicts:
	lib/command.coffee
	lib/ex.coffee
2015-02-23 10:51:45 +01:00
66e5f697f6 comply with vim behavior 2015-02-23 10:44:51 +01:00
7d4aee9ed2 Merge pull request #16 from AsaAyers/write-all
add :wa
2015-02-23 09:28:14 +01:00
c4771a9b85 Merge pull request #15 from Germanika/master
add split, vsplit, sp, and vsp commands
2015-02-23 09:27:53 +01:00
Asa Ayers
1248bd5769 add :wa 2015-02-22 21:39:47 -05:00
Ian Germann
7f667d90e0 add split, vsplit, sp, and vsp commands 2015-02-22 14:56:51 -05:00
Dave de Fijter
2cafba248c added new commands for quitting, opening tabs and added argument functionality for commands 2015-02-22 16:09:40 +01:00
28 changed files with 2626 additions and 159 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,93 @@
## (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
* No project/multiple projects paths (uses first one)
* Support for :set
* Fixes
## 0.5.0
* Comply with upcoming Atom API 1.0
* Added `:d`
* Fixes
## 0.4.1 - C-C-C-Combo Edition
* Added ex command parser, including ranges
* Added `:wq`
* Added `:s`
* Alert on unknown or invalid command
## 0.3.0 - Extrovert Edition
* Register new commands from the outside world
* Added `:tabn`, `:tabp`, `:e`, `:enew`, and a few aliases
## 0.2.0 - NotAOneTrickPony Edition
* Added `:quit`, `:tabedit`, `:wa`, `:split`, `:vsplit`
* Commands can take arguments
## 0.1.0 - First Release ## 0.1.0 - First Release
* Every feature added * Every feature added
* Every bug fixed * Every bug fixed

63
README.md Normal file → Executable file
View file

@ -2,10 +2,67 @@
ex-mode for Atom's vim-mode ex-mode for Atom's vim-mode
## Usage ## Use
Install vim-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
Use the service to register commands, from your own package, or straight from `init.coffee`:
```coffee
# in Atom's init.coffee
atom.packages.onDidActivatePackage (pack) ->
if pack.name == 'ex-mode'
Ex = pack.mainModule.provideEx()
Ex.registerCommand 'z', -> console.log("Zzzzzz...")
```
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
Groundwork is done. More ex commands are easy to add and will be coming soon. Groundwork is done. More ex commands are easy to add and will be coming as time permits and contributions come in.
## License
MIT

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

5
lib/command-error.coffee Normal file
View file

@ -0,0 +1,5 @@
class CommandError
constructor: (@message) ->
@name = 'Command Error'
module.exports = CommandError

View file

@ -1,21 +1,201 @@
ExViewModel = require './ex-view-model' ExViewModel = require './ex-view-model'
Ex = require './ex' Ex = require './ex'
Find = require './find'
class CommandError CommandError = require './command-error'
constructor: (@message) ->
@name = '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, cursor) ->
row = cursor.getBufferRow()
if str is '.'
addr = row
else if str is '$'
# Lines are 0-indexed in Atom, but 1-indexed in vim.
# The two ways of getting length let us support Atom 1.19's new buffer
# implementation (https://github.com/atom/atom/pull/14435) and still
# support 1.18 and below
buffer = @editor.getBuffer()
addr = (buffer.getLineCount?() ? buffer.lines.length) - 1
else if str[0] in ["+", "-"]
addr = row + @parseOffset(str)
else if not isNaN(str)
addr = parseInt(str) - 1
else if str[0] is "'" # Parse Mark...
unless @vimState?
throw new CommandError("Couldn't get access to vim-mode.")
mark = @vimState.mark.marks[str[1]]
unless mark?
throw new CommandError("Mark #{str} not set.")
addr = mark.getEndBufferPosition().row
else if str[0] is "/"
str = str[1...]
if str[str.length-1] is "/"
str = str[...-1]
addr = Find.scanEditor(str, @editor, cursor.getCurrentLineBufferRange().end)[0]
unless addr?
throw new CommandError("Pattern not found: #{str}")
addr = addr.start.row
else if str[0] is "?"
str = str[1...]
if str[str.length-1] is "?"
str = str[...-1]
addr = Find.scanEditor(str, @editor, cursor.getCurrentLineBufferRange().start, true)[0]
unless addr?
throw new CommandError("Pattern not found: #{str[1...-1]}")
addr = addr.start.row
return addr
parseOffset: (str) ->
if str.length is 0
return 0
if str.length is 1
o = 1
else
o = parseInt(str[1..])
if str[0] is '+'
return o
else
return -o
execute: (input) -> execute: (input) ->
return unless input.characters.length > 0 @vimState = @exState.globalExState.vim?.getEditorState(@editor)
# Command line parsing (mostly) following the rules at
# http://pubs.opengroup.org/onlinepubs/9699919799/utilities
# /ex.html#tag_20_40_13_03
func = (new Ex)[input.characters] # Steps 1/2: Leading blanks and colons are ignored.
if func? cl = input.characters
func() cl = cl.replace(/^(:|\s)*/, '')
return unless cl.length > 0
# Step 3: If the first character is a ", ignore the rest of the line
if cl[0] is '"'
return
# Step 4: Address parsing
# see comment in parseAddr about line length
buffer = @editor.getBuffer()
lastLine = (buffer.getLineCount?() ? buffer.lines.length) - 1
if cl[0] is '%'
range = [0, lastLine]
cl = cl[1..]
else else
throw new CommandError("#{input.characters}") addrPattern = ///^
(?: # First address
(
\.| # Current line
\$| # Last line
\d+| # n-th line
'[\[\]<>'`"^.(){}a-zA-Z]| # Marks
/.*?(?:[^\\]/|$)| # Regex
\?.*?(?:[^\\]\?|$)| # Backwards search
[+-]\d* # Current line +/- a number of lines
)((?:\s*[+-]\d*)*) # Line offset
)?
(?:, # Second address
( # Same as first address
\.|
\$|
\d+|
'[\[\]<>'`"^.(){}a-zA-Z]|
/.*?[^\\]/|
\?.*?[^\\]\?|
[+-]\d*
)((?:\s*[+-]\d*)*)
)?
///
module.exports = {Command, CommandError} [match, addr1, off1, addr2, off2] = cl.match(addrPattern)
cursor = @editor.getLastCursor()
# 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?
address1 = @parseAddr(addr1, cursor)
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 = lastLine if address1 > lastLine
if address1 < 0
throw new CommandError('Invalid range')
if addr2?
address2 = @parseAddr(addr2, cursor)
if off2?
address2 += @parseOffset(off2)
address2 = 0 if address2 is -1
address2 = lastLine if address2 > lastLine
if address2 < 0
throw new CommandError('Invalid range')
if address2 < address1
throw new CommandError('Backwards range given')
range = [address1, if address2? then address2 else address1]
cl = cl[match?.length..]
# Step 5: Leading blanks are ignored
cl = cl.trimLeft()
# Step 6a: If no command is specified, go to the last specified address
if cl.length is 0
@editor.setCursorBufferPosition([range[1], 0])
return
# Ignore steps 6b and 6c since they only make sense for print commands and
# print doesn't make sense
# Ignore step 7a since flags are only useful for print
# Step 7b: :k<valid mark> is equal to :mark <valid mark> - only a-zA-Z is
# in vim-mode for now
if cl.length is 2 and cl[0] is 'k' and /[a-z]/i.test(cl[1])
command = 'mark'
args = cl[1]
else if not /[a-z]/i.test(cl[0])
command = cl[0]
args = cl[1..]
else
[m, command, args] = cl.match(/^(\w+)(.*)/)
# If the command matches an existing one exactly, execute that one
unless (func = Ex.singleton()[command])?
# Step 8: Match command against existing commands
matching = (name for name, val of Ex.singleton() when \
name.indexOf(command) is 0)
matching.sort()
command = matching[0]
func = Ex.singleton()[command]
if func?
if runOverSelections
for id, selection of @selections
bufferRange = selection.getBufferRange()
range = [bufferRange.start.row, bufferRange.end.row]
func({ range, args, @vimState, @exState, @editor })
else
func({ range, args, @vimState, @exState, @editor })
else
throw new CommandError("Not an editor command: #{input.characters}")
module.exports = Command

View file

@ -1,58 +0,0 @@
{View, TextEditorView} = require 'atom'
module.exports =
class ExCommandModeInputView extends View
@content: ->
@div class: 'command-mode-input', =>
@div class: 'editor-container', outlet: 'editorContainer', =>
@subview 'editor', new TextEditorView(mini: true)
initialize: (@viewModel, opts = {})->
if opts.class?
@editorContainer.addClass opts.class
if opts.hidden
@editorContainer.addClass 'hidden-input'
@singleChar = opts.singleChar
@defaultText = opts.defaultText ? ''
@panel = atom.workspace.addBottomPanel(item: this, priority: 100)
@focus()
@handleEvents()
handleEvents: ->
if @singleChar?
@editor.find('input').on 'textInput', @autosubmit
@editor.on 'core:confirm', @confirm
@editor.on 'core:cancel', @cancel
@editor.find('input').on 'blur', @cancel
stopHandlingEvents: ->
if @singleChar?
@editor.find('input').off 'textInput', @autosubmit
@editor.off 'core:confirm', @confirm
@editor.off 'core:cancel', @cancel
@editor.find('input').off 'blur', @cancel
autosubmit: (event) =>
@editor.setText(event.originalEvent.data)
@confirm()
confirm: =>
@value = @editor.getText() or @defaultText
@viewModel.confirm(@)
@remove()
focus: =>
@editorContainer.find('.editor').focus()
cancel: (e) =>
@viewModel.cancel(@)
@remove()
remove: =>
@stopHandlingEvents()
atom.workspace.getActivePane().activate()
@panel.destroy()

View file

@ -1,5 +1,6 @@
GlobalExState = require './global-ex-state' GlobalExState = require './global-ex-state'
ExState = require './ex-state' ExState = require './ex-state'
Ex = require './ex'
{Disposable, CompositeDisposable} = require 'event-kit' {Disposable, CompositeDisposable} = require 'event-kit'
module.exports = ExMode = module.exports = ExMode =
@ -26,3 +27,36 @@ module.exports = ExMode =
deactivate: -> deactivate: ->
@disposables.dispose() @disposables.dispose()
provideEx: ->
registerCommand: Ex.registerCommand.bind(Ex)
registerAlias: Ex.registerAlias.bind(Ex)
consumeVim: (vim) ->
@vim = 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

@ -0,0 +1,71 @@
class ExCommandModeInputElement extends HTMLDivElement
createdCallback: ->
@className = "command-mode-input"
@editorContainer = document.createElement("div")
@editorContainer.className = "editor-container"
@appendChild(@editorContainer)
initialize: (@viewModel, opts = {}) ->
if opts.class?
@editorContainer.classList.add(opts.class)
if opts.hidden
@editorContainer.style.height = "0px"
@editorElement = document.createElement "atom-text-editor"
@editorElement.classList.add('editor') # Consider this deprecated!
@editorElement.classList.add('ex-mode-editor')
@editorElement.getModel().setMini(true)
@editorElement.setAttribute('mini', '')
@editorContainer.appendChild(@editorElement)
@singleChar = opts.singleChar
@defaultText = opts.defaultText ? ''
@panel = atom.workspace.addBottomPanel(item: this, priority: 100)
@focus()
@handleEvents()
this
handleEvents: ->
if @singleChar?
@editorElement.getModel().getBuffer().onDidChange (e) =>
@confirm() if e.newText
else
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:cancel', @cancel.bind(this))
atom.commands.add(@editorElement, 'ex-mode:close', @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: ->
@value = @editorElement.getModel().getText() or @defaultText
@viewModel.confirm(this)
@removePanel()
focus: ->
@editorElement.focus()
cancel: (e) ->
@viewModel.cancel(this)
@removePanel()
removePanel: ->
atom.workspace.getActivePane().activate()
@panel.destroy()
module.exports =
document.registerElement("ex-command-mode-input"
extends: "div",
prototype: ExCommandModeInputElement.prototype
)

View file

@ -1,6 +1,7 @@
{Emitter, Disposable, CompositeDisposable} = require 'event-kit' {Emitter, Disposable, CompositeDisposable} = require 'event-kit'
{Command, CommandError} = require './command' Command = require './command'
CommandError = require './command-error'
class ExState class ExState
constructor: (@editorElement, @globalExState) -> constructor: (@editorElement, @globalExState) ->
@ -33,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
@ -44,14 +48,25 @@ class ExState
processOpStack: -> processOpStack: ->
[command, input] = @opStack [command, input] = @opStack
if input.characters.length > 0 if input.characters.length > 0
@history.unshift command
try try
command.execute(input) command.execute(input)
@history.unshift command
catch e catch e
if (e instanceof CommandError) if (e instanceof CommandError)
atom.notifications.addError("Command error: #{e.message}")
@emitter.emit('failed-to-execute') @emitter.emit('failed-to-execute')
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
@view.editor.on('core:move-up', @increaseHistoryEx) if withSelection
@view.editor.on('core:move-down', @decreaseHistoryEx) @view.editorElement.getModel().setText("'<,'>")
@view.editorElement.addEventListener('keydown', @tabAutocomplete)
atom.commands.add(@view.editorElement, 'core:move-up', @increaseHistoryEx)
atom.commands.add(@view.editorElement, 'core:move-down', @decreaseHistoryEx)
@autoComplete = new AutoComplete(Ex.getCommands())
restoreHistory: (index) -> restoreHistory: (index) ->
@view.editor.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
@ -24,7 +45,7 @@ class ExViewModel extends ViewModel
if @historyIndex <= 0 if @historyIndex <= 0
# get us back to a clean slate # get us back to a clean slate
@historyIndex = -1 @historyIndex = -1
@view.editor.setText('') @view.editorElement.getModel().setText('')
else else
@historyIndex -= 1 @historyIndex -= 1
@restoreHistory(@historyIndex) @restoreHistory(@historyIndex)

View file

@ -1,9 +1,472 @@
class Ex path = require 'path'
write: -> CommandError = require './command-error'
if atom.workspace.getActiveTextEditor().getPath() isnt undefined fs = require 'fs-plus'
atom.workspace.getActiveEditor().save() VimOption = require './vim-option'
_ = require 'underscore-plus'
atom
defer = () ->
deferred = {}
deferred.promise = new Promise((resolve, reject) ->
deferred.resolve = resolve
deferred.reject = reject
)
return deferred
trySave = (func) ->
deferred = defer()
try
response = func()
if response instanceof Promise
response.then ->
deferred.resolve()
else else
atom.workspace.getActivePane().saveActiveItemAs() deferred.resolve()
w: => @write() catch error
if error.message.endsWith('is a directory')
atom.notifications.addWarning("Unable to save file: #{error.message}")
else if error.path?
if error.code is 'EACCES'
atom.notifications
.addWarning("Unable to save file: Permission denied '#{error.path}'")
else if error.code in ['EPERM', 'EBUSY', 'UNKNOWN', 'EEXIST']
atom.notifications.addWarning("Unable to save file '#{error.path}'",
detail: error.message)
else if error.code is 'EROFS'
atom.notifications.addWarning(
"Unable to save file: Read-only file system '#{error.path}'")
else if (errorMatch =
/ENOTDIR, not a directory '([^']+)'/.exec(error.message))
fileName = errorMatch[1]
atom.notifications.addWarning("Unable to save file: A directory in the "+
"path '#{fileName}' could not be written to")
else
throw error
deferred.promise
saveAs = (filePath, editor) ->
fs.writeFileSync(filePath, editor.getText())
getFullPath = (filePath) ->
filePath = fs.normalize(filePath)
if path.isAbsolute(filePath)
filePath
else if atom.project.getPaths().length == 0
path.join(fs.normalize('~'), filePath)
else
path.join(atom.project.getPaths()[0], filePath)
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
replaced
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)
class Ex
@singleton: =>
@ex ||= new Ex
@registerCommand: (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: ->
atom.workspace.getActivePane().destroyActiveItem()
quitall: ->
if !atom.config.get('ex-mode.onlyCloseBuffers')
atom.close()
else
atom.workspace.getTextEditors().forEach (editor) ->
editor.destroy()
q: => @quit()
qall: => @quitall()
tabedit: (args) =>
if args.args.trim() isnt ''
@edit(args)
else
@tabnew(args)
tabe: (args) => @tabedit(args)
tabnew: (args) =>
if args.args.trim() is ''
atom.workspace.open()
else
@tabedit(args)
tabclose: (args) => @quit(args)
tabc: => @tabclose()
tabnext: ->
pane = atom.workspace.getActivePane()
pane.activateNextItem()
tabn: => @tabnext()
tabprevious: ->
pane = atom.workspace.getActivePane()
pane.activatePreviousItem()
tabp: => @tabprevious()
tabonly: ->
tabBar = atom.workspace.getPanes()[0]
tabBarElement = atom.views.getView(tabBar).querySelector(".tab-bar")
tabBarElement.querySelector(".right-clicked") && tabBarElement.querySelector(".right-clicked").classList.remove("right-clicked")
tabBarElement.querySelector(".active").classList.add("right-clicked")
atom.commands.dispatch(tabBarElement, 'tabs:close-other-tabs')
tabBarElement.querySelector(".active").classList.remove("right-clicked")
tabo: => @tabonly()
edit: ({ range, args, editor }) ->
filePath = args.trim()
if filePath[0] is '!'
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
throw new CommandError('Only one file name allowed')
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: ->
atom.workspace.open()
write: ({ range, args, editor, saveas }) ->
saveas ?= false
filePath = args
if filePath[0] is '!'
force = true
filePath = filePath[1..]
else
force = false
filePath = filePath.trim()
if filePath.indexOf(' ') isnt -1
throw new CommandError('Only one file name allowed')
deferred = defer()
editor = atom.workspace.getActiveTextEditor()
# Case 1; path is provided
if filePath.length isnt 0
fullPath = getFullPath filePath
# 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()
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()
filePaths = args.split(' ')
filePaths = undefined if filePaths.length is 1 and filePaths[0] is ''
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
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, editor, vimState }) ->
args_ = args.trimLeft()
delim = args_[0]
if /[a-z1-9\\"|]/i.test(delim)
throw new CommandError(
"Regular expressions can't be delimited by alphanumeric characters, '\\', '\"' or '|'")
args_ = args_[1..]
escapeChars = {t: '\t', n: '\n', r: '\r'}
parsed = ['', '', '']
parsing = 0
escaped = false
while (char = args_[0])?
args_ = args_[1..]
if char is delim
if not escaped
parsing++
if parsing > 2
throw new CommandError('Trailing characters')
else
parsed[parsing] = parsed[parsing][...-1]
else if char is '\\' and not escaped
parsed[parsing] += char
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
flagsObj = {}
flags.split('').forEach((flag) -> flagsObj[flag] = true)
# gdefault option
if atom.config.get('ex-mode.gdefault')
flagsObj.g = !flagsObj.g
patternRE = getSearchTerm(pattern, flagsObj)
catch e
if e.message.indexOf('Invalid flags supplied to RegExp constructor') is 0
throw new CommandError("Invalid flags: #{e.message[45..]}")
else if e.message.indexOf('Invalid regular expression: ') is 0
throw new CommandError("Invalid RegEx: #{e.message[27..]}")
else
throw e
editor.transact ->
for line in [range[0]..range[1]]
editor.scanInBufferRange(
patternRE,
[[line, 0], [line + 1, 0]],
({match, replace}) ->
replace(replaceGroups(match[..], substition))
)
s: (args) => @substitute(args)
vsplit: ({ range, args }) ->
args = args.trim()
filePaths = args.split(' ')
filePaths = undefined if filePaths.length is 1 and filePaths[0] is ''
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
newPane = pane.splitLeft()
for file in filePaths
do ->
atom.workspace.openURIInPane file, newPane
else
pane.splitLeft(copyActiveItem: true)
vsp: (args) => @vsplit(args)
delete: ({ range }) ->
range = [[range[0], 0], [range[1] + 1, 0]]
editor = atom.workspace.getActiveTextEditor()
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()
if args == ""
throw new CommandError("No option specified")
options = args.split(' ')
for option in options
do ->
if option.includes("=")
nameValPair = option.split("=")
if (nameValPair.length != 2)
throw new CommandError("Wrong option format. [name]=[value] format is expected")
optionName = nameValPair[0]
optionValue = nameValPair[1]
optionProcessor = VimOption.singleton()[optionName]
if not optionProcessor?
throw new CommandError("No such option: #{optionName}")
optionProcessor(optionValue)
else
optionProcessor = VimOption.singleton()[option]
if not optionProcessor?
throw new CommandError("No such option: #{option}")
optionProcessor()
sort: ({ range }) =>
editor = atom.workspace.getActiveTextEditor()
sortingRange = [[]]
# If no range is provided, the entire file should be sorted.
isMultiLine = range[1] - range[0] > 1
if isMultiLine
sortingRange = [[range[0], 0], [range[1] + 1, 0]]
else
sortingRange = [[0, 0], [editor.getLastBufferRow(), 0]]
# Store every bufferedRow string in an array.
textLines = []
for lineIndex in [sortingRange[0][0]..sortingRange[1][0] - 1]
textLines.push(editor.lineTextForBufferRow(lineIndex))
# Sort the array and join them together with newlines for writing back to the file.
sortedText = _.sortBy(textLines).join('\n') + '\n'
editor.buffer.setTextInRange(sortingRange, sortedText)
module.exports = Ex module.exports = Ex

85
lib/find.coffee Normal file
View file

@ -0,0 +1,85 @@
_ = require 'underscore-plus'
getSearchTerm = (term, modifiers = {'g': true}) ->
escaped = false
hasc = false
hasC = false
term_ = term
term = ''
for char in term_
if char is '\\' and not escaped
escaped = true
term += char
else
if char is 'c' and escaped
hasc = true
term = term[...-1]
else if char is 'C' and escaped
hasC = true
term = term[...-1]
else if char isnt '\\'
term += char
escaped = false
if hasC
modifiers['i'] = false
if (not hasC and not term.match('[A-Z]') and \
atom.config.get("vim-mode:useSmartcaseForSearch")) or hasc
modifiers['i'] = true
modFlags = Object.keys(modifiers).filter((key) -> modifiers[key]).join('')
try
new RegExp(term, modFlags)
catch
new RegExp(_.escapeRegExp(term), modFlags)
module.exports = {
findInBuffer : (buffer, pattern) ->
found = []
buffer.scan(new RegExp(pattern, 'g'), (obj) -> found.push obj.range)
return found
findNextInBuffer : (buffer, curPos, pattern) ->
found = @findInBuffer(buffer, pattern)
more = (i for i in found when i.compare([curPos, curPos]) is 1)
if more.length > 0
return more[0].start.row
else if found.length > 0
return found[0].start.row
else
return null
findPreviousInBuffer : (buffer, curPos, pattern) ->
found = @findInBuffer(buffer, pattern)
less = (i for i in found when i.compare([curPos, curPos]) is -1)
if less.length > 0
return less[less.length - 1].start.row
else if found.length > 0
return found[found.length - 1].start.row
else
return null
# Returns an array of ranges of all occurences of `term` in `editor`.
# The array is sorted so that the first occurences after the cursor come
# first (and the search wraps around). If `reverse` is true, the array is
# reversed so that the first occurence before the cursor comes first.
scanEditor: (term, editor, position, reverse = false) ->
[rangesBefore, rangesAfter] = [[], []]
editor.scan getSearchTerm(term), ({range}) ->
if reverse
isBefore = range.start.compare(position) < 0
else
isBefore = range.start.compare(position) <= 0
if isBefore
rangesBefore.push(range)
else
rangesAfter.push(range)
if reverse
rangesAfter.concat(rangesBefore).reverse()
else
rangesAfter.concat(rangesBefore)
}

View file

@ -1,4 +1,5 @@
class GlobalExState class GlobalExState
commandHistory: [] commandHistory: []
setVim: (@vim) ->
module.exports = GlobalExState module.exports = GlobalExState

View file

@ -1,18 +1,22 @@
ExCommandModeInputView = require './ex-command-mode-input-view' 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 ExCommandModeInputView(@, opts) @view = new ExNormalModeInputElement().initialize(@, opts)
@editor.commandModeInputView = @view @editor.normalModeInputView = @view
@exState.onDidFailToExecute => @view.remove() @exState.onDidFailToExecute => @view.remove()
@done = false
confirm: (view) -> confirm: (view) ->
@exState.pushOperations(new Input(@view.value)) @exState.pushOperations(new Input(@view.value))
@done = true
cancel: (view) -> cancel: (view) ->
@exState.pushOperations(new Input('')) unless @done
@exState.pushOperations(new Input(''))
@done = true
class Input class Input
constructor: (@characters) -> constructor: (@characters) ->

65
lib/vim-option.coffee Normal file
View file

@ -0,0 +1,65 @@
class VimOption
@singleton: =>
@option ||= new VimOption
list: =>
atom.config.set("editor.showInvisibles", true)
nolist: =>
atom.config.set("editor.showInvisibles", false)
number: =>
atom.config.set("editor.showLineNumbers", true)
nu: =>
@number()
nonumber: =>
atom.config.set("editor.showLineNumbers", false)
nonu: =>
@nonumber()
splitright: =>
atom.config.set("ex-mode.splitright", true)
spr: =>
@splitright()
nosplitright: =>
atom.config.set("ex-mode.splitright", false)
nospr: =>
@nosplitright()
splitbelow: =>
atom.config.set("ex-mode.splitbelow", true)
sb: =>
@splitbelow()
nosplitbelow: =>
atom.config.set("ex-mode.splitbelow", false)
nosb: =>
@nosplitbelow()
smartcase: =>
atom.config.set("vim-mode.useSmartcaseForSearch", true)
scs: =>
@smartcase()
nosmartcase: =>
atom.config.set("vim-mode.useSmartcaseForSearch", false)
noscs: =>
@nosmartcase()
gdefault: =>
atom.config.set("ex-mode.gdefault", true)
nogdefault: =>
atom.config.set("ex-mode.gdefault", false)
module.exports = VimOption

View file

@ -1,7 +1,7 @@
{ {
"name": "ex-mode", "name": "ex-mode",
"main": "./lib/ex-mode", "main": "./lib/ex-mode",
"version": "0.1.1", "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"
@ -9,10 +9,36 @@
"repository": "https://github.com/lloeki/ex-mode", "repository": "https://github.com/lloeki/ex-mode",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"atom": ">=0.174.0 <2.0.0" "atom": ">=0.200.0 <2.0.0"
}, },
"dependencies": { "dependencies": {
"underscore-plus": "1.x", "underscore-plus": "1.x",
"event-kit": "^0.7.2" "event-kit": "^0.7.2",
"space-pen": "^5.1.1",
"atom-space-pen-views": "^2.0.4",
"fs-plus": "^2.2.8"
},
"consumedServices": {
"vim-mode": {
"versions": {
"^0.1.0": "consumeVim"
}
},
"vim-mode-plus": {
"versions": {
"^0.1.0": "consumeVimModePlus"
}
}
},
"providedServices": {
"ex-mode": {
"description": "Ex commands",
"versions": {
"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

@ -6,3 +6,25 @@
.ex-mode { .ex-mode {
} }
div[is=ex-command-mode-input] atom-text-editor[mini]::before {
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;
}