vi.py 66 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903
  1. # pylint: disable=function-redefined
  2. from __future__ import unicode_literals
  3. from prompt_toolkit.buffer import ClipboardData, indent, unindent, reshape_text
  4. from prompt_toolkit.document import Document
  5. from prompt_toolkit.enums import IncrementalSearchDirection, SEARCH_BUFFER, SYSTEM_BUFFER
  6. from prompt_toolkit.filters import Filter, Condition, HasArg, Always, IsReadOnly
  7. from prompt_toolkit.filters.cli import ViNavigationMode, ViInsertMode, ViInsertMultipleMode, ViReplaceMode, ViSelectionMode, ViWaitingForTextObjectMode, ViDigraphMode, ViMode
  8. from prompt_toolkit.key_binding.digraphs import DIGRAPHS
  9. from prompt_toolkit.key_binding.vi_state import CharacterFind, InputMode
  10. from prompt_toolkit.keys import Keys
  11. from prompt_toolkit.layout.utils import find_window_for_buffer_name
  12. from prompt_toolkit.selection import SelectionType, SelectionState, PasteMode
  13. from .scroll import scroll_forward, scroll_backward, scroll_half_page_up, scroll_half_page_down, scroll_one_line_up, scroll_one_line_down, scroll_page_up, scroll_page_down
  14. from .named_commands import get_by_name
  15. from ..registry import Registry, ConditionalRegistry, BaseRegistry
  16. import prompt_toolkit.filters as filters
  17. from six.moves import range
  18. import codecs
  19. import six
  20. import string
  21. try:
  22. from itertools import accumulate
  23. except ImportError: # < Python 3.2
  24. def accumulate(iterable):
  25. " Super simpel 'accumulate' implementation. "
  26. total = 0
  27. for item in iterable:
  28. total += item
  29. yield total
  30. __all__ = (
  31. 'load_vi_bindings',
  32. 'load_vi_search_bindings',
  33. 'load_vi_system_bindings',
  34. 'load_extra_vi_page_navigation_bindings',
  35. )
  36. if six.PY2:
  37. ascii_lowercase = string.ascii_lowercase.decode('ascii')
  38. else:
  39. ascii_lowercase = string.ascii_lowercase
  40. vi_register_names = ascii_lowercase + '0123456789'
  41. class TextObjectType(object):
  42. EXCLUSIVE = 'EXCLUSIVE'
  43. INCLUSIVE = 'INCLUSIVE'
  44. LINEWISE = 'LINEWISE'
  45. BLOCK = 'BLOCK'
  46. class TextObject(object):
  47. """
  48. Return struct for functions wrapped in ``text_object``.
  49. Both `start` and `end` are relative to the current cursor position.
  50. """
  51. def __init__(self, start, end=0, type=TextObjectType.EXCLUSIVE):
  52. self.start = start
  53. self.end = end
  54. self.type = type
  55. @property
  56. def selection_type(self):
  57. if self.type == TextObjectType.LINEWISE:
  58. return SelectionType.LINES
  59. if self.type == TextObjectType.BLOCK:
  60. return SelectionType.BLOCK
  61. else:
  62. return SelectionType.CHARACTERS
  63. def sorted(self):
  64. """
  65. Return a (start, end) tuple where start <= end.
  66. """
  67. if self.start < self.end:
  68. return self.start, self.end
  69. else:
  70. return self.end, self.start
  71. def operator_range(self, document):
  72. """
  73. Return a (start, end) tuple with start <= end that indicates the range
  74. operators should operate on.
  75. `buffer` is used to get start and end of line positions.
  76. """
  77. start, end = self.sorted()
  78. doc = document
  79. if (self.type == TextObjectType.EXCLUSIVE and
  80. doc.translate_index_to_position(end + doc.cursor_position)[1] == 0):
  81. # If the motion is exclusive and the end of motion is on the first
  82. # column, the end position becomes end of previous line.
  83. end -= 1
  84. if self.type == TextObjectType.INCLUSIVE:
  85. end += 1
  86. if self.type == TextObjectType.LINEWISE:
  87. # Select whole lines
  88. row, col = doc.translate_index_to_position(start + doc.cursor_position)
  89. start = doc.translate_row_col_to_index(row, 0) - doc.cursor_position
  90. row, col = doc.translate_index_to_position(end + doc.cursor_position)
  91. end = doc.translate_row_col_to_index(row, len(doc.lines[row])) - doc.cursor_position
  92. return start, end
  93. def get_line_numbers(self, buffer):
  94. """
  95. Return a (start_line, end_line) pair.
  96. """
  97. # Get absolute cursor positions from the text object.
  98. from_, to = self.operator_range(buffer.document)
  99. from_ += buffer.cursor_position
  100. to += buffer.cursor_position
  101. # Take the start of the lines.
  102. from_, _ = buffer.document.translate_index_to_position(from_)
  103. to, _ = buffer.document.translate_index_to_position(to)
  104. return from_, to
  105. def cut(self, buffer):
  106. """
  107. Turn text object into `ClipboardData` instance.
  108. """
  109. from_, to = self.operator_range(buffer.document)
  110. from_ += buffer.cursor_position
  111. to += buffer.cursor_position
  112. to -= 1 # SelectionState does not include the end position, `operator_range` does.
  113. document = Document(buffer.text, to, SelectionState(
  114. original_cursor_position=from_, type=self.selection_type))
  115. new_document, clipboard_data = document.cut_selection()
  116. return new_document, clipboard_data
  117. def create_text_object_decorator(registry):
  118. """
  119. Create a decorator that can be used to register Vi text object implementations.
  120. """
  121. assert isinstance(registry, BaseRegistry)
  122. operator_given = ViWaitingForTextObjectMode()
  123. navigation_mode = ViNavigationMode()
  124. selection_mode = ViSelectionMode()
  125. def text_object_decorator(*keys, **kw):
  126. """
  127. Register a text object function.
  128. Usage::
  129. @text_object('w', filter=..., no_move_handler=False)
  130. def handler(event):
  131. # Return a text object for this key.
  132. return TextObject(...)
  133. :param no_move_handler: Disable the move handler in navigation mode.
  134. (It's still active in selection mode.)
  135. """
  136. filter = kw.pop('filter', Always())
  137. no_move_handler = kw.pop('no_move_handler', False)
  138. no_selection_handler = kw.pop('no_selection_handler', False)
  139. eager = kw.pop('eager', False)
  140. assert not kw
  141. def decorator(text_object_func):
  142. assert callable(text_object_func)
  143. @registry.add_binding(*keys, filter=operator_given & filter, eager=eager)
  144. def _(event):
  145. # Arguments are multiplied.
  146. vi_state = event.cli.vi_state
  147. event._arg = (vi_state.operator_arg or 1) * (event.arg or 1)
  148. # Call the text object handler.
  149. text_obj = text_object_func(event)
  150. if text_obj is not None:
  151. assert isinstance(text_obj, TextObject)
  152. # Call the operator function with the text object.
  153. vi_state.operator_func(event, text_obj)
  154. # Clear operator.
  155. event.cli.vi_state.operator_func = None
  156. event.cli.vi_state.operator_arg = None
  157. # Register a move operation. (Doesn't need an operator.)
  158. if not no_move_handler:
  159. @registry.add_binding(*keys, filter=~operator_given & filter & navigation_mode, eager=eager)
  160. def _(event):
  161. " Move handler for navigation mode. "
  162. text_object = text_object_func(event)
  163. event.current_buffer.cursor_position += text_object.start
  164. # Register a move selection operation.
  165. if not no_selection_handler:
  166. @registry.add_binding(*keys, filter=~operator_given & filter & selection_mode, eager=eager)
  167. def _(event):
  168. " Move handler for selection mode. "
  169. text_object = text_object_func(event)
  170. buff = event.current_buffer
  171. # When the text object has both a start and end position, like 'i(' or 'iw',
  172. # Turn this into a selection, otherwise the cursor.
  173. if text_object.end:
  174. # Take selection positions from text object.
  175. start, end = text_object.operator_range(buff.document)
  176. start += buff.cursor_position
  177. end += buff.cursor_position
  178. buff.selection_state.original_cursor_position = start
  179. buff.cursor_position = end
  180. # Take selection type from text object.
  181. if text_object.type == TextObjectType.LINEWISE:
  182. buff.selection_state.type = SelectionType.LINES
  183. else:
  184. buff.selection_state.type = SelectionType.CHARACTERS
  185. else:
  186. event.current_buffer.cursor_position += text_object.start
  187. # Make it possible to chain @text_object decorators.
  188. return text_object_func
  189. return decorator
  190. return text_object_decorator
  191. def create_operator_decorator(registry):
  192. """
  193. Create a decorator that can be used for registering Vi operators.
  194. """
  195. assert isinstance(registry, BaseRegistry)
  196. operator_given = ViWaitingForTextObjectMode()
  197. navigation_mode = ViNavigationMode()
  198. selection_mode = ViSelectionMode()
  199. def operator_decorator(*keys, **kw):
  200. """
  201. Register a Vi operator.
  202. Usage::
  203. @operator('d', filter=...)
  204. def handler(cli, text_object):
  205. # Do something with the text object here.
  206. """
  207. filter = kw.pop('filter', Always())
  208. eager = kw.pop('eager', False)
  209. assert not kw
  210. def decorator(operator_func):
  211. @registry.add_binding(*keys, filter=~operator_given & filter & navigation_mode, eager=eager)
  212. def _(event):
  213. """
  214. Handle operator in navigation mode.
  215. """
  216. # When this key binding is matched, only set the operator
  217. # function in the ViState. We should execute it after a text
  218. # object has been received.
  219. event.cli.vi_state.operator_func = operator_func
  220. event.cli.vi_state.operator_arg = event.arg
  221. @registry.add_binding(*keys, filter=~operator_given & filter & selection_mode, eager=eager)
  222. def _(event):
  223. """
  224. Handle operator in selection mode.
  225. """
  226. buff = event.current_buffer
  227. selection_state = buff.selection_state
  228. # Create text object from selection.
  229. if selection_state.type == SelectionType.LINES:
  230. text_obj_type = TextObjectType.LINEWISE
  231. elif selection_state.type == SelectionType.BLOCK:
  232. text_obj_type = TextObjectType.BLOCK
  233. else:
  234. text_obj_type = TextObjectType.INCLUSIVE
  235. text_object = TextObject(
  236. selection_state.original_cursor_position - buff.cursor_position,
  237. type=text_obj_type)
  238. # Execute operator.
  239. operator_func(event, text_object)
  240. # Quit selection mode.
  241. buff.selection_state = None
  242. return operator_func
  243. return decorator
  244. return operator_decorator
  245. def load_vi_bindings(get_search_state=None):
  246. """
  247. Vi extensions.
  248. # Overview of Readline Vi commands:
  249. # http://www.catonmat.net/download/bash-vi-editing-mode-cheat-sheet.pdf
  250. :param get_search_state: None or a callable that takes a
  251. CommandLineInterface and returns a SearchState.
  252. """
  253. # Note: Some key bindings have the "~IsReadOnly()" filter added. This
  254. # prevents the handler to be executed when the focus is on a
  255. # read-only buffer.
  256. # This is however only required for those that change the ViState to
  257. # INSERT mode. The `Buffer` class itself throws the
  258. # `EditReadOnlyBuffer` exception for any text operations which is
  259. # handled correctly. There is no need to add "~IsReadOnly" to all key
  260. # bindings that do text manipulation.
  261. registry = ConditionalRegistry(Registry(), ViMode())
  262. handle = registry.add_binding
  263. # Default get_search_state.
  264. if get_search_state is None:
  265. def get_search_state(cli): return cli.search_state
  266. # (Note: Always take the navigation bindings in read-only mode, even when
  267. # ViState says different.)
  268. navigation_mode = ViNavigationMode()
  269. insert_mode = ViInsertMode()
  270. insert_multiple_mode = ViInsertMultipleMode()
  271. replace_mode = ViReplaceMode()
  272. selection_mode = ViSelectionMode()
  273. operator_given = ViWaitingForTextObjectMode()
  274. digraph_mode = ViDigraphMode()
  275. vi_transform_functions = [
  276. # Rot 13 transformation
  277. (('g', '?'), Always(), lambda string: codecs.encode(string, 'rot_13')),
  278. # To lowercase
  279. (('g', 'u'), Always(), lambda string: string.lower()),
  280. # To uppercase.
  281. (('g', 'U'), Always(), lambda string: string.upper()),
  282. # Swap case.
  283. (('g', '~'), Always(), lambda string: string.swapcase()),
  284. (('~', ), Condition(lambda cli: cli.vi_state.tilde_operator), lambda string: string.swapcase()),
  285. ]
  286. # Insert a character literally (quoted insert).
  287. handle(Keys.ControlV, filter=insert_mode)(get_by_name('quoted-insert'))
  288. @handle(Keys.Escape)
  289. def _(event):
  290. """
  291. Escape goes to vi navigation mode.
  292. """
  293. buffer = event.current_buffer
  294. vi_state = event.cli.vi_state
  295. if vi_state.input_mode in (InputMode.INSERT, InputMode.REPLACE):
  296. buffer.cursor_position += buffer.document.get_cursor_left_position()
  297. vi_state.reset(InputMode.NAVIGATION)
  298. if bool(buffer.selection_state):
  299. buffer.exit_selection()
  300. @handle('k', filter=selection_mode)
  301. def _(event):
  302. """
  303. Arrow up in selection mode.
  304. """
  305. event.current_buffer.cursor_up(count=event.arg)
  306. @handle('j', filter=selection_mode)
  307. def _(event):
  308. """
  309. Arrow down in selection mode.
  310. """
  311. event.current_buffer.cursor_down(count=event.arg)
  312. @handle(Keys.Up, filter=navigation_mode)
  313. @handle(Keys.ControlP, filter=navigation_mode)
  314. def _(event):
  315. """
  316. Arrow up and ControlP in navigation mode go up.
  317. """
  318. event.current_buffer.auto_up(count=event.arg)
  319. @handle('k', filter=navigation_mode)
  320. def _(event):
  321. """
  322. Go up, but if we enter a new history entry, move to the start of the
  323. line.
  324. """
  325. event.current_buffer.auto_up(
  326. count=event.arg, go_to_start_of_line_if_history_changes=True)
  327. @handle(Keys.Down, filter=navigation_mode)
  328. @handle(Keys.ControlN, filter=navigation_mode)
  329. def _(event):
  330. """
  331. Arrow down and Control-N in navigation mode.
  332. """
  333. event.current_buffer.auto_down(count=event.arg)
  334. @handle('j', filter=navigation_mode)
  335. def _(event):
  336. """
  337. Go down, but if we enter a new history entry, go to the start of the line.
  338. """
  339. event.current_buffer.auto_down(
  340. count=event.arg, go_to_start_of_line_if_history_changes=True)
  341. @handle(Keys.ControlH, filter=navigation_mode)
  342. @handle(Keys.Backspace, filter=navigation_mode)
  343. def _(event):
  344. """
  345. In navigation-mode, move cursor.
  346. """
  347. event.current_buffer.cursor_position += \
  348. event.current_buffer.document.get_cursor_left_position(count=event.arg)
  349. @handle(Keys.ControlN, filter=insert_mode)
  350. def _(event):
  351. b = event.current_buffer
  352. if b.complete_state:
  353. b.complete_next()
  354. else:
  355. event.cli.start_completion(select_first=True)
  356. @handle(Keys.ControlP, filter=insert_mode)
  357. def _(event):
  358. """
  359. Control-P: To previous completion.
  360. """
  361. b = event.current_buffer
  362. if b.complete_state:
  363. b.complete_previous()
  364. else:
  365. event.cli.start_completion(select_last=True)
  366. @handle(Keys.ControlY, filter=insert_mode)
  367. def _(event):
  368. """
  369. Accept current completion.
  370. """
  371. event.current_buffer.complete_state = None
  372. @handle(Keys.ControlE, filter=insert_mode)
  373. def _(event):
  374. """
  375. Cancel completion. Go back to originally typed text.
  376. """
  377. event.current_buffer.cancel_completion()
  378. @handle(Keys.ControlJ, filter=navigation_mode) # XXX: only if the selected buffer has a return handler.
  379. def _(event):
  380. """
  381. In navigation mode, pressing enter will always return the input.
  382. """
  383. b = event.current_buffer
  384. if b.accept_action.is_returnable:
  385. b.accept_action.validate_and_handle(event.cli, b)
  386. # ** In navigation mode **
  387. # List of navigation commands: http://hea-www.harvard.edu/~fine/Tech/vi.html
  388. @handle(Keys.Insert, filter=navigation_mode)
  389. def _(event):
  390. " Presing the Insert key. "
  391. event.cli.vi_state.input_mode = InputMode.INSERT
  392. @handle('a', filter=navigation_mode & ~IsReadOnly())
  393. # ~IsReadOnly, because we want to stay in navigation mode for
  394. # read-only buffers.
  395. def _(event):
  396. event.current_buffer.cursor_position += event.current_buffer.document.get_cursor_right_position()
  397. event.cli.vi_state.input_mode = InputMode.INSERT
  398. @handle('A', filter=navigation_mode & ~IsReadOnly())
  399. def _(event):
  400. event.current_buffer.cursor_position += event.current_buffer.document.get_end_of_line_position()
  401. event.cli.vi_state.input_mode = InputMode.INSERT
  402. @handle('C', filter=navigation_mode & ~IsReadOnly())
  403. def _(event):
  404. """
  405. # Change to end of line.
  406. # Same as 'c$' (which is implemented elsewhere.)
  407. """
  408. buffer = event.current_buffer
  409. deleted = buffer.delete(count=buffer.document.get_end_of_line_position())
  410. event.cli.clipboard.set_text(deleted)
  411. event.cli.vi_state.input_mode = InputMode.INSERT
  412. @handle('c', 'c', filter=navigation_mode & ~IsReadOnly())
  413. @handle('S', filter=navigation_mode & ~IsReadOnly())
  414. def _(event): # TODO: implement 'arg'
  415. """
  416. Change current line
  417. """
  418. buffer = event.current_buffer
  419. # We copy the whole line.
  420. data = ClipboardData(buffer.document.current_line, SelectionType.LINES)
  421. event.cli.clipboard.set_data(data)
  422. # But we delete after the whitespace
  423. buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True)
  424. buffer.delete(count=buffer.document.get_end_of_line_position())
  425. event.cli.vi_state.input_mode = InputMode.INSERT
  426. @handle('D', filter=navigation_mode)
  427. def _(event):
  428. buffer = event.current_buffer
  429. deleted = buffer.delete(count=buffer.document.get_end_of_line_position())
  430. event.cli.clipboard.set_text(deleted)
  431. @handle('d', 'd', filter=navigation_mode)
  432. def _(event):
  433. """
  434. Delete line. (Or the following 'n' lines.)
  435. """
  436. buffer = event.current_buffer
  437. # Split string in before/deleted/after text.
  438. lines = buffer.document.lines
  439. before = '\n'.join(lines[:buffer.document.cursor_position_row])
  440. deleted = '\n'.join(lines[buffer.document.cursor_position_row:
  441. buffer.document.cursor_position_row + event.arg])
  442. after = '\n'.join(lines[buffer.document.cursor_position_row + event.arg:])
  443. # Set new text.
  444. if before and after:
  445. before = before + '\n'
  446. # Set text and cursor position.
  447. buffer.document = Document(
  448. text=before + after,
  449. # Cursor At the start of the first 'after' line, after the leading whitespace.
  450. cursor_position = len(before) + len(after) - len(after.lstrip(' ')))
  451. # Set clipboard data
  452. event.cli.clipboard.set_data(ClipboardData(deleted, SelectionType.LINES))
  453. @handle('x', filter=selection_mode)
  454. def _(event):
  455. """
  456. Cut selection.
  457. ('x' is not an operator.)
  458. """
  459. clipboard_data = event.current_buffer.cut_selection()
  460. event.cli.clipboard.set_data(clipboard_data)
  461. @handle('i', filter=navigation_mode & ~IsReadOnly())
  462. def _(event):
  463. event.cli.vi_state.input_mode = InputMode.INSERT
  464. @handle('I', filter=navigation_mode & ~IsReadOnly())
  465. def _(event):
  466. event.cli.vi_state.input_mode = InputMode.INSERT
  467. event.current_buffer.cursor_position += \
  468. event.current_buffer.document.get_start_of_line_position(after_whitespace=True)
  469. @Condition
  470. def in_block_selection(cli):
  471. buff = cli.current_buffer
  472. return buff.selection_state and buff.selection_state.type == SelectionType.BLOCK
  473. @handle('I', filter=in_block_selection & ~IsReadOnly())
  474. def go_to_block_selection(event, after=False):
  475. " Insert in block selection mode. "
  476. buff = event.current_buffer
  477. # Store all cursor positions.
  478. positions = []
  479. if after:
  480. def get_pos(from_to):
  481. return from_to[1] + 1
  482. else:
  483. def get_pos(from_to):
  484. return from_to[0]
  485. for i, from_to in enumerate(buff.document.selection_ranges()):
  486. positions.append(get_pos(from_to))
  487. if i == 0:
  488. buff.cursor_position = get_pos(from_to)
  489. buff.multiple_cursor_positions = positions
  490. # Go to 'INSERT_MULTIPLE' mode.
  491. event.cli.vi_state.input_mode = InputMode.INSERT_MULTIPLE
  492. buff.exit_selection()
  493. @handle('A', filter=in_block_selection & ~IsReadOnly())
  494. def _(event):
  495. go_to_block_selection(event, after=True)
  496. @handle('J', filter=navigation_mode & ~IsReadOnly())
  497. def _(event):
  498. " Join lines. "
  499. for i in range(event.arg):
  500. event.current_buffer.join_next_line()
  501. @handle('g', 'J', filter=navigation_mode & ~IsReadOnly())
  502. def _(event):
  503. " Join lines without space. "
  504. for i in range(event.arg):
  505. event.current_buffer.join_next_line(separator='')
  506. @handle('J', filter=selection_mode & ~IsReadOnly())
  507. def _(event):
  508. " Join selected lines. "
  509. event.current_buffer.join_selected_lines()
  510. @handle('g', 'J', filter=selection_mode & ~IsReadOnly())
  511. def _(event):
  512. " Join selected lines without space. "
  513. event.current_buffer.join_selected_lines(separator='')
  514. @handle('p', filter=navigation_mode)
  515. def _(event):
  516. """
  517. Paste after
  518. """
  519. event.current_buffer.paste_clipboard_data(
  520. event.cli.clipboard.get_data(),
  521. count=event.arg,
  522. paste_mode=PasteMode.VI_AFTER)
  523. @handle('P', filter=navigation_mode)
  524. def _(event):
  525. """
  526. Paste before
  527. """
  528. event.current_buffer.paste_clipboard_data(
  529. event.cli.clipboard.get_data(),
  530. count=event.arg,
  531. paste_mode=PasteMode.VI_BEFORE)
  532. @handle('"', Keys.Any, 'p', filter=navigation_mode)
  533. def _(event):
  534. " Paste from named register. "
  535. c = event.key_sequence[1].data
  536. if c in vi_register_names:
  537. data = event.cli.vi_state.named_registers.get(c)
  538. if data:
  539. event.current_buffer.paste_clipboard_data(
  540. data, count=event.arg, paste_mode=PasteMode.VI_AFTER)
  541. @handle('"', Keys.Any, 'P', filter=navigation_mode)
  542. def _(event):
  543. " Paste (before) from named register. "
  544. c = event.key_sequence[1].data
  545. if c in vi_register_names:
  546. data = event.cli.vi_state.named_registers.get(c)
  547. if data:
  548. event.current_buffer.paste_clipboard_data(
  549. data, count=event.arg, paste_mode=PasteMode.VI_BEFORE)
  550. @handle('r', Keys.Any, filter=navigation_mode)
  551. def _(event):
  552. """
  553. Replace single character under cursor
  554. """
  555. event.current_buffer.insert_text(event.data * event.arg, overwrite=True)
  556. event.current_buffer.cursor_position -= 1
  557. @handle('R', filter=navigation_mode)
  558. def _(event):
  559. """
  560. Go to 'replace'-mode.
  561. """
  562. event.cli.vi_state.input_mode = InputMode.REPLACE
  563. @handle('s', filter=navigation_mode & ~IsReadOnly())
  564. def _(event):
  565. """
  566. Substitute with new text
  567. (Delete character(s) and go to insert mode.)
  568. """
  569. text = event.current_buffer.delete(count=event.arg)
  570. event.cli.clipboard.set_text(text)
  571. event.cli.vi_state.input_mode = InputMode.INSERT
  572. @handle('u', filter=navigation_mode, save_before=(lambda e: False))
  573. def _(event):
  574. for i in range(event.arg):
  575. event.current_buffer.undo()
  576. @handle('V', filter=navigation_mode)
  577. def _(event):
  578. """
  579. Start lines selection.
  580. """
  581. event.current_buffer.start_selection(selection_type=SelectionType.LINES)
  582. @handle(Keys.ControlV, filter=navigation_mode)
  583. def _(event):
  584. " Enter block selection mode. "
  585. event.current_buffer.start_selection(selection_type=SelectionType.BLOCK)
  586. @handle('V', filter=selection_mode)
  587. def _(event):
  588. """
  589. Exit line selection mode, or go from non line selection mode to line
  590. selection mode.
  591. """
  592. selection_state = event.current_buffer.selection_state
  593. if selection_state.type != SelectionType.LINES:
  594. selection_state.type = SelectionType.LINES
  595. else:
  596. event.current_buffer.exit_selection()
  597. @handle('v', filter=navigation_mode)
  598. def _(event):
  599. " Enter character selection mode. "
  600. event.current_buffer.start_selection(selection_type=SelectionType.CHARACTERS)
  601. @handle('v', filter=selection_mode)
  602. def _(event):
  603. """
  604. Exit character selection mode, or go from non-character-selection mode
  605. to character selection mode.
  606. """
  607. selection_state = event.current_buffer.selection_state
  608. if selection_state.type != SelectionType.CHARACTERS:
  609. selection_state.type = SelectionType.CHARACTERS
  610. else:
  611. event.current_buffer.exit_selection()
  612. @handle(Keys.ControlV, filter=selection_mode)
  613. def _(event):
  614. """
  615. Exit block selection mode, or go from non block selection mode to block
  616. selection mode.
  617. """
  618. selection_state = event.current_buffer.selection_state
  619. if selection_state.type != SelectionType.BLOCK:
  620. selection_state.type = SelectionType.BLOCK
  621. else:
  622. event.current_buffer.exit_selection()
  623. @handle('a', 'w', filter=selection_mode)
  624. @handle('a', 'W', filter=selection_mode)
  625. def _(event):
  626. """
  627. Switch from visual linewise mode to visual characterwise mode.
  628. """
  629. buffer = event.current_buffer
  630. if buffer.selection_state and buffer.selection_state.type == SelectionType.LINES:
  631. buffer.selection_state.type = SelectionType.CHARACTERS
  632. @handle('x', filter=navigation_mode)
  633. def _(event):
  634. """
  635. Delete character.
  636. """
  637. text = event.current_buffer.delete(count=event.arg)
  638. event.cli.clipboard.set_text(text)
  639. @handle('X', filter=navigation_mode)
  640. def _(event):
  641. text = event.current_buffer.delete_before_cursor()
  642. event.cli.clipboard.set_text(text)
  643. @handle('y', 'y', filter=navigation_mode)
  644. @handle('Y', filter=navigation_mode)
  645. def _(event):
  646. """
  647. Yank the whole line.
  648. """
  649. text = '\n'.join(event.current_buffer.document.lines_from_current[:event.arg])
  650. event.cli.clipboard.set_data(ClipboardData(text, SelectionType.LINES))
  651. @handle('+', filter=navigation_mode)
  652. def _(event):
  653. """
  654. Move to first non whitespace of next line
  655. """
  656. buffer = event.current_buffer
  657. buffer.cursor_position += buffer.document.get_cursor_down_position(count=event.arg)
  658. buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True)
  659. @handle('-', filter=navigation_mode)
  660. def _(event):
  661. """
  662. Move to first non whitespace of previous line
  663. """
  664. buffer = event.current_buffer
  665. buffer.cursor_position += buffer.document.get_cursor_up_position(count=event.arg)
  666. buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True)
  667. @handle('>', '>', filter=navigation_mode)
  668. def _(event):
  669. """
  670. Indent lines.
  671. """
  672. buffer = event.current_buffer
  673. current_row = buffer.document.cursor_position_row
  674. indent(buffer, current_row, current_row + event.arg)
  675. @handle('<', '<', filter=navigation_mode)
  676. def _(event):
  677. """
  678. Unindent lines.
  679. """
  680. current_row = event.current_buffer.document.cursor_position_row
  681. unindent(event.current_buffer, current_row, current_row + event.arg)
  682. @handle('O', filter=navigation_mode & ~IsReadOnly())
  683. def _(event):
  684. """
  685. Open line above and enter insertion mode
  686. """
  687. event.current_buffer.insert_line_above(
  688. copy_margin=not event.cli.in_paste_mode)
  689. event.cli.vi_state.input_mode = InputMode.INSERT
  690. @handle('o', filter=navigation_mode & ~IsReadOnly())
  691. def _(event):
  692. """
  693. Open line below and enter insertion mode
  694. """
  695. event.current_buffer.insert_line_below(
  696. copy_margin=not event.cli.in_paste_mode)
  697. event.cli.vi_state.input_mode = InputMode.INSERT
  698. @handle('~', filter=navigation_mode)
  699. def _(event):
  700. """
  701. Reverse case of current character and move cursor forward.
  702. """
  703. buffer = event.current_buffer
  704. c = buffer.document.current_char
  705. if c is not None and c != '\n':
  706. buffer.insert_text(c.swapcase(), overwrite=True)
  707. @handle('g', 'u', 'u', filter=navigation_mode & ~IsReadOnly())
  708. def _(event):
  709. " Lowercase current line. "
  710. buff = event.current_buffer
  711. buff.transform_current_line(lambda s: s.lower())
  712. @handle('g', 'U', 'U', filter=navigation_mode & ~IsReadOnly())
  713. def _(event):
  714. " Uppercase current line. "
  715. buff = event.current_buffer
  716. buff.transform_current_line(lambda s: s.upper())
  717. @handle('g', '~', '~', filter=navigation_mode & ~IsReadOnly())
  718. def _(event):
  719. " Swap case of the current line. "
  720. buff = event.current_buffer
  721. buff.transform_current_line(lambda s: s.swapcase())
  722. @handle('#', filter=navigation_mode)
  723. def _(event):
  724. """
  725. Go to previous occurence of this word.
  726. """
  727. b = event.cli.current_buffer
  728. search_state = get_search_state(event.cli)
  729. search_state.text = b.document.get_word_under_cursor()
  730. search_state.direction = IncrementalSearchDirection.BACKWARD
  731. b.apply_search(search_state, count=event.arg,
  732. include_current_position=False)
  733. @handle('*', filter=navigation_mode)
  734. def _(event):
  735. """
  736. Go to next occurence of this word.
  737. """
  738. b = event.cli.current_buffer
  739. search_state = get_search_state(event.cli)
  740. search_state.text = b.document.get_word_under_cursor()
  741. search_state.direction = IncrementalSearchDirection.FORWARD
  742. b.apply_search(search_state, count=event.arg,
  743. include_current_position=False)
  744. @handle('(', filter=navigation_mode)
  745. def _(event):
  746. # TODO: go to begin of sentence.
  747. # XXX: should become text_object.
  748. pass
  749. @handle(')', filter=navigation_mode)
  750. def _(event):
  751. # TODO: go to end of sentence.
  752. # XXX: should become text_object.
  753. pass
  754. operator = create_operator_decorator(registry)
  755. text_object = create_text_object_decorator(registry)
  756. @text_object(Keys.Any, filter=operator_given)
  757. def _(event):
  758. """
  759. Unknown key binding while waiting for a text object.
  760. """
  761. event.cli.output.bell()
  762. #
  763. # *** Operators ***
  764. #
  765. def create_delete_and_change_operators(delete_only, with_register=False):
  766. """
  767. Delete and change operators.
  768. :param delete_only: Create an operator that deletes, but doesn't go to insert mode.
  769. :param with_register: Copy the deleted text to this named register instead of the clipboard.
  770. """
  771. if with_register:
  772. handler_keys = ('"', Keys.Any, 'cd'[delete_only])
  773. else:
  774. handler_keys = 'cd'[delete_only]
  775. @operator(*handler_keys, filter=~IsReadOnly())
  776. def delete_or_change_operator(event, text_object):
  777. clipboard_data = None
  778. buff = event.current_buffer
  779. if text_object:
  780. new_document, clipboard_data = text_object.cut(buff)
  781. buff.document = new_document
  782. # Set deleted/changed text to clipboard or named register.
  783. if clipboard_data and clipboard_data.text:
  784. if with_register:
  785. reg_name = event.key_sequence[1].data
  786. if reg_name in vi_register_names:
  787. event.cli.vi_state.named_registers[reg_name] = clipboard_data
  788. else:
  789. event.cli.clipboard.set_data(clipboard_data)
  790. # Only go back to insert mode in case of 'change'.
  791. if not delete_only:
  792. event.cli.vi_state.input_mode = InputMode.INSERT
  793. create_delete_and_change_operators(False, False)
  794. create_delete_and_change_operators(False, True)
  795. create_delete_and_change_operators(True, False)
  796. create_delete_and_change_operators(True, True)
  797. def create_transform_handler(filter, transform_func, *a):
  798. @operator(*a, filter=filter & ~IsReadOnly())
  799. def _(event, text_object):
  800. """
  801. Apply transformation (uppercase, lowercase, rot13, swap case).
  802. """
  803. buff = event.current_buffer
  804. start, end = text_object.operator_range(buff.document)
  805. if start < end:
  806. # Transform.
  807. buff.transform_region(
  808. buff.cursor_position + start,
  809. buff.cursor_position + end,
  810. transform_func)
  811. # Move cursor
  812. buff.cursor_position += (text_object.end or text_object.start)
  813. for k, f, func in vi_transform_functions:
  814. create_transform_handler(f, func, *k)
  815. @operator('y')
  816. def yank_handler(event, text_object):
  817. """
  818. Yank operator. (Copy text.)
  819. """
  820. _, clipboard_data = text_object.cut(event.current_buffer)
  821. if clipboard_data.text:
  822. event.cli.clipboard.set_data(clipboard_data)
  823. @operator('"', Keys.Any, 'y')
  824. def _(event, text_object):
  825. " Yank selection to named register. "
  826. c = event.key_sequence[1].data
  827. if c in vi_register_names:
  828. _, clipboard_data = text_object.cut(event.current_buffer)
  829. event.cli.vi_state.named_registers[c] = clipboard_data
  830. @operator('>')
  831. def _(event, text_object):
  832. """
  833. Indent.
  834. """
  835. buff = event.current_buffer
  836. from_, to = text_object.get_line_numbers(buff)
  837. indent(buff, from_, to + 1, count=event.arg)
  838. @operator('<')
  839. def _(event, text_object):
  840. """
  841. Unindent.
  842. """
  843. buff = event.current_buffer
  844. from_, to = text_object.get_line_numbers(buff)
  845. unindent(buff, from_, to + 1, count=event.arg)
  846. @operator('g', 'q')
  847. def _(event, text_object):
  848. """
  849. Reshape text.
  850. """
  851. buff = event.current_buffer
  852. from_, to = text_object.get_line_numbers(buff)
  853. reshape_text(buff, from_, to)
  854. #
  855. # *** Text objects ***
  856. #
  857. @text_object('b')
  858. def _(event):
  859. """ Move one word or token left. """
  860. return TextObject(event.current_buffer.document.find_start_of_previous_word(count=event.arg) or 0)
  861. @text_object('B')
  862. def _(event):
  863. """ Move one non-blank word left """
  864. return TextObject(event.current_buffer.document.find_start_of_previous_word(count=event.arg, WORD=True) or 0)
  865. @text_object('$')
  866. def key_dollar(event):
  867. """ 'c$', 'd$' and '$': Delete/change/move until end of line. """
  868. return TextObject(event.current_buffer.document.get_end_of_line_position())
  869. @text_object('w')
  870. def _(event):
  871. """ 'word' forward. 'cw', 'dw', 'w': Delete/change/move one word. """
  872. return TextObject(event.current_buffer.document.find_next_word_beginning(count=event.arg) or
  873. event.current_buffer.document.get_end_of_document_position())
  874. @text_object('W')
  875. def _(event):
  876. """ 'WORD' forward. 'cW', 'dW', 'W': Delete/change/move one WORD. """
  877. return TextObject(event.current_buffer.document.find_next_word_beginning(count=event.arg, WORD=True) or
  878. event.current_buffer.document.get_end_of_document_position())
  879. @text_object('e')
  880. def _(event):
  881. """ End of 'word': 'ce', 'de', 'e' """
  882. end = event.current_buffer.document.find_next_word_ending(count=event.arg)
  883. return TextObject(end - 1 if end else 0, type=TextObjectType.INCLUSIVE)
  884. @text_object('E')
  885. def _(event):
  886. """ End of 'WORD': 'cE', 'dE', 'E' """
  887. end = event.current_buffer.document.find_next_word_ending(count=event.arg, WORD=True)
  888. return TextObject(end - 1 if end else 0, type=TextObjectType.INCLUSIVE)
  889. @text_object('i', 'w', no_move_handler=True)
  890. def _(event):
  891. """ Inner 'word': ciw and diw """
  892. start, end = event.current_buffer.document.find_boundaries_of_current_word()
  893. return TextObject(start, end)
  894. @text_object('a', 'w', no_move_handler=True)
  895. def _(event):
  896. """ A 'word': caw and daw """
  897. start, end = event.current_buffer.document.find_boundaries_of_current_word(include_trailing_whitespace=True)
  898. return TextObject(start, end)
  899. @text_object('i', 'W', no_move_handler=True)
  900. def _(event):
  901. """ Inner 'WORD': ciW and diW """
  902. start, end = event.current_buffer.document.find_boundaries_of_current_word(WORD=True)
  903. return TextObject(start, end)
  904. @text_object('a', 'W', no_move_handler=True)
  905. def _(event):
  906. """ A 'WORD': caw and daw """
  907. start, end = event.current_buffer.document.find_boundaries_of_current_word(WORD=True, include_trailing_whitespace=True)
  908. return TextObject(start, end)
  909. @text_object('a', 'p', no_move_handler=True)
  910. def _(event):
  911. """
  912. Auto paragraph.
  913. """
  914. start = event.current_buffer.document.start_of_paragraph()
  915. end = event.current_buffer.document.end_of_paragraph(count=event.arg)
  916. return TextObject(start, end)
  917. @text_object('^')
  918. def key_circumflex(event):
  919. """ 'c^', 'd^' and '^': Soft start of line, after whitespace. """
  920. return TextObject(event.current_buffer.document.get_start_of_line_position(after_whitespace=True))
  921. @text_object('0')
  922. def key_zero(event):
  923. """
  924. 'c0', 'd0': Hard start of line, before whitespace.
  925. (The move '0' key is implemented elsewhere, because a '0' could also change the `arg`.)
  926. """
  927. return TextObject(event.current_buffer.document.get_start_of_line_position(after_whitespace=False))
  928. def create_ci_ca_handles(ci_start, ci_end, inner, key=None):
  929. # TODO: 'dat', 'dit', (tags (like xml)
  930. """
  931. Delete/Change string between this start and stop character. But keep these characters.
  932. This implements all the ci", ci<, ci{, ci(, di", di<, ca", ca<, ... combinations.
  933. """
  934. def handler(event):
  935. if ci_start == ci_end:
  936. # Quotes
  937. start = event.current_buffer.document.find_backwards(ci_start, in_current_line=False)
  938. end = event.current_buffer.document.find(ci_end, in_current_line=False)
  939. else:
  940. # Brackets
  941. start = event.current_buffer.document.find_enclosing_bracket_left(ci_start, ci_end)
  942. end = event.current_buffer.document.find_enclosing_bracket_right(ci_start, ci_end)
  943. if start is not None and end is not None:
  944. offset = 0 if inner else 1
  945. return TextObject(start + 1 - offset, end + offset)
  946. else:
  947. # Nothing found.
  948. return TextObject(0)
  949. if key is None:
  950. text_object('ai'[inner], ci_start, no_move_handler=True)(handler)
  951. text_object('ai'[inner], ci_end, no_move_handler=True)(handler)
  952. else:
  953. text_object('ai'[inner], key, no_move_handler=True)(handler)
  954. for inner in (False, True):
  955. for ci_start, ci_end in [('"', '"'), ("'", "'"), ("`", "`"),
  956. ('[', ']'), ('<', '>'), ('{', '}'), ('(', ')')]:
  957. create_ci_ca_handles(ci_start, ci_end, inner)
  958. create_ci_ca_handles('(', ')', inner, 'b') # 'dab', 'dib'
  959. create_ci_ca_handles('{', '}', inner, 'B') # 'daB', 'diB'
  960. @text_object('{')
  961. def _(event):
  962. """
  963. Move to previous blank-line separated section.
  964. Implements '{', 'c{', 'd{', 'y{'
  965. """
  966. index = event.current_buffer.document.start_of_paragraph(
  967. count=event.arg, before=True)
  968. return TextObject(index)
  969. @text_object('}')
  970. def _(event):
  971. """
  972. Move to next blank-line separated section.
  973. Implements '}', 'c}', 'd}', 'y}'
  974. """
  975. index = event.current_buffer.document.end_of_paragraph(count=event.arg, after=True)
  976. return TextObject(index)
  977. @text_object('f', Keys.Any)
  978. def _(event):
  979. """
  980. Go to next occurance of character. Typing 'fx' will move the
  981. cursor to the next occurance of character. 'x'.
  982. """
  983. event.cli.vi_state.last_character_find = CharacterFind(event.data, False)
  984. match = event.current_buffer.document.find(
  985. event.data, in_current_line=True, count=event.arg)
  986. if match:
  987. return TextObject(match, type=TextObjectType.INCLUSIVE)
  988. else:
  989. return TextObject(0)
  990. @text_object('F', Keys.Any)
  991. def _(event):
  992. """
  993. Go to previous occurance of character. Typing 'Fx' will move the
  994. cursor to the previous occurance of character. 'x'.
  995. """
  996. event.cli.vi_state.last_character_find = CharacterFind(event.data, True)
  997. return TextObject(event.current_buffer.document.find_backwards(
  998. event.data, in_current_line=True, count=event.arg) or 0)
  999. @text_object('t', Keys.Any)
  1000. def _(event):
  1001. """
  1002. Move right to the next occurance of c, then one char backward.
  1003. """
  1004. event.cli.vi_state.last_character_find = CharacterFind(event.data, False)
  1005. match = event.current_buffer.document.find(
  1006. event.data, in_current_line=True, count=event.arg)
  1007. if match:
  1008. return TextObject(match - 1, type=TextObjectType.INCLUSIVE)
  1009. else:
  1010. return TextObject(0)
  1011. @text_object('T', Keys.Any)
  1012. def _(event):
  1013. """
  1014. Move left to the previous occurance of c, then one char forward.
  1015. """
  1016. event.cli.vi_state.last_character_find = CharacterFind(event.data, True)
  1017. match = event.current_buffer.document.find_backwards(
  1018. event.data, in_current_line=True, count=event.arg)
  1019. return TextObject(match + 1 if match else 0)
  1020. def repeat(reverse):
  1021. """
  1022. Create ',' and ';' commands.
  1023. """
  1024. @text_object(',' if reverse else ';')
  1025. def _(event):
  1026. # Repeat the last 'f'/'F'/'t'/'T' command.
  1027. pos = 0
  1028. vi_state = event.cli.vi_state
  1029. type = TextObjectType.EXCLUSIVE
  1030. if vi_state.last_character_find:
  1031. char = vi_state.last_character_find.character
  1032. backwards = vi_state.last_character_find.backwards
  1033. if reverse:
  1034. backwards = not backwards
  1035. if backwards:
  1036. pos = event.current_buffer.document.find_backwards(char, in_current_line=True, count=event.arg)
  1037. else:
  1038. pos = event.current_buffer.document.find(char, in_current_line=True, count=event.arg)
  1039. type = TextObjectType.INCLUSIVE
  1040. if pos:
  1041. return TextObject(pos, type=type)
  1042. else:
  1043. return TextObject(0)
  1044. repeat(True)
  1045. repeat(False)
  1046. @text_object('h')
  1047. @text_object(Keys.Left)
  1048. def _(event):
  1049. """ Implements 'ch', 'dh', 'h': Cursor left. """
  1050. return TextObject(event.current_buffer.document.get_cursor_left_position(count=event.arg))
  1051. @text_object('j', no_move_handler=True, no_selection_handler=True)
  1052. # Note: We also need `no_selection_handler`, because we in
  1053. # selection mode, we prefer the other 'j' binding that keeps
  1054. # `buffer.preferred_column`.
  1055. def _(event):
  1056. """ Implements 'cj', 'dj', 'j', ... Cursor up. """
  1057. return TextObject(event.current_buffer.document.get_cursor_down_position(count=event.arg),
  1058. type=TextObjectType.LINEWISE)
  1059. @text_object('k', no_move_handler=True, no_selection_handler=True)
  1060. def _(event):
  1061. """ Implements 'ck', 'dk', 'k', ... Cursor up. """
  1062. return TextObject(event.current_buffer.document.get_cursor_up_position(count=event.arg),
  1063. type=TextObjectType.LINEWISE)
  1064. @text_object('l')
  1065. @text_object(' ')
  1066. @text_object(Keys.Right)
  1067. def _(event):
  1068. """ Implements 'cl', 'dl', 'l', 'c ', 'd ', ' '. Cursor right. """
  1069. return TextObject(event.current_buffer.document.get_cursor_right_position(count=event.arg))
  1070. @text_object('H')
  1071. def _(event):
  1072. """
  1073. Moves to the start of the visible region. (Below the scroll offset.)
  1074. Implements 'cH', 'dH', 'H'.
  1075. """
  1076. w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name)
  1077. b = event.current_buffer
  1078. if w and w.render_info:
  1079. # When we find a Window that has BufferControl showing this window,
  1080. # move to the start of the visible area.
  1081. pos = (b.document.translate_row_col_to_index(
  1082. w.render_info.first_visible_line(after_scroll_offset=True), 0) -
  1083. b.cursor_position)
  1084. else:
  1085. # Otherwise, move to the start of the input.
  1086. pos = -len(b.document.text_before_cursor)
  1087. return TextObject(pos, type=TextObjectType.LINEWISE)
  1088. @text_object('M')
  1089. def _(event):
  1090. """
  1091. Moves cursor to the vertical center of the visible region.
  1092. Implements 'cM', 'dM', 'M'.
  1093. """
  1094. w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name)
  1095. b = event.current_buffer
  1096. if w and w.render_info:
  1097. # When we find a Window that has BufferControl showing this window,
  1098. # move to the center of the visible area.
  1099. pos = (b.document.translate_row_col_to_index(
  1100. w.render_info.center_visible_line(), 0) -
  1101. b.cursor_position)
  1102. else:
  1103. # Otherwise, move to the start of the input.
  1104. pos = -len(b.document.text_before_cursor)
  1105. return TextObject(pos, type=TextObjectType.LINEWISE)
  1106. @text_object('L')
  1107. def _(event):
  1108. """
  1109. Moves to the end of the visible region. (Above the scroll offset.)
  1110. """
  1111. w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name)
  1112. b = event.current_buffer
  1113. if w and w.render_info:
  1114. # When we find a Window that has BufferControl showing this window,
  1115. # move to the end of the visible area.
  1116. pos = (b.document.translate_row_col_to_index(
  1117. w.render_info.last_visible_line(before_scroll_offset=True), 0) -
  1118. b.cursor_position)
  1119. else:
  1120. # Otherwise, move to the end of the input.
  1121. pos = len(b.document.text_after_cursor)
  1122. return TextObject(pos, type=TextObjectType.LINEWISE)
  1123. @text_object('n', no_move_handler=True)
  1124. def _(event):
  1125. " Search next. "
  1126. buff = event.current_buffer
  1127. cursor_position = buff.get_search_position(
  1128. get_search_state(event.cli), include_current_position=False,
  1129. count=event.arg)
  1130. return TextObject(cursor_position - buff.cursor_position)
  1131. @handle('n', filter=navigation_mode)
  1132. def _(event):
  1133. " Search next in navigation mode. (This goes through the history.) "
  1134. event.current_buffer.apply_search(
  1135. get_search_state(event.cli), include_current_position=False,
  1136. count=event.arg)
  1137. @text_object('N', no_move_handler=True)
  1138. def _(event):
  1139. " Search previous. "
  1140. buff = event.current_buffer
  1141. cursor_position = buff.get_search_position(
  1142. ~get_search_state(event.cli), include_current_position=False,
  1143. count=event.arg)
  1144. return TextObject(cursor_position - buff.cursor_position)
  1145. @handle('N', filter=navigation_mode)
  1146. def _(event):
  1147. " Search previous in navigation mode. (This goes through the history.) "
  1148. event.current_buffer.apply_search(
  1149. ~get_search_state(event.cli), include_current_position=False,
  1150. count=event.arg)
  1151. @handle('z', '+', filter=navigation_mode|selection_mode)
  1152. @handle('z', 't', filter=navigation_mode|selection_mode)
  1153. @handle('z', Keys.ControlJ, filter=navigation_mode|selection_mode)
  1154. def _(event):
  1155. """
  1156. Scrolls the window to makes the current line the first line in the visible region.
  1157. """
  1158. w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name)
  1159. b = event.cli.current_buffer
  1160. w.vertical_scroll = b.document.cursor_position_row
  1161. @handle('z', '-', filter=navigation_mode|selection_mode)
  1162. @handle('z', 'b', filter=navigation_mode|selection_mode)
  1163. def _(event):
  1164. """
  1165. Scrolls the window to makes the current line the last line in the visible region.
  1166. """
  1167. w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name)
  1168. # We can safely set the scroll offset to zero; the Window will meke
  1169. # sure that it scrolls at least enough to make the cursor visible
  1170. # again.
  1171. w.vertical_scroll = 0
  1172. @handle('z', 'z', filter=navigation_mode|selection_mode)
  1173. def _(event):
  1174. """
  1175. Center Window vertically around cursor.
  1176. """
  1177. w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name)
  1178. b = event.cli.current_buffer
  1179. if w and w.render_info:
  1180. info = w.render_info
  1181. # Calculate the offset that we need in order to position the row
  1182. # containing the cursor in the center.
  1183. scroll_height = info.window_height // 2
  1184. y = max(0, b.document.cursor_position_row - 1)
  1185. height = 0
  1186. while y > 0:
  1187. line_height = info.get_height_for_line(y)
  1188. if height + line_height < scroll_height:
  1189. height += line_height
  1190. y -= 1
  1191. else:
  1192. break
  1193. w.vertical_scroll = y
  1194. @text_object('%')
  1195. def _(event):
  1196. """
  1197. Implements 'c%', 'd%', '%, 'y%' (Move to corresponding bracket.)
  1198. If an 'arg' has been given, go this this % position in the file.
  1199. """
  1200. buffer = event.current_buffer
  1201. if event._arg:
  1202. # If 'arg' has been given, the meaning of % is to go to the 'x%'
  1203. # row in the file.
  1204. if 0 < event.arg <= 100:
  1205. absolute_index = buffer.document.translate_row_col_to_index(
  1206. int((event.arg * buffer.document.line_count - 1) / 100), 0)
  1207. return TextObject(absolute_index - buffer.document.cursor_position, type=TextObjectType.LINEWISE)
  1208. else:
  1209. return TextObject(0) # Do nothing.
  1210. else:
  1211. # Move to the corresponding opening/closing bracket (()'s, []'s and {}'s).
  1212. match = buffer.document.find_matching_bracket_position()
  1213. if match:
  1214. return TextObject(match, type=TextObjectType.INCLUSIVE)
  1215. else:
  1216. return TextObject(0)
  1217. @text_object('|')
  1218. def _(event):
  1219. # Move to the n-th column (you may specify the argument n by typing
  1220. # it on number keys, for example, 20|).
  1221. return TextObject(event.current_buffer.document.get_column_cursor_position(event.arg - 1))
  1222. @text_object('g', 'g')
  1223. def _(event):
  1224. """
  1225. Implements 'gg', 'cgg', 'ygg'
  1226. """
  1227. d = event.current_buffer.document
  1228. if event._arg:
  1229. # Move to the given line.
  1230. return TextObject(d.translate_row_col_to_index(event.arg - 1, 0) - d.cursor_position, type=TextObjectType.LINEWISE)
  1231. else:
  1232. # Move to the top of the input.
  1233. return TextObject(d.get_start_of_document_position(), type=TextObjectType.LINEWISE)
  1234. @text_object('g', '_')
  1235. def _(event):
  1236. """
  1237. Go to last non-blank of line.
  1238. 'g_', 'cg_', 'yg_', etc..
  1239. """
  1240. return TextObject(
  1241. event.current_buffer.document.last_non_blank_of_current_line_position(), type=TextObjectType.INCLUSIVE)
  1242. @text_object('g', 'e')
  1243. def _(event):
  1244. """
  1245. Go to last character of previous word.
  1246. 'ge', 'cge', 'yge', etc..
  1247. """
  1248. prev_end = event.current_buffer.document.find_previous_word_ending(count=event.arg)
  1249. return TextObject(prev_end - 1 if prev_end is not None else 0, type=TextObjectType.INCLUSIVE)
  1250. @text_object('g', 'E')
  1251. def _(event):
  1252. """
  1253. Go to last character of previous WORD.
  1254. 'gE', 'cgE', 'ygE', etc..
  1255. """
  1256. prev_end = event.current_buffer.document.find_previous_word_ending(count=event.arg, WORD=True)
  1257. return TextObject(prev_end - 1 if prev_end is not None else 0, type=TextObjectType.INCLUSIVE)
  1258. @text_object('g', 'm')
  1259. def _(event):
  1260. """
  1261. Like g0, but half a screenwidth to the right. (Or as much as possible.)
  1262. """
  1263. w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name)
  1264. buff = event.current_buffer
  1265. if w and w.render_info:
  1266. width = w.render_info.window_width
  1267. start = buff.document.get_start_of_line_position(after_whitespace=False)
  1268. start += int(min(width / 2, len(buff.document.current_line)))
  1269. return TextObject(start, type=TextObjectType.INCLUSIVE)
  1270. return TextObject(0)
  1271. @text_object('G')
  1272. def _(event):
  1273. """
  1274. Go to the end of the document. (If no arg has been given.)
  1275. """
  1276. buf = event.current_buffer
  1277. return TextObject(buf.document.translate_row_col_to_index(buf.document.line_count - 1, 0) -
  1278. buf.cursor_position, type=TextObjectType.LINEWISE)
  1279. #
  1280. # *** Other ***
  1281. #
  1282. @handle('G', filter=HasArg())
  1283. def _(event):
  1284. """
  1285. If an argument is given, move to this line in the history. (for
  1286. example, 15G)
  1287. """
  1288. event.current_buffer.go_to_history(event.arg - 1)
  1289. for n in '123456789':
  1290. @handle(n, filter=navigation_mode|selection_mode|operator_given)
  1291. def _(event):
  1292. """
  1293. Always handle numberics in navigation mode as arg.
  1294. """
  1295. event.append_to_arg_count(event.data)
  1296. @handle('0', filter=(navigation_mode|selection_mode|operator_given) & HasArg())
  1297. def _(event):
  1298. " Zero when an argument was already give. "
  1299. event.append_to_arg_count(event.data)
  1300. @handle(Keys.Any, filter=replace_mode)
  1301. def _(event):
  1302. """
  1303. Insert data at cursor position.
  1304. """
  1305. event.current_buffer.insert_text(event.data, overwrite=True)
  1306. @handle(Keys.Any, filter=insert_multiple_mode,
  1307. save_before=(lambda e: not e.is_repeat))
  1308. def _(event):
  1309. """
  1310. Insert data at multiple cursor positions at once.
  1311. (Usually a result of pressing 'I' or 'A' in block-selection mode.)
  1312. """
  1313. buff = event.current_buffer
  1314. original_text = buff.text
  1315. # Construct new text.
  1316. text = []
  1317. p = 0
  1318. for p2 in buff.multiple_cursor_positions:
  1319. text.append(original_text[p:p2])
  1320. text.append(event.data)
  1321. p = p2
  1322. text.append(original_text[p:])
  1323. # Shift all cursor positions.
  1324. new_cursor_positions = [
  1325. p + i + 1 for i, p in enumerate(buff.multiple_cursor_positions)]
  1326. # Set result.
  1327. buff.text = ''.join(text)
  1328. buff.multiple_cursor_positions = new_cursor_positions
  1329. buff.cursor_position += 1
  1330. @handle(Keys.Backspace, filter=insert_multiple_mode)
  1331. def _(event):
  1332. " Backspace, using multiple cursors. "
  1333. buff = event.current_buffer
  1334. original_text = buff.text
  1335. # Construct new text.
  1336. deleted_something = False
  1337. text = []
  1338. p = 0
  1339. for p2 in buff.multiple_cursor_positions:
  1340. if p2 > 0 and original_text[p2 - 1] != '\n': # Don't delete across lines.
  1341. text.append(original_text[p:p2 - 1])
  1342. deleted_something = True
  1343. else:
  1344. text.append(original_text[p:p2])
  1345. p = p2
  1346. text.append(original_text[p:])
  1347. if deleted_something:
  1348. # Shift all cursor positions.
  1349. lengths = [len(part) for part in text[:-1]]
  1350. new_cursor_positions = list(accumulate(lengths))
  1351. # Set result.
  1352. buff.text = ''.join(text)
  1353. buff.multiple_cursor_positions = new_cursor_positions
  1354. buff.cursor_position -= 1
  1355. else:
  1356. event.cli.output.bell()
  1357. @handle(Keys.Delete, filter=insert_multiple_mode)
  1358. def _(event):
  1359. " Delete, using multiple cursors. "
  1360. buff = event.current_buffer
  1361. original_text = buff.text
  1362. # Construct new text.
  1363. deleted_something = False
  1364. text = []
  1365. new_cursor_positions = []
  1366. p = 0
  1367. for p2 in buff.multiple_cursor_positions:
  1368. text.append(original_text[p:p2])
  1369. if p2 >= len(original_text) or original_text[p2] == '\n':
  1370. # Don't delete across lines.
  1371. p = p2
  1372. else:
  1373. p = p2 + 1
  1374. deleted_something = True
  1375. text.append(original_text[p:])
  1376. if deleted_something:
  1377. # Shift all cursor positions.
  1378. lengths = [len(part) for part in text[:-1]]
  1379. new_cursor_positions = list(accumulate(lengths))
  1380. # Set result.
  1381. buff.text = ''.join(text)
  1382. buff.multiple_cursor_positions = new_cursor_positions
  1383. else:
  1384. event.cli.output.bell()
  1385. @handle(Keys.ControlX, Keys.ControlL, filter=insert_mode)
  1386. def _(event):
  1387. """
  1388. Pressing the ControlX - ControlL sequence in Vi mode does line
  1389. completion based on the other lines in the document and the history.
  1390. """
  1391. event.current_buffer.start_history_lines_completion()
  1392. @handle(Keys.ControlX, Keys.ControlF, filter=insert_mode)
  1393. def _(event):
  1394. """
  1395. Complete file names.
  1396. """
  1397. # TODO
  1398. pass
  1399. @handle(Keys.ControlK, filter=insert_mode|replace_mode)
  1400. def _(event):
  1401. " Go into digraph mode. "
  1402. event.cli.vi_state.waiting_for_digraph = True
  1403. @Condition
  1404. def digraph_symbol_1_given(cli):
  1405. return cli.vi_state.digraph_symbol1 is not None
  1406. @handle(Keys.Any, filter=digraph_mode & ~digraph_symbol_1_given)
  1407. def _(event):
  1408. event.cli.vi_state.digraph_symbol1 = event.data
  1409. @handle(Keys.Any, filter=digraph_mode & digraph_symbol_1_given)
  1410. def _(event):
  1411. " Insert digraph. "
  1412. try:
  1413. # Lookup.
  1414. code = (event.cli.vi_state.digraph_symbol1, event.data)
  1415. if code not in DIGRAPHS:
  1416. code = code[::-1] # Try reversing.
  1417. symbol = DIGRAPHS[code]
  1418. except KeyError:
  1419. # Unkown digraph.
  1420. event.cli.output.bell()
  1421. else:
  1422. # Insert digraph.
  1423. overwrite = event.cli.vi_state.input_mode == InputMode.REPLACE
  1424. event.current_buffer.insert_text(
  1425. six.unichr(symbol), overwrite=overwrite)
  1426. event.cli.vi_state.waiting_for_digraph = False
  1427. finally:
  1428. event.cli.vi_state.waiting_for_digraph = False
  1429. event.cli.vi_state.digraph_symbol1 = None
  1430. return registry
  1431. def load_vi_open_in_editor_bindings():
  1432. """
  1433. Pressing 'v' in navigation mode will open the buffer in an external editor.
  1434. """
  1435. registry = Registry()
  1436. navigation_mode = ViNavigationMode()
  1437. registry.add_binding('v', filter=navigation_mode)(
  1438. get_by_name('edit-and-execute-command'))
  1439. return registry
  1440. def load_vi_system_bindings():
  1441. registry = ConditionalRegistry(Registry(), ViMode())
  1442. handle = registry.add_binding
  1443. has_focus = filters.HasFocus(SYSTEM_BUFFER)
  1444. navigation_mode = ViNavigationMode()
  1445. @handle('!', filter=~has_focus & navigation_mode)
  1446. def _(event):
  1447. """
  1448. '!' opens the system prompt.
  1449. """
  1450. event.cli.push_focus(SYSTEM_BUFFER)
  1451. event.cli.vi_state.input_mode = InputMode.INSERT
  1452. @handle(Keys.Escape, filter=has_focus)
  1453. @handle(Keys.ControlC, filter=has_focus)
  1454. def _(event):
  1455. """
  1456. Cancel system prompt.
  1457. """
  1458. event.cli.vi_state.input_mode = InputMode.NAVIGATION
  1459. event.cli.buffers[SYSTEM_BUFFER].reset()
  1460. event.cli.pop_focus()
  1461. @handle(Keys.ControlJ, filter=has_focus)
  1462. def _(event):
  1463. """
  1464. Run system command.
  1465. """
  1466. event.cli.vi_state.input_mode = InputMode.NAVIGATION
  1467. system_buffer = event.cli.buffers[SYSTEM_BUFFER]
  1468. event.cli.run_system_command(system_buffer.text)
  1469. system_buffer.reset(append_to_history=True)
  1470. # Focus previous buffer again.
  1471. event.cli.pop_focus()
  1472. return registry
  1473. def load_vi_search_bindings(get_search_state=None,
  1474. search_buffer_name=SEARCH_BUFFER):
  1475. assert get_search_state is None or callable(get_search_state)
  1476. if not get_search_state:
  1477. def get_search_state(cli): return cli.search_state
  1478. registry = ConditionalRegistry(Registry(), ViMode())
  1479. handle = registry.add_binding
  1480. has_focus = filters.HasFocus(search_buffer_name)
  1481. navigation_mode = ViNavigationMode()
  1482. selection_mode = ViSelectionMode()
  1483. reverse_vi_search_direction = Condition(
  1484. lambda cli: cli.application.reverse_vi_search_direction(cli))
  1485. @handle('/', filter=(navigation_mode|selection_mode)&~reverse_vi_search_direction)
  1486. @handle('?', filter=(navigation_mode|selection_mode)&reverse_vi_search_direction)
  1487. @handle(Keys.ControlS, filter=~has_focus)
  1488. def _(event):
  1489. """
  1490. Vi-style forward search.
  1491. """
  1492. # Set the ViState.
  1493. get_search_state(event.cli).direction = IncrementalSearchDirection.FORWARD
  1494. event.cli.vi_state.input_mode = InputMode.INSERT
  1495. # Focus search buffer.
  1496. event.cli.push_focus(search_buffer_name)
  1497. @handle('?', filter=(navigation_mode|selection_mode)&~reverse_vi_search_direction)
  1498. @handle('/', filter=(navigation_mode|selection_mode)&reverse_vi_search_direction)
  1499. @handle(Keys.ControlR, filter=~has_focus)
  1500. def _(event):
  1501. """
  1502. Vi-style backward search.
  1503. """
  1504. # Set the ViState.
  1505. get_search_state(event.cli).direction = IncrementalSearchDirection.BACKWARD
  1506. # Focus search buffer.
  1507. event.cli.push_focus(search_buffer_name)
  1508. event.cli.vi_state.input_mode = InputMode.INSERT
  1509. @handle(Keys.ControlJ, filter=has_focus)
  1510. @handle(Keys.Escape, filter=has_focus)
  1511. def _(event):
  1512. """
  1513. Apply the search. (At the / or ? prompt.)
  1514. """
  1515. input_buffer = event.cli.buffers.previous(event.cli)
  1516. search_buffer = event.cli.buffers[search_buffer_name]
  1517. # Update search state.
  1518. if search_buffer.text:
  1519. get_search_state(event.cli).text = search_buffer.text
  1520. # Apply search.
  1521. input_buffer.apply_search(get_search_state(event.cli))
  1522. # Add query to history of search line.
  1523. search_buffer.append_to_history()
  1524. search_buffer.reset()
  1525. # Focus previous document again.
  1526. event.cli.vi_state.input_mode = InputMode.NAVIGATION
  1527. event.cli.pop_focus()
  1528. def incremental_search(cli, direction, count=1):
  1529. " Apply search, but keep search buffer focussed. "
  1530. # Update search_state.
  1531. search_state = get_search_state(cli)
  1532. direction_changed = search_state.direction != direction
  1533. search_state.text = cli.buffers[search_buffer_name].text
  1534. search_state.direction = direction
  1535. # Apply search to current buffer.
  1536. if not direction_changed:
  1537. input_buffer = cli.buffers.previous(cli)
  1538. input_buffer.apply_search(search_state,
  1539. include_current_position=False, count=count)
  1540. @handle(Keys.ControlR, filter=has_focus)
  1541. def _(event):
  1542. incremental_search(event.cli, IncrementalSearchDirection.BACKWARD, count=event.arg)
  1543. @handle(Keys.ControlS, filter=has_focus)
  1544. def _(event):
  1545. incremental_search(event.cli, IncrementalSearchDirection.FORWARD, count=event.arg)
  1546. def search_buffer_is_empty(cli):
  1547. """ Returns True when the search buffer is empty. """
  1548. return cli.buffers[search_buffer_name].text == ''
  1549. @handle(Keys.ControlC, filter=has_focus)
  1550. @handle(Keys.ControlH, filter=has_focus & Condition(search_buffer_is_empty))
  1551. @handle(Keys.Backspace, filter=has_focus & Condition(search_buffer_is_empty))
  1552. def _(event):
  1553. """
  1554. Cancel search.
  1555. """
  1556. event.cli.vi_state.input_mode = InputMode.NAVIGATION
  1557. event.cli.pop_focus()
  1558. event.cli.buffers[search_buffer_name].reset()
  1559. return registry
  1560. def load_extra_vi_page_navigation_bindings():
  1561. """
  1562. Key bindings, for scrolling up and down through pages.
  1563. This are separate bindings, because GNU readline doesn't have them.
  1564. """
  1565. registry = ConditionalRegistry(Registry(), ViMode())
  1566. handle = registry.add_binding
  1567. handle(Keys.ControlF)(scroll_forward)
  1568. handle(Keys.ControlB)(scroll_backward)
  1569. handle(Keys.ControlD)(scroll_half_page_down)
  1570. handle(Keys.ControlU)(scroll_half_page_up)
  1571. handle(Keys.ControlE)(scroll_one_line_down)
  1572. handle(Keys.ControlY)(scroll_one_line_up)
  1573. handle(Keys.PageDown)(scroll_page_down)
  1574. handle(Keys.PageUp)(scroll_page_up)
  1575. return registry
  1576. class ViStateFilter(Filter):
  1577. " Deprecated! "
  1578. def __init__(self, get_vi_state, mode):
  1579. self.get_vi_state = get_vi_state
  1580. self.mode = mode
  1581. def __call__(self, cli):
  1582. return self.get_vi_state(cli).input_mode == self.mode