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