Coverage for C:\leo.repo\leo-editor\leo\core\leoVim.py: 20%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1375 statements  

1# -*- coding: utf-8 -*- 

2#@+leo-ver=5-thin 

3#@+node:ekr.20131109170017.16504: * @file leoVim.py 

4#@@first 

5""" 

6Leo's vim mode. 

7 

8**Important** 

9 

10`@bool vim-mode` enables vim *mode*. 

11 

12`@keys Vim bindings` enables vim *emulation*. 

13 

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 

25 

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.""" 

52 

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. 

60 

61 def __repr__(self): 

62 """Return the representation of the stroke.""" 

63 return show_stroke(self.stroke) 

64 

65 __str__ = __repr__ 

66#@+node:ekr.20131113045621.16547: ** class VimCommands 

67class VimCommands: 

68 """ 

69 A class that handles vim mode in Leo. 

70 

71 In vim mode, k.masterKeyHandler calls 

72 

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 self.trace_flag = 'keys' in g.app.debug 

81 # Toggled by :toggle-vim-trace. 

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 

122 

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. 

464 

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. 

522 

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) 

635 

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! 

717 

718 # Don't use add_to_dot(): it updates self.command_list. 

719 

720 def add(stroke): 

721 event = VimEvent(c=self.c, char=stroke, stroke=stroke, w=self.w) 

722 self.dot_list.append(event) 

723 

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) 

832 

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. 

861 

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() 

868 

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. 

1021 

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() 

1041 

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() 

1075 

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() 

1124 

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) 

1268 

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) 

1380 

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) 

1407 

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) 

1420 

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() 

1472 

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() 

1509 

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() 

1646 

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() 

1666 

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) 

1708 

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 """ 

1923 

1924 def __init__(self, vc): 

1925 """Ctor for VimCommands.LoadFileAtCursor class.""" 

1926 self.vc = vc 

1927 

1928 __name__ = ':r' 

1929 # Required. 

1930 #@+others 

1931 #@+node:ekr.20140820034724.18316: *5* :r.__call__ 

1932 def __call__(self, event=None): 

1933 """Prompt for a file name, then load it at the cursor.""" 

1934 self.vc.c.k.getFileName(event, callback=self.load_file_at_cursor) 

1935 #@+node:ekr.20140820034724.18317: *5* :r.load_file_at_cursor 

1936 def load_file_at_cursor(self, fn): 

1937 vc = self.vc 

1938 c, w = vc.c, vc.colon_w 

1939 if not w: 

1940 w = vc.w = c.frame.body.wrapper 

1941 if g.os_path_exists(fn): 

1942 f = open(fn) 

1943 s = f.read() 

1944 f.close() 

1945 i = w.getInsertPoint() 

1946 w.insert(i, s) 

1947 vc.save_body() 

1948 else: 

1949 g.es('does not exist:', fn) 

1950 #@+node:ekr.20140820034724.18318: *5* :r.tab_callback 

1951 def tab_callback(self): 

1952 """Called when the user types :r<tab>""" 

1953 self.vc.c.k.getFileName(event=None, callback=self.load_file_at_cursor) 

1954 #@-others 

1955 #@+node:ekr.20140815160132.18828: *4* class vc.Substitution (:%s & :s) 

1956 class Substitution: 

1957 """A class to handle Vim's :% command.""" 

1958 

1959 def __init__(self, vc, all_lines): 

1960 """Ctor for VimCommands.tabnew class.""" 

1961 self.all_lines = all_lines # True: :%s command. False: :s command. 

1962 self.vc = vc 

1963 

1964 __name__ = ':%' # Required. 

1965 #@+others 

1966 #@+node:ekr.20140820063930.18321: *5* Substitution.__call__ (:%s & :s) 

1967 def __call__(self, event=None): 

1968 """Handle the :s and :%s commands. Neither command affects the dot.""" 

1969 vc = self.vc 

1970 c, w = vc.c, vc.w 

1971 w = vc.w if c.vim_mode else c.frame.body 

1972 if vc.is_text_wrapper(w): 

1973 fc = vc.c.findCommands 

1974 vc.search_stroke = None # Tell vc.update_dot_before_search not to update the dot. 

