ipython_view.py 23.4 KB
Newer Older
1
#!/usr/bin/python
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

# Copyright (C) 2008-2014 Yann Leboulanger <asterix AT lagaule.org>
#
# This file is part of Gajim.
#
# Gajim is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
#
# Copyright (c) 2007, IBM Corporation
# All rights reserved.
roidelapluie's avatar
roidelapluie committed
21

Daniel Brötzmann's avatar
Daniel Brötzmann committed
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:

# * Redistributions of source code must retain the above copyright notice,
#   this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
# * Neither the name of the IBM Corporation nor the names of its contributors
#   may be used to endorse or promote products derived from this software
#   without specific prior written permission.

# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
roidelapluie's avatar
roidelapluie committed
45

46 47
"""
Provides IPython console widget
48 49 50 51 52 53

@author: Eitan Isaacson
@organization: IBM Corporation
@copyright: Copyright (c) 2007 IBM Corporation
@license: BSD

54 55
All rights reserved. This program and the accompanying materials are made
available under the terms of the BSD which accompanies this distribution, and
56
is available at U{http://www.opensource.org/licenses/bsd-license.php}
57
"""
58

59 60 61 62
import re
import sys
import os
from io import StringIO
63
from functools import reduce
Daniel Brötzmann's avatar
Daniel Brötzmann committed
64
from pkg_resources import parse_version
65

66
from gi.repository import Gtk
67
from gi.repository import Gdk
68
from gi.repository import GObject
Yann Leboulanger's avatar
Yann Leboulanger committed
69
from gi.repository import GLib
70
from gi.repository import Pango
71

72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
import IPython

from pygments.token import Token
# pylint: disable=ungrouped-imports
from IPython.core.displayhook import DisplayHook
from IPython.core.display_trap import DisplayTrap
# pylint: enable=ungrouped-imports


class MyPromptDisplayHook(DisplayHook):
    def __init__(self, shell, view):
        DisplayHook.__init__(self, shell=shell)
        self.view = view

    def write_output_prompt(self):
        tokens = self.shell.prompts.out_prompt_tokens()
        self.view.write('\n')
        self.view.write(tokens)
Yann Leboulanger's avatar
Yann Leboulanger committed
90

Daniel Brötzmann's avatar
Daniel Brötzmann committed
91

92
class IterableIPShell:
93
    """
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
    Create an IPython instance. Does not start a blocking event loop,
    instead allow single iterations. This allows embedding in GTK+
    without blockage

    @ivar IP: IPython instance.
    @type IP: IPython.iplib.InteractiveShell
    @ivar iter_more: Indicates if the line executed was a complete command,
    or we should wait for more.
    @type iter_more: integer
    @ivar history_level: The place in history where we currently are
    when pressing up/down.
    @type history_level: integer
    @ivar complete_sep: Seperation delimeters for completion function.
    @type complete_sep: _sre.SRE_Pattern
    """
109 110
    def __init__(self, argv=None, user_ns=None, user_global_ns=None, cin=None,
                 cout=None, cerr=None, input_func=None):
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
        """
        @param argv: Command line options for IPython
        @type argv: list
        @param user_ns: User namespace.
        @type user_ns: dictionary
        @param user_global_ns: User global namespace.
        @type user_global_ns: dictionary.
        @param cin: Console standard input.
        @type cin: IO stream
        @param cout: Console standard output.
        @type cout: IO stream
        @param cerr: Console standard error.
        @type cerr: IO stream
        @param input_func: Replacement for builtin raw_input()
        @type input_func: function
        """
127 128 129
        if argv is None:
            argv = []

Yann Leboulanger's avatar
Yann Leboulanger committed
130
        io = IPython.utils.io
131
        if input_func:
132 133
            IPython.terminal.interactiveshell.raw_input_original = input_func

134 135 136

        # This is to get rid of the blockage that accurs during
        # IPython.Shell.InteractiveShell.user_setup()
Yann Leboulanger's avatar
Yann Leboulanger committed
137
        io.raw_input = lambda x: None
138 139 140

        os.environ['TERM'] = 'dumb'
        excepthook = sys.excepthook
141

142
        from traitlets.config.loader import Config
Yann Leboulanger's avatar
Yann Leboulanger committed
143

144 145 146
        cfg = Config()
        cfg.InteractiveShell.colors = "Linux"

147 148
        # InteractiveShell's __init__ gets a reference of stdout and stderr
        # so we save the standard here to revert it after init
