1""" 2`Fish-style <http://fishshell.com/>`_ like auto-suggestion. 3 4While a user types input in a certain buffer, suggestions are generated 5(asynchronously.) Usually, they are displayed after the input. When the cursor 6presses the right arrow and the cursor is at the end of the input, the 7suggestion will be inserted. 8 9If you want the auto suggestions to be asynchronous (in a background thread), 10because they take too much time, and could potentially block the event loop, 11then wrap the :class:`.AutoSuggest` instance into a 12:class:`.ThreadedAutoSuggest`. 13""" 14from __future__ import unicode_literals 15 16from abc import ABCMeta, abstractmethod 17 18from six import with_metaclass 19 20from .eventloop import Future, run_in_executor 21from .filters import to_filter 22 23__all__ = [ 24 'Suggestion', 25 'AutoSuggest', 26 'ThreadedAutoSuggest', 27 'DummyAutoSuggest', 28 'AutoSuggestFromHistory', 29 'ConditionalAutoSuggest', 30 'DynamicAutoSuggest', 31] 32 33 34class Suggestion(object): 35 """ 36 Suggestion returned by an auto-suggest algorithm. 37 38 :param text: The suggestion text. 39 """ 40 def __init__(self, text): 41 self.text = text 42 43 def __repr__(self): 44 return 'Suggestion(%s)' % self.text 45 46 47class AutoSuggest(with_metaclass(ABCMeta, object)): 48 """ 49 Base class for auto suggestion implementations. 50 """ 51 @abstractmethod 52 def get_suggestion(self, buffer, document): 53 """ 54 Return `None` or a :class:`.Suggestion` instance. 55 56 We receive both :class:`~prompt_toolkit.buffer.Buffer` and 57 :class:`~prompt_toolkit.document.Document`. The reason is that auto 58 suggestions are retrieved asynchronously. (Like completions.) The 59 buffer text could be changed in the meantime, but ``document`` contains 60 the buffer document like it was at the start of the auto suggestion 61 call. So, from here, don't access ``buffer.text``, but use 62 ``document.text`` instead. 63 64 :param buffer: The :class:`~prompt_toolkit.buffer.Buffer` instance. 65 :param document: The :class:`~prompt_toolkit.document.Document` instance. 66 """ 67 68 def get_suggestion_future(self, buff, document): 69 """ 70 Return a :class:`.Future` which is set when the suggestions are ready. 71 This function can be overloaded in order to provide an asynchronous 72 implementation. 73 """ 74 return Future.succeed(self.get_suggestion(buff, document)) 75 76 77class ThreadedAutoSuggest(AutoSuggest): 78 """ 79 Wrapper that runs auto suggestions in a thread. 80 (Use this to prevent the user interface from becoming unresponsive if the 81 generation of suggestions takes too much time.) 82 """ 83 def __init__(self, auto_suggest): 84 assert isinstance(auto_suggest, AutoSuggest) 85 self.auto_suggest = auto_suggest 86 87 def get_suggestion(self, buff, document): 88 return self.auto_suggest.get_suggestion(buff, document) 89 90 def get_suggestion_future(self, buff, document): 91 """ 92 Run the `get_suggestion` function in a thread. 93 """ 94 def run_get_suggestion_thread(): 95 return self.get_suggestion(buff, document) 96 f = run_in_executor(run_get_suggestion_thread) 97 return f 98 99 100class DummyAutoSuggest(AutoSuggest): 101 """ 102 AutoSuggest class that doesn't return any suggestion. 103 """ 104 def get_suggestion(self, buffer, document): 105 return # No suggestion 106 107 108class AutoSuggestFromHistory(AutoSuggest): 109 """ 110 Give suggestions based on the lines in the history. 111 """ 112 def get_suggestion(self, buffer, document): 113 history = buffer.history 114 115 # Consider only the last line for the suggestion. 116 text = document.text.rsplit('\n', 1)[-1] 117 118 # Only create a suggestion when this is not an empty line. 119 if text.strip(): 120 # Find first matching line in history. 121 for string in reversed(list(history.get_strings())): 122 for line in reversed(string.splitlines()): 123 if line.startswith(text): 124 return Suggestion(line[len(text):]) 125 126 127class ConditionalAutoSuggest(AutoSuggest): 128 """ 129 Auto suggest that can be turned on and of according to a certain condition. 130 """ 131 def __init__(self, auto_suggest, filter): 132 assert isinstance(auto_suggest, AutoSuggest) 133 134 self.auto_suggest = auto_suggest 135 self.filter = to_filter(filter) 136 137 def get_suggestion(self, buffer, document): 138 if self.filter(): 139 return self.auto_suggest.get_suggestion(buffer, document) 140 141 142class DynamicAutoSuggest(AutoSuggest): 143 """ 144 Validator class that can dynamically returns any Validator. 145 146 :param get_validator: Callable that returns a :class:`.Validator` instance. 147 """ 148 def __init__(self, get_auto_suggest): 149 assert callable(get_auto_suggest) 150 self.get_auto_suggest = get_auto_suggest 151 152 def get_suggestion(self, buff, document): 153 auto_suggest = self.get_auto_suggest() or DummyAutoSuggest() 154 assert isinstance(auto_suggest, AutoSuggest) 155 return auto_suggest.get_suggestion(buff, document) 156 157 def get_suggestion_future(self, buff, document): 158 auto_suggest = self.get_auto_suggest() or DummyAutoSuggest() 159 assert isinstance(auto_suggest, AutoSuggest) 160 return auto_suggest.get_suggestion_future(buff, document) 161