def reset_command_data(view) -> None: # Resets all temp data needed to build a command or partial command. motion = get_motion(view) action = get_action(view) if _must_update_xpos(motion, action): update_xpos(view) if _must_scroll_into_view(motion, action): # Intentionally using the active view because the previous command # may have switched views and view would be the previous one. active_view = active_window().active_view() _scroll_into_view(active_view, get_mode(active_view)) action and action.reset() set_action(view, None) motion and motion.reset() set_motion(view, None) set_action_count(view, '') set_motion_count(view, '') set_sequence(view, '') set_partial_sequence(view, '') set_register(view, '"') set_must_capture_register_name(view, False) reset_status_line(view, get_mode(view))
def mappings_resolve(view, sequence: str = None, mode: str = None, check_user_mappings: bool = True): # Look at the current global state and return the command mapped to the available sequence. # # Args: # sequence (str): The command sequence. If a sequence is passed, it is # used instead of the global state's. This is necessary for some # commands that aren't name spaces but act as them (for example, # ys from the surround plugin). # mode (str): If different than None, it will be used instead of the # global state's. This is necessary when we are in operator # pending mode and we receive a new action. By combining the # existing action's name with name of the action just received we # could find a new action. # check_user_mappings (bool): # # Returns: # Mapping: # ViMissingCommandDef: If not found. # We usually need to look at the partial sequence, but some commands do # weird things, like ys, which isn't a namespace but behaves as such # sometimes. seq = sequence or get_partial_sequence(view) command = None if check_user_mappings: # Resolve the full sequence rather than the "bare" sequence, because the # user may have defined some mappings that start with numbers (counts), # or " (register character), which are stripped from the bare sequences. # See https://github.com/NeoVintageous/NeoVintageous/issues/434. # XXX The reason these does not pass the mode, and instead uses the # get_mode(), is because implementation of commands like dd are a bit # hacky. For example, the dd definition does is not assigned to operator # pending mode, the second d is instead caught by the feed key command # and resolved by specifying NORMAL mode explicitly, which resolves the # delete line command definition. Commands like this can probably be # fixed by allowing the definitions to handle the OPERATOR PENDING and # let the definition handle any special-cases itself instead of passing # off the responsibility to the feed key command. command = _seq_to_mapping(get_mode(view), seq) if not command: command = _seq_to_command(view, to_bare_command_name(seq), mode or get_mode(view)) _log.info('resolved %s mode=%s sequence=%s %s', command, mode, sequence, command.__class__.__mro__) return command
def is_runnable(view) -> bool: # Returns: # True if motion and/or action is in a runnable state, False otherwise. # Raises: # ValueError: Invlid mode. action = get_action(view) motion = get_motion(view) if must_collect_input(view, motion, action): return False mode = get_mode(view) if action and motion: if mode != NORMAL: raise ValueError('invalid mode') return True if (action and (not action.motion_required or is_visual_mode(mode))): if mode == OPERATOR_PENDING: raise ValueError('action has invalid mode') return True if motion: if mode == OPERATOR_PENDING: raise ValueError('motion has invalid mode') return True return False
def translate(self, view): return { 'action': 'nv_enter_select_mode', 'action_args': { 'mode': get_mode(view) } }
def update_status_line(view) -> None: mode_name = mode_to_name(get_mode(view)) if mode_name: view.set_status('vim-mode', '-- {} --'.format(mode_name) if mode_name else '') view.set_status('vim-seq', get_sequence(view))
def translate(self, view): return { 'action': 'nv_vi_select_k', 'action_args': { 'mode': get_mode(view), 'count': get_count(view) } }
def translate(self, view): return { 'action': '_nv_surround', 'action_args': { 'action': 'ys', 'mode': get_mode(view), 'replacement': self.inp } }
def translate(self, view): return { 'action': '_nv_surround', 'action_args': { 'action': 'ds', 'mode': get_mode(view), 'target': self.inp } }
def translate(self, view): return { 'action': '_nv_commentary', 'action_args': { 'action': 'C', 'mode': get_mode(view), 'count': get_count(view) } }
def translate(self, view): return { 'action': '_nv_unimpaired', 'action_args': { 'mode': get_mode(view), 'count': get_count(view), 'action': 'context_next' } }
def translate(self, view): return { 'action': '_nv_unimpaired', 'action_args': { 'mode': get_mode(view), 'count': get_count(view), 'action': 'goto_next_conflict_marker' } }
def translate(self, view): return { 'action': '_nv_unimpaired', 'action_args': { 'mode': get_mode(view), 'count': get_count(view), 'action': 'blank_down' } }
def translate(self, view): return { 'action': '_nv_unimpaired', 'action_args': { 'mode': get_mode(view), 'count': get_count(view), 'action': 'tabprevious' } }
def translate(self, view): return { 'motion': '_nv_sneak', 'motion_args': { 'mode': get_mode(view), 'count': get_count(view), 'search': self.inp.rstrip() } }
def translate(self, view): return { 'action': 'nv_unimpaired', 'action_args': { 'mode': get_mode(view), 'count': get_count(view), 'action': 'move_up' } }
def translate(self, view): return { 'action': 'nv_surround', 'action_args': { 'action': 'cs', 'mode': get_mode(view), 'target': self.inp[0], 'replacement': self.inp[1:] } }
def on_text_command(self, view, command: str, args: dict): # Called when a text command is issued. # # The listener may return a (command, arguments) tuple to rewrite the # command, or None to run the command unmodified. if command == 'drag_select': # Updates the mode based on mouse events. For example, a double # click will select a word and enter VISUAL mode. A triple click # will select a line and enter VISUAL LINE mode. # # The command is rewritten by returning a chain of commands that # executes the original drag_select command followed by entering the # correct mode. mode = get_mode(view) if mode in (VISUAL, VISUAL_LINE, VISUAL_BLOCK): if (args.get('extend') or (args.get('by') == 'words') or args.get('additive')): return elif args.get('by') == 'lines': # Triple click: enter VISUAL LINE. return ('nv_run_cmds', { 'commands': [['drag_select', args], ['nv_enter_visual_line_mode', { 'mode': mode }]] }) elif not args.get('extend'): # Single click: enter NORMAL. return ('nv_run_cmds', { 'commands': [['drag_select', args], ['nv_enter_normal_mode', { 'mode': mode }]] }) elif mode == NORMAL: # TODO Dragging the mouse does not seem to fire a different event than simply clicking. This makes it hard to update the xpos. See https://github.com/SublimeTextIssues/Core/issues/2117. # noqa: E501 if args.get('extend') or (args.get('by') == 'words'): # Double click: enter VISUAL. return ('nv_run_cmds', { 'commands': [['drag_select', args], ['nv_enter_visual_mode', { 'mode': mode }]] })
def translate(self, view): return { 'action': '_nv_surround', 'action_args': { 'action': 'ys', 'mode': get_mode(view), 'motion': { 'motion': '_vi_select_text_object', 'motion_args': { 'mode': INTERNAL_NORMAL, 'count': 1, 'inclusive': False, 'text_object': 'l' } }, 'replacement': self.inp } }
def on_query_context(self, view, key, operator, operand, match_all): # Called when determining to trigger a key binding with the given context key. # # If the plugin knows how to respond to the context, it should return # either True of False. If the context is unknown, it should return # None. # # Args: # view (View): # key (str): # operator (int): # operand (bool): # match_all (bool): # # Returns: # bool: If the context is known. # None: If the context is unknown. if key == 'nv_handle_key': handle_keys = get_setting(view, 'handle_keys') if handle_keys: try: # Check if the key (no mode prefix; all modes) should be handled. return bool(handle_keys[operand]) except KeyError: # Check if the key should be handled only for a specific mode. # The format is "{mode}_{key}" e.g. "n_<C-w>", "v_<C-w>" # meaning NORMAL, VISUAL respectively. No prefix implies all # modes. See mode_to_char() for a list of valid mode prefixes. cur_mode_char = mode_to_char(get_mode(view)) if cur_mode_char: try: return bool(handle_keys['%s_%s' % (cur_mode_char, operand)]) except KeyError: pass # By default all keys are handled. return True try: return _query_contexts[key](view, operator, operand, match_all) except KeyError: pass
def on_activated(self, view): # Clear any visual selections in the view we are leaving. This mirrors # Vim behaviour. We can't put this functionality in the # view.on_deactivate() event, because that event is triggered when the # user right button clicks the view with the mouse, and we don't want # visual selections to be cleared on mouse right button clicks. if is_view(view): window = view.window() if window: active_group = window.active_group() for group in range(window.num_groups()): if group != active_group: other_view = window.active_view_in_group(group) if other_view and other_view != view: sel = other_view.sel() if len(sel) > 0 and any( [not s.empty() for s in sel]): enter_normal_mode(other_view, get_mode(other_view)) # Initialise view. init_state(view)
def init_state(view) -> None: # Initialise view state. # # Runs every time a view is activated, loaded, etc. # Don't initialise if we get a console, widget, panel, or any other view # where Vim modes are not relevant. Some related initialised settings that # may cause unexpected behaviours if they exist are erased "cleaned" too. if not is_view(view): try: # TODO "cleaning" views that are not initialised shouldn't be necessary? clean_view(view) except Exception: _log.debug( 'could not clean an object: console, widget, panel, etc.') finally: return if not get_reset_during_init(view): # Probably exiting from an input panel, like when using '/'. Don't reset # the global state, as it may contain data needed to complete the # command that's being built. set_reset_during_init(view, True) return mode = get_mode(view) # Does user want to reset mode (to normal mode) when initialising state? if mode not in (NORMAL, UNKNOWN) and not get_setting( view, 'reset_mode_when_switching_tabs'): return # Fix malformed selection: if we have no selections, add one. if len(view.sel()) == 0: view.sel().add(0) if get_setting(view, 'default_mode') == 'insert': if mode in (NORMAL, UNKNOWN): enter_insert_mode(view, mode) elif mode in (VISUAL, VISUAL_LINE, VISUAL_BLOCK): # Visual modes are not reset (to normal mode), because actions like # pressing the super key or opening a command-palette/overlay will cause # the active view to lose focus and when focus is received again it # triggers the on_activated() event, this in turn initialises the view' # state, which would reset the visual mode to normal mode, therefore, # for example, any command run from the command palette that expects to # operate on a visual selection wouldn't work because the visual # selection is reset to normal mode before the command has time to run. # See https://github.com/NeoVintageous/NeoVintageous/issues/547 pass elif mode in (INSERT, REPLACE): # NOTE that the mode is not passed as an argument because it causes the # cursor to move back one point from it's current position, for example # when pressing i<Esc>i<Esc>i<Esc> the cursor moves one point each time, # which is expected, but not expected when initialising state. But not # passing the mode may also be causing some other hidden bugs too. view.window().run_command('_enter_normal_mode', {'from_init': True}) elif mode != VISUAL and view.has_non_empty_selection_region(): # Try to fixup a malformed visual state. For example, apparently this # can happen when a search is performed via a search panel and "Find # All" is pressed. In that case, multiple selections may need fixing. view.window().run_command('_enter_visual_mode', {'mode': mode}) else: # This may be run when we're coming from cmdline mode. mode = VISUAL if view.has_non_empty_selection_region() else mode view.window().run_command('_enter_normal_mode', { 'mode': mode, 'from_init': True }) reset_command_data(view)
def evaluate_state(view) -> None: _log.debug('evaluating...') if not is_runnable(view): _log.debug('not runnable!') return action = get_action(view) motion = get_motion(view) if action and motion: # Evaluate action with motion: runs the action with the motion as an # argument. The motion's mode is set to INTERNAL_NORMAL and is run # by the action internally to make the selection it operates on. For # example the motion commands can be used after an operator command, # to have the command operate on the text that was moved over. action_cmd = action.translate(view) motion_cmd = motion.translate(view) _log.debug('action: %s', action_cmd) _log.debug('motion: %s', motion_cmd) set_mode(view, INTERNAL_NORMAL) if 'mode' in action_cmd['action_args']: action_cmd['action_args']['mode'] = INTERNAL_NORMAL if 'mode' in motion_cmd['motion_args']: motion_cmd['motion_args']['mode'] = INTERNAL_NORMAL args = action_cmd['action_args'] args['count'] = 1 # Let the action run the motion within its edit object so that we # don't need to worry about grouping edits to the buffer. args['motion'] = motion_cmd if get_glue_until_normal_mode( view) and not is_processing_notation(view): run_window_command('mark_undo_groups_for_gluing') add_macro_step(view, action_cmd['action'], args) run_window_command(action_cmd['action'], args) if is_interactive(view) and get_action(view).repeatable: set_repeat_data( view, ('vi', str(get_sequence(view)), get_mode(view), None)) reset_command_data(view) return # Nothing more to do. if motion: # Evaluate motion: Run it. motion_cmd = motion.translate(view) _log.debug('motion: %s', motion_cmd) add_macro_step(view, motion_cmd['motion'], motion_cmd['motion_args']) run_motion(view, motion_cmd) if action: # Evaluate action. Run it. action_cmd = action.translate(view) _log.debug('action: %s', action_cmd) if get_mode(view) == NORMAL: set_mode(view, INTERNAL_NORMAL) if 'mode' in action_cmd['action_args']: action_cmd['action_args']['mode'] = INTERNAL_NORMAL elif is_visual_mode(get_mode(view)): # Special-case exclusion: saving the previous selection would # overwrite the previous selection needed e.g. gv in a VISUAL # mode needs to expand or contract to previous selection. if action_cmd['action'] != '_vi_gv': save_previous_selection(view, get_mode(view)) # Some commands, like 'i' or 'a', open a series of edits that need # to be grouped together unless we are gluing a larger sequence # through _nv_process_notation. For example, aFOOBAR<Esc> should be # grouped atomically, but not inside a sequence like # iXXX<Esc>llaYYY<Esc>, where we want to group the whole sequence # instead. if get_glue_until_normal_mode( view) and not is_processing_notation(view): run_window_command('mark_undo_groups_for_gluing') sequence = get_sequence(view) visual_repeat_data = get_visual_repeat_data(view, get_mode(view)) action = get_action(view) add_macro_step(view, action_cmd['action'], action_cmd['action_args']) run_action(active_window(), action_cmd) if not (is_processing_notation(view) and get_glue_until_normal_mode(view)) and action.repeatable: set_repeat_data( view, ('vi', sequence, get_mode(view), visual_repeat_data)) if get_mode(view) == INTERNAL_NORMAL: set_mode(view, NORMAL) reset_command_data(view)
def on_post_save(self, view): if is_view(view): # Ensure the carets are within valid bounds. For instance, this is a # concern when 'trim_trailing_white_space_on_save' is set to true. fix_eol_cursor(view, get_mode(view))