Yann Leboulanger's avatar
Yann Leboulanger committed
149
        old_stdout, old_stderr = sys.stdout, sys.stderr
150
        sys.stdout, sys.stderr = cout, cerr
Yann Leboulanger's avatar
Yann Leboulanger committed
151 152

        # InteractiveShell inherits from SingletonConfigurable so use instance()
153 154
        self.IP = IPython.terminal.embed.InteractiveShellEmbed.instance(
            config=cfg, user_ns=user_ns, user_module=user_global_ns)
Yann Leboulanger's avatar
Yann Leboulanger committed
155

156
        # Set back stdout and stderr to what it was before
Yann Leboulanger's avatar
Yann Leboulanger committed
157 158
        sys.stdout, sys.stderr = old_stdout, old_stderr

159 160
        self.IP.system = lambda cmd: self.shell(self.IP.var_expand(cmd),
                                                header='IPython system call: ',
161 162 163
                                                local_ns=user_ns)

        self.IP.raw_input = input_func
164 165 166
        sys.excepthook = excepthook
        self.iter_more = 0
        self.history_level = 0
167
        self.complete_sep = re.compile(r'[\s\{\}\[\]\(\)]')
Daniel Brötzmann's avatar
Daniel Brötzmann committed
168 169 170
        self.updateNamespace({'exit': lambda: None})
        self.updateNamespace({'quit': lambda: None})

171 172 173
        # Workaround for updating namespace with sys.modules
        #
        self.__update_namespace()
174

Daniel Brötzmann's avatar
Daniel Brötzmann committed
175 176 177 178 179 180 181
        # Avoid using input splitter when not really needed.
        # Perhaps it could work even before 5.8.0
        # But it definitely does not work any more with >= 7.0.0
        self.no_input_splitter = parse_version(IPython.release.version) >= \
            parse_version('5.8.0')
        self.lines = []

182 183 184 185
    def __update_namespace(self):
        '''
        Update self.IP namespace for autocompletion with sys.modules
        '''
186
        for k, v in sys.modules.items():
Daniel Brötzmann's avatar
Daniel Brötzmann committed
187 188
            if '.' not in k:
                self.IP.user_ns.update({k: v})
189 190 191 192 193 194 195

    def execute(self):
        """
        Execute the current line provided by the shell object
        """
        self.history_level = 0
        orig_stdout = sys.stdout
Yann Leboulanger's avatar
Yann Leboulanger committed
196
        sys.stdout = IPython.utils.io.stdout
197 198

        orig_stdin = sys.stdin
199
        sys.stdin = IPython.utils.io.stdin
200 201 202 203 204 205
        self.prompt = self.generatePrompt(self.iter_more)

        self.IP.hooks.pre_prompt_hook()
        if self.iter_more:
            try:
                self.prompt = self.generatePrompt(True)
206
            except Exception:
207
                self.IP.showtraceback()
208
            if self.IP.autoindent:
209 210 211 212
                self.IP.rl_do_indent = True

        try:
            line = self.IP.raw_input(self.prompt)
213 214
        except KeyboardInterrupt:
            self.IP.write('\nKeyboardInterrupt\n')
215
            self.IP.input_splitter.reset()
216
        except Exception:
217 218
            self.IP.showtraceback()
        else:
Daniel Brötzmann's avatar
Daniel Brötzmann committed
219 220 221 222 223 224 225 226
            if self.no_input_splitter:
                self.lines.append(self.IP.raw_input(self.prompt))
                self.iter_more = self.IP.check_complete(
                    '\n'.join(self.lines))[0] == 'incomplete'
            else:
                self.IP.input_splitter.push(line)
                self.iter_more = self.IP.input_splitter.push_accepts_more()

227 228
            self.prompt = self.generatePrompt(self.iter_more)
            if not self.iter_more:
Daniel Brötzmann's avatar
Daniel Brötzmann committed
229 230 231
                if self.no_input_splitter:
                    source_raw = '\n'.join(self.lines)
                    self.lines = []
Yann Leboulanger's avatar
Yann Leboulanger committed
232
                else:
233 234
                    source_raw = self.IP.input_splitter.raw_reset()

235
                self.IP.run_cell(source_raw, store_history=True)
Yann Leboulanger's avatar
Yann Leboulanger committed
236
                self.IP.rl_do_indent = False
237 238 239
            else:
                # TODO: Auto-indent
                #
Yann Leboulanger's avatar
Yann Leboulanger committed
240
                self.IP.rl_do_indent = True
