1
|
|
|
''' |
2
|
|
|
Implements a UI for neovim using tkinter. |
3
|
|
|
|
4
|
|
|
* The widget has lines updated/deleted so that any |
5
|
|
|
given time it only contains what is being displayed. |
6
|
|
|
|
7
|
|
|
* The widget is filled with spaces |
8
|
|
|
''' |
9
|
|
|
import sys |
10
|
|
|
import math |
11
|
|
|
import time |
12
|
|
|
from distutils.spawn import find_executable |
13
|
|
|
from neovim import attach |
14
|
|
|
|
15
|
|
|
from pytknvim.ui_bridge import UIBridge |
16
|
|
|
from pytknvim.screen import Screen |
17
|
|
|
from pytknvim.util import _stringify_key, _stringify_color |
18
|
|
|
from pytknvim.util import _split_color, _invert_color |
19
|
|
|
from pytknvim.util import debug_echo, delay_call |
20
|
|
|
from pytknvim import tk_util |
21
|
|
|
|
22
|
|
|
try: |
23
|
|
|
import Tkinter as tk |
24
|
|
|
import tkFont as tkfont |
25
|
|
|
import ttk |
26
|
|
|
except ImportError: |
27
|
|
|
import tkinter as tk |
28
|
|
|
import tkinter.font as tkfont |
29
|
|
|
|
30
|
|
|
RESIZE_DELAY = 0.04 |
31
|
|
|
|
32
|
|
|
def parse_tk_state(state): |
33
|
|
|
if state & 0x4: |
34
|
|
|
return 'Ctrl' |
35
|
|
|
elif state & 0x8: |
36
|
|
|
return 'Alt' |
37
|
|
|
elif state & 0x1: |
38
|
|
|
return 'Shift' |
39
|
|
|
|
40
|
|
|
|
41
|
|
|
tk_modifiers = ('Alt_L', 'Alt_R', |
42
|
|
|
'Control_L', 'Control_R', |
43
|
|
|
'Shift_L', 'Shift_R', |
44
|
|
|
'Win_L', 'Win_R') |
45
|
|
|
|
46
|
|
|
|
47
|
|
|
KEY_TABLE = { |
48
|
|
|
'slash': '/', |
49
|
|
|
'backslash': '\\', |
50
|
|
|
'asciicircumf': '^', |
51
|
|
|
'at': '@', |
52
|
|
|
'numbersign': '#', |
53
|
|
|
'dollar': '$', |
54
|
|
|
'percent': '%', |
55
|
|
|
'ampersand': '&', |
56
|
|
|
'asterisk': '*', |
57
|
|
|
'parenleft': '(', |
58
|
|
|
'parenright': ')', |
59
|
|
|
'underscore': '_', |
60
|
|
|
'plus': '+', |
61
|
|
|
'minus': '-', |
62
|
|
|
'bracketleft': '[', |
63
|
|
|
'bracketright': ']', |
64
|
|
|
'braceleft': '{', |
65
|
|
|
'braceright': '}', |
66
|
|
|
'quotedbl': '"', |
67
|
|
|
'apostrophe': "'", |
68
|
|
|
'less': "<", |
69
|
|
|
'greater': ">", |
70
|
|
|
'comma': ",", |
71
|
|
|
'period': ".", |
72
|
|
|
'BackSpace': 'BS', |
73
|
|
|
'Return': 'CR', |
74
|
|
|
'Escape': 'Esc', |
75
|
|
|
'Delete': 'Del', |
76
|
|
|
'Next': 'PageUp', |
77
|
|
|
'Prior': 'PageDown', |
78
|
|
|
'Enter': 'CR', |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
|
82
|
|
|
class MixTk(): |
83
|
|
|
''' |
84
|
|
|
Tkinter actions we bind and use to communicate to neovim |
85
|
|
|
''' |
86
|
|
|
def _tk_key(self,event, **k): |
87
|
|
|
keysym = event.keysym |
88
|
|
|
state = parse_tk_state(event.state) |
89
|
|
|
if event.char not in ('', ' ') \ |
90
|
|
|
and state in (None, 'Shift'): |
91
|
|
|
if event.keysym_num == ord(event.char): |
92
|
|
|
# Send through normal keys |
93
|
|
|
self._bridge.input(event.char) |
94
|
|
|
return |
95
|
|
|
if keysym in tk_modifiers: |
96
|
|
|
# We don't need to track the state of modifier bits |
97
|
|
|
return |
98
|
|
|
if keysym.startswith('KP_'): |
99
|
|
|
keysym = keysym[3:] |
100
|
|
|
|
101
|
|
|
# Translated so vim understands |
102
|
|
|
input_str = _stringify_key( |
103
|
|
|
KEY_TABLE.get(keysym, keysym), state) |
104
|
|
|
self._bridge.input(input_str) |
105
|
|
|
|
106
|
|
|
|
107
|
|
|
def _tk_quit(self, *args): |
108
|
|
|
self._bridge.exit() |
109
|
|
|
|
110
|
|
|
|
111
|
|
|
# @debug_echo |
112
|
|
|
@delay_call(RESIZE_DELAY) |
113
|
|
|
def _tk_resize(self, event): |
114
|
|
|
'''Let Neovim know we are changing size''' |
115
|
|
|
# if not self._screen: |
116
|
|
|
# return |
117
|
|
|
cols = int(math.floor(event.width / self._colsize)) |
118
|
|
|
rows = int(math.floor(event.height / self._rowsize)) |
119
|
|
|
if self._screen.columns == cols: |
120
|
|
|
if self._screen.rows == rows: |
121
|
|
|
return |
122
|
|
|
self.current_cols = cols |
123
|
|
|
self.current_rows = rows |
124
|
|
|
self._bridge.resize(cols, rows) |
125
|
|
|
if self.debug_echo: |
126
|
|
|
print('resizing c, r, w, h', |
127
|
|
|
cols,rows, event.width, event.height) |
128
|
|
|
|
129
|
|
|
|
130
|
|
|
def bind_resize(self): |
131
|
|
|
''' |
132
|
|
|
after calling, |
133
|
|
|
widget changes will now be passed along to neovim |
134
|
|
|
''' |
135
|
|
|
self._configure_id = self.text.bind('<Configure>', |
136
|
|
|
self._tk_resize) |
137
|
|
|
|
138
|
|
|
|
139
|
|
|
def unbind_resize(self): |
140
|
|
|
''' |
141
|
|
|
after calling, |
142
|
|
|
widget size changes will not be passed along to nvim |
143
|
|
|
''' |
144
|
|
|
self.text.unbind('<Configure>', self._configure_id) |
145
|
|
|
|
146
|
|
|
|
147
|
|
|
def _get_row(self, screen_row): |
148
|
|
|
'''change a screen row to a tkinter row, |
149
|
|
|
defaults to screen.row''' |
150
|
|
|
if screen_row is None: |
151
|
|
|
screen_row = self._screen.row |
152
|
|
|
return screen_row + 1 |
153
|
|
|
|
154
|
|
|
|
155
|
|
|
def _get_col(self, screen_col): |
156
|
|
|
'''change a screen col to a tkinter row, |
157
|
|
|
defaults to screen.col''' |
158
|
|
|
if screen_col is None: |
159
|
|
|
screen_col = self._screen.col |
160
|
|
|
return screen_col |
161
|
|
|
|
162
|
|
|
|
163
|
|
|
def tk_delete_line(self, screen_col=None, screen_row=None, |
164
|
|
|
del_eol=False, count=1): |
165
|
|
|
''' |
166
|
|
|
To specifiy where to start the delete from |
167
|
|
|
screen_col (defualts to screen.row) |
168
|
|
|
screen_row (defaults to screen.col) |
169
|
|
|
|
170
|
|
|
To delete the eol char aswell |
171
|
|
|
del_eol (defaults to False) |
172
|
|
|
|
173
|
|
|
count is the number of lines to delete |
174
|
|
|
''' |
175
|
|
|
line = self._get_row(screen_row) |
176
|
|
|
col = self._get_col(screen_col) |
177
|
|
|
start = "%d.%d" % (line, col) |
178
|
|
|
if del_eol: |
179
|
|
|
end = "%d.0" % (line + count) |
180
|
|
|
else: |
181
|
|
|
end = "%d.end" % (line + count - 1) |
182
|
|
|
self.text.delete(start, end) |
183
|
|
|
gotten = self.text.get(start, end) |
184
|
|
|
if self.debug_echo == True: |
185
|
|
|
print('deleted from ' + start + ' to end ' +end) |
186
|
|
|
print('deleted '+repr(gotten)) |
187
|
|
|
|
188
|
|
|
|
189
|
|
|
def tk_pad_line(self, screen_col=None, add_eol=False, |
190
|
|
|
screen_row=None, count=1): |
191
|
|
|
''' |
192
|
|
|
add required blank spaces at the end of the line |
193
|
|
|
can apply action to multiple rows py passing a count |
194
|
|
|
in |
195
|
|
|
''' |
196
|
|
|
line = self._get_row(screen_row) |
197
|
|
|
col = self._get_col(screen_col) |
198
|
|
|
for n in range(0, count): |
199
|
|
|
start = "%d.%d" % (line + n, col) |
200
|
|
|
spaces = " " * (self.current_cols - col) |
201
|
|
|
if add_eol: |
202
|
|
|
spaces += '\n' |
203
|
|
|
if self.debug_echo: |
204
|
|
|
pass |
205
|
|
|
# print('padding from ', start, ' with %d: ' |
206
|
|
|
# % len(spaces)) |
207
|
|
|
# print(repr(spaces)) |
208
|
|
|
self.text.insert(start, spaces) |
209
|
|
|
|
210
|
|
|
|
211
|
|
|
def _start_blinking(self): |
212
|
|
|
# cursor is drawn seperatley in the window |
213
|
|
|
row, col = self._screen.row, self._screen.col |
214
|
|
|
text, attrs = self._screen.get_cursor() |
215
|
|
|
pos = "%d.%d" % (row +1, col) |
216
|
|
|
|
217
|
|
|
if not attrs: |
218
|
|
|
attrs = self._get_tk_attrs(None) |
219
|
|
|
fg = attrs[1].get('foreground') |
220
|
|
|
bg = attrs[1].get('background') |
221
|
|
|
try: |
222
|
|
|
self.text.stop_blink() |
223
|
|
|
except: |
224
|
|
|
pass |
225
|
|
|
self.text.blink_cursor(pos, fg, bg) |
226
|
|
|
|
227
|
|
|
|
228
|
|
|
class MixNvim(): |
229
|
|
|
|
230
|
|
|
|
231
|
|
|
'''These methods get called by neovim''' |
232
|
|
|
|
233
|
|
|
@debug_echo |
234
|
|
|
def _nvim_resize(self, cols, rows): |
235
|
|
|
'''Let neovim update tkinter when neovim changes size''' |
236
|
|
|
# TODO |
237
|
|
|
# Make sure it works when user changes font, |
238
|
|
|
# only can support mono font i think.. |
239
|
|
|
self._screen = Screen(cols, rows) |
240
|
|
|
|
241
|
|
|
toplevel = self.text.master |
242
|
|
|
border = toplevel.winfo_rootx() - toplevel.winfo_x() |
243
|
|
|
titlebar = toplevel.winfo_rooty() - toplevel.winfo_y() |
244
|
|
|
width = (cols * self._colsize) + (2 * border) |
245
|
|
|
height = (rows * self._rowsize) + (titlebar + border) |
246
|
|
|
# fudge factor... |
247
|
|
|
height += self._rowsize/3 |
248
|
|
|
width += self._colsize/1.2 |
249
|
|
|
def resize(): |
250
|
|
|
self.unbind_resize() |
251
|
|
|
toplevel.geometry('%dx%d' % (width, height)) |
252
|
|
|
self.root.after(1, resize) |
253
|
|
|
# Gemotery function is not serial, it gives a chance for other |
254
|
|
|
# functions to be called while the resize is happening. |
255
|
|
|
# Tkinter must finish resizing the window before calling bind_resize |
256
|
|
|
# otherwise _tk_resize will be called and if widght and height |
257
|
|
|
# doesn't map into the existing cols/rows we will get an infinte |
258
|
|
|
# loop |
259
|
|
|
self.root.after(int(RESIZE_DELAY*1000), self.bind_resize) |
260
|
|
|
|
261
|
|
|
|
262
|
|
|
@debug_echo |
263
|
|
|
def _nvim_clear(self): |
264
|
|
|
''' |
265
|
|
|
wipe everyything, even the ~ and status bar |
266
|
|
|
''' |
267
|
|
|
self._screen.clear() |
268
|
|
|
|
269
|
|
|
self.tk_delete_line(del_eol=True, |
270
|
|
|
screen_row=0, |
271
|
|
|
screen_col=0, |
272
|
|
|
count=self.current_rows) |
273
|
|
|
# Add spaces everywhere |
274
|
|
|
self.tk_pad_line(screen_row=0, |
275
|
|
|
screen_col=0, |
276
|
|
|
count=self.current_rows, |
277
|
|
|
add_eol=True,) |
278
|
|
|
|
279
|
|
|
|
280
|
|
|
# @debug_echo |
281
|
|
|
def _nvim_eol_clear(self): |
282
|
|
|
''' |
283
|
|
|
delete from index to end of line, |
284
|
|
|
fill with whitespace |
285
|
|
|
leave eol intact |
286
|
|
|
''' |
287
|
|
|
self._screen.eol_clear() |
288
|
|
|
self.tk_delete_line(del_eol=False) |
289
|
|
|
self.tk_pad_line(screen_col=self._screen.col, |
290
|
|
|
add_eol=False) |
291
|
|
|
|
292
|
|
|
|
293
|
|
|
# @debug_echo |
294
|
|
|
def _nvim_cursor_goto(self, row, col): |
295
|
|
|
'''Move gui cursor to position''' |
296
|
|
|
self._screen.cursor_goto(row, col) |
297
|
|
|
self.text.see("1.0") |
298
|
|
|
|
299
|
|
|
|
300
|
|
|
def _nvim_busy_start(self): |
301
|
|
|
self._busy = True |
302
|
|
|
|
303
|
|
|
|
304
|
|
|
def _nvim_busy_stop(self): |
305
|
|
|
self._busy = False |
306
|
|
|
|
307
|
|
|
|
308
|
|
|
def _nvim_mouse_on(self): |
309
|
|
|
self.mouse_enabled = True |
310
|
|
|
|
311
|
|
|
|
312
|
|
|
def _nvim_mouse_off(self): |
313
|
|
|
self.mouse_enabled = False |
314
|
|
|
|
315
|
|
|
|
316
|
|
|
def _nvim_mode_change(self, mode): |
317
|
|
|
self._insert_cursor = mode == 'insert' |
318
|
|
|
|
319
|
|
|
|
320
|
|
|
# @debug_echo |
321
|
|
|
def _nvim_set_scroll_region(self, top, bot, left, right): |
322
|
|
|
self._screen.set_scroll_region(top, bot, left, right) |
323
|
|
|
|
324
|
|
|
|
325
|
|
|
def _nvim_scroll(self, count): |
326
|
|
|
self._flush() |
327
|
|
|
self._screen.scroll(count) |
328
|
|
|
abs_count = abs(count) |
329
|
|
|
# The minus 1 is because we want our tk_* functions |
330
|
|
|
# to operate on the row passed in |
331
|
|
|
delta = abs_count - 1 |
332
|
|
|
# Down |
333
|
|
|
if count > 0: |
334
|
|
|
delete_row = self._screen.top |
335
|
|
|
pad_row = self._screen.bot - delta |
336
|
|
|
# Up |
337
|
|
|
else: |
338
|
|
|
delete_row = self._screen.bot - delta |
339
|
|
|
pad_row = self._screen.top |
340
|
|
|
|
341
|
|
|
self.tk_delete_line(screen_row=delete_row, |
342
|
|
|
screen_col=0, |
343
|
|
|
del_eol=True, |
344
|
|
|
count=abs_count) |
345
|
|
|
self.tk_pad_line(screen_row=pad_row, |
346
|
|
|
screen_col=0, |
347
|
|
|
add_eol=True, |
348
|
|
|
count=abs_count) |
349
|
|
|
# self.text.yview_scroll(count, 'units') |
350
|
|
|
|
351
|
|
|
|
352
|
|
|
# @debug_echo |
353
|
|
|
def _nvim_highlight_set(self, attrs): |
354
|
|
|
self._attrs = self._get_tk_attrs(attrs) |
355
|
|
|
|
356
|
|
|
|
357
|
|
|
def _reset_attrs_cache(self): |
358
|
|
|
self._tk_text_cache = {} |
359
|
|
|
self._tk_attrs_cache = {} |
360
|
|
|
|
361
|
|
|
|
362
|
|
|
def _get_tk_attrs(self, attrs): |
363
|
|
|
key = tuple(sorted((k, v,) for k, v in (attrs or {}).items())) |
364
|
|
|
rv = self._tk_attrs_cache.get(key, None) |
365
|
|
|
if rv is None: |
366
|
|
|
fg = self._foreground if self._foreground != -1\ |
367
|
|
|
else 0 |
368
|
|
|
bg = self._background if self._background != -1\ |
369
|
|
|
else 0xffffff |
370
|
|
|
n = {'foreground': _split_color(fg), |
371
|
|
|
'background': _split_color(bg),} |
372
|
|
|
if attrs: |
373
|
|
|
# make sure that fg and bg are assigned first |
374
|
|
|
for k in ['foreground', 'background']: |
375
|
|
|
if k in attrs: |
376
|
|
|
n[k] = _split_color(attrs[k]) |
377
|
|
|
for k, v in attrs.items(): |
378
|
|
|
if k == 'reverse': |
379
|
|
|
n['foreground'], n['background'] = \ |
380
|
|
|
n['background'], n['foreground'] |
381
|
|
|
elif k == 'italic': |
382
|
|
|
n['slant'] = 'italic' |
383
|
|
|
elif k == 'bold': |
384
|
|
|
n['weight'] = 'bold' |
385
|
|
|
# TODO |
386
|
|
|
# if self._bold_spacing: |
387
|
|
|
# n['letter_spacing'] \ |
388
|
|
|
# = str(self._bold_spacing) |
389
|
|
|
elif k == 'underline': |
390
|
|
|
n['underline'] = '1' |
391
|
|
|
c = dict(n) |
392
|
|
|
c['foreground'] = _invert_color(*_split_color(fg)) |
393
|
|
|
c['background'] = _invert_color(*_split_color(bg)) |
394
|
|
|
c['foreground'] = _stringify_color(*c['foreground']) |
395
|
|
|
c['background'] = _stringify_color(*c['background']) |
396
|
|
|
n['foreground'] = _stringify_color(*n['foreground']) |
397
|
|
|
n['background'] = _stringify_color(*n['background']) |
398
|
|
|
# n = normal, c = cursor |
399
|
|
|
rv = (n, c) |
400
|
|
|
self._tk_attrs_cache[key] = (n, c) |
401
|
|
|
return rv |
402
|
|
|
|
403
|
|
|
|
404
|
|
|
# @debug_echo |
405
|
|
|
def _nvim_put(self, text): |
406
|
|
|
''' |
407
|
|
|
put a charachter into position, we only write the lines |
408
|
|
|
when a new row is being edited |
409
|
|
|
''' |
410
|
|
|
if self._screen.row != self._pending[0]: |
411
|
|
|
# write to screen if vim puts stuff on a new line |
412
|
|
|
self._flush() |
413
|
|
|
|
414
|
|
|
self._screen.put(text, self._attrs) |
415
|
|
|
self._pending[1] = min(self._screen.col - 1, |
416
|
|
|
self._pending[1]) |
417
|
|
|
self._pending[2] = max(self._screen.col, |
418
|
|
|
self._pending[2]) |
419
|
|
|
|
420
|
|
|
|
421
|
|
|
def _nvim_bell(self): |
422
|
|
|
pass |
423
|
|
|
|
424
|
|
|
|
425
|
|
|
def _nvim_visual_bell(self): |
426
|
|
|
pass |
427
|
|
|
|
428
|
|
|
|
429
|
|
|
# @debug_echo |
430
|
|
|
def _nvim_update_fg(self, fg): |
431
|
|
|
self._foreground = fg |
432
|
|
|
self._reset_attrs_cache() |
433
|
|
|
foreground = self._get_tk_attrs(None)[0]['foreground'] |
434
|
|
|
self.text.config(foreground=foreground) |
435
|
|
|
|
436
|
|
|
|
437
|
|
|
# @debug_echo |
438
|
|
|
def _nvim_update_bg(self, bg): |
439
|
|
|
self._background = bg |
440
|
|
|
self._reset_attrs_cache() |
441
|
|
|
background = self._get_tk_attrs(None)[0]['background'] |
442
|
|
|
self.text.config(background=background) |
443
|
|
|
|
444
|
|
|
|
445
|
|
|
def _nvim_update_suspend(self, arg): |
446
|
|
|
self.root.iconify() |
447
|
|
|
|
448
|
|
|
|
449
|
|
|
def _nvim_set_title(self, title): |
450
|
|
|
self.root.title(title) |
451
|
|
|
|
452
|
|
|
|
453
|
|
|
def _nvim_set_icon(self, icon): |
454
|
|
|
self._icon = tk.PhotoImage(file=icon) |
455
|
|
|
self.root.tk.call('wm', 'iconphoto', |
456
|
|
|
self.root._w, self._icon) |
457
|
|
|
|
458
|
|
|
|
459
|
|
|
def _flush(self): |
460
|
|
|
row, startcol, endcol = self._pending |
461
|
|
|
self._pending[0] = self._screen.row |
462
|
|
|
self._pending[1] = self._screen.col |
463
|
|
|
self._pending[2] = self._screen.col |
464
|
|
|
if startcol == endcol: |
465
|
|
|
#print('startcol is endcol return, row %s col %s'% (self._screen.row, self._screen.col)) |
466
|
|
|
return |
467
|
|
|
ccol = startcol |
468
|
|
|
buf = [] |
469
|
|
|
bold = False |
470
|
|
|
for _, col, text, attrs in self._screen.iter(row, |
471
|
|
|
row, startcol, endcol - 1): |
472
|
|
|
newbold = attrs and 'bold' in attrs[0] |
473
|
|
|
if newbold != bold or not text: |
474
|
|
|
if buf: |
475
|
|
|
self._draw(row, ccol, buf) |
476
|
|
|
bold = newbold |
477
|
|
|
buf = [(text, attrs,)] |
478
|
|
|
ccol = col |
479
|
|
|
else: |
480
|
|
|
buf.append((text, attrs,)) |
481
|
|
|
if buf: |
482
|
|
|
self._draw(row, ccol, buf) |
483
|
|
|
else: |
484
|
|
|
pass |
485
|
|
|
# print('flush with no draw') |
486
|
|
|
|
487
|
|
|
|
488
|
|
|
def _draw(self, row, col, data): |
489
|
|
|
''' |
490
|
|
|
updates a line :) |
491
|
|
|
''' |
492
|
|
|
for text, attrs in data: |
493
|
|
|
try: |
494
|
|
|
start = end |
495
|
|
|
except UnboundLocalError: |
496
|
|
|
start = "{}.{}".format(row + 1, col) |
497
|
|
|
end = start+'+{0}c'.format(len(text)) |
498
|
|
|
|
499
|
|
|
if not attrs: |
500
|
|
|
attrs = self._get_tk_attrs(None) |
501
|
|
|
attrs = attrs[0] |
502
|
|
|
|
503
|
|
|
# if self.debug_echo: |
504
|
|
|
# print('replacing ', repr(self.text.get(start, end))) |
505
|
|
|
# print('with ', repr(text), ' at ', start, ' ',end) |
506
|
|
|
self.text.replace(start, end, text) |
507
|
|
|
|
508
|
|
|
if attrs: |
509
|
|
|
self.text.apply_attribute(attrs, start, end) |
510
|
|
|
start |
511
|
|
|
|
512
|
|
|
|
513
|
|
|
def _nvim_exit(self, arg): |
514
|
|
|
self.root.destroy() |
515
|
|
|
|
516
|
|
|
|
517
|
|
|
class NvimTk(MixNvim, MixTk): |
518
|
|
|
''' |
519
|
|
|
Business Logic for making a tkinter neovim text widget |
520
|
|
|
|
521
|
|
|
we get keys, mouse movements inside tkinter, using binds, |
522
|
|
|
These binds are handed off to neovim using _input |
523
|
|
|
|
524
|
|
|
Neovim interpruts the actions and calls certain |
525
|
|
|
functions which are defined and implemented in tk |
526
|
|
|
|
527
|
|
|
The api from neovim does stuff line by line, |
528
|
|
|
so each callback from neovim produces a series |
529
|
|
|
of miniscule actions which in the end updates a line |
530
|
|
|
''' |
531
|
|
|
|
532
|
|
|
def __init__(self): |
533
|
|
|
# we destroy this when the layout changes |
534
|
|
|
self.start_time = time.time() |
535
|
|
|
self._insert_cursor = False |
536
|
|
|
self._screen = None |
537
|
|
|
self._foreground = -1 |
538
|
|
|
self._background = -1 |
539
|
|
|
self._pending = [0,0,0] |
540
|
|
|
self._attrs = {} |
541
|
|
|
self._reset_attrs_cache() |
542
|
|
|
|
543
|
|
|
def start(self, bridge): |
544
|
|
|
# Maximum cols and rows avaliable |
545
|
|
|
# When we resize then this changes |
546
|
|
|
cols, rows = 80, 24 |
547
|
|
|
self.current_cols = cols |
548
|
|
|
self.current_rows = rows |
549
|
|
|
self._screen = Screen(cols, rows) |
550
|
|
|
bridge.attach(cols, rows, rgb=True) |
551
|
|
|
self._bridge = bridge |
552
|
|
|
|
553
|
|
|
self.debug_echo = False |
554
|
|
|
|
555
|
|
|
self.root = tk.Tk() |
556
|
|
|
self.root.protocol('WM_DELETE_WINDOW', self._tk_quit) |
557
|
|
|
text = tk_util.Text(self.root) |
558
|
|
|
self.text = text |
559
|
|
|
# Hide tkinter cursor |
560
|
|
|
self.text.config(insertontime=0) |
561
|
|
|
|
562
|
|
|
# Remove Default Bindings and what happens on insert etc |
563
|
|
|
bindtags = list(text.bindtags()) |
564
|
|
|
bindtags.remove("Text") |
565
|
|
|
text.bindtags(tuple(bindtags)) |
566
|
|
|
|
567
|
|
|
text.pack(expand=1, fill=tk.BOTH) |
568
|
|
|
text.focus_set() |
569
|
|
|
|
570
|
|
|
text.bind('<Key>', self._tk_key) |
571
|
|
|
self.bind_resize() |
572
|
|
|
|
573
|
|
|
# The negative number makes it pixels instead of point sizes |
574
|
|
|
size = text.make_font_size(13) |
575
|
|
|
self._fnormal = tkfont.Font(family='Monospace', size=size) |
576
|
|
|
self._fbold = tkfont.Font(family='Monospace', weight='bold', size=size) |
577
|
|
|
self._fitalic = tkfont.Font(family='Monospace', slant='italic', size=size) |
578
|
|
|
self._fbolditalic = tkfont.Font(family='Monospace', weight='bold', |
579
|
|
|
slant='italic', size=size) |
580
|
|
|
self.text.config(font=self._fnormal, wrap=tk.NONE) |
581
|
|
|
self._colsize = self._fnormal.measure('M') |
582
|
|
|
self._rowsize = self._fnormal.metrics('linespace') |
583
|
|
|
|
584
|
|
|
self.text.config(background='black') |
585
|
|
|
self.root.mainloop() |
586
|
|
|
|
587
|
|
|
|
588
|
|
|
def schedule_screen_update(self, apply_updates): |
589
|
|
|
'''This function is called from the bridge, |
590
|
|
|
apply_updates calls the required nvim actions''' |
591
|
|
|
# if time.time() - self.start_time > 1: |
592
|
|
|
# print() |
593
|
|
|
# self.start_time = time.time() |
594
|
|
|
def do(): |
595
|
|
|
apply_updates() |
596
|
|
|
self._flush() |
597
|
|
|
self._start_blinking() |
598
|
|
|
self.root.after_idle(do) |
599
|
|
|
|
600
|
|
|
|
601
|
|
|
def quit(self): |
602
|
|
|
self.root.after_idle(self.root.quit) |
603
|
|
|
|
604
|
|
|
|
605
|
|
|
class NvimFriendly(NvimTk): |
606
|
|
|
'''Meant to be subclassed so the user can tweak easily, |
607
|
|
|
atm im just using it to keep the config code seperate''' |
608
|
|
|
|
609
|
|
|
def __init__(self): |
610
|
|
|
super().__init__() |
611
|
|
|
|
612
|
|
|
|
613
|
|
|
def main(address=None): |
614
|
|
|
if address: |
615
|
|
|
nvim = attach('socket', path=address) |
616
|
|
|
else: |
617
|
|
|
try: |
618
|
|
|
address = sys.argv[1] |
619
|
|
|
nvim = attach('socket', path=address) |
620
|
|
|
except: |
621
|
|
|
nvim_binary = find_executable('nvim') |
622
|
|
|
args = [nvim_binary, '--embed'] |
623
|
|
|
# args.extend(['-u', 'NONE']) |
624
|
|
|
nvim = attach('child', argv=args) |
625
|
|
|
ui = NvimFriendly() |
626
|
|
|
bridge = UIBridge() |
627
|
|
|
bridge.connect(nvim, ui) |
628
|
|
|
if len(sys.argv) > 1: |
629
|
|
|
nvim.command('edit ' + sys.argv[1]) |
630
|
|
|
|
631
|
|
|
if __name__ == '__main__': |
632
|
|
|
main() |
633
|
|
|
|