def testFromVisualModeToNormalMode(self): set_text(self.view, 'abc\nxxx\nabc\nabc') add_sel(self.view, self.R((1, 0), (1, 1))) state = VintageState(self.view) state.enter_visual_mode() prev_mode = state.mode self.view.run_command('ex_copy', {'address': '3'}) state = VintageState(self.view) new_mode = state.mode self.assertNotEqual(prev_mode, new_mode) self.assertEqual(new_mode, MODE_NORMAL)
def testFromNormalModeToNormalMode(self): set_text(self.view, 'abc\nxxx\nabc\nabc') add_selection(self.view, self.R((1, 0), (1, 0))) state = VintageState(self.view) state.enter_normal_mode() self.view.run_command('vi_enter_normal_mode') prev_mode = state.mode self.view.run_command('ex_move', {'address': '3'}) state = VintageState(self.view) new_mode = state.mode self.assertEqual(prev_mode, new_mode)
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 on_cancel(self): self.view.erase_regions('vi_inc_search') state = VintageState(self.view) state.reset() if not self.view.visible_region().contains(self.view.sel()[0]): self.view.show(self.view.sel()[0])
def run(self): Vintageous.state._dont_reset_during_init = True state = VintageState(self.view) on_change = self.on_change if state.settings.vi['incsearch'] else None self.view.window().show_input_panel('', '', self.on_done, on_change, self.on_cancel)
def f(view, s): line_text = view.substr(sublime.Region(view.line(s.b).a, s.b)) 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: return sublime.Region(s.a, pt + 1) return sublime.Region(pt + 1, pt + 1) return s
def run(self, edit, extend=False, character=None, mode=None, count=1): def f(view, s): line_text = view.substr(sublime.Region(view.line(s.b).a, s.b)) 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: return sublime.Region(s.a, pt + 1) return sublime.Region(pt + 1, pt + 1) return s if character is None: return else: state = VintageState(self.view) state.last_character_search = character regions_transformer(self.view, f)
def run(self, edit, mode=None, extend=False, exact_word=True): def f(view, s): if exact_word: pattern = r'\b{0}\b'.format(query) else: pattern = query flags = sublime.IGNORECASE if mode == _MODE_INTERNAL_NORMAL: match = reverse_search(view, pattern, 0, current_sel.a, flags) else: match = reverse_search(view, pattern, 0, current_sel.a, flags) if match: if mode == _MODE_INTERNAL_NORMAL: return sublime.Region(s.b, match.begin()) elif state.mode == MODE_VISUAL: return sublime.Region(s.b, match.begin()) elif state.mode == MODE_NORMAL: return sublime.Region(match.begin(), match.begin()) return s state = VintageState(self.view) # TODO: make sure we swallow any leading white space. query = self.view.substr(self.view.word(self.view.sel()[0].end())) if query: state.last_buffer_search = query current_sel = self.view.sel()[0] regions_transformer(self.view, f)
def run(self, edit): state = VintageState(self.view) pairs = [ '"{0} {1}'.format(k, v) for k, v in state.registers.to_dict().items() if v ] self.view.window().show_quick_panel(pairs, self.on_done)
def run(self, path=None, forced=False): if self.view.is_dirty() and not forced: ex_error.display_error(ex_error.ERR_UNSAVED_CHANGES) return state = VintageState(self.view) if not path: state.settings.vi['_cmdline_cd'] = os.path.expanduser("~") self.view.run_command('ex_print_working_dir') return # TODO: It seems there a few symbols that are always substituted when they represent a # filename. We should have a global method of substiting them. if path == '%:h': fname = self.view.file_name() if fname: state.settings.vi['_cmdline_cd'] = os.path.dirname(fname) self.view.run_command('ex_print_working_dir') return path = os.path.realpath(os.path.expandvars(os.path.expanduser(path))) if not os.path.exists(path): # TODO: Add error number in ex_error.py. ex_error.display_error(ex_error.ERR_CANT_FIND_DIR_IN_CDPATH) return state.settings.vi['_cmdline_cd'] = path self.view.run_command('ex_print_working_dir')
def run(self): # We define our own transformer here because we want to handle undo as a special case. # TODO: I don't know if it needs to be an special case in reality. def f(view, s): # Compensates the move issued below. if s.a < s.b: return sublime.Region(s.a + 1, s.a + 1) else: return sublime.Region(s.a, s.a) state = VintageState(self.view) for i in range(state.count): self.view.run_command('undo') if self.view.has_non_empty_selection_region(): regions_transformer(self.view, f) # !! HACK !! ///////////////////////////////////////////////////////// # This is a hack to work around an issue in Sublime Text: # When undoing in normal mode, Sublime Text seems to prime a move by chars # forward that has never been requested by the user or Vintageous. # As far as I can tell, Vintageous isn't at fault here, but it seems weird # to think that Sublime Text is wrong. self.view.run_command('move', { 'by': 'characters', 'forward': False }) # //////////////////////////////////////////////////////////////////// state.update_xpos() # Ensure that we wipe the count, if any. state.reset()
def f(view, s): if mode == _MODE_INTERNAL_NORMAL: if view.line(s).empty(): return s elif mode == MODE_VISUAL: if view.line(s.b - 1).empty() and s.size() == 1: return s state = VintageState(self.view) autoindent = state.settings.vi['autoindent'] if mode == _MODE_INTERNAL_NORMAL: if not autoindent: return self.view.line(s) else: pt = utils.next_non_white_space_char( view, self.view.line(s).a) return sublime.Region(pt, self.view.line(s).b) elif mode == MODE_VISUAL: if not autoindent: return self.view.line(sublime.Region(s.a, s.b - 1)) else: pt = utils.next_non_white_space_char( view, self.view.line(s.a).a) return sublime.Region(pt, self.view.line(s.b - 1).b)
def run(self): state = VintageState(self.view) try: cmd, args, _ = state.repeat_command except TypeError: # Unreachable. return if not cmd: return elif cmd == 'vi_run': args['next_mode'] = MODE_NORMAL args['follow_up_mode'] = 'vi_enter_normal_mode' args['count'] = state.count * args['count'] self.view.run_command(cmd, args) elif cmd == 'sequence': for i, _ in enumerate(args['commands']): # Access this shape: {"commands":[['vi_run', {"foo": 100}],...]} args['commands'][i][1]['next_mode'] = MODE_NORMAL args['commands'][i][1][ 'follow_up_mode'] = 'vi_enter_normal_mode' # TODO: Implement counts properly for 'sequence' command. for i in range(state.count): self.view.run_command(cmd, args) # Ensure we wipe count data if any. state.reset() # XXX: Needed here? Maybe enter_... type commands should be IrreversibleTextCommands so we # must/can call them whenever we need them withouth affecting the undo stack. self.view.run_command('vi_enter_normal_mode')
def run(self, edit, mode=None, character=None, extend=False): def f(view, s): if mode == MODE_VISUAL: if s.a <= s.b: if address.b < s.b: return sublime.Region(s.a + 1, address.b) else: return sublime.Region(s.a, address.b) else: return sublime.Region(s.a + 1, address.b) elif mode == MODE_NORMAL: return address elif mode == _MODE_INTERNAL_NORMAL: return sublime.Region(s.a, address.b) return s state = VintageState(self.view) address = state.marks.get_as_encoded_address(character) if address is None: return if isinstance(address, str): if not address.startswith('<command'): self.view.window().open_file(address, sublime.ENCODED_POSITION) else: # We get a command in this form: <command _vi_double_quote> self.view.run_command(address.split(' ')[1][:-1]) return # This is a motion in a composite command. regions_transformer(self.view, f)
def paste_all(self, edit, sel, at, text, count): state = VintageState(self.view) if state.mode not in (MODE_VISUAL, MODE_VISUAL_LINE): # TODO: generate string first, then insert? # Make sure we can paste at EOF. at = at if at <= self.view.size() else self.view.size() for x in range(count): self.view.insert(edit, at, text) # Return position at which we have just pasted. return at else: if text.startswith('\n'): text = text * count if not text.endswith('\n'): text = text + '\n' else: text = text * count if state.mode == MODE_VISUAL_LINE: if text.startswith('\n'): text = text[1:] self.view.replace(edit, sel, text) # Return position at which we have just pasted. return sel.a
def run(self, edit, register=None, count=1): state = VintageState(self.view) if register: fragments = state.registers[register] else: # TODO: There should be a simpler way of getting the unnamed register's content. fragments = state.registers['"'] sels = list(self.view.sel()) if len(sels) == len(fragments): sel_frag = zip(sels, fragments) else: sel_frag = zip(sels, [ fragments[0], ] * len(sels)) offset = 0 for s, text in sel_frag: if text.endswith('\n'): if utils.is_at_eol(self.view, s) or utils.is_at_bol( self.view, s): self.paste_all(edit, s, self.view.line(s.b).a, text, count) else: self.paste_all(edit, s, self.view.line(s.b - 1).a, text, count) else: self.paste_all(edit, s, s.b + offset, text, count) offset += len(text) * count
def setUp(self): sublime.set_clipboard('') registers._REGISTER_DATA = {} TestsState.view.settings().erase('vintage') TestsState.view.settings().erase('vintageous_use_sys_clipboard') self.regs = VintageState(TestsState.view).registers self.regs.view = mock.Mock()
def run(self, edit): if self.view.score_selector(0, 'text.excmdline') == 0: return cmd, prefix, only_dirs = parse(self.view.substr(self.view.line(0))) if not cmd: return if not FsCompletion.prefix and prefix: FsCompletion.prefix = prefix FsCompletion.is_stale = True elif not FsCompletion.prefix: state = VintageState(self.view) FsCompletion.prefix = state.settings.vi['_cmdline_cd'] + '/' if not FsCompletion.items or FsCompletion.is_stale: FsCompletion.items = iter_paths(FsCompletion.prefix, only_dirs=only_dirs) FsCompletion.is_stale = False try: self.view.run_command('write_fs_completion', { 'cmd': cmd, 'completion': next(FsCompletion.items) }) except StopIteration: try: FsCompletion.items = iter_paths(FsCompletion.prefix, only_dirs=only_dirs) self.view.run_command('write_fs_completion', { 'cmd': cmd, 'completion': next(FsCompletion.items) }) except StopIteration: return
def inner(*args, **kwargs): try: state = VintageState(args[0].view) except AttributeError: state = VintageState(args[0].window.active_view()) old = os.getcwd() try: # FIXME: Under some circumstances, like when switching projects to # a file whose _cmdline_cd has not been set, _cmdline_cd might # return 'None'. In such cases, change to the actual current # directory as a last measure. (We should probably fix this anyway). os.chdir(state.settings.vi['_cmdline_cd'] or old) f(*args, **kwargs) finally: os.chdir(old)
def run(self, edit, line_range=None): if not line_range['text_range']: # No-op: user issued ":". return ranges, _ = ex_range.new_calculate_range(self.view, line_range) a, b = ranges[0] # FIXME: This should be handled by the parser. # FIXME: In Vim, 0 seems to equal 1 in ranges. b = b if line_range['text_range'] != '0' else 1 state = VintageState(self.view) # FIXME: In Visual mode, goto line does some weird stuff. if state.mode == MODE_NORMAL: # TODO: push all this code down to ViGoToLine? self.view.window().run_command('vi_add_to_jump_list') self.view.run_command('vi_go_to_line', { 'line': b, 'mode': MODE_NORMAL }) self.view.window().run_command('vi_add_to_jump_list') self.view.show(self.view.sel()[0]) elif state.mode in (MODE_VISUAL, MODE_VISUAL_LINE) and line_range['right_offset']: # TODO: push all this code down to ViGoToLine? self.view.run_command('vi_enter_normal_mode') self.view.window().run_command('vi_add_to_jump_list') # FIXME: The parser fails with '<,'>100. 100 is not the right_offset, but an argument. b = self.view.rowcol(self.view.sel()[0].b - 1)[0] + line_range['right_offset'] + 1 self.view.run_command('vi_go_to_line', { 'line': b, 'mode': MODE_NORMAL }) self.view.window().run_command('vi_add_to_jump_list') self.view.show(self.view.sel()[0]) state.display_partial_command()
def run(self, inclusive=False): state = VintageState(self.view) if inclusive: state.motion = 'vi_inclusive_text_object' else: state.motion = 'vi_exclusive_text_object' state.expecting_user_input = True
def setUp(self): sublime.set_clipboard('') registers._REGISTER_DATA = {} TestsState.view.settings().erase('vintage') TestsState.view.settings().erase('vintageous_use_sys_clipboard') # self.regs = Registers(view=TestsState.view, # settings=SettingsManager(view=TestsState.view)) self.regs = VintageState(TestsState.view).registers
def testCaretEndsInExpectedRegion(self): set_text(self.view, ''.join(('foo bar\nfoo bar\nfoo bar\n', ))) add_sel(self.view, self.R((1, 3), (1, 0))) VintageState(self.view).mode = MODE_VISUAL self.view.run_command('vi_enter_normal_mode', {}) self.assertEqual(self.R((1, 0), (1, 0)), first_sel(self.view))
def testFromVisualModeToNormalMode(self): set_text(self.view, 'abc\nxxx\nabc\nabc') add_sel(self.view, self.R((1, 0), (1, 1))) state = VintageState(self.view) state.enter_visual_mode() prev_mode = state.mode self.range['left_ref'] = "'<" self.range['right_ref'] = "'>" self.range['text_range'] = "'<,'>" self.view.run_command('ex_delete', {'line_range': self.range}) state = VintageState(self.view) new_mode = state.mode self.assertNotEqual(prev_mode, new_mode) self.assertEqual(new_mode, MODE_NORMAL)
def on_done(self, idx): """Save selected value to `"` register.""" if idx == -1: return state = VintageState(self.view) value = list(state.registers.to_dict().values())[idx] state.registers['"'] = value
def testFromNormalModeToNormalMode(self): set_text(self.view, 'abc\nxxx\nabc\nabc') add_sel(self.view, self.R((1, 0), (1, 0))) state = VintageState(self.view) state.enter_normal_mode() self.view.run_command('vi_enter_normal_mode') prev_mode = state.mode self.range['left_offset'] = 2 self.range['text_range'] = '2' self.view.run_command('ex_delete', {'line_range': self.range}) state = VintageState(self.view) new_mode = state.mode self.assertEqual(prev_mode, new_mode)
def run(self, edit, commands): for cmd, args in commands: self.view.run_command(cmd, args) # XXX: Sequence is a special case in that it doesn't run through vi_run, so we need to # ensure the next mode is correct. Maybe we can improve this by making it more similar to # regular commands? state = VintageState(self.view) state.enter_normal_mode()
def run(self, edit): state = VintageState(self.view) if state.mode == MODE_VISUAL: state.store_visual_selections() self.view.run_command('collapse_to_direction') self.view.run_command('dont_stay_on_eol_backward') state.enter_normal_mode()
def run(self, edit, register=None, count=1): state = VintageState(self.view) register = register or '"' fragments = state.registers[register] if not fragments: print("Vintageous: Nothing in register \".") return sels = list(self.view.sel()) # If we have the same number of pastes and selections, map 1:1. Otherwise paste paste[0] # to all target selections. if len(sels) == len(fragments): sel_to_frag_mapped = zip(sels, fragments) else: sel_to_frag_mapped = zip(sels, [ fragments[0], ] * len(sels)) # FIXME: Fix this mess. Separate linewise from charwise pasting. pasting_linewise = True offset = 0 paste_locations = [] for selection, fragment in reversed(list(sel_to_frag_mapped)): fragment = self.prepare_fragment(fragment) if fragment.startswith('\n'): # Pasting linewise... # If pasting at EOL or BOL, make sure we paste before the newline character. if (utils.is_at_eol(self.view, selection) or utils.is_at_bol(self.view, selection)): l = self.paste_all(edit, selection, self.view.line(selection.b).b, fragment, count) paste_locations.append(l) else: l = self.paste_all(edit, selection, self.view.line(selection.b - 1).b, fragment, count) paste_locations.append(l) else: pasting_linewise = False # Pasting charwise... # If pasting at EOL, make sure we don't paste after the newline character. if self.view.substr(selection.b) == '\n': l = self.paste_all(edit, selection, selection.b + offset, fragment, count) paste_locations.append(l) else: l = self.paste_all(edit, selection, selection.b + offset + 1, fragment, count) paste_locations.append(l) offset += len(fragment) * count if pasting_linewise: self.reset_carets_linewise() else: self.reset_carets_charwise(paste_locations, len(fragment))
def run(self, character=None): state = VintageState(self.view) if character is None: state.motion = 'vi_big_t' state.expecting_user_input = True else: state.user_input = character state.expecting_user_input = False state.eval()