├── .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 |
--------------------------------------------------------------------------------