forked from FredKSchott/CoVim
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCoVimClient.vim
More file actions
376 lines (332 loc) · 15.7 KB
/
Copy pathCoVimClient.vim
File metadata and controls
376 lines (332 loc) · 15.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
"Check for Python Support"
if !has('python')
com! -nargs=* CoVim echoerr "Error: CoVim requires vim compiled with +python"
finish
endif
com! -nargs=* CoVim py CoVim.command(<f-args>)
"Needs to be set on connect, MacVim overrides otherwise"
function! SetCoVimColors ()
hi CursorUser gui=bold term=bold cterm=bold
hi Cursor1 ctermbg=DarkRed ctermfg=White guibg=DarkRed guifg=White gui=bold term=bold cterm=bold
hi Cursor2 ctermbg=DarkBlue ctermfg=White guibg=DarkBlue guifg=White gui=bold term=bold cterm=bold
hi Cursor3 ctermbg=DarkGreen ctermfg=White guibg=DarkGreen guifg=White gui=bold term=bold cterm=bold
hi Cursor4 ctermbg=DarkCyan ctermfg=White guibg=DarkCyan guifg=White gui=bold term=bold cterm=bold
hi Cursor5 ctermbg=DarkMagenta ctermfg=White guibg=DarkMagenta guifg=White gui=bold term=bold cterm=bold
hi Cursor6 ctermbg=Brown ctermfg=White guibg=Brown guifg=White gui=bold term=bold cterm=bold
hi Cursor7 ctermbg=LightRed ctermfg=Black guibg=LightRed guifg=Black gui=bold term=bold cterm=bold
hi Cursor8 ctermbg=LightBlue ctermfg=Black guibg=LightBlue guifg=Black gui=bold term=bold cterm=bold
hi Cursor9 ctermbg=LightGreen ctermfg=Black guibg=LightGreen guifg=Black gui=bold term=bold cterm=bold
hi Cursor10 ctermbg=LightCyan ctermfg=Black guibg=LightCyan guifg=Black gui=bold term=bold cterm=bold
hi Cursor0 ctermbg=LightYellow ctermfg=Black guibg=LightYellow guifg=Black gui=bold term=bold cterm=bold
endfunction
if !exists("CoVim_default_name")
let CoVim_default_name = 0
endif
if !exists("CoVim_default_port")
let CoVim_default_port = 0
endif
python << EOF
import vim
import os
import json
import warnings
from twisted.internet.protocol import ClientFactory, Protocol
from twisted.internet import reactor
from threading import Thread
from time import sleep
# Ignore Warnings
warnings.filterwarnings('ignore', '.*', UserWarning)
warnings.filterwarnings('ignore', '.*', DeprecationWarning)
# Find the server path
CoVimServerPath = vim.eval('expand("<sfile>:h")') + '/CoVimServer.py'
## CoVim Protocol
class CoVimProtocol(Protocol):
def __init__(self, fact):
self.fact = fact
def send(self, event):
self.transport.write(event)
def connectionMade(self):
self.send(CoVim.username)
def dataReceived(self, data_string):
def to_utf8(d):
if isinstance(d, dict):
# no dict comprehension in python2.5/2.6
d2 = {}
for key, value in d.iteritems():
d2[to_utf8(key)] = to_utf8(value)
return d2
elif isinstance(d, list):
return map(to_utf8, d)
elif isinstance(d, unicode):
return d.encode('utf-8')
else:
return d
def clean_data_string(d_s):
bad_data = d_s.find("}{")
if bad_data > -1:
d_s = d_s[:bad_data+1]
return d_s
data_string = clean_data_string(data_string)
packet = to_utf8(json.loads(data_string))
if 'packet_type' in packet.keys():
data = packet['data']
if packet['packet_type'] == 'message':
if data['message_type'] == 'error_newname_taken':
CoVim.disconnect()
print 'ERROR: Name already in use. Please try a different name'
if data['message_type'] == 'error_newname_invalid':
CoVim.disconnect()
print 'ERROR: Name contains illegal characters. Only numbers, letters, underscores, and dashes allowed. Please try a different name'
if data['message_type'] == 'connect_success':
CoVim.setupWorkspace()
if 'buffer' in data.keys():
CoVim.vim_buffer = data['buffer']
vim.current.buffer[:] = CoVim.vim_buffer
CoVim.addUsers(data['collaborators'])
print 'Success! You\'re now connected [Port '+str(CoVim.port)+']'
if data['message_type'] == 'user_connected':
CoVim.addUsers([data['user']])
print data['user']['name']+' connected to this document'
if data['message_type'] == 'user_disconnected':
CoVim.remUser(data['name'])
print data['name']+' disconnected from this document'
if packet['packet_type'] == 'update':
if 'buffer' in data.keys() and data['name'] != CoVim.username:
b_data = data['buffer']
CoVim.vim_buffer = vim.current.buffer[:b_data['start']] \
+ b_data['buffer'] \
+ vim.current.buffer[b_data['end']-b_data['change_y']+1:]
vim.current.buffer[:] = CoVim.vim_buffer
if 'updated_cursors' in data.keys():
# We need to update your own cursor as soon as possible, then update other cursors after
for updated_user in data['updated_cursors']:
if CoVim.username == updated_user['name'] and data['name'] != CoVim.username:
vim.current.window.cursor = (updated_user['cursor']['y'], updated_user['cursor']['x'])
for updated_user in data['updated_cursors']:
if CoVim.username != updated_user['name']:
vim.command(':call matchdelete('+str(CoVim.collab_manager.collaborators[updated_user['name']][1]) + ')')
vim.command(':call matchadd(\''+CoVim.collab_manager.collaborators[updated_user['name']][0]+'\', \'\%' + str(updated_user['cursor']['x']) + 'v.\%'+str(updated_user['cursor']['y'])+'l\', 10, ' + str(CoVim.collab_manager.collaborators[updated_user['name']][1]) + ')')
#data['cursor']['x'] = max(1,data['cursor']['x'])
#print str(data['cursor']['x'])+', '+str(data['cursor']['y'])
vim.command(':redraw')
#CoVimFactory - Handles Socket Communication
class CoVimFactory(ClientFactory):
def buildProtocol(self, addr):
self.p = CoVimProtocol(self)
return self.p
def startFactory(self):
self.isConnected = True
def stopFactory(self):
self.isConnected = False
def buff_update(self):
d = {
"packet_type": "update",
"data": {
"cursor": {
"x": max(1, vim.current.window.cursor[1]),
"y": vim.current.window.cursor[0]
},
"name": CoVim.username
}
}
d = self.create_update_packet(d)
data = json.dumps(d)
self.p.send(data)
def cursor_update(self):
d = {
"packet_type": "update",
"data": {
"cursor": {
"x": max(1, vim.current.window.cursor[1]+1),
"y": vim.current.window.cursor[0]
},
"name": CoVim.username
}
}
d = self.create_update_packet(d)
data = json.dumps(d)
self.p.send(data)
def create_update_packet(self, d):
current_buffer = vim.current.buffer[:]
if current_buffer != CoVim.vim_buffer:
cursor_y = vim.current.window.cursor[0] - 1
change_y = len(current_buffer) - len(CoVim.vim_buffer)
change_x = 0
if len(CoVim.vim_buffer) > cursor_y-change_y and cursor_y-change_y >= 0 \
and len(current_buffer) > cursor_y and cursor_y >= 0:
change_x = len(current_buffer[cursor_y]) - len(CoVim.vim_buffer[cursor_y-change_y])
limits = {
'from': max(0, cursor_y-abs(change_y)),
'to': min(len(vim.current.buffer)-1, cursor_y+abs(change_y))
}
d_buffer = {
'start': limits['from'],
'end': limits['to'],
'change_y': change_y,
'change_x': change_x,
'buffer': vim.current.buffer[limits['from']:limits['to']+1],
'buffer_size': len(current_buffer)
}
d['data']['buffer'] = d_buffer
CoVim.vim_buffer = current_buffer
return d
def clientConnectionLost(self, connector, reason):
#THIS IS A HACK
if hasattr(CoVim, 'buddylist'):
CoVim.disconnect()
print 'Lost connection.'
def clientConnectionFailed(self, connector, reason):
CoVim.disconnect()
print 'Connection failed.'
#Manage Collaborators
class CollaboratorManager:
def __init__(self):
self.collab_id_itr = 4
self.reset()
def reset(self):
self.collab_color_itr = 1
self.collaborators = {}
self.buddylist_highlight_ids = []
def addUser(self, user_obj):
if user_obj['name'] == CoVim.username:
self.collaborators[user_obj['name']] = ('CursorUser', 4000)
else:
self.collaborators[user_obj['name']] = ('Cursor' + str(self.collab_color_itr), self.collab_id_itr)
self.collab_id_itr += 1
self.collab_color_itr = (self.collab_id_itr-3) % 11
vim.command(':call matchadd(\''+self.collaborators[user_obj['name']][0]+'\', \'\%' + str(user_obj['cursor']['x']) + 'v.\%'+str(user_obj['cursor']['y'])+'l\', 10, ' + str(self.collaborators[user_obj['name']][1]) + ')')
self.refreshCollabDisplay()
def remUser(self, name):
vim.command('call matchdelete('+str(self.collaborators[name][1]) + ')')
del(self.collaborators[name])
self.refreshCollabDisplay()
def refreshCollabDisplay(self):
buddylist_window_width = int(vim.eval('winwidth(0)'))
CoVim.buddylist[:] = ['']
x_a = 1
line_i = 0
vim.command("1wincmd w")
for match_id in self.buddylist_highlight_ids:
vim.command('call matchdelete('+str(match_id) + ')')
self.buddylist_highlight_ids = []
for name in self.collaborators.keys():
x_b = x_a + len(name)
if x_b > buddylist_window_width:
line_i += 1
x_a = 1
x_b = x_a + len(name)
CoVim.buddylist.append('')
vim.command('resize '+str(line_i+1))
CoVim.buddylist[line_i] += name+' '
self.buddylist_highlight_ids.append(vim.eval('matchadd(\''+self.collaborators[name][0]+'\',\'\%<'+str(x_b)+'v.\%>'+str(x_a)+'v\%'+str(line_i+1)+'l\',10,'+str(self.collaborators[name][1]+2000)+')'))
x_a = x_b + 1
vim.command("wincmd p")
#Manage all of CoVim
class CoVimScope:
def initiate(self, addr, port, name):
#Check if connected. If connected, throw error.
if hasattr(self, 'fact') and self.fact.isConnected:
print 'ERROR: Already connected. Please disconnect first'
return
if not port and hasattr(self, 'port') and self.port:
port = self.port
if not addr and hasattr(self, 'addr') and self.addr:
addr = self.addr
if not addr or not port or not name:
print 'Syntax Error: Use form :Covim connect <server address> <port> <name>'
return
port = int(port)
addr = str(addr)
vim.command('autocmd VimLeave * py CoVim.quit()')
if not hasattr(self, 'connection'):
self.addr = addr
self.port = port
self.username = name
self.vim_buffer = []
self.fact = CoVimFactory()
self.collab_manager = CollaboratorManager()
self.connection = reactor.connectTCP(addr, port, self.fact)
self.reactor_thread = Thread(target=reactor.run, args=(False,))
self.reactor_thread.start()
print 'Connecting...'
elif (hasattr(self, 'port') and port != self.port) or (hasattr(self, 'addr') and addr != self.addr):
print 'ERROR: Different address/port already used. To try another, you need to restart Vim'
else:
self.collab_manager.reset()
self.connection.connect()
print 'Reconnecting...'
def createServer(self, port, name):
vim.command(':silent execute "!'+CoVimServerPath+' '+port+' &>/dev/null &"')
sleep(0.5)
self.initiate('localhost', port, name)
def setupWorkspace(self):
vim.command('call SetCoVimColors()')
vim.command(':autocmd!')
vim.command('autocmd CursorMoved <buffer> py reactor.callFromThread(CoVim.fact.cursor_update)')
vim.command('autocmd CursorMovedI <buffer> py reactor.callFromThread(CoVim.fact.buff_update)')
vim.command('autocmd VimLeave * py CoVim.quit()')
vim.command("1new +setlocal\ stl=%!'CoVim-Collaborators'")
self.buddylist = vim.current.buffer
self.buddylist_window = vim.current.window
vim.command("wincmd j")
def addUsers(self, list):
map(self.collab_manager.addUser, list)
def remUser(self, name):
self.collab_manager.remUser(name)
def refreshCollabDisplay(self):
self.collab_manager.refreshCollabDisplay()
def command(self, arg1=False, arg2=False, arg3=False, arg4=False):
default_name = vim.eval('CoVim_default_name')
default_name_string = " - default: '"+default_name+"'" if default_name != '0' else ""
default_port = vim.eval('CoVim_default_port')
default_port_string = " - default: "+default_port if default_port != '0' else ""
if arg1 == "connect":
if arg2 and arg3 and arg4:
self.initiate(arg2, arg3, arg4)
elif arg2 and arg3 and default_name != '0':
self.initiate(arg2, arg3, default_name)
elif arg2 and default_port != '0' and default_name != '0':
self.initiate(arg2, default_port, default_name)
else:
print "usage :CoVim connect [host address / 'localhost'] [port"+default_port_string+"] [name"+default_name_string+"]"
elif arg1 == "disconnect":
self.disconnect()
elif arg1 == "quit":
self.exit()
elif arg1 == "start":
if arg2 and arg3:
self.createServer(arg2, arg3)
elif arg2 and default_name != '0':
self.createServer(arg2, default_name)
elif default_port != '0' and default_name != '0':
self.createServer(default_port, default_name)
else:
print "usage :CoVim start [port"+default_port_string+"] [name"+default_name_string+"]"
else:
print "usage: CoVim [start] [connect] [disconnect] [quit]"
def exit(self):
if hasattr(self, 'buddylist_window') and hasattr(self, 'connection'):
self.disconnect()
vim.command('q')
else:
print "ERROR: CoVim must be running to use this command"
def disconnect(self):
if hasattr(self, 'buddylist'):
vim.command("1wincmd w")
vim.command("q!")
self.collab_manager.buddylist_highlight_ids = []
for name in self.collab_manager.collaborators.keys():
if name != CoVim.username:
vim.command(':call matchdelete('+str(self.collab_manager.collaborators[name][1]) + ')')
del(self.buddylist)
if hasattr(self, 'buddylist_window'):
del(self.buddylist_window)
if hasattr(self, 'connection'):
reactor.callFromThread(self.connection.disconnect)
print 'Successfully disconnected from document!'
else:
print "ERROR: CoVim must be running to use this command"
def quit(self):
reactor.callFromThread(reactor.stop)
CoVim = CoVimScope()
EOF