Skip to content

Commit

Permalink
[refactor] Separate completion.State() and completion.Lookup().
Browse files Browse the repository at this point in the history
And a few minor renamings.
  • Loading branch information
Andy Chu committed Jan 21, 2019
1 parent ed55a3e commit 6db6aca
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 79 deletions.
15 changes: 8 additions & 7 deletions bin/oil.py
Expand Up @@ -129,7 +129,7 @@ def DefineCommonFlags(spec):
builtin.AddOptionsToArgSpec(OSH_SPEC)


def _InitDefaultCompletions(ex, complete_builtin, comp_state):
def _InitDefaultCompletions(ex, complete_builtin, comp_lookup):
# register builtins and words
complete_builtin(['-E', '-A', 'command'])
# register path completion
Expand All @@ -140,10 +140,10 @@ def _InitDefaultCompletions(ex, complete_builtin, comp_state):
if 1:
# Something for fun, to show off. Also: test that you don't repeatedly hit
# the file system / network / coprocess.
A1 = completion.WordsAction(['foo.py', 'foo', 'bar.py'])
A2 = completion.WordsAction(['m%d' % i for i in xrange(5)], delay=0.1)
A1 = completion.TestAction(['foo.py', 'foo', 'bar.py'])
A2 = completion.TestAction(['m%d' % i for i in xrange(5)], delay=0.1)
C1 = completion.UserSpec([A1, A2], [], [], lambda candidate: True)
comp_state.RegisterName('slowc', completion.Options([]), C1)
comp_lookup.RegisterName('slowc', completion.Options([]), C1)


def _MaybeWriteHistoryFile(history_filename):
Expand Down Expand Up @@ -325,6 +325,7 @@ def ShellMain(lang, argv0, argv, login_shell):

# TODO: Separate comp_state and comp_lookup.
comp_state = completion.State()
comp_lookup = completion.Lookup()