241

242
        sys.stdout = orig_stdout
243
        sys.stdin = orig_stdin
Yann Leboulanger's avatar
Yann Leboulanger committed
244

245 246 247 248 249
    def generatePrompt(self, is_continuation):
        '''
        Generate prompt depending on is_continuation value

        @param is_continuation
250
        @type is_continuation: boolean
251 252 253 254 255

        @return: The prompt string representation
        @rtype: string

        '''
256 257
        # TODO: Update to IPython 5.x and later
        prompt = "In [%d]: " % self.IP.execution_count
258 259

        return prompt
260 261 262 263

    def historyBack(self):
        """
        Provide one history command back
264

265 266 267 268 269
        @return: The command string.
        @rtype: string
        """
        self.history_level -= 1
        return self._getHistory()
270

271 272 273
    def historyForward(self):
        """
        Provide one history command forward
274

275 276 277 278 279
        @return: The command string.
        @rtype: string
        """
        self.history_level += 1
        return self._getHistory()
280

281 282 283
    def _getHistory(self):
        """
        Get the command string of the current history level
284

285 286 287 288 289 290 291 292 293 294 295 296 297
        @return: Historic command string.
        @rtype: string
        """
        try:
            rv = self.IP.user_ns['In'][self.history_level].strip('\n')
        except IndexError:
            self.history_level = 0
            rv = ''
        return rv

    def updateNamespace(self, ns_dict):
        """
        Add the current dictionary to the shell namespace
298

299 300 301 302
        @param ns_dict: A dictionary of symbol-values.
        @type ns_dict: dictionary
        """
        self.IP.user_ns.update(ns_dict)
303

304
    def complete(self, line):
305
        """
Alexander Krotov's avatar
Alexander Krotov committed
306
        Returns an auto completed line and/or possibilities for completion
307

308 309
        @param line: Given line so far.
        @type line: string
310

311 312 313 314 315
        @return: Line completed as for as possible,
        and possible further completions.
        @rtype: tuple
        """
        split_line = self.complete_sep.split(line)
316 317 318 319
        if split_line[-1]:
            possibilities = self.IP.complete(split_line[-1])
        else:
            completed = line
320
            possibilities = ['', []]
321
        if possibilities:
322 323 324 325 326 327 328 329 330 331 332 333 334
            def _commonPrefix(str1, str2):
                '''
                Reduction function. returns common prefix of two given strings.

                @param str1: First string.
                @type str1: string
                @param str2: Second string
                @type str2: string

                @return: Common prefix to both strings.
                @rtype: string
                '''
                for i in range(len(str1)):
335
                    if not str2.startswith(str1[:i + 1]):
336 337 338 339
                        return str1[:i]
                return str1

            if possibilities[1]:
Daniel Brötzmann's avatar
Daniel Brötzmann committed
340 341 342
                common_prefix = \
                    reduce(_commonPrefix, possibilities[1]) or line[-1]
                completed = line[:-len(split_line[-1])] + common_prefix
343 344
            else:
                completed = line
345 346
        else:
            completed = line
347
        return completed, possibilities[1]
348 349


350
class ConsoleView(Gtk.TextView):
351
    """
352
    Specialized text view for console-like workflow
353

354 355
    @cvar ANSI_COLORS: Mapping of terminal colors to X11 names.
    @type ANSI_COLORS: dictionary
356

357
    @ivar text_buffer: Widget's text buffer.
358
    @type text_buffer: Gtk.TextBuffer
359 360 361
    @ivar color_pat: Regex of terminal color pattern
    @type color_pat: _sre.SRE_Pattern
    @ivar mark: Scroll mark for automatic scrolling on input.
362
    @type mark: Gtk.TextMark
363
    @ivar line_start: Start of command line mark.
364
    @type line_start: Gtk.TextMark
365
    """
366

367 368 369 370 371 372 373 374
    ANSI_COLORS = {'0;30': 'Black', '0;31': 'Red',
                   '0;32': 'Green', '0;33': 'Brown',
                   '0;34': 'Blue', '0;35': 'Purple',
                   '0;36': 'Cyan', '0;37': 'LightGray',
                   '1;30': 'DarkGray', '1;31': 'DarkRed',
                   '1;32': 'SeaGreen', '1;33': 'Yellow',
                   '1;34': 'LightBlue', '1;35': 'MediumPurple',
                   '1;36': 'LightCyan', '1;37': 'White'}
375

