Blob Blame History Raw
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