PageRenderTime 103ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/Packages/Vintage/vintage_motions.py

https://gitlab.com/Blueprint-Marketing/sublime-config
Python | 375 lines | 308 code | 53 blank | 14 comment | 49 complexity | acadebf43509930e8755baac67f3c1cc MD5 | raw file
  1. import re
  2. import sublime, sublime_plugin
  3. from vintage import transform_selection
  4. from vintage import transform_selection_regions
  5. class ViSpanCountLines(sublime_plugin.TextCommand):
  6. def run(self, edit, repeat = 1):
  7. for i in xrange(repeat - 1):
  8. self.view.run_command('move', {'by': 'lines',
  9. 'extend': True,
  10. 'forward': True})
  11. class ViMoveByCharactersInLine(sublime_plugin.TextCommand):
  12. def run(self, edit, forward = True, extend = False, visual = False):
  13. delta = 1 if forward else -1
  14. transform_selection(self.view, lambda pt: pt + delta, extend=extend,
  15. clip_to_line=(not visual))
  16. class ViMoveByCharacters(sublime_plugin.TextCommand):
  17. def advance(self, delta, visual, pt):
  18. pt += delta
  19. if not visual and self.view.substr(pt) == '\n':
  20. pt += delta
  21. return pt
  22. def run(self, edit, forward = True, extend = False, visual = False):
  23. delta = 1 if forward else -1
  24. transform_selection(self.view, lambda pt: self.advance(delta, visual, pt),
  25. extend=extend)
  26. class ViMoveToHardEol(sublime_plugin.TextCommand):
  27. def run(self, edit, repeat = 1, extend = False):
  28. repeat = int(repeat)
  29. if repeat > 1:
  30. for i in xrange(repeat - 1):
  31. self.view.run_command('move',
  32. {'by': 'lines', 'extend': extend, 'forward': True})
  33. transform_selection(self.view, lambda pt: self.view.line(pt).b,
  34. extend=extend, clip_to_line=False)
  35. class ViMoveToFirstNonWhiteSpaceCharacter(sublime_plugin.TextCommand):
  36. def first_character(self, pt):
  37. l = self.view.line(pt)
  38. lstr = self.view.substr(l)
  39. offset = 0
  40. for c in lstr:
  41. if c == ' ' or c == '\t':
  42. offset += 1
  43. else:
  44. break
  45. return l.a + offset
  46. def run(self, edit, repeat = 1, extend = False, register = '"'):
  47. # According to Vim's help, _ moves count - 1 lines downward.
  48. for i in xrange(repeat - 1):
  49. self.view.run_command('move', {'by': 'lines', 'forward': True, 'extend': extend})
  50. transform_selection(self.view, lambda pt: self.first_character(pt),
  51. extend=extend)
  52. g_last_move_command = None
  53. class ViMoveToCharacter(sublime_plugin.TextCommand):
  54. def find_next(self, forward, char, before, pt):
  55. lr = self.view.line(pt)
  56. extra = 0 if before else 1
  57. if forward:
  58. line = self.view.substr(sublime.Region(pt, lr.b))
  59. idx = line.find(char, 1)
  60. if idx >= 0:
  61. return pt + idx + 1 * extra
  62. else:
  63. line = self.view.substr(sublime.Region(lr.a, pt))[::-1]
  64. idx = line.find(char, 0)
  65. if idx >= 0:
  66. return pt - idx - 1 * extra
  67. return pt
  68. def run(self, edit, character, extend = False, forward = True, before = False, record = True):
  69. if record:
  70. global g_last_move_command
  71. g_last_move_command = {'character': character, 'extend': extend,
  72. 'forward':forward, 'before':before}
  73. transform_selection(self.view,
  74. lambda pt: self.find_next(forward, character, before, pt),
  75. extend=extend)
  76. class ViExtendToEndOfWhitespaceOrWord(sublime_plugin.TextCommand):
  77. def run(self, edit, repeat = 1, separators=None):
  78. repeat = int(repeat)
  79. # Selections that start on whitespace should extend to the end of the
  80. # the whitespace. Other selections can simply be moved to word ends.
  81. sel = self.view.sel()
  82. sels_advanced_from_whitespace = []
  83. sels_to_move_to_word_end = []
  84. for r in sel:
  85. b = advance_while_white_space_character(self.view, r.b)
  86. if b > r.b:
  87. sels_advanced_from_whitespace.append(sublime.Region(r.a, b))
  88. else:
  89. sels_to_move_to_word_end.append(r)
  90. sel.clear()
  91. for r in sels_to_move_to_word_end:
  92. sel.add(r)
  93. move_args = {"by": "stops", "word_end": True, "punct_end": True,
  94. "empty_line": True, "forward": True, "extend": True}
  95. if separators != None:
  96. move_args.update(separators=separators)
  97. self.view.run_command('move', move_args)
  98. for r in sels_advanced_from_whitespace:
  99. sel.add(r)
  100. # Only the first move differs from a normal move to word end.
  101. for i in xrange(repeat - 1):
  102. self.view.run_command('move', move_args)
  103. # Helper class used to implement ';'' and ',', which repeat the last f, F, t
  104. # or T command (reversed in the case of ',')
  105. class SetRepeatMoveToCharacterMotion(sublime_plugin.TextCommand):
  106. def run_(self, args):
  107. if args:
  108. return self.run(**args)
  109. else:
  110. return self.run()
  111. def run(self, reverse = False):
  112. if g_last_move_command:
  113. cmd = g_last_move_command.copy()
  114. cmd['record'] = False
  115. if reverse:
  116. cmd['forward'] = not cmd['forward']
  117. self.view.run_command('set_motion', {
  118. 'motion': 'vi_move_to_character',
  119. 'motion_args': cmd,
  120. 'inclusive': True })
  121. class ViMoveToBrackets(sublime_plugin.TextCommand):
  122. def move_by_percent(self, percent):
  123. destination = int(self.view.rowcol(self.view.size())[0] * (percent / 100.0))
  124. destination = self.view.line(self.view.text_point(destination, 0)).a
  125. destination = advance_while_white_space_character(self.view, destination)
  126. transform_selection(self.view, lambda pt: destination)
  127. def run(self, edit, repeat=1):
  128. repeat = int(repeat)
  129. if repeat == 1:
  130. re_brackets = re.compile(r"([(\[{])|([)}\])])")
  131. def move_to_next_bracket(pt):
  132. line = self.view.line(pt)
  133. remaining_line = self.view.substr(sublime.Region(pt, line.b))
  134. match = re_brackets.search(remaining_line)
  135. if match:
  136. return pt + match.start() + (1 if match.group(2) else 0)
  137. else:
  138. return pt
  139. transform_selection(self.view, move_to_next_bracket, extend=True)
  140. self.view.run_command("move_to", {"to": "brackets", "extend": True, "force_outer": True})
  141. else:
  142. self.move_by_percent(repeat)
  143. class ViGotoLine(sublime_plugin.TextCommand):
  144. def run(self, edit, repeat=1, explicit_repeat=True, extend=False,
  145. ending='eof'):
  146. # G or gg
  147. if not explicit_repeat:
  148. self.view.run_command('move_to', {'to': ending, 'extend':extend})
  149. # <count>G or <count>gg
  150. else:
  151. new_address = int(repeat) - 1
  152. target_pt = self.view.text_point(new_address, 0)
  153. transform_selection(self.view, lambda pt: target_pt,
  154. extend=extend)
  155. def advance_while_white_space_character(view, pt, white_space="\t "):
  156. while view.substr(pt) in white_space:
  157. pt += 1
  158. return pt
  159. class MoveCaretToScreenCenter(sublime_plugin.TextCommand):
  160. def run(self, edit, extend = True):
  161. screenful = self.view.visible_region()
  162. row_a = self.view.rowcol(screenful.a)[0]
  163. row_b = self.view.rowcol(screenful.b)[0]
  164. middle_row = (row_a + row_b) / 2
  165. middle_point = self.view.text_point(middle_row, 0)
  166. middle_point = advance_while_white_space_character(self.view, middle_point)
  167. transform_selection(self.view, lambda pt: middle_point, extend=extend)
  168. class MoveCaretToScreenTop(sublime_plugin.TextCommand):
  169. def run(self, edit, repeat, extend = True):
  170. # Don't modify offset so not fully visible regions have a lower chance
  171. # of scrolling the screen.
  172. # lines_offset = int(repeat) - 1
  173. lines_offset = int(repeat)
  174. screenful = self.view.visible_region()
  175. target = screenful.begin()
  176. for x in xrange(lines_offset):
  177. current_line = self.view.line(target)
  178. target = current_line.b + 1
  179. target = advance_while_white_space_character(self.view, target)
  180. transform_selection(self.view, lambda pt: target, extend=extend)
  181. class MoveCaretToScreenBottom(sublime_plugin.TextCommand):
  182. def run(self, edit, repeat, extend = True):
  183. # Don't modify offset so not fully visible regions have a lower chance
  184. # of scrolling the screen.
  185. # lines_offset = int(repeat) - 1
  186. lines_offset = int(repeat)
  187. screenful = self.view.visible_region()
  188. target = screenful.end()
  189. for x in xrange(lines_offset):
  190. current_line = self.view.line(target)
  191. target = current_line.a - 1
  192. target = self.view.line(target).a
  193. target = advance_while_white_space_character(self.view, target)
  194. transform_selection(self.view, lambda pt: target, extend=extend)
  195. def expand_to_whitespace(view, r):
  196. a = r.a
  197. b = r.b
  198. while view.substr(b) in " \t":
  199. b += 1
  200. if b == r.b:
  201. while view.substr(a - 1) in " \t":
  202. a -= 1
  203. return sublime.Region(a, b)
  204. class ViExpandToWords(sublime_plugin.TextCommand):
  205. def run(self, edit, outer = False, repeat = 1):
  206. repeat = int(repeat)
  207. transform_selection_regions(self.view, lambda r: sublime.Region(r.b + 1, r.b + 1))
  208. self.view.run_command("move", {"by": "stops", "extend":False, "forward":False, "word_begin":True, "punct_begin":True})
  209. for i in xrange(repeat):
  210. self.view.run_command("move", {"by": "stops", "extend":True, "forward":True, "word_end":True, "punct_end":True})
  211. if outer:
  212. transform_selection_regions(self.view, lambda r: expand_to_whitespace(self.view, r))
  213. class ViExpandToBigWords(sublime_plugin.TextCommand):
  214. def run(self, edit, outer = False, repeat = 1):
  215. repeat = int(repeat)
  216. transform_selection_regions(self.view, lambda r: sublime.Region(r.b + 1, r.b + 1))
  217. self.view.run_command("move", {"by": "stops", "extend":False, "forward":False, "word_begin":True, "punct_begin":True, "separators": ""})
  218. for i in xrange(repeat):
  219. self.view.run_command("move", {"by": "stops", "extend":True, "forward":True, "word_end":True, "punct_end":True, "separators": ""})
  220. if outer:
  221. transform_selection_regions(self.view, lambda r: expand_to_whitespace(self.view, r))
  222. class ViExpandToQuotes(sublime_plugin.TextCommand):
  223. def compare_quote(self, character, p):
  224. if self.view.substr(p) == character:
  225. return self.view.score_selector(p, "constant.character.escape") == 0
  226. else:
  227. return False
  228. def expand_to_quote(self, character, r):
  229. # We'll limit the search to the current line.
  230. line_begin = self.view.line(r).begin()
  231. line_end = self.view.line(r).end()
  232. caret_pos_in_line = r.begin() - line_begin
  233. # Find out whether there's any quoted text.
  234. line_text = self.view.substr(self.view.line(r))
  235. first_quote = line_text.find(character)
  236. closing_quote = None
  237. # Look for a closing quote after the first quote.
  238. if ((line_text[caret_pos_in_line] == character and
  239. first_quote == caret_pos_in_line) or
  240. (first_quote > caret_pos_in_line)):
  241. closing_quote = line_text.find(character, first_quote + 1)
  242. # The caret may be on a quote character, so don't look past it.
  243. # This ensures we favor quoted text before the caret over quoted
  244. # text after it, as Vim does.
  245. else:
  246. closing_quote = line_text.find(character, caret_pos_in_line)
  247. # No quoted text --do nothing (Vim).
  248. # TODO: Vintage will enter insert mode after this, whereas it should
  249. # stay in command mode as Vim does.
  250. if closing_quote == -1:
  251. return r
  252. # Quoted text is before the caret --do nothing (Vim).
  253. if closing_quote < caret_pos_in_line:
  254. return r
  255. p = r.b
  256. if closing_quote == caret_pos_in_line:
  257. p -= 1
  258. # Quoted text is after the caret --advance there (Vim).
  259. if first_quote > caret_pos_in_line:
  260. p = line_begin + first_quote
  261. a = p
  262. while a >= line_begin and not self.compare_quote(character, a):
  263. a -= 1
  264. b = a + 1
  265. while b < line_end and not self.compare_quote(character, b):
  266. b += 1
  267. return sublime.Region(a + 1, b)
  268. def expand_to_outer(self, r):
  269. a, b = r.a, r.b
  270. if a > 0:
  271. a -= 1
  272. if b < self.view.size():
  273. b += 1
  274. return expand_to_whitespace(self.view, sublime.Region(a, b))
  275. def run(self, edit, character, outer = False):
  276. transform_selection_regions(self.view, lambda r: self.expand_to_quote(character, r))
  277. if outer:
  278. transform_selection_regions(self.view, lambda r: self.expand_to_outer(r))
  279. class ViExpandToTag(sublime_plugin.TextCommand):
  280. def run(self, edit, outer = False):
  281. self.view.run_command('expand_selection', {'to': 'tag'})
  282. if outer:
  283. self.view.run_command('expand_selection', {'to': 'tag'})
  284. class ViExpandToBrackets(sublime_plugin.TextCommand):
  285. def run(self, edit, character, outer = False):
  286. self.view.run_command('expand_selection', {'to': 'brackets', 'brackets': character})
  287. if outer:
  288. self.view.run_command('expand_selection', {'to': 'brackets', 'brackets': character})
  289. class ScrollCurrentLineToScreenTop(sublime_plugin.TextCommand):
  290. def run(self, edit, repeat, extend=False):
  291. bos = self.view.visible_region().a
  292. caret = self.view.line(self.view.sel()[0].begin()).a
  293. offset = self.view.rowcol(caret)[0] - self.view.rowcol(bos)[0]
  294. caret = advance_while_white_space_character(self.view, caret)
  295. transform_selection(self.view, lambda pt: caret, extend)
  296. self.view.run_command('scroll_lines', {'amount': -offset})
  297. class ScrollCurrentLineToScreenCenter(sublime_plugin.TextCommand):
  298. def run(self, edit, repeat, extend=True):
  299. line_nr = self.view.rowcol(self.view.sel()[0].a)[0] if \
  300. int(repeat) == 1 else int(repeat) - 1
  301. point = self.view.line(self.view.text_point(line_nr, 0)).a
  302. point = advance_while_white_space_character(self.view, point)
  303. transform_selection(self.view, lambda pt: point, extend)
  304. self.view.run_command('show_at_center')