├── .gitignore ├── CHANGELOG.md ├── ISSUE_TEMPLATE.md ├── LICENSE.md ├── README.md ├── keymaps └── vim-mode-plus-ex-mode.cson ├── lib ├── commands.js ├── main.js └── view.js ├── package.json └── styles └── vim-mode-plus-ex-mode.less /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.11.0: 2 | - Maintenance: Catchup vmp changes, no longer use service.Base, use getClass instead. 3 | 4 | ## 0.10.2: 5 | - Maintenance: Remove conditional code used in vmp version migration. 6 | 7 | ## 0.10.1: 8 | - Maintenance: 9 | - Use `Base.register` over `Base.initClass`. 10 | 11 | ## 0.10.0: 12 | - Maintenance: 13 | - Convert Coffee-to-JS 14 | - Also make vmp commands compatible with upcoming ES6-class-based-vmp-operations. 15 | 16 | ## 0.9.1: 17 | - New: Add one-time notifcation to use original ex-mode, since it support vim-mode-plus now. 18 | 19 | ## 0.9.0: 20 | - New: `nohlsearch` command. 21 | 22 | ## 0.8.1: 23 | - Keymaps: Provide default keymaps. 24 | 25 | ```coffeescript 26 | 'atom-text-editor.vim-mode-plus.normal-mode': 27 | ':': 'vim-mode-plus-ex-mode:open' 28 | '!': 'vim-mode-plus-ex-mode:toggle-setting' 29 | ``` 30 | 31 | ## 0.8.0: Unpublished because of mistake. 32 | 33 | ## 0.7.1: 34 | - New: `Move To Relative Line` e.g. `+5`, `-5` by @gabeboning. 35 | 36 | ## 0.7.0 37 | - Improve: Cleanup 38 | - Improve: Cache select-list-item once generated. 39 | - Improve: Make `MoveToLineAndColumn` motion to `characterwise` 40 | - Breaking: List item was limited to max=5, but now no longer set max items. 41 | 42 | ## 0.6.1 43 | - Fix: Bug 44 | 45 | ## 0.6.0 46 | - New: `x` command(alias of `wq`) by @nwaywood 47 | - New: `xall` command(alias of `wqall`) 48 | - New: `moveToLineAndColumn` command. input with `line:column` e.g. `10:13`(line=10, column=13) 49 | 50 | ## 0.5.0 51 | - Fix: Fast typing :w results in q command because #8 52 | 53 | ## 0.4.0 - Refactoring 54 | ## 0.3.0 - Improve 55 | ## 0.2.0 - First published version 56 | ## 0.1.0 - prototype 57 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | [Check this](https://github.com/t9md/atom-vim-mode-plus/blob/master/ISSUE_TEMPLATE.md) 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-mode-plus-ex-mode 2 | 3 | - I originally released this package for quick workaround of lack of ex-mode support. 4 | - But now [ex-mode](https://atom.io/packages/ex-mode) works with vim-mode-plus. 5 | - If you want better ex-mode, don't use this package, use [ex-mode](https://atom.io/packages/ex-mode) instead. 6 | 7 | Experimental ex-mode support for vim-mode-plus 8 | 9 | ## How to use? 10 | 11 | Default keymaps are provided from v0.8.0. 12 | In `normal-mode`, following keymaps are available. 13 | 14 | - `:`: `vim-mode-plus-ex-mode:open`: open ex-mode then can use following operations 15 | - `11`: Move to line 11. 16 | - `+11`, `-11`: Move to relative line. 17 | - `50%`: Move to 50% of buffer.. 18 | - `15:10`: Move to line 15, column 10 19 | - `!`: Toggle boolean config parameter. 20 | - `w`, `wq`, `split`, `vsplit` etc. 21 | - `nohlsearch`: to clear `highlight-search`. 22 | 23 | - `!`: `vim-mode-plus-ex-mode:toggle-setting`: toggle boolean setting-value. 24 | - Choose setting parameter to toggle boolean value. 25 | - Shorthand of `:` then input `!` 26 | 27 | ## NOTE for heavy ex-mode user 28 | 29 | I'm not motivated for this package. 30 | My thought for providing ex-mode(`:` command) in Atom is [here #52](https://github.com/t9md/atom-vim-mode-plus/issues/52). 31 | Please understand this package as reference, example, prototype implementation. 32 | Don't ask me to improve this package into **true** ex-mode emulation. 33 | 34 | Instead, you can learn 35 | 36 | - [Atom's way](https://flight-manual.atom.io/) 37 | - [vim-mode-plus's way](https://github.com/t9md/atom-vim-mode-plus/wiki) 38 | 39 | For example, there are quicker and effective ways than doing `s/abc/def/g`. 40 | By using Atom's multiple-cursors, and also vim-mod-plus's `occurrence` feature(unique enhancement). 41 | -------------------------------------------------------------------------------- /keymaps/vim-mode-plus-ex-mode.cson: -------------------------------------------------------------------------------- 1 | 'atom-text-editor.vim-mode-plus.normal-mode': 2 | ':': 'vim-mode-plus-ex-mode:open' 3 | '!': 'vim-mode-plus-ex-mode:toggle-setting' 4 | -------------------------------------------------------------------------------- /lib/commands.js: -------------------------------------------------------------------------------- 1 | // ex command 2 | // ------------------------- 3 | function w({editor} = {}) { 4 | if (editor && editor.getPath()) { 5 | editor.save() 6 | } else { 7 | atom.workspace.saveActivePaneItem() 8 | } 9 | } 10 | 11 | function q() { 12 | atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow() 13 | } 14 | 15 | function wq() { 16 | w() 17 | q() 18 | } 19 | 20 | function qall() { 21 | atom.workspace.getPaneItems().forEach(() => q()) 22 | } 23 | 24 | function wall() { 25 | atom.workspace 26 | .getTextEditors() 27 | .filter(editor => editor.isModified()) 28 | .forEach(editor => w({editor})) 29 | } 30 | 31 | function wqall() { 32 | atom.workspace.getPaneItems().forEach(() => wq()) 33 | } 34 | 35 | function split({editorElement}) { 36 | atom.commands.dispatch(editorElement, "pane:split-down-and-copy-active-item") 37 | } 38 | 39 | function vsplit({editorElement}) { 40 | atom.commands.dispatch(editorElement, "pane:split-right-and-copy-active-item") 41 | } 42 | 43 | function nohlsearch({globalState}) { 44 | globalState.set("highlightSearchPattern", null) 45 | } 46 | 47 | // Configuration switch 48 | // ------------------------- 49 | function toggleConfig(param) { 50 | atom.config.set(param, !atom.config.get(param)) 51 | } 52 | 53 | function showInvisible() { 54 | toggleConfig("editor.showInvisibles") 55 | } 56 | 57 | function highlightSearch() { 58 | toggleConfig("vim-mode-plus.highlightSearch") 59 | } 60 | 61 | function softWrap({editorElement}) { 62 | atom.commands.dispatch(editorElement, "editor:toggle-soft-wrap") 63 | } 64 | 65 | function indentGuide({editorElement}) { 66 | atom.commands.dispatch(editorElement, "editor:toggle-indent-guide") 67 | } 68 | 69 | function lineNumbers({editorElement}) { 70 | atom.commands.dispatch(editorElement, "editor:toggle-line-numbers") 71 | } 72 | 73 | // When number was typed 74 | // ------------------------- 75 | function moveToLine(vimState, {row}) { 76 | vimState.operationStack.run("MoveToFirstLine", {count: row}) 77 | } 78 | 79 | function moveToLineAndColumn(vimState, {row, column}) { 80 | vimState.operationStack.run("MoveToLineAndColumn", {count: row, column}) 81 | } 82 | 83 | function moveToLineByPercent(vimState, {percent}) { 84 | vimState.operationStack.run("MoveToLineByPercent", {count: percent}) 85 | } 86 | 87 | function moveToRelativeLine(vimState, {offset}) { 88 | vimState.operationStack.run("MoveToRelativeLine", {count: offset + (offset > 0 ? 1 : -1)}) 89 | } 90 | 91 | const x = wq 92 | const xall = wqall 93 | const normalCommands = { 94 | w, 95 | wq, 96 | x, 97 | wall, 98 | wqall, 99 | xall, 100 | q, 101 | qall, 102 | split, 103 | vsplit, 104 | nohlsearch, 105 | } 106 | 107 | const toggleCommands = { 108 | showInvisible, 109 | softWrap, 110 | indentGuide, 111 | lineNumbers, 112 | highlightSearch, 113 | } 114 | 115 | const numberCommands = { 116 | moveToLine, 117 | moveToLineAndColumn, 118 | moveToLineByPercent, 119 | moveToRelativeLine, 120 | } 121 | 122 | module.exports = {normalCommands, toggleCommands, numberCommands} 123 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | const {CompositeDisposable} = require("atom") 2 | 3 | module.exports = { 4 | activate() { 5 | if (!atom.config.get("vim-mode-plus-ex-mode.notifiedUseExMode")) { 6 | const message = `Use [ex-mode](https://atom.io/packages/ex-mode) if you want better ex-mode.` 7 | atom.notifications.addInfo(message, {dismissable: true}) 8 | atom.config.set("vim-mode-plus-ex-mode.notifiedUseExMode", this.notifiedUseExMode) 9 | } 10 | 11 | const consumeVimPromise = new Promise(resolve => { 12 | this.resolveConsumeVimPromise = resolve 13 | }) 14 | 15 | const toggle = (editor, commandKind) => { 16 | consumeVimPromise.then(service => { 17 | this.getView().toggle(service.getEditorState(editor), commandKind) 18 | }) 19 | } 20 | 21 | this.subscriptions = new CompositeDisposable( 22 | atom.commands.add("atom-text-editor:not([mini])", { 23 | "vim-mode-plus-ex-mode:open"() { 24 | toggle(this.getModel(), "normalCommands") 25 | }, 26 | "vim-mode-plus-ex-mode:toggle-setting"() { 27 | toggle(this.getModel(), "toggleCommands") 28 | }, 29 | }) 30 | ) 31 | }, 32 | 33 | deactivate() { 34 | this.subscriptions.dispose() 35 | }, 36 | 37 | getView() { 38 | if (!this.view) { 39 | const View = require("./view") 40 | this.view = new View() 41 | } 42 | return this.view 43 | }, 44 | 45 | consumeVim(service) { 46 | class MoveToLineAndColumn extends service.getClass("MoveToFirstLine") { 47 | constructor(...args) { 48 | super(...args) 49 | this.wise = "characterwise" 50 | } 51 | 52 | moveCursor(cursor) { 53 | super.moveCursor(cursor) 54 | cursor.setBufferPosition([cursor.getBufferRow(), this.column - 1]) 55 | } 56 | } 57 | MoveToLineAndColumn.commandPrefix = "vim-mode-plus-user" 58 | MoveToLineAndColumn.register() 59 | this.resolveConsumeVimPromise(service) 60 | }, 61 | } 62 | -------------------------------------------------------------------------------- /lib/view.js: -------------------------------------------------------------------------------- 1 | const _ = require("underscore-plus") 2 | const {SelectListView, $, $$} = require("atom-space-pen-views") 3 | const fuzzaldrin = require("fuzzaldrin") 4 | 5 | module.exports = class View extends SelectListView { 6 | // Disable throttling populateList for initialInput 7 | schedulePopulateList() { 8 | if (this.initialInput) { 9 | if (this.isOnDom()) this.populateList() 10 | this.initialInput = false 11 | } else { 12 | super.schedulePopulateList() 13 | } 14 | } 15 | 16 | initialize() { 17 | this.commandsByKind = require("./commands") 18 | this.commandItemsByKind = {} 19 | this.panel = atom.workspace.addModalPanel({item: this, visible: false}) 20 | 21 | for (const kind in this.commandsByKind) { 22 | const commandNames = Object.keys(this.commandsByKind[kind]) 23 | this.commandItemsByKind[kind] = commandNames.map(name => this.buildItem(kind, name)) 24 | } 25 | this.addClass("vim-mode-plus-ex-mode") 26 | super.initialize() 27 | } 28 | 29 | getFilterKey() { 30 | return "displayName" 31 | } 32 | 33 | cancelled() { 34 | this.panel.hide() 35 | } 36 | 37 | toggle(vimState, commandKind) { 38 | if (this.panel.isVisible()) { 39 | this.cancel() 40 | } else { 41 | this.vimState = vimState 42 | 43 | this.initialInput = true 44 | this.storeFocusedElement() 45 | this.panel.show() 46 | this.setItems(this.commandItemsByKind[commandKind]) 47 | this.focusFilterEditor() 48 | } 49 | } 50 | 51 | buildItem(kind, name, options) { 52 | const displayName = ["toggleCommands", "numberCommands"].includes(kind) 53 | ? _.humanizeEventName(_.dasherize(name)) 54 | : name 55 | return {name, kind, displayName, options} 56 | } 57 | 58 | getFallBackItemsForQuery(query) { 59 | if (/^!/.test(query)) { 60 | const filterQuery = query.slice(1) // to trim first '!' 61 | const items = this.commandItemsByKind["toggleCommands"] 62 | return fuzzaldrin.filter(items, filterQuery, {key: this.getFilterKey()}) 63 | } else if (/^[+-\d]/.test(query)) { 64 | const item = this.getNumberCommandItem(query) 65 | if (item) return [item] 66 | } 67 | return [] 68 | } 69 | 70 | getNumberCommandItem(query) { 71 | const buildItem = this.buildItem.bind(this, "numberCommands") 72 | 73 | let match 74 | if ((match = query.match(/^(\d+)+$/))) { 75 | return buildItem("moveToLine", {row: Number(match[1])}) 76 | } else if ((match = query.match(/^(\d+)%$/))) { 77 | return buildItem("moveToLineByPercent", {percent: Number(match[1])}) 78 | } else if ((match = query.match(/^(\d+):(\d+)$/))) { 79 | return buildItem("moveToLineAndColumn", {row: Number(match[1]), column: Number(match[2])}) 80 | } else if ((match = query.match(/^([+-]\d+)$/))) { 81 | return buildItem("moveToRelativeLine", {offset: Number(match[1])}) 82 | } 83 | } 84 | 85 | // Use as command missing hook. 86 | getEmptyMessage(itemCount, filteredItemCount) { 87 | this.setError(null) 88 | this.setFallbackItems(this.getFallBackItemsForQuery(this.getFilterQuery())) 89 | return this.selectItemView(this.list.find("li:first")) // FIXME 90 | } 91 | 92 | setFallbackItems(items) { 93 | for (const item of items) { 94 | const itemView = $(this.viewForItem(item)) 95 | itemView.data("select-list-item", item) 96 | this.list.append(itemView) 97 | } 98 | } 99 | 100 | viewForItem({displayName}) { 101 | // Style matched characters in search results 102 | let filterQuery = this.getFilterQuery() 103 | if (filterQuery.startsWith("!")) { 104 | filterQuery = filterQuery.slice(1) 105 | } 106 | 107 | const matches = fuzzaldrin.match(displayName, filterQuery) 108 | return $$(function() { 109 | this.li({class: "event", "data-event-name": name}, () => { 110 | this.span({title: displayName}, () => { 111 | highlightMatches(this, displayName, matches, 0) 112 | }) 113 | }) 114 | }) 115 | } 116 | 117 | confirmed({kind, name, options}) { 118 | this.cancel() 119 | this.commandsByKind[kind][name](this.vimState, options) 120 | } 121 | } 122 | 123 | function highlightMatches(context, name, matches, offsetIndex = 0) { 124 | let lastIndex = 0 125 | let matchedChars = [] // Build up a set of matched chars to be more semantic 126 | 127 | for (let matchIndex of matches) { 128 | matchIndex -= offsetIndex 129 | if (matchIndex < 0) continue 130 | 131 | // If marking up the basename, omit name matches 132 | const unmatched = name.substring(lastIndex, matchIndex) 133 | if (unmatched) { 134 | if (matchedChars.length) { 135 | context.span(matchedChars.join(""), {class: "character-match"}) 136 | } 137 | matchedChars = [] 138 | context.text(unmatched) 139 | } 140 | matchedChars.push(name[matchIndex]) 141 | lastIndex = matchIndex + 1 142 | } 143 | 144 | if (matchedChars.length) { 145 | context.span(matchedChars.join(""), {class: "character-match"}) 146 | } 147 | // Remaining characters are plain text 148 | context.text(name.substring(lastIndex)) 149 | } 150 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vim-mode-plus-ex-mode", 3 | "main": "./lib/main", 4 | "version": "0.11.0", 5 | "description": "Use original ex-mode instead of this package", 6 | "repository": "https://github.com/t9md/atom-vim-mode-plus-ex-mode", 7 | "license": "MIT", 8 | "activationCommands": { 9 | "atom-text-editor": [ 10 | "vim-mode-plus-ex-mode:open", 11 | "vim-mode-plus-ex-mode:toggle-setting" 12 | ] 13 | }, 14 | "engines": { 15 | "atom": "^1.19.0" 16 | }, 17 | "dependencies": { 18 | "atom-space-pen-views": "^2.1.0", 19 | "fuzzaldrin": "^2.1.0", 20 | "underscore-plus": "^1.6.6" 21 | }, 22 | "consumedServices": { 23 | "vim-mode-plus": { 24 | "versions": { 25 | "^0.1.0": "consumeVim" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /styles/vim-mode-plus-ex-mode.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | 3 | // SelectList Highlight matched text 4 | // ========================= 5 | .vim-mode-plus-ex-mode .list-group { 6 | .character-match { 7 | color: @text-color-highlight; 8 | font-weight: bold; 9 | } 10 | } 11 | --------------------------------------------------------------------------------