/Packages/Vintage/vintage_motions.py
Python | 375 lines | 308 code | 53 blank | 14 comment | 49 complexity | acadebf43509930e8755baac67f3c1cc MD5 | raw file
- import re
- import sublime, sublime_plugin
- from vintage import transform_selection
- from vintage import transform_selection_regions
- class ViSpanCountLines(sublime_plugin.TextCommand):
- def run(self, edit, repeat = 1):
- for i in xrange(repeat - 1):
- self.view.run_command('move', {'by': 'lines',
- 'extend': True,
- 'forward': True})
- class ViMoveByCharactersInLine(sublime_plugin.TextCommand):
- def run(self, edit, forward = True, extend = False, visual = False):
- delta = 1 if forward else -1
- transform_selection(self.view, lambda pt: pt + delta, extend=extend,
- clip_to_line=(not visual))
- class ViMoveByCharacters(sublime_plugin.TextCommand):
- def advance(self, delta, visual, pt):
- pt += delta
- if not visual and self.view.substr(pt) == '\n':
- pt += delta
- return pt
- def run(self, edit, forward = True, extend = False, visual = False):
- delta = 1 if forward else -1
- transform_selection(self.view, lambda pt: self.advance(delta, visual, pt),
- extend=extend)
- class ViMoveToHardEol(sublime_plugin.TextCommand):
- def run(self, edit, repeat = 1, extend = False):
- repeat = int(repeat)
- if repeat > 1:
- for i in xrange(repeat - 1):
- self.view.run_command('move',
- {'by': 'lines', 'extend': extend, 'forward': True})
- transform_selection(self.view, lambda pt: self.view.line(pt).b,
- extend=extend, clip_to_line=False)
- class ViMoveToFirstNonWhiteSpaceCharacter(sublime_plugin.TextCommand):
- def first_character(self, pt):
- l = self.view.line(pt)
- lstr = self.view.substr(l)
- offset = 0
- for c in lstr:
- if c == ' ' or c == '\t':
- offset += 1
- else:
- break
- return l.a + offset
- def run(self, edit, repeat = 1, extend = False, register = '"'):
- # According to Vim's help, _ moves count - 1 lines downward.
- for i in xrange(repeat - 1):
- self.view.run_command('move', {'by': 'lines', 'forward': True, 'extend': extend})
- transform_selection(self.view, lambda pt: self.first_character(pt),
- extend=extend)
- g_last_move_command = None
- class ViMoveToCharacter(sublime_plugin.TextCommand):
- def find_next(self, forward, char, before, pt):
- lr = self.view.line(pt)
- extra = 0 if before else 1
- if forward:
- line = self.view.substr(sublime.Region(pt, lr.b))
- idx = line.find(char, 1)
- if idx >= 0:
- return pt + idx + 1 * extra
- else:
- line = self.view.substr(sublime.Region(lr.a, pt))[::-1]
- idx = line.find(char, 0)
- if idx >= 0:
- return pt - idx - 1 * extra
- return pt
- def run(self, edit, character, extend = False, forward = True, before = False, record = True):
- if record:
- global g_last_move_command
- g_last_move_command = {'character': character, 'extend': extend,
- 'forward':forward, 'before':before}
- transform_selection(self.view,
- lambda pt: self.find_next(forward, character, before, pt),
- extend=extend)
- class ViExtendToEndOfWhitespaceOrWord(sublime_plugin.TextCommand):
- def run(self, edit, repeat = 1, separators=None):
- repeat = int(repeat)
- # Selections that start on whitespace should extend to the end of the
- # the whitespace. Other selections can simply be moved to word ends.
- sel = self.view.sel()
- sels_advanced_from_whitespace = []
- sels_to_move_to_word_end = []
- for r in sel:
- b = advance_while_white_space_character(self.view, r.b)
- if b > r.b:
- sels_advanced_from_whitespace.append(sublime.Region(r.a, b))
- else:
- sels_to_move_to_word_end.append(r)
- sel.clear()
- for r in sels_to_move_to_word_end:
- sel.add(r)
- move_args = {"by": "stops", "word_end": True, "punct_end": True,
- "empty_line": True, "forward": True, "extend": True}
- if separators != None:
- move_args.update(separators=separators)
- self.view.run_command('move', move_args)
- for r in sels_advanced_from_whitespace:
- sel.add(r)
- # Only the first move differs from a normal move to word end.
- for i in xrange(repeat - 1):
- self.view.run_command('move', move_args)
- # Helper class used to implement ';'' and ',', which repeat the last f, F, t
- # or T command (reversed in the case of ',')
- class SetRepeatMoveToCharacterMotion(sublime_plugin.TextCommand):
- def run_(self, args):
- if args:
- return self.run(**args)
- else:
- return self.run()
- def run(self, reverse = False):
- if g_last_move_command:
- cmd = g_last_move_command.copy()
- cmd['record'] = False
- if reverse:
- cmd['forward'] = not cmd['forward']
- self.view.run_command('set_motion', {
- 'motion': 'vi_move_to_character',
- 'motion_args': cmd,
- 'inclusive': True })
- class ViMoveToBrackets(sublime_plugin.TextCommand):
- def move_by_percent(self, percent):
- destination = int(self.view.rowcol(self.view.size())[0] * (percent / 100.0))
- destination = self.view.line(self.view.text_point(destination, 0)).a
- destination = advance_while_white_space_character(self.view, destination)
- transform_selection(self.view, lambda pt: destination)
- def run(self, edit, repeat=1):
- repeat = int(repeat)
- if repeat == 1:
- re_brackets = re.compile(r"([(\[{])|([)}\])])")
- def move_to_next_bracket(pt):
- line = self.view.line(pt)
- remaining_line = self.view.substr(sublime.Region(pt, line.b))
- match = re_brackets.search(remaining_line)
- if match:
- return pt + match.start() + (1 if match.group(2) else 0)
- else:
- return pt
- transform_selection(self.view, move_to_next_bracket, extend=True)
- self.view.run_command("move_to", {"to": "brackets", "extend": True, "force_outer": True})
- else:
- self.move_by_percent(repeat)
- class ViGotoLine(sublime_plugin.TextCommand):
- def run(self, edit, repeat=1, explicit_repeat=True, extend=False,
- ending='eof'):
- # G or gg
- if not explicit_repeat:
- self.view.run_command('move_to', {'to': ending, 'extend':extend})
- # <count>G or <count>gg
- else:
- new_address = int(repeat) - 1
- target_pt = self.view.text_point(new_address, 0)
- transform_selection(self.view, lambda pt: target_pt,
- extend=extend)
- def advance_while_white_space_character(view, pt, white_space="\t "):
- while view.substr(pt) in white_space:
- pt += 1
- return pt
- class MoveCaretToScreenCenter(sublime_plugin.TextCommand):
- def run(self, edit, extend = True):
- screenful = self.view.visible_region()
- row_a = self.view.rowcol(screenful.a)[0]
- row_b = self.view.rowcol(screenful.b)[0]
- middle_row = (row_a + row_b) / 2
- middle_point = self.view.text_point(middle_row, 0)
- middle_point = advance_while_white_space_character(self.view, middle_point)
- transform_selection(self.view, lambda pt: middle_point, extend=extend)
- class MoveCaretToScreenTop(sublime_plugin.TextCommand):
- def run(self, edit, repeat, extend = True):
- # Don't modify offset so not fully visible regions have a lower chance
- # of scrolling the screen.
- # lines_offset = int(repeat) - 1
- lines_offset = int(repeat)
- screenful = self.view.visible_region()
- target = screenful.begin()
- for x in xrange(lines_offset):
- current_line = self.view.line(target)
- target = current_line.b + 1
- target = advance_while_white_space_character(self.view, target)
- transform_selection(self.view, lambda pt: target, extend=extend)
- class MoveCaretToScreenBottom(sublime_plugin.TextCommand):
- def run(self, edit, repeat, extend = True):
- # Don't modify offset so not fully visible regions have a lower chance
- # of scrolling the screen.
- # lines_offset = int(repeat) - 1
- lines_offset = int(repeat)
- screenful = self.view.visible_region()
- target = screenful.end()
- for x in xrange(lines_offset):
- current_line = self.view.line(target)
- target = current_line.a - 1
- target = self.view.line(target).a
- target = advance_while_white_space_character(self.view, target)
- transform_selection(self.view, lambda pt: target, extend=extend)
- def expand_to_whitespace(view, r):
- a = r.a
- b = r.b
- while view.substr(b) in " \t":
- b += 1
- if b == r.b:
- while view.substr(a - 1) in " \t":
- a -= 1
- return sublime.Region(a, b)
- class ViExpandToWords(sublime_plugin.TextCommand):
- def run(self, edit, outer = False, repeat = 1):
- repeat = int(repeat)
- transform_selection_regions(self.view, lambda r: sublime.Region(r.b + 1, r.b + 1))
- self.view.run_command("move", {"by": "stops", "extend":False, "forward":False, "word_begin":True, "punct_begin":True})
- for i in xrange(repeat):
- self.view.run_command("move", {"by": "stops", "extend":True, "forward":True, "word_end":True, "punct_end":True})
- if outer:
- transform_selection_regions(self.view, lambda r: expand_to_whitespace(self.view, r))
- class ViExpandToBigWords(sublime_plugin.TextCommand):
- def run(self, edit, outer = False, repeat = 1):
- repeat = int(repeat)
- transform_selection_regions(self.view, lambda r: sublime.Region(r.b + 1, r.b + 1))
- self.view.run_command("move", {"by": "stops", "extend":False, "forward":False, "word_begin":True, "punct_begin":True, "separators": ""})
- for i in xrange(repeat):
- self.view.run_command("move", {"by": "stops", "extend":True, "forward":True, "word_end":True, "punct_end":True, "separators": ""})
- if outer:
- transform_selection_regions(self.view, lambda r: expand_to_whitespace(self.view, r))
- class ViExpandToQuotes(sublime_plugin.TextCommand):
- def compare_quote(self, character, p):
- if self.view.substr(p) == character:
- return self.view.score_selector(p, "constant.character.escape") == 0
- else:
- return False
- def expand_to_quote(self, character, r):
- # We'll limit the search to the current line.
- line_begin = self.view.line(r).begin()
- line_end = self.view.line(r).end()
- caret_pos_in_line = r.begin() - line_begin
- # Find out whether there's any quoted text.
- line_text = self.view.substr(self.view.line(r))
- first_quote = line_text.find(character)
- closing_quote = None
- # Look for a closing quote after the first quote.
- if ((line_text[caret_pos_in_line] == character and
- first_quote == caret_pos_in_line) or
- (first_quote > caret_pos_in_line)):
- closing_quote = line_text.find(character, first_quote + 1)
- # The caret may be on a quote character, so don't look past it.
- # This ensures we favor quoted text before the caret over quoted
- # text after it, as Vim does.
- else:
- closing_quote = line_text.find(character, caret_pos_in_line)
- # No quoted text --do nothing (Vim).
- # TODO: Vintage will enter insert mode after this, whereas it should
- # stay in command mode as Vim does.
- if closing_quote == -1:
- return r
- # Quoted text is before the caret --do nothing (Vim).
- if closing_quote < caret_pos_in_line:
- return r
- p = r.b
- if closing_quote == caret_pos_in_line:
- p -= 1
- # Quoted text is after the caret --advance there (Vim).
- if first_quote > caret_pos_in_line:
- p = line_begin + first_quote
- a = p
- while a >= line_begin and not self.compare_quote(character, a):
- a -= 1
- b = a + 1
- while b < line_end and not self.compare_quote(character, b):
- b += 1
- return sublime.Region(a + 1, b)
- def expand_to_outer(self, r):
- a, b = r.a, r.b
- if a > 0:
- a -= 1
- if b < self.view.size():
- b += 1
- return expand_to_whitespace(self.view, sublime.Region(a, b))
- def run(self, edit, character, outer = False):
- transform_selection_regions(self.view, lambda r: self.expand_to_quote(character, r))
- if outer:
- transform_selection_regions(self.view, lambda r: self.expand_to_outer(r))
- class ViExpandToTag(sublime_plugin.TextCommand):
- def run(self, edit, outer = False):
- self.view.run_command('expand_selection', {'to': 'tag'})
- if outer:
- self.view.run_command('expand_selection', {'to': 'tag'})
- class ViExpandToBrackets(sublime_plugin.TextCommand):
- def run(self, edit, character, outer = False):
- self.view.run_command('expand_selection', {'to': 'brackets', 'brackets': character})
- if outer:
- self.view.run_command('expand_selection', {'to': 'brackets', 'brackets': character})
- class ScrollCurrentLineToScreenTop(sublime_plugin.TextCommand):
- def run(self, edit, repeat, extend=False):
- bos = self.view.visible_region().a
- caret = self.view.line(self.view.sel()[0].begin()).a
- offset = self.view.rowcol(caret)[0] - self.view.rowcol(bos)[0]
- caret = advance_while_white_space_character(self.view, caret)
- transform_selection(self.view, lambda pt: caret, extend)
- self.view.run_command('scroll_lines', {'amount': -offset})
- class ScrollCurrentLineToScreenCenter(sublime_plugin.TextCommand):
- def run(self, edit, repeat, extend=True):
- line_nr = self.view.rowcol(self.view.sel()[0].a)[0] if \
- int(repeat) == 1 else int(repeat) - 1
- point = self.view.line(self.view.text_point(line_nr, 0)).a
- point = advance_while_white_space_character(self.view, point)
- transform_selection(self.view, lambda pt: point, extend)
- self.view.run_command('show_at_center')