vith 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vith/__init__.py +25 -0
- vith/__main__.py +7 -0
- vith/commands/__init__.py +0 -0
- vith/commands/commands.py +811 -0
- vith/commands/completer.py +61 -0
- vith/commands/grammar.py +50 -0
- vith/commands/handler.py +60 -0
- vith/commands/lexer.py +21 -0
- vith/commands/preview.py +134 -0
- vith/completion.py +224 -0
- vith/editor.py +320 -0
- vith/editor_buffer.py +265 -0
- vith/entry_points/__init__.py +0 -0
- vith/entry_points/run_vith.py +53 -0
- vith/help.py +15 -0
- vith/io/__init__.py +10 -0
- vith/io/backends.py +169 -0
- vith/io/base.py +48 -0
- vith/key_bindings.py +323 -0
- vith/layout.py +805 -0
- vith/lexer.py +207 -0
- vith/rc_file.py +56 -0
- vith/reporting.py +134 -0
- vith/style.py +168 -0
- vith/thai.py +1461 -0
- vith/welcome_message.py +48 -0
- vith/window_arrangement.py +553 -0
- vith-0.9.0.dist-info/METADATA +180 -0
- vith-0.9.0.dist-info/RECORD +33 -0
- vith-0.9.0.dist-info/WHEEL +5 -0
- vith-0.9.0.dist-info/entry_points.txt +2 -0
- vith-0.9.0.dist-info/licenses/LICENSE +28 -0
- vith-0.9.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,811 @@
|
|
|
1
|
+
from __future__ import print_function
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
from typing import Any, Dict, Set
|
|
6
|
+
|
|
7
|
+
from prompt_toolkit.application import run_in_terminal
|
|
8
|
+
from prompt_toolkit.document import Document
|
|
9
|
+
|
|
10
|
+
__all__ = (
|
|
11
|
+
'has_command_handler',
|
|
12
|
+
'call_command_handler',
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
COMMANDS_TO_HANDLERS: Dict[str, Any] = {} # Global mapping Vi commands to their handler.
|
|
17
|
+
COMMANDS_TAKING_LOCATIONS: Set[str] = set() # Name of commands that accept locations.
|
|
18
|
+
SET_COMMANDS: Dict[str, Any] = {} # Mapping ':set'-commands to their handler.
|
|
19
|
+
SET_COMMANDS_TAKING_VALUE: Set[str] = set()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_NO_WRITE_SINCE_LAST_CHANGE_TEXT = 'No write since last change (add ! to override)'
|
|
23
|
+
_NO_FILE_NAME = 'No file name'
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def has_command_handler(command):
|
|
27
|
+
return command in COMMANDS_TO_HANDLERS
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def call_command_handler(command, editor, variables):
|
|
31
|
+
"""
|
|
32
|
+
Execute command.
|
|
33
|
+
"""
|
|
34
|
+
COMMANDS_TO_HANDLERS[command](editor, variables)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_commands():
|
|
38
|
+
return COMMANDS_TO_HANDLERS.keys()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_commands_taking_locations():
|
|
42
|
+
return COMMANDS_TAKING_LOCATIONS
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# Decorators
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _cmd(name):
|
|
49
|
+
"""
|
|
50
|
+
Base decorator for registering commands in this namespace.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def decorator(func):
|
|
54
|
+
COMMANDS_TO_HANDLERS[name] = func
|
|
55
|
+
return func
|
|
56
|
+
|
|
57
|
+
return decorator
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def location_cmd(name, accepts_force=False):
|
|
61
|
+
"""
|
|
62
|
+
Decorator that registers a command that takes a location as (optional)
|
|
63
|
+
parameter.
|
|
64
|
+
"""
|
|
65
|
+
COMMANDS_TAKING_LOCATIONS.add(name)
|
|
66
|
+
|
|
67
|
+
def decorator(func):
|
|
68
|
+
@_cmd(name)
|
|
69
|
+
def command_wrapper(editor, variables):
|
|
70
|
+
location = variables.get('location')
|
|
71
|
+
force = bool(variables['force'])
|
|
72
|
+
|
|
73
|
+
if force and not accepts_force:
|
|
74
|
+
editor.show_message('No ! allowed')
|
|
75
|
+
elif accepts_force:
|
|
76
|
+
func(editor, location, force=force)
|
|
77
|
+
else:
|
|
78
|
+
func(editor, location)
|
|
79
|
+
|
|
80
|
+
return func
|
|
81
|
+
|
|
82
|
+
return decorator
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def cmd(name, accepts_force=False):
|
|
86
|
+
"""
|
|
87
|
+
Decorator that registers a command that doesn't take any parameters.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def decorator(func):
|
|
91
|
+
@_cmd(name)
|
|
92
|
+
def command_wrapper(editor, variables):
|
|
93
|
+
force = bool(variables['force'])
|
|
94
|
+
|
|
95
|
+
if force and not accepts_force:
|
|
96
|
+
editor.show_message('No ! allowed')
|
|
97
|
+
elif accepts_force:
|
|
98
|
+
func(editor, force=force)
|
|
99
|
+
else:
|
|
100
|
+
func(editor)
|
|
101
|
+
|
|
102
|
+
return func
|
|
103
|
+
|
|
104
|
+
return decorator
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def set_cmd(name, accepts_value=False):
|
|
108
|
+
"""
|
|
109
|
+
Decorator that registers a ':set'-command.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def decorator(func):
|
|
113
|
+
SET_COMMANDS[name] = func
|
|
114
|
+
if accepts_value:
|
|
115
|
+
SET_COMMANDS_TAKING_VALUE.add(name)
|
|
116
|
+
return func
|
|
117
|
+
|
|
118
|
+
return decorator
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# Actual command implementations
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@_cmd('set')
|
|
125
|
+
def set_command_execute(editor, variables):
|
|
126
|
+
"""
|
|
127
|
+
Execute a ':set' command.
|
|
128
|
+
"""
|
|
129
|
+
option = variables.get('set_option')
|
|
130
|
+
value = variables.get('set_value')
|
|
131
|
+
|
|
132
|
+
if option in SET_COMMANDS:
|
|
133
|
+
# Call the correct handler.
|
|
134
|
+
if option in SET_COMMANDS_TAKING_VALUE:
|
|
135
|
+
SET_COMMANDS[option](editor, value)
|
|
136
|
+
else:
|
|
137
|
+
SET_COMMANDS[option](editor)
|
|
138
|
+
else:
|
|
139
|
+
editor.show_message('Unknown option: %s' % option)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@cmd('bn', accepts_force=True)
|
|
143
|
+
def _bn(editor, force=False):
|
|
144
|
+
"""
|
|
145
|
+
Go to next buffer.
|
|
146
|
+
"""
|
|
147
|
+
eb = editor.window_arrangement.active_editor_buffer
|
|
148
|
+
|
|
149
|
+
if not force and eb.has_unsaved_changes:
|
|
150
|
+
editor.show_message(_NO_WRITE_SINCE_LAST_CHANGE_TEXT)
|
|
151
|
+
else:
|
|
152
|
+
editor.window_arrangement.go_to_next_buffer()
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@cmd('bp', accepts_force=True)
|
|
156
|
+
def _bp(editor, force=False):
|
|
157
|
+
"""
|
|
158
|
+
Go to previous buffer.
|
|
159
|
+
"""
|
|
160
|
+
eb = editor.window_arrangement.active_editor_buffer
|
|
161
|
+
|
|
162
|
+
if not force and eb.has_unsaved_changes:
|
|
163
|
+
editor.show_message(_NO_WRITE_SINCE_LAST_CHANGE_TEXT)
|
|
164
|
+
else:
|
|
165
|
+
editor.window_arrangement.go_to_previous_buffer()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@cmd('only')
|
|
169
|
+
def only(editor):
|
|
170
|
+
"""
|
|
171
|
+
Keep only the current window.
|
|
172
|
+
"""
|
|
173
|
+
editor.window_arrangement.keep_only_current_window()
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@cmd('hide')
|
|
177
|
+
def hide(editor):
|
|
178
|
+
"""
|
|
179
|
+
Hide the current window.
|
|
180
|
+
"""
|
|
181
|
+
editor.window_arrangement.close_window()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@location_cmd('sp')
|
|
185
|
+
@location_cmd('split')
|
|
186
|
+
def horizontal_split(editor, location):
|
|
187
|
+
"""
|
|
188
|
+
Split window horizontally.
|
|
189
|
+
"""
|
|
190
|
+
editor.window_arrangement.hsplit(location or None)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@location_cmd('vsp')
|
|
194
|
+
@location_cmd('vsplit')
|
|
195
|
+
def vertical_split(editor, location):
|
|
196
|
+
"""
|
|
197
|
+
Split window vertically.
|
|
198
|
+
"""
|
|
199
|
+
editor.window_arrangement.vsplit(location or None)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@cmd('new')
|
|
203
|
+
def new_buffer(editor):
|
|
204
|
+
"""
|
|
205
|
+
Create new buffer.
|
|
206
|
+
"""
|
|
207
|
+
editor.window_arrangement.hsplit(new=True)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@cmd('vnew')
|
|
211
|
+
def new_vertical_buffer(editor):
|
|
212
|
+
"""
|
|
213
|
+
Create new buffer, splitting vertically.
|
|
214
|
+
"""
|
|
215
|
+
editor.window_arrangement.vsplit(new=True)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@location_cmd('badd')
|
|
219
|
+
def buffer_add(editor, location):
|
|
220
|
+
"""
|
|
221
|
+
Add a new buffer.
|
|
222
|
+
"""
|
|
223
|
+
editor.window_arrangement.open_buffer(location)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@cmd('buffers')
|
|
227
|
+
def buffer_list(editor):
|
|
228
|
+
"""
|
|
229
|
+
List all buffers.
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
def handler():
|
|
233
|
+
wa = editor.window_arrangement
|
|
234
|
+
for info in wa.list_open_buffers():
|
|
235
|
+
char = '%' if info.is_active else ''
|
|
236
|
+
eb = info.editor_buffer
|
|
237
|
+
print(
|
|
238
|
+
' %3i %-2s %-20s line %i'
|
|
239
|
+
% (info.index, char, eb.location, (eb.buffer.document.cursor_position_row + 1))
|
|
240
|
+
)
|
|
241
|
+
input('\nPress ENTER to continue...')
|
|
242
|
+
|
|
243
|
+
run_in_terminal(handler)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@_cmd('b')
|
|
247
|
+
@_cmd('buffer')
|
|
248
|
+
def _buffer(editor, variables):
|
|
249
|
+
"""
|
|
250
|
+
Go to one of the open buffers.
|
|
251
|
+
"""
|
|
252
|
+
eb = editor.window_arrangement.active_editor_buffer
|
|
253
|
+
force = bool(variables['force'])
|
|
254
|
+
|
|
255
|
+
buffer_name = variables.get('buffer_name')
|
|
256
|
+
if buffer_name:
|
|
257
|
+
if not force and eb.has_unsaved_changes:
|
|
258
|
+
editor.show_message(_NO_WRITE_SINCE_LAST_CHANGE_TEXT)
|
|
259
|
+
else:
|
|
260
|
+
editor.window_arrangement.go_to_buffer(buffer_name)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@cmd('bw', accepts_force=True)
|
|
264
|
+
@cmd('bd', accepts_force=True)
|
|
265
|
+
def buffer_wipe(editor, force=False):
|
|
266
|
+
"""
|
|
267
|
+
Wipe buffer.
|
|
268
|
+
"""
|
|
269
|
+
eb = editor.window_arrangement.active_editor_buffer
|
|
270
|
+
if not force and eb.has_unsaved_changes:
|
|
271
|
+
editor.show_message(_NO_WRITE_SINCE_LAST_CHANGE_TEXT)
|
|
272
|
+
else:
|
|
273
|
+
editor.window_arrangement.close_buffer()
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@location_cmd('o', accepts_force=True)
|
|
277
|
+
@location_cmd('open', accepts_force=True)
|
|
278
|
+
@location_cmd('e', accepts_force=True)
|
|
279
|
+
@location_cmd('edit', accepts_force=True)
|
|
280
|
+
def buffer_edit(editor, location, force=False):
|
|
281
|
+
"""
|
|
282
|
+
Edit new buffer.
|
|
283
|
+
"""
|
|
284
|
+
if location is None:
|
|
285
|
+
# Edit/open without a location will reload the current file, if there are
|
|
286
|
+
# no changes.
|
|
287
|
+
eb = editor.window_arrangement.active_editor_buffer
|
|
288
|
+
if eb.location is None:
|
|
289
|
+
editor.show_message(_NO_FILE_NAME)
|
|
290
|
+
elif not force and eb.has_unsaved_changes:
|
|
291
|
+
editor.show_message(_NO_WRITE_SINCE_LAST_CHANGE_TEXT)
|
|
292
|
+
else:
|
|
293
|
+
eb.reload()
|
|
294
|
+
else:
|
|
295
|
+
editor.window_arrangement.open_buffer(location, show_in_current_window=True)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
@cmd('q', accepts_force=True)
|
|
299
|
+
@cmd('quit', accepts_force=True)
|
|
300
|
+
def quit(editor, all_=False, force=False):
|
|
301
|
+
"""
|
|
302
|
+
Quit.
|
|
303
|
+
"""
|
|
304
|
+
ebs = editor.window_arrangement.editor_buffers
|
|
305
|
+
|
|
306
|
+
# When there are buffers that have unsaved changes, show balloon.
|
|
307
|
+
if not force and any(eb.has_unsaved_changes for eb in ebs):
|
|
308
|
+
editor.show_message(_NO_WRITE_SINCE_LAST_CHANGE_TEXT)
|
|
309
|
+
|
|
310
|
+
# When there is more than one buffer open.
|
|
311
|
+
elif not all_ and len(ebs) > 1:
|
|
312
|
+
editor.show_message('%i more files to edit' % (len(ebs) - 1))
|
|
313
|
+
|
|
314
|
+
else:
|
|
315
|
+
editor.application.exit()
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
@cmd('qa', accepts_force=True)
|
|
319
|
+
@cmd('qall', accepts_force=True)
|
|
320
|
+
def quit_all(editor, force=False):
|
|
321
|
+
"""
|
|
322
|
+
Quit all.
|
|
323
|
+
"""
|
|
324
|
+
quit(editor, all_=True, force=force)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@location_cmd('w', accepts_force=True)
|
|
328
|
+
@location_cmd('write', accepts_force=True)
|
|
329
|
+
def write(editor, location, force=False):
|
|
330
|
+
"""
|
|
331
|
+
Write file. Returns whether the buffer was written.
|
|
332
|
+
"""
|
|
333
|
+
if location and not force and os.path.exists(os.path.expanduser(location)):
|
|
334
|
+
editor.show_message('File exists (add ! to override)')
|
|
335
|
+
return False
|
|
336
|
+
eb = editor.window_arrangement.active_editor_buffer
|
|
337
|
+
if location is None and eb.location is None:
|
|
338
|
+
editor.show_message(_NO_FILE_NAME)
|
|
339
|
+
return False
|
|
340
|
+
return eb.write(location)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
@location_cmd('wq', accepts_force=True)
|
|
344
|
+
def write_and_quit(editor, location, force=False):
|
|
345
|
+
"""
|
|
346
|
+
Write file and quit. Does not quit when the write failed.
|
|
347
|
+
"""
|
|
348
|
+
if write(editor, location, force=force):
|
|
349
|
+
editor.application.exit()
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@cmd('cq')
|
|
353
|
+
def quit_nonzero(editor):
|
|
354
|
+
"""
|
|
355
|
+
Quit with non zero exit status.
|
|
356
|
+
"""
|
|
357
|
+
# Note: the try/finally in `prompt_toolkit.Interface.read_input`
|
|
358
|
+
# will ensure that the render output is reset, leaving the alternate
|
|
359
|
+
# screen before quitting.
|
|
360
|
+
editor.application.exit()
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
@cmd('wqa')
|
|
364
|
+
def write_and_quit_all(editor):
|
|
365
|
+
"""
|
|
366
|
+
Write current buffer and quit all.
|
|
367
|
+
"""
|
|
368
|
+
eb = editor.window_arrangement.active_editor_buffer
|
|
369
|
+
if eb.location is None:
|
|
370
|
+
editor.show_message(_NO_FILE_NAME)
|
|
371
|
+
elif eb.write():
|
|
372
|
+
quit(editor, all_=True, force=False)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
@cmd('h')
|
|
376
|
+
@cmd('help')
|
|
377
|
+
def help(editor):
|
|
378
|
+
"""
|
|
379
|
+
Show help.
|
|
380
|
+
"""
|
|
381
|
+
editor.show_help()
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
@location_cmd('tabe')
|
|
385
|
+
@location_cmd('tabedit')
|
|
386
|
+
@location_cmd('tabnew')
|
|
387
|
+
def tab_new(editor, location):
|
|
388
|
+
"""
|
|
389
|
+
Create new tab page.
|
|
390
|
+
"""
|
|
391
|
+
editor.window_arrangement.create_tab(location or None)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
@cmd('tabclose')
|
|
395
|
+
@cmd('tabc')
|
|
396
|
+
def tab_close(editor):
|
|
397
|
+
"""
|
|
398
|
+
Close tab page.
|
|
399
|
+
"""
|
|
400
|
+
editor.window_arrangement.close_tab()
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
@cmd('tabnext')
|
|
404
|
+
@cmd('tabn')
|
|
405
|
+
def tab_next(editor):
|
|
406
|
+
"""
|
|
407
|
+
Go to next tab.
|
|
408
|
+
"""
|
|
409
|
+
editor.window_arrangement.go_to_next_tab()
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
@cmd('tabprevious')
|
|
413
|
+
@cmd('tabp')
|
|
414
|
+
def tab_previous(editor):
|
|
415
|
+
"""
|
|
416
|
+
Go to previous tab.
|
|
417
|
+
"""
|
|
418
|
+
editor.window_arrangement.go_to_previous_tab()
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
@cmd('pwd')
|
|
422
|
+
def pwd(editor):
|
|
423
|
+
"Print working directory."
|
|
424
|
+
directory = os.getcwd()
|
|
425
|
+
editor.show_message('{}'.format(directory))
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
@location_cmd('cd', accepts_force=False)
|
|
429
|
+
def cd(editor, location):
|
|
430
|
+
"Change working directory."
|
|
431
|
+
target = os.path.expanduser(location or '~')
|
|
432
|
+
try:
|
|
433
|
+
os.chdir(target)
|
|
434
|
+
except OSError as e:
|
|
435
|
+
editor.show_message('{}'.format(e))
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
@_cmd('colorscheme')
|
|
439
|
+
@_cmd('colo')
|
|
440
|
+
def color_scheme(editor, variables):
|
|
441
|
+
"""
|
|
442
|
+
Apply a colorscheme by name.
|
|
443
|
+
"""
|
|
444
|
+
colorscheme = variables.get('colorscheme')
|
|
445
|
+
if colorscheme:
|
|
446
|
+
editor.use_colorscheme(colorscheme)
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
@set_cmd('nu')
|
|
450
|
+
@set_cmd('number')
|
|
451
|
+
def line_numbers_show(editor):
|
|
452
|
+
"""Show line numbers."""
|
|
453
|
+
editor.show_line_numbers = True
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
@set_cmd('nonu')
|
|
457
|
+
@set_cmd('nonumber')
|
|
458
|
+
def line_numbers_hide(editor):
|
|
459
|
+
"""Hide line numbers."""
|
|
460
|
+
editor.show_line_numbers = False
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
@set_cmd('hlsearch')
|
|
464
|
+
@set_cmd('hls')
|
|
465
|
+
def search_highlight(editor):
|
|
466
|
+
"""Highlight search matches."""
|
|
467
|
+
editor.highlight_search = True
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
@set_cmd('nohlsearch')
|
|
471
|
+
@set_cmd('nohls')
|
|
472
|
+
def search_no_highlight(editor):
|
|
473
|
+
"""Don't highlight search matches."""
|
|
474
|
+
editor.highlight_search = False
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
@set_cmd('paste')
|
|
478
|
+
def paste_mode(editor):
|
|
479
|
+
"""Enter paste mode."""
|
|
480
|
+
editor.paste_mode = True
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
@set_cmd('nopaste')
|
|
484
|
+
def paste_mode_leave(editor):
|
|
485
|
+
"""Leave paste mode."""
|
|
486
|
+
editor.paste_mode = False
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
@set_cmd('ruler')
|
|
490
|
+
@set_cmd('ru')
|
|
491
|
+
def ruler_show(editor):
|
|
492
|
+
"""Show ruler."""
|
|
493
|
+
editor.show_ruler = True
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
@set_cmd('noruler')
|
|
497
|
+
@set_cmd('noru')
|
|
498
|
+
def ruler_hide(editor):
|
|
499
|
+
"""Hide ruler."""
|
|
500
|
+
editor.show_ruler = False
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
@set_cmd('wildmenu')
|
|
504
|
+
@set_cmd('wmnu')
|
|
505
|
+
def wild_menu_show(editor):
|
|
506
|
+
"""Show wildmenu."""
|
|
507
|
+
editor.show_wildmenu = True
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
@set_cmd('nowildmenu')
|
|
511
|
+
@set_cmd('nowmnu')
|
|
512
|
+
def wild_menu_hide(editor):
|
|
513
|
+
"""Hide wildmenu."""
|
|
514
|
+
editor.show_wildmenu = False
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
@set_cmd('expandtab')
|
|
518
|
+
@set_cmd('et')
|
|
519
|
+
def tab_expand(editor):
|
|
520
|
+
"""Enable tab expension."""
|
|
521
|
+
editor.expand_tab = True
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
@set_cmd('noexpandtab')
|
|
525
|
+
@set_cmd('noet')
|
|
526
|
+
def tab_no_expand(editor):
|
|
527
|
+
"""Disable tab expension."""
|
|
528
|
+
editor.expand_tab = False
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def _set_int_option(editor, value, attribute, display_name, minimum):
|
|
532
|
+
"""
|
|
533
|
+
Shared implementation of the integer-valued `:set` handlers.
|
|
534
|
+
|
|
535
|
+
`value is None` displays the current value; invalid input shows vim-like
|
|
536
|
+
error messages; otherwise the attribute is set when `value >= minimum`.
|
|
537
|
+
"""
|
|
538
|
+
if value is None:
|
|
539
|
+
editor.show_message('%s=%i' % (display_name, getattr(editor, attribute)))
|
|
540
|
+
return
|
|
541
|
+
try:
|
|
542
|
+
number = int(value)
|
|
543
|
+
except ValueError:
|
|
544
|
+
editor.show_message('Number required after =')
|
|
545
|
+
return
|
|
546
|
+
if number < minimum:
|
|
547
|
+
editor.show_message('Argument must be positive')
|
|
548
|
+
else:
|
|
549
|
+
setattr(editor, attribute, number)
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
@set_cmd('tabstop', accepts_value=True)
|
|
553
|
+
@set_cmd('ts', accepts_value=True)
|
|
554
|
+
def tab_stop(editor, value):
|
|
555
|
+
"""
|
|
556
|
+
Set tabstop.
|
|
557
|
+
"""
|
|
558
|
+
_set_int_option(editor, value, 'tabstop', 'tabstop', minimum=1)
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
@set_cmd('scrolloff', accepts_value=True)
|
|
562
|
+
@set_cmd('so', accepts_value=True)
|
|
563
|
+
def set_scroll_offset(editor, value):
|
|
564
|
+
"""
|
|
565
|
+
Set scroll offset.
|
|
566
|
+
"""
|
|
567
|
+
_set_int_option(editor, value, 'scroll_offset', 'scrolloff', minimum=0)
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
@set_cmd('incsearch')
|
|
571
|
+
@set_cmd('is')
|
|
572
|
+
def incsearch_enable(editor):
|
|
573
|
+
"""Enable incsearch."""
|
|
574
|
+
editor.incsearch = True
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
@set_cmd('noincsearch')
|
|
578
|
+
@set_cmd('nois')
|
|
579
|
+
def incsearch_disable(editor):
|
|
580
|
+
"""Disable incsearch."""
|
|
581
|
+
editor.incsearch = False
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
@set_cmd('ignorecase')
|
|
585
|
+
@set_cmd('ic')
|
|
586
|
+
def search_ignorecase(editor):
|
|
587
|
+
"""Enable case insensitive searching."""
|
|
588
|
+
editor.ignore_case = True
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
@set_cmd('noignorecase')
|
|
592
|
+
@set_cmd('noic')
|
|
593
|
+
def search_no_ignorecase(editor):
|
|
594
|
+
"""Disable case insensitive searching."""
|
|
595
|
+
editor.ignore_case = False
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
@set_cmd('list')
|
|
599
|
+
def unprintable_show(editor):
|
|
600
|
+
"""Display unprintable characters."""
|
|
601
|
+
editor.display_unprintable_characters = True
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
@set_cmd('nolist')
|
|
605
|
+
def unprintable_hide(editor):
|
|
606
|
+
"""Hide unprintable characters."""
|
|
607
|
+
editor.display_unprintable_characters = False
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
@set_cmd('jedi')
|
|
611
|
+
def jedi_enable(editor):
|
|
612
|
+
"""Enable Jedi autocompletion for Python files."""
|
|
613
|
+
editor.enable_jedi = True
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
@set_cmd('nojedi')
|
|
617
|
+
def jedi_disable(editor):
|
|
618
|
+
"""Disable Jedi autocompletion."""
|
|
619
|
+
editor.enable_jedi = False
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
@set_cmd('relativenumber')
|
|
623
|
+
@set_cmd('rnu')
|
|
624
|
+
def relative_number(editor):
|
|
625
|
+
"Enable relative number"
|
|
626
|
+
editor.relative_number = True
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
@set_cmd('norelativenumber')
|
|
630
|
+
@set_cmd('nornu')
|
|
631
|
+
def no_relative_number(editor):
|
|
632
|
+
"Disable relative number"
|
|
633
|
+
editor.relative_number = False
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
@set_cmd('wrap')
|
|
637
|
+
def enable_wrap(editor):
|
|
638
|
+
"Enable line wrapping."
|
|
639
|
+
editor.wrap_lines = True
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
@set_cmd('nowrap')
|
|
643
|
+
def disable_wrap(editor):
|
|
644
|
+
"disable line wrapping."
|
|
645
|
+
editor.wrap_lines = False
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
@set_cmd('breakindent')
|
|
649
|
+
@set_cmd('bri')
|
|
650
|
+
def enable_breakindent(editor):
|
|
651
|
+
"Enable the breakindent option."
|
|
652
|
+
editor.break_indent = True
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
@set_cmd('nobreakindent')
|
|
656
|
+
@set_cmd('nobri')
|
|
657
|
+
def disable_breakindent(editor):
|
|
658
|
+
"Disable the breakindent option."
|
|
659
|
+
editor.break_indent = False
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
@set_cmd('mouse')
|
|
663
|
+
def enable_mouse(editor):
|
|
664
|
+
"Enable mouse ."
|
|
665
|
+
editor.enable_mouse_support = True
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
@set_cmd('nomouse')
|
|
669
|
+
def disable_mouse(editor):
|
|
670
|
+
"Disable mouse."
|
|
671
|
+
editor.enable_mouse_support = False
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
@set_cmd('tildeop')
|
|
675
|
+
@set_cmd('top')
|
|
676
|
+
def enable_tildeop(editor):
|
|
677
|
+
"Enable tilde operator."
|
|
678
|
+
editor.application.vi_state.tilde_operator = True
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
@set_cmd('notildeop')
|
|
682
|
+
@set_cmd('notop')
|
|
683
|
+
def disable_tildeop(editor):
|
|
684
|
+
"Disable tilde operator."
|
|
685
|
+
editor.application.vi_state.tilde_operator = False
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
@set_cmd('cursorline')
|
|
689
|
+
@set_cmd('cul')
|
|
690
|
+
def enable_cursorline(editor):
|
|
691
|
+
"Highlight the line that contains the cursor."
|
|
692
|
+
editor.cursorline = True
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
@set_cmd('nocursorline')
|
|
696
|
+
@set_cmd('nocul')
|
|
697
|
+
def disable_cursorline(editor):
|
|
698
|
+
"No cursorline."
|
|
699
|
+
editor.cursorline = False
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
@set_cmd('cursorcolumn')
|
|
703
|
+
@set_cmd('cuc')
|
|
704
|
+
def enable_cursorcolumn(editor):
|
|
705
|
+
"Highlight the column that contains the cursor."
|
|
706
|
+
editor.cursorcolumn = True
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
@set_cmd('nocursorcolumn')
|
|
710
|
+
@set_cmd('nocuc')
|
|
711
|
+
def disable_cursorcolumn(editor):
|
|
712
|
+
"No cursorcolumn."
|
|
713
|
+
editor.cursorcolumn = False
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
@set_cmd('colorcolumn', accepts_value=True)
|
|
717
|
+
@set_cmd('cc', accepts_value=True)
|
|
718
|
+
def set_colorcolumn(editor, value):
|
|
719
|
+
try:
|
|
720
|
+
if value:
|
|
721
|
+
numbers = [int(val) for val in value.split(',')]
|
|
722
|
+
else:
|
|
723
|
+
numbers = []
|
|
724
|
+
except ValueError:
|
|
725
|
+
editor.show_message('Invalid value. Expecting comma separated list of integers')
|
|
726
|
+
else:
|
|
727
|
+
editor.colorcolumn = numbers
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
def substitute(editor, range_start, range_end, search, replace, flags):
|
|
731
|
+
"""Substitute /search/ with /replace/ over a range of text"""
|
|
732
|
+
|
|
733
|
+
search_state = editor.application.current_search_state
|
|
734
|
+
buffer = editor.current_editor_buffer.buffer
|
|
735
|
+
cursor_position_row = buffer.document.cursor_position_row
|
|
736
|
+
|
|
737
|
+
# Resolve search pattern from history when omitted.
|
|
738
|
+
if not search:
|
|
739
|
+
search = search_state.text
|
|
740
|
+
if not search:
|
|
741
|
+
editor.show_message('No previous regular expression')
|
|
742
|
+
return
|
|
743
|
+
|
|
744
|
+
if replace is None:
|
|
745
|
+
replace = editor.last_substitute_text
|
|
746
|
+
|
|
747
|
+
# Compile the regex once; reject malformed patterns up front.
|
|
748
|
+
regex_flags = re.IGNORECASE if getattr(editor, 'ignore_case', False) else 0
|
|
749
|
+
try:
|
|
750
|
+
pattern = re.compile(search, flags=regex_flags)
|
|
751
|
+
except re.error as e:
|
|
752
|
+
editor.show_message('Invalid regex: %s' % e)
|
|
753
|
+
return
|
|
754
|
+
|
|
755
|
+
# Convert and validate the captured range.
|
|
756
|
+
if not range_start:
|
|
757
|
+
assert not range_end
|
|
758
|
+
range_start_row = range_end_row = cursor_position_row
|
|
759
|
+
else:
|
|
760
|
+
range_start_row = int(range_start) - 1
|
|
761
|
+
range_end_row = int(range_end) - 1 if range_end else range_start_row
|
|
762
|
+
if not 0 <= range_start_row <= range_end_row < len(buffer.document.lines):
|
|
763
|
+
editor.show_message('E19: No such line in current buffer')
|
|
764
|
+
return
|
|
765
|
+
line_numbers = list(range(range_start_row, range_end_row + 1))
|
|
766
|
+
|
|
767
|
+
sub_count = 0 if 'g' in flags else 1
|
|
768
|
+
|
|
769
|
+
# Transform each line once, tracking the last substituted row and
|
|
770
|
+
# rejecting invalid replacement escapes without mutating the buffer.
|
|
771
|
+
buffer_lines = buffer.document.lines
|
|
772
|
+
substituted = {}
|
|
773
|
+
last_substituted_row = None
|
|
774
|
+
regex_error = None
|
|
775
|
+
for row in line_numbers:
|
|
776
|
+
original = buffer_lines[row]
|
|
777
|
+
try:
|
|
778
|
+
result = pattern.sub(replace, original, count=sub_count)
|
|
779
|
+
except re.error as e:
|
|
780
|
+
regex_error = e
|
|
781
|
+
break
|
|
782
|
+
substituted[row] = result
|
|
783
|
+
if result != original:
|
|
784
|
+
last_substituted_row = row
|
|
785
|
+
|
|
786
|
+
if regex_error is not None:
|
|
787
|
+
editor.show_message('Invalid regex: %s' % regex_error)
|
|
788
|
+
return
|
|
789
|
+
|
|
790
|
+
if last_substituted_row is None:
|
|
791
|
+
editor.show_message('Pattern not found')
|
|
792
|
+
return
|
|
793
|
+
|
|
794
|
+
# Apply substitution.
|
|
795
|
+
buffer.save_to_undo_stack()
|
|
796
|
+
lines = list(buffer.document.lines)
|
|
797
|
+
for row in line_numbers:
|
|
798
|
+
lines[row] = substituted[row]
|
|
799
|
+
new_text = "\n".join(lines)
|
|
800
|
+
|
|
801
|
+
doc = Document(new_text)
|
|
802
|
+
buffer.document = doc
|
|
803
|
+
row = last_substituted_row
|
|
804
|
+
buffer.cursor_position = doc.translate_row_col_to_index(row, 0) + (
|
|
805
|
+
len(doc.lines[row]) - len(doc.lines[row].lstrip())
|
|
806
|
+
)
|
|
807
|
+
buffer._search(search_state, include_current_position=True)
|
|
808
|
+
|
|
809
|
+
# update editor state
|
|
810
|
+
editor.last_substitute_text = replace
|
|
811
|
+
search_state.text = search
|