1975 fc.reverse = False 

1976 fc.openFindTab(vc.event) 

1977 fc.ftm.clear_focus() 

1978 fc.node_only = True # Doesn't work. 

1979 # This returns immediately, before the actual search. 

1980 # leoFind.show_success calls vc.update_selection_after_search. 

1981 fc.start_search1(vc.event) 

1982 if c.vim_mode: 

1983 vc.done(add_to_dot=False, set_dot=False) 

1984 elif c.vim_mode: 

1985 vc.quit() 

1986 #@+node:ekr.20140820063930.18323: *5* :%.tab_callback (not used) 

1987 if 0: 

1988 # This Easter Egg is a bad idea. 

1989 # It will just confuse real vim users. 

1990 

1991 def tab_callback(self): 

1992 """ 

1993 Called when the user types :%<tab> or :%/x<tab>. 

1994 This never ends the command: only return does that. 

1995 """ 

1996 k = self.vc.k 

1997 tail = k.functionTail 

1998 tail = tail[1:] if tail.startswith(' ') else tail 

1999 if not tail.startswith('/'): 

2000 tail = '/' + tail 

2001 k.setLabel(k.mb_prefix) 

2002 k.extendLabel(':%' + tail + '/') 

2003 #@-others 

2004 #@+node:ekr.20140815160132.18829: *4* class vc.Tabnew (:e & :tabnew) 

2005 class Tabnew: 

2006 """ 

2007 A class to handle Vim's :tabnew command. 

2008 This class supports the do_tab callback. 

2009 """ 

2010 

2011 def __init__(self, vc): 

2012 """Ctor for VimCommands.tabnew class.""" 

2013 self.vc = vc 

2014 

2015 __name__ = ':tabnew' 

2016 # Required. 

2017 #@+others 

2018 #@+node:ekr.20140820034724.18313: *5* :tabnew.__call__ 

2019 def __call__(self, event=None): 

2020 """Prompt for a file name, the open a new Leo tab.""" 

2021 self.vc.c.k.getFileName(event, callback=self.open_file_by_name) 

2022 #@+node:ekr.20140820034724.18315: *5* :tabnew.open_file_by_name 

2023 def open_file_by_name(self, fn): 

2024 c = self.vc.c 

2025 if fn and g.os_path_isdir(fn): 

2026 # change the working directory. 

2027 c.new() 

2028 try: 

2029 os.chdir(fn) 

2030 g.es(f"chdir({fn})", color='blue') 

2031 except Exception: 

2032 g.es('curdir not changed', color='red') 

2033 elif fn: 

2034 c2 = g.openWithFileName(fn, old_c=c) 

2035 else: 

2036 c.new() 

2037 try: 

2038 g.app.gui.runAtIdle(c2.treeWantsFocusNow) 

2039 except Exception: 

2040 pass 

2041 #@+node:ekr.20140820034724.18314: *5* :tabnew.tab_callback 

2042 def tab_callback(self): 

2043 """Called when the user types :tabnew<tab>""" 

2044 self.vc.c.k.getFileName(event=None, callback=self.open_file_by_name) 

2045 #@-others 

2046 #@+node:ekr.20150509050905.1: *4* vc.e_command & tabnew_command 

2047 @cmd(':e') 

2048 def e_command(self, event=None): 

2049 self.Tabnew(self) 

2050 

2051 @cmd(':tabnew') 

2052 def tabnew_command(self, event=None): 

2053 self.Tabnew(self) 

2054 #@+node:ekr.20140815160132.18824: *4* vc.print_dot (:print-dot) 

2055 @cmd(':print-dot') 

2056 def print_dot(self, event=None): 

2057 """Print the dot.""" 

2058 aList = [z.stroke if isinstance(z, VimEvent) else z for z in self.dot_list] 

2059 aList = [show_stroke(self.c.k.stroke2char(z)) for z in aList] 

2060 if self.n1 > 1: 

2061 g.es_print('dot repeat count:', self.n1) 

2062 i, n = 0, 0 

2063 while i < len(aList): 

2064 g.es_print(f"dot[{n}]:", ''.join(aList[i : i + 10])) 

