1# pylint: disable=function-redefined
2from __future__ import unicode_literals
3
4from prompt_toolkit.enums import DEFAULT_BUFFER
5from prompt_toolkit.filters import HasSelection, Condition, EmacsInsertMode, ViInsertMode
6from prompt_toolkit.keys import Keys
7from prompt_toolkit.layout.screen import Point
8from prompt_toolkit.mouse_events import MouseEventType, MouseEvent
9from prompt_toolkit.renderer import HeightIsUnknownError
10from prompt_toolkit.utils import suspend_to_background_supported, is_windows
11
12from .named_commands import get_by_name
13from ..registry import Registry
14
15
16__all__ = (
17    'load_basic_bindings',
18    'load_abort_and_exit_bindings',
19    'load_basic_system_bindings',
20    'load_auto_suggestion_bindings',
21)
22
23def if_no_repeat(event):
24    """ Callable that returns True when the previous event was delivered to
25    another handler. """
26    return not event.is_repeat
27
28
29def load_basic_bindings():
30    registry = Registry()
31    insert_mode = ViInsertMode() | EmacsInsertMode()
32    handle = registry.add_binding
33    has_selection = HasSelection()
34
35    @handle(Keys.ControlA)
36    @handle(Keys.ControlB)
37    @handle(Keys.ControlC)
38    @handle(Keys.ControlD)
39    @handle(Keys.ControlE)
40    @handle(Keys.ControlF)
41    @handle(Keys.ControlG)
42    @handle(Keys.ControlH)
43    @handle(Keys.ControlI)
44    @handle(Keys.ControlJ)
45    @handle(Keys.ControlK)
46    @handle(Keys.ControlL)
47    @handle(Keys.ControlM)
48    @handle(Keys.ControlN)
49    @handle(Keys.ControlO)
50    @handle(Keys.ControlP)
51    @handle(Keys.ControlQ)
52    @handle(Keys.ControlR)
53    @handle(Keys.ControlS)
54    @handle(Keys.ControlT)
55    @handle(Keys.ControlU)
56    @handle(Keys.ControlV)
57    @handle(Keys.ControlW)
58    @handle(Keys.ControlX)
59    @handle(Keys.ControlY)
60    @handle(Keys.ControlZ)
61    @handle(Keys.F1)
62    @handle(Keys.F2)
63    @handle(Keys.F3)
64    @handle(Keys.F4)
65    @handle(Keys.F5)
66    @handle(Keys.F6)
67    @handle(Keys.F7)
68    @handle(Keys.F8)
69    @handle(Keys.F9)
70    @handle(Keys.F10)
71    @handle(Keys.F11)
72    @handle(Keys.F12)
73    @handle(Keys.F13)
74    @handle(Keys.F14)
75    @handle(Keys.F15)
76    @handle(Keys.F16)
77    @handle(Keys.F17)
78    @handle(Keys.F18)
79    @handle(Keys.F19)
80    @handle(Keys.F20)
81    @handle(Keys.ControlSpace)
82    @handle(Keys.ControlBackslash)
83    @handle(Keys.ControlSquareClose)
84    @handle(Keys.ControlCircumflex)
85    @handle(Keys.ControlUnderscore)
86    @handle(Keys.Backspace)
87    @handle(Keys.Up)
88    @handle(Keys.Down)
89    @handle(Keys.Right)
90    @handle(Keys.Left)
91    @handle(Keys.ShiftUp)
92    @handle(Keys.ShiftDown)
93    @handle(Keys.ShiftRight)
94    @handle(Keys.ShiftLeft)
95    @handle(Keys.Home)
96    @handle(Keys.End)
97    @handle(Keys.Delete)
98    @handle(Keys.ShiftDelete)
99    @handle(Keys.ControlDelete)
100    @handle(Keys.PageUp)
101    @handle(Keys.PageDown)
102    @handle(Keys.BackTab)
103    @handle(Keys.Tab)
104    @handle(Keys.ControlLeft)
105    @handle(Keys.ControlRight)
106    @handle(Keys.ControlUp)
107    @handle(Keys.ControlDown)
108    @handle(Keys.Insert)
109    @handle(Keys.Ignore)
110    def _(event):
111        """
112        First, for any of these keys, Don't do anything by default. Also don't
113        catch them in the 'Any' handler which will insert them as data.
114
115        If people want to insert these characters as a literal, they can always
116        do by doing a quoted insert. (ControlQ in emacs mode, ControlV in Vi
117        mode.)
118        """
119        pass
120
121    # Readline-style bindings.
122    handle(Keys.Home)(get_by_name('beginning-of-line'))
123    handle(Keys.End)(get_by_name('end-of-line'))
124    handle(Keys.Left)(get_by_name('backward-char'))
125    handle(Keys.Right)(get_by_name('forward-char'))
126    handle(Keys.ControlUp)(get_by_name('previous-history'))
127    handle(Keys.ControlDown)(get_by_name('next-history'))
128    handle(Keys.ControlL)(get_by_name('clear-screen'))
129
130    handle(Keys.ControlK, filter=insert_mode)(get_by_name('kill-line'))
131    handle(Keys.ControlU, filter=insert_mode)(get_by_name('unix-line-discard'))
132    handle(Keys.ControlH, filter=insert_mode, save_before=if_no_repeat)(
133        get_by_name('backward-delete-char'))
134    handle(Keys.Backspace, filter=insert_mode, save_before=if_no_repeat)(
135        get_by_name('backward-delete-char'))
136    handle(Keys.Delete, filter=insert_mode, save_before=if_no_repeat)(
137        get_by_name('delete-char'))
138    handle(Keys.ShiftDelete, filter=insert_mode, save_before=if_no_repeat)(
139        get_by_name('delete-char'))
140    handle(Keys.Any, filter=insert_mode, save_before=if_no_repeat)(
141        get_by_name('self-insert'))
142    handle(Keys.ControlT, filter=insert_mode)(get_by_name('transpose-chars'))
143    handle(Keys.ControlW, filter=insert_mode)(get_by_name('unix-word-rubout'))
144    handle(Keys.ControlI, filter=insert_mode)(get_by_name('menu-complete'))
145    handle(Keys.BackTab, filter=insert_mode)(get_by_name('menu-complete-backward'))
146
147    handle(Keys.PageUp, filter= ~has_selection)(get_by_name('previous-history'))
148    handle(Keys.PageDown, filter= ~has_selection)(get_by_name('next-history'))
149
150    # CTRL keys.
151
152    text_before_cursor = Condition(lambda cli: cli.current_buffer.text)
153    handle(Keys.ControlD, filter=text_before_cursor & insert_mode)(get_by_name('delete-char'))
154
155    is_multiline = Condition(lambda cli: cli.current_buffer.is_multiline())
156    is_returnable = Condition(lambda cli: cli.current_buffer.accept_action.is_returnable)
157
158    @handle(Keys.ControlJ, filter=is_multiline & insert_mode)
159    def _(event):
160        " Newline (in case of multiline input. "
161        event.current_buffer.newline(copy_margin=not event.cli.in_paste_mode)
162
163    @handle(Keys.ControlJ, filter=~is_multiline & is_returnable)
164    def _(event):
165        " Enter, accept input. "
166        buff = event.current_buffer
167        buff.accept_action.validate_and_handle(event.cli, buff)
168
169    # Delete the word before the cursor.
170
171    @handle(Keys.Up)
172    def _(event):
173        event.current_buffer.auto_up(count=event.arg)
174
175    @handle(Keys.Down)
176    def _(event):
177        event.current_buffer.auto_down(count=event.arg)
178
179    @handle(Keys.Delete, filter=has_selection)
180    def _(event):
181        data = event.current_buffer.cut_selection()
182        event.cli.clipboard.set_data(data)
183
184    # Global bindings.
185
186    @handle(Keys.ControlZ)
187    def _(event):
188        """
189        By default, control-Z should literally insert Ctrl-Z.
190        (Ansi Ctrl-Z, code 26 in MSDOS means End-Of-File.
191        In a Python REPL for instance, it's possible to type
192        Control-Z followed by enter to quit.)
193
194        When the system bindings are loaded and suspend-to-background is
195        supported, that will override this binding.
196        """
197        event.current_buffer.insert_text(event.data)
198
199    @handle(Keys.CPRResponse, save_before=lambda e: False)
200    def _(event):
201        """
202        Handle incoming Cursor-Position-Request response.
203        """
204        # The incoming data looks like u'\x1b[35;1R'
205        # Parse row/col information.
206        row, col = map(int, event.data[2:-1].split(';'))
207
208        # Report absolute cursor position to the renderer.
209        event.cli.renderer.report_absolute_cursor_row(row)
210
211    @handle(Keys.BracketedPaste)
212    def _(event):
213        " Pasting from clipboard. "
214        data = event.data
215
216        # Be sure to use \n as line ending.
217        # Some terminals (Like iTerm2) seem to paste \r\n line endings in a
218        # bracketed paste. See: https://github.com/ipython/ipython/issues/9737
219        data = data.replace('\r\n', '\n')
220        data = data.replace('\r', '\n')
221
222        event.current_buffer.insert_text(data)
223
224    @handle(Keys.Any, filter=Condition(lambda cli: cli.quoted_insert), eager=True)
225    def _(event):
226        """
227        Handle quoted insert.
228        """
229        event.current_buffer.insert_text(event.data, overwrite=False)
230        event.cli.quoted_insert = False
231
232    return registry
233
234
235def load_mouse_bindings():
236    """
237    Key bindings, required for mouse support.
238    (Mouse events enter through the key binding system.)
239    """
240    registry = Registry()
241
242    @registry.add_binding(Keys.Vt100MouseEvent)
243    def _(event):
244        """
245        Handling of incoming mouse event.
246        """
247        # Typical:   "Esc[MaB*"
248        # Urxvt:     "Esc[96;14;13M"
249        # Xterm SGR: "Esc[<64;85;12M"
250
251        # Parse incoming packet.
252        if event.data[2] == 'M':
253            # Typical.
254            mouse_event, x, y = map(ord, event.data[3:])
255            mouse_event = {
256                32: MouseEventType.MOUSE_DOWN,
257                35: MouseEventType.MOUSE_UP,
258                96: MouseEventType.SCROLL_UP,
259                97: MouseEventType.SCROLL_DOWN,
260            }.get(mouse_event)
261
262            # Handle situations where `PosixStdinReader` used surrogateescapes.
263            if x >= 0xdc00: x-= 0xdc00
264            if y >= 0xdc00: y-= 0xdc00
265
266            x -= 32
267            y -= 32
268        else:
269            # Urxvt and Xterm SGR.
270            # When the '<' is not present, we are not using the Xterm SGR mode,
271            # but Urxvt instead.
272            data = event.data[2:]
273            if data[:1] == '<':
274                sgr = True
275                data = data[1:]
276            else:
277                sgr = False
278
279            # Extract coordinates.
280            mouse_event, x, y = map(int, data[:-1].split(';'))
281            m = data[-1]
282
283            # Parse event type.
284            if sgr:
285                mouse_event = {
286                    (0, 'M'): MouseEventType.MOUSE_DOWN,
287                    (0, 'm'): MouseEventType.MOUSE_UP,
288                    (64, 'M'): MouseEventType.SCROLL_UP,
289                    (65, 'M'): MouseEventType.SCROLL_DOWN,
290                }.get((mouse_event, m))
291            else:
292                mouse_event = {
293                    32: MouseEventType.MOUSE_DOWN,
294                    35: MouseEventType.MOUSE_UP,
295                    96: MouseEventType.SCROLL_UP,
296                    97: MouseEventType.SCROLL_DOWN,
297                    }.get(mouse_event)
298
299        x -= 1
300        y -= 1
301
302        # Only handle mouse events when we know the window height.
303        if event.cli.renderer.height_is_known and mouse_event is not None:
304            # Take region above the layout into account. The reported
305            # coordinates are absolute to the visible part of the terminal.
306            try:
307                y -= event.cli.renderer.rows_above_layout
308            except HeightIsUnknownError:
309                return
310
311            # Call the mouse handler from the renderer.
312            handler = event.cli.renderer.mouse_handlers.mouse_handlers[x,y]
313            handler(event.cli, MouseEvent(position=Point(x=x, y=y),
314                                          event_type=mouse_event))
315
316    @registry.add_binding(Keys.WindowsMouseEvent)
317    def _(event):
318        """
319        Handling of mouse events for Windows.
320        """
321        assert is_windows()  # This key binding should only exist for Windows.
322
323        # Parse data.
324        event_type, x, y = event.data.split(';')
325        x = int(x)
326        y = int(y)
327
328        # Make coordinates absolute to the visible part of the terminal.
329        screen_buffer_info = event.cli.renderer.output.get_win32_screen_buffer_info()
330        rows_above_cursor = screen_buffer_info.dwCursorPosition.Y - event.cli.renderer._cursor_pos.y
331        y -= rows_above_cursor
332
333        # Call the mouse event handler.
334        handler = event.cli.renderer.mouse_handlers.mouse_handlers[x,y]
335        handler(event.cli, MouseEvent(position=Point(x=x, y=y),
336                                      event_type=event_type))
337
338    return registry
339
340
341def load_abort_and_exit_bindings():
342    """
343    Basic bindings for abort (Ctrl-C) and exit (Ctrl-D).
344    """
345    registry = Registry()
346    handle = registry.add_binding
347
348    @handle(Keys.ControlC)
349    def _(event):
350        " Abort when Control-C has been pressed. "
351        event.cli.abort()
352
353    @Condition
354    def ctrl_d_condition(cli):
355        """ Ctrl-D binding is only active when the default buffer is selected
356        and empty. """
357        return (cli.current_buffer_name == DEFAULT_BUFFER and
358                not cli.current_buffer.text)
359
360    handle(Keys.ControlD, filter=ctrl_d_condition)(get_by_name('end-of-file'))
361
362    return registry
363
364
365def load_basic_system_bindings():
366    """
367    Basic system bindings (For both Emacs and Vi mode.)
368    """
369    registry = Registry()
370
371    suspend_supported = Condition(
372        lambda cli: suspend_to_background_supported())
373
374    @registry.add_binding(Keys.ControlZ, filter=suspend_supported)
375    def _(event):
376        """
377        Suspend process to background.
378        """
379        event.cli.suspend_to_background()
380
381    return registry
382
383
384def load_auto_suggestion_bindings():
385    """
386    Key bindings for accepting auto suggestion text.
387    """
388    registry = Registry()
389    handle = registry.add_binding
390
391    suggestion_available = Condition(
392        lambda cli:
393            cli.current_buffer.suggestion is not None and
394            cli.current_buffer.document.is_cursor_at_the_end)
395
396    @handle(Keys.ControlF, filter=suggestion_available)
397    @handle(Keys.ControlE, filter=suggestion_available)
398    @handle(Keys.Right, filter=suggestion_available)
399    def _(event):
400        " Accept suggestion. "
401        b = event.current_buffer
402        suggestion = b.suggestion
403
404        if suggestion:
405            b.insert_text(suggestion.text)
406
407    return registry
408