376 377 378 379
    def __init__(self):
        """
        Initialize console view
        """
380
        GObject.GObject.__init__(self)
381
        self.override_font(Pango.FontDescription('Mono'))
382 383
        self.set_cursor_visible(True)
        self.text_buffer = self.get_buffer()
Daniel Brötzmann's avatar
Daniel Brötzmann committed
384 385 386 387
        self.mark = self.text_buffer.create_mark(
            'scroll_mark',
            self.text_buffer.get_end_iter(),
            False)
388 389 390 391 392 393
        for code in self.ANSI_COLORS:
            self.text_buffer.create_tag(code,
                                        foreground=self.ANSI_COLORS[code],
                                        weight=700)
        self.text_buffer.create_tag('0')
        self.text_buffer.create_tag('notouch', editable=False)
394
        self.color_pat = re.compile(r'\x01?\x1b\[(.*?)m\x02?')
395 396 397 398 399 400 401 402

        self.style_dict = {
            Token.Prompt: '0;32',
            Token.PromptNum: '1;32',
            Token.OutPrompt: '0;31',
            Token.OutPromptNum: '1;31',
        }

403 404
        self.line_start = \
            self.text_buffer.create_mark('line_start',
Daniel Brötzmann's avatar
Daniel Brötzmann committed
405 406
                                         self.text_buffer.get_end_iter(),
                                         True)
407 408 409
        self.connect('key-press-event', self.onKeyPress)

    def write(self, text, editable=False):
410
        if isinstance(text, str):
Yann Leboulanger's avatar
Yann Leboulanger committed
411
            GLib.idle_add(self._write, text, editable)
412
        else:
Yann Leboulanger's avatar
Yann Leboulanger committed
413 414 415 416 417 418 419 420 421 422 423
            GLib.idle_add(self._write5, text, editable)

    def _write5(self, text, editable=False):
        """
        Write given text to buffer

        @param text: Text to append.
        @type text: list of (token: string)
        @param editable: If true, added text is editable.
        @type editable: boolean
        """
Daniel Brötzmann's avatar
Daniel Brötzmann committed
424 425
        start_mark = self.text_buffer.create_mark(
            None, self.text_buffer.get_end_iter(), True)
Yann Leboulanger's avatar
Yann Leboulanger committed
426 427 428

        for token, segment in text:
            tag = self.style_dict[token]
Daniel Brötzmann's avatar
Daniel Brötzmann committed
429 430
            self.text_buffer.insert_with_tags_by_name(
                self.text_buffer.get_end_iter(), segment, tag)
Yann Leboulanger's avatar
Yann Leboulanger committed
431
        if not editable:
Daniel Brötzmann's avatar
Daniel Brötzmann committed
432 433 434 435
            self.text_buffer.apply_tag_by_name(
                'notouch',
                self.text_buffer.get_iter_at_mark(start_mark),
                self.text_buffer.get_end_iter())
Yann Leboulanger's avatar
Yann Leboulanger committed
436 437
        self.text_buffer.delete_mark(start_mark)
        self.scroll_mark_onscreen(self.mark)
438 439 440 441

    def _write(self, text, editable=False):
        """
        Write given text to buffer
442

443 444 445 446 447
        @param text: Text to append.
        @type text: string
        @param editable: If true, added text is editable.
        @type editable: boolean
        """
448
        if isinstance(text, list):
Yann Leboulanger's avatar
Yann Leboulanger committed
449 450
            self._write5(text, editable)
            return
451 452
        segments = self.color_pat.split(text)
        segment = segments.pop(0)
Daniel Brötzmann's avatar
Daniel Brötzmann committed
453 454
        start_mark = self.text_buffer.create_mark(
            None, self.text_buffer.get_end_iter(), True)
455 456 457 458 459 460
        self.text_buffer.insert(self.text_buffer.get_end_iter(), segment)

        if segments:
            ansi_tags = self.color_pat.findall(text)
            for tag in ansi_tags:
                i = segments.index(tag)
Daniel Brötzmann's avatar
Daniel Brötzmann committed
461
                self.text_buffer.insert_with_tags_by_name(
462
                    self.text_buffer.get_end_iter(), segments[i + 1], str(tag))
463 464
                segments.pop(i)
        if not editable:
Daniel Brötzmann's avatar
Daniel Brötzmann committed
465 466 467 468 469
            self.text_buffer.apply_tag_by_name(
                'notouch',
                self.text_buffer.get_iter_at_mark(start_mark),
                self.text_buffer.get_end_iter())

