def f(view, s): eol = view.line(s.b).end() if not s.empty(): eol = view.line(s.b - 1).end() match = s for i in range(count): # Define search range as 'rest of the line to the right'. if state.mode != MODE_VISUAL: search_range = sublime.Region(min(match.b + 1, eol), eol) else: search_range = sublime.Region(min(match.b, eol), eol) match = find_in_range(view, character, search_range.a, search_range.b, sublime.LITERAL) # Count too high or simply no match; break. if match is None: match = s break if state.mode == MODE_VISUAL or mode == _MODE_INTERNAL_NORMAL: if match == s: # FIXME: It won't blink because the current light can't be highlighted right # now (we are in command mode and there is a selection on the screen. Perhaps # we can make the gutter blink instead.) utils.blink() return sublime.Region(s.a, match.b) if match == s: utils.blink() return sublime.Region(match.a, match.a)
def f(view, s): if mode == _MODE_INTERNAL_NORMAL: x_limit = max(view.line(s.b).a, s.b - count) return sublime.Region(s.a, x_limit) elif mode == MODE_VISUAL: if s.a < s.b: x_limit = max(view.line(s.b - 1).a + 1, s.b - count) if view.line(s.a) == view.line(s.b - 1) and count >= s.size(): x_limit = max(view.line(s.b - 1).a, s.b - count - 1) return sublime.Region(s.a + 1, x_limit) return sublime.Region(s.a, x_limit) if s.a > s.b: x_limit = max(view.line(s.b).a, s.b - count) return sublime.Region(s.a, x_limit) elif mode == MODE_NORMAL: x_limit = max(view.line(s.b).a, s.b - count) if s.b == x_limit: utils.blink() return sublime.Region(x_limit, x_limit) # XXX: We should never reach this. return s
def f(view, s): # TODO: Refactor this mess. line_text = view.substr(sublime.Region(view.line(s.b).a, s.b)) offset = 0 a, b = view.line(s.b).a, s.b final_offset = -1 try: for i in range(count): line_text = view.substr(sublime.Region(a, b)) match_in_line = line_text.rindex(character) final_offset = match_in_line b = view.line(s.a).a + final_offset except ValueError: pass if final_offset > -1: pt = view.line(s.b).a + final_offset state = VintageState(view) if state.mode == MODE_VISUAL or mode == _MODE_INTERNAL_NORMAL: if sublime.Region(s.b, pt) == s: utils.blink() return s return sublime.Region(s.a, pt) if pt == s.b: utils.blink() return s return sublime.Region(pt, pt) return s
def do_full_command(self): """Evaluates a command like 3dj, where there is an action as well as a motion. """ vi_cmd_data = self.parse_motion() vi_cmd_data = self.parse_action(vi_cmd_data) if not vi_cmd_data['is_digraph_start']: # We are about to run an action, so let Sublime Text know we want all editing # steps folded into a single sequence. "All editing steps" means slightly different # things depending on the mode we are in. if vi_cmd_data['_mark_groups_for_gluing']: self.view.run_command('maybe_mark_undo_groups_for_gluing') self.view.run_command('vi_run', vi_cmd_data) self.reset() else: # If we have a digraph start, the global data is in an invalid state because we # are still missing the complete digraph. Abort and clean up. if vi_cmd_data['_exit_mode'] == MODE_INSERT: # We've been requested to change to this mode. For example, we're looking at # CTRL+r,j in INSERTMODE, which is an invalid sequence. # !!! This could be simplified using parameters in .reset(), but then it # wouldn't be obvious what was going on. Don't refactor. !!! utils.blink() self.reset() self.enter_insert_mode() elif self.mode != MODE_NORMAL: # Normally we'd go back to normal mode. self.enter_normal_mode() self.reset()
def run(self, edit, count=1, mode=None, subtract=False): if mode != _MODE_INTERNAL_NORMAL: return # TODO: Deal with octal, hex notations. # TODO: Improve detection of numbers. regs = list(self.view.sel()) pts = self.find_next_num(regs) if not pts: utils.blink() return count = count if not subtract else -count end_sels = [] for pt in reversed(pts): sign, num, tail = self.get_editable_data(pt) num_as_text = str((sign * num) + count) new_text = num_as_text + self.view.substr(tail) offset = 0 if sign == -1: offset = -1 self.view.replace(edit, sublime.Region(pt - 1, tail.b), new_text) else: self.view.replace(edit, sublime.Region(pt, tail.b), new_text) rowcol = self.view.rowcol(pt + len(num_as_text) - 1 + offset) end_sels.append(rowcol) self.view.sel().clear() for (row, col) in end_sels: self.view.sel().add(sublime.Region(self.view.text_point(row, col)))
def f(view, s): if mode == MODE_NORMAL: if view.line(s.b).empty(): utils.blink() return s x_limit = min(view.line(s.b).b - 1, s.b + count, view.size()) if s.b == x_limit: utils.blink() return sublime.Region(x_limit, x_limit) if mode == _MODE_INTERNAL_NORMAL: x_limit = min(view.line(s.b).b, s.b + count) x_limit = max(0, x_limit) return sublime.Region(s.a, x_limit) if mode == MODE_VISUAL: if s.a < s.b: x_limit = min(view.full_line(s.b - 1).b, s.b + count) return sublime.Region(s.a, x_limit) if s.a > s.b: x_limit = min(view.full_line(s.b).b - 1, s.b + count) if view.substr(s.b) == "\n": return s if view.line(s.a) == view.line(s.b) and count >= s.size(): x_limit = min(view.full_line(s.b).b, s.b + count + 1) return sublime.Region(s.a - 1, x_limit) return sublime.Region(s.a, x_limit) return s
def eval(self): """Examines the current state and decides whether to actually run the action/motion. """ if self.cancel_action: self.eval_cancel_action() self.reset() utils.blink() elif self.expecting_user_input: return # Action + motion, like in '3dj'. elif self.action and self.motion: self.eval_full_command() # Motion only, like in '3j'. elif self.motion: vi_cmd_data = self.parse_motion() self.view.run_command('vi_run', vi_cmd_data) self.reset() # Action only, like in 'd' or 'esc'. Some actions can be executed without a motion. elif self.action: self.eval_lone_action()
def eval_cancel_action(self): """Cancels the whole run of the command. """ # TODO: add a .parse() method that includes boths steps? vi_cmd_data = self.parse_motion() vi_cmd_data = self.parse_action(vi_cmd_data) if vi_cmd_data['must_blink_on_error']: utils.blink() # Modify the data that determines the mode we'll end up in when the command finishes. self.next_mode = vi_cmd_data['_exit_mode'] # Since we are exiting early, ensure we leave the selections as the commands wants them. if vi_cmd_data['_exit_mode_command']: self.view.run_command(vi_cmd_data['_exit_mode_command'])
def vi_can_enter_any_visual_mode(self, key, operator, operand, match_all): sels = self.state.view.sel() rv = True for sel in sels: # We're assuming we are in normal mode. if sel.b == self.state.view.size() and self.state.view.line(sel.b).empty(): rv = False break if not rv: print("Vintageous: Can't enter visual mode at EOF if last line is empty.") utils.blink() return self._check(rv, operator, operand, match_all)
def run(self, edit, count=1, mode=None): def f(view, s): if mode == _MODE_INTERNAL_NORMAL: word, match = next(pairs) sign, amount, suffix = match.groups() sign = -1 if sign else 1 suffix = suffix or '' new_digit = (sign * int(amount)) - count view.replace(edit, word, str(new_digit) + suffix) offset = len(str(new_digit)) # FIXME: Deal with multiple sels as we should. if len(view.sel()) == 1: return sublime.Region(word.a + offset - 1) # return sublime.Region(word.b - len(suffix) - 1) return s if mode != _MODE_INTERNAL_NORMAL: return # TODO: Deal with octal, hex notations. # TODO: Improve detection of numbers. pairs = list(self.check_words(list(self.view.sel()))) if not pairs: next_nums = self.find_next_num(list(self.view.sel())) if not next_nums: utils.blink() return pairs = iter(reversed(list(self.check_words(next_nums)))) else: pairs = iter(reversed(list(self.check_words(self.view.sel())))) try: xpos = [] if len(self.view.sel()) > 1: rowcols = [self.view.rowcol(s.b - 1) for s in self.view.sel()] regions_transformer_reversed(self.view, f) if len(self.view.sel()) > 1: regs = [sublime.Region(self.view.text_point(*rc)) for rc in rowcols] next_nums = self.find_next_num(regs) if next_nums: self.view.sel().clear() self.view.sel().add_all(next_nums) except StopIteration: utils.blink() return
def run(self, name=None): state = VintageState(self.view) state.cancel_macro = False if not (name and state.latest_macro_name): return if name == '@': # Run the macro recorded latest. commands = state.macros[state.latest_macro_name] else: try: commands = state.macros[name] except KeyError: # TODO: Add 'delayed message' support to VintageState. return for cmd in commands: if state.cancel_macro: utils.blink() break self.view.run_command(cmd['command'], cmd['args'])
def run(self, edit, count=1, mode=None): def f(view, s): if mode == _MODE_INTERNAL_NORMAL: word = view.word(s.a) new_digit = int(view.substr(word)) - count view.replace(edit, word, str(new_digit)) return s if mode != _MODE_INTERNAL_NORMAL: return # TODO: Deal with octal, hex notations. # TODO: Improve detection of numbers. # TODO: Find the next numeric word in the line if none is found under the caret. words = [self.view.substr(self.view.word(s)) for s in self.view.sel()] if not all([w.isdigit() for w in words]): utils.blink() return regions_transformer(self.view, f)
def run(self, edit, count=1, mode=None): def f(view, s): if mode == _MODE_INTERNAL_NORMAL: word = view.word(s.a) new_digit = int(view.substr(word)) + count view.replace(edit, word, str(new_digit)) return s if mode != _MODE_INTERNAL_NORMAL: return # TODO: Deal with octal, hex notations. # TODO: Improve detection of numbers. # TODO: Find the next numeric word in the line if none is found under the caret. words = [self.view.substr(self.view.word(s)) for s in self.view.sel()] if not all([w.isdigit() for w in words]): utils.blink() return regions_transformer(self.view, f)
def f(view, s): eol = view.line(s.b).end() if not s.empty(): eol = view.line(s.b - 1).end() match = s offset = 1 if count > 1 else 0 for i in range(count): # Define search range as 'rest of the line to the right'. if state.mode != MODE_VISUAL: search_range = sublime.Region( min(match.b + 1 + offset, eol), eol) else: search_range = sublime.Region(min(match.b + offset, eol), eol) match = find_in_range(view, character, search_range.a, search_range.b, sublime.LITERAL) # Count too high or simply no match; break. if match is None: match = s break if state.mode == MODE_VISUAL or mode == _MODE_INTERNAL_NORMAL: if match == s: # FIXME: It won't blink because the current light can't be highlighted right # now (we are in command mode and there is a selection on the screen. Perhaps # we can make the gutter blink instead.) utils.blink() return s return sublime.Region(s.a, match.b - 1) if match == s: utils.blink() return s return sublime.Region(match.a - 1, match.a - 1)
def eval(self): """Examines the current state and decides whether to actually run the action/motion. """ if self.cancel_action: self.eval_cancel_action() self.reset() utils.blink() # Action + motion, like in '3dj'. elif self.action and self.motion: self.eval_full_command() # Motion only, like in '3j'. elif self.motion: vi_cmd_data = self.parse_motion() self.view.run_command('vi_run', vi_cmd_data) self.reset() self.update_status() # Action only, like in 'd' or 'esc'. Some actions can be executed without a motion. elif self.action: self.eval_lone_action()
def eval_full_command(self): """Evaluates a command like 3dj, where there is an action as well as a motion. """ vi_cmd_data = self.parse_motion() # Sometimes we'll have an incomplete motion, like in dg leading up to dgg. In this case, # we don't want the vi command evaluated just yet. if vi_cmd_data['is_digraph_start']: return vi_cmd_data = self.parse_action(vi_cmd_data) if not vi_cmd_data['is_digraph_start']: # We are about to run an action, so let Sublime Text know we want all editing # steps folded into a single sequence. "All editing steps" means slightly different # things depending on the mode we are in. if vi_cmd_data['_mark_groups_for_gluing']: self.view.run_command('maybe_mark_undo_groups_for_gluing') self.view.run_command('vi_run', vi_cmd_data) self.reset() else: # If we have a digraph start, the global data is in an invalid state because we # are still missing the complete digraph. Abort and clean up. if vi_cmd_data['_exit_mode'] == MODE_INSERT: # We've been requested to change to this mode. For example, we're looking at # CTRL+r,j in INSERTMODE, which is an invalid sequence. utils.blink() self.reset() self.enter_insert_mode() # We have an invalid command which consists in an action and a motion, like gl. Abort. elif (self.mode == MODE_NORMAL) and self.motion: utils.blink() self.reset() elif self.mode != MODE_NORMAL: # Normally we'd go back to normal mode. self.enter_normal_mode() self.reset()
def eval(self): """Examines the current state and decides whether to actually run the action/motion. """ if self.cancel_action: self.eval_cancel_action() self.reset() utils.blink() elif self.expecting_user_input: return # Action + motion, like in '3dj'. elif self.action and self.motion: # TODO: abstract this away into a function. read_only = False if self.view.file_name(): mode = os.stat(self.view.file_name()) read_only = (stat.S_IMODE(mode.st_mode) & stat.S_IWUSR != stat.S_IWUSR) if read_only or self.view.is_read_only(): self.reset() utils.blink() sublime.status_message("Vintageous: Readonly file.") return self.eval_full_command() # Motion only, like in '3j'. elif self.motion: vi_cmd_data = self.parse_motion() self.view.run_command('vi_run', vi_cmd_data) self.reset() # Action only, like in 'd' or 'esc'. Some actions can be executed without a motion. elif self.action: # TODO: abstract this away into a function. read_only = False if self.view.file_name(): mode = os.stat(self.view.file_name()) read_only = (stat.S_IMODE(mode.st_mode) & stat.S_IWUSR != stat.S_IWUSR) if read_only or self.view.is_read_only(): self.reset() utils.blink() sublime.status_message("Vintageous: Readonly file.") return self.eval_lone_action()
def run(self, line_range=None, forced=False, file_name='', plusplus_args='', operator='', target_redirect='', subcmd=''): if file_name and target_redirect: sublime.status_message('Vintageous: Too many arguments.') return appending = operator == '>>' a_range = line_range['text_range'] self.view = self.window.active_view() content = get_region_by_range(self.view, line_range=line_range) if a_range else \ [sublime.Region(0, self.view.size())] read_only = False if self.view.file_name(): mode = os.stat(self.view.file_name()) read_only = (stat.S_IMODE(mode.st_mode) & stat.S_IWUSR != stat.S_IWUSR) if target_redirect: target = self.window.new_file() target.set_name(target_redirect) elif file_name: def report_error(msg): sublime.status_message('Vintageous: %s' % msg) file_path = os.path.abspath(os.path.expanduser(file_name)) if os.path.exists(file_path) and (file_path != self.view.file_name()): # TODO add w! flag # TODO: Hook this up with ex error handling (ex/errors.py). msg = "File '{0}' already exists.".format(file_path) report_error(msg) return if not os.path.exists(os.path.dirname(file_path)): msg = "Directory '{0}' does not exist.".format( os.path.dirname(file_path)) report_error(msg) return try: # FIXME: We need to do some work with encodings here, don't we? with open(file_path, 'w+') as temp_file: for frag in reversed(content): temp_file.write(self.view.substr(frag)) temp_file.close() sublime.status_message( "Vintageous: Saved {0}".format(file_path)) row, col = self.view.rowcol(self.view.sel()[0].b) encoded_fn = "{0}:{1}:{2}".format(file_path, row + 1, col + 1) self.view.set_scratch(True) w = self.window w.run_command('close') w.open_file(encoded_fn, sublime.ENCODED_POSITION) return except IOError as e: report_error("Failed to create file '%s'." % file_name) return window = self.window window.open_file(file_path) return else: target = self.view if (read_only or self.view.is_read_only()) and not forced: utils.blink() sublime.status_message( "Vintageous: Can't write read-only file.") return start = 0 if not appending else target.size() prefix = '\n' if appending and target.size() > 0 else '' if appending or target_redirect: for frag in reversed(content): target.run_command( 'append', {'characters': prefix + self.view.substr(frag) + '\n'}) elif a_range: start_deleting = 0 text = '' for frag in content: text += self.view.substr(frag) + '\n' start_deleting = len(text) self.view.run_command('ex_replace_file', { 'start': 0, 'end': 0, 'with_what': text }) else: dirname = os.path.dirname(self.view.file_name()) if not os.path.exists(dirname): os.makedirs(dirname) self.window.run_command('save') # This may unluckily prevent the user from seeing ST's feedback about saving the current # file. state = State(self.window.active_view()) if state.mode != MODE_NORMAL: state.enter_normal_mode() self.window.run_command('vi_enter_normal_mode')
def run(self, edit, **vi_cmd_data): self.debug("Data in ViRunCommand:", vi_cmd_data) state = VintageState(self.view) try: # Always save carets just in case. self.save_caret_pos() # If we're about to jump, we need to remember the current position so we can jump back # here. XXX creates_jump_at_current_position might be redundant. # TODO: Extract method. if vi_cmd_data['creates_jump_at_current_position'] or vi_cmd_data['is_jump']: self.add_to_jump_list(vi_cmd_data) sels_before_motion = list(self.view.sel()) # XXX: Fix this. When should we run the motion exactly? if vi_cmd_data['action']: # If no motion is present, we know we just have to run the action (like ctrl+w, v). if ((vi_cmd_data['motion']) or (vi_cmd_data['motion_required'] and not view.has_non_empty_selection_region())): self.do_whole_motion(vi_cmd_data) # The motion didn't change the selections: abort action if so required. # TODO: What to do with .post_action() and .restore_original_carets_if_needed() in this event? if (vi_cmd_data['mode'] in (_MODE_INTERNAL_NORMAL, MODE_VISUAL) and any([(old == new) for (old, new) in zip(sels_before_motion, list(self.view.sel()))]) and vi_cmd_data['cancel_action_if_motion_fails']): # TODO: There should be a method that lets us do this without modifying vi_cmd_data. vi_cmd_data['restore_original_carets'] = True # FIXME: This is redundant: we call the same method in 'finally' clause. self.restore_original_carets_if_needed(vi_cmd_data) utils.blink() state.cancel_macro = True return self.reorient_begin_to_end(vi_cmd_data) self.do_action(vi_cmd_data) else: self.do_whole_motion(vi_cmd_data) if (vi_cmd_data['mode'] == MODE_NORMAL and any([(old == new) for (old, new) in zip(sels_before_motion, list(self.view.sel()))])): state.cancel_macro = True utils.blink() finally: # XXX: post_action should be run only if do_action succeeds (?). self.do_post_action(vi_cmd_data) # TODO: Extract method. if vi_cmd_data['must_update_xpos']: state = VintageState(self.view) state.update_xpos() self.do_modify_selections(vi_cmd_data) self.restore_original_carets_if_needed(vi_cmd_data) # TODO: Extract method. if vi_cmd_data['scroll_into_view']: if vi_cmd_data['scroll_command']: self.view.run_command(*vi_cmd_data['scroll_command']) else: # TODO: If moving by lines, scroll the minimum amount to display the new sels. self.view.show(self.view.sel()[0]) # We cannot run (maybe_)mark_undo_groups_for_gluing/glue_marked_undo_groups commands # within a command meant to be subsumed in the group to be glued. It won't work. So # let's save the data we need to transtion to the next mode, which will be taken care # of later by VintageState.reset(). # XXX: This code can probably be moved to VintageState.run(). # TODO: Extract method. state = VintageState(self.view) state.next_mode = vi_cmd_data['next_mode'] state.next_mode_command = vi_cmd_data['follow_up_mode']
def f(view, s): if mode == MODE_NORMAL: current_row = view.rowcol(s.b)[0] target_row = min(current_row + count, view.rowcol(view.size())[0]) target_pt = view.text_point(target_row, 0) if current_row == view.rowcol(view.size())[0]: utils.blink() if view.line(target_pt).empty(): return sublime.Region(target_pt, target_pt) target_pt = min(target_pt + xpos, view.line(target_pt).b - 1) return sublime.Region(target_pt, target_pt) if mode == _MODE_INTERNAL_NORMAL: current_row = view.rowcol(s.b)[0] target_row = min(current_row + count, view.rowcol(view.size())[0]) target_pt = view.text_point(target_row, 0) return sublime.Region(view.line(s.a).a, view.full_line(target_pt).b) if mode == MODE_VISUAL: exact_position = s.b - 1 if (s.a < s.b) else s.b current_row = view.rowcol(exact_position)[0] target_row = min(current_row + count, view.rowcol(view.size())[0]) target_pt = view.text_point(target_row, 0) is_long_enough = view.full_line(target_pt).size() > xpos # We're crossing over to the other side of .a; we need to modify .a. crosses_a = False if (s.a > s.b) and (view.rowcol(s.a)[0] < target_row): crosses_a = True if view.line(s.begin()) == view.line(s.end() - 1): if s.a > s.b: if is_long_enough: return sublime.Region(s.a - 1, view.text_point(target_row, xpos) + 1) else: return sublime.Region(s.a - 1, view.full_line(target_pt).b) # Returning to the same line... if not crosses_a and abs(view.rowcol(s.begin())[0] - view.rowcol(s.end())[0]) == 1: if s.a > s.b: if is_long_enough: if view.rowcol(s.a - 1)[1] <= view.rowcol(s.b)[1]: return sublime.Region(s.a - 1, view.text_point(target_row, xpos) + 1) if is_long_enough: if s.a < s.b: return sublime.Region(s.a, view.text_point(target_row, xpos) + 1) elif s.a > s.b: start = s.a if not crosses_a else s.a - 1 end = view.text_point(target_row, xpos) if crosses_a and xpos == 0: end += 1 return sublime.Region(start, end) else: if s.a < s.b: return sublime.Region(s.a, view.full_line(target_pt).b) elif s.a > s.b: end = view.full_line(target_pt).b end = end - 1 if not crosses_a else end return sublime.Region(s.a, end) if mode == MODE_VISUAL_LINE: if s.a < s.b: current_row = view.rowcol(s.b - 1)[0] target_row = min(current_row + count, view.rowcol(view.size())[0]) target_pt = view.text_point(target_row, 0) return sublime.Region(s.a, view.full_line(target_pt).b) elif s.a > s.b: current_row = view.rowcol(s.b)[0] target_row = min(current_row + count, view.rowcol(view.size())[0]) target_pt = view.text_point(target_row, 0) if target_row > view.rowcol(s.a - 1)[0]: return sublime.Region(view.line(s.a - 1).a, view.full_line(target_pt).b) return sublime.Region(s.a, view.full_line(target_pt).a) return s
def run(self, line_range=None, forced=False, file_name='', plusplus_args='', operator='', target_redirect='', subcmd=''): if file_name and target_redirect: sublime.status_message('Vintageous: Too many arguments.') return appending = operator == '>>' a_range = line_range['text_range'] self.view = self.window.active_view() content = get_region_by_range(self.view, line_range=line_range) if a_range else \ [sublime.Region(0, self.view.size())] read_only = False if self.view.file_name(): mode = os.stat(self.view.file_name()) read_only = (stat.S_IMODE(mode.st_mode) & stat.S_IWUSR != stat.S_IWUSR) if target_redirect: target = self.window.new_file() target.set_name(target_redirect) elif file_name: def report_error(msg): sublime.status_message('Vintageous: %s' % msg) file_path = os.path.abspath(os.path.expanduser(file_name)) if os.path.exists(file_path) and (file_path != self.view.file_name()): # TODO add w! flag # TODO: Hook this up with ex error handling (ex/errors.py). msg = "File '{0}' already exists.".format(file_path) report_error(msg) return if not os.path.exists(os.path.dirname(file_path)): msg = "Directory '{0}' does not exist.".format(os.path.dirname(file_path)) report_error(msg) return try: # FIXME: We need to do some work with encodings here, don't we? with open(file_path, 'w+') as temp_file: for frag in reversed(content): temp_file.write(self.view.substr(frag)) temp_file.close() sublime.status_message("Vintageous: Saved {0}".format(file_path)) row, col = self.view.rowcol(self.view.sel()[0].b) encoded_fn = "{0}:{1}:{2}".format(file_path, row + 1, col + 1) self.view.set_scratch(True) w = self.window w.run_command('close') w.open_file(encoded_fn, sublime.ENCODED_POSITION) return except IOError as e: report_error( "Failed to create file '%s'." % file_name ) return window = self.window window.open_file(file_path) return else: target = self.view if (read_only or self.view.is_read_only()) and not forced: utils.blink() sublime.status_message("Vintageous: Can't write read-only file.") return start = 0 if not appending else target.size() prefix = '\n' if appending and target.size() > 0 else '' if appending or target_redirect: for frag in reversed(content): target.run_command('append', {'characters': prefix + self.view.substr(frag) + '\n'}) elif a_range: start_deleting = 0 text = '' for frag in content: text += self.view.substr(frag) + '\n' start_deleting = len(text) self.view.run_command('ex_replace_file', {'start': 0, 'end': 0, 'with_what': text}) else: dirname = os.path.dirname(self.view.file_name()) if not os.path.exists(dirname): os.makedirs(dirname) self.window.run_command('save') # This may unluckily prevent the user from seeing ST's feedback about saving the current # file. state = VintageState(self.window.active_view()) if state.mode != MODE_NORMAL: state.enter_normal_mode() self.window.run_command('vi_enter_normal_mode')
def run(self, edit, **vi_cmd_data): self.debug("Data in ViRunCommand:", vi_cmd_data) state = VintageState(self.view) try: # Always save carets just in case. self.save_caret_pos() # If we're about to jump, we need to remember the current position so we can jump back # here. XXX creates_jump_at_current_position might be redundant. # TODO: Extract method. if vi_cmd_data['creates_jump_at_current_position'] or vi_cmd_data[ 'is_jump']: self.add_to_jump_list(vi_cmd_data) sels_before_motion = list(self.view.sel()) # XXX: Fix this. When should we run the motion exactly? if vi_cmd_data['action']: # If no motion is present, we know we just have to run the action (like ctrl+w, v). if ((vi_cmd_data['motion']) or (vi_cmd_data['motion_required'] and not view.has_non_empty_selection_region())): self.do_whole_motion(vi_cmd_data) # The motion didn't change the selections: abort action if so required. # TODO: What to do with .post_action() and .restore_original_carets_if_needed() in this event? if (vi_cmd_data['mode'] in (_MODE_INTERNAL_NORMAL, MODE_VISUAL) and any([(old == new) for (old, new) in zip( sels_before_motion, list(self.view.sel()))]) and vi_cmd_data['cancel_action_if_motion_fails']): # TODO: There should be a method that lets us do this without modifying vi_cmd_data. vi_cmd_data['restore_original_carets'] = True # FIXME: This is redundant: we call the same method in 'finally' clause. self.restore_original_carets_if_needed(vi_cmd_data) utils.blink() state.cancel_macro = True return self.do_action(vi_cmd_data) else: self.do_whole_motion(vi_cmd_data) if (vi_cmd_data['mode'] == MODE_NORMAL and any([(old == new) for (old, new) in zip( sels_before_motion, list(self.view.sel()))])): state.cancel_macro = True utils.blink() finally: # XXX: post_action should be run only if do_action succeeds (?). self.do_post_action(vi_cmd_data) # TODO: Extract method. if vi_cmd_data['must_update_xpos']: state = VintageState(self.view) state.update_xpos() self.do_modify_selections(vi_cmd_data) self.restore_original_carets_if_needed(vi_cmd_data) # TODO: Extract method. if vi_cmd_data['scroll_into_view']: if vi_cmd_data['scroll_command']: self.view.run_command(*vi_cmd_data['scroll_command']) else: # TODO: If moving by lines, scroll the minimum amount to display the new sels. self.view.show(self.view.sel()[0]) # We cannot run (maybe_)mark_undo_groups_for_gluing/glue_marked_undo_groups commands # within a command meant to be subsumed in the group to be glued. It won't work. So # let's save the data we need to transtion to the next mode, which will be taken care # of later by VintageState.reset(). # XXX: This code can probably be moved to VintageState.run(). # TODO: Extract method. state = VintageState(self.view) state.next_mode = vi_cmd_data['next_mode'] state.next_mode_command = vi_cmd_data['follow_up_mode']
def run(self): """Examines the current state and decides whether to actually run the action/motion. """ if self.cancel_action: # TODO: add a .parse() method that includes boths steps? vi_cmd_data = self.parse_motion() vi_cmd_data = self.parse_action(vi_cmd_data) if vi_cmd_data['must_blink_on_error']: utils.blink() self.reset(next_mode=vi_cmd_data['_exit_mode']) return # Action + motion, like in "3dj". if self.action and self.motion: vi_cmd_data = self.parse_motion() vi_cmd_data = self.parse_action(vi_cmd_data) if not vi_cmd_data['is_digraph_start']: self.view.run_command('vi_run', vi_cmd_data) else: # If we have a digraph start, the global data is in an invalid state because we # are still missing the complete digraph. Abort and clean up. if vi_cmd_data['_exit_mode'] == MODE_INSERT: # We've been requested to change to this mode. For example, we're looking at # CTRL+r,j in INSERTMODE, which is an invalid sequence. # !!! This could be simplified using parameters in .reset(), but then it # wouldn't be obvious what was going on. Don't refactor. !!! utils.blink() self.reset() self.enter_insert_mode() elif self.mode != MODE_NORMAL: # Normally we'd go back to normal mode. self.enter_normal_mode() self.reset() # Motion only, like in "3j". elif self.motion: self.view.run_command('vi_run', self.parse_motion()) # Action only, like in "d" or "esc". Some actions can be executed without a motion. elif self.action: vi_cmd_data = self.parse_motion() vi_cmd_data = self.parse_action(vi_cmd_data) if vi_cmd_data['is_digraph_start']: if vi_cmd_data['_change_mode_to']: if vi_cmd_data['_change_mode_to'] == MODE_NORMAL: self.enter_normal_mode() # We know we are not ready. return # In cases like gg, we might receive the motion here, so check for that. if self.motion and not self.action: self.view.run_command('vi_run', self.parse_motion()) self.update_status() return if not vi_cmd_data['motion_required']: self.view.run_command('vi_run', vi_cmd_data) self.update_status()
def eval(self): """Examines the current state and decides whether to actually run the action/motion. """ if self.cancel_action: # TODO: add a .parse() method that includes boths steps? vi_cmd_data = self.parse_motion() vi_cmd_data = self.parse_action(vi_cmd_data) if vi_cmd_data["must_blink_on_error"]: utils.blink() # Modify the data that determines the mode we'll end up in when the command finishes. self.next_mode = vi_cmd_data["_exit_mode"] # Since we are exiting early, ensure we leave the selections as the commands wants them. if vi_cmd_data["_exit_mode_command"]: self.view.run_command(vi_cmd_data["_exit_mode_command"]) self.reset() return # Action + motion, like in "3dj". if self.action and self.motion: vi_cmd_data = self.parse_motion() vi_cmd_data = self.parse_action(vi_cmd_data) if not vi_cmd_data["is_digraph_start"]: # We are about to run an action, so let Sublime Text know we want all editing # steps folded into a single sequence. "All editing steps" means slightly different # things depending on the mode we are in. if vi_cmd_data["_mark_groups_for_gluing"]: self.view.run_command("maybe_mark_undo_groups_for_gluing") self.view.run_command("vi_run", vi_cmd_data) self.reset() else: # If we have a digraph start, the global data is in an invalid state because we # are still missing the complete digraph. Abort and clean up. if vi_cmd_data["_exit_mode"] == MODE_INSERT: # We've been requested to change to this mode. For example, we're looking at # CTRL+r,j in INSERTMODE, which is an invalid sequence. # !!! This could be simplified using parameters in .reset(), but then it # wouldn't be obvious what was going on. Don't refactor. !!! utils.blink() self.reset() self.enter_insert_mode() elif self.mode != MODE_NORMAL: # Normally we'd go back to normal mode. self.enter_normal_mode() self.reset() # Motion only, like in '3j'. elif self.motion: vi_cmd_data = self.parse_motion() self.view.run_command("vi_run", vi_cmd_data) self.reset() # Action only, like in "d" or "esc". Some actions can be executed without a motion. elif self.action: vi_cmd_data = self.parse_motion() vi_cmd_data = self.parse_action(vi_cmd_data) if vi_cmd_data["is_digraph_start"]: if vi_cmd_data["_change_mode_to"]: # XXX: When does this happen? Why are we only interested in MODE_NORMAL? # XXX In response to the above, this must be due to Ctrl+r. if vi_cmd_data["_change_mode_to"] == MODE_NORMAL: self.enter_normal_mode() # We know we are not ready. return # In cases like gg, we might receive the motion here, so check for that. # XXX: The above doesn't seem to be true. When is this path reached? if self.motion and not self.action: self.view.run_command("vi_run", self.parse_motion()) self.update_status() self.reset() return if not vi_cmd_data["motion_required"]: # We are about to run an action, so let Sublime Text know we want all editing # steps folded into a single sequence. "All editing steps" means slightly different # things depending on the mode we are in. if vi_cmd_data["_mark_groups_for_gluing"]: self.view.run_command("maybe_mark_undo_groups_for_gluing") self.view.run_command("vi_run", vi_cmd_data) self.reset() self.update_status()