From 83fb2aef72f06bb12e967c446d1629b7b688c66a Mon Sep 17 00:00:00 2001 From: Willie Xu Date: Tue, 13 Mar 2018 12:28:58 -0700 Subject: [PATCH] Better Completer responsiveness in interactive after command table loaded (#5794) * improved completer responsiveness upon command table load * history and version bump * mock create_interface() * delay create_interface and remove refresh_cli flag * close file descriptor --- azure-cli.pyproj | 98 ++++++++++++------- .../azure-cli-interactive/HISTORY.rst | 4 + .../interactive/azclishell/app.py | 23 ++--- .../interactive/azclishell/az_completer.py | 66 +++++++++---- .../interactive/azclishell/threads.py | 4 +- .../tests/latest/test_completion.py | 6 +- .../interactive/tests/latest/test_feedback.py | 6 +- .../azure-cli-interactive/setup.py | 2 +- 8 files changed, 134 insertions(+), 75 deletions(-) diff --git a/azure-cli.pyproj b/azure-cli.pyproj index 381f8b5920d..5728bfe336e 100644 --- a/azure-cli.pyproj +++ b/azure-cli.pyproj @@ -12,11 +12,10 @@ . {888888a0-9f3d-457c-b088-3a5042f75d52} Standard Python launcher - {0739f18f-8f0c-4ff7-b592-f6a44eacf0ba} - 3.5 + {78c1ee79-34bd-41e9-beb0-9ecf0336c9a2} + 2.7 False - - + interactive @@ -351,30 +350,29 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - @@ -856,11 +854,12 @@ - + + @@ -1088,6 +1087,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1165,18 +1199,16 @@ - - - - - {0739f18f-8f0c-4ff7-b592-f6a44eacf0ba} - {6121b83b-bf4d-4953-9b9d-9360b34efe09} - 3.5 - env (Python 3.6) + + {78c1ee79-34bd-41e9-beb0-9ecf0336c9a2} + {d09116de-bd02-4de4-8a41-0d9752df0ac8} + 2.7 + env36b (env36) Scripts\python.exe - Scripts\pythonw.exe + Scripts\python.exe Lib\ - PYTHONPATH + + Amd64 diff --git a/src/command_modules/azure-cli-interactive/HISTORY.rst b/src/command_modules/azure-cli-interactive/HISTORY.rst index 6bbc695ad92..2db3c16f66a 100644 --- a/src/command_modules/azure-cli-interactive/HISTORY.rst +++ b/src/command_modules/azure-cli-interactive/HISTORY.rst @@ -3,6 +3,10 @@ Release History =============== +0.3.18 +++++++ +* Completions kick in as soon as command table loading is done. + 0.3.17 ++++++ * Persist history across different sessions diff --git a/src/command_modules/azure-cli-interactive/azure/cli/command_modules/interactive/azclishell/app.py b/src/command_modules/azure-cli-interactive/azure/cli/command_modules/interactive/azclishell/app.py index 32a60fd8977..8aee3c25985 100644 --- a/src/command_modules/azure-cli-interactive/azure/cli/command_modules/interactive/azclishell/app.py +++ b/src/command_modules/azure-cli-interactive/azure/cli/command_modules/interactive/azclishell/app.py @@ -76,11 +76,6 @@ def space_toolbar(settings_items, empty_space): return settings, empty_space -def restart_completer(shell_ctx): - shell_ctx.completer = AzCompleter(shell_ctx, GatherCommands(shell_ctx.config)) - shell_ctx.refresh_cli = True - - # pylint: disable=too-many-instance-attributes class AzInteractiveShell(object): @@ -98,21 +93,18 @@ def __init__(self, cli_ctx, style=None, completer=None, self.lexer = lexer or get_az_lexer(self.config) if self.styles else None try: self.completer = completer or AzCompleter(self, GatherCommands(self.config)) - from .az_completer import initialize_command_table_attributes - initialize_command_table_attributes(self.completer) + self.completer.initialize_command_table_attributes() except IOError: # if there is no cache - self.completer = None + self.completer = AzCompleter(self, None) self.history = history or FileHistory(os.path.join(self.config.config_dir, self.config.get_history())) os.environ[ENV_ADDITIONAL_USER_AGENT] = 'AZURECLISHELL/' + __version__ # OH WHAT FUN TO FIGURE OUT WHAT THESE ARE! self._cli = None - self.refresh_cli = False self.layout = None self.description_docs = u'' self.param_docs = u'' self.example_docs = u'' - self._env = os.environ self.last = None self.last_exit = 0 self.user_feedback = user_feedback @@ -155,9 +147,8 @@ def __call__(self): @property def cli(self): """ Makes the interface or refreshes it """ - if self._cli is None or self.refresh_cli: + if self._cli is None: self._cli = self.create_interface() - self.refresh_cli = False return self._cli def handle_cd(self, cmd): @@ -201,6 +192,12 @@ def on_input_timeout(self, cli): self._update_toolbar() cli.request_redraw() + def restart_completer(self): + if not self.completer: + self.completer.start(self, GatherCommands(self.config)) + self.completer.initialize_command_table_attributes() + self._cli = self.create_interface() + def _space_examples(self, list_examples, rows, section_value): """ makes the example text """ examples_with_index = [] @@ -686,7 +683,7 @@ def run(self): self.cli_ctx.get_progress_controller().init_progress(ShellProgressView()) self.cli_ctx.get_progress_controller = self.progress_patch - self.command_table_thread = LoadCommandTableThread(restart_completer, self) + self.command_table_thread = LoadCommandTableThread(self.restart_completer, self) self.command_table_thread.start() from .configuration import SHELL_HELP diff --git a/src/command_modules/azure-cli-interactive/azure/cli/command_modules/interactive/azclishell/az_completer.py b/src/command_modules/azure-cli-interactive/azure/cli/command_modules/interactive/azclishell/az_completer.py index 03182a16288..1c751f4dc46 100644 --- a/src/command_modules/azure-cli-interactive/azure/cli/command_modules/interactive/azclishell/az_completer.py +++ b/src/command_modules/azure-cli-interactive/azure/cli/command_modules/interactive/azclishell/az_completer.py @@ -15,14 +15,6 @@ SELECT_SYMBOL = configuration.SELECT_SYMBOL -def initialize_command_table_attributes(completer): - from ._dump_commands import FreshTable - completer.cmdtab = FreshTable(completer.shell_ctx).command_table - if completer.cmdtab: - completer.parser.load_command_table(completer.cmdtab) - completer.argsfinder = ArgsFinder(completer.parser) - - def error_pass(_, message): # pylint: disable=unused-argument return @@ -78,32 +70,33 @@ class AzCompleter(Completer): """ Completes Azure CLI commands """ def __init__(self, shell_ctx, commands, global_params=True): - self.shell_ctx = shell_ctx + self.shell_ctx = None + # dictionary of command to descriptions - self.command_description = commands.descrip + self.command_description = None # from a command to a list of parameters - self.command_parameters = commands.command_param + self.command_parameters = None # a list of all the possible parameters - self.completable_param = commands.completable_param + self.completable_param = None # the command tree - self.command_tree = commands.command_tree + self.command_tree = None # a dictionary of parameter (which is command + " " + parameter name) # to a description of what it does - self.param_description = commands.param_descript + self.param_description = None # a dictionary of command to examples of how to use it - self.command_examples = commands.command_example + self.command_examples = None # a dictionary of commands with parameters with multiple names (e.g. {'vm create':{-n: --name}}) - self.same_param_doubles = commands.same_param_doubles or {} + self.same_param_doubles = {} self._is_command = True - self.branch = self.command_tree + self.branch = None self.curr_command = "" - self.global_param = commands.global_param if global_params else [] - self.output_choices = commands.output_choices if global_params else [] - self.output_options = commands.output_options if global_params else [] - self.global_param_descriptions = commands.global_param_descriptions if global_params else [] + self.global_param = [] + self.output_choices = [] + self.output_options = [] + self.global_param_descriptions = [] self.global_parser = AzCliCommandParser(add_help=False) self.global_parser.add_argument_group('global', 'Global Arguments') @@ -111,6 +104,37 @@ def __init__(self, shell_ctx, commands, global_params=True): self.argsfinder = ArgsFinder(self.parser) self.cmdtab = {} + if commands: + self.start(shell_ctx, commands, global_params=global_params) + + def __bool__(self): + return bool(self.shell_ctx) + + def start(self, shell_ctx, commands, global_params=True): + self.shell_ctx = shell_ctx + self.command_description = commands.descrip + self.command_parameters = commands.command_param + self.completable_param = commands.completable_param + self.command_tree = commands.command_tree + self.param_description = commands.param_descript + self.command_examples = commands.command_example + self.same_param_doubles = commands.same_param_doubles or self.same_param_doubles + + self.branch = self.command_tree + + if global_params: + self.global_param = commands.global_param + self.output_choices = commands.output_choices + self.output_options = commands.output_options + self.global_param_descriptions = commands.global_param_descriptions + + def initialize_command_table_attributes(self): + from ._dump_commands import FreshTable + self.cmdtab = FreshTable(self.shell_ctx).command_table + if self.cmdtab: + self.parser.load_command_table(self.cmdtab) + self.argsfinder = ArgsFinder(self.parser) + def validate_completion(self, param, words, text_before_cursor, check_double=True): """ validates that a param should be completed """ # validates the position of the parameter diff --git a/src/command_modules/azure-cli-interactive/azure/cli/command_modules/interactive/azclishell/threads.py b/src/command_modules/azure-cli-interactive/azure/cli/command_modules/interactive/azclishell/threads.py index 20727e4c0f8..4b736e9a7d4 100644 --- a/src/command_modules/azure-cli-interactive/azure/cli/command_modules/interactive/azclishell/threads.py +++ b/src/command_modules/azure-cli-interactive/azure/cli/command_modules/interactive/azclishell/threads.py @@ -16,11 +16,9 @@ def __init__(self, target, shell): def run(self): from ._dump_commands import FreshTable - from .az_completer import initialize_command_table_attributes try: FreshTable(self.shell).dump_command_table(self.shell) except KeyboardInterrupt: pass - self.initialize_function(self.shell) - initialize_command_table_attributes(self.shell.completer) + self.initialize_function() diff --git a/src/command_modules/azure-cli-interactive/azure/cli/command_modules/interactive/tests/latest/test_completion.py b/src/command_modules/azure-cli-interactive/azure/cli/command_modules/interactive/tests/latest/test_completion.py index e9aa1944c59..dacd93baace 100644 --- a/src/command_modules/azure-cli-interactive/azure/cli/command_modules/interactive/tests/latest/test_completion.py +++ b/src/command_modules/azure-cli-interactive/azure/cli/command_modules/interactive/tests/latest/test_completion.py @@ -5,6 +5,7 @@ import unittest import six +import mock from azure.cli.command_modules.interactive.azclishell import command_tree as tree from azure.cli.command_modules.interactive.azclishell.az_completer import AzCompleter @@ -15,8 +16,9 @@ def _build_completer(commands, global_params): from azure.cli.testsdk import TestCli from azure.cli.command_modules.interactive.azclishell.app import AzInteractiveShell - shell_ctx = AzInteractiveShell(TestCli(), None) - return AzCompleter(shell_ctx, commands, global_params) + with mock.patch.object(AzInteractiveShell, 'create_interface', lambda _: None): + shell_ctx = AzInteractiveShell(TestCli(), None) + return AzCompleter(shell_ctx, commands, global_params) class _Commands(): diff --git a/src/command_modules/azure-cli-interactive/azure/cli/command_modules/interactive/tests/latest/test_feedback.py b/src/command_modules/azure-cli-interactive/azure/cli/command_modules/interactive/tests/latest/test_feedback.py index 145a7eb3fcf..0946c4b1d3c 100644 --- a/src/command_modules/azure-cli-interactive/azure/cli/command_modules/interactive/tests/latest/test_feedback.py +++ b/src/command_modules/azure-cli-interactive/azure/cli/command_modules/interactive/tests/latest/test_feedback.py @@ -56,7 +56,7 @@ def test_update_freq(self): fh.update_frequency = self.norm_update now = fh.day_format(datetime.datetime.now()) - _, freq_path = tempfile.mkstemp() + fd, freq_path = tempfile.mkstemp() freq_dir, freq_file = freq_path.rsplit(os.path.sep, 1) def _get_freq(): @@ -72,13 +72,14 @@ def _get_freq(): self.assertEqual(json_freq, {now: 2}) if os.path.exists(freq_path): + os.close(fd) os.remove(freq_path) def test_update_freq_no_file(self): # tests updating the files for frequency with no file written fh.update_frequency = self.norm_update - _, freq_path = tempfile.mkstemp() + fd, freq_path = tempfile.mkstemp() freq_dir, freq_file = freq_path.rsplit(os.path.sep, 1) def _get_freq(): @@ -88,6 +89,7 @@ def _get_freq(): self.shell_ctx.config.get_frequency = _get_freq if os.path.exists(freq_path): + os.close(fd) os.remove(freq_path) # without a file already written diff --git a/src/command_modules/azure-cli-interactive/setup.py b/src/command_modules/azure-cli-interactive/setup.py index c0428a41920..a24178464c4 100644 --- a/src/command_modules/azure-cli-interactive/setup.py +++ b/src/command_modules/azure-cli-interactive/setup.py @@ -14,7 +14,7 @@ cmdclass = {} # Version is also defined in azclishell.__init__.py. -VERSION = "0.3.17" +VERSION = "0.3.18" # The full list of classifiers is available at # https://pypi.python.org/pypi?%3Aaction=list_classifiers CLASSIFIERS = [