2065 i += 10 

2066 n += 1 

2067 #@+node:ekr.20140815160132.18825: *4* vc.q/qa_command & quit_now (:q & q! & :qa) 

2068 @cmd(':q') 

2069 def q_command(self, event=None): 

2070 """Quit the present Leo outline, prompting for saves.""" 

2071 g.app.closeLeoWindow(self.c.frame, new_c=None) 

2072 

2073 @cmd(':qa') 

2074 def qa_command(self, event=None): 

2075 """Quit only if there are no unsaved changes.""" 

2076 for c in g.app.commanders(): 

2077 if c.isChanged(): 

2078 return 

2079 g.app.onQuit(event) 

2080 

2081 @cmd(':q!') 

2082 def quit_now(self, event=None): 

2083 """Quit immediately.""" 

2084 g.app.forceShutdown() 

2085 #@+node:ekr.20150509050918.1: *4* vc.r_command 

2086 @cmd(':r') 

2087 def r_command(self, event=None): 

2088 self.LoadFileAtCursor(self) 

2089 #@+node:ekr.20140815160132.18826: *4* vc.revert (:e!) 

2090 @cmd(':e!') 

2091 def revert(self, event=None): 

2092 """Revert all changes to a .leo file, prompting if there have been changes.""" 

2093 self.c.revert() 

2094 #@+node:ekr.20150509050755.1: *4* vc.s_command & percent_s_command 

2095 @cmd(':%s') 

2096 def percent_s_command(self, event=None): 

2097 self.Substitution(self, all_lines=True) 

2098 

2099 @cmd(':s') 

2100 def s_command(self, event=None): 

2101 self.Substitution(self, all_lines=False) 

2102 #@+node:ekr.20140815160132.18827: *4* vc.shell_command (:!) 

2103 @cmd(':!') 

2104 def shell_command(self, event=None): 

2105 """Execute a shell command.""" 

2106 c, k = self.c, self.c.k 

2107 if k.functionTail: 

2108 command = k.functionTail 

2109 c.controlCommands.executeSubprocess(event, command) 

2110 else: 

2111 event = VimEvent(c=self.c, char='', stroke='', w=self.colon_w) 

2112 self.do('shell-command', event=event) 

2113 #@+node:ekr.20140815160132.18830: *4* vc.toggle_vim_mode 

2114 @cmd(':toggle-vim-mode') 

2115 def toggle_vim_mode(self, event=None): 

2116 """toggle vim-mode.""" 

2117 c = self.c 

2118 c.vim_mode = not c.vim_mode 

2119 val = 'on' if c.vim_mode else 'off' 

2120 g.es(f"vim-mode: {val}", color='red') 

2121 if c.vim_mode: 

2122 self.quit() 

2123 else: 

2124 try: 

2125 self.state = 'insert' 

2126 c.bodyWantsFocusNow() 

2127 w = c.frame.body.widget 

2128 self.set_border(kind=None, w=w, activeFlag=True) 

2129 except Exception: 

2130 # g.es_exception() 

2131 pass 

2132 #@+node:ekr.20140909140052.18128: *4* vc.toggle_vim_trace 

2133 @cmd(':toggle-vim-trace') 

2134 def toggle_vim_trace(self, event=None): 

2135 """toggle vim tracing.""" 

2136 self.trace_flag = not self.trace_flag 

2137 val = 'On' if self.trace_flag else 'Off' 

2138 g.es_print(f"vim tracing: {val}") 

2139 #@+node:ekr.20140815160132.18831: *4* vc.toggle_vim_trainer_mode 

2140 @cmd(':toggle-vim-trainer-mode') 

2141 def toggle_vim_trainer_mode(self, event=None): 

2142 """toggle vim-trainer mode.""" 

2143 self.trainer = not self.trainer 

2144 val = 'on' if self.trainer else 'off' 

2145 g.es(f"vim-trainer-mode: {val}", color='red') 

2146 #@+node:ekr.20140815160132.18832: *4* w/xa/wq_command (:w & :xa & wq) 

2147 @cmd(':w') 

2148 def w_command(self, event=None): 

2149 """Save the .leo file.""" 

