From a02699f0a69698c98b17c211b1777928035fb2b7 Mon Sep 17 00:00:00 2001 From: chronologos Date: Wed, 3 Aug 2016 20:46:45 -0700 Subject: [PATCH 1/3] cache arguments and decrease frequency of _complete_from_full_parse --- awsshell/autocomplete.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/awsshell/autocomplete.py b/awsshell/autocomplete.py index a9aee4f..360b8c3 100644 --- a/awsshell/autocomplete.py +++ b/awsshell/autocomplete.py @@ -24,6 +24,7 @@ def __init__(self, index_data, match_fuzzy=True): # This will get populated as a command is completed. self.cmd_path = [self._current_name] self.match_fuzzy = match_fuzzy + self._cache_all_args = [] @property def global_arg_metadata(self): @@ -42,6 +43,7 @@ def reset(self): self._last_position = 0 self.last_option = '' self.cmd_path = [self._current_name] + self._cache_all_args = [] def autocomplete(self, line): """Given a line, return a list of suggestions.""" @@ -57,7 +59,9 @@ def autocomplete(self, line): return self._handle_backspace() elif not line: return [] - elif current_length != self._last_position + 1: + elif self._last_position == 0 and current_length > 2: + return self._complete_from_full_parse() + elif current_length != self._last_position + 1 and '--' in line: return self._complete_from_full_parse() # This position is important. We only update the _last_position @@ -75,6 +79,7 @@ def autocomplete(self, line): # this as self.last_arg self.last_option = last_word if line[-1] == ' ': + self._cache_all_args = [] # At this point the user has autocompleted a command # or an argument and has hit space. If they've # just completed a command, we need to change the @@ -101,14 +106,12 @@ def autocomplete(self, line): # in either of the above two cases. return self._current['commands'][:] elif last_word.startswith('-'): - # TODO: cache this for the duration of the current context. - # We don't need to recompute this until the args are - # different. - all_args = self._get_all_args() + if not self._cache_all_args: + self._cache_all_args = self._get_all_args() if self.match_fuzzy: - return fuzzy_search(last_word, all_args) + return fuzzy_search(last_word, self._cache_all_args) else: - return substring_search(last_word, all_args) + return substring_search(last_word, self._cache_all_args) if self.match_fuzzy: return fuzzy_search(last_word, self._current['commands']) else: From 487c5c9627cdcac601c9c30c280945cb49225d67 Mon Sep 17 00:00:00 2001 From: chronologos Date: Wed, 3 Aug 2016 22:24:41 -0700 Subject: [PATCH 2/3] added ability to add contexts/state to commands --- awsshell/app.py | 27 +++++++++++++++++++++++++-- awsshell/autocomplete.py | 10 ++++++++++ tests/unit/test_autocomplete.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/awsshell/app.py b/awsshell/app.py index 40b93c3..496df08 100644 --- a/awsshell/app.py +++ b/awsshell/app.py @@ -234,6 +234,7 @@ def __init__(self, completer, model_completer, docs, self._dot_cmd = DotCommandHandler() self._env = os.environ.copy() self._profile = None + self.prompt_tokens = u'aws> ' self._input = input self._output = output @@ -282,7 +283,10 @@ def run(self): while True: try: document = self.cli.run(reset_current_buffer=True) - text = document.text + if self.model_completer.context and isinstance(self.model_completer.context, list): + text = " ".join(self.model_completer.context) + " " + document.text + else: + text = document.text except InputInterrupt: pass except (KeyboardInterrupt, EOFError): @@ -300,6 +304,25 @@ def run(self): if text.startswith('!'): # Then run the rest as a normally shell command. full_cmd = text[1:] + elif text.startswith('@') and len(text.split()) == 1: + # Add word as context to completions + self.model_completer.context.append(text.split()[0].strip('@')) + self.model_completer.reset() + self.prompt_tokens = u'aws ' + ' '.join(self.model_completer.context) + u' > ' + self.refresh_cli = True + self.cli.request_redraw() + continue + elif 'exit' in text.split(): + # Remove most recently added context + if self.model_completer.context: + self.model_completer.context.pop() + if self.model_completer.context: + self.prompt_tokens = u'aws ' + ' '.join(self.model_completer.context) + u' > ' + else: + self.prompt_tokens = u'aws > ' + self.refresh_cli = True + self.cli.request_redraw() + continue else: full_cmd = 'aws ' + text self.history.append(full_cmd) @@ -331,7 +354,7 @@ def create_layout(self, display_completions_in_columns, toolbar): if self.config_section['theme'] == 'none': lexer = None return create_default_layout( - self, u'aws> ', lexer=lexer, reserve_space_for_menu=True, + self, self.prompt_tokens, lexer=lexer, reserve_space_for_menu=True, display_completions_in_columns=display_completions_in_columns, get_bottom_toolbar_tokens=toolbar.handler) diff --git a/awsshell/autocomplete.py b/awsshell/autocomplete.py index 360b8c3..fff13f0 100644 --- a/awsshell/autocomplete.py +++ b/awsshell/autocomplete.py @@ -24,6 +24,7 @@ def __init__(self, index_data, match_fuzzy=True): # This will get populated as a command is completed. self.cmd_path = [self._current_name] self.match_fuzzy = match_fuzzy + self.context = [] self._cache_all_args = [] @property @@ -43,6 +44,15 @@ def reset(self): self._last_position = 0 self.last_option = '' self.cmd_path = [self._current_name] + for context in self.context: + next_command = self._current['children'].get(context) + if not next_command: + self.context.remove(context) + self.reset() + return + self._current = next_command + self._current_name = context + self.cmd_path.append(self._current_name) self._cache_all_args = [] def autocomplete(self, line): diff --git a/tests/unit/test_autocomplete.py b/tests/unit/test_autocomplete.py index 0bb2f46..f103e89 100644 --- a/tests/unit/test_autocomplete.py +++ b/tests/unit/test_autocomplete.py @@ -380,3 +380,31 @@ def test_global_arg_metadata_property(index_data): } completer = AWSCLIModelCompleter(index_data) assert '--global1' in completer.global_arg_metadata + +def test_add_context_changes_context(index_data): + index_data['aws']['commands'] = ['ec2'] + index_data['aws']['children'] = { + 'ec2': { + 'commands': ['create-tags'], + 'argument_metadata': {}, + 'arguments': [], + 'children': { + 'create-tags': { + 'commands': [], + 'argument_metadata': { + '--resources': {'example': '', 'minidoc': 'foo'}, + }, + 'arguments': ['--resources'], + 'children': {}, + } + } + } + } + completer = AWSCLIModelCompleter(index_data) + completer.reset() + completer.autocomplete('c') + assert completer.autocomplete('c') == ['ec2'] + completer.context = ['ec2'] + completer.reset() + completer.autocomplete('c') + assert completer.autocomplete('c') == ['create-tags'] From eed78f8bc43b00e68a0be76709214f28ea546c31 Mon Sep 17 00:00:00 2001 From: chronologos Date: Wed, 3 Aug 2016 22:31:58 -0700 Subject: [PATCH 3/3] bugfixes --- awsshell/app.py | 52 ++++++++++++++++++++++++---------------- awsshell/autocomplete.py | 7 +++--- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/awsshell/app.py b/awsshell/app.py index 496df08..a9b5598 100644 --- a/awsshell/app.py +++ b/awsshell/app.py @@ -234,9 +234,9 @@ def __init__(self, completer, model_completer, docs, self._dot_cmd = DotCommandHandler() self._env = os.environ.copy() self._profile = None - self.prompt_tokens = u'aws> ' self._input = input self._output = output + self.prompt_tokens = u'aws > ' # These attrs come from the config file. self.config_obj = None @@ -272,6 +272,22 @@ def save_config(self): self.config_section['theme'] = self.theme self.config_obj.write() + def add_context(self, text): + self.model_completer.context.append( + text.split()[0].strip('@')) + self.model_completer.reset() + self.prompt_tokens = u'aws ' + ' '.join( + self.model_completer.context) + u' > ' + self.refresh_cli = True + self.cli.request_redraw() + + def remove_context(self): + self.model_completer.context.pop() + self.prompt_tokens = u'aws ' + ' '.join( + self.model_completer.context) + u' > ' + self.refresh_cli = True + self.cli.request_redraw() + @property def cli(self): if self._cli is None or self.refresh_cli: @@ -283,17 +299,21 @@ def run(self): while True: try: document = self.cli.run(reset_current_buffer=True) - if self.model_completer.context and isinstance(self.model_completer.context, list): - text = " ".join(self.model_completer.context) + " " + document.text + if self.model_completer.context and isinstance( + self.model_completer.context, list): + text = " ".join(self.model_completer.context) + \ + " " + document.text + original_text = document.text else: text = document.text + original_text = text except InputInterrupt: pass except (KeyboardInterrupt, EOFError): self.save_config() break else: - if text.startswith('.'): + if original_text.startswith('.'): # These are special commands (dot commands) that are # interpreted by the aws-shell directly and typically used # to modify some type of behavior in the aws-shell. @@ -301,27 +321,17 @@ def run(self): if result is EXIT_REQUESTED: break else: - if text.startswith('!'): - # Then run the rest as a normally shell command. + if original_text.startswith('!'): + # Then run the rest as a normal shell command. full_cmd = text[1:] - elif text.startswith('@') and len(text.split()) == 1: + elif original_text.startswith('@'): # Add word as context to completions - self.model_completer.context.append(text.split()[0].strip('@')) - self.model_completer.reset() - self.prompt_tokens = u'aws ' + ' '.join(self.model_completer.context) + u' > ' - self.refresh_cli = True - self.cli.request_redraw() + self.add_context(text) continue - elif 'exit' in text.split(): + elif original_text == 'exit' and\ + self.model_completer.context: # Remove most recently added context - if self.model_completer.context: - self.model_completer.context.pop() - if self.model_completer.context: - self.prompt_tokens = u'aws ' + ' '.join(self.model_completer.context) + u' > ' - else: - self.prompt_tokens = u'aws > ' - self.refresh_cli = True - self.cli.request_redraw() + self.remove_context() continue else: full_cmd = 'aws ' + text diff --git a/awsshell/autocomplete.py b/awsshell/autocomplete.py index fff13f0..b0a015f 100644 --- a/awsshell/autocomplete.py +++ b/awsshell/autocomplete.py @@ -69,9 +69,10 @@ def autocomplete(self, line): return self._handle_backspace() elif not line: return [] - elif self._last_position == 0 and current_length > 2: - return self._complete_from_full_parse() - elif current_length != self._last_position + 1 and '--' in line: + elif (self._last_position == 0 and + current_length > 2) or ( + current_length != self._last_position + 1 + and '--' in line): return self._complete_from_full_parse() # This position is important. We only update the _last_position