commit 530cf2d1d088fa822c19817fba6b16754d75e60b
Author: Dick Marinus <dick@mrns.nl>
Date: Wed Nov 7 20:55:03 2018 +0100
prompt-toolkit 1.0 patches
diff --git a/mycli/clibuffer.py b/mycli/clibuffer.py
index f6cc737..41a63df 100644
--- a/mycli/clibuffer.py
+++ b/mycli/clibuffer.py
@@ -1,21 +1,17 @@
-from __future__ import unicode_literals
-
-from prompt_toolkit.enums import DEFAULT_BUFFER
+from prompt_toolkit.buffer import Buffer
from prompt_toolkit.filters import Condition
-from prompt_toolkit.application import get_app
-from .packages.parseutils import is_open_quote
+class CLIBuffer(Buffer):
+ def __init__(self, always_multiline, *args, **kwargs):
+ self.always_multiline = always_multiline
-def cli_is_multiline(mycli):
- @Condition
- def cond():
- doc = get_app().layout.get_buffer_by_name(DEFAULT_BUFFER).document
+ @Condition
+ def is_multiline():
+ doc = self.document
+ return self.always_multiline and not _multiline_exception(doc.text)
- if not mycli.multi_line:
- return False
- else:
- return not _multiline_exception(doc.text)
- return cond
+ super(self.__class__, self).__init__(*args, is_multiline=is_multiline,
+ tempfile_suffix='.sql', **kwargs)
def _multiline_exception(text):
orig = text
diff --git a/mycli/clistyle.py b/mycli/clistyle.py
index bde464c..89caf71 100644
--- a/mycli/clistyle.py
+++ b/mycli/clistyle.py
@@ -1,118 +1,28 @@
-from __future__ import unicode_literals
-
-import logging
-
+import pygments.style
import pygments.styles
-from pygments.token import string_to_tokentype, Token
-from pygments.style import Style as PygmentsStyle
+from pygments.token import string_to_tokentype
from pygments.util import ClassNotFound
-from prompt_toolkit.styles.pygments import style_from_pygments_cls
-from prompt_toolkit.styles import merge_styles, Style
-
-logger = logging.getLogger(__name__)
-
-# map Pygments tokens (ptk 1.0) to class names (ptk 2.0).
-TOKEN_TO_PROMPT_STYLE = {
- Token.Menu.Completions.Completion.Current: 'completion-menu.completion.current',
- Token.Menu.Completions.Completion: 'completion-menu.completion',
- Token.Menu.Completions.Meta.Current: 'completion-menu.meta.completion.current',
- Token.Menu.Completions.Meta: 'completion-menu.meta.completion',
- Token.Menu.Completions.MultiColumnMeta: 'completion-menu.multi-column-meta',
- Token.Menu.Completions.ProgressButton: 'scrollbar.arrow', # best guess
- Token.Menu.Completions.ProgressBar: 'scrollbar', # best guess
- Token.SelectedText: 'selected',
- Token.SearchMatch: 'search',
- Token.SearchMatch.Current: 'search.current',
- Token.Toolbar: 'bottom-toolbar',
- Token.Toolbar.Off: 'bottom-toolbar.off',
- Token.Toolbar.On: 'bottom-toolbar.on',
- Token.Toolbar.Search: 'search-toolbar',
- Token.Toolbar.Search.Text: 'search-toolbar.text',
- Token.Toolbar.System: 'system-toolbar',
- Token.Toolbar.Arg: 'arg-toolbar',
- Token.Toolbar.Arg.Text: 'arg-toolbar.text',
- Token.Toolbar.Transaction.Valid: 'bottom-toolbar.transaction.valid',
- Token.Toolbar.Transaction.Failed: 'bottom-toolbar.transaction.failed',
- Token.Output.Header: 'output.header',
- Token.Output.OddRow: 'output.odd-row',
- Token.Output.EvenRow: 'output.even-row',
-}
-# reverse dict for cli_helpers, because they still expect Pygments tokens.
-PROMPT_STYLE_TO_TOKEN = {
- v: k for k, v in TOKEN_TO_PROMPT_STYLE.items()
-}
+def style_factory(name, cli_style):
+ """Create a Pygments Style class based on the user's preferences.
-def parse_pygments_style(token_name, style_object, style_dict):
- """Parse token type and style string.
-
- :param token_name: str name of Pygments token. Example: "Token.String"
- :param style_object: pygments.style.Style instance to use as base
- :param style_dict: dict of token names and their styles, customized to this cli
+ :param str name: The name of a built-in Pygments style.
+ :param dict cli_style: The user's token-type style preferences.
"""
- token_type = string_to_tokentype(token_name)
- try:
- other_token_type = string_to_tokentype(style_dict[token_name])
- return token_type, style_object.styles[other_token_type]
- except AttributeError as err:
- return token_type, style_dict[token_name]
-
-
-def style_factory(name, cli_style):
try:
style = pygments.styles.get_style_by_name(name)
except ClassNotFound:
style = pygments.styles.get_style_by_name('native')
- prompt_styles = []
- # prompt-toolkit used pygments tokens for styling before, switched to style
- # names in 2.0. Convert old token types to new style names, for backwards compatibility.
- for token in cli_style:
- if token.startswith('Token.'):
- # treat as pygments token (1.0)
- token_type, style_value = parse_pygments_style(
- token, style, cli_style)
- if token_type in TOKEN_TO_PROMPT_STYLE:
- prompt_style = TOKEN_TO_PROMPT_STYLE[token_type]
- prompt_styles.append((prompt_style, style_value))
- else:
- # we don't want to support tokens anymore
- logger.error('Unhandled style / class name: %s', token)
- else:
- # treat as prompt style name (2.0). See default style names here:
- # https://github.com/jonathanslenders/python-prompt-toolkit/blob/master/prompt_toolkit/styles/defaults.py
- prompt_styles.append((token, cli_style[token]))
-
- override_style = Style([('bottom-toolbar', 'noreverse')])
- return merge_styles([
- style_from_pygments_cls(style),
- override_style,
- Style(prompt_styles)
- ])
-
-
-def style_factory_output(name, cli_style):
- try:
- style = pygments.styles.get_style_by_name(name).styles
- except ClassNotFound:
- style = pygments.styles.get_style_by_name('native').styles
-
- for token in cli_style:
- if token.startswith('Token.'):
- token_type, style_value = parse_pygments_style(
- token, style, cli_style)
- style.update({token_type: style_value})
- elif token in PROMPT_STYLE_TO_TOKEN:
- token_type = PROMPT_STYLE_TO_TOKEN[token]
- style.update({token_type: cli_style[token]})
- else:
- # TODO: cli helpers will have to switch to ptk.Style
- logger.error('Unhandled style / class name: %s', token)
+ style_tokens = {}
+ style_tokens.update(style.styles)
+ custom_styles = {string_to_tokentype(x): y for x, y in cli_style.items()}
+ style_tokens.update(custom_styles)
- class OutputStyle(PygmentsStyle):
- default_style = ""
- styles = style
+ class MycliStyle(pygments.style.Style):
+ default_styles = ''
+ styles = style_tokens
- return OutputStyle
+ return MycliStyle
diff --git a/mycli/clitoolbar.py b/mycli/clitoolbar.py
index 89e6afa..bb638d5 100644
--- a/mycli/clitoolbar.py
+++ b/mycli/clitoolbar.py
@@ -1,48 +1,48 @@
-from __future__ import unicode_literals
-
+from pygments.token import Token
+from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
from prompt_toolkit.key_binding.vi_state import InputMode
-from prompt_toolkit.application import get_app
-from prompt_toolkit.enums import EditingMode
-def create_toolbar_tokens_func(mycli, show_fish_help):
- """Return a function that generates the toolbar tokens."""
- def get_toolbar_tokens():
- result = []
- result.append(('class:bottom-toolbar', ' '))
+def create_toolbar_tokens_func(get_is_refreshing, show_fish_help):
+ """
+ Return a function that generates the toolbar tokens.
+ """
+ token = Token.Toolbar
- if mycli.multi_line:
- result.append(
- ('class:bottom-toolbar', ' (Semi-colon [;] will end the line) '))
+ def get_toolbar_tokens(cli):
+ result = []
+ result.append((token, ' '))
- if mycli.multi_line:
- result.append(('class:bottom-toolbar.on', '[F3] Multiline: ON '))
+ if cli.buffers[DEFAULT_BUFFER].always_multiline:
+ result.append((token.On, '[F3] Multiline: ON '))
else:
- result.append(('class:bottom-toolbar.off',
- '[F3] Multiline: OFF '))
- if mycli.prompt_app.editing_mode == EditingMode.VI:
+ result.append((token.Off, '[F3] Multiline: OFF '))
+
+ if cli.buffers[DEFAULT_BUFFER].always_multiline:
+ result.append((token,
+ ' (Semi-colon [;] will end the line)'))
+
+ if cli.editing_mode == EditingMode.VI:
result.append((
- 'class:botton-toolbar.on',
- 'Vi-mode ({})'.format(_get_vi_mode())
+ token.On,
+ 'Vi-mode ({})'.format(_get_vi_mode(cli))
))
if show_fish_help():
- result.append(
- ('class:bottom-toolbar', ' Right-arrow to complete suggestion'))
+ result.append((token, ' Right-arrow to complete suggestion'))
- if mycli.completion_refresher.is_refreshing():
- result.append(
- ('class:bottom-toolbar', ' Refreshing completions...'))
+ if get_is_refreshing():
+ result.append((token, ' Refreshing completions...'))
return result
return get_toolbar_tokens
-def _get_vi_mode():
+def _get_vi_mode(cli):
"""Get the current vi mode for display."""
return {
InputMode.INSERT: 'I',
InputMode.NAVIGATION: 'N',
InputMode.REPLACE: 'R',
- InputMode.INSERT_MULTIPLE: 'M',
- }[get_app().vi_state.input_mode]
+ InputMode.INSERT_MULTIPLE: 'M'
+ }[cli.vi_state.input_mode]
diff --git a/mycli/filters.py b/mycli/filters.py
new file mode 100644
index 0000000..6a8075f
--- /dev/null
+++ b/mycli/filters.py
@@ -0,0 +1,12 @@
+from prompt_toolkit.filters import Filter
+
+class HasSelectedCompletion(Filter):
+ """Enable when the current buffer has a selected completion."""
+
+ def __call__(self, cli):
+ complete_state = cli.current_buffer.complete_state
+ return (complete_state is not None and
+ complete_state.current_completion is not None)
+
+ def __repr__(self):
+ return "HasSelectedCompletion()"
diff --git a/mycli/key_bindings.py b/mycli/key_bindings.py
index abc559f..33bb4f2 100644
--- a/mycli/key_bindings.py
+++ b/mycli/key_bindings.py
@@ -1,50 +1,65 @@
-from __future__ import unicode_literals
import logging
from prompt_toolkit.enums import EditingMode
-from prompt_toolkit.filters import completion_is_selected
-from prompt_toolkit.key_binding import KeyBindings
+from prompt_toolkit.keys import Keys
+from prompt_toolkit.key_binding.manager import KeyBindingManager
+from .filters import HasSelectedCompletion
_logger = logging.getLogger(__name__)
-def mycli_bindings(mycli):
- """Custom key bindings for mycli."""
- kb = KeyBindings()
+def mycli_bindings():
+ """
+ Custom key bindings for mycli.
+ """
+ key_binding_manager = KeyBindingManager(
+ enable_open_in_editor=True,
+ enable_system_bindings=True,
+ enable_auto_suggest_bindings=True,
+ enable_search=True,
+ enable_abort_and_exit_bindings=True)
- @kb.add('f2')
+ @key_binding_manager.registry.add_binding(Keys.F2)
def _(event):
- """Enable/Disable SmartCompletion Mode."""
+ """
+ Enable/Disable SmartCompletion Mode.
+ """
_logger.debug('Detected F2 key.')
- mycli.completer.smart_completion = not mycli.completer.smart_completion
+ buf = event.cli.current_buffer
+ buf.completer.smart_completion = not buf.completer.smart_completion
- @kb.add('f3')
+ @key_binding_manager.registry.add_binding(Keys.F3)
def _(event):
- """Enable/Disable Multiline Mode."""
+ """
+ Enable/Disable Multiline Mode.
+ """
_logger.debug('Detected F3 key.')
- mycli.multi_line = not mycli.multi_line
+ buf = event.cli.current_buffer
+ buf.always_multiline = not buf.always_multiline
- @kb.add('f4')
+ @key_binding_manager.registry.add_binding(Keys.F4)
def _(event):
- """Toggle between Vi and Emacs mode."""
+ """
+ Toggle between Vi and Emacs mode.
+ """
_logger.debug('Detected F4 key.')
- if mycli.key_bindings == "vi":
- event.app.editing_mode = EditingMode.EMACS
- mycli.key_bindings = "emacs"
+ if event.cli.editing_mode == EditingMode.VI:
+ event.cli.editing_mode = EditingMode.EMACS
else:
- event.app.editing_mode = EditingMode.VI
- mycli.key_bindings = "vi"
+ event.cli.editing_mode = EditingMode.VI
- @kb.add('tab')
+ @key_binding_manager.registry.add_binding(Keys.Tab)
def _(event):
- """Force autocompletion at cursor."""
+ """
+ Force autocompletion at cursor.
+ """
_logger.debug('Detected <Tab> key.')
- b = event.app.current_buffer
+ b = event.cli.current_buffer
if b.complete_state:
b.complete_next()
else:
- b.start_completion(select_first=True)
+ event.cli.start_completion(select_first=True)
- @kb.add('c-space')
+ @key_binding_manager.registry.add_binding(Keys.ControlSpace)
def _(event):
"""
Initialize autocompletion at cursor.
@@ -56,25 +71,21 @@ def mycli_bindings(mycli):
"""
_logger.debug('Detected <C-Space> key.')
- b = event.app.current_buffer
+ b = event.cli.current_buffer
if b.complete_state:
b.complete_next()
else:
- b.start_completion(select_first=False)
+ event.cli.start_completion(select_first=False)
- @kb.add('enter', filter=completion_is_selected)
+ @key_binding_manager.registry.add_binding(Keys.ControlJ, filter=HasSelectedCompletion())
def _(event):
- """Makes the enter key work as the tab key only when showing the menu.
-
- In other words, don't execute query when enter is pressed in
- the completion dropdown menu, instead close the dropdown menu
- (accept current selection).
-
"""
- _logger.debug('Detected enter key.')
+ Makes the enter key work as the tab key only when showing the menu.
+ """
+ _logger.debug('Detected <C-J> key.')
event.current_buffer.complete_state = None
- b = event.app.current_buffer
+ b = event.cli.current_buffer
b.complete_state = None
- return kb
+ return key_binding_manager
diff --git a/mycli/main.py b/mycli/main.py
index 2c5f2b6..d4206ad 100755
--- a/mycli/main.py
+++ b/mycli/main.py
@@ -15,17 +15,18 @@ from cli_helpers.tabular_output import TabularOutputFormatter
from cli_helpers.tabular_output import preprocessors
import click
import sqlparse
-from prompt_toolkit.completion import DynamicCompleter
+from prompt_toolkit import CommandLineInterface, Application, AbortAction
+from prompt_toolkit.interface import AcceptAction
from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
-from prompt_toolkit.shortcuts import PromptSession, CompleteStyle
-from prompt_toolkit.styles.pygments import style_from_pygments_cls
+from prompt_toolkit.shortcuts import create_prompt_layout, create_eventloop
+from prompt_toolkit.styles.from_pygments import style_from_pygments
from prompt_toolkit.document import Document
-from prompt_toolkit.filters import HasFocus, IsDone
+from prompt_toolkit.filters import Always, HasFocus, IsDone
from prompt_toolkit.layout.processors import (HighlightMatchingBracketProcessor,
ConditionalProcessor)
-from prompt_toolkit.lexers import PygmentsLexer
from prompt_toolkit.history import FileHistory
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
+from pygments.token import Token
from .packages.special.main import NO_QUERY
from .packages.prompt_utils import confirm, confirm_destructive_query, prompt
@@ -33,9 +34,9 @@ from .packages.tabular_output import sql_format
import mycli.packages.special as special
from .sqlcompleter import SQLCompleter
from .clitoolbar import create_toolbar_tokens_func
-from .clistyle import style_factory, style_factory_output
+from .clistyle import style_factory
from .sqlexecute import FIELD_TYPES, SQLExecute
-from .clibuffer import cli_is_multiline
+from .clibuffer import CLIBuffer
from .completion_refresher import CompletionRefresher
from .config import (write_default_config, get_mylogin_cnf_path,
open_mylogin_cnf, read_config_files, str_to_bool)
@@ -125,10 +126,7 @@ class MyCli(object):
self.syntax_style = c['main']['syntax_style']
self.less_chatty = c['main'].as_bool('less_chatty')
self.cli_style = c['colors']
- self.output_style = style_factory_output(
- self.syntax_style,
- self.cli_style
- )
+ self.output_style = style_factory(self.syntax_style, self.cli_style)
self.wider_completion_menu = c['main'].as_bool('wider_completion_menu')
c_dest_warning = c['main'].as_bool('destructive_warning')
self.destructive_warning = c_dest_warning if warn is None else warn
@@ -186,7 +184,7 @@ class MyCli(object):
# There was an error reading the login path file.
print('Error: Unable to read login path file.')
- self.prompt_app = None
+ self.cli = None
def register_special_commands(self):
special.register_special_command(self.change_db, 'use',
@@ -460,39 +458,37 @@ class MyCli(object):
self.echo(str(e), err=True, fg='red')
exit(1)
- def handle_editor_command(self, text):
- """Editor command is any query that is prefixed or suffixed by a '\e'.
- The reason for a while loop is because a user might edit a query
- multiple times. For eg:
-
+ def handle_editor_command(self, cli, document):
+ """
+ Editor command is any query that is prefixed or suffixed
+ by a '\e'. The reason for a while loop is because a user
+ might edit a query multiple times.
+ For eg:
"select * from \e"<enter> to edit it in vim, then come
back to the prompt with the edited query "select * from
blah where q = 'abc'\e" to edit it again.
- :param text: Document
+ :param cli: CommandLineInterface
+ :param document: Document
:return: Document
-
"""
-
- while special.editor_command(text):
- filename = special.get_filename(text)
- query = (special.get_editor_query(text) or
+ # FIXME: using application.pre_run_callables like this here is not the best solution.
+ # It's internal api of prompt_toolkit that may change. This was added to fix
+ # https://github.com/dbcli/pgcli/issues/668. We may find a better way to do it in the future.
+ saved_callables = cli.application.pre_run_callables
+ while special.editor_command(document.text):
+ filename = special.get_filename(document.text)
+ query = (special.get_editor_query(document.text) or
self.get_last_query())
sql, message = special.open_external_editor(filename, sql=query)
if message:
# Something went wrong. Raise an exception and bail.
raise RuntimeError(message)
- while True:
- try:
- text = self.prompt_app.prompt(
- default=sql,
- vi_mode=self.key_bindings == 'vi'
- )
- break
- except KeyboardInterrupt:
- sql = None
-
+ cli.current_buffer.document = Document(sql, cursor_position=len(sql))
+ cli.application.pre_run_callables = []
+ document = cli.run()
continue
- return text
+ cli.application.pre_run_callables = saved_callables
+ return document
def run_cli(self):
iterations = 0
@@ -517,7 +513,7 @@ class MyCli(object):
'Your query history will not be saved.'.format(history_file),
err=True, fg='red')
- key_bindings = mycli_bindings(self)
+ key_binding_manager = mycli_bindings()
if not self.less_chatty:
print(' '.join(sqlexecute.server_type()))
@@ -527,43 +523,38 @@ class MyCli(object):
print('Home: http://mycli.net')
print('Thanks to the contributor -', thanks_picker([author_file, sponsor_file]))
- def get_message():
+ def prompt_tokens(cli):
prompt = self.get_prompt(self.prompt_format)
if self.prompt_format == self.default_prompt and len(prompt) > self.max_len_prompt:
prompt = self.get_prompt('\\d> ')
- return [('class:prompt', prompt)]
+ return [(Token.Prompt, prompt)]
- def get_continuation(width, line_number, is_soft_wrap):
- continuation = ' ' * (width - 1) + ' '
- return [('class:continuation', continuation)]
+ def get_continuation_tokens(cli, width):
+ continuation_prompt = self.get_prompt(self.prompt_continuation_format)
+ return [(Token.Continuation, ' ' * (width - len(continuation_prompt)) + continuation_prompt)]
def show_suggestion_tip():
return iterations < 2
- def one_iteration(text=None):
- if text is None:
- try:
- text = self.prompt_app.prompt(
- vi_mode=self.key_bindings == 'vi'
- )
- except KeyboardInterrupt:
- return
+ def one_iteration(document=None):
+ if document is None:
+ document = self.cli.run()
special.set_expanded_output(False)
try:
- text = self.handle_editor_command(text)
+ document = self.handle_editor_command(self.cli, document)
except RuntimeError as e:
- logger.error("sql: %r, error: %r", text, e)
+ logger.error("sql: %r, error: %r", document.text, e)
logger.error("traceback: %r", traceback.format_exc())
self.echo(str(e), err=True, fg='red')
return
- if not text.strip():
+ if not document.text.strip():
return
if self.destructive_warning:
- destroy = confirm_destructive_query(text)
+ destroy = confirm_destructive_query(document.text)
if destroy is None:
pass # Query was not destructive. Nothing to do here.
elif destroy is True:
@@ -578,18 +569,18 @@ class MyCli(object):
mutating = False
try:
- logger.debug('sql: %r', text)
+ logger.debug('sql: %r', document.text)
- special.write_tee(self.get_prompt(self.prompt_format) + text)
+ special.write_tee(self.get_prompt(self.prompt_format) + document.text)
if self.logfile:
self.logfile.write('\n# %s\n' % datetime.now())
- self.logfile.write(text)
+ self.logfile.write(document.text)
self.logfile.write('\n')
successful = False
start = time()
- res = sqlexecute.run(text)
- self.formatter.query = text
+ res = sqlexecute.run(document.text)
+ self.formatter.query = document.text
successful = True
result_count = 0
for title, cur, headers, status in res:
@@ -606,7 +597,7 @@ class MyCli(object):
break
if self.auto_vertical_output:
- max_width = self.prompt_app.output.get_size().columns
+ max_width = self.cli.output.get_size().columns
else:
max_width = None
@@ -644,7 +635,7 @@ class MyCli(object):
status_str = str(status).lower()
if status_str.find('ok') > -1:
logger.debug("cancelled query, connection id: %r, sql: %r",
- connection_id_to_kill, text)
+ connection_id_to_kill, document.text)
self.echo("cancelled query", err=True, fg='red')
except Exception as e:
self.echo('Encountered error while cancelling query: {}'.format(e),
@@ -659,7 +650,7 @@ class MyCli(object):
try:
sqlexecute.connect()
logger.debug('Reconnected successfully.')
- one_iteration(text)
+ one_iteration(document)
return # OK to just return, cuz the recursion call runs to the end.
except OperationalError as e:
logger.debug('Reconnect failed. e: %r', e)
@@ -667,69 +658,67 @@ class MyCli(object):
# If reconnection failed, don't proceed further.
return
else:
- logger.error("sql: %r, error: %r", text, e)
+ logger.error("sql: %r, error: %r", document.text, e)
logger.error("traceback: %r", traceback.format_exc())
self.echo(str(e), err=True, fg='red')
except Exception as e:
- logger.error("sql: %r, error: %r", text, e)
+ logger.error("sql: %r, error: %r", document.text, e)
logger.error("traceback: %r", traceback.format_exc())
self.echo(str(e), err=True, fg='red')
else:
- if is_dropping_database(text, self.sqlexecute.dbname):
+ if is_dropping_database(document.text, self.sqlexecute.dbname):
self.sqlexecute.dbname = None
self.sqlexecute.connect()
# Refresh the table names and column names if necessary.
- if need_completion_refresh(text):
+ if need_completion_refresh(document.text):
self.refresh_completions(
- reset=need_completion_reset(text))
+ reset=need_completion_reset(document.text))
finally:
if self.logfile is False:
self.echo("Warning: This query was not logged.",
err=True, fg='red')
- query = Query(text, successful, mutating)
+ query = Query(document.text, successful, mutating)
self.query_history.append(query)
get_toolbar_tokens = create_toolbar_tokens_func(
- self, show_suggestion_tip)
- if self.wider_completion_menu:
- complete_style = CompleteStyle.MULTI_COLUMN
- else:
- complete_style = CompleteStyle.COLUMN
-
+ self.completion_refresher.is_refreshing,
+ show_suggestion_tip)
+
+ layout = create_prompt_layout(
+ lexer=MyCliLexer,
+ multiline=True,
+ get_prompt_tokens=prompt_tokens,
+ get_continuation_tokens=get_continuation_tokens,
+ get_bottom_toolbar_tokens=get_toolbar_tokens,
+ display_completions_in_columns=self.wider_completion_menu,
+ extra_input_processors=[ConditionalProcessor(
+ processor=HighlightMatchingBracketProcessor(chars='[](){}'),
+ filter=HasFocus(DEFAULT_BUFFER) & ~IsDone()
+ )],
+ reserve_space_for_menu=self.get_reserved_space()
+ )
with self._completer_lock:
+ buf = CLIBuffer(
+ always_multiline=self.multi_line, completer=self.completer,
+ history=history, auto_suggest=AutoSuggestFromHistory(),
+ complete_while_typing=Always(),
+ accept_action=AcceptAction.RETURN_DOCUMENT)
if self.key_bindings == 'vi':
editing_mode = EditingMode.VI
else:
editing_mode = EditingMode.EMACS
- self.prompt_app = PromptSession(
- lexer=PygmentsLexer(MyCliLexer),
- reserve_space_for_menu=self.get_reserved_space(),
- message=get_message,
- prompt_continuation=get_continuation,
- bottom_toolbar=get_toolbar_tokens,
- complete_style=complete_style,
- input_processors=[ConditionalProcessor(
- processor=HighlightMatchingBracketProcessor(
- chars='[](){}'),
- filter=HasFocus(DEFAULT_BUFFER) & ~IsDone()
- )],
- tempfile_suffix='.sql',
- completer=DynamicCompleter(lambda: self.completer),
- history=history,
- auto_suggest=AutoSuggestFromHistory(),
- complete_while_typing=True,
- multiline=cli_is_multiline(self),
- style=style_factory(self.syntax_style, self.cli_style),
- include_default_pygments_style=False,
- key_bindings=key_bindings,
- enable_open_in_editor=True,
- enable_system_prompt=True,
- editing_mode=editing_mode,
- search_ignore_case=True
- )
+ application = Application(
+ style=style_from_pygments(style_cls=self.output_style),
+ layout=layout, buffer=buf,
+ key_bindings_registry=key_binding_manager.registry,
+ on_exit=AbortAction.RAISE_EXCEPTION,
+ on_abort=AbortAction.RETRY, editing_mode=editing_mode,
+ ignore_case=True)
+ self.cli = CommandLineInterface(application=application,
+ eventloop=create_eventloop())
try:
while True:
@@ -779,7 +768,7 @@ class MyCli(object):
"""
if output:
- size = self.prompt_app.output.get_size()
+ size = self.cli.output.get_size()
margin = self.get_output_margin(status)
@@ -853,11 +842,16 @@ class MyCli(object):
"""
with self._completer_lock:
self.completer = new_completer
+ # When mycli is first launched we call refresh_completions before
+ # instantiating the cli object. So it is necessary to check if cli
+ # exists before trying the replace the completer object in cli.
+ if self.cli:
+ self.cli.current_buffer.completer = new_completer
- if self.prompt_app:
+ if self.cli:
# After refreshing, redraw the CLI to clear the statusbar
# "Refreshing completions..." indicator
- self.prompt_app.app.invalidate()
+ self.cli.request_redraw()
def get_completions(self, text, cursor_positition):
with self._completer_lock:
@@ -1162,6 +1156,8 @@ def cli(database, user, host, port, socket, password, dbname,
sys.stdin = open('/dev/tty')
except FileNotFoundError:
mycli.logger.warning('Unable to open TTY as stdin.')
+ except OSError:
+ mycli.logger.warning('Unable to open TTY as stdin.')
if (mycli.destructive_warning and
confirm_destructive_query(stdin_text) is False):
diff --git a/mycli/myclirc b/mycli/myclirc
index 8ecbddc..7e564db 100644
--- a/mycli/myclirc
+++ b/mycli/myclirc
@@ -85,31 +85,36 @@ enable_pager = True
# Custom colors for the completion menu, toolbar, etc.
[colors]
-completion-menu.completion.current = 'bg:#ffffff #000000'
-completion-menu.completion = 'bg:#008888 #ffffff'
-completion-menu.meta.completion.current = 'bg:#44aaaa #000000'
-completion-menu.meta.completion = 'bg:#448888 #ffffff'
-completion-menu.multi-column-meta = 'bg:#aaffff #000000'
-scrollbar.arrow = 'bg:#003333'
-scrollbar = 'bg:#00aaaa'
-selected = '#ffffff bg:#6666aa'
-search = '#ffffff bg:#4444aa'
-search.current = '#ffffff bg:#44aa44'
-bottom-toolbar = 'bg:#222222 #aaaaaa'
-bottom-toolbar.off = 'bg:#222222 #888888'
-bottom-toolbar.on = 'bg:#222222 #ffffff'
-search-toolbar = 'noinherit bold'
-search-toolbar.text = 'nobold'
-system-toolbar = 'noinherit bold'
-arg-toolbar = 'noinherit bold'
-arg-toolbar.text = 'nobold'
-bottom-toolbar.transaction.valid = 'bg:#222222 #00ff5f bold'
-bottom-toolbar.transaction.failed = 'bg:#222222 #ff005f bold'
-
-# style classes for colored table output
-output.header = "#00ff5f bold"
-output.odd-row = ""
-output.even-row = ""
+# Completion menus.
+Token.Menu.Completions.Completion.Current = 'bg:#00aaaa #000000'
+Token.Menu.Completions.Completion = 'bg:#008888 #ffffff'
+Token.Menu.Completions.MultiColumnMeta = 'bg:#aaffff #000000'
+Token.Menu.Completions.ProgressButton = 'bg:#003333'
+Token.Menu.Completions.ProgressBar = 'bg:#00aaaa'
+
+# Query results
+Token.Output.Header = 'bold'
+Token.Output.OddRow = ''
+Token.Output.EvenRow = ''
+
+# Selected text.
+Token.SelectedText = '#ffffff bg:#6666aa'
+
+# Search matches. (reverse-i-search)
+Token.SearchMatch = '#ffffff bg:#4444aa'
+Token.SearchMatch.Current = '#ffffff bg:#44aa44'
+
+# The bottom toolbar.
+Token.Toolbar = 'bg:#222222 #aaaaaa'
+Token.Toolbar.Off = 'bg:#222222 #888888'
+Token.Toolbar.On = 'bg:#222222 #ffffff'
+
+# Search/arg/system toolbars.
+Token.Toolbar.Search = 'noinherit bold'
+Token.Toolbar.Search.Text = 'nobold'
+Token.Toolbar.System = 'noinherit bold'
+Token.Toolbar.Arg = 'noinherit bold'
+Token.Toolbar.Arg.Text = 'nobold'
# Favorite queries.
[favorite_queries]
diff --git a/mycli/packages/filepaths.py b/mycli/packages/filepaths.py
index 5ebdcd9..87e5e74 100644
--- a/mycli/packages/filepaths.py
+++ b/mycli/packages/filepaths.py
@@ -1,6 +1,4 @@
# -*- coding: utf-8
-from __future__ import unicode_literals
-from mycli.encodingutils import text_type
import os
@@ -62,10 +60,10 @@ def suggest_path(root_dir):
"""
if not root_dir:
- return [text_type(os.path.abspath(os.sep)), text_type('~'), text_type(os.curdir), text_type(os.pardir)]
+ return [os.path.abspath(os.sep), '~', os.curdir, os.pardir]
if '~' in root_dir:
- root_dir = text_type(os.path.expanduser(root_dir))
+ root_dir = os.path.expanduser(root_dir)
if not os.path.exists(root_dir):
root_dir, _ = os.path.split(root_dir)
diff --git a/mycli/packages/parseutils.py b/mycli/packages/parseutils.py
index 3e0f2e7..672d70c 100644
--- a/mycli/packages/parseutils.py
+++ b/mycli/packages/parseutils.py
@@ -209,14 +209,6 @@ def is_destructive(queries):
return queries_start_with(queries, keywords)
-def is_open_quote(sql):
- """Returns true if the query contains an unclosed quote."""
-
- # parsed can contain one or more semi-colon separated commands
- parsed = sqlparse.parse(sql)
- return any(_parsed_is_open_quote(p) for p in parsed)
-
-
if __name__ == '__main__':
sql = 'select * from (select t. from tabl t'
print (extract_tables(sql))
diff --git a/mycli/packages/prompt_utils.py b/mycli/packages/prompt_utils.py
index 138cef3..420ea2a 100644
--- a/mycli/packages/prompt_utils.py
+++ b/mycli/packages/prompt_utils.py
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
import sys
diff --git a/setup.py b/setup.py
index 6a24d24..5f0f742 100755
--- a/setup.py
+++ b/setup.py
@@ -19,7 +19,7 @@ description = 'CLI for MySQL Database. With auto-completion and syntax highlight
install_requirements = [
'click >= 4.1',
'Pygments >= 1.6',
- 'prompt_toolkit>=2.0.0,<2.0.5',
+ 'prompt_toolkit>=1.0.10,<1.1.0',
'PyMySQL >= 0.9.2',
'sqlparse>=0.2.2,<0.3.0',
'configobj >= 5.0.5',
diff --git a/test/features/steps/crud_table.py b/test/features/steps/crud_table.py
index dc058c4..8a4cd73 100644
--- a/test/features/steps/crud_table.py
+++ b/test/features/steps/crud_table.py
@@ -76,7 +76,7 @@ def step_see_data_selected(context):
+-----+\r
| yyy |\r
+-----+\r
- """), timeout=2)
+ """), timeout=1)
wrappers.expect_exact(context, '1 row in set', timeout=2)
@@ -108,5 +108,5 @@ def step_see_null_selected(context):
+--------+\r
| <null> |\r
+--------+\r
- """), timeout=2)
+ """), timeout=1)
wrappers.expect_exact(context, '1 row in set', timeout=2)
diff --git a/test/features/steps/iocommands.py b/test/features/steps/iocommands.py
index 1d3ba65..27b5529 100644
--- a/test/features/steps/iocommands.py
+++ b/test/features/steps/iocommands.py
@@ -38,7 +38,7 @@ def step_edit_quit(context):
@then('we see the sql in prompt')
def step_edit_done_sql(context):
for match in 'select * from abc'.split(' '):
- wrappers.expect_exact(context, match, timeout=5)
+ wrappers.expect_exact(context, match, timeout=1)
# Cleanup the command line.
context.cli.sendcontrol('c')
# Cleanup the edited file.
diff --git a/test/features/steps/named_queries.py b/test/features/steps/named_queries.py
index b82d5f4..651af3b 100644
--- a/test/features/steps/named_queries.py
+++ b/test/features/steps/named_queries.py
@@ -32,19 +32,19 @@ def step_delete_named_query(context):
@then('we see the named query saved')
def step_see_named_query_saved(context):
"""Wait to see query saved."""
- wrappers.expect_exact(context, 'Saved.', timeout=2)
+ wrappers.expect_exact(context, 'Saved.', timeout=1)
@then('we see the named query executed')
def step_see_named_query_executed(context):
"""Wait to see select output."""
- wrappers.expect_exact(context, 'SELECT 12345', timeout=2)
+ wrappers.expect_exact(context, 'SELECT 12345', timeout=1)
@then('we see the named query deleted')
def step_see_named_query_deleted(context):
"""Wait to see query deleted."""
- wrappers.expect_exact(context, 'foo: Deleted', timeout=2)
+ wrappers.expect_exact(context, 'foo: Deleted', timeout=1)
@when('we save a named query with parameters')
@@ -63,7 +63,7 @@ def step_use_named_query_with_parameters(context):
def step_see_named_query_with_parameters_executed(context):
"""Wait to see select output."""
wrappers.expect_exact(
- context, 'SELECT 101, "second", "third value"', timeout=2)
+ context, 'SELECT 101, "second", "third value"', timeout=1)
@when('we use named query with too few parameters')
@@ -76,7 +76,7 @@ def step_use_named_query_with_too_few_parameters(context):
def step_see_named_query_with_parameters_fail_with_missing_parameters(context):
"""Wait to see select output."""
wrappers.expect_exact(
- context, 'missing substitution for $2 in query:', timeout=2)
+ context, 'missing substitution for $2 in query:', timeout=1)
@when('we use named query with too many parameters')
@@ -89,4 +89,4 @@ def step_use_named_query_with_too_many_parameters(context):
def step_see_named_query_with_parameters_fail_with_extra_parameters(context):
"""Wait to see select output."""
wrappers.expect_exact(
- context, 'query does not have substitution parameter $4:', timeout=2)
+ context, 'query does not have substitution parameter $4:', timeout=1)
diff --git a/test/test_clistyle.py b/test/test_clistyle.py
index e18a530..aa1f221 100644
--- a/test/test_clistyle.py
+++ b/test/test_clistyle.py
@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
"""Test the mycli.clistyle module."""
-import pytest
from pygments.style import Style
from pygments.token import Token
@@ -8,7 +7,6 @@ from pygments.token import Token
from mycli.clistyle import style_factory
-@pytest.mark.skip(reason="incompatible with new prompt toolkit")
def test_style_factory():
"""Test that a Pygments Style class is created."""
header = 'bold underline #ansired'
@@ -20,7 +18,6 @@ def test_style_factory():
assert header == style.styles[Token.Output.Header]
-@pytest.mark.skip(reason="incompatible with new prompt toolkit")
def test_style_factory_unknown_name():
"""Test that an unrecognized name will not throw an error."""
style = style_factory('foobar', {})
diff --git a/test/test_main.py b/test/test_main.py
index 75f7a24..95cdf67 100644
--- a/test/test_main.py
+++ b/test/test_main.py
@@ -186,10 +186,10 @@ def output(monkeypatch, terminal_size, testdata, explicit_pager, expect_pager):
def server_type(self):
return ['test']
- class PromptBuffer():
+ class CommandLineInterface():
output = TestOutput()
- m.prompt_app = PromptBuffer()
+ m.cli = CommandLineInterface()
m.sqlexecute = TestExecute()
m.explicit_pager = explicit_pager