2150 self.c.save() 

2151 

2152 @cmd(':xa') 

2153 def xa_command(self, event=None): # same as :xa 

2154 """Save all open files and keep working.""" 

2155 for c in g.app.commanders(): 

2156 if c.isChanged(): 

2157 c.save() 

2158 

2159 @cmd(':wq') 

2160 def wq_command(self, event=None): 

2161 """Save all open files and exit.""" 

2162 for c in g.app.commanders(): 

2163 c.save() 

2164 g.app.onQuit(event) 

2165 #@+node:ekr.20140802225657.18026: *3* vc.state handlers 

2166 # Neither state handler nor key handlers ever return non-None. 

2167 #@+node:ekr.20140803220119.18089: *4* vc.do_inner_motion 

2168 def do_inner_motion(self, restart=False): 

2169 """Handle strokes in motions.""" 

2170 try: 

2171 assert self.in_motion 

2172 if restart: 

2173 self.next_func = None 

2174 func = self.next_func or self.motion_dispatch_d.get(self.stroke) 

2175 if func: 

2176 func() 

2177 if self.motion_func: 

2178 self.motion_func() 

2179 self.in_motion = False # Required. 

2180 self.done() 

2181 elif self.is_plain_key(self.stroke): 

2182 self.ignore() 

2183 self.quit() 

2184 else: 

2185 # Pass non-plain keys to k.masterKeyHandler 

2186 self.delegate() 

2187 except Exception: 

2188 g.es_exception() 

2189 self.quit() 

2190 #@+node:ekr.20140803220119.18090: *4* vc.do_insert_mode & helper 

2191 def do_insert_mode(self): 

2192 """Handle insert mode: delegate all strokes to k.masterKeyHandler.""" 

2193 # Support the jj abbreviation when there is no selection. 

2194 self.do_trace() 

2195 try: 

2196 self.state = 'insert' 

2197 w = self.w 

2198 if self.is_text_wrapper(w) and self.test_for_insert_escape(w): 

2199 if self.trace_flag: 

2200 g.trace('*** abort ***', w) 

2201 return 

2202 # Special case for arrow keys. 

2203 if self.stroke in self.arrow_d: 

2204 self.vim_arrow() 

2205 else: 

2206 self.delegate() 

2207 except Exception: 

2208 g.es_exception() 

2209 self.quit() 

2210 #@+node:ekr.20140807112800.18122: *5* vc.test_for_insert_escape 

2211 def test_for_insert_escape(self, w): 

2212 """Return True if the j,j escape sequence has ended insert mode.""" 

2213 c = self.c 

2214 s = w.getAllText() 

2215 i = w.getInsertPoint() 

2216 i2, j = w.getSelectionRange() 

2217 if i2 == j and self.stroke == 'j': 

2218 if i > 0 and s[i - 1] == 'j': 

2219 w.delete(i - 1, i) 

2220 w.setInsertPoint(i - 1) 

2221 # A benign hack: simulate an Escape for the dot. 

2222 self.stroke = 'Escape' 

2223 self.end_insert_mode() 

2224 return True 

2225 # Remember the changed state when we saw the first 'j'. 

2226 self.j_changed = c.isChanged() 

2227 return False 

2228 #@+node:ekr.20140803220119.18091: *4* vc.do_normal_mode 

2229 def do_normal_mode(self): 

2230 """Handle strokes in normal mode.""" 

2231 # Unlike visual mode, there is no need to init anything, 

2232 # because all normal mode commands call self.done. 

2233 self.do_state(self.normal_mode_dispatch_d, 'normal') 

2234 #@+node:ekr.20140802225657.18029: *4* vc.do_state 

2235 def do_state(self, d, mode_name): 

2236 """General dispatcher code. d is a dispatch dict.""" 

2237 try: 

2238 func = d.get(self.stroke) 

2239 if func: 

2240 func() 

2241 elif self.is_plain_key(self.stroke): 

2242 self.ignore() 

2243 self.quit() 

2244 else: 

2245 # Pass non-plain keys to k.masterKeyHandler 

2246 self.delegate() 

2247 except Exception: 

2248 g.es_exception() 