470 471 472 473
        self.text_buffer.delete_mark(start_mark)
        self.scroll_mark_onscreen(self.mark)

    def showPrompt(self, prompt):
Yann Leboulanger's avatar
Yann Leboulanger committed
474
        GLib.idle_add(self._showPrompt, prompt)
475 476 477 478

    def _showPrompt(self, prompt):
        """
        Print prompt at start of line
479

480 481 482 483 484 485
        @param prompt: Prompt to print.
        @type prompt: string
        """
        self._write(prompt)
        self.text_buffer.move_mark(self.line_start,
                                   self.text_buffer.get_end_iter())
486

487
    def changeLine(self, text):
Yann Leboulanger's avatar
Yann Leboulanger committed
488
        GLib.idle_add(self._changeLine, text)
489

490 491 492
    def _changeLine(self, text):
        """
        Replace currently entered command line with given text
493

494 495 496 497 498
        @param text: Text to use as replacement.
        @type text: string
        """
        iter_ = self.text_buffer.get_iter_at_mark(self.line_start)
        iter_.forward_to_line_end()
Daniel Brötzmann's avatar
Daniel Brötzmann committed
499 500
        self.text_buffer.delete(
            self.text_buffer.get_iter_at_mark(self.line_start), iter_)
501
        self._write(text, True)
502

503 504 505
    def getCurrentLine(self):
        """
        Get text in current command line
506

507 508 509 510
        @return: Text of current command line.
        @rtype: string
        """
        rv = self.text_buffer.get_slice(
511 512
            self.text_buffer.get_iter_at_mark(self.line_start),
            self.text_buffer.get_end_iter(), False)
513
        return rv
514

515
    def showReturned(self, text):
Yann Leboulanger's avatar
Yann Leboulanger committed
516
        GLib.idle_add(self._showReturned, text)
517 518 519 520 521 522 523 524 525 526 527

    def _showReturned(self, text):
        """
        Show returned text from last command and print new prompt

        @param text: Text to show.
        @type text: string
        """
        iter_ = self.text_buffer.get_iter_at_mark(self.line_start)
        iter_.forward_to_line_end()
        self.text_buffer.apply_tag_by_name(
528 529 530
            'notouch',
            self.text_buffer.get_iter_at_mark(self.line_start),
            iter_)
531
        self._write('\n' + text)
532 533 534
        if text:
            self._write('\n')
        self._showPrompt(self.prompt)
Daniel Brötzmann's avatar
Daniel Brötzmann committed
535 536
        self.text_buffer.move_mark(
            self.line_start, self.text_buffer.get_end_iter())
537 538
        self.text_buffer.place_cursor(self.text_buffer.get_end_iter())

Yann Leboulanger's avatar
Yann Leboulanger committed
539 540 541 542
        if self.IP.rl_do_indent:
            indentation = self.IP.input_splitter.indent_spaces * ' '
            self.text_buffer.insert_at_cursor(indentation)

543
    def onKeyPress(self, _widget, event):
544 545
        """
        Key press callback used for correcting behavior for console-like
Alexander Krotov's avatar
Alexander Krotov committed
546
        interfaces. For example 'home' should go to prompt, not to beginning of
547 548
        line

Alexander Krotov's avatar
Alexander Krotov committed
549
        @param widget: Widget that key press occurred in.
550
        @type widget: Gtk.Widget
551
        @param event: Event object
552
        @type event: Gdk.Event
553 554 555 556 557 558 559 560 561

        @return: Return True if event should not trickle.
        @rtype: boolean
        """
        insert_mark = self.text_buffer.get_insert()
        insert_iter = self.text_buffer.get_iter_at_mark(insert_mark)
        selection_mark = self.text_buffer.get_selection_bound()
        selection_iter = self.text_buffer.get_iter_at_mark(selection_mark)
        start_iter = self.text_buffer.get_iter_at_mark(self.line_start)
562 563
        if event.keyval == Gdk.KEY_Home:
            if event.get_state() == 0:
564 565
                self.text_buffer.place_cursor(start_iter)
                return True
566
            if event.get_state() == Gdk.ModifierType.SHIFT_MASK:
567 568
                self.text_buffer.move_mark(insert_mark, start_iter)
                return True
569 570

        if event.keyval == Gdk.KEY_Left:
571 572 573 574 575
            insert_iter.backward_cursor_position()
            if not insert_iter.editable(True):
                return True
        elif not event.string:
            pass
