Coverage for C:\Repos\leo-editor\leo\core\leoVim.py: 20%
1375 statements
« prev ^ index » next coverage.py v6.4, created at 2022-05-24 10:21 -0500
« prev ^ index » next coverage.py v6.4, created at 2022-05-24 10:21 -0500
1# -*- coding: utf-8 -*-
2#@+leo-ver=5-thin
3#@+node:ekr.20131109170017.16504: * @file leoVim.py
4#@@first
5"""
6Leo's vim mode.
8**Important**
10`@bool vim-mode` enables vim *mode*.
12`@keys Vim bindings` enables vim *emulation*.
14Vim *mode* is independent of vim *emulation* because
15k.masterKeyHandler dispatches keys to vim mode before
16doing the normal key handling that vim emulation uses.
17"""
18import os
19import string
20from leo.core import leoGlobals as g
21from leo.core.leoGui import LeoKeyEvent
22#@+others
23#@+node:ekr.20140802183521.17997: ** show_stroke
24#@@nobeautify
26def show_stroke(stroke):
27 """Return the best human-readable form of stroke."""
28 s = stroke.s if g.isStroke(stroke) else stroke
29 d = {
30 '\n': r'<NL>',
31 'Ctrl+Left': '<Ctrl+Lt>',
32 'Ctrl+Right': '<Ctrl+Rt>',
33 'Ctrl+r': '<Ctrl+r>',
34 'Down': '<Dn>',
35 'Escape': '<Esc>',
36 'Left': '<Lt>',
37 'Right': '<Rt>',
38 'Up': '<Up>',
39 'colon': ':',
40 'dollar': '$',
41 'period': '.',
42 'space': ' ',
43 }
44 return d.get(s, s)
45#@+node:ekr.20150509040011.1: ** vc.cmd (decorator)
46def cmd(name):
47 """Command decorator for the VimCommands class."""
48 return g.new_cmd_decorator(name, ['c', 'vimCommands',])
49#@+node:ekr.20140802183521.17996: ** class VimEvent
50class VimEvent:
51 """A class to contain the components of the dot."""
53 def __init__(self, c, char, stroke, w):
54 """ctor for the VimEvent class."""
55 self.c = c
56 self.char = char # For Leo's core.
57 self.stroke = stroke
58 self.w = w
59 self.widget = w # For Leo's core.
61 def __repr__(self):
62 """Return the representation of the stroke."""
63 return show_stroke(self.stroke)
65 __str__ = __repr__
66#@+node:ekr.20131113045621.16547: ** class VimCommands
67class VimCommands:
68 """
69 A class that handles vim mode in Leo.
71 In vim mode, k.masterKeyHandler calls
73 """
74 #@+others
75 #@+node:ekr.20131109170017.16507: *3* vc.ctor & helpers
76 def __init__(self, c):
77 """The ctor for the VimCommands class."""
78 self.c = c
79 self.k = c.k
80 # Toggled by :toggle-vim-trace.
81 self.trace_flag = 'keys' in g.app.debug
82 self.init_constant_ivars()
83 self.init_dot_ivars()
84 self.init_persistent_ivars()
85 self.init_state_ivars()
86 self.create_dispatch_dicts()
87 #@+node:ekr.20140805130800.18157: *4* dispatch dicts...
88 #@+node:ekr.20140805130800.18162: *5* vc.create_dispatch_dicts
89 def create_dispatch_dicts(self):
90 """Create all dispatch dicts."""
91 # Dispatch table for normal mode.
92 self.normal_mode_dispatch_d = d1 = self.create_normal_dispatch_d()
93 # Dispatch table for motions.
94 self.motion_dispatch_d = d2 = self.create_motion_dispatch_d()
95 # Dispatch table for visual mode.
96 self.vis_dispatch_d = d3 = self.create_vis_dispatch_d()
97 # Add all entries in arrow dict to the other dicts.
98 self.arrow_d = arrow_d = self.create_arrow_d()
99 for d, tag in ((d1, 'normal'), (d2, 'motion'), (d3, 'visual')):
100 for key in arrow_d:
101 if key in d:
102 g.trace(f"duplicate arrow key in {tag} dict: {key}")
103 else:
104 d[key] = arrow_d.get(key)
105 if 1:
106 # Check for conflicts between motion dict (d2) and the normal and visual dicts.
107 # These are not necessarily errors, but are useful for debugging.
108 for d, tag in ((d1, 'normal'), (d3, 'visual')):
109 for key in d2:
110 f, f2 = d.get(key), d2.get(key)
111 if f2 and f and f != f2:
112 g.trace(
113 f"conflicting motion key in {tag} "
114 f"dict: {key} {f2.__name__} {f.__name__}")
115 elif f2 and not f:
116 g.trace(
117 f"missing motion key in {tag} "
118 f"dict: {key} {f2.__name__}")
119 # d[key] = f2
120 #@+node:ekr.20140222064735.16702: *5* vc.create_motion_dispatch_d
121 #@@nobeautify
123 def create_motion_dispatch_d(self):
124 """
125 Return the dispatch dict for motions.
126 Keys are strokes, values are methods.
127 """
128 d = {
129 '^': self.vim_caret,
130 '~': None,
131 '*': None,
132 '@': None,
133 '|': None,
134 '{': None,
135 '}': None,
136 '[': None,
137 ']': None,
138 ':': None, # Not a motion.
139 ',': None,
140 '$': self.vim_dollar,
141 '>': None,
142 '<': None,
143 '-': None,
144 '#': None,
145 '(': None,
146 ')': None,
147 '%': None,
148 '.': None, # Not a motion.
149 '+': None,
150 '?': self.vim_question,
151 '"': None,
152 '`': None,
153 '\n': self.vim_return,
154 ';': None,
155 '/': self.vim_slash,
156 '_': None,
157 # Digits.
158 '0': self.vim_0, # Only 0 starts a motion.
159 # Uppercase letters.
160 'A': None, # vim doesn't enter insert mode.
161 'B': None,
162 'C': None,
163 'D': None,
164 'E': None,
165 'F': self.vim_F,
166 'G': self.vim_G,
167 'H': None,
168 'I': None,
169 'J': None,
170 'K': None,
171 'L': None,
172 'M': None,
173 'N': None,
174 'O': None, # vim doesn't enter insert mode.
175 'P': None,
176 'R': None,
177 'S': None,
178 'T': self.vim_T,
179 'U': None,
180 'V': None,
181 'W': None,
182 'X': None,
183 'Y': self.vim_Y, # Yank Leo outline.
184 'Z': None,
185 # Lowercase letters...
186 'a': None, # vim doesn't enter insert mode.
187 'b': self.vim_b,
188 # 'c': self.vim_c,
189 'd': None, # Not valid.
190 'e': self.vim_e,
191 'f': self.vim_f,
192 'g': self.vim_g,
193 'h': self.vim_h,
194 'i': None, # vim doesn't enter insert mode.
195 'j': self.vim_j,
196 'k': self.vim_k,
197 'l': self.vim_l,
198 # 'm': self.vim_m,
199 # 'n': self.vim_n,
200 'o': None, # vim doesn't enter insert mode.
201 # 'p': self.vim_p,
202 # 'q': self.vim_q,
203 # 'r': self.vim_r,
204 # 's': self.vim_s,
205 't': self.vim_t,
206 # 'u': self.vim_u,
207 # 'v': self.vim_v,
208 'w': self.vim_w,
209 # 'x': self.vim_x,
210 # 'y': self.vim_y,
211 # 'z': self.vim_z,
212 }
213 return d
214 #@+node:ekr.20131111061547.16460: *5* vc.create_normal_dispatch_d
215 def create_normal_dispatch_d(self):
216 """
217 Return the dispatch dict for normal mode.
218 Keys are strokes, values are methods.
219 """
220 d = {
221 # Vim hard-coded control characters...
222 # 'Ctrl+r': self.vim_ctrl_r,
223 '^': self.vim_caret,
224 '~': None,
225 '*': self.vim_star,
226 '@': None,
227 '|': None,
228 '{': None,
229 '}': None,
230 '[': None,
231 ']': None,
232 ':': self.vim_colon,
233 ',': None,
234 '$': self.vim_dollar,
235 '>': None,
236 '<': None,
237 '-': None,
238 '#': self.vim_pound,
239 '(': None,
240 ')': None,
241 '%': None,
242 '.': self.vim_dot,
243 '+': None,
244 '?': self.vim_question,
245 '"': None,
246 '`': None,
247 '\n': self.vim_return,
248 ';': None,
249 '/': self.vim_slash,
250 '_': None,
251 # Digits.
252 '0': self.vim_0,
253 '1': self.vim_digits,
254 '2': self.vim_digits,
255 '3': self.vim_digits,
256 '4': self.vim_digits,
257 '5': self.vim_digits,
258 '6': self.vim_digits,
259 '7': self.vim_digits,
260 '8': self.vim_digits,
261 '9': self.vim_digits,
262 # Uppercase letters.
263 'A': self.vim_A,
264 'B': None,
265 'C': None,
266 'D': None,
267 'E': None,
268 'F': self.vim_F,
269 'G': self.vim_G,
270 'H': None,
271 'I': None,
272 'J': None,
273 'K': None,
274 'L': None,
275 'M': None,
276 'N': self.vim_N,
277 'O': self.vim_O,
278 'P': self.vim_P, # Paste *outline*
279 'R': None,
280 'S': None,
281 'T': self.vim_T,
282 'U': None,
283 'V': self.vim_V,
284 'W': None,
285 'X': None,
286 'Y': self.vim_Y,
287 'Z': None,
288 # Lowercase letters...
289 'a': self.vim_a,
290 'b': self.vim_b,
291 'c': self.vim_c,
292 'd': self.vim_d,
293 'e': self.vim_e,
294 'f': self.vim_f,
295 'g': self.vim_g,
296 'h': self.vim_h,
297 'i': self.vim_i,
298 'j': self.vim_j,
299 'k': self.vim_k,
300 'l': self.vim_l,
301 'm': self.vim_m,
302 'n': self.vim_n,
303 'o': self.vim_o,
304 'p': self.vim_p,
305 'q': self.vim_q,
306 'r': self.vim_r,
307 's': self.vim_s,
308 't': self.vim_t,
309 'u': self.vim_u,
310 'v': self.vim_v,
311 'w': self.vim_w,
312 'x': self.vim_x,
313 'y': self.vim_y,
314 'z': self.vim_z,
315 }
316 return d
317 #@+node:ekr.20140222064735.16630: *5* vc.create_vis_dispatch_d
318 def create_vis_dispatch_d(self):
319 """
320 Create a dispatch dict for visual mode.
321 Keys are strokes, values are methods.
322 """
323 d = {
324 '\n': self.vim_return,
325 ' ': self.vim_l,
326 # Terminating commands...
327 'Escape': self.vis_escape,
328 'J': self.vis_J,
329 'c': self.vis_c,
330 'd': self.vis_d,
331 'u': self.vis_u,
332 'v': self.vis_v,
333 'y': self.vis_y,
334 # Motions...
335 '0': self.vim_0,
336 '1': self.vim_digits,
337 '2': self.vim_digits,
338 '3': self.vim_digits,
339 '4': self.vim_digits,
340 '5': self.vim_digits,
341 '6': self.vim_digits,
342 '7': self.vim_digits,
343 '8': self.vim_digits,
344 '9': self.vim_digits,
345 'F': self.vim_F,
346 'G': self.vim_G,
347 'T': self.vim_T,
348 'Y': self.vim_Y,
349 '^': self.vim_caret,
350 'b': self.vim_b,
351 '$': self.vim_dollar,
352 'e': self.vim_e,
353 'f': self.vim_f,
354 'g': self.vim_g,
355 'h': self.vim_h,
356 'j': self.vim_j,
357 'k': self.vim_k,
358 'l': self.vim_l,
359 'n': self.vim_n,
360 '?': self.vim_question,
361 '/': self.vim_slash,
362 't': self.vim_t,
363 'V': self.vim_V,
364 'w': self.vim_w,
365 }
366 return d
367 #@+node:ekr.20140805130800.18161: *5* vc.create_arrow_d
368 def create_arrow_d(self):
369 """Return a dict binding *all* arrows to self.arrow."""
370 d = {}
371 for arrow in ('Left', 'Right', 'Up', 'Down'):
372 for mod in ('',
373 'Alt+', 'Alt+Ctrl', 'Alt+Ctrl+Shift',
374 'Ctrl+', 'Shift+', 'Ctrl+Shift+'
375 ):
376 d[mod + arrow] = self.vim_arrow
377 return d
378 #@+node:ekr.20140804222959.18930: *4* vc.finishCreate
379 def finishCreate(self):
380 """Complete the initialization for the VimCommands class."""
381 # Set the widget for set_border.
382 c = self.c
383 if c.vim_mode:
384 # g.registerHandler('idle',self.on_idle)
385 try:
386 # Be careful: c.frame or c.frame.body may not exist in some gui's.
387 self.w = self.c.frame.body.wrapper
388 except Exception:
389 self.w = None
390 if c.config.getBool('vim-trainer-mode', default=False):
391 self.toggle_vim_trainer_mode()
392 #@+node:ekr.20140803220119.18103: *4* vc.init helpers
393 # Every ivar of this class must be initied in exactly one init helper.
394 #@+node:ekr.20140803220119.18104: *5* vc.init_dot_ivars
395 def init_dot_ivars(self):
396 """Init all dot-related ivars."""
397 self.in_dot = False # True if we are executing the dot command.
398 self.dot_list = [] # This list is preserved across commands.
399 self.old_dot_list = [] # The dot_list saved at the start of visual mode.
400 #@+node:ekr.20140803220119.18109: *5* vc.init_constant_ivars
401 def init_constant_ivars(self):
402 """Init ivars whose values never change."""
403 # List of printable characters
404 self.chars = [ch for ch in string.printable if 32 <= ord(ch) < 128]
405 # List of register names.
406 self.register_names = string.ascii_letters
407 #@+node:ekr.20140803220119.18106: *5* vc.init_state_ivars
408 def init_state_ivars(self):
409 """Init all ivars related to command state."""
410 self.ch = None # The incoming character.
411 self.command_i = None # The offset into the text at the start of a command.
412 self.command_list = [] # The list of all characters seen in this command.
413 self.command_n = None # The repeat count in effect at the start of a command.
414 self.command_w = None # The widget in effect at the start of a command.
415 self.event = None # The event for the current key.
416 self.extend = False # True: extending selection.
417 self.handler = self.do_normal_mode # Use the handler for normal mode.
418 self.in_command = False # True: we have seen some command characters.
419 self.in_motion = False # True if parsing an *inner* motion, the 2j in d2j.
420 self.motion_func = None # The callback handler to execute after executing an inner motion.
421 self.motion_i = None # The offset into the text at the start of a motion.
422 self.n1 = 1 # The first repeat count.
423 self.n = 1 # The second repeat count.
424 self.n1_seen = False # True if self.n1 has been set.
425 self.next_func = None # The continuation of a multi-character command.
426 self.old_sel = None # The selection range at the start of a command.
427 self.repeat_list = [] # The characters of the current repeat count.
428 # The value returned by do_key().
429 # Handlers set this to False to tell k.masterKeyHandler to handle the key.
430 self.return_value = True
431 self.state = 'normal' # in ('normal','insert','visual',)
432 self.stroke = None # The incoming stroke.
433 self.visual_line_flag = False # True: in visual-line state.
434 self.vis_mode_i = None # The insertion point at the start of visual mode.
435 self.vis_mode_w = None # The widget in effect at the start of visual mode.
436 #@+node:ekr.20140803220119.18107: *5* vc.init_persistent_ivars
437 def init_persistent_ivars(self):
438 """Init ivars that are never re-inited."""
439 c = self.c
440 # The widget that has focus when a ':' command begins. May be None.
441 self.colon_w = None
442 # True: allow f,F,h,l,t,T,x to cross line boundaries.
443 self.cross_lines = c.config.getBool('vim-crosses-lines', default=True)
444 self.register_d = {} # Keys are letters; values are strings.
445 # The stroke ('/' or '?') that starts a vim search command.
446 self.search_stroke = None
447 # True: in vim-training mode: Mouse clicks and arrows are disabled.
448 self.trainer = False
449 # The present widget. c.frame.body.wrapper is a QTextBrowser.
450 self.w = None
451 # False if the .leo file's change indicator should be
452 # Cleared after doing the j,j abbreviation.
453 self.j_changed = True
454 #@+node:ekr.20140802225657.18023: *3* vc.acceptance methods
455 # All key handlers must end with a call to an acceptance method.
456 #
457 # Acceptance methods set the return_value ivar, which becomes the value
458 # returned to k.masterKeyHandler by c.vimCommands.do_key:
459 #
460 # - True: k.masterKeyHandler returns.
461 # Vim mode has completely handled the key.
462 #
463 # - False: k.masterKeyHander handles the key.
465 #@+node:ekr.20140803220119.18097: *4* direct acceptance methods
466 #@+node:ekr.20140802225657.18031: *5* vc.accept
467 def accept(self, add_to_dot=True, handler=None):
468 """
469 Accept the present stroke.
470 Optionally, this can set the dot or change self.handler.
471 This can be a no-op, but even then it is recommended.
472 """
473 self.do_trace()
474 if handler:
475 if self.in_motion:
476 # Tricky: queue up do_inner_motion() to continue the motion.
477 self.handler = self.do_inner_motion
478 self.next_func = handler
479 else:
480 # Queue the outer handler as usual.
481 self.handler = handler
482 if add_to_dot:
483 self.add_to_dot()
484 self.show_status()
485 self.return_value = True
486 #@+node:ekr.20140802225657.18024: *5* vc.delegate
487 def delegate(self):
488 """Delegate the present key to k.masterKeyHandler."""
489 self.do_trace()
490 self.show_status()
491 self.return_value = False
492 #@+node:ekr.20140222064735.16631: *5* vc.done
493 def done(self, add_to_dot=True, return_value=True, set_dot=True, stroke=None):
494 """Complete a command, preserving text and optionally updating the dot."""
495 self.do_trace()
496 if self.state == 'visual':
497 self.handler = self.do_visual_mode # A major bug fix.
498 if set_dot:
499 stroke2 = stroke or self.stroke if add_to_dot else None
500 self.compute_dot(stroke2)
501 self.command_list = []
502 self.show_status()
503 self.return_value = True
504 else:
505 if set_dot:
506 stroke2 = stroke or self.stroke if add_to_dot else None
507 self.compute_dot(stroke2)
508 # Undoably preserve any changes to the body.
509 self.save_body()
510 # Clear all state, enter normal mode & show the status.
511 if self.in_motion:
512 # Do *not* change in_motion!
513 self.next_func = None
514 else:
515 self.init_state_ivars()
516 self.show_status()
517 self.return_value = return_value
518 #@+node:ekr.20140802225657.18025: *5* vc.ignore
519 def ignore(self):
520 """
521 Ignore the present key without passing it to k.masterKeyHandler.
523 **Important**: all code now calls quit() after ignore().
524 This code could do that, but calling quit() emphasizes what happens.
525 """
526 self.do_trace()
527 aList = [z.stroke if isinstance(z, VimEvent) else z for z in self.command_list]
528 aList = [show_stroke(self.c.k.stroke2char(z)) for z in aList]
529 g.es_print(
530 f"ignoring {self.stroke} "
531 f"in {self.state} mode "
532 f"after {''.join(aList)}",
533 color='blue',
534 )
535 # This is a surprisingly helpful trace.
536 # g.trace(g.callers())
537 self.show_status()
538 self.return_value = True
539 #@+node:ekr.20140806204042.18115: *5* vc.not_ready
540 def not_ready(self):
541 """Print a not ready message and quit."""
542 g.es('not ready', g.callers(1))
543 self.quit()
544 #@+node:ekr.20160918060654.1: *5* vc.on_activate
545 def on_activate(self):
546 """Handle an activate event."""
547 # Fix #270: Vim keys don't always work after double Alt+Tab.
548 self.quit()
549 self.show_status()
550 # This seems not to be needed.
551 # self.c.k.keyboardQuit()
552 #@+node:ekr.20140802120757.17999: *5* vc.quit
553 def quit(self):
554 """
555 Abort any present command.
556 Don't set the dot and enter normal mode.
557 """
558 self.do_trace()
559 # Undoably preserve any changes to the body.
560 self.save_body()
561 self.init_state_ivars()
562 self.state = 'normal'
563 self.show_status()
564 self.return_value = True
565 #@+node:ekr.20140807070500.18163: *5* vc.reset
566 def reset(self, setFocus):
567 """
568 Called from k.keyboardQuit when the user types Ctrl-G (setFocus = True).
569 Also called when the user clicks the mouse (setFocus = False).
570 """
571 self.do_trace()
572 if setFocus:
573 # A hard reset.
574 self.quit()
575 elif 0:
576 # Do *not* change state!
577 g.trace('no change! state:', self.state, g.callers())
578 #@+node:ekr.20140802225657.18034: *4* indirect acceptance methods
579 #@+node:ekr.20140222064735.16709: *5* vc.begin_insert_mode
580 def begin_insert_mode(self, i=None, w=None):
581 """Common code for beginning insert mode."""
582 self.do_trace()
583 # c = self.c
584 if not w:
585 w = self.w
586 self.state = 'insert'
587 self.command_i = w.getInsertPoint() if i is None else i
588 self.command_w = w
589 if 1:
590 # Add the starting character to the dot, but don't show it.
591 self.accept(handler=self.do_insert_mode, add_to_dot=False)
592 self.show_status()
593 self.add_to_dot()
594 else:
595 self.accept(handler=self.do_insert_mode, add_to_dot=True)
596 #@+node:ekr.20140222064735.16706: *5* vc.begin_motion
597 def begin_motion(self, motion_func):
598 """Start an inner motion."""
599 self.do_trace()
600 w = self.w
601 self.command_w = w
602 self.in_motion = True
603 self.motion_func = motion_func
604 self.motion_i = w.getInsertPoint()
605 self.n = 1
606 if self.stroke in '123456789':
607 self.vim_digits()
608 else:
609 self.do_inner_motion()
610 #@+node:ekr.20140801121720.18076: *5* vc.end_insert_mode
611 def end_insert_mode(self):
612 """End an insert mode started with the a,A,i,o and O commands."""
613 # Called from vim_esc.
614 self.do_trace()
615 w = self.w
616 s = w.getAllText()
617 i1 = self.command_i
618 i2 = w.getInsertPoint()
619 if i1 > i2:
620 i1, i2 = i2, i1
621 s2 = s[i1:i2]
622 if self.n1 > 1:
623 s3 = s2 * (self.n1 - 1)
624 w.insert(i2, s3)
625 for stroke in s2:
626 self.add_to_dot(stroke)
627 self.done()
628 #@+node:ekr.20140222064735.16629: *5* vc.vim_digits
629 def vim_digits(self):
630 """Handle a digit that starts an outer repeat count."""
631 self.do_trace()
632 self.repeat_list = []
633 self.repeat_list.append(self.stroke)
634 self.accept(handler=self.vim_digits_2)
636 def vim_digits_2(self):
637 self.do_trace()
638 if self.stroke in '0123456789':
639 self.repeat_list.append(self.stroke)
640 self.accept(handler=self.vim_digits_2)
641 else:
642 # Set self.n1 before self.n, so that inner motions won't repeat
643 # until the end of vim mode.
644 try:
645 n = int(''.join(self.repeat_list))
646 except Exception:
647 n = 1
648 if self.n1_seen:
649 self.n = n
650 else:
651 self.n1_seen = True
652 self.n1 = n
653 # Don't clear the repeat_list here.
654 # The ending character may not be valid,
655 if self.in_motion:
656 # Handle the stroke that ended the repeat count.
657 self.do_inner_motion(restart=True)
658 else:
659 # Restart the command.
660 self.do_normal_mode()
661 #@+node:ekr.20131111061547.16467: *3* vc.commands
662 #@+node:ekr.20140805130800.18158: *4* vc.arrow...
663 def vim_arrow(self):
664 """
665 Handle all non-Alt arrows in any mode.
666 This method attempts to leave focus unchanged.
667 """
668 # pylint: disable=maybe-no-member
669 s = self.stroke.s if g.isStroke(self.stroke) else self.stroke
670 if s.find('Alt+') > -1:
671 # Any Alt key changes c.p.
672 self.quit()
673 self.delegate()
674 elif self.trainer:
675 # Ignore all non-Alt arrow keys in text widgets.
676 if self.is_text_wrapper(self.w):
677 self.ignore()
678 self.quit()
679 else:
680 # Allow plain-arrow keys work in the outline pane.
681 self.delegate()
682 else:
683 # Delegate all arrow keys.
684 self.delegate()
685 #@+node:ekr.20140806075456.18152: *4* vc.vim_return
686 def vim_return(self):
687 """
688 Handle a return key, regardless of mode.
689 In the body pane only, it has special meaning.
690 """
691 if self.w:
692 if self.is_body(self.w):
693 if self.state == 'normal':
694 # Entering insert mode is confusing for real vim users.
695 # It should advance the cursor to the next line.
696 self.vim_j()
697 elif self.state == 'visual':
698 # same as v
699 self.stroke = 'v'
700 self.vis_v()
701 else:
702 self.done()
703 else:
704 self.delegate()
705 else:
706 self.delegate()
707 #@+node:ekr.20140222064735.16634: *4* vc.vim...(normal mode)
708 #@+node:ekr.20140810181832.18220: *5* vc.update_dot_before_search
709 def update_dot_before_search(self, find_pattern, change_pattern):
710 """
711 A callback that updates the dot just before searching.
712 At present, this **leaves the dot unchanged**.
713 Use the n or N commands to repeat searches,
714 """
715 self.command_list = []
716 if 0: # Don't do anything else!
718 # Don't use add_to_dot(): it updates self.command_list.
720 def add(stroke):
721 event = VimEvent(c=self.c, char=stroke, stroke=stroke, w=self.w)
722 self.dot_list.append(event)
724 if self.in_dot:
725 # Don't set the dot again.
726 return
727 if self.search_stroke is None:
728 # We didn't start the search with / or ?
729 return
730 if 1:
731 # This is all we can do until there is a substitution command.
732 self.change_pattern = change_pattern # Not used at present.
733 add(self.search_stroke)
734 for ch in find_pattern:
735 add(ch)
736 self.search_stroke = None
737 else:
738 # We could do this is we had a substitution command.
739 if change_pattern is None:
740 # A search pattern.
741 add(self.search_stroke)
742 for ch in find_pattern:
743 add(ch)
744 else:
745 # A substitution: :%s/find_pattern/change_pattern/g
746 for s in (":%s/", find_pattern, "/", change_pattern, "/g"):
747 for ch in s:
748 add(ch)
749 self.search_stroke = None
750 #@+node:ekr.20140811044942.18243: *5* vc.update_selection_after_search
751 def update_selection_after_search(self):
752 """
753 Extend visual mode's selection after a search.
754 Called from leoFind.show_success.
755 """
756 if self.state == 'visual':
757 w = self.w
758 if w == g.app.gui.get_focus():
759 if self.visual_line_flag:
760 self.visual_line_helper()
761 else:
762 i = w.getInsertPoint()
763 w.setSelectionRange(self.vis_mode_i, i, insert=i)
764 else:
765 g.trace('Search has changed nodes.')
766 #@+node:ekr.20140221085636.16691: *5* vc.vim_0
767 def vim_0(self):
768 """Handle zero, either the '0' command or part of a repeat count."""
769 if self.is_text_wrapper(self.w):
770 if self.repeat_list:
771 self.vim_digits()
772 else:
773 if self.state == 'visual':
774 self.do('beginning-of-line-extend-selection')
775 else:
776 self.do('beginning-of-line')
777 self.done()
778 elif self.in_tree(self.w):
779 self.do('goto-first-visible-node')
780 self.done()
781 else:
782 self.quit()
783 #@+node:ekr.20140220134748.16614: *5* vc.vim_a
784 def vim_a(self):
785 """Append text after the cursor N times."""
786 if self.in_tree(self.w):
787 c = self.c
788 c.bodyWantsFocusNow()
789 self.w = w = c.frame.body.wrapper
790 else:
791 w = self.w
792 if self.is_text_wrapper(w):
793 self.do('forward-char')
794 self.begin_insert_mode()
795 else:
796 self.quit()
797 #@+node:ekr.20140730175636.17981: *5* vc.vim_A
798 def vim_A(self):
799 """Append text at the end the line N times."""
800 if self.in_tree(self.w):
801 c = self.c
802 c.bodyWantsFocusNow()
803 self.w = w = c.frame.body.wrapper
804 else:
805 w = self.w
806 if self.is_text_wrapper(w):
807 self.do('end-of-line')
808 self.begin_insert_mode()
809 else:
810 self.quit()
811 #@+node:ekr.20140220134748.16618: *5* vc.vim_b
812 def vim_b(self):
813 """N words backward."""
814 if self.is_text_wrapper(self.w):
815 for z in range(self.n1 * self.n):
816 if self.state == 'visual':
817 self.do('back-word-extend-selection')
818 else:
819 self.do('back-word')
820 self.done()
821 else:
822 self.quit()
823 #@+node:ekr.20140220134748.16619: *5* vc.vim_c (to do)
824 def vim_c(self):
825 """
826 N cc change N lines
827 N c{motion} change the text that is moved over with {motion}
828 VIS c change the highlighted text
829 """
830 self.not_ready()
831 # self.accept(handler=self.vim_c2)
833 def vim_c2(self):
834 if self.is_text_wrapper(self.w):
835 g.trace(self.stroke)
836 self.done()
837 else:
838 self.quit()
839 #@+node:ekr.20140807152406.18128: *5* vc.vim_caret
840 def vim_caret(self):
841 """Move to start of line."""
842 if self.is_text_wrapper(self.w):
843 if self.state == 'visual':
844 self.do('back-to-home-extend-selection')
845 else:
846 self.do('back-to-home')
847 self.done()
848 else:
849 self.quit()
850 #@+node:ekr.20140730175636.17983: *5* vc.vim_colon
851 def vim_colon(self):
852 """Enter the minibuffer."""
853 k = self.k
854 self.colon_w = self.w # A scratch ivar, for :gt & gT commands.
855 self.quit()
856 event = VimEvent(c=self.c, char=':', stroke='colon', w=self.w)
857 k.fullCommand(event=event)
858 k.extendLabel(':')
859 #@+node:ekr.20140806123540.18159: *5* vc.vim_comma (not used)
860 # This was an attempt to be clever: two commas would switch to insert mode.
862 def vim_comma(self):
863 """Handle a comma in normal mode."""
864 if self.is_text_wrapper(self.w):
865 self.accept(handler=self.vim_comma2)
866 else:
867 self.quit()
869 def vim_comma2(self):
870 if self.is_text_wrapper(self.w):
871 if self.stroke == 'comma':
872 self.begin_insert_mode()
873 else:
874 self.done()
875 else:
876 self.quit()
877 #@+node:ekr.20140730175636.17992: *5* vc.vim_ctrl_r
878 def vim_ctrl_r(self):
879 """Redo the last command."""
880 self.c.undoer.redo()
881 self.done()
882 #@+node:ekr.20131111171616.16498: *5* vc.vim_d & helpers
883 def vim_d(self):
884 """
885 N dd delete N lines
886 d{motion} delete the text that is moved over with {motion}
887 """
888 if self.is_text_wrapper(self.w):
889 self.n = 1
890 self.accept(handler=self.vim_d2)
891 else:
892 self.quit()
893 #@+node:ekr.20140811175537.18146: *6* vc.vim_d2
894 def vim_d2(self):
895 """Handle the second stroke of the d command."""
896 w = self.w
897 if self.is_text_wrapper(w):
898 if self.stroke == 'd':
899 i = w.getInsertPoint()
900 for z in range(self.n1 * self.n):
901 # It's simplest just to get the text again.
902 s = w.getAllText()
903 i, j = g.getLine(s, i)
904 # Special case for end of buffer only for n == 1.
905 # This is exactly how vim works.
906 if self.n1 * self.n == 1 and i == j == len(s):
907 i = max(0, i - 1)
908 g.app.gui.replaceClipboardWith(s[i:j])
909 w.delete(i, j)
910 self.done()
911 elif self.stroke == 'i':
912 self.accept(handler=self.vim_di)
913 else:
914 self.d_stroke = self.stroke # A scratch var.
915 self.begin_motion(self.vim_d3)
916 else:
917 self.quit()
918 #@+node:ekr.20140811175537.18147: *6* vc.vim_d3
919 def vim_d3(self):
920 """Complete the d command after the cursor has moved."""
921 # d2w doesn't extend to line. d2j does.
922 w = self.w
923 if self.is_text_wrapper(w):
924 extend_to_line = self.d_stroke in 'jk'
925 s = w.getAllText()
926 i1, i2 = self.motion_i, w.getInsertPoint()
927 if i1 == i2:
928 pass
929 elif i1 < i2:
930 for z in range(self.n1 * self.n):
931 if extend_to_line:
932 i2 = self.to_eol(s, i2)
933 if i2 < len(s) and s[i2] == '\n':
934 i2 += 1
935 g.app.gui.replaceClipboardWith(s[i1:i2])
936 w.delete(i1, i2)
937 else: # i1 > i2
938 i1, i2 = i2, i1
939 for z in range(self.n1 * self.n):
940 if extend_to_line:
941 i1 = self.to_bol(s, i1)
942 g.app.gui.replaceClipboardWith(s[i1:i2])
943 w.delete(i1, i2)
944 self.done()
945 else:
946 self.quit()
947 #@+node:ekr.20140811175537.18145: *6* vc.vim_di
948 def vim_di(self):
949 """Handle delete inner commands."""
950 w = self.w
951 if self.is_text_wrapper(w):
952 if self.stroke == 'w':
953 # diw
954 self.do('extend-to-word')
955 g.app.gui.replaceClipboardWith(w.getSelectedText())
956 self.do('backward-delete-char')
957 self.done()
958 else:
959 self.ignore()
960 self.quit()
961 else:
962 self.quit()
963 #@+node:ekr.20140730175636.17991: *5* vc.vim_dollar
964 def vim_dollar(self):
965 """Move the cursor to the end of the line."""
966 if self.is_text_wrapper(self.w):
967 if self.state == 'visual':
968 self.do('end-of-line-extend-selection')
969 else:
970 self.do('end-of-line')
971 self.done()
972 else:
973 self.quit()
974 #@+node:ekr.20131111105746.16544: *5* vc.vim_dot
975 def vim_dot(self):
976 """Repeat the last command."""
977 if self.in_dot:
978 return
979 try:
980 self.in_dot = True
981 # Save the dot list.
982 self.old_dot_list = self.dot_list[:]
983 # Copy the list so it can't change in the loop.
984 for event in self.dot_list[:]:
985 # Only k.masterKeyHandler can insert characters!
986 #
987 # #1757: Create a LeoKeyEvent.
988 event = LeoKeyEvent(
989 binding=g.KeyStroke(event.stroke),
990 c=self.c,
991 char=event.char,
992 event=event,
993 w=self.w,
994 )
995 self.k.masterKeyHandler(event)
996 # For the dot list to be the old dot list, whatever happens.
997 self.command_list = self.old_dot_list[:]
998 self.dot_list = self.old_dot_list[:]
999 finally:
1000 self.in_dot = False
1001 self.done()
1002 #@+node:ekr.20140222064735.16623: *5* vc.vim_e
1003 def vim_e(self):
1004 """Forward to the end of the Nth word."""
1005 if self.is_text_wrapper(self.w):
1006 for z in range(self.n1 * self.n):
1007 if self.state == 'visual':
1008 self.do('forward-word-extend-selection')
1009 else:
1010 self.do('forward-word')
1011 self.done()
1012 elif self.in_tree(self.w):
1013 self.do('goto-last-visible-node')
1014 self.done()
1015 else:
1016 self.quit()
1017 #@+node:ekr.20140222064735.16632: *5* vc.vim_esc
1018 def vim_esc(self):
1019 """
1020 Handle Esc while accumulating a normal mode command.
1022 Esc terminates the a,A,i,o and O commands normally.
1023 Call self.end_insert command to support repeat counts
1024 such as 5a<lots of typing><esc>
1025 """
1026 if self.state == 'insert':
1027 self.end_insert_mode()
1028 elif self.state == 'visual':
1029 # Clear the selection and reset dot.
1030 self.vis_v()
1031 else:
1032 # self.done()
1033 self.quit() # It's helpful to clear everything.
1034 #@+node:ekr.20140222064735.16687: *5* vc.vim_F
1035 def vim_F(self):
1036 """Back to the Nth occurrence of <char>."""
1037 if self.is_text_wrapper(self.w):
1038 self.accept(handler=self.vim_F2)
1039 else:
1040 self.quit()
1042 def vim_F2(self):
1043 """Handle F <stroke>"""
1044 if self.is_text_wrapper(self.w):
1045 w = self.w
1046 s = w.getAllText()
1047 if s:
1048 i = i1 = w.getInsertPoint()
1049 match_i, n = None, self.n1 * self.n
1050 i -= 1 # Ensure progress
1051 while i >= 0:
1052 if s[i] == self.ch:
1053 match_i, n = i, n - 1
1054 if n == 0:
1055 break
1056 elif s[i] == '\n' and not self.cross_lines:
1057 break
1058 i -= 1
1059 if match_i is not None:
1060 for z in range(i1 - match_i):
1061 if self.state == 'visual':
1062 self.do('back-char-extend-selection')
1063 else:
1064 self.do('back-char')
1065 self.done()
1066 else:
1067 self.quit()
1068 #@+node:ekr.20140220134748.16620: *5* vc.vim_f
1069 def vim_f(self):
1070 """move past the Nth occurrence of <stroke>."""
1071 if self.is_text_wrapper(self.w):
1072 self.accept(handler=self.vim_f2)
1073 else:
1074 self.quit()
1076 def vim_f2(self):
1077 """Handle f <stroke>"""
1078 if self.is_text_wrapper(self.w):
1079 # ec = self.c.editCommands
1080 w = self.w
1081 s = w.getAllText()
1082 if s:
1083 i = i1 = w.getInsertPoint()
1084 match_i, n = None, self.n1 * self.n
1085 while i < len(s):
1086 if s[i] == self.ch:
1087 match_i, n = i, n - 1
1088 if n == 0:
1089 break
1090 elif s[i] == '\n' and not self.cross_lines:
1091 break
1092 i += 1
1093 if match_i is not None:
1094 for z in range(match_i - i1 + 1):
1095 if self.state == 'visual':
1096 self.do('forward-char-extend-selection')
1097 else:
1098 self.do('forward-char')
1099 self.done()
1100 else:
1101 self.quit()
1102 #@+node:ekr.20140803220119.18112: *5* vc.vim_G
1103 def vim_G(self):
1104 """Put the cursor on the last character of the file."""
1105 if self.is_text_wrapper(self.w):
1106 if self.state == 'visual':
1107 self.do('end-of-buffer-extend-selection')
1108 else:
1109 self.do('end-of-buffer')
1110 self.done()
1111 else:
1112 self.quit()
1113 #@+node:ekr.20140220134748.16621: *5* vc.vim_g
1114 def vim_g(self):
1115 """
1116 N ge backward to the end of the Nth word
1117 N gg goto line N (default: first line), on the first non-blank character
1118 gv start highlighting on previous visual area
1119 """
1120 if self.is_text_wrapper(self.w):
1121 self.accept(handler=self.vim_g2)
1122 else:
1123 self.quit()
1125 def vim_g2(self):
1126 if self.is_text_wrapper(self.w):
1127 # event = self.event
1128 w = self.w
1129 extend = self.state == 'visual'
1130 s = w.getAllText()
1131 i = w.getInsertPoint()
1132 if self.stroke == 'g':
1133 # Go to start of buffer.
1134 on_line = self.on_same_line(s, 0, i)
1135 if on_line and extend:
1136 self.do('back-to-home-extend-selection')
1137 elif on_line:
1138 self.do('back-to-home')
1139 elif extend:
1140 self.do('beginning-of-buffer-extend-selection')
1141 else:
1142 self.do('beginning-of-buffer')
1143 self.done()
1144 elif self.stroke == 'b':
1145 # go to beginning of line: like 0.
1146 if extend:
1147 self.do('beginning-of-line-extend-selection')
1148 else:
1149 self.do('beginning-of-line')
1150 self.done()
1151 elif self.stroke == 'e':
1152 # got to end of line: like $
1153 if self.state == 'visual':
1154 self.do('end-of-line-extend-selection')
1155 else:
1156 self.do('end-of-line')
1157 self.done()
1158 elif self.stroke == 'h':
1159 # go home: like ^.
1160 if extend:
1161 self.do('back-to-home-extend-selection')
1162 elif on_line:
1163 self.do('back-to-home')
1164 self.done()
1165 else:
1166 self.ignore()
1167 self.quit()
1168 else:
1169 self.quit()
1170 #@+node:ekr.20131111061547.16468: *5* vc.vim_h
1171 def vim_h(self):
1172 """Move the cursor left n chars, but not out of the present line."""
1173 if self.is_text_wrapper(self.w):
1174 w = self.w
1175 s = w.getAllText()
1176 i = w.getInsertPoint()
1177 if i == 0 or (i > 0 and s[i - 1] == '\n'):
1178 pass
1179 else:
1180 for z in range(self.n1 * self.n):
1181 if i > 0 and s[i - 1] != '\n':
1182 i -= 1
1183 if i == 0 or (i > 0 and s[i - 1] == '\n'):
1184 break # Don't go past present line.
1185 if self.state == 'visual':
1186 w.setSelectionRange(self.vis_mode_i, i, insert=i)
1187 else:
1188 w.setInsertPoint(i)
1189 self.done()
1190 elif self.in_tree(self.w):
1191 self.do('contract-or-go-left')
1192 self.done()
1193 else:
1194 self.quit()
1195 #@+node:ekr.20140222064735.16618: *5* vc.vim_i
1196 def vim_i(self):
1197 """Insert text before the cursor N times."""
1198 if self.in_tree(self.w):
1199 c = self.c
1200 c.bodyWantsFocusNow()
1201 self.w = w = c.frame.body.wrapper
1202 else:
1203 w = self.w
1204 if self.is_text_wrapper(w):
1205 self.begin_insert_mode()
1206 else:
1207 self.done()
1208 #@+node:ekr.20140220134748.16617: *5* vc.vim_j
1209 def vim_j(self):
1210 """N j Down n lines."""
1211 if self.is_text_wrapper(self.w):
1212 for z in range(self.n1 * self.n):
1213 if self.state == 'visual':
1214 self.do('next-line-extend-selection')
1215 else:
1216 self.do('next-line')
1217 self.done()
1218 elif self.in_tree(self.w):
1219 self.do('goto-next-visible')
1220 self.done()
1221 else:
1222 self.quit()
1223 #@+node:ekr.20140222064735.16628: *5* vc.vim_k
1224 def vim_k(self):
1225 """Cursor up N lines."""
1226 if self.is_text_wrapper(self.w):
1227 for z in range(self.n1 * self.n):
1228 if self.state == 'visual':
1229 self.do('previous-line-extend-selection')
1230 else:
1231 self.do('previous-line')
1232 self.done()
1233 elif self.in_tree(self.w):
1234 self.do('goto-prev-visible')
1235 self.done()
1236 else:
1237 self.quit()
1238 #@+node:ekr.20140222064735.16627: *5* vc.vim_l
1239 def vim_l(self):
1240 """Move the cursor right self.n chars, but not out of the present line."""
1241 if self.is_text_wrapper(self.w):
1242 w = self.w
1243 s = w.getAllText()
1244 i = w.getInsertPoint()
1245 if i >= len(s) or s[i] == '\n':
1246 pass
1247 else:
1248 for z in range(self.n1 * self.n):
1249 if i < len(s) and s[i] != '\n':
1250 i += 1
1251 if i >= len(s) or s[i] == '\n':
1252 break # Don't go past present line.
1253 if self.state == 'visual':
1254 w.setSelectionRange(self.vis_mode_i, i, insert=i)
1255 else:
1256 w.setInsertPoint(i)
1257 self.done()
1258 elif self.in_tree(self.w):
1259 self.do('expand-and-go-right')
1260 self.done()
1261 else:
1262 self.quit()
1263 #@+node:ekr.20131111171616.16497: *5* vc.vim_m (to do)
1264 def vim_m(self):
1265 """m<a-zA-Z> mark current position with mark."""
1266 self.not_ready()
1267 # self.accept(handler=self.vim_m2)
1269 def vim_m2(self):
1270 g.trace(self.stroke)
1271 self.done()
1272 #@+node:ekr.20140220134748.16625: *5* vc.vim_n
1273 def vim_n(self):
1274 """Repeat last search N times."""
1275 fc = self.c.findCommands
1276 fc.setup_ivars()
1277 old_node_only = fc.node_only
1278 fc.node_only = True
1279 for z in range(self.n1 * self.n):
1280 if not fc.findNext():
1281 break
1282 fc.node_only = old_node_only
1283 self.done()
1284 #@+node:ekr.20140823045819.18292: *5* vc.vim_N
1285 def vim_N(self):
1286 """Repeat last search N times (reversed)."""
1287 fc = self.c.findCommands
1288 fc.setup_ivars()
1289 old_node_only = fc.node_only
1290 old_reverse = fc.reverse
1291 fc.node_only = True
1292 fc.reverse = True
1293 for z in range(self.n1 * self.n):
1294 if not fc.findNext():
1295 break
1296 fc.node_only = old_node_only
1297 fc.reverse = old_reverse
1298 self.done()
1299 #@+node:ekr.20140222064735.16692: *5* vc.vim_O
1300 def vim_O(self):
1301 """Open a new line above the current line N times."""
1302 if self.in_tree(self.w):
1303 c = self.c
1304 c.bodyWantsFocusNow()
1305 self.w = c.frame.body.wrapper
1306 if self.is_text_wrapper(self.w):
1307 self.do(['beginning-of-line', 'insert-newline', 'back-char'])
1308 self.begin_insert_mode()
1309 else:
1310 self.quit()
1311 #@+node:ekr.20140222064735.16619: *5* vc.vim_o
1312 def vim_o(self):
1313 """Open a new line below the current line N times."""
1314 if self.in_tree(self.w):
1315 c = self.c
1316 c.bodyWantsFocusNow()
1317 self.w = w = c.frame.body.wrapper
1318 else:
1319 w = self.w
1320 if self.is_text_wrapper(w):
1321 self.do(['end-of-line', 'insert-newline'])
1322 self.begin_insert_mode()
1323 else:
1324 self.quit()
1325 #@+node:ekr.20140220134748.16622: *5* vc.vim_p
1326 def vim_p(self):
1327 """Paste after the cursor."""
1328 if self.in_tree(self.w):
1329 self.do('paste-node')
1330 self.done()
1331 elif self.is_text_wrapper(self.w):
1332 self.do('paste-text')
1333 self.done()
1334 else:
1335 self.quit()
1336 #@+node:ekr.20140807152406.18125: *5* vc.vim_P
1337 def vim_P(self):
1338 """Paste text at the cursor or paste a node before the present node."""
1339 if self.in_tree(self.w):
1340 self.do(['goto-prev-visible', 'paste-node'])
1341 self.done()
1342 elif self.is_text_wrapper(self.w):
1343 self.do(['back-char', 'paste-text'])
1344 self.done()
1345 else:
1346 self.quit()
1347 #@+node:ekr.20140808173212.18070: *5* vc.vim_pound
1348 def vim_pound(self):
1349 """Find previous occurance of word under the cursor."""
1350 # ec = self.c.editCommands
1351 w = self.w
1352 if self.is_text_wrapper(w):
1353 i1 = w.getInsertPoint()
1354 if not w.hasSelection():
1355 self.do('extend-to-word')
1356 if w.hasSelection():
1357 fc = self.c.findCommands
1358 s = w.getSelectedText()
1359 w.setSelectionRange(i1, i1)
1360 if not self.in_dot:
1361 self.dot_list.append(self.stroke)
1362 old_node_only = fc.node_only
1363 fc.reverse = True
1364 fc.find_text = s
1365 fc.findNext()
1366 fc.reverse = False
1367 fc.node_only = old_node_only
1368 self.done()
1369 else:
1370 self.quit()
1371 #@+node:ekr.20140220134748.16623: *5* vc.vim_q (registers)
1372 def vim_q(self):
1373 """
1374 q stop recording
1375 q<A-Z> record typed characters, appended to register <a-z>
1376 q<a-z> record typed characters into register <a-z>
1377 """
1378 self.not_ready()
1379 # self.accept(handler=self.vim_q2)
1381 def vim_q2(self):
1382 g.trace(self.stroke)
1383 # letters = string.ascii_letters
1384 self.done()
1385 #@+node:ekr.20140807152406.18127: *5* vc.vim_question
1386 def vim_question(self):
1387 """Begin a search."""
1388 if self.is_text_wrapper(self.w):
1389 fc = self.c.findCommands
1390 self.search_stroke = self.stroke # A scratch ivar for update_dot_before_search().
1391 fc.reverse = True
1392 fc.openFindTab(self.event)
1393 fc.ftm.clear_focus()
1394 old_node_only = fc.node_only
1395 # This returns immediately, before the actual search.
1396 # leoFind.show_success calls update_selection_after_search().
1397 fc.start_search1(self.event)
1398 fc.node_only = old_node_only
1399 self.done(add_to_dot=False, set_dot=False)
1400 else:
1401 self.quit()
1402 #@+node:ekr.20140220134748.16624: *5* vc.vim_r (to do)
1403 def vim_r(self):
1404 """Replace next N characters with <char>"""
1405 self.not_ready()
1406 # self.accept(handler=self.vim_r2)
1408 def vim_r2(self):
1409 g.trace(self.n, self.stroke)
1410 self.done()
1411 #@+node:ekr.20140222064735.16625: *5* vc.vim_redo (to do)
1412 def vim_redo(self):
1413 """N Ctrl-R redo last N changes"""
1414 self.not_ready()
1415 #@+node:ekr.20140222064735.16626: *5* vc.vim_s (to do)
1416 def vim_s(self):
1417 """Change N characters"""
1418 self.not_ready()
1419 # self.accept(handler=self.vim_s2)
1421 def vim_s2(self):
1422 g.trace(self.n, self.stroke)
1423 self.done()
1424 #@+node:ekr.20140222064735.16622: *5* vc.vim_slash
1425 def vim_slash(self):
1426 """Begin a search."""
1427 if self.is_text_wrapper(self.w):
1428 fc = self.c.findCommands
1429 self.search_stroke = self.stroke # A scratch ivar for update_dot_before_search().
1430 fc.reverse = False
1431 fc.openFindTab(self.event)
1432 fc.ftm.clear_focus()
1433 old_node_only = fc.node_only
1434 # This returns immediately, before the actual search.
1435 # leoFind.show_success calls update_selection_after_search().
1436 fc.start_search1(self.event)
1437 fc.node_only = old_node_only
1438 fc.reverse = False
1439 self.done(add_to_dot=False, set_dot=False)
1440 else:
1441 self.quit()
1442 #@+node:ekr.20140810210411.18239: *5* vc.vim_star
1443 def vim_star(self):
1444 """Find previous occurance of word under the cursor."""
1445 # ec = self.c.editCommands
1446 w = self.w
1447 if self.is_text_wrapper(w):
1448 i1 = w.getInsertPoint()
1449 if not w.hasSelection():
1450 self.do('extend-to-word')
1451 if w.hasSelection():
1452 fc = self.c.findCommands
1453 s = w.getSelectedText()
1454 w.setSelectionRange(i1, i1)
1455 if not self.in_dot:
1456 self.dot_list.append(self.stroke)
1457 old_node_only = fc.node_only
1458 fc.reverse = False
1459 fc.find_text = s
1460 fc.findNext()
1461 fc.node_only = old_node_only
1462 self.done()
1463 else:
1464 self.quit()
1465 #@+node:ekr.20140222064735.16620: *5* vc.vim_t
1466 def vim_t(self):
1467 """Move before the Nth occurrence of <char> to the right."""
1468 if self.is_text_wrapper(self.w):
1469 self.accept(handler=self.vim_t2)
1470 else:
1471 self.quit()
1473 def vim_t2(self):
1474 """Handle t <stroke>"""
1475 if self.is_text_wrapper(self.w):
1476 # ec = self.c.editCommands
1477 w = self.w
1478 s = w.getAllText()
1479 if s:
1480 i = i1 = w.getInsertPoint()
1481 # ensure progress:
1482 if i < len(s) and s[i] == self.ch:
1483 i += 1
1484 match_i, n = None, self.n1 * self.n
1485 while i < len(s):
1486 if s[i] == self.ch:
1487 match_i, n = i, n - 1
1488 if n == 0:
1489 break
1490 elif s[i] == '\n' and not self.cross_lines:
1491 break
1492 i += 1
1493 if match_i is not None:
1494 for z in range(match_i - i1):
1495 if self.state == 'visual':
1496 self.do('forward-char-extend-selection')
1497 else:
1498 self.do('forward-char')
1499 self.done()
1500 else:
1501 self.quit()
1502 #@+node:ekr.20140222064735.16686: *5* vc.vim_T
1503 def vim_T(self):
1504 """Back before the Nth occurrence of <char>."""
1505 if self.is_text_wrapper(self.w):
1506 self.accept(handler=self.vim_T2)
1507 else:
1508 self.quit()
1510 def vim_T2(self):
1511 """Handle T <stroke>"""
1512 if self.is_text_wrapper(self.w):
1513 # ec = self.c.editCommands
1514 w = self.w
1515 s = w.getAllText()
1516 if s:
1517 i = i1 = w.getInsertPoint()
1518 match_i, n = None, self.n1 * self.n
1519 i -= 1
1520 if i >= 0 and s[i] == self.ch:
1521 i -= 1
1522 while i >= 0:
1523 if s[i] == self.ch:
1524 match_i, n = i, n - 1
1525 if n == 0:
1526 break
1527 elif s[i] == '\n' and not self.cross_lines:
1528 break
1529 i -= 1
1530 if match_i is not None:
1531 for z in range(i1 - match_i - 1):
1532 if self.state == 'visual':
1533 self.do('back-char-extend-selection')
1534 else:
1535 self.do('back-char')
1536 self.done()
1537 else:
1538 self.quit()
1539 #@+node:ekr.20140220134748.16626: *5* vc.vim_u
1540 def vim_u(self):
1541 """U undo the last command."""
1542 self.c.undoer.undo()
1543 self.quit()
1544 #@+node:ekr.20140220134748.16627: *5* vc.vim_v
1545 def vim_v(self):
1546 """Start visual mode."""
1547 if self.n1_seen:
1548 self.ignore()
1549 self.quit()
1550 # self.beep('%sv not valid' % self.n1)
1551 # self.done()
1552 elif self.is_text_wrapper(self.w):
1553 # Enter visual mode.
1554 self.vis_mode_w = w = self.w
1555 self.vis_mode_i = w.getInsertPoint()
1556 self.state = 'visual'
1557 # Save the dot list in case v terminates visual mode.
1558 self.old_dot_list = self.dot_list[:]
1559 self.accept(add_to_dot=False, handler=self.do_visual_mode)
1560 else:
1561 self.quit()
1562 #@+node:ekr.20140811110221.18250: *5* vc.vim_V
1563 def vim_V(self):
1564 """Visually select line."""
1565 if self.is_text_wrapper(self.w):
1566 if self.state == 'visual':
1567 self.visual_line_flag = not self.visual_line_flag
1568 if self.visual_line_flag:
1569 # Switch visual mode to visual-line mode.
1570 # do_visual_mode extends the selection.
1571 # pylint: disable=unnecessary-pass
1572 pass
1573 else:
1574 # End visual mode.
1575 self.quit()
1576 else:
1577 # Enter visual line mode.
1578 self.vis_mode_w = w = self.w
1579 self.vis_mode_i = w.getInsertPoint()
1580 self.state = 'visual'
1581 self.visual_line_flag = True
1582 bx = 'beginning-of-line'
1583 ex = 'end-of-line-extend-selection'
1584 self.do([bx, ex])
1585 self.done()
1586 else:
1587 self.quit()
1588 #@+node:ekr.20140222064735.16624: *5* vc.vim_w
1589 def vim_w(self):
1590 """N words forward."""
1591 if self.is_text_wrapper(self.w):
1592 for z in range(self.n1 * self.n):
1593 if self.state == 'visual':
1594 self.do('forward-word-extend-selection')
1595 else:
1596 self.do('forward-word')
1597 self.done()
1598 else:
1599 self.quit()
1600 #@+node:ekr.20140220134748.16629: *5* vc.vim_x
1601 def vim_x(self):
1602 """
1603 Works like Del if there is a character after the cursor.
1604 Works like Backspace otherwise.
1605 """
1606 w = self.w
1607 if self.is_text_wrapper(w):
1608 delete_flag = False
1609 for z in range(self.n1 * self.n):
1610 # It's simplest just to get the text again.
1611 s = w.getAllText()
1612 i = w.getInsertPoint()
1613 if i >= 0:
1614 if self.cross_lines or s[i] != '\n':
1615 w.delete(i, i + 1)
1616 delete_flag = True
1617 else:
1618 break
1619 # Vim works exactly this way:
1620 # backspace over one character, regardless of count,
1621 # if nothing has been deleted so far.
1622 if not delete_flag:
1623 s = w.getAllText()
1624 i = w.getInsertPoint()
1625 if i > 0 and (self.cross_lines or s[i - 1] != '\n'):
1626 w.delete(i - 1, i)
1627 self.done()
1628 else:
1629 self.quit()
1630 #@+node:ekr.20140220134748.16630: *5* vc.vim_y
1631 def vim_y(self):
1632 """
1633 N yy yank N lines
1634 N y{motion} yank the text moved over with {motion}
1635 """
1636 if self.is_text_wrapper(self.w):
1637 self.accept(handler=self.vim_y2)
1638 elif self.in_tree(self.w):
1639 # Paste an outline.
1640 c = self.c
1641 g.es(f"Yank outline: {c.p.h}")
1642 c.copyOutline()
1643 self.done()
1644 else:
1645 self.quit()
1647 def vim_y2(self):
1648 if self.is_text_wrapper(self.w):
1649 if self.stroke == 'y':
1650 # Yank n lines.
1651 w = self.w
1652 i1 = i = w.getInsertPoint()
1653 s = w.getAllText()
1654 for z in range(self.n1 * self.n):
1655 i, j = g.getLine(s, i)
1656 i = j + 1
1657 w.setSelectionRange(i1, j, insert=j)
1658 self.c.frame.copyText(event=self.event)
1659 w.setInsertPoint(i1)
1660 self.done()
1661 else:
1662 self.y_stroke = self.stroke # A scratch var.
1663 self.begin_motion(self.vim_y3)
1664 else:
1665 self.quit()
1667 def vim_y3(self):
1668 """Complete the y command after the cursor has moved."""
1669 # The motion is responsible for all repeat counts.
1670 # y2w doesn't extend to line. y2j does.
1671 if self.is_text_wrapper(self.w):
1672 extend_to_line = self.y_stroke in 'jk'
1673 # n = self.n1 * self.n
1674 w = self.w
1675 s = w.getAllText()
1676 i1, i2 = self.motion_i, w.getInsertPoint()
1677 if i1 == i2:
1678 pass
1679 elif i1 < i2:
1680 if extend_to_line:
1681 i2 = self.to_eol(s, i2)
1682 if i2 < len(s) and s[i2] == '\n':
1683 i2 += 1
1684 else: # i1 > i2
1685 i1, i2 = i2, i1
1686 if extend_to_line:
1687 i1 = self.to_bol(s, i1)
1688 if i1 != i2:
1689 w.setSelectionRange(i1, i2, insert=i2)
1690 self.c.frame.copyText(event=self.event)
1691 w.setInsertPoint(self.motion_i)
1692 self.done()
1693 else:
1694 self.quit()
1695 #@+node:ekr.20140807152406.18126: *5* vc.vim_Y
1696 def vim_Y(self):
1697 """Yank a Leo outline."""
1698 self.not_ready()
1699 #@+node:ekr.20140220134748.16631: *5* vc.vim_z (to do)
1700 def vim_z(self):
1701 """
1702 zb redraw current line at bottom of window
1703 zz redraw current line at center of window
1704 zt redraw current line at top of window
1705 """
1706 self.not_ready()
1707 # self.accept(handler=self.vim_z2)
1709 def vim_z2(self):
1710 g.trace(self.stroke)
1711 self.done()
1712 #@+node:ekr.20140222064735.16658: *4* vc.vis_...(motions) (just notes)
1713 #@+node:ekr.20140801121720.18071: *5* Notes
1714 #@@language rest
1715 #@+at
1716 # Not yet:
1717 # N B (motion) N blank-separated WORDS backward
1718 # N E (motion) forward to the end of the Nth blank-separated WORD
1719 # N G (motion) goto line N (default: last line), on the first non-blank character
1720 # N N (motion) repeat last search, in opposite direction
1721 # N W (motion) N blank-separated WORDS forward
1722 # N g# (motion) like "#", but also find partial matches
1723 # N g$ (motion) to last character in screen line (differs from "$" when lines wrap)
1724 # N g* (motion) like "*", but also find partial matches
1725 # N g0 (motion) to first character in screen line (differs from "0" when lines wrap)
1726 # gD (motion) goto global declaration of identifier under the cursor
1727 # N gE (motion) backward to the end of the Nth blank-separated WORD
1728 # gd (motion) goto local declaration of identifier under the cursor
1729 # N ge (motion) backward to the end of the Nth word
1730 # N gg (motion) goto line N (default: first line), on the first non-blank character
1731 # N gj (motion) down N screen lines (differs from "j" when line wraps)
1732 # N gk (motion) up N screen lines (differs from "k" when line wraps)
1733 #@+node:ekr.20140222064735.16635: *5* motion non-letters (to do)
1734 #@@nobeautify
1735 #@@nocolor-node
1736 #@+at
1737 #
1738 # First:
1739 #
1740 # 0 (motion) to first character in the line (also: <Home> key)
1741 # N $ (motion) go to the last character in the line (N-1 lines lower) (also: <End> key)
1742 # ^ (motion) go to first non-blank character in the line
1743 # N , (motion) repeat the last "f", "F", "t", or "T" N times in opposite direction
1744 # N ; (motion) repeat the last "f", "F", "t", or "T" N times
1745 # N /<CR> (motion) repeat last search, in the forward direction
1746 # N /{pattern}[/[offset]]<CR> (motion) search forward for the Nth occurrence of {pattern}
1747 # N ?<CR> (motion) repeat last search, in the backward direction
1748 # N ?{pattern}[?[offset]]<CR> (motion) search backward for the Nth occurrence of {pattern}
1749 #
1750 # Later or never:
1751 #
1752 # N CTRL-I (motion) go to Nth newer position in jump list
1753 # N CTRL-O (motion) go to Nth older position in jump list
1754 # N CTRL-T (motion) Jump back from Nth older tag in tag list
1755 #
1756 # N + (motion) down N lines, on the first non-blank character (also: CTRL-M and <CR>)
1757 # N _ (motion) down N-1 lines, on the first non-blank character
1758 # N - (motion) up N lines, on the first non-blank character
1759 #
1760 # N ( (motion) N sentences backward
1761 # N ) (motion) N sentences forward
1762 # N { (motion) N paragraphs backward
1763 # N } (motion) N paragraphs forward
1764 # N | (motion) to column N (default: 1)
1765 # `" (motion) go to the position when last editing this file
1766 # '<a-zA-Z0-9[]'"<>> (motion) same as `, but on the first non-blank in the line
1767 # `< (motion) go to the start of the (previous) Visual area
1768 # `<0-9> (motion) go to the position where Vim was last exited
1769 # `<A-Z> (motion) go to mark <A-Z> in any file
1770 # `<a-z> (motion) go to mark <a-z> within current file
1771 # `> (motion) go to the end of the (previous) Visual area
1772 # `[ (motion) go to the start of the previously operated or put text
1773 # `] (motion) go to the end of the previously operated or put text
1774 # `` (motion) go to the position before the last jump
1775 #
1776 # N % (motion) goto line N percentage down in the file.
1777 # N must be given, otherwise it is the % command.
1778 # % (motion) find the next brace, bracket, comment,
1779 # or "#if"/ "#else"/"#endif" in this line and go to its match
1780 #
1781 # N # (motion) search backward for the identifier under the cursor
1782 # N * (motion) search forward for the identifier under the cursor
1783 #
1784 # N [# (motion) N times back to unclosed "#if" or "#else"
1785 # N [( (motion) N times back to unclosed '('
1786 # N [* (motion) N times back to start of comment "/*"
1787 # N [[ (motion) N sections backward, at start of section
1788 # N [] (motion) N sections backward, at end of section
1789 # N [p (motion?) like P, but adjust indent to current line
1790 # N [{ (motion) N times back to unclosed '{'
1791 # N ]# (motion) N times forward to unclosed "#else" or "#endif"
1792 # N ]) (motion) N times forward to unclosed ')'
1793 # N ]* (motion) N times forward to end of comment "*/"
1794 # N ][ (motion) N sections forward, at end of section
1795 # N ]] (motion) N sections forward, at start of section
1796 # N ]p (motion?) like p, but adjust indent to current line
1797 # N ]} (motion) N times forward to unclosed '}'
1798 #@+node:ekr.20140222064735.16655: *6* vis_minus
1799 #@+node:ekr.20140222064735.16654: *6* vis_plus
1800 #@+node:ekr.20140222064735.16647: *4* vc.vis_...(terminators)
1801 # Terminating commands call self.done().
1802 #@+node:ekr.20140222064735.16684: *5* vis_escape
1803 def vis_escape(self):
1804 """Handle Escape in visual mode."""
1805 self.state = 'normal'
1806 self.done()
1807 #@+node:ekr.20140222064735.16661: *5* vis_J
1808 def vis_J(self):
1809 """Join the highlighted lines."""
1810 self.state = 'normal'
1811 self.not_ready()
1812 # self.done(set_dot=True)
1813 #@+node:ekr.20140222064735.16656: *5* vis_c (to do)
1814 def vis_c(self):
1815 """Change the highlighted text."""
1816 self.state = 'normal'
1817 self.not_ready()
1818 # self.done(set_dot=True)
1819 #@+node:ekr.20140222064735.16657: *5* vis_d
1820 def vis_d(self):
1821 """Delete the highlighted text and terminate visual mode."""
1822 w = self.vis_mode_w
1823 if self.is_text_wrapper(w):
1824 i1 = self.vis_mode_i
1825 i2 = w.getInsertPoint()
1826 g.app.gui.replaceClipboardWith(w.getSelectedText())
1827 w.delete(i1, i2)
1828 self.state = 'normal'
1829 self.done(set_dot=True)
1830 else:
1831 self.quit()
1832 #@+node:ekr.20140222064735.16659: *5* vis_u
1833 def vis_u(self):
1834 """Make highlighted text lowercase."""
1835 self.state = 'normal'
1836 self.not_ready()
1837 # self.done(set_dot=True)
1838 #@+node:ekr.20140222064735.16681: *5* vis_v
1839 def vis_v(self):
1840 """End visual mode."""
1841 if 1:
1842 # End visual node, retain the selection, and set the dot.
1843 # This makes much more sense in Leo.
1844 self.state = 'normal'
1845 self.done()
1846 else:
1847 # The real vim clears the selection.
1848 w = self.w
1849 if self.is_text_wrapper(w):
1850 i = w.getInsertPoint()
1851 w.setSelectionRange(i, i)
1852 # Visual mode affects the dot only if there is a terminating command.
1853 self.dot_list = self.old_dot_list
1854 self.state = 'normal'
1855 self.done(set_dot=False)
1856 #@+node:ekr.20140222064735.16660: *5* vis_y
1857 def vis_y(self):
1858 """Yank the highlighted text."""
1859 if self.is_text_wrapper(self.w):
1860 self.c.frame.copyText(event=self.event)
1861 self.state = 'normal'
1862 self.done(set_dot=True)
1863 else:
1864 self.quit()
1865 #@+node:ekr.20140221085636.16685: *3* vc.do_key & helpers
1866 def do_key(self, event):
1867 """
1868 Handle the next key in vim mode:
1869 - Set event, w, stroke and ch ivars for *all* handlers.
1870 - Call handler().
1871 Return True if k.masterKeyHandler should handle this key.
1872 """
1873 try:
1874 self.init_scanner_vars(event)
1875 self.do_trace(blank_line=True)
1876 self.return_value = None
1877 if not self.handle_specials():
1878 self.handler()
1879 if self.return_value not in (True, False):
1880 # It looks like no acceptance method has been called.
1881 self.oops(
1882 f"bad return_value: {repr(self.return_value)} "
1883 f"{self.state} {self.next_func}")
1884 self.done() # Sets self.return_value to True.
1885 except Exception:
1886 g.es_exception()
1887 self.quit()
1888 return self.return_value
1889 #@+node:ekr.20140802225657.18021: *4* vc.handle_specials
1890 def handle_specials(self):
1891 """Return True self.stroke is an Escape or a Return in the outline pane."""
1892 if self.stroke == 'Escape':
1893 # k.masterKeyHandler handles Ctrl-G.
1894 # Escape will end insert mode.
1895 self.vim_esc()
1896 return True
1897 if self.stroke == '\n' and self.in_headline(self.w):
1898 # End headline editing and enter normal mode.
1899 self.c.endEditing()
1900 self.done()
1901 return True
1902 return False
1903 #@+node:ekr.20140802120757.18003: *4* vc.init_scanner_vars
1904 def init_scanner_vars(self, event):
1905 """Init all ivars used by the scanner."""
1906 assert event
1907 self.event = event
1908 stroke = event.stroke
1909 self.ch = event.char # Required for f,F,t,T.
1910 self.stroke = stroke.s if g.isStroke(stroke) else stroke
1911 self.w = event and event.w
1912 if not self.in_command:
1913 self.in_command = True # May be cleared later.
1914 if self.is_text_wrapper(self.w):
1915 self.old_sel = self.w.getSelectionRange()
1916 #@+node:ekr.20140815160132.18821: *3* vc.external commands
1917 #@+node:ekr.20140815160132.18823: *4* class vc.LoadFileAtCursor (:r)
1918 class LoadFileAtCursor:
1919 """
1920 A class to handle Vim's :r command.
1921 This class supports the do_tab callback.
1922 """
1924 def __init__(self, vc):
1925 """Ctor for VimCommands.LoadFileAtCursor class."""
1926 self.vc = vc
1928 __name__ = ':r' # Required.
1929 #@+others
1930 #@+node:ekr.20140820034724.18316: *5* :r.__call__
1931 def __call__(self, event=None):
1932 """Prompt for a file name, then load it at the cursor."""
1933 self.vc.c.k.getFileName(event, callback=self.load_file_at_cursor)
1934 #@+node:ekr.20140820034724.18317: *5* :r.load_file_at_cursor
1935 def load_file_at_cursor(self, fn):
1936 vc = self.vc
1937 c, w = vc.c, vc.colon_w
1938 if not w:
1939 w = vc.w = c.frame.body.wrapper
1940 if g.os_path_exists(fn):
1941 f = open(fn)
1942 s = f.read()
1943 f.close()
1944 i = w.getInsertPoint()
1945 w.insert(i, s)
1946 vc.save_body()
1947 else:
1948 g.es('does not exist:', fn)
1949 #@+node:ekr.20140820034724.18318: *5* :r.tab_callback
1950 def tab_callback(self):
1951 """Called when the user types :r<tab>"""
1952 self.vc.c.k.getFileName(event=None, callback=self.load_file_at_cursor)
1953 #@-others
1954 #@+node:ekr.20140815160132.18828: *4* class vc.Substitution (:%s & :s)
1955 class Substitution:
1956 """A class to handle Vim's :% command."""
1958 def __init__(self, vc, all_lines):
1959 """Ctor for VimCommands.tabnew class."""
1960 self.all_lines = all_lines # True: :%s command. False: :s command.
1961 self.vc = vc
1963 __name__ = ':%' # Required.
1964 #@+others
1965 #@+node:ekr.20140820063930.18321: *5* Substitution.__call__ (:%s & :s)
1966 def __call__(self, event=None):
1967 """Handle the :s and :%s commands. Neither command affects the dot."""
1968 vc = self.vc
1969 c, w = vc.c, vc.w
1970 w = vc.w if c.vim_mode else c.frame.body
1971 if vc.is_text_wrapper(w):
1972 fc = vc.c.findCommands
1973 vc.search_stroke = None # Tell vc.update_dot_before_search not to update the dot.
1974 fc.reverse = False
1975 fc.openFindTab(vc.event)
1976 fc.ftm.clear_focus()
1977 fc.node_only = True # Doesn't work.
1978 # This returns immediately, before the actual search.
1979 # leoFind.show_success calls vc.update_selection_after_search.
1980 fc.start_search1(vc.event)
1981 if c.vim_mode:
1982 vc.done(add_to_dot=False, set_dot=False)
1983 elif c.vim_mode:
1984 vc.quit()
1985 #@+node:ekr.20140820063930.18323: *5* :%.tab_callback (not used)
1986 if 0:
1987 # This Easter Egg is a bad idea.
1988 # It will just confuse real vim users.
1990 def tab_callback(self):
1991 """
1992 Called when the user types :%<tab> or :%/x<tab>.
1993 This never ends the command: only return does that.
1994 """
1995 k = self.vc.k
1996 tail = k.functionTail
1997 tail = tail[1:] if tail.startswith(' ') else tail
1998 if not tail.startswith('/'):
1999 tail = '/' + tail
2000 k.setLabel(k.mb_prefix)
2001 k.extendLabel(':%' + tail + '/')
2002 #@-others
2003 #@+node:ekr.20140815160132.18829: *4* class vc.Tabnew (:e & :tabnew)
2004 class Tabnew:
2005 """
2006 A class to handle Vim's :tabnew command.
2007 This class supports the do_tab callback.
2008 """
2010 def __init__(self, vc):
2011 """Ctor for VimCommands.tabnew class."""
2012 self.vc = vc
2014 __name__ = ':tabnew' # Required.
2015 #@+others
2016 #@+node:ekr.20140820034724.18313: *5* :tabnew.__call__
2017 def __call__(self, event=None):
2018 """Prompt for a file name, the open a new Leo tab."""
2019 self.vc.c.k.getFileName(event, callback=self.open_file_by_name)
2020 #@+node:ekr.20140820034724.18315: *5* :tabnew.open_file_by_name
2021 def open_file_by_name(self, fn):
2022 c = self.vc.c
2023 if fn and g.os_path_isdir(fn):
2024 # change the working directory.
2025 c.new()
2026 try:
2027 os.chdir(fn)
2028 g.es(f"chdir({fn})", color='blue')
2029 except Exception:
2030 g.es('curdir not changed', color='red')
2031 elif fn:
2032 c2 = g.openWithFileName(fn, old_c=c)
2033 else:
2034 c.new()
2035 try:
2036 g.app.gui.runAtIdle(c2.treeWantsFocusNow)
2037 except Exception:
2038 pass
2039 #@+node:ekr.20140820034724.18314: *5* :tabnew.tab_callback
2040 def tab_callback(self):
2041 """Called when the user types :tabnew<tab>"""
2042 self.vc.c.k.getFileName(event=None, callback=self.open_file_by_name)
2043 #@-others
2044 #@+node:ekr.20150509050905.1: *4* vc.e_command & tabnew_command
2045 @cmd(':e')
2046 def e_command(self, event=None):
2047 self.Tabnew(self)
2049 @cmd(':tabnew')
2050 def tabnew_command(self, event=None):
2051 self.Tabnew(self)
2052 #@+node:ekr.20140815160132.18824: *4* vc.print_dot (:print-dot)
2053 @cmd(':print-dot')
2054 def print_dot(self, event=None):
2055 """Print the dot."""
2056 aList = [z.stroke if isinstance(z, VimEvent) else z for z in self.dot_list]
2057 aList = [show_stroke(self.c.k.stroke2char(z)) for z in aList]
2058 if self.n1 > 1:
2059 g.es_print('dot repeat count:', self.n1)
2060 i, n = 0, 0
2061 while i < len(aList):
2062 g.es_print(f"dot[{n}]:", ''.join(aList[i : i + 10]))
2063 i += 10
2064 n += 1
2065 #@+node:ekr.20140815160132.18825: *4* vc.q/qa_command & quit_now (:q & q! & :qa)
2066 @cmd(':q')
2067 def q_command(self, event=None):
2068 """Quit the present Leo outline, prompting for saves."""
2069 g.app.closeLeoWindow(self.c.frame, new_c=None)
2071 @cmd(':qa')
2072 def qa_command(self, event=None):
2073 """Quit only if there are no unsaved changes."""
2074 for c in g.app.commanders():
2075 if c.isChanged():
2076 return
2077 g.app.onQuit(event)
2079 @cmd(':q!')
2080 def quit_now(self, event=None):
2081 """Quit immediately."""
2082 g.app.forceShutdown()
2083 #@+node:ekr.20150509050918.1: *4* vc.r_command
2084 @cmd(':r')
2085 def r_command(self, event=None):
2086 self.LoadFileAtCursor(self)
2087 #@+node:ekr.20140815160132.18826: *4* vc.revert (:e!)
2088 @cmd(':e!')
2089 def revert(self, event=None):
2090 """Revert all changes to a .leo file, prompting if there have been changes."""
2091 self.c.revert()
2092 #@+node:ekr.20150509050755.1: *4* vc.s_command & percent_s_command
2093 @cmd(':%s')
2094 def percent_s_command(self, event=None):
2095 self.Substitution(self, all_lines=True)
2097 @cmd(':s')
2098 def s_command(self, event=None):
2099 self.Substitution(self, all_lines=False)
2100 #@+node:ekr.20140815160132.18827: *4* vc.shell_command (:!)
2101 @cmd(':!')
2102 def shell_command(self, event=None):
2103 """Execute a shell command."""
2104 c, k = self.c, self.c.k
2105 if k.functionTail:
2106 command = k.functionTail
2107 c.controlCommands.executeSubprocess(event, command)
2108 else:
2109 event = VimEvent(c=self.c, char='', stroke='', w=self.colon_w)
2110 self.do('shell-command', event=event)
2111 #@+node:ekr.20140815160132.18830: *4* vc.toggle_vim_mode
2112 @cmd(':toggle-vim-mode')
2113 def toggle_vim_mode(self, event=None):
2114 """toggle vim-mode."""
2115 c = self.c
2116 c.vim_mode = not c.vim_mode
2117 val = 'on' if c.vim_mode else 'off'
2118 g.es(f"vim-mode: {val}", color='red')
2119 if c.vim_mode:
2120 self.quit()
2121 else:
2122 try:
2123 self.state = 'insert'
2124 c.bodyWantsFocusNow()
2125 w = c.frame.body.widget
2126 self.set_border(kind=None, w=w, activeFlag=True)
2127 except Exception:
2128 # g.es_exception()
2129 pass
2130 #@+node:ekr.20140909140052.18128: *4* vc.toggle_vim_trace
2131 @cmd(':toggle-vim-trace')
2132 def toggle_vim_trace(self, event=None):
2133 """toggle vim tracing."""
2134 self.trace_flag = not self.trace_flag
2135 val = 'On' if self.trace_flag else 'Off'
2136 g.es_print(f"vim tracing: {val}")
2137 #@+node:ekr.20140815160132.18831: *4* vc.toggle_vim_trainer_mode
2138 @cmd(':toggle-vim-trainer-mode')
2139 def toggle_vim_trainer_mode(self, event=None):
2140 """toggle vim-trainer mode."""
2141 self.trainer = not self.trainer
2142 val = 'on' if self.trainer else 'off'
2143 g.es(f"vim-trainer-mode: {val}", color='red')
2144 #@+node:ekr.20140815160132.18832: *4* w/xa/wq_command (:w & :xa & wq)
2145 @cmd(':w')
2146 def w_command(self, event=None):
2147 """Save the .leo file."""
2148 self.c.save()
2150 @cmd(':xa')
2151 def xa_command(self, event=None): # same as :xa
2152 """Save all open files and keep working."""
2153 for c in g.app.commanders():
2154 if c.isChanged():
2155 c.save()
2157 @cmd(':wq')
2158 def wq_command(self, event=None):
2159 """Save all open files and exit."""
2160 for c in g.app.commanders():
2161 c.save()
2162 g.app.onQuit(event)
2163 #@+node:ekr.20140802225657.18026: *3* vc.state handlers
2164 # Neither state handler nor key handlers ever return non-None.
2165 #@+node:ekr.20140803220119.18089: *4* vc.do_inner_motion
2166 def do_inner_motion(self, restart=False):
2167 """Handle strokes in motions."""
2168 try:
2169 assert self.in_motion
2170 if restart:
2171 self.next_func = None
2172 func = self.next_func or self.motion_dispatch_d.get(self.stroke)
2173 if func:
2174 func()
2175 if self.motion_func:
2176 self.motion_func()
2177 self.in_motion = False # Required.
2178 self.done()
2179 elif self.is_plain_key(self.stroke):
2180 self.ignore()
2181 self.quit()
2182 else:
2183 # Pass non-plain keys to k.masterKeyHandler
2184 self.delegate()
2185 except Exception:
2186 g.es_exception()
2187 self.quit()
2188 #@+node:ekr.20140803220119.18090: *4* vc.do_insert_mode & helper
2189 def do_insert_mode(self):
2190 """Handle insert mode: delegate all strokes to k.masterKeyHandler."""
2191 # Support the jj abbreviation when there is no selection.
2192 self.do_trace()
2193 try:
2194 self.state = 'insert'
2195 w = self.w
2196 if self.is_text_wrapper(w) and self.test_for_insert_escape(w):
2197 if self.trace_flag:
2198 g.trace('*** abort ***', w)
2199 return
2200 # Special case for arrow keys.
2201 if self.stroke in self.arrow_d:
2202 self.vim_arrow()
2203 else:
2204 self.delegate()
2205 except Exception:
2206 g.es_exception()
2207 self.quit()
2208 #@+node:ekr.20140807112800.18122: *5* vc.test_for_insert_escape
2209 def test_for_insert_escape(self, w):
2210 """Return True if the j,j escape sequence has ended insert mode."""
2211 c = self.c
2212 s = w.getAllText()
2213 i = w.getInsertPoint()
2214 i2, j = w.getSelectionRange()
2215 if i2 == j and self.stroke == 'j':
2216 if i > 0 and s[i - 1] == 'j':
2217 w.delete(i - 1, i)
2218 w.setInsertPoint(i - 1)
2219 # A benign hack: simulate an Escape for the dot.
2220 self.stroke = 'Escape'
2221 self.end_insert_mode()
2222 return True
2223 # Remember the changed state when we saw the first 'j'.
2224 self.j_changed = c.isChanged()
2225 return False
2226 #@+node:ekr.20140803220119.18091: *4* vc.do_normal_mode
2227 def do_normal_mode(self):
2228 """Handle strokes in normal mode."""
2229 # Unlike visual mode, there is no need to init anything,
2230 # because all normal mode commands call self.done.
2231 self.do_state(self.normal_mode_dispatch_d, 'normal')
2232 #@+node:ekr.20140802225657.18029: *4* vc.do_state
2233 def do_state(self, d, mode_name):
2234 """General dispatcher code. d is a dispatch dict."""
2235 try:
2236 func = d.get(self.stroke)
2237 if func:
2238 func()
2239 elif self.is_plain_key(self.stroke):
2240 self.ignore()
2241 self.quit()
2242 else:
2243 # Pass non-plain keys to k.masterKeyHandler
2244 self.delegate()
2245 except Exception:
2246 g.es_exception()
2247 self.quit()
2248 #@+node:ekr.20140803220119.18092: *4* vc.do_visual_mode
2249 def do_visual_mode(self):
2250 """Handle strokes in visual mode."""
2251 try:
2252 self.n1 = self.n = 1
2253 self.do_state(self.vis_dispatch_d,
2254 mode_name='visual-line' if self.visual_line_flag else 'visual')
2255 if self.visual_line_flag:
2256 self.visual_line_helper()
2257 except Exception:
2258 g.es_exception()
2259 self.quit()
2260 #@+node:ekr.20140222064735.16682: *3* vc.Utilities
2261 #@+node:ekr.20140802183521.17998: *4* vc.add_to_dot
2262 def add_to_dot(self, stroke=None):
2263 """
2264 Add a new VimEvent to self.command_list.
2265 Never change self.command_list if self.in_dot is True
2266 Never add . to self.command_list
2267 """
2268 if not self.in_dot:
2269 s = stroke or self.stroke
2270 # Never add '.' to the dot list.
2271 if s and s != 'period':
2272 event = VimEvent(c=self.c, char=s, stroke=s, w=self.w)
2273 self.command_list.append(event)
2274 #@+node:ekr.20140802120757.18002: *4* vc.compute_dot
2275 def compute_dot(self, stroke):
2276 """Compute the dot and set the dot ivar."""
2277 if stroke:
2278 self.add_to_dot(stroke)
2279 if self.command_list:
2280 self.dot_list = self.command_list[:]
2281 #@+node:ekr.20140810214537.18241: *4* vc.do
2282 def do(self, o, event=None):
2283 """Do one or more Leo commands by name."""
2284 if not event:
2285 event = self.event
2286 if isinstance(o, (tuple, list)):
2287 for z in o:
2288 self.c.k.simulateCommand(z, event=event)
2289 else:
2290 self.c.k.simulateCommand(o, event=event)
2291 #@+node:ekr.20180424055522.1: *4* vc.do_trace
2292 def do_trace(self, blank_line=False):
2294 if self.stroke and self.trace_flag and not g.unitTesting:
2295 if blank_line:
2296 print('')
2297 g.es_print(f"{g.caller():20}: {self.stroke!r}")
2298 #@+node:ekr.20140802183521.17999: *4* vc.in_headline & vc.in_tree
2299 def in_headline(self, w):
2300 """Return True if we are in a headline edit widget."""
2301 return self.widget_name(w).startswith('head')
2303 def in_tree(self, w):
2304 """Return True if we are in the outline pane, but not in a headline."""
2305 return self.widget_name(w).startswith('canvas')
2306 #@+node:ekr.20140806081828.18157: *4* vc.is_body & is_head
2307 def is_body(self, w):
2308 """Return True if w is the QTextBrowser of the body pane."""
2309 w2 = self.c.frame.body.wrapper
2310 return w == w2
2312 def is_head(self, w):
2313 """Return True if w is an headline edit widget."""
2314 return self.widget_name(w).startswith('head')
2315 #@+node:ekr.20140801121720.18083: *4* vc.is_plain_key & is_text_wrapper
2316 def is_plain_key(self, stroke):
2317 """Return True if stroke is a plain key."""
2318 return self.k.isPlainKey(stroke)
2320 def is_text_wrapper(self, w=None):
2321 """Return True if w is a text widget."""
2322 return self.is_body(w) or self.is_head(w) or g.isTextWrapper(w)
2323 #@+node:ekr.20140805064952.18153: *4* vc.on_idle (no longer used)
2324 def on_idle(self, tag, keys):
2325 """The idle-time handler for the VimCommands class."""
2326 c = keys.get('c')
2327 if c and c.vim_mode and self == c.vimCommands:
2328 # #1273: only for vim mode.
2329 g.trace('=====')
2330 # Call set_border only for the presently selected tab.
2331 try:
2332 # Careful: we may not have tabs.
2333 w = g.app.gui.frameFactory.masterFrame
2334 except AttributeError:
2335 w = None
2336 if w:
2337 i = w.indexOf(c.frame.top)
2338 if i == w.currentIndex():
2339 self.set_border()
2340 else:
2341 self.set_border()
2342 #@+node:ekr.20140801121720.18079: *4* vc.on_same_line
2343 def on_same_line(self, s, i1, i2):
2344 """Return True if i1 and i2 are on the same line."""
2345 # Ensure that i1 <= i2 and that i1 and i2 are in range.
2346 if i1 > i2:
2347 i1, i2 = i2, i1
2348 if i1 < 0:
2349 i1 = 0
2350 if i1 >= len(s):
2351 i1 = len(s) - 1
2352 if i2 < 0:
2353 i2 = 0
2354 if i2 >= len(s):
2355 i2 = len(s) - 1
2356 if s[i2] == '\n':
2357 i2 = max(0, i2 - 1)
2358 return s[i1:i2].count('\n') == 0
2359 #@+node:ekr.20140802225657.18022: *4* vc.oops
2360 def oops(self, message):
2361 """Report an internal error"""
2362 g.warning(f"Internal vim-mode error: {message}")
2363 #@+node:ekr.20140802120757.18001: *4* vc.save_body (handles undo)
2364 def save_body(self):
2365 """Undoably preserve any changes to body text."""
2366 c, p, u = self.c, self.c.p, self.c.undoer
2367 w = self.command_w or self.w
2368 name = c.widget_name(w)
2369 if w and name.startswith('body'):
2370 bunch = u.beforeChangeBody(p)
2371 # Similar to selfInsertCommand.
2372 newText = w.getAllText()
2373 if c.p.b != newText:
2374 p.v.b = newText
2375 u.afterChangeBody(p, 'vc-save-body', bunch)
2376 #@+node:ekr.20140804123147.18929: *4* vc.set_border & helper
2377 def set_border(self, kind=None, w=None, activeFlag=None):
2378 """
2379 Set the border color of self.w, depending on state.
2380 Called from qtBody.onFocusColorHelper and self.show_status.
2381 """
2382 if not w:
2383 w = g.app.gui.get_focus()
2384 if not w:
2385 return
2386 w_name = self.widget_name(w)
2387 if w_name == 'richTextEdit':
2388 self.set_property(w, focus_flag=activeFlag in (None, True))
2389 elif w_name.startswith('head'):
2390 self.set_property(w, True)
2391 elif w_name != 'richTextEdit':
2392 # Clear the border in the body pane.
2393 try:
2394 w = self.c.frame.body.widget
2395 self.set_property(w, False)
2396 except Exception:
2397 pass
2398 #@+node:ekr.20140807070500.18161: *5* vc.set_property
2399 def set_property(self, w, focus_flag):
2400 """Set the property of w, depending on focus and state."""
2401 c, state = self.c, self.state
2402 #
2403 # #1221: Use a style sheet based on new settings.
2404 if focus_flag:
2405 d = {
2406 'normal': ('vim-mode-normal-border', 'border: 3px solid white'),
2407 'insert': ('vim-mode-insert-border', 'border: 3px solid red'),
2408 'visual': ('vim-mode-visual-border', 'border: 3px solid yellow'),
2409 }
2410 data = d.get(state)
2411 if not data:
2412 g.trace('bad vim mode', repr(state))
2413 return
2414 setting, default_border = data
2415 else:
2416 setting = 'vim-mode-unfocused-border'
2417 default_border = 'border: 3px dashed white'
2418 border = c.config.getString(setting) or default_border
2419 # g.trace(setting, border)
2420 w.setStyleSheet(border)
2421 return
2422 #
2423 # This code doesn't work on Qt 5, because of a Qt bug.
2424 # It probably isn't coming back.
2425 # selector = f"vim_{state}" if focus_flag else 'vim_unfocused'
2426 # w.setProperty('vim_state', selector)
2427 # w.style().unpolish(w)
2428 # w.style().polish(w)
2429 #@+node:ekr.20140802142132.17981: *4* vc.show_dot & show_list
2430 def show_command(self):
2431 """Show the accumulating command."""
2432 return ''.join([repr(z) for z in self.command_list])
2434 def show_dot(self):
2435 """Show the dot."""
2436 s = ''.join([repr(z) for z in self.dot_list[:10]])
2437 if len(self.dot_list) > 10:
2438 s = s + '...'
2439 return s
2440 #@+node:ekr.20140222064735.16615: *4* vc.show_status
2441 def show_status(self):
2442 """Show self.state and self.command_list"""
2443 k = self.k
2444 self.set_border()
2445 if k.state.kind:
2446 pass
2447 # elif self.state == 'visual':
2448 # s = '%8s:' % self.state.capitalize()
2449 # k.setLabelBlue(s)
2450 else:
2451 if self.visual_line_flag:
2452 state_s = 'Visual Line'
2453 else:
2454 state_s = self.state.capitalize()
2455 command_s = self.show_command()
2456 dot_s = self.show_dot()
2457 # if self.in_motion: state_s = state_s + '(in_motion)'
2458 if 1: # Don't show the dot:
2459 s = f"{state_s:8}: {command_s}"
2460 else:
2461 s = f"{state_s:8}: {command_s:>5} dot: {dot_s}"
2462 k.setLabelBlue(s)
2463 #@+node:ekr.20140801121720.18080: *4* vc.to_bol & vc.eol
2464 def to_bol(self, s, i):
2465 """Return the index of the first character on the line containing s[i]"""
2466 if i >= len(s):
2467 i = len(s)
2468 while i > 0 and s[i - 1] != '\n':
2469 i -= 1
2470 return i
2472 def to_eol(self, s, i):
2473 """Return the index of the last character on the line containing s[i]"""
2474 while i < len(s) and s[i] != '\n':
2475 i += 1
2476 return i
2477 #@+node:ekr.20140822072856.18256: *4* vc.visual_line_helper
2478 def visual_line_helper(self):
2479 """Extend the selection as necessary in visual line mode."""
2480 bx = 'beginning-of-line-extend-selection'
2481 ex = 'end-of-line-extend-selection'
2482 w = self.w
2483 i = w.getInsertPoint()
2484 # We would like to set insert=i0, but
2485 # w.setSelectionRange requires either insert==i or insert==j.
2486 # i0 = i
2487 if self.vis_mode_i < i:
2488 # Select from the beginning of the line containing self.vismode_i
2489 # to the end of the line containing i.
2490 w.setInsertPoint(self.vis_mode_i)
2491 self.do(bx)
2492 i1, i2 = w.getSelectionRange()
2493 w.setInsertPoint(i)
2494 self.do(ex)
2495 j1, j2 = w.getSelectionRange()
2496 i, j = min(i1, i2), max(j1, j2)
2497 w.setSelectionRange(i, j, insert=j)
2498 else:
2499 # Select from the beginning of the line containing i
2500 # to the end of the line containing self.vismode_i.
2501 w.setInsertPoint(i)
2502 self.do(bx)
2503 i1, i2 = w.getSelectionRange()
2504 w.setInsertPoint(self.vis_mode_i)
2505 self.do(ex)
2506 j1, j2 = w.getSelectionRange()
2507 i, j = min(i1, i2), max(j1, j2)
2508 w.setSelectionRange(i, j, insert=i)
2509 #@+node:ekr.20140805064952.18152: *4* vc.widget_name
2510 def widget_name(self, w):
2511 return self.c.widget_name(w)
2512 #@-others
2513#@-others
2514#@@language python
2515#@@tabwidth -4
2516#@@pagewidth 70
2517#@-leo