2249 self.quit() 

2250 #@+node:ekr.20140803220119.18092: *4* vc.do_visual_mode 

2251 def do_visual_mode(self): 

2252 """Handle strokes in visual mode.""" 

2253 try: 

2254 self.n1 = self.n = 1 

2255 self.do_state(self.vis_dispatch_d, 

2256 mode_name='visual-line' if self.visual_line_flag else 'visual') 

2257 if self.visual_line_flag: 

2258 self.visual_line_helper() 

2259 except Exception: 

2260 g.es_exception() 

2261 self.quit() 

2262 #@+node:ekr.20140222064735.16682: *3* vc.Utilities 

2263 #@+node:ekr.20140802183521.17998: *4* vc.add_to_dot 

2264 def add_to_dot(self, stroke=None): 

2265 """ 

2266 Add a new VimEvent to self.command_list. 

2267 Never change self.command_list if self.in_dot is True 

2268 Never add . to self.command_list 

2269 """ 

2270 if not self.in_dot: 

2271 s = stroke or self.stroke 

2272 # Never add '.' to the dot list. 

2273 if s and s != 'period': 

2274 event = VimEvent(c=self.c, char=s, stroke=s, w=self.w) 

2275 self.command_list.append(event) 

2276 #@+node:ekr.20140802120757.18002: *4* vc.compute_dot 

2277 def compute_dot(self, stroke): 

2278 """Compute the dot and set the dot ivar.""" 

2279 if stroke: 

2280 self.add_to_dot(stroke) 

2281 if self.command_list: 

2282 self.dot_list = self.command_list[:] 

2283 #@+node:ekr.20140810214537.18241: *4* vc.do 

2284 def do(self, o, event=None): 

2285 """Do one or more Leo commands by name.""" 

2286 if not event: 

2287 event = self.event 

2288 if isinstance(o, (tuple, list)): 

2289 for z in o: 

2290 self.c.k.simulateCommand(z, event=event) 

2291 else: 

2292 self.c.k.simulateCommand(o, event=event) 

2293 #@+node:ekr.20180424055522.1: *4* vc.do_trace 

2294 def do_trace(self, blank_line=False): 

2295 

2296 if self.stroke and self.trace_flag and not g.unitTesting: 

2297 if blank_line: 

2298 print('') 

2299 g.es_print(f"{g.caller():20}: {self.stroke!r}") 

2300 #@+node:ekr.20140802183521.17999: *4* vc.in_headline & vc.in_tree 

2301 def in_headline(self, w): 

2302 """Return True if we are in a headline edit widget.""" 

2303 return self.widget_name(w).startswith('head') 

2304 

2305 def in_tree(self, w): 

2306 """Return True if we are in the outline pane, but not in a headline.""" 

2307 return self.widget_name(w).startswith('canvas') 

2308 #@+node:ekr.20140806081828.18157: *4* vc.is_body & is_head 

2309 def is_body(self, w): 

2310 """Return True if w is the QTextBrowser of the body pane.""" 

2311 w2 = self.c.frame.body.wrapper 

2312 return w == w2 

2313 

2314 def is_head(self, w): 

2315 """Return True if w is an headline edit widget.""" 

2316 return self.widget_name(w).startswith('head') 

2317 #@+node:ekr.20140801121720.18083: *4* vc.is_plain_key & is_text_wrapper 

2318 def is_plain_key(self, stroke): 

2319 """Return True if stroke is a plain key.""" 

2320 return self.k.isPlainKey(stroke) 

2321 

2322 def is_text_wrapper(self, w=None): 

2323 """Return True if w is a text widget.""" 

2324 return self.is_body(w) or self.is_head(w) or g.isTextWrapper(w) 

2325 #@+node:ekr.20140805064952.18153: *4* vc.on_idle (no longer used) 

2326 def on_idle(self, tag, keys): 

2327 """The idle-time handler for the VimCommands class.""" 

2328 c = keys.get('c') 

2329 if c and c.vim_mode and self == c.vimCommands: 

2330 # #1273: only for vim mode. 

2331 g.trace('=====') 

2332 # Call set_border only for the presently selected tab. 

2333 try: 

