Skip to content

Commit

Permalink
Internal: Refactor ex mode (core)
Browse files Browse the repository at this point in the history
  • Loading branch information
gerardroche committed Apr 13, 2018
1 parent f9f3765 commit 590ede8
Show file tree
Hide file tree
Showing 13 changed files with 906 additions and 325 deletions.
84 changes: 18 additions & 66 deletions nv/cmds.py
Expand Up @@ -24,13 +24,13 @@
from sublime_plugin import WindowCommand

from NeoVintageous.nv import rc
from NeoVintageous.nv.ex_cmds import do_ex_command
from NeoVintageous.nv.ex_cmds import do_ex_command_default
from NeoVintageous.nv.ex_cmds import do_ex_text_command
from NeoVintageous.nv.ex.completions import iter_paths
from NeoVintageous.nv.ex.completions import parse_for_fs
from NeoVintageous.nv.ex.completions import parse_for_setting
from NeoVintageous.nv.ex.parser import parse_command_line
from NeoVintageous.nv.ex_cmds import do_ex_cmd_edit_wrap
from NeoVintageous.nv.ex_cmds import do_ex_cmdline
from NeoVintageous.nv.ex_cmds import do_ex_command
from NeoVintageous.nv.ex_cmds import do_ex_user_cmdline
from NeoVintageous.nv.history import history_get
from NeoVintageous.nv.history import history_get_type
from NeoVintageous.nv.history import history_len
Expand All @@ -56,7 +56,6 @@
from NeoVintageous.nv.vi.utils import next_non_white_space_char
from NeoVintageous.nv.vi.utils import regions_transformer
from NeoVintageous.nv.vi.utils import translate_char
from NeoVintageous.nv.vim import console_message
from NeoVintageous.nv.vim import get_logger
from NeoVintageous.nv.vim import INSERT
from NeoVintageous.nv.vim import INTERNAL_NORMAL
Expand All @@ -74,7 +73,7 @@
__all__ = [
'_nv_cmdline',
'_nv_cmdline_feed_key',
'_nv_ex_text_cmd',
'_nv_ex_cmd_edit_wrap',
'_nv_feed_key',
'_nv_fix_st_eol_caret',
'_nv_fs_completion',
Expand Down Expand Up @@ -241,7 +240,6 @@ def run(self):

match = re.match('^\'[a-z_]+\'|\\|[^\\s\\|]+\\|$', subject)
if match:
# TODO [refactor] Use ex_help command directly
do_ex_command(self.window, 'help', {'subject': subject.strip('|')})
else:
return message('E149: Sorry, no help for %s' % subject)
Expand Down Expand Up @@ -344,7 +342,7 @@ def run(self, key, repeat_count=None, do_eval=True, check_user_mappings=True):
_log.debug('found user mapping...')

if do_eval:
_log.debug('evaluating user mapping...')
_log.debug('evaluating user mapping (mode=%s)...', state.mode)

new_keys = command.mapping
if state.mode == OPERATOR_PENDING:
Expand All @@ -357,41 +355,13 @@ def run(self, key, repeat_count=None, do_eval=True, check_user_mappings=True):
state.motion_count = mcount
state.action_count = acount

_log.info('found user mapping %s -> %s', key, new_keys)

# Support for basic Command-line mode mappings:
#
# `:Command<CR>` maps to Sublime Text command (starts with uppercase letter).
# `:command<CR>` maps to Command-line mode command.
_log.info('user mapping %s -> %s', command.sequence, new_keys)

if ':' in new_keys:
match = re.match('^\\:(?P<cmdline>[a-zA-Z][a-zA-Z_]*)\\<CR\\>', new_keys)
if match:
cmdline = match.group('cmdline')
if cmdline[0].isupper():
# run regular sublime text command
def _coerce_to_snakecase(string):
string = re.sub(r"([A-Z]+)([A-Z][a-z])", r'\1_\2', string)
string = re.sub(r"([a-z\d])([A-Z])", r'\1_\2', string)
string = string.replace("-", "_")

return string.lower()

command = _coerce_to_snakecase(cmdline)
command_args = {}
else:
# TODO skip _nv_cmdline and call do_ex_command directly
command = '_nv_cmdline'
command_args = {'cmdline': ':' + cmdline}

_log.info('run command -> %s %s', command, command_args)

return self.window.run_command(command, command_args)

if ':' == new_keys:
return self.window.run_command('_nv_cmdline')

return console_message('invalid command line mapping %s -> %s (only `:[a-zA-Z][a-zA-Z_]*<CR>` is supported)' % (command.head, command.mapping)) # noqa: E501
return do_ex_user_cmdline(self.window, new_keys)

self.window.run_command('_nv_process_notation', {'keys': new_keys, 'check_user_mappings': False})

Expand Down Expand Up @@ -477,20 +447,20 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

def run(self, keys, repeat_count=None, check_user_mappings=True):
# type: (str, int, bool) -> None
# Args:
# keys (str): Key sequence to be run.
# repeat_count (int): Count to be applied when repeating through the
# '.' command.
# check_user_mappings (bool): Whether user mappings should be
# consulted to expand key sequences.
state = self.state
_log.debug('process notation keys %s for mode %s', keys, state.mode)
initial_mode = state.mode
# Disable interactive prompts. For example, to supress interactive
# input collection in /foo<CR>.
state.non_interactive = True

_log.debug('process notation keys %s for initial mode %s', keys, initial_mode)

# First, run any motions coming before the first action. We don't keep
# these in the undo stack, but they will still be repeated via '.'.
# This ensures that undoing will leave the caret where the first
Expand Down Expand Up @@ -627,14 +597,15 @@ def run(self, edit, with_what):
self.view.replace(edit, Region(pt, self.view.line(pt).b), with_what)


class _nv_ex_text_cmd(TextCommand):
class _nv_ex_cmd_edit_wrap(TextCommand):

# This command is required for ex commands that need an Sublime Text edit
# object, which can only be accessed via text commands. Some ex commands
# don't need access to an edit object, so they can skip this command.
# This command is required to wrap ex commands that need a Sublime Text edit
# token. Edit tokens can only be obtained from a TextCommand. Some ex
# commands don't need an edit token, those commands don't need to be wrapped
# by a text command.

def run(self, edit, *args, **kwargs):
do_ex_text_command(self, edit, *args, **kwargs)
def run(self, edit, **kwargs):
do_ex_cmd_edit_wrap(self, edit, **kwargs)


class _nv_cmdline(WindowCommand):
Expand Down Expand Up @@ -716,26 +687,7 @@ def on_done(self, cmdline):

_nv_cmdline_feed_key.reset_last_history_index()

try:
parsed = parse_command_line(cmdline[1:])
if not parsed.command:

# Default ex command. See :h [range].

view = self.window.active_view()
if not view:
return

if not parsed.line_range:
return

do_ex_command_default(window=self.window, view=self.window.active_view(), line_range=parsed.line_range)
else:
do_ex_command(self.window, parsed.command.target, {'command_line': cmdline[1:]})

except Exception as e:
message('{} ({})'.format(str(e), cmdline))
_log.exception('{}'.format(cmdline))
do_ex_cmdline(self.window, cmdline)

def _force_cancel(self):
self.on_cancel()
Expand Down
4 changes: 2 additions & 2 deletions nv/cmds_vi_actions.py
Expand Up @@ -456,7 +456,7 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

def run(self, edit, mode=None, from_init=False):
_log.debug('enter normal mode (%s)', mode)
_log.debug('enter normal mode (mode=%s)', mode)
state = self.state

self.view.window().run_command('hide_auto_complete')
Expand Down Expand Up @@ -529,7 +529,7 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

def run(self, edit, mode=None):
_log.debug('enter normal mode (impl) (%s)', mode)
_log.debug('enter normal mode (mode=%s) (impl)', mode)

def f(view, s):
if mode == INSERT:
Expand Down
8 changes: 4 additions & 4 deletions nv/ex/nodes.py
Expand Up @@ -120,9 +120,9 @@ class RangeNode(Node):

def __init__(self, start=None, end=None, separator=None):
# Args:
# start (list):
# end (list):
# separator (str):
# start (list[Token]):
# end (list[Token]):
# separator (Token):
self.start = start or []
self.end = end or []
self.separator = separator
Expand Down Expand Up @@ -185,7 +185,7 @@ def __init__(self, line_range, command):
self.command = command

def __str__(self):
return '{} {}'.format(str(self.line_range), str(self.command))
return '{}{}'.format(str(self.line_range), str(self.command) if self.command else '')

def validate(self):
# type: () -> None
Expand Down
10 changes: 10 additions & 0 deletions nv/ex/parser.py
Expand Up @@ -76,6 +76,16 @@ def parse_command_line(source):
return command_line


def parse_command_line_address(address):
# type: (str) -> CommandLineNode

# TODO Refactor parsing address; currently just calls parse_command_line().

_log.debug('parsing address >>>%s<<<', address)

return parse_command_line(address)


def _init_line_range(command_line):
# type: (CommandLineNode) -> None
if command_line.line_range:
Expand Down
30 changes: 17 additions & 13 deletions nv/ex/tokens.py
Expand Up @@ -37,12 +37,14 @@ def __eq__(self, other):
class TokenCommand(Token):

def __init__(self, name, target=None, params=None, forced=False, addressable=False, cooperates_with_global=False): # noqa: E501
# type: (str, str, dict, bool, bool, bool) -> None
#
# Args:
# :name (str):
# :target (str): The name of the ex command to exectute. Defaults to the
# name prefixed with "ex_".
# :name (str): The name of the command.
# :target (str): The name of the ex command to execute. Defaults to
# the name. SHOULD NOT include the prefix "ex_".
# :params (dict): Default is {}.
# :forced (bool): Indicates if the '!' (bang) character was placed
# :forced (bool): Indicates if the bang character ! was placed
# immediatley after the command. The '!' (bang) character after an
# Ex command makes a command behave in a different way. The '!'
# should be placed immediately after the command, without any
Expand All @@ -54,20 +56,22 @@ def __init__(self, name, target=None, params=None, forced=False, addressable=Fal
# :w !name Send the current buffer as standard input to
# command "name".
# :addressable (bool): Indicates if the command accepts ranges.
# :cooperates_with_global (bool): Indicates if the command cooperates with
# the :global command. This is special flag, because ex commands don't
# yet support a global_lines argument. It seems that, in Vim, some ex
# commands work well with :global and others ignore :global ranges.
# However, according to the docs, all ex commands should work with
# :global ranges. At the time of writing, the only command that
# supports the global_lines argument is the "print" command e.g. print
# all lines matching \d+ into new buffer: ":%global/\d+/print".
# :cooperates_with_global (bool): Indicates if the command cooperates
# with the :global command. This is special flag, because ex
# commands don't yet support a global_lines argument. It seems
# that, in Vim, some ex commands work well with :global and others
# ignore :global ranges. However, according to the docs, all ex
# commands should work with :global ranges. At the time of
# writing, the only command that supports the global_lines
# argument is the "print" command e.g. print all lines matching
# \d+ into new buffer: ":%global/\d+/print".

super().__init__(content=name)

self.name = name
self.target = target or 'ex_' + name
self.target = target or name
self.params = params or {}
# TODO Make forced a param and rename it "forceit", because this is sometimes required by ex commands.
self.forced = forced
self.addressable = addressable
self.cooperates_with_global = cooperates_with_global
Expand Down

0 comments on commit 590ede8

Please sign in to comment.