├── .gitignore ├── .travis.yml ├── README.md ├── docs ├── motions.md ├── operators.md ├── overview.md ├── scrolling.md └── windows.md ├── keymaps └── vim-mode.cson ├── lib ├── vim-globals.coffee ├── vim-mode.coffee ├── vim-neovim-session.coffee ├── vim-state.coffee ├── vim-sync.coffee └── vim-utils.coffee ├── package.json └── styles └── vim-mode.less /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | 3 | notifications: 4 | email: 5 | on_success: never 6 | on_failure: change 7 | 8 | script: 'curl -s https://raw.githubusercontent.com/atom/ci/master/build-package.sh | sh' 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # I'm not working on this anymore. It was all experimental. 3 | 4 | This project/idea needs a new home. 5 | 6 | # What is this? 7 | 8 | This is a work in progress [Atom](http://atom.io/) package that implements 9 | complete vim bindings by connecting to 10 | [Neovim](http://github.com/neovim/neovim). 11 | 12 | 13 | # What's new? 14 | 15 | I've update everything to work with Neovim 0.1.2 (it should also work with the 16 | version in master of [Neovim](http://github.com/neovim/neovim). The version 17 | I'm currently using: [download]( https://github.com/neovim/neovim/archive/v0.1.2.tar.gz). 18 | 19 | On the [Atom](https://atom.io/) side I am currently using version 1.5.3. In 20 | versions 0.206 and later you will need to change the name of the directory 21 | vim-mode to something else (I use the name nvim-mode). If you don't Atom 22 | confuses this plugin with the one developed by GitHub. 23 | 24 | It should be usable enough that if you are adventurous you will be able to get 25 | day-to-day work done. There are, however, plenty of features missing, so you 26 | will have to be patient when you use it. 27 | 28 | # How do you run this? 29 | 30 | Install, run, and quit Atom to make sure .atom exists 31 | 32 | Install vim-mode 33 | 34 | $ cd .atom/packages 35 | $ git clone https://github.com/carlosdcastillo/vim-mode.git 36 | $ cd vim-mode 37 | $ apm install 38 | 39 | On OS X and Linux, create a folder for the named pipe: 40 | 41 | $ mkdir -p /tmp/neovim 42 | 43 | Run Neovim, pointing it to the named pipe, on OS X and Linux: 44 | 45 | $ NVIM_LISTEN_ADDRESS=/tmp/neovim/neovim nvim 46 | 47 | The equivalent in Windows (define an environment variable and point it to the 48 | named pipe) is: 49 | 50 | set NVIM_LISTEN_ADDRESS=\\.\pipe\neovim 51 | 52 | and then 53 | 54 | nvim.exe 55 | 56 | # What do you want to do with this? 57 | 58 | This project aims to: 59 | 60 | * Bring real vim bindings to Atom. 61 | * Give the abstract-ui Neovim functionality a work out and find issues using 62 | the msgpack api. 63 | * Eventually build an editor that I would find useful. At the current state it 64 | is pre-alpha. 65 | 66 | # See it in action 67 | 68 | ***A video that shows the current (June/2015) status:*** 69 | 70 | http://youtu.be/FTInd3H7Zec 71 | 72 | A video that shows the integration in action in March/2015: 73 | 74 | https://www.youtube.com/watch?v=7TVBcdONEJo 75 | 76 | An older video from January of the integration in action, using the abstract-ui 77 | branch: 78 | 79 | https://www.youtube.com/watch?v=yluIxQRjUCk 80 | 81 | and this is an old video from 2014 using the old redraw-events branch (from mid 82 | 2014): 83 | 84 | http://www.youtube.com/watch?v=lH_zb7X6mZw 85 | 86 | # Things TO DO 87 | 88 | * Handle files of more than 9999 lines. 89 | * Handle (or handle better) Atom initiated cursor position changes. 90 | * Make one of the following UI connections/integrations: visual selection, 91 | highlight search, auto completion, etc. 92 | * Better handle editing of new files 93 | * Make the geometry of the Atom buffer fully match the geometry of the Neovim 94 | buffer. 95 | 96 | # Contributing 97 | 98 | 1. Find something that doesn't work (this step shouldn't be that hard, plenty 99 | of things don't work yet) 100 | 2. Either (a) fix it and send me a pull request or (b) file a bug report so I know it 101 | needs to be fixed. 102 | 103 | # Configuring Atom 104 | 105 | To make sure that hjkl get repeated like (Vim and Neovim) on Mac you will need to 106 | run (from the command line): 107 | 108 | defaults write com.github.atom ApplePressAndHoldEnabled -bool false 109 | 110 | -------------------------------------------------------------------------------- /docs/motions.md: -------------------------------------------------------------------------------- 1 | ## Implemented Motions 2 | 3 | * [w](http://vimhelp.appspot.com/motion.txt.html#w) 4 | * [W](http://vimhelp.appspot.com/motion.txt.html#W) 5 | * [e](http://vimhelp.appspot.com/motion.txt.html#e) 6 | * [E](http://vimhelp.appspot.com/motion.txt.html#E) 7 | * [b](http://vimhelp.appspot.com/motion.txt.html#b) 8 | * [B](http://vimhelp.appspot.com/motion.txt.html#B) 9 | * [h](http://vimhelp.appspot.com/motion.txt.html#h) 10 | * [j](http://vimhelp.appspot.com/motion.txt.html#j) 11 | * [k](http://vimhelp.appspot.com/motion.txt.html#k) 12 | * [l](http://vimhelp.appspot.com/motion.txt.html#l) 13 | * [{](http://vimhelp.appspot.com/motion.txt.html#%7B) 14 | * [}](http://vimhelp.appspot.com/motion.txt.html#%7D) 15 | * [^](http://vimhelp.appspot.com/motion.txt.html#%5E) 16 | * [$](http://vimhelp.appspot.com/motion.txt.html#%24) 17 | * [0](http://vimhelp.appspot.com/motion.txt.html#0) 18 | * [gg](http://vimhelp.appspot.com/motion.txt.html#gg) 19 | * [G](http://vimhelp.appspot.com/motion.txt.html#G) 20 | * [gt](http://vimhelp.appspot.com/tabpage.txt.html#gt) 21 | * [gT](http://vimhelp.appspot.com/tabpage.txt.html#gT) 22 | * [H](http://vimhelp.appspot.com/motion.txt.html#H) 23 | * [L](http://vimhelp.appspot.com/motion.txt.html#L) 24 | * [M](http://vimhelp.appspot.com/motion.txt.html#M) 25 | * ['[a-z][A-Z]](http://vimhelp.appspot.com/motion.txt.html#%27) 26 | * [`[a-z][A-Z]](http://vimhelp.appspot.com/motion.txt.html#%27) 27 | -------------------------------------------------------------------------------- /docs/operators.md: -------------------------------------------------------------------------------- 1 | ## Implemented Operators 2 | 3 | * [Delete](http://vimhelp.appspot.com/change.txt.html#deleting) 4 | * `vwd` - works in visual mode 5 | * `dw` - with a motion 6 | * `3d2w` - with repeating operator and motion 7 | * `dd` - linewise 8 | * `d2d` - repeated linewise 9 | * `D` - delete to the end of the line 10 | * `X` - delete the character before the cursor 11 | * [Change](http://vimhelp.appspot.com/change.txt.html#c) 12 | * `vwc` - works in visual mode 13 | * `cw` - deletes the next word and switches to insert mode. 14 | * `cc` - linewise 15 | * `c2c` - repeated linewise 16 | * `C` - change to the end of the line 17 | * [Yank](http://vimhelp.appspot.com/change.txt.html#yank) 18 | * `vwy` - works in visual mode 19 | * `yw` - with a motion 20 | * `yy` - linewise 21 | * `y2y` - repeated linewise 22 | * `"ayy` - supports registers (only named a-h, pending more 23 | advanced atom keymap support) 24 | * `Y` - linewise 25 | * Indent/Outdent/Auto-indent 26 | * `vw>` - works in visual mode 27 | * `>>` - indent current line one level 28 | * `<<` - outdent current line one level 29 | * `==` - auto-indents current line 30 | * [Put](http://vimhelp.appspot.com/change.txt.html#p) 31 | * `p` - default register 32 | * `P` - pastes the default register before the current cursor. 33 | * `"ap` - supports registers (only named a-h, pending more 34 | advanced atom keymap support) 35 | * [Join](http://vimhelp.appspot.com/change.txt.html#J) 36 | * `J` - joins the current line with the immediately following line. 37 | * [Mark](http://vimhelp.appspot.com/motion.txt.html#m) 38 | * `m[a-z][A-Z]` - marks the current cursor position 39 | -------------------------------------------------------------------------------- /docs/overview.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | * There are only currently two modes, command mode and insert mode. 4 | * Motions have repeat support, `d3w` will delete three words. 5 | * Insert mode can be entered using `i`, `I`, `a`, `A`, `o`, or `O`. 6 | * Registers are a work in progress 7 | * What Exists: 8 | * `a-z` - Lowercase named registers 9 | * `*,` `+` - System clipboard registers, although there's no distinction between the two currently. 10 | * `%` - Current filename read-only register 11 | * `_` - Blackhole register 12 | * What Doesn't Exist: 13 | * default buffer doesn't yet save on delete operations. 14 | * `A-Z` - Appending via upper case registers 15 | -------------------------------------------------------------------------------- /docs/scrolling.md: -------------------------------------------------------------------------------- 1 | ## Implemented Scrolling Commands 2 | 3 | * [ctrl-e](http://vimhelp.appspot.com/scroll.txt.html#CTRL-E) 4 | * [ctrl-y](http://vimhelp.appspot.com/scroll.txt.html#CTRL-Y) 5 | * [ctrl-f](http://vimhelp.appspot.com/scroll.txt.html#CTRL-F) 6 | * [ctrl-b](http://vimhelp.appspot.com/scroll.txt.html#CTRL-B) 7 | -------------------------------------------------------------------------------- /docs/windows.md: -------------------------------------------------------------------------------- 1 | ## Implemented Split Pane Commands 2 | 3 | * `ctrl-w h`/`ctrl-w left`/`ctrl-w ctrl-h` - focus pane on left 4 | * `ctrl-w l`/`ctrl-w right`/`ctrl-w ctrl-l` - focus pane on right 5 | * `ctrl-w k`/`ctrl-w up`/`ctrl-w ctrl-k` - focus pane above 6 | * `ctrl-w j`/`ctrl-w down`/`ctrl-w ctrl-j` - focus pane below 7 | * `ctrl-w w`/`ctrl-w ctrl-w` - focus next pane 8 | * `ctrl-w p`/`ctrl-w ctrl-p` - focus previous pane 9 | * `ctrl-w v`/`ctrl-w ctrl-v` - create vertical split 10 | * `ctrl-w s`/`ctrl-w ctrl-s` - create horizontal split 11 | * `ctrl-w c`/`ctrl-w ctrl-c`: close focused pane 12 | * `ctrl-w q`/`ctrl-w ctrl-q` - close focused tab 13 | -------------------------------------------------------------------------------- /keymaps/vim-mode.cson: -------------------------------------------------------------------------------- 1 | 'atom-text-editor.vim-mode': 2 | 'enter': 'unset!' 3 | 'backspace': 'core:cancel' 4 | 'ctrl-b': 'core:cancel' 5 | 'ctrl-f': 'core:cancel' 6 | 'ctrl-n': 'unset!' 7 | 'ctrl-p': 'unset!' 8 | 'tab': 'core:cancel' 9 | 'end': 'unset!' 10 | 'home': 'unset!' 11 | 'delete': 'unset!' 12 | 'up': 'unset!' 13 | 'down': 'unset!' 14 | 'left': 'unset!' 15 | 'right': 'unset!' 16 | 'pagedown': 'unset!' 17 | 'pageup': 'unset!' 18 | 'shift-tab': 'core:cancel' 19 | 20 | '.platform-win32 .atom-text-editor': 21 | 'ctrl-r': 'unset!' 22 | 23 | 'atom-text-editor.vim-mode:not(mini).autocomplete-active': 24 | 'tab': 'autocomplete-plus:confirm' 25 | 'up': 'core:move-up' 26 | 'down': 'core:move-down' 27 | 'esc': 'autocomplete-plus:cancel' 28 | -------------------------------------------------------------------------------- /lib/vim-globals.coffee: -------------------------------------------------------------------------------- 1 | root = exports ? this 2 | 3 | unless root.lupdates 4 | root.lupdates = [] 5 | 6 | unless root.current_editor 7 | root.current_editor = undefined 8 | 9 | unless root.tlnumber 10 | root.tlnumber = 0 11 | 12 | unless root.internal_change 13 | internal_change = false 14 | 15 | unless root.updating 16 | updating = false 17 | -------------------------------------------------------------------------------- /lib/vim-mode.coffee: -------------------------------------------------------------------------------- 1 | 2 | {Disposable, CompositeDisposable} = require 'event-kit' 3 | 4 | VimState = require './vim-state' 5 | 6 | module.exports = 7 | config: 8 | embed: 9 | type: 'boolean' 10 | default: false 11 | "neovim-path": 12 | type: 'string' 13 | default: '/usr/local/bin/nvim' 14 | 15 | activate: -> 16 | 17 | @disposables = new CompositeDisposable 18 | 19 | @disposables.add atom.workspace.observeTextEditors (editor) -> 20 | 21 | console.log 'uri:',editor.getURI() 22 | editorView = atom.views.getView(editor) 23 | 24 | if editorView 25 | console.log 'view:',editorView 26 | editorView.classList.add('vim-mode') 27 | editorView.vimState = new VimState(editorView) 28 | 29 | 30 | deactivate: -> 31 | 32 | atom.workspaceView?.eachEditorView (editorView) -> 33 | editorView.off('.vim-mode') 34 | 35 | @disposables.dispose() 36 | 37 | -------------------------------------------------------------------------------- /lib/vim-neovim-session.coffee: -------------------------------------------------------------------------------- 1 | 2 | MsgPack = require 'msgpack5rpc' 3 | os = require 'os' 4 | net = require 'net' 5 | cp = require 'child_process' 6 | 7 | socket_address = () -> 8 | if os.platform() is 'win32' 9 | '\\\\.\\pipe\\neovim' 10 | else 11 | '/tmp/neovim/neovim' 12 | 13 | nvim_proc = undefined 14 | 15 | input = undefined 16 | output = undefined 17 | 18 | if atom.config.get('vim-mode.embed') 19 | nvim_proc = cp.spawn( 20 | atom.config.get('vim-mode.neovim-path'), 21 | ['--embed', '-u', 'NONE', '-N'], 22 | {}) 23 | console.log('Spawning nvim instance') 24 | input = nvim_proc.stdin 25 | output = nvim_proc.stdout 26 | else 27 | tmp_socket = new net.Socket() 28 | tmp_socket.connect(socket_address()) 29 | tmp_socket.on('error', (error) -> 30 | console.log 'error communicating (send message): ' + error 31 | tmp_socket.destroy() 32 | ) 33 | console.log('Connecting to existing nvim instance') 34 | input = tmp_socket 35 | output = tmp_socket 36 | 37 | tmpsession = new MsgPack() 38 | tmpsession.attach(input, output) 39 | 40 | class RBuffer 41 | constructor:(data) -> 42 | @data = data 43 | 44 | class RWindow 45 | constructor:(data) -> 46 | @data = data 47 | 48 | class RTabpage 49 | constructor:(data) -> 50 | @data = data 51 | 52 | types = [] 53 | 54 | # Actual persisted session 55 | session = undefined 56 | 57 | tmpsession.request('vim_get_api_info', [], (err, res) -> 58 | metadata = res[1] 59 | constructors = [ 60 | RBuffer 61 | RWindow 62 | RTabpage 63 | ] 64 | i = 0 65 | l = constructors.length 66 | while i < l 67 | ((constructor) -> 68 | types.push 69 | constructor: constructor 70 | code: metadata.types[constructor.name[1..]].id 71 | decode: (data) -> 72 | new constructor(data) 73 | encode: (obj) -> 74 | obj.data 75 | return 76 | ) constructors[i] 77 | i++ 78 | 79 | tmpsession.detach() 80 | 81 | if !atom.config.get('vim-mode.embed') 82 | socket = new net.Socket() 83 | socket.connect(socket_address()) 84 | input = socket 85 | output = socket 86 | 87 | session = new MsgPack(types) 88 | session.attach(input, output) 89 | ) 90 | 91 | module.exports = { 92 | sendMessage: (message,f = undefined) -> 93 | try 94 | if message[0] and message[1] 95 | session.request(message[0], message[1], (err, res) -> 96 | if f 97 | if typeof(res) is 'number' 98 | f(util.inspect(res)) 99 | else 100 | f(res) 101 | ) 102 | catch err 103 | console.log 'error in neovim_send_message '+err 104 | console.log 'm1:',message[0] 105 | console.log 'm2:',message[1] 106 | 107 | addNotificationListener: (callback) -> 108 | session.on('notification', callback) 109 | } 110 | 111 | -------------------------------------------------------------------------------- /lib/vim-state.coffee: -------------------------------------------------------------------------------- 1 | _ = require 'underscore-plus' 2 | $ = require 'jquery' 3 | {Point, Range} = require 'atom' 4 | Marker = require 'atom' 5 | util = require 'util' 6 | 7 | VimUtils = require './vim-utils' 8 | VimGlobals = require './vim-globals' 9 | VimSync = require './vim-sync' 10 | 11 | neovim = require './vim-neovim-session' 12 | 13 | DEBUG = false 14 | 15 | COLS = 120 16 | 17 | eventHandler = undefined 18 | nrows = 10 19 | ncols = 10 20 | mode = 'command' 21 | subscriptions = {} 22 | subscriptions['redraw'] = false 23 | screen = [] 24 | screen_f = [] 25 | scrolled = false 26 | editor_views = {} 27 | active_change = true 28 | next_new_file_id = 0 29 | 30 | scrolltopchange_subscription = undefined 31 | bufferchange_subscription = undefined 32 | bufferchangeend_subscription = undefined 33 | cursorpositionchange_subscription = undefined 34 | 35 | buffer_change_subscription = undefined 36 | buffer_destroy_subscription = undefined 37 | 38 | non_file_assoc_atom_to_nvim = {} 39 | non_file_assoc_nvim_to_atom = {} 40 | 41 | scrolltop = undefined 42 | reversed_selection = false 43 | 44 | element = document.createElement("item-view") 45 | interval_sync = undefined 46 | interval_timeout = undefined 47 | 48 | getMaxOccurrence = (arr) -> 49 | o = {} 50 | maxCount = 0 51 | maxValue = undefined 52 | m = undefined 53 | i = 0 54 | iLen = arr.length 55 | while i < iLen 56 | m = arr[i] 57 | if !o.hasOwnProperty(m) 58 | o[m] = 0 59 | ++o[m] 60 | if o[m] > maxCount 61 | maxCount = o[m] 62 | maxValue = m 63 | i++ 64 | maxValue 65 | 66 | #These two functions are a work around so we don't stack 67 | #vim_evals in the middle of the user typing text. 68 | #see: https://github.com/neovim/neovim/issues/3720 69 | 70 | activate_timer = () -> 71 | f = -> ( 72 | ns_redraw_win_end() 73 | ) 74 | g = -> ( 75 | console.log 'INNER',element.innerHTML 76 | text = element.innerHTML.split(' ').join(' ') 77 | if text 78 | text = text.split('')[1] 79 | if text 80 | text = text.split('')[0] 81 | console.log 'text:',text 82 | 83 | textb = text[0..text.length/2] 84 | text = text[text.length/2..text.length-1] 85 | text = text.split(' ').join('') 86 | console.log 'text:',text 87 | console.log 'textb:',textb 88 | if ((text.length==1 and mode=='command' and \ 89 | textb.indexOf('VISUAL')==-1) ) 90 | neovim.sendMessage(['vim_input',['']]) 91 | if (textb.indexOf('completion')==-1) 92 | interval_sync = setInterval(f, 100) 93 | ) 94 | interval_timeout = setTimeout(g, 500) 95 | 96 | deactivate_timer = () -> 97 | if interval_timeout 98 | clearTimeout(interval_timeout) 99 | if interval_sync 100 | clearInterval(interval_sync) 101 | 102 | 103 | #This code registers the change handler. The undo fix is a workaround 104 | #a bug I was not able to detect what coupled an extra state when 105 | #I did Cmd-X and then pressed u. Challenge: give me the set of reasons that 106 | #trigger such situation in the code. 107 | 108 | register_change_handler = () -> 109 | bufferchange_subscription = \ 110 | VimGlobals.current_editor.onDidChange ( (change) -> 111 | 112 | if not VimGlobals.internal_change and not VimGlobals.updating 113 | 114 | try 115 | last_text = VimGlobals.current_editor.getText() 116 | text_list_tmp = last_text.split('\n') 117 | text_list = [] 118 | for item in text_list_tmp 119 | text_list.push item.split('\r').join('') 120 | 121 | undo_fix = 122 | not (change.start is 0 and change.end is text_list.length-1 \ 123 | and change.bufferDelta is 0) 124 | 125 | #undo_fix = true 126 | 127 | qtop = VimGlobals.current_editor.element.getScrollTop() 128 | qbottom = VimGlobals.current_editor.element.getScrollBottom() 129 | 130 | tln = Math.floor((qtop)/lineSpacing()+1) 131 | bot = Math.floor((qbottom )/lineSpacing()+1) 132 | bot2 = VimGlobals.current_editor.getLineCount() 133 | if bot2 < bot 134 | bot = bot2 135 | 136 | rows = bot - tln 137 | #valid_loc = not (change.bufferDelta is 0 and \ 138 | #change.end-change.start >= rows-3) and \ 139 | #(change.start >= tln and \ 140 | #change.start < tln+rows-3) 141 | 142 | valid_loc = not (change.bufferDelta is 0 and \ 143 | change.end-change.start >= rows) and \ 144 | (change.start >= tln-1 and change.start < bot) 145 | 146 | #console.log 'try tln:',tln,'start:',change.start, 'bot:',bot 147 | if undo_fix and valid_loc 148 | console.log 'change:',change 149 | console.log 'tln:',tln,'start:',change.start, 'rows:',rows 150 | console.log '(uri:',VimGlobals.current_editor.getURI(),\ 151 | 'start:',change.start 152 | console.log 'end:',change.end,'delta:',change.bufferDelta,')' 153 | #deactivate_timer() 154 | VimGlobals.lupdates.push({uri: VimGlobals.current_editor.getURI(), \ 155 | text: last_text, start: change.start, end: change.end, \ 156 | delta: change.bufferDelta}) 157 | 158 | VimSync.real_update() 159 | #activate_timer() 160 | 161 | catch err 162 | console.log err 163 | console.log 'err: probably not a text editor window changed' 164 | 165 | ) 166 | 167 | 168 | 169 | #This code is called indirectly by timer and it's sole purpose is to sync the 170 | # number of lines from Neovim -> Atom. 171 | 172 | sync_lines = () -> 173 | 174 | if VimGlobals.updating 175 | return 176 | 177 | if VimGlobals.internal_change 178 | return 179 | 180 | if VimGlobals.current_editor 181 | VimGlobals.internal_change = true 182 | VimGlobals.updating = true 183 | neovim.sendMessage(['vim_eval',["line('$')"]], (nLines) -> 184 | 185 | if VimGlobals.current_editor.buffer.getLastRow() < parseInt(nLines) 186 | nl = parseInt(nLines) - VimGlobals.current_editor.buffer.getLastRow() 187 | diff = '' 188 | for i in [0..nl-2] 189 | diff = diff + '\n' 190 | append_options = {normalizeLineEndings: false} 191 | VimGlobals.current_editor.buffer.append(diff, append_options) 192 | 193 | neovim.sendMessage(['vim_command',['redraw!']], 194 | (() -> 195 | VimGlobals.internal_change = false 196 | VimGlobals.updating = false 197 | ) 198 | ) 199 | else if VimGlobals.current_editor.buffer.getLastRow() > parseInt(nLines) 200 | for i in [parseInt(nLines)..\ 201 | VimGlobals.current_editor.buffer.getLastRow()-1] 202 | VimGlobals.current_editor.buffer.deleteRow(i) 203 | 204 | neovim.sendMessage(['vim_command',['redraw!']], 205 | (() -> 206 | VimGlobals.internal_change = false 207 | VimGlobals.updating = false 208 | ) 209 | ) 210 | else 211 | VimGlobals.internal_change = false 212 | VimGlobals.updating = false 213 | 214 | ) 215 | 216 | # This is directly called by timer and makes sure of a bunch of housekeeping 217 | #functions like, marking the buffer modified, working around some Neovim for 218 | #Windows issues and invoking the code to sync the number of lines. 219 | 220 | ns_redraw_win_end = () -> 221 | 222 | VimGlobals.current_editor = atom.workspace.getActiveTextEditor() 223 | 224 | if not VimGlobals.current_editor 225 | return 226 | 227 | uri = VimGlobals.current_editor.getURI() 228 | 229 | if not uri 230 | uri = 'newfile'+next_new_file_id 231 | next_new_file_id = next_new_file_id + 1 232 | 233 | #console.log 'URI:',uri 234 | 235 | editor_views[uri] = atom.views.getView(VimGlobals.current_editor) 236 | 237 | if not editor_views[uri] 238 | return 239 | 240 | 241 | focused = editor_views[uri].classList.contains('is-focused') 242 | 243 | 244 | qtop = VimGlobals.current_editor.element.getScrollTop() 245 | qbottom = VimGlobals.current_editor.element.getScrollBottom() 246 | qrows = Math.floor((qbottom - qtop)/lineSpacing()+1) 247 | 248 | qleft = VimGlobals.current_editor.element.getScrollLeft() 249 | qright= VimGlobals.current_editor.element.getScrollRight() 250 | qcols = Math.floor((qright-qleft)/lineSpacingHorizontal())-1 251 | 252 | 253 | if (nrows isnt qrows or ncols isnt qcols) 254 | editor_views[uri].vimState.neovim_resize(180, qrows) 255 | nrows = qrows 256 | ncols = qcols 257 | 258 | 259 | if not VimGlobals.updating and not VimGlobals.internal_change 260 | neovim.sendMessage(['vim_eval',["expand('%:p')"]], (filename) -> 261 | if filename.indexOf('term://') == -1 262 | filename = filename.replace /^\s+|\s+$/g, "" 263 | console.log 'filename after processing:', filename 264 | if filename is '' 265 | filename = 'newfile'+next_new_file_id 266 | next_new_file_id = next_new_file_id + 1 267 | 268 | #console.log 'orig filename reported by vim:',filename 269 | ncefn = VimUtils.normalize_filename(uri) 270 | nfn = VimUtils.normalize_filename(filename) 271 | 272 | if ncefn and nfn and nfn isnt ncefn 273 | #console.log '-------------------------------',nfn 274 | #console.log '*******************************',ncefn 275 | atom.workspace.open(filename) 276 | 277 | #else 278 | # if filename and uri 279 | # sync_lines() 280 | else if filename of non_file_assoc_nvim_to_atom 281 | 282 | else 283 | tmpfilename = 'newfile'+next_new_file_id 284 | next_new_file_id = next_new_file_id + 1 285 | non_file_assoc_atom_to_nvim[tmpfilename] = filename 286 | non_file_assoc_nvim_to_atom[filename] = tmpfilename 287 | atom.workspace.open(tmpfilename) 288 | #sync_lines() 289 | ) 290 | 291 | active_change = false 292 | for texteditor in atom.workspace.getTextEditors() 293 | turi = texteditor.getURI() 294 | if turi 295 | if turi[turi.length-1] is '~' 296 | texteditor.destroy() 297 | if not turi 298 | texteditor.destroy() 299 | 300 | active_change = true 301 | 302 | lineSpacing = -> 303 | lineheight = parseFloat(atom.config.get('editor.lineHeight')) 304 | fontsize = parseFloat(atom.config.get('editor.fontSize')) 305 | return Math.floor(lineheight * fontsize) 306 | 307 | lineSpacingHorizontal = -> 308 | fontsize = parseFloat(atom.config.get('editor.fontSize')) 309 | return Math.floor(fontsize*0.6) 310 | 311 | vim_mode_save_file = () -> 312 | #console.log 'inside neovim save file' 313 | # 314 | VimGlobals.current_editor = atom.workspace.getActiveTextEditor() 315 | neovim.sendMessage(['vim_command',['write!']]) 316 | setTimeout( ( -> 317 | VimGlobals.current_editor.buffer.reload() 318 | VimGlobals.internal_change = false 319 | VimGlobals.updating = false 320 | ), 500) 321 | 322 | #VimGlobals.current_editor.setText(a) 323 | 324 | cursorPosChanged = (event) -> 325 | 326 | if not VimGlobals.internal_change 327 | VimGlobals.internal_change = true 328 | 329 | if (VimGlobals.current_editor and \ 330 | editor_views[VimGlobals.current_editor.getURI()].\ 331 | classList.contains('is-focused')) 332 | pos = event.newBufferPosition 333 | rp = pos.row + 1 334 | cp = pos.column + 1 335 | sel = VimGlobals.current_editor.getSelectedBufferRange() 336 | r = sel.end.row + 1 337 | c = sel.end.column + 1 338 | reversed_selection = false 339 | console.log '!!!!!!!!!!!!!!!!!!!!!!!!!',r,rp,c,cp 340 | if r isnt rp or c isnt cp 341 | r = sel.start.row + 1 342 | c = sel.start.column + 1 343 | reversed_selection = true 344 | 345 | 346 | #console.log 'sel:',sel 347 | neovim.sendMessage(['vim_command',['cal cursor('+r+','+c+')']], 348 | (() -> 349 | if not sel.isEmpty() 350 | VimGlobals.current_editor.setSelectedBufferRange(sel,\ 351 | {reversed:reversed_selection}) 352 | ) 353 | ) 354 | VimGlobals.internal_change = false 355 | 356 | scrollTopChanged = () -> 357 | if not VimGlobals.internal_change and not VimGlobals.updating 358 | if VimGlobals.current_editor 359 | if editor_views[VimGlobals.current_editor.getURI()].\ 360 | classList.contains('is-focused') 361 | 362 | else 363 | up = false 364 | if scrolltop 365 | diff = scrolltop - VimGlobals.current_editor.element.getScrollTop() 366 | if diff > 0 367 | up = false 368 | else 369 | up = true 370 | 371 | sels = VimGlobals.current_editor.getSelectedBufferRanges() 372 | #console.log 'sels:',sels 373 | for sel in sels 374 | if up 375 | r = sel.start.row + 1 376 | c = sel.start.column + 1 377 | else 378 | r = sel.end.row + 1 379 | c = sel.end.column + 1 380 | #console.log 'sel:',sel 381 | neovim.sendMessage(['vim_command',['cal cursor('+r+','+c+')']], 382 | (() -> 383 | if not sel.isEmpty() 384 | VimGlobals.current_editor.setSelectedBufferRange(\ 385 | sel,{selected: up}) 386 | ) 387 | ) 388 | 389 | if VimGlobals.current_editor 390 | scrolltop = VimGlobals.current_editor.element.getScrollTop() 391 | 392 | 393 | destroyPaneItem = (event) -> 394 | if event.item 395 | console.log 'destroying pane, will send command:', event.item 396 | console.log 'b:', event.item.getURI() 397 | uri =event.item.getURI() 398 | neovim.sendMessage(['vim_eval',["expand('%:p')"]], 399 | ((filename) -> 400 | 401 | #filename = VimUtils.buf2str(filename) 402 | console.log 'filename reported by vim:',filename 403 | console.log 'current editor uri:',uri 404 | ncefn = VimUtils.normalize_filename(uri) 405 | nfn = VimUtils.normalize_filename(filename) 406 | 407 | if ncefn and nfn and nfn isnt ncefn 408 | console.log '-------------------------------',nfn 409 | console.log '*******************************',ncefn 410 | 411 | neovim.sendMessage(['vim_command',['e! '+ncefn]], 412 | (() -> 413 | neovim.sendMessage(['vim_command',['bd!']]) 414 | ) 415 | ) 416 | 417 | else 418 | neovim.sendMessage(['vim_command',['bd!']]) 419 | ) 420 | 421 | ) 422 | console.log 'destroyed pane' 423 | 424 | activePaneChanged = () -> 425 | if active_change 426 | cnt = 0 427 | while ( VimGlobals.updating or VimGlobals.internal_change) 428 | console.log 'waiting for conditions' 429 | cnt = cnt + 1 430 | if cnt > 50 431 | return 432 | 433 | VimGlobals.tlnumber = -9999 434 | VimGlobals.updating = true 435 | VimGlobals.internal_change = true 436 | 437 | try 438 | VimGlobals.current_editor = atom.workspace.getActiveTextEditor() 439 | if VimGlobals.current_editor 440 | filename = atom.workspace.getActiveTextEditor().getURI() 441 | filename2 = filename.split('/') 442 | if filename2[filename2.length-1] of non_file_assoc_atom_to_nvim 443 | cmd = 'b '+ non_file_assoc_atom_to_nvim[filename2[filename2.length-1]] 444 | for key, value of non_file_assoc_atom_to_nvim 445 | console.log 'key:',key, 'value:',value 446 | console.log 'CMD: ', cmd 447 | else 448 | if filename 449 | cmd = 'e! '+ filename 450 | else 451 | cmd = 'e! newfile'+next_new_file_id 452 | next_new_file_id = next_new_file_id + 1 453 | 454 | neovim.sendMessage(['vim_command',[cmd]],(x) -> 455 | 456 | if scrolltopchange_subscription 457 | scrolltopchange_subscription.dispose() 458 | if cursorpositionchange_subscription 459 | cursorpositionchange_subscription.dispose() 460 | 461 | VimGlobals.current_editor = atom.workspace.getActiveTextEditor() 462 | if VimGlobals.current_editor 463 | scrolltopchange_subscription = 464 | VimGlobals.current_editor.element.onDidChangeScrollTop \ 465 | scrollTopChanged 466 | 467 | cursorpositionchange_subscription = 468 | VimGlobals.current_editor.onDidChangeCursorPosition \ 469 | cursorPosChanged 470 | 471 | if bufferchange_subscription 472 | bufferchange_subscription.dispose() 473 | 474 | if bufferchangeend_subscription 475 | bufferchangeend_subscription.dispose() 476 | 477 | register_change_handler() 478 | 479 | scrolltop = undefined 480 | 481 | editor_views[VimGlobals.current_editor.getURI()].\ 482 | vimState.afterOpen() 483 | ) 484 | catch err 485 | 486 | console.log err 487 | console.log 'problem changing panes' 488 | 489 | VimGlobals.internal_change = false 490 | VimGlobals.updating = false 491 | 492 | class EventHandler 493 | constructor: (@vimState) -> 494 | qtop = VimGlobals.current_editor.element.getScrollTop() 495 | qbottom = VimGlobals.current_editor.element.getScrollBottom() 496 | 497 | @rows = Math.floor((qbottom - qtop)/lineSpacing()+1) 498 | 499 | nrows = @rows 500 | #console.log 'rows:', @rows 501 | 502 | height = Math.floor(30+(@rows) * lineSpacing()) 503 | 504 | atom.setWindowDimensions ('width': 1400, 'height': height) 505 | 506 | qleft = VimGlobals.current_editor.element.getScrollLeft() 507 | qright= VimGlobals.current_editor.element.getScrollRight() 508 | 509 | @cols = Math.floor((qright-qleft)/lineSpacingHorizontal())-1 510 | 511 | COLS = @cols 512 | @rows = Math.floor((qbottom - qtop)/lineSpacing()+1) 513 | screen = ((' ' for ux in [1..@cols]) for uy in [1..@rows+2]) 514 | @command_mode = true 515 | 516 | handleEvent: (event, q) => 517 | if q.length is 0 518 | return 519 | if VimGlobals.updating 520 | return 521 | 522 | VimGlobals.internal_change = true 523 | dirty = (false for i in [0..@rows-1]) 524 | 525 | if event is "redraw" and subscriptions['redraw'] 526 | #console.log "eventInfo", eventInfo 527 | for x in q 528 | if not x 529 | continue 530 | #x[0] = VimUtils.buf2str(x[0]) 531 | if x[0] is "cursor_goto" 532 | for v in x[1..] 533 | try 534 | v[0] = util.inspect(v[0]) 535 | @vimState.location[0] = parseInt(v[0]) 536 | catch 537 | @vimState.location[0] = 0 538 | console.log 'problem in goto' 539 | 540 | try 541 | v[1] = util.inspect(v[1]) 542 | @vimState.location[1] = parseInt(v[1]) 543 | catch 544 | @vimState.location[1] = 0 545 | console.log 'problem in goto' 546 | 547 | else if x[0] is 'set_scroll_region' 548 | @screen_top = parseInt(util.inspect(x[1][0])) 549 | @screen_bot = parseInt(util.inspect(x[1][1])) 550 | @screen_left = parseInt(util.inspect(x[1][2])) 551 | @screen_right = parseInt(util.inspect(x[1][3])) 552 | 553 | else if x[0] is "mode_change" 554 | if x[1][0] is 'insert' 555 | @vimState.activateInsertMode() 556 | @command_mode = false 557 | else if x[1][0] is 'normal' 558 | @vimState.activateCommandMode() 559 | @command_mode = true 560 | else if x[1][0] is 'replace' 561 | @vimState.activateReplaceMode() 562 | @command_mode = true 563 | else 564 | @vimState.activateCommandMode() 565 | @command_mode = true 566 | 567 | else if x[0] is "bell" 568 | atom.beep() 569 | 570 | else if x[0] is "cursor_on" 571 | if @command_mode 572 | @vimState.activateCommandMode() 573 | else 574 | @vimState.activateInsertMode() 575 | @vimState.cursor_visible = true 576 | 577 | else if x[0] is "cursor_off" 578 | @vimState.activateInvisibleMode() 579 | @vimState.cursor_visible = false 580 | 581 | else if x[0] is "scroll" 582 | for v in x[1..] 583 | try 584 | top = @screen_top 585 | bot = @screen_bot + 1 586 | 587 | left = @screen_left 588 | right = @screen_right + 1 589 | 590 | if not v 591 | console.log 'not v' 592 | count = parseInt(util.inspect(v[0])) 593 | #console.log 'scrolling:',count 594 | #tlnumber = tlnumber + count 595 | if count > 0 596 | src_top = top+count 597 | src_bot = bot 598 | dst_top = top 599 | dst_bot = bot - count 600 | clr_top = dst_bot 601 | clr_bot = src_bot 602 | 603 | else 604 | src_top = top 605 | src_bot = bot + count 606 | dst_top = top - count 607 | dst_bot = bot 608 | clr_top = src_top 609 | clr_bot = dst_top 610 | 611 | #for posi in range(clr_top,clr_bot) 612 | #for posj in range(left,right) 613 | #screen[posi][posj] = ' ' 614 | 615 | top = @screen_top 616 | bottom = @screen_bot 617 | left = @screen_left 618 | right = @screen_right 619 | #console.log 'left:',left 620 | if count > 0 621 | start = top 622 | stop = bottom - count + 1 623 | step = 1 624 | else 625 | start = bottom 626 | stop = top - count - 1 627 | step = -1 628 | 629 | 630 | if count > 0 631 | for row in VimUtils.range(start,stop,step) 632 | dirty[row] = true 633 | target_row = screen[row] 634 | source_row = screen[row + count] 635 | for col in VimUtils.range(left,right+1) 636 | target_row[col] = source_row[col] 637 | 638 | for row in VimUtils.range(stop, stop+count,step) 639 | for col in VimUtils.range(left,right+1) 640 | if screen[row] 641 | screen[row][col] = ' ' 642 | dirty[row] = true 643 | else 644 | for row in VimUtils.range(start,stop,step) 645 | dirty[row] = true 646 | target_row = screen[row] 647 | 648 | source_row = screen[row + count] 649 | for col in VimUtils.range(left,right+1) 650 | target_row[col] = source_row[col] 651 | 652 | for row in VimUtils.range(stop, stop+count-1,step) 653 | for col in VimUtils.range(left,right+1) 654 | if screen[row] 655 | screen[row][col] = ' ' 656 | dirty[row] = true 657 | 658 | scrolled = true 659 | if count > 0 660 | @vimState.scrolled_down = true 661 | else 662 | @vimState.scrolled_down = false 663 | catch error 664 | 665 | 666 | console.log 'problem scrolling:',error 667 | console.log 'stack:',error.stack 668 | 669 | else if x[0] is "put" 670 | for v in x[1..] 671 | try 672 | ly = @vimState.location[0] 673 | lx = @vimState.location[1] 674 | if 0<=ly and ly < @rows-1 675 | if v 676 | qq = v[0] 677 | if qq and qq[0] 678 | screen[ly][lx] = qq[0] 679 | @vimState.location[1] = lx + 1 680 | dirty[ly] = true 681 | else if ly == @rows - 1 682 | if v 683 | qq = v[0] 684 | if qq 685 | @vimState.status_bar[lx] = qq[0] 686 | @vimState.location[1] = lx + 1 687 | else if ly > @rows - 1 688 | console.log 'over the max' 689 | catch err 690 | console.log 'problem putting',err 691 | 692 | else if x[0] is "clear" 693 | #console.log 'clear' 694 | for posj in [0..@cols-1] 695 | for posi in [0..@rows-2] 696 | if screen and screen[posi] 697 | screen[posi][posj] = ' ' 698 | dirty[posi] = true 699 | 700 | @vimState.status_bar[posj] = ' ' 701 | 702 | else if x[0] is "eol_clear" 703 | ly = @vimState.location[0] 704 | lx = @vimState.location[1] 705 | if ly < @rows - 1 706 | for posj in [lx..@cols-1] 707 | for posi in [ly..ly] 708 | if screen and screen[posi] 709 | if posj >= 0 710 | dirty[posi] = true 711 | screen[posi][posj] = ' ' 712 | 713 | else if ly == @rows - 1 714 | for posj in [lx..@cols-1] 715 | @vimState.status_bar[posj] = ' ' 716 | else if ly > @rows - 1 717 | console.log 'over the max' 718 | 719 | @vimState.redraw_screen(@rows, dirty) 720 | 721 | options = { normalizeLineEndings: false, undo: 'skip' } 722 | if VimGlobals.current_editor 723 | VimGlobals.current_editor.buffer.setTextInRange(new Range( 724 | new Point(VimGlobals.current_editor.buffer.getLastRow(),0), 725 | new Point(VimGlobals.current_editor.buffer.getLastRow(),COLS-8)),'', 726 | options) 727 | 728 | VimGlobals.internal_change = false 729 | 730 | 731 | module.exports = 732 | class VimState 733 | editor: null 734 | 735 | constructor: (@editorView) -> 736 | @editor = @editorView.getModel() 737 | editor_views[@editor.getURI()] = @editorView 738 | @editorView.component.setInputEnabled(false) 739 | mode = 'command' 740 | @cursor_visible = true 741 | @scrolled_down = false 742 | VimGlobals.tlnumber = 0 743 | @status_bar = [] 744 | @location = [] 745 | 746 | 747 | if not VimGlobals.current_editor 748 | VimGlobals.current_editor = @editor 749 | @changeModeClass('command-mode') 750 | @activateCommandMode() 751 | 752 | atom.packages.onDidActivatePackage( -> 753 | element.innerHTML = '' 754 | @statusbar = 755 | document.querySelector('status-bar').addLeftTile(item:element, 756 | priority:10 ) 757 | ) 758 | 759 | if not buffer_change_subscription 760 | buffer_change_subscription = 761 | atom.workspace.onDidChangeActivePaneItem activePaneChanged 762 | if not buffer_destroy_subscription 763 | buffer_destroy_subscription = 764 | atom.workspace.onDidDestroyPaneItem destroyPaneItem 765 | 766 | atom.commands.add 'atom-text-editor', 'core:save', (e) -> 767 | VimGlobals.internal_change = true 768 | VimGlobals.updating = true 769 | e.preventDefault() 770 | e.stopPropagation() 771 | vim_mode_save_file() 772 | 773 | 774 | @editorView.onkeypress = (e) => 775 | deactivate_timer() 776 | q1 = @editorView.classList.contains('is-focused') 777 | q2 = @editorView.classList.contains('autocomplete-active') 778 | q3 = VimGlobals.current_editor.getSelectedBufferRange().isEmpty() 779 | if q1 and not q2 and q3 780 | @editorView.component.setInputEnabled(false) 781 | q = String.fromCharCode(e.which) 782 | neovim.sendMessage(['vim_input',[q]]) 783 | activate_timer() 784 | false 785 | else if q1 and not q2 and not q3 786 | @editorView.component.setInputEnabled(true) 787 | activate_timer() 788 | true 789 | else 790 | VimGlobals.internal_change = false 791 | VimGlobals.updating = false 792 | q = String.fromCharCode(e.which) 793 | neovim.sendMessage(['vim_input',[q]]) 794 | activate_timer() 795 | true 796 | 797 | @editorView.onkeydown = (e) => 798 | deactivate_timer() 799 | q1 = @editorView.classList.contains('is-focused') 800 | q2 = @editorView.classList.contains('autocomplete-active') 801 | q3 = VimGlobals.current_editor.getSelectedBufferRange().isEmpty() 802 | if q1 and not q2 and not e.altKey and q3 803 | @editorView.component.setInputEnabled(false) 804 | translation = @translateCode(e.which, e.shiftKey, e.ctrlKey) 805 | if translation != "" 806 | neovim.sendMessage(['vim_input',[translation]]) 807 | activate_timer() 808 | false 809 | else if q1 and not q2 and not q3 810 | @editorView.component.setInputEnabled(true) 811 | activate_timer() 812 | true 813 | else 814 | VimGlobals.internal_change = false 815 | VimGlobals.updating = false 816 | activate_timer() 817 | true 818 | 819 | 820 | 821 | translateCode: (code, shift, control) -> 822 | #console.log 'code:',code 823 | if control && code>=65 && code<=90 824 | String.fromCharCode(code-64) 825 | else if code>=8 && code<=10 || code==13 || code==27 826 | String.fromCharCode(code) 827 | else if code==35 828 | '' 829 | else if code==36 830 | '' 831 | else if code==33 832 | '' 833 | else if code==34 834 | '' 835 | else if code==37 836 | '' 837 | else if code==38 838 | '' 839 | else if code==39 840 | '' 841 | else if code==40 842 | '' 843 | else if code==188 and shift 844 | '' 845 | else 846 | "" 847 | 848 | destroy_sockets:(editor) => 849 | if subscriptions['redraw'] 850 | if editor.getURI() != @editor.getURI() 851 | #subscriptions['redraw'] = false 852 | console.log 'unsubscribing' 853 | 854 | afterOpen: => 855 | #console.log 'in after open' 856 | neovim.sendMessage(['vim_command',['set scrolloff=2']]) 857 | neovim.sendMessage(['vim_command',['set nocompatible']]) 858 | neovim.sendMessage(['vim_command',['set noswapfile']]) 859 | neovim.sendMessage(['vim_command',['set nowrap']]) 860 | neovim.sendMessage(['vim_command',['set numberwidth=8']]) 861 | neovim.sendMessage(['vim_command',['set nu']]) 862 | neovim.sendMessage(['vim_command',['set autochdir']]) 863 | neovim.sendMessage(['vim_command',['set autoindent']]) 864 | neovim.sendMessage(['vim_command',['set smartindent']]) 865 | neovim.sendMessage(['vim_command',['set hlsearch']]) 866 | neovim.sendMessage(['vim_command',['set tabstop=4']]) 867 | neovim.sendMessage(['vim_command',['set encoding=utf-8']]) 868 | neovim.sendMessage(['vim_command',['set shiftwidth=4']]) 869 | neovim.sendMessage(['vim_command',['set shortmess+=I']]) 870 | neovim.sendMessage(['vim_command',['set expandtab']]) 871 | neovim.sendMessage(['vim_command',['set hidden']]) 872 | neovim.sendMessage(['vim_command',['set listchars=eol:$']]) 873 | neovim.sendMessage(['vim_command',['set list']]) 874 | neovim.sendMessage(['vim_command',['set wildmenu']]) 875 | neovim.sendMessage(['vim_command',['set showcmd']]) 876 | neovim.sendMessage(['vim_command',['set incsearch']]) 877 | neovim.sendMessage(['vim_command',['set autoread']]) 878 | neovim.sendMessage(['vim_command',['set laststatus=2']]) 879 | neovim.sendMessage(['vim_command',['set rulerformat=%L']]) 880 | neovim.sendMessage(['vim_command',['set ruler']]) 881 | #neovim.sendMessage(['vim_command',['set visualbell']]) 882 | 883 | 884 | neovim.sendMessage(['vim_command', 885 | ['set backspace=indent,eol,start']]) 886 | 887 | neovim.sendMessage(['vim_input',['']]) 888 | @activateCommandMode() 889 | 890 | if not subscriptions['redraw'] 891 | #console.log 'subscribing, after open' 892 | @neovim_subscribe() 893 | #else 894 | #console.log 'NOT SUBSCRIBING, problem' 895 | # 896 | 897 | #last_text = VimGlobals.current_editor.getText() 898 | 899 | postprocess: (rows, dirty) -> 900 | screen_f = [] 901 | for posi in [0..rows-1] 902 | line = undefined 903 | if screen[posi] and dirty[posi] 904 | line = [] 905 | for posj in [0..COLS-8] 906 | if screen[posi][posj]=='$' and screen[posi][posj+1]==' ' and \ 907 | screen[posi][posj+2]==' ' 908 | break 909 | line.push screen[posi][posj] 910 | else 911 | if screen[posi] 912 | line = screen[posi] 913 | screen_f.push line 914 | 915 | redraw_screen:(rows, dirty) => 916 | 917 | VimGlobals.current_editor = atom.workspace.getActiveTextEditor() 918 | if VimGlobals.current_editor 919 | 920 | if DEBUG 921 | initial = 0 922 | else 923 | initial = 8 924 | 925 | sbr = VimGlobals.current_editor.getSelectedBufferRange() 926 | @postprocess(rows, dirty) 927 | tlnumberarr = [] 928 | for posi in [0..rows-3] 929 | try 930 | pos = parseInt(screen_f[posi][0..8].join('')) 931 | #if not isNaN(pos) 932 | tlnumberarr.push ( (pos - 1) - posi ) 933 | #else 934 | # tlnumberarr.push -1 935 | catch err 936 | tlnumberarr.push -9999 937 | 938 | VimGlobals.tlnumber = NaN 939 | array = [] 940 | for i in [0..rows-3] 941 | if not isNaN(tlnumberarr[i]) and tlnumberarr[i] >= 0 942 | array.push(tlnumberarr[i]) 943 | #console.log array 944 | 945 | VimGlobals.tlnumber = getMaxOccurrence(array) 946 | #console.log 'TLNUMBERarr********************',tlnumberarr 947 | #console.log 'TLNUMBER********************',VimGlobals.tlnumber 948 | 949 | if dirty 950 | 951 | options = { normalizeLineEndings: false, undo: 'skip' } 952 | for posi in [0..rows-3] 953 | if not isNaN(VimGlobals.tlnumber) and (VimGlobals.tlnumber isnt -9999) 954 | if (tlnumberarr[posi] + posi == VimGlobals.tlnumber + posi) and \ 955 | dirty[posi] 956 | qq = screen_f[posi] 957 | qq = qq[initial..].join('') 958 | linerange = new Range(new Point(VimGlobals.tlnumber+posi,0), 959 | new Point(VimGlobals.tlnumber + posi, 960 | COLS-initial)) 961 | 962 | txt = VimGlobals.current_editor.buffer.getTextInRange(linerange) 963 | if qq isnt txt 964 | console.log 'qq:',qq 965 | console.log 'txt:',txt 966 | VimGlobals.current_editor.buffer.setTextInRange(linerange, 967 | qq, options) 968 | dirty[posi] = false 969 | 970 | sbt = @status_bar.join('') 971 | @updateStatusBarWithText(sbt, (rows - 1 == @location[0]), @location[1]) 972 | 973 | q = screen[rows-2] 974 | text = q[q.length/2..q.length-1].join('') 975 | text = text.split(' ').join('') 976 | num_lines = parseInt(text, 10) 977 | 978 | if VimGlobals.current_editor.buffer.getLastRow() < num_lines 979 | nl = num_lines - VimGlobals.current_editor.buffer.getLastRow() 980 | diff = '' 981 | for i in [0..nl-2] 982 | diff = diff + '\n' 983 | append_options = {normalizeLineEndings: false} 984 | VimGlobals.current_editor.buffer.append(diff, append_options) 985 | 986 | else if VimGlobals.current_editor.buffer.getLastRow() > num_lines 987 | for i in [num_lines..\ 988 | VimGlobals.current_editor.buffer.getLastRow()-1] 989 | VimGlobals.current_editor.buffer.deleteRow(i) 990 | 991 | 992 | if not isNaN(VimGlobals.tlnumber) and (VimGlobals.tlnumber isnt -9999) 993 | 994 | if @cursor_visible and @location[0] <= rows - 2 995 | if not DEBUG 996 | VimGlobals.current_editor.setCursorBufferPosition( 997 | new Point(VimGlobals.tlnumber + @location[0], 998 | @location[1]-initial),{autoscroll:false}) 999 | else 1000 | VimGlobals.current_editor.setCursorBufferPosition( 1001 | new Point(VimGlobals.tlnumber + @location[0], 1002 | @location[1]),{autoscroll:false}) 1003 | 1004 | if VimGlobals.current_editor 1005 | VimGlobals.current_editor.element.setScrollTop(lineSpacing()*\ 1006 | VimGlobals.tlnumber) 1007 | 1008 | #console.log 'sbr:',sbr 1009 | if not sbr.isEmpty() 1010 | VimGlobals.current_editor.setSelectedBufferRange(sbr, 1011 | {reversed:reversed_selection}) 1012 | 1013 | neovim_unsubscribe: -> 1014 | message = ['ui_detach',[]] 1015 | neovim.sendMessage(message) 1016 | subscriptions['redraw'] = false 1017 | 1018 | neovim_resize:(cols, rows) => 1019 | 1020 | VimGlobals.internal_change = true 1021 | VimGlobals.updating = true 1022 | qtop = 10 1023 | qbottom =0 1024 | @rows = 0 1025 | 1026 | qtop = VimGlobals.current_editor.element.getScrollTop() 1027 | qbottom = VimGlobals.current_editor.element.getScrollBottom() 1028 | 1029 | qleft = VimGlobals.current_editor.element.getScrollLeft() 1030 | qright= VimGlobals.current_editor.element.getScrollRight() 1031 | 1032 | @cols = Math.floor((qright-qleft)/lineSpacingHorizontal())-1 1033 | 1034 | COLS = @cols 1035 | @rows = Math.floor((qbottom - qtop)/lineSpacing()+1) 1036 | 1037 | eventHandler.cols = @cols 1038 | eventHandler.rows= @rows+2 1039 | message = ['ui_try_resize',[@cols,@rows+2]] 1040 | neovim.sendMessage(message) 1041 | 1042 | screen = ((' ' for ux in [1..@cols]) for uy in [1..@rows+2]) 1043 | @location = [0,0] 1044 | neovim.sendMessage(['vim_command',['redraw!']], 1045 | (() -> 1046 | VimGlobals.internal_change = false 1047 | ) 1048 | ) 1049 | VimGlobals.internal_change = false 1050 | VimGlobals.updating = false 1051 | 1052 | 1053 | neovim_subscribe: => 1054 | #console.log 'neovim_subscribe' 1055 | 1056 | eventHandler = new EventHandler this 1057 | 1058 | message = ['ui_attach',[eventHandler.cols,eventHandler.rows,true]] 1059 | neovim.sendMessage(message) 1060 | 1061 | 1062 | neovim.addNotificationListener(eventHandler.handleEvent) 1063 | #rows = @editor.getScreenLineCount() 1064 | @location = [0,0] 1065 | @status_bar = (' ' for ux in [1..eventHandler.cols]) 1066 | 1067 | subscriptions['redraw'] = true 1068 | 1069 | #Used to enable command mode. 1070 | activateCommandMode: -> 1071 | mode = 'command' 1072 | @changeModeClass('command-mode') 1073 | @updateStatusBar() 1074 | 1075 | #Used to enable insert mode. 1076 | activateInsertMode: (transactionStarted = false)-> 1077 | mode = 'insert' 1078 | @changeModeClass('insert-mode') 1079 | @updateStatusBar() 1080 | 1081 | activateReplaceMode: ()-> 1082 | mode = 'replace' 1083 | @changeModeClass('command-mode') 1084 | 1085 | activateInvisibleMode: (transactionStarted = false)-> 1086 | mode = 'insert' 1087 | @changeModeClass('invisible-mode') 1088 | @updateStatusBar() 1089 | 1090 | changeModeClass: (targetMode) -> 1091 | if VimGlobals.current_editor 1092 | editorview = editor_views[VimGlobals.current_editor.getURI()] 1093 | if editorview 1094 | for qmode in ['command-mode', 'insert-mode', 'visual-mode',\ 1095 | 'operator-pending-mode', 'invisible-mode'] 1096 | if qmode is targetMode 1097 | editorview.classList.add(qmode) 1098 | else 1099 | editorview.classList.remove(qmode) 1100 | 1101 | updateStatusBarWithText:(text, addcursor, loc) -> 1102 | if addcursor 1103 | text = text[0..loc-1].concat('■').concat(text[loc+1..]) 1104 | text = text.split(' ').join(' ') 1105 | q = '' 1106 | qend = '' 1107 | element.innerHTML = q.concat(text).concat(qend) 1108 | 1109 | updateStatusBar: -> 1110 | element.innerHTML = mode 1111 | 1112 | -------------------------------------------------------------------------------- /lib/vim-sync.coffee: -------------------------------------------------------------------------------- 1 | 2 | util = require 'util' 3 | VimGlobals = require './vim-globals' 4 | VimUtils = require './vim-utils' 5 | 6 | neovim_send_message = (message,f = undefined) -> 7 | try 8 | if message[0] and message[1] 9 | VimGlobals.session.request(message[0], message[1], (err, res) -> 10 | if f 11 | if typeof(res) is 'number' 12 | f(util.inspect(res)) 13 | else 14 | f(res) 15 | ) 16 | catch err 17 | console.log 'error in neovim_send_message '+err 18 | console.log 'm1:',message[0] 19 | console.log 'm2:',message[1] 20 | 21 | #This function changes the text between start and end changing the number 22 | #of lines by delta. The change occurs directionaly from Atom -> Neovim. 23 | #There is a bunch of bookkeeping to make sure the change is unidirectional. 24 | 25 | neovim_set_text = (text, start, end, delta) -> 26 | lines_tmp = text.split('\n') 27 | lines = [] 28 | for item in lines_tmp 29 | lines.push item.split('\r').join('') 30 | 31 | lines = lines[0..lines.length-1] 32 | cpos = VimGlobals.current_editor.getCursorBufferPosition() 33 | 34 | neovim_send_message(['vim_get_current_buffer',[]], 35 | ((buf) -> 36 | #console.log 'buff',buf 37 | neovim_send_message(['buffer_line_count',[buf]], 38 | ((vim_cnt) -> 39 | 40 | neovim_send_message(['buffer_get_line_slice', [buf, 0, 41 | parseInt(vim_cnt), 42 | true, 43 | false]], 44 | ((vim_lines_r) -> 45 | vim_lines = [] 46 | for item in vim_lines_r 47 | vim_lines.push item 48 | l = [] 49 | pos = 0 50 | for pos in [0..vim_lines.length + delta-1] 51 | item = vim_lines[pos] 52 | if pos < start 53 | l.push(item) 54 | 55 | if pos >= start and pos <= end + delta 56 | l.push(lines[pos]) 57 | 58 | if pos > end + delta 59 | l.push(vim_lines[pos-delta]) 60 | 61 | 62 | send_data(buf,l,-delta, cpos.row+1, cpos.column+1) 63 | 64 | ) 65 | ) 66 | ) 67 | ) 68 | ) 69 | ) 70 | 71 | #This function sends the data and updates the the cursor location. It then 72 | #calls a function to update the state to the syncing from Atom -> Neovim 73 | #stops and the Neovim -> Atom change resumes. 74 | 75 | send_data = (buf, l, i, r, c) -> 76 | lines = [] 77 | l2 = [] 78 | for item in l 79 | if item 80 | item2 = item.split('\\').join('\\\\') 81 | if item2 82 | item2 = item2.split('"').join('\\"') 83 | if item2 84 | l2.push '"'+item2+'"' 85 | else 86 | l2.push '""' 87 | else 88 | l2.push '""' 89 | else 90 | l2.push '""' 91 | 92 | #lines.push('undojoin') 93 | lines.push('cal setline(1, ['+l2.join()+'])') 94 | #lines.push('undojoin') 95 | 96 | if i > 0 97 | j = l.length + i 98 | while j > l.length 99 | lines.push(''+(j)+'d') 100 | #lines.push('undojoin') 101 | j = j - 1 102 | 103 | lines.push('cal cursor('+r+','+c+')') 104 | console.log 'lines2',lines 105 | 106 | VimGlobals.internal_change = true 107 | VimGlobals.updating = true 108 | neovim_send_message(['vim_command', [lines.join(' | ')]], 109 | update_state) 110 | 111 | #This function redraws everything and updates the state to re-enable 112 | #Neovim -> Atom syncing. 113 | 114 | update_state = () -> 115 | VimGlobals.updating = false 116 | VimGlobals.internal_change = false 117 | 118 | 119 | module.exports = 120 | 121 | #This function performs the "real update" from Atom -> Neovim. In case 122 | #of Cmd-X, Cmd-V, etc. 123 | 124 | real_update : () -> 125 | if not VimGlobals.updating 126 | VimGlobals.updating = true 127 | 128 | curr_updates = VimGlobals.lupdates.slice(0) 129 | 130 | VimGlobals.lupdates = [] 131 | if curr_updates.length > 0 132 | 133 | for item in curr_updates 134 | #console.log 'item:',item 135 | if item.uri is atom.workspace.getActiveTextEditor().getURI() 136 | neovim_set_text(item.text, item.start, item.end, item.delta) 137 | 138 | 139 | -------------------------------------------------------------------------------- /lib/vim-utils.coffee: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = 4 | 5 | normalize_filename: (filename) -> 6 | if filename 7 | filename = filename.split('\\').join('/') 8 | return filename 9 | 10 | range: (start, stop, step) -> 11 | if typeof stop is "undefined" 12 | # one param defined 13 | stop = start 14 | start = 0 15 | step = 1 if typeof step is "undefined" 16 | return [] if (step > 0 and start >= stop) or (step < 0 and start <= stop) 17 | result = [] 18 | i = start 19 | 20 | while (if step > 0 then i < stop else i > stop) 21 | result.push i 22 | i += step 23 | result 24 | 25 | 26 | buf2str: (buffer) -> 27 | if not buffer 28 | return '' 29 | res = '' 30 | i = 0 31 | while i < buffer.length 32 | res = res + String.fromCharCode(buffer[i]) 33 | i++ 34 | res 35 | 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atom-neovim", 3 | "main": "./lib/vim-mode", 4 | "version": "0.10.1", 5 | "description": "Make Atom your GUI to Neovim.", 6 | "license": "MIT", 7 | "private": true, 8 | "repository": "https://github.com/carlosdcastillo/vim-mode", 9 | "engines": { 10 | "atom": ">0.174.0" 11 | }, 12 | "dependencies": { 13 | "underscore-plus": "1.x", 14 | "event-kit": "^0.7.2", 15 | "atom-space-pen-views":"^2.0.3", 16 | "jquery":"^3", 17 | "msgpack5rpc":"1.1.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /styles/vim-mode.less: -------------------------------------------------------------------------------- 1 | @import "syntax-variables"; 2 | @import "ui-variables"; 3 | 4 | .command-mode-input atom-text-editor[mini] { 5 | background-color: inherit; 6 | border: none; 7 | width: 100%; 8 | font-weight: normal; 9 | color: @text-color; 10 | line-height: 1.28; 11 | cursor: default; 12 | white-space: nowrap; 13 | padding-left: 10px; 14 | } 15 | 16 | .block-cursor(@visibility: visible) { 17 | border: 0; 18 | background-color: @syntax-cursor-color; 19 | visibility: @visibility; 20 | opacity: 0.5; 21 | } 22 | 23 | atom-text-editor.vim-mode.command-mode, 24 | atom-text-editor.vim-mode.operator-pending-mode, 25 | atom-text-editor.vim-mode.visual-mode { 26 | .cursor, .cursor.blink-off { 27 | .block-cursor(hidden); 28 | } 29 | } 30 | 31 | atom-text-editor.vim-mode.command-mode.is-focused, 32 | atom-text-editor.vim-mode.operator-pending-mode.is-focused, 33 | atom-text-editor.vim-mode.visual-mode.is-focused { 34 | .cursor, .cursor.blink-off { 35 | .block-cursor; 36 | } 37 | } 38 | 39 | atom-text-editor.vim-mode.visual-mode { 40 | .cursor.hidden-cursor { 41 | display: block; 42 | } 43 | } 44 | --------------------------------------------------------------------------------