576 577
        elif (start_iter.compare(insert_iter) <= 0 and
                start_iter.compare(selection_iter) <= 0):
578
            pass
579 580
        elif (start_iter.compare(insert_iter) > 0 and
                start_iter.compare(selection_iter) > 0):
581 582 583 584 585 586 587 588 589 590 591 592
            self.text_buffer.place_cursor(start_iter)
        elif insert_iter.compare(selection_iter) < 0:
            self.text_buffer.move_mark(insert_mark, start_iter)
        elif insert_iter.compare(selection_iter) > 0:
            self.text_buffer.move_mark(selection_mark, start_iter)

        return self.onKeyPressExtend(event)

    def onKeyPressExtend(self, event):
        """
        For some reason we can't extend onKeyPress directly (bug #500900)
        """
593

Daniel Brötzmann's avatar
Daniel Brötzmann committed
594

595
class IPythonView(ConsoleView, IterableIPShell):
596 597 598 599 600 601 602 603 604 605
    '''
    Sub-class of both modified IPython shell and L{ConsoleView} this makes
    a GTK+ IPython console.
    '''
    def __init__(self):
        """
        Initialize. Redirect I/O to console
        """
        ConsoleView.__init__(self)
        self.cout = StringIO()
606
        IterableIPShell.__init__(self, cout=self.cout, cerr=self.cout,
607
                                 input_func=self.raw_input)
608 609 610 611

        displayhook = MyPromptDisplayHook(shell=self.IP, view=self)
        self.IP.displayhook = displayhook
        self.IP.display_trap = DisplayTrap(hook=displayhook)
612

Yann Leboulanger's avatar
Yann Leboulanger committed
613
        self.interrupt = False
614
        self.execute()
615
        self.prompt = self.generatePrompt(False)
616 617
        self.cout.truncate(0)
        self.showPrompt(self.prompt)
618

Yann Leboulanger's avatar
Yann Leboulanger committed
619 620 621 622
    def prompt_for_code(self):
        # IPython 5.0 calls prompt_for_code instead of raw_input
        return self.raw_input(self)

623
    def raw_input(self, _prompt=''):
624 625
        """
        Custom raw_input() replacement. Get's current line from console buffer
626

Alexander Krotov's avatar
Alexander Krotov committed
627
        @param prompt: Prompt to print. Here for compatibility as replacement.
628
        @type prompt: string
629

630 631 632 633 634 635 636
        @return: The current command line text.
        @rtype: string
        """
        if self.interrupt:
            self.interrupt = False
            raise KeyboardInterrupt
        return self.getCurrentLine()
637

638 639 640 641
    def onKeyPressExtend(self, event):
        """
        Key press callback with plenty of shell goodness, like history,
        autocompletions, etc
642

Alexander Krotov's avatar
Alexander Krotov committed
643
        @param widget: Widget that key press occurred in.
644
        @type widget: Gtk.Widget
645
        @param event: Event object.
646
        @type event: Gdk.Event
647

648 649 650
        @return: True if event should not trickle.
        @rtype: boolean
        """
651 652
        if (event.get_state() & Gdk.ModifierType.CONTROL_MASK and
                event.keyval == 99):
653 654 655
            self.interrupt = True
            self._processLine()
            return True
656
        if event.keyval == Gdk.KEY_Return:
657 658
            self._processLine()
            return True
659
        if event.keyval == Gdk.KEY_Up:
660 661
            self.changeLine(self.historyBack())
            return True
662
        if event.keyval == Gdk.KEY_Down:
663 664
            self.changeLine(self.historyForward())
            return True
665
        if event.keyval == Gdk.KEY_Tab:
666 667 668 669 670 671 672
            if not self.getCurrentLine().strip():
                return False
            completed, possibilities = self.complete(self.getCurrentLine())
            if len(possibilities) > 1:
                slice_ = self.getCurrentLine()
                self.write('\n')
                for symbol in possibilities:
673
                    self.write(symbol + '\n')
674 675 676
                self.showPrompt(self.prompt)
            self.changeLine(completed or slice_)
            return True
677

678 679 680 681 682 683 684
    def _processLine(self):
        """
        Process current command line
        """
        self.history_pos = 0
        self.execute()
        rv = self.cout.getvalue()
685 686
        if rv:
            rv = rv.strip('\n')
687 688
        self.showReturned(rv)
        self.cout.truncate(0)
Yann Leboulanger's avatar
Yann Leboulanger committed
689
        self.cout.seek(0)