2334 # Careful: we may not have tabs. 

2335 w = g.app.gui.frameFactory.masterFrame 

2336 except AttributeError: 

2337 w = None 

2338 if w: 

2339 i = w.indexOf(c.frame.top) 

2340 if i == w.currentIndex(): 

2341 self.set_border() 

2342 else: 

2343 self.set_border() 

2344 #@+node:ekr.20140801121720.18079: *4* vc.on_same_line 

2345 def on_same_line(self, s, i1, i2): 

2346 """Return True if i1 and i2 are on the same line.""" 

2347 # Ensure that i1 <= i2 and that i1 and i2 are in range. 

2348 if i1 > i2: 

2349 i1, i2 = i2, i1 

2350 if i1 < 0: 

2351 i1 = 0 

2352 if i1 >= len(s): 

2353 i1 = len(s) - 1 

2354 if i2 < 0: 

2355 i2 = 0 

2356 if i2 >= len(s): 

2357 i2 = len(s) - 1 

2358 if s[i2] == '\n': 

2359 i2 = max(0, i2 - 1) 

2360 return s[i1:i2].count('\n') == 0 

2361 #@+node:ekr.20140802225657.18022: *4* vc.oops 

2362 def oops(self, message): 

2363 """Report an internal error""" 

2364 g.warning(f"Internal vim-mode error: {message}") 

2365 #@+node:ekr.20140802120757.18001: *4* vc.save_body (handles undo) 

2366 def save_body(self): 

2367 """Undoably preserve any changes to body text.""" 

2368 c, p, u = self.c, self.c.p, self.c.undoer 

2369 w = self.command_w or self.w 

2370 name = c.widget_name(w) 

2371 if w and name.startswith('body'): 

2372 bunch = u.beforeChangeBody(p) 

2373 # Similar to selfInsertCommand. 

2374 newText = w.getAllText() 

2375 if c.p.b != newText: 

2376 p.v.b = newText 

2377 u.afterChangeBody(p, 'vc-save-body', bunch) 

2378 #@+node:ekr.20140804123147.18929: *4* vc.set_border & helper 

2379 def set_border(self, kind=None, w=None, activeFlag=None): 

2380 """ 

2381 Set the border color of self.w, depending on state. 

2382 Called from qtBody.onFocusColorHelper and self.show_status. 

2383 """ 

2384 if not w: 

2385 w = g.app.gui.get_focus() 

2386 if not w: 

2387 return 

2388 w_name = self.widget_name(w) 

2389 if w_name == 'richTextEdit': 

2390 self.set_property(w, focus_flag=activeFlag in (None, True)) 

2391 elif w_name.startswith('head'): 

2392 self.set_property(w, True) 

2393 elif w_name != 'richTextEdit': 

2394 # Clear the border in the body pane. 

2395 try: 

2396 w = self.c.frame.body.widget 

2397 self.set_property(w, False) 

2398 except Exception: 

2399 pass 

2400 #@+node:ekr.20140807070500.18161: *5* vc.set_property 

2401 def set_property(self, w, focus_flag): 

2402 """Set the property of w, depending on focus and state.""" 

2403 c, state = self.c, self.state 

2404 # 

2405 # #1221: Use a style sheet based on new settings. 

2406 if focus_flag: 

2407 d = { 

2408 'normal': ('vim-mode-normal-border', 'border: 3px solid white'), 

2409 'insert': ('vim-mode-insert-border', 'border: 3px solid red'), 

2410 'visual': ('vim-mode-visual-border', 'border: 3px solid yellow'), 

2411 } 

2412 data = d.get(state) 

2413 if not data: 

2414 g.trace('bad vim mode', repr(state)) 

2415 return 

2416 setting, default_border = data 

2417 else: 

2418 setting = 'vim-mode-unfocused-border' 

2419 default_border = 'border: 3px dashed white' 

2420 border = c.config.getString(setting) or default_border 

2421 # g.trace(setting, border) 

2422 w.setStyleSheet(border) 

2423 return 

2424 # 

2425 # This code doesn't work on Qt 5, because of a Qt bug. 

2426 # It probably isn't coming back. 