builtins = { # Lookup
builtin_e.HISTORY: builtin.History(readline),
Expand Down Expand Up @@ -357,7 +358,7 @@ def ShellMain(lang, argv0, argv, login_shell):

spec_builder = builtin_comp.SpecBuilder(ex, parse_ctx, word_ev, splitter)
# Add some builtins that depend on the executor!
complete_builtin = builtin_comp.Complete(spec_builder, comp_state) # used later
complete_builtin = builtin_comp.Complete(spec_builder, comp_lookup) # used later
builtins[builtin_e.COMPLETE] = complete_builtin
builtins[builtin_e.COMPGEN] = builtin_comp.CompGen(spec_builder)

Expand Down Expand Up @@ -433,10 +434,10 @@ def ShellMain(lang, argv0, argv, login_shell):
if readline:
ev = word_eval.CompletionWordEvaluator(mem, exec_opts, exec_deps, arena)
progress_f = ui.StatusLine()
root_comp = completion.RootCompleter(ev, comp_state, mem,
root_comp = completion.RootCompleter(ev, mem, comp_lookup, comp_state,
comp_ctx, progress_f, debug_f)
_InitReadline(readline, history_filename, root_comp, debug_f)
_InitDefaultCompletions(ex, complete_builtin, comp_state)
_InitDefaultCompletions(ex, complete_builtin, comp_lookup)

return main_loop.Interactive(opts, ex, c_parser, arena)

Expand Down
51 changes: 31 additions & 20 deletions core/completion.py
Expand Up @@ -142,16 +142,18 @@ def Set(self, opt_name, b):


class State(object):
"""Global completion state.
It has two separate parts:
1. Stores completion hooks registered by the user.
2. Stores the state of the CURRENT completion.
"""Stores the state of the CURRENT completion."""

def __init__(self):
# For the IN-PROGRESS completion.
self.currently_completing = False
# should be SET to a COPY of the registration options by the completer.
self.current_opts = None


class Lookup(object):
"""Stores completion hooks registered by the user."""

Both of them are needed in the RootCompleter and various builtins, so let's
store them in the same object for convenience.
"""
def __init__(self):
# command name -> UserSpec
# Pseudo-commands __first and __fallback are for -E and -D.
Expand All @@ -164,11 +166,6 @@ def __init__(self):
# searched linearly.
self.patterns = []

# For the IN-PROGRESS completion.
self.currently_completing = False
# should be SET to a COPY of the registration options by the completer.
self.current_opts = None

def __str__(self):
return '<completion.State %s>' % self.lookup

Expand Down Expand Up @@ -259,7 +256,20 @@ def Matches(self, comp):
pass


class WordsAction(CompletionAction):
class TestAction(CompletionAction):
def __init__(self, words, delay=None):
self.words = words
self.delay = delay

def Matches(self, comp):
for w in self.words:
if w.startswith(comp.to_complete):
if self.delay:
time.sleep(self.delay)
yield w


class DynamicWordsAction(CompletionAction):
# NOTE: Have to split the words passed to -W. Using IFS or something else?
def __init__(self, words, delay=None):
self.words = words
Expand Down Expand Up @@ -585,11 +595,12 @@ class RootCompleter(object):
- Complete the OSH language (variables, etc.), or
- Statically evaluate argv and dispatch to a command completer.
"""
def __init__(self, word_ev, comp_state, mem, parse_ctx, progress_f,
debug_f):
def __init__(self, word_ev, mem, comp_lookup, comp_state, parse_ctx,
progress_f, debug_f):
self.word_ev = word_ev # for static evaluation of words
self.comp_state = comp_state # to look up plugins
self.mem = mem # to complete variable names
self.comp_lookup = comp_lookup
self.comp_state = comp_state # to look up plugins

self.parse_ctx = parse_ctx
self.progress_f = progress_f
Expand Down Expand Up @@ -800,9 +811,9 @@ def LastColForWord(w):
raise AssertionError
elif n == 1:
# First
comp_opts, user_spec = self.comp_state.GetFirstSpec()
comp_opts, user_spec = self.comp_lookup.GetFirstSpec()
else:
comp_opts, user_spec = self.comp_state.GetSpecForName(
comp_opts, user_spec = self.comp_lookup.GetSpecForName(
partial_argv[0])

# Update the API for user-defined functions.
Expand Down
38 changes: 18 additions & 20 deletions core/completion_test.py
Expand Up @@ -29,14 +29,14 @@
value_e = runtime_asdl.value_e
log = util.log

A1 = completion.WordsAction(['foo.py', 'foo', 'bar.py'])
A1 = completion.TestAction(['foo.py', 'foo', 'bar.py'])
U1 = completion.UserSpec([A1], [], [], lambda candidate: True)

COMP_OPTS = completion.Options([])

mem = state.Mem('', [], {}, None)

FIRST = completion.WordsAction(['grep', 'sed', 'test'])
FIRST = completion.TestAction(['grep', 'sed', 'test'])
U2 = completion.UserSpec([FIRST], [], [], lambda candidate: True)


Expand All @@ -52,8 +52,10 @@ def MockApi(line):
return completion.Api(line=line, begin=i+1, end=end)


def _MakeRootCompleter(comp_state=None):
comp_state = comp_state or completion.State()
def _MakeRootCompleter(comp_lookup=None):
#comp_state = comp_state or completion.State()
comp_state = completion.State()
comp_lookup = comp_lookup or completion.Lookup()
ev = test_lib.MakeTestEvaluator()

pool = alloc.Pool()
Expand All @@ -66,7 +68,7 @@ def _MakeRootCompleter(comp_state=None):
else:
debug_f = util.NullDebugFile()
progress_f = ui.TestStatusLine()
return completion.RootCompleter(ev, comp_state, mem, parse_ctx,
return completion.RootCompleter(ev, mem, comp_lookup, comp_state, parse_ctx,
progress_f, debug_f)


Expand Down Expand Up @@ -100,7 +102,7 @@ def _MakeComp(self, words, index, to_complete):
return comp

def testLookup(self):
c = completion.State()
c = completion.Lookup()
c.RegisterName('grep', COMP_OPTS, U1)
print(c.GetSpecForName('grep'))
print(c.GetSpecForName('/usr/bin/grep'))
Expand All @@ -114,10 +116,6 @@ def testLookup(self):
comp_rb = c.GetSpecForName('foo.rb')
print('rb', comp_rb)

def testWordsAction(self):
comp = self._MakeComp(['f'], 0, 'f')
print(list(A1.Matches(comp)))

def testExternalCommandAction(self):
mem = state.Mem('dummy', [], {}, None)
a = completion.ExternalCommandAction(mem)
Expand Down Expand Up @@ -349,11 +347,11 @@ def testCompletesRedirectArguments(self):
self.assertEqual(0, len(m))

def testCompletesWords(self):
comp_state = completion.State()
comp_lookup = completion.Lookup()

comp_state.RegisterName('grep', COMP_OPTS, U1)
comp_state.RegisterName('__first', COMP_OPTS, U2)
r = _MakeRootCompleter(comp_state=comp_state)
comp_lookup.RegisterName('grep', COMP_OPTS, U1)
comp_lookup.RegisterName('__first', COMP_OPTS, U2)
r = _MakeRootCompleter(comp_lookup=comp_lookup)

comp = MockApi('grep f')
m = list(r.Matches(comp))
Expand Down Expand Up @@ -384,12 +382,12 @@ def testCompletesWords(self):

def testRunsUserDefinedFunctions(self):
# This is here because it's hard to test readline with the spec tests.
comp_state = completion.State()
comp_lookup = completion.Lookup()
with open('testdata/completion/osh-unit.bash') as f:
code_str = f.read()
ex = test_lib.EvalCode(code_str, comp_state=comp_state)
ex = test_lib.EvalCode(code_str, comp_lookup=comp_lookup)

r = _MakeRootCompleter(comp_state=comp_state)
r = _MakeRootCompleter(comp_lookup=comp_lookup)

# By default, we get a space on the end.
m = list(r.Matches(MockApi('mywords t')))
Expand Down Expand Up @@ -624,13 +622,13 @@ def testMatchesOracle(self):
state.SetGlobalString(mem, 'ORACLE_cword', oracle_cword)
state.SetGlobalString(mem, 'ORACLE_split', oracle_split)

comp_state = completion.State()
ex = test_lib.EvalCode(init_code, comp_state=comp_state, arena=arena,
comp_lookup = completion.Lookup()
ex = test_lib.EvalCode(init_code, comp_lookup=comp_lookup, arena=arena,
mem=mem)

#print(ex.comp_state)

r = _MakeRootCompleter(comp_state=comp_state)
r = _MakeRootCompleter(comp_lookup=comp_lookup)
#print(r)
comp = MockApi(code_str[:-1])
m = list(r.Matches(comp))
Expand Down
14 changes: 8 additions & 6 deletions core/test_lib.py
Expand Up @@ -119,14 +119,16 @@ def MakeTestEvaluator():
return ev


def InitExecutor(comp_state=None, arena=None, mem=None):
def InitExecutor(comp_lookup=None, arena=None, mem=None):
arena = arena or MakeArena('<InitExecutor>')

mem = mem or state.Mem('', [], {}, arena)
fd_state = process.FdState()
funcs = {}

comp_state = comp_state or completion.State()
comp_state = completion.State()
comp_lookup = comp_lookup or completion.Lookup()

readline = None # simulate not having it
builtins = { # Lookup
builtin_e.HISTORY: builtin.History(readline),
Expand Down Expand Up @@ -167,24 +169,24 @@ def InitExecutor(comp_state=None, arena=None, mem=None):

spec_builder = builtin_comp.SpecBuilder(ex, parse_ctx, word_ev, splitter)
# Add some builtins that depend on the executor!
complete_builtin = builtin_comp.Complete(spec_builder, comp_state) # used later
complete_builtin = builtin_comp.Complete(spec_builder, comp_lookup) # used later
builtins[builtin_e.COMPLETE] = complete_builtin
builtins[builtin_e.COMPGEN] = builtin_comp.CompGen(spec_builder)

return ex


def EvalCode(code_str, comp_state=None, arena=None, mem=None):
def EvalCode(code_str, comp_lookup=None, arena=None, mem=None):
"""
This allows unit tests to write code strings and have functions appear in the
executor.
"""
comp_state = comp_state or completion.State()
comp_lookup = comp_lookup or completion.Lookup()
arena = arena or MakeArena('<test_lib>')
mem = mem or state.Mem('', [], {}, arena)

c_parser = InitCommandParser(code_str, arena=arena)
ex = InitExecutor(comp_state=comp_state, arena=arena, mem=mem)
ex = InitExecutor(comp_lookup=comp_lookup, arena=arena, mem=mem)
# Parse and execute!
main_loop.Batch(ex, c_parser, arena)
return ex
Expand Down
5 changes: 3 additions & 2 deletions core/ui.py
Expand Up @@ -227,7 +227,7 @@ def _ReplaceBackslashCodes(self, tokens):

return ''.join(ret)

def EvalPromptEvaluator(self, val):
def EvalPrompt(self, val):
"""Perform the two evaluations that bash does. Used by $PS1 and ${x@P}."""
if val.tag != value_e.Str:
return self.default_prompt # no evaluation necessary
Expand Down Expand Up @@ -257,13 +257,14 @@ def EvalPromptEvaluator(self, val):
self.parse_cache[ps1_str] = ps1_word

# Evaluate, e.g. "${debian_chroot}\u" -> '\u'
# TODO: Handle runtime errors like unset variables, etc.
val2 = self.ex.word_ev.EvalWordToString(ps1_word)
return val2.s

def FirstPromptEvaluator(self):
if self.lang == 'osh':
val = self.mem.GetVar('PS1')
return self.EvalPromptEvaluator(val)
return self.EvalPrompt(val)
else:
# TODO: If the lang is Oil, we should use a better prompt language than
# $PS1!!!
Expand Down
4 changes: 2 additions & 2 deletions core/ui_test.py
Expand Up @@ -26,8 +26,8 @@ def testPromptEvaluator(self):
p = ui.PromptEvaluator('osh', arena, ex.parse_ctx, ex, mem)

# Rgression for caching bug!
self.assertEqual('foo', p.EvalPromptEvaluator(value.Str('foo')))
self.assertEqual('foo', p.EvalPromptEvaluator(value.Str('foo')))
self.assertEqual('foo', p.EvalPrompt(value.Str('foo')))
self.assertEqual('foo', p.EvalPrompt(value.Str('foo')))


if __name__ == '__main__':
Expand Down

0 comments on commit 6db6aca

Please sign in to comment.