diff --git a/CHANGES.txt b/CHANGES.txt index e07ccf95..58761474 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -5,6 +5,9 @@ Changes for crash Unreleased ========== + - Automatically capitalize keywords while typing, + e.g. ``select`` -> ``SELECT`` + - Automatically suggest and complete to ``UPPERCASE`` keywords when using autocomplete. diff --git a/docs/cli.txt b/docs/cli.txt index b1007768..3725756a 100644 --- a/docs/cli.txt +++ b/docs/cli.txt @@ -40,6 +40,9 @@ The ``crash`` binary supports several command line arguments. | | overlay for suggestions. | | | Disabling autocomplete removes this limitation. | +---------------------------------------+--------------------------------------------------+ +| ``-C`` , ``--no-autocapitalize`` | Disable automatic capitalization of SQL keywords | +| | while typing. | ++---------------------------------------+--------------------------------------------------+ Example Usage ============= @@ -98,6 +101,8 @@ You can get a list of commands by typing ``\?``: +----------------------+------------------------------------------------------------------------------------+ | ``\autocomplete`` | Turn autocomplete feature on or off. Works as a toggle. | +----------------------+------------------------------------------------------------------------------------+ +| ``\autocapitalize`` | Turn automatic capitalization for SQL keywords or off. Works as a toggle. | ++----------------------+------------------------------------------------------------------------------------+ =============== Status Messages diff --git a/src/crate/crash/command.py b/src/crate/crash/command.py index 03163382..fc59e4c8 100644 --- a/src/crate/crash/command.py +++ b/src/crate/crash/command.py @@ -134,6 +134,10 @@ def _conf_or_default(key, value): dest='autocomplete', default=_conf_or_default('autocomplete', True), help='use -A to disable SQL autocompletion') + parser.add_argument('-C', '--no-autocapitalize', action='store_false', + dest='autocapitalize', + default=_conf_or_default('autocapitalize', True), + help='use -C to disable automatic capitalization of SQL keywords') parser.add_argument('--history', type=str, help='the history file to use', default=HISTORY_PATH) @@ -181,7 +185,8 @@ def __init__(self, connection=None, error_trace=False, is_tty=True, - autocomplete=True): + autocomplete=True, + autocapitalize=True): self.error_trace = error_trace self.connection = connection or connect(error_trace=error_trace) self.cursor = self.connection.cursor() @@ -201,6 +206,7 @@ def __init__(self, self.commands.update(built_in_commands) self.logger = ColorPrinter(is_tty) self._autocomplete = autocomplete + self._autocapitalize = autocapitalize def get_num_columns(self): return 80 @@ -208,6 +214,9 @@ def get_num_columns(self): def should_autocomplete(self): return self._autocomplete + def should_autocapitalize(self): + return self._autocapitalize + def pprint(self, rows, cols): result = Result(cols, rows, @@ -432,7 +441,8 @@ def main(): error_trace=error_trace, output_writer=output_writer, is_tty=is_tty, - autocomplete=args.autocomplete) + autocomplete=args.autocomplete, + autocapitalize=args.autocapitalize) if error_trace: # log CONNECT command only in verbose mode cmd._connect(crate_hosts) diff --git a/src/crate/crash/commands.py b/src/crate/crash/commands.py index 7451f2dd..51f62700 100644 --- a/src/crate/crash/commands.py +++ b/src/crate/crash/commands.py @@ -93,6 +93,17 @@ def __call__(self, cmd, *args, **kwargs): ) +class ToggleAutoCapitalizeCommand(Command): + """ toggle automatic capitalization of SQL keywords """ + + @noargs_command + def __call__(self, cmd, *args, **kwargs): + cmd._autocapitalize = not cmd._autocapitalize + return 'Auto-capitalization {0}'.format( + cmd._autocapitalize and 'ON' or 'OFF' + ) + + class CheckBaseCommand(Command): check_name = None @@ -198,5 +209,6 @@ def __call__(self, cmd, check_name=None, **kwargs): 'r': ReadFileCommand(), 'format': SwitchFormatCommand(), 'autocomplete': ToggleAutocompleteCommand(), + 'autocapitalize': ToggleAutoCapitalizeCommand(), 'check': CheckCommand(), } diff --git a/src/crate/crash/repl.py b/src/crate/crash/repl.py index b05c45a3..7e5c282e 100644 --- a/src/crate/crash/repl.py +++ b/src/crate/crash/repl.py @@ -170,6 +170,25 @@ def is_multiline(): *args, is_multiline=is_multiline, **kwargs) +class Capitalizer: + + def __init__(self, cmd): + self.cmd = cmd + self.last_changed = None + + def __call__(self, buffer): + if not self.cmd.should_autocapitalize(): + return + last_word = buffer.document.get_word_before_cursor(WORD=True) + if not last_word.isupper() and last_word.lower() in SQLCompleter.keywords: + buffer.delete_before_cursor(len(last_word)) + buffer.insert_text(last_word.upper(), fire_event=False) + self.last_changed = last_word + elif self.last_changed and last_word.startswith(self.last_changed.upper()): + buffer.delete_before_cursor(len(last_word)) + buffer.insert_text(self.last_changed + last_word[-1:], fire_event=False) + + def loop(cmd, history_file): key_binding_manager = KeyBindingManager( enable_search=True, @@ -190,7 +209,8 @@ def loop(cmd, history_file): buffer = CrashBuffer( history=TruncatedFileHistory(history_file, max_length=MAX_HISTORY_LENGTH), accept_action=AcceptAction.RETURN_DOCUMENT, - completer=SQLCompleter(cmd) + completer=SQLCompleter(cmd), + on_text_insert=Capitalizer(cmd) ) buffer.complete_while_typing = lambda cli=None: cmd.should_autocomplete() application = Application( diff --git a/src/crate/crash/test_command.py b/src/crate/crash/test_command.py index 9c577af5..ddbab391 100644 --- a/src/crate/crash/test_command.py +++ b/src/crate/crash/test_command.py @@ -459,6 +459,7 @@ def test_help_command(self): command = CrateCmd(is_tty=False) expected = "\n".join([ '\\? print this help', + '\\autocapitalize toggle automatic capitalization of SQL keywords', '\\autocomplete toggle autocomplete', '\\c connect to the given server, e.g.: \connect localhost:4200', '\\check print failed cluster and/or node checks, e.g. \check nodes', diff --git a/src/crate/crash/test_commands.py b/src/crate/crash/test_commands.py index 92d424aa..06464515 100644 --- a/src/crate/crash/test_commands.py +++ b/src/crate/crash/test_commands.py @@ -26,7 +26,8 @@ from six import StringIO from unittest import TestCase from mock import patch, MagicMock -from .commands import ReadFileCommand, ToggleAutocompleteCommand, \ +from .commands import ReadFileCommand, \ + ToggleAutocompleteCommand, ToggleAutoCapitalizeCommand , \ NodeCheckCommand, ClusterCheckCommand, CheckCommand from .command import CrateCmd @@ -76,6 +77,18 @@ def test_toggle_output(self, fake_cmd): self.assertEqual(output, 'Autocomplete ON') +class ToggleAutoCapitalizeCommandTest(TestCase): + + @patch('crate.crash.command.CrateCmd') + def test_toggle_output(self, fake_cmd): + fake_cmd._autocapitalization = True + command = ToggleAutoCapitalizeCommand() + output = command(fake_cmd) + self.assertEqual(output, 'Auto-capitalization OFF') + output = command(fake_cmd) + self.assertEqual(output, 'Auto-capitalization ON') + + class ShowTablesCommandTest(TestCase): def test_post_0_57(self): diff --git a/src/crate/crash/test_repl.py b/src/crate/crash/test_repl.py index 227a5d50..d8dc1610 100644 --- a/src/crate/crash/test_repl.py +++ b/src/crate/crash/test_repl.py @@ -18,22 +18,70 @@ # software solely pursuant to the terms of the relevant commercial agreement. from unittest import TestCase -from .repl import SQLCompleter +from prompt_toolkit.buffer import Buffer +from prompt_toolkit.document import Document +from .repl import SQLCompleter, Capitalizer from .command import CrateCmd class SQLCompleterTest(TestCase): + def setUp(self): - self.cmd = CrateCmd() - self.completer = SQLCompleter(self.cmd) + cmd = CrateCmd() + self.completer = SQLCompleter(cmd) def test_get_builtin_command_completions(self): - c = self.completer - result = sorted(list(c.get_command_completions('\\c'))) + result = sorted(list(self.completer.get_command_completions('\\c'))) self.assertEqual(result, ['c', 'check', 'connect']) def test_get_command_completions_format(self): - cmd = CrateCmd() - completer = SQLCompleter(cmd) - result = list(completer.get_command_completions(r'\format dyn')) + result = list(self.completer.get_command_completions(r'\format dyn')) self.assertEqual(result, ['dynamic']) + + +class AutoCapitalizeTest(TestCase): + + def setUp(self): + cmd = CrateCmd() + self.capitalizer = Capitalizer(cmd) + + def test_capitalize(self): + buffer = Buffer() + + text = u'selec' + buffer.set_document(Document(text, len(text))) + self.capitalizer(buffer) + self.assertEqual(u'selec', buffer.text) + + text = u'select' + buffer.set_document(Document(text, len(text))) + self.capitalizer(buffer) + self.assertEqual(u'SELECT', buffer.text) + + text = u'CREATE TABLE "select' + buffer.set_document(Document(text, len(text))) + self.capitalizer(buffer) + self.assertEqual(u'CREATE TABLE "select', buffer.text) + + def test_undo_capitalize(self): + buffer = Buffer() + + text = u'Selec' + buffer.set_document(Document(text, len(text))) + self.capitalizer(buffer) + self.assertEqual(u'Selec', buffer.text) + + text = buffer.text + 't' + buffer.set_document(Document(text, len(text))) + self.capitalizer(buffer) + self.assertEqual(u'SELECT', buffer.text) + + text = buffer.text + 'i' + buffer.set_document(Document(text, len(text))) + self.capitalizer(buffer) + self.assertEqual(u'Selecti', buffer.text) + + text = buffer.text + 'on' + buffer.set_document(Document(text, len(text))) + self.capitalizer(buffer) + self.assertEqual(u'Selection', buffer.text) diff --git a/src/crate/crash/tests.py b/src/crate/crash/tests.py index 5efee516..16f1bd2c 100644 --- a/src/crate/crash/tests.py +++ b/src/crate/crash/tests.py @@ -32,7 +32,7 @@ from .test_commands import ReadFileCommandTest, ToggleAutocompleteCommandTest, \ ChecksCommandTest, ShowTablesCommandTest from .test_sysinfo import SysInfoTest -from .test_repl import SQLCompleterTest +from .test_repl import SQLCompleterTest, AutoCapitalizeTest from .test_config import ConfigurationTest @@ -100,6 +100,7 @@ def test_suite(): suite.addTest(unittest.makeSuite(ChecksCommandTest)) suite.addTest(unittest.makeSuite(ShowTablesCommandTest)) suite.addTest(unittest.makeSuite(SQLCompleterTest)) + suite.addTest(unittest.makeSuite(AutoCapitalizeTest)) suite.addTest(unittest.makeSuite(ConfigurationTest)) suite.addTest(unittest.makeSuite(TestGetInformationSchemaQuery))