2427 # selector = f"vim_{state}" if focus_flag else 'vim_unfocused' 

2428 # w.setProperty('vim_state', selector) 

2429 # w.style().unpolish(w) 

2430 # w.style().polish(w) 

2431 #@+node:ekr.20140802142132.17981: *4* vc.show_dot & show_list 

2432 def show_command(self): 

2433 """Show the accumulating command.""" 

2434 return ''.join([repr(z) for z in self.command_list]) 

2435 

2436 def show_dot(self): 

2437 """Show the dot.""" 

2438 s = ''.join([repr(z) for z in self.dot_list[:10]]) 

2439 if len(self.dot_list) > 10: 

2440 s = s + '...' 

2441 return s 

2442 #@+node:ekr.20140222064735.16615: *4* vc.show_status 

2443 def show_status(self): 

2444 """Show self.state and self.command_list""" 

2445 k = self.k 

2446 self.set_border() 

2447 if k.state.kind: 

2448 pass 

2449 # elif self.state == 'visual': 

2450 # s = '%8s:' % self.state.capitalize() 

2451 # k.setLabelBlue(s) 

2452 else: 

2453 if self.visual_line_flag: 

2454 state_s = 'Visual Line' 

2455 else: 

2456 state_s = self.state.capitalize() 

2457 command_s = self.show_command() 

2458 dot_s = self.show_dot() 

2459 # if self.in_motion: state_s = state_s + '(in_motion)' 

2460 if 1: # Don't show the dot: 

2461 s = f"{state_s:8}: {command_s}" 

2462 else: 

2463 s = f"{state_s:8}: {command_s:>5} dot: {dot_s}" 

2464 k.setLabelBlue(s) 

2465 #@+node:ekr.20140801121720.18080: *4* vc.to_bol & vc.eol 

2466 def to_bol(self, s, i): 

2467 """Return the index of the first character on the line containing s[i]""" 

2468 if i >= len(s): 

2469 i = len(s) 

2470 while i > 0 and s[i - 1] != '\n': 

2471 i -= 1 

2472 return i 

2473 

2474 def to_eol(self, s, i): 

2475 """Return the index of the last character on the line containing s[i]""" 

2476 while i < len(s) and s[i] != '\n': 

2477 i += 1 

2478 return i 

2479 #@+node:ekr.20140822072856.18256: *4* vc.visual_line_helper 

2480 def visual_line_helper(self): 

2481 """Extend the selection as necessary in visual line mode.""" 

2482 bx = 'beginning-of-line-extend-selection' 

2483 ex = 'end-of-line-extend-selection' 

2484 w = self.w 

2485 i = w.getInsertPoint() 

2486 # We would like to set insert=i0, but 

2487 # w.setSelectionRange requires either insert==i or insert==j. 

2488 # i0 = i 

2489 if self.vis_mode_i < i: 

2490 # Select from the beginning of the line containing self.vismode_i 

2491 # to the end of the line containing i. 

2492 w.setInsertPoint(self.vis_mode_i) 

2493 self.do(bx) 

2494 i1, i2 = w.getSelectionRange() 

2495 w.setInsertPoint(i) 

2496 self.do(ex) 

2497 j1, j2 = w.getSelectionRange() 

2498 i, j = min(i1, i2), max(j1, j2) 

2499 w.setSelectionRange(i, j, insert=j) 

2500 else: 

2501 # Select from the beginning of the line containing i 

2502 # to the end of the line containing self.vismode_i. 

2503 w.setInsertPoint(i) 

2504 self.do(bx) 

2505 i1, i2 = w.getSelectionRange() 

2506 w.setInsertPoint(self.vis_mode_i) 

2507 self.do(ex) 

2508 j1, j2 = w.getSelectionRange() 

2509 i, j = min(i1, i2), max(j1, j2) 

2510 w.setSelectionRange(i, j, insert=i) 

2511 #@+node:ekr.20140805064952.18152: *4* vc.widget_name 

2512 def widget_name(self, w): 

2513 return self.c.widget_name(w) 

2514 #@-others 

2515#@-others 

2516#@@language python 

2517#@@tabwidth -4 

2518#@@pagewidth 70 

2519#@-leo