From 80c619a1ec3e54084d05f1ee4d9669b5ab7df007 2015-04-10 02:26:12
From: Min RK '+text+u' '
- trs = lambda text : u''+text+u' '
- tds_items = [list(map(tds, row)) for row in item_matrix]
- if select :
- row, col = select
- tds_items[row][col] = u''\
- +item_matrix[row][col]\
- +u' '
- #select the right item
- html_cols = map(trs, (u''.join(row) for row in tds_items))
- head = ''
- foot = ''
- if header :
- head = (u''\
- +''.join((u' ')
-
- if footer :
- foot = (u''+header+u' ')*len(item_matrix[0]))\
- +''\
- +''.join((u' ')
- html = (u''+footer+u' ')*len(item_matrix[0]))\
- +''+head+(u''.join(html_cols))+foot+u'
')
- return html
-
-class SlidingInterval(object):
- """a bound interval that follows a cursor
-
- internally used to scoll the completion view when the cursor
- try to go beyond the edges, and show '...' when rows are hidden
- """
-
- _min = 0
- _max = 1
- _current = 0
- def __init__(self, maximum=1, width=6, minimum=0, sticky_lenght=1):
- """Create a new bounded interval
-
- any value return by this will be bound between maximum and
- minimum. usual width will be 'width', and sticky_length
- set when the return interval should expand to max and min
- """
- self._min = minimum
- self._max = maximum
- self._start = 0
- self._width = width
- self._stop = self._start+self._width+1
- self._sticky_lenght = sticky_lenght
-
- @property
- def current(self):
- """current cursor position"""
- return self._current
-
- @current.setter
- def current(self, value):
- """set current cursor position"""
- current = min(max(self._min, value), self._max)
-
- self._current = current
-
- if current > self._stop :
- self._stop = current
- self._start = current-self._width
- elif current < self._start :
- self._start = current
- self._stop = current + self._width
-
- if abs(self._start - self._min) <= self._sticky_lenght :
- self._start = self._min
-
- if abs(self._stop - self._max) <= self._sticky_lenght :
- self._stop = self._max
-
- @property
- def start(self):
- """begiiing of interval to show"""
- return self._start
-
- @property
- def stop(self):
- """end of interval to show"""
- return self._stop
-
- @property
- def width(self):
- return self._stop - self._start
-
- @property
- def nth(self):
- return self.current - self.start
-
-class CompletionHtml(QtGui.QWidget):
- """ A widget for tab completion, navigable by arrow keys """
-
- #--------------------------------------------------------------------------
- # 'QObject' interface
- #--------------------------------------------------------------------------
-
- _items = ()
- _index = (0, 0)
- _consecutive_tab = 0
- _size = (1, 1)
- _old_cursor = None
- _start_position = 0
- _slice_start = 0
- _slice_len = 4
-
- def __init__(self, console_widget):
- """ Create a completion widget that is attached to the specified Qt
- text edit widget.
- """
- assert isinstance(console_widget._control, (QtGui.QTextEdit, QtGui.QPlainTextEdit))
- super(CompletionHtml, self).__init__()
-
- self._text_edit = console_widget._control
- self._console_widget = console_widget
- self._text_edit.installEventFilter(self)
- self._sliding_interval = None
- self._justified_items = None
-
- # Ensure that the text edit keeps focus when widget is displayed.
- self.setFocusProxy(self._text_edit)
-
-
- def eventFilter(self, obj, event):
- """ Reimplemented to handle keyboard input and to auto-hide when the
- text edit loses focus.
- """
- if obj == self._text_edit:
- etype = event.type()
- if etype == QtCore.QEvent.KeyPress:
- key = event.key()
- if self._consecutive_tab == 0 and key in (QtCore.Qt.Key_Tab,):
- return False
- elif self._consecutive_tab == 1 and key in (QtCore.Qt.Key_Tab,):
- # ok , called twice, we grab focus, and show the cursor
- self._consecutive_tab = self._consecutive_tab+1
- self._update_list()
- return True
- elif self._consecutive_tab == 2:
- if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
- self._complete_current()
- return True
- if key in (QtCore.Qt.Key_Tab,):
- self.select_right()
- self._update_list()
- return True
- elif key in ( QtCore.Qt.Key_Down,):
- self.select_down()
- self._update_list()
- return True
- elif key in (QtCore.Qt.Key_Right,):
- self.select_right()
- self._update_list()
- return True
- elif key in ( QtCore.Qt.Key_Up,):
- self.select_up()
- self._update_list()
- return True
- elif key in ( QtCore.Qt.Key_Left,):
- self.select_left()
- self._update_list()
- return True
- elif key in ( QtCore.Qt.Key_Escape,):
- self.cancel_completion()
- return True
- else :
- self.cancel_completion()
- else:
- self.cancel_completion()
-
- elif etype == QtCore.QEvent.FocusOut:
- self.cancel_completion()
-
- return super(CompletionHtml, self).eventFilter(obj, event)
-
- #--------------------------------------------------------------------------
- # 'CompletionHtml' interface
- #--------------------------------------------------------------------------
- def cancel_completion(self):
- """Cancel the completion
-
- should be called when the completer have to be dismissed
-
- This reset internal variable, clearing the temporary buffer
- of the console where the completion are shown.
- """
- self._consecutive_tab = 0
- self._slice_start = 0
- self._console_widget._clear_temporary_buffer()
- self._index = (0, 0)
- if(self._sliding_interval):
- self._sliding_interval = None
-
- #
- # ... 2 4 4 4 4 4 4 4 4 4 4 4 4
- # 2 2 4 4 4 4 4 4 4 4 4 4 4 4
- #
- #2 2 x x x x x x x x x x x 5 5
- #6 6 x x x x x x x x x x x 5 5
- #6 6 x x x x x x x x x x ? 5 5
- #6 6 x x x x x x x x x x ? 1 1
- #
- #3 3 3 3 3 3 3 3 3 3 3 3 1 1 1 ...
- #3 3 3 3 3 3 3 3 3 3 3 3 1 1 1 ...
- def _select_index(self, row, col):
- """Change the selection index, and make sure it stays in the right range
-
- A little more complicated than just dooing modulo the number of row columns
- to be sure to cycle through all element.
-
- horizontaly, the element are maped like this :
- to r <-- a b c d e f --> to g
- to f <-- g h i j k l --> to m
- to l <-- m n o p q r --> to a
-
- and vertically
- a d g j m p
- b e h k n q
- c f i l o r
- """
-
- nr, nc = self._size
- nr = nr-1
- nc = nc-1
-
- # case 1
- if (row > nr and col >= nc) or (row >= nr and col > nc):
- self._select_index(0, 0)
- # case 2
- elif (row <= 0 and col < 0) or (row < 0 and col <= 0):
- self._select_index(nr, nc)
- # case 3
- elif row > nr :
- self._select_index(0, col+1)
- # case 4
- elif row < 0 :
- self._select_index(nr, col-1)
- # case 5
- elif col > nc :
- self._select_index(row+1, 0)
- # case 6
- elif col < 0 :
- self._select_index(row-1, nc)
- elif 0 <= row and row <= nr and 0 <= col and col <= nc :
- self._index = (row, col)
- else :
- raise NotImplementedError("you'r trying to go where no completion\
- have gone before : %d:%d (%d:%d)"%(row, col, nr, nc) )
-
-
- @property
- def _slice_end(self):
- end = self._slice_start+self._slice_len
- if end > len(self._items) :
- return None
- return end
-
- def select_up(self):
- """move cursor up"""
- r, c = self._index
- self._select_index(r-1, c)
-
- def select_down(self):
- """move cursor down"""
- r, c = self._index
- self._select_index(r+1, c)
-
- def select_left(self):
- """move cursor left"""
- r, c = self._index
- self._select_index(r, c-1)
-
- def select_right(self):
- """move cursor right"""
- r, c = self._index
- self._select_index(r, c+1)
-
- def show_items(self, cursor, items):
- """ Shows the completion widget with 'items' at the position specified
- by 'cursor'.
- """
- if not items :
- return
- self._start_position = cursor.position()
- self._consecutive_tab = 1
- items_m, ci = text.compute_item_matrix(items, empty=' ')
- self._sliding_interval = SlidingInterval(len(items_m)-1)
-
- self._items = items_m
- self._size = (ci['rows_numbers'], ci['columns_numbers'])
- self._old_cursor = cursor
- self._index = (0, 0)
- sjoin = lambda x : [ y.ljust(w, ' ') for y, w in zip(x, ci['columns_width'])]
- self._justified_items = list(map(sjoin, items_m))
- self._update_list(hilight=False)
-
-
-
-
- def _update_list(self, hilight=True):
- """ update the list of completion and hilight the currently selected completion """
- self._sliding_interval.current = self._index[0]
- head = None
- foot = None
- if self._sliding_interval.start > 0 :
- head = '...'
-
- if self._sliding_interval.stop < self._sliding_interval._max:
- foot = '...'
- items_m = self._justified_items[\
- self._sliding_interval.start:\
- self._sliding_interval.stop+1\
- ]
-
- self._console_widget._clear_temporary_buffer()
- if(hilight):
- sel = (self._sliding_interval.nth, self._index[1])
- else :
- sel = None
-
- strng = html_tableify(items_m, select=sel, header=head, footer=foot)
- self._console_widget._fill_temporary_buffer(self._old_cursor, strng, html=True)
-
- #--------------------------------------------------------------------------
- # Protected interface
- #--------------------------------------------------------------------------
-
- def _complete_current(self):
- """ Perform the completion with the currently selected item.
- """
- i = self._index
- item = self._items[i[0]][i[1]]
- item = item.strip()
- if item :
- self._current_text_cursor().insertText(item)
- self.cancel_completion()
-
- def _current_text_cursor(self):
- """ Returns a cursor with text between the start position and the
- current position selected.
- """
- cursor = self._text_edit.textCursor()
- if cursor.position() >= self._start_position:
- cursor.setPosition(self._start_position,
- QtGui.QTextCursor.KeepAnchor)
- return cursor
-
diff --git a/jupyter_qtconsole/console/completion_plain.py b/jupyter_qtconsole/console/completion_plain.py
deleted file mode 100644
index c1b09a0..0000000
--- a/jupyter_qtconsole/console/completion_plain.py
+++ /dev/null
@@ -1,62 +0,0 @@
-"""A simple completer for the qtconsole"""
-#-----------------------------------------------------------------------------
-# Copyright (c) 2012, IPython Development Team.$
-#
-# Distributed under the terms of the Modified BSD License.$
-#
-# The full license is in the file COPYING.txt, distributed with this software.
-#-------------------------------------------------------------------
-
-# System library imports
-from IPython.external.qt import QtCore, QtGui
-import IPython.utils.text as text
-
-
-class CompletionPlain(QtGui.QWidget):
- """ A widget for tab completion, navigable by arrow keys """
-
- #--------------------------------------------------------------------------
- # 'QObject' interface
- #--------------------------------------------------------------------------
-
- def __init__(self, console_widget):
- """ Create a completion widget that is attached to the specified Qt
- text edit widget.
- """
- assert isinstance(console_widget._control, (QtGui.QTextEdit, QtGui.QPlainTextEdit))
- super(CompletionPlain, self).__init__()
-
- self._text_edit = console_widget._control
- self._console_widget = console_widget
-
- self._text_edit.installEventFilter(self)
-
- def eventFilter(self, obj, event):
- """ Reimplemented to handle keyboard input and to auto-hide when the
- text edit loses focus.
- """
- if obj == self._text_edit:
- etype = event.type()
-
- if etype in( QtCore.QEvent.KeyPress, QtCore.QEvent.FocusOut ):
- self.cancel_completion()
-
- return super(CompletionPlain, self).eventFilter(obj, event)
-
- #--------------------------------------------------------------------------
- # 'CompletionPlain' interface
- #--------------------------------------------------------------------------
- def cancel_completion(self):
- """Cancel the completion, reseting internal variable, clearing buffer """
- self._console_widget._clear_temporary_buffer()
-
-
- def show_items(self, cursor, items):
- """ Shows the completion widget with 'items' at the position specified
- by 'cursor'.
- """
- if not items :
- return
- self.cancel_completion()
- strng = text.columnize(items)
- self._console_widget._fill_temporary_buffer(cursor, strng, html=False)
diff --git a/jupyter_qtconsole/console/completion_widget.py b/jupyter_qtconsole/console/completion_widget.py
deleted file mode 100644
index cf5a61d..0000000
--- a/jupyter_qtconsole/console/completion_widget.py
+++ /dev/null
@@ -1,139 +0,0 @@
-"""A dropdown completer widget for the qtconsole."""
-# System library imports
-from IPython.external.qt import QtCore, QtGui
-
-
-class CompletionWidget(QtGui.QListWidget):
- """ A widget for GUI tab completion.
- """
-
- #--------------------------------------------------------------------------
- # 'QObject' interface
- #--------------------------------------------------------------------------
-
- def __init__(self, console_widget):
- """ Create a completion widget that is attached to the specified Qt
- text edit widget.
- """
- text_edit = console_widget._control
- assert isinstance(text_edit, (QtGui.QTextEdit, QtGui.QPlainTextEdit))
- super(CompletionWidget, self).__init__()
-
- self._text_edit = text_edit
-
- self.setAttribute(QtCore.Qt.WA_StaticContents)
- self.setWindowFlags(QtCore.Qt.ToolTip | QtCore.Qt.WindowStaysOnTopHint)
-
- # Ensure that the text edit keeps focus when widget is displayed.
- self.setFocusProxy(self._text_edit)
-
- self.setFrameShadow(QtGui.QFrame.Plain)
- self.setFrameShape(QtGui.QFrame.StyledPanel)
-
- self.itemActivated.connect(self._complete_current)
-
- def eventFilter(self, obj, event):
- """ Reimplemented to handle keyboard input and to auto-hide when the
- text edit loses focus.
- """
- if obj == self._text_edit:
- etype = event.type()
-
- if etype == QtCore.QEvent.KeyPress:
- key, text = event.key(), event.text()
- if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter,
- QtCore.Qt.Key_Tab):
- self._complete_current()
- return True
- elif key == QtCore.Qt.Key_Escape:
- self.hide()
- return True
- elif key in (QtCore.Qt.Key_Up, QtCore.Qt.Key_Down,
- QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown,
- QtCore.Qt.Key_Home, QtCore.Qt.Key_End):
- self.keyPressEvent(event)
- return True
-
- elif etype == QtCore.QEvent.FocusOut:
- self.hide()
-
- return super(CompletionWidget, self).eventFilter(obj, event)
-
- #--------------------------------------------------------------------------
- # 'QWidget' interface
- #--------------------------------------------------------------------------
-
- def hideEvent(self, event):
- """ Reimplemented to disconnect signal handlers and event filter.
- """
- super(CompletionWidget, self).hideEvent(event)
- self._text_edit.cursorPositionChanged.disconnect(self._update_current)
- self._text_edit.removeEventFilter(self)
-
- def showEvent(self, event):
- """ Reimplemented to connect signal handlers and event filter.
- """
- super(CompletionWidget, self).showEvent(event)
- self._text_edit.cursorPositionChanged.connect(self._update_current)
- self._text_edit.installEventFilter(self)
-
- #--------------------------------------------------------------------------
- # 'CompletionWidget' interface
- #--------------------------------------------------------------------------
-
- def show_items(self, cursor, items):
- """ Shows the completion widget with 'items' at the position specified
- by 'cursor'.
- """
- text_edit = self._text_edit
- point = text_edit.cursorRect(cursor).bottomRight()
- point = text_edit.mapToGlobal(point)
- height = self.sizeHint().height()
- screen_rect = QtGui.QApplication.desktop().availableGeometry(self)
- if screen_rect.size().height() - point.y() - height < 0:
- point = text_edit.mapToGlobal(text_edit.cursorRect().topRight())
- point.setY(point.y() - height)
- self.move(point)
-
- self._start_position = cursor.position()
- self.clear()
- self.addItems(items)
- self.setCurrentRow(0)
- self.show()
-
- #--------------------------------------------------------------------------
- # Protected interface
- #--------------------------------------------------------------------------
-
- def _complete_current(self):
- """ Perform the completion with the currently selected item.
- """
- self._current_text_cursor().insertText(self.currentItem().text())
- self.hide()
-
- def _current_text_cursor(self):
- """ Returns a cursor with text between the start position and the
- current position selected.
- """
- cursor = self._text_edit.textCursor()
- if cursor.position() >= self._start_position:
- cursor.setPosition(self._start_position,
- QtGui.QTextCursor.KeepAnchor)
- return cursor
-
- def _update_current(self):
- """ Updates the current item based on the current text.
- """
- prefix = self._current_text_cursor().selection().toPlainText()
- if prefix:
- items = self.findItems(prefix, (QtCore.Qt.MatchStartsWith |
- QtCore.Qt.MatchCaseSensitive))
- if items:
- self.setCurrentItem(items[0])
- else:
- self.hide()
- else:
- self.hide()
-
- def cancel_completion(self):
- self.hide()
diff --git a/jupyter_qtconsole/console/console_widget.py b/jupyter_qtconsole/console/console_widget.py
deleted file mode 100644
index 1ce177e..0000000
--- a/jupyter_qtconsole/console/console_widget.py
+++ /dev/null
@@ -1,2167 +0,0 @@
-""" An abstract base class for console-type widgets.
-"""
-#-----------------------------------------------------------------------------
-# Imports
-#-----------------------------------------------------------------------------
-
-# Standard library imports
-import os.path
-import re
-import sys
-from textwrap import dedent
-import time
-from unicodedata import category
-import webbrowser
-
-# System library imports
-from IPython.external.qt import QtCore, QtGui
-
-# Local imports
-from IPython.config.configurable import LoggingConfigurable
-from IPython.core.inputsplitter import ESC_SEQUENCES
-from IPython.qt.rich_text import HtmlExporter
-from IPython.qt.util import MetaQObjectHasTraits, get_font
-from IPython.utils.text import columnize
-from IPython.utils.traitlets import Bool, Enum, Integer, Unicode
-from .ansi_code_processor import QtAnsiCodeProcessor
-from .completion_widget import CompletionWidget
-from .completion_html import CompletionHtml
-from .completion_plain import CompletionPlain
-from .kill_ring import QtKillRing
-
-
-#-----------------------------------------------------------------------------
-# Functions
-#-----------------------------------------------------------------------------
-
-ESCAPE_CHARS = ''.join(ESC_SEQUENCES)
-ESCAPE_RE = re.compile("^["+ESCAPE_CHARS+"]+")
-
-def commonprefix(items):
- """Get common prefix for completions
-
- Return the longest common prefix of a list of strings, but with special
- treatment of escape characters that might precede commands in IPython,
- such as %magic functions. Used in tab completion.
-
- For a more general function, see os.path.commonprefix
- """
- # the last item will always have the least leading % symbol
- # min / max are first/last in alphabetical order
- first_match = ESCAPE_RE.match(min(items))
- last_match = ESCAPE_RE.match(max(items))
- # common suffix is (common prefix of reversed items) reversed
- if first_match and last_match:
- prefix = os.path.commonprefix((first_match.group(0)[::-1], last_match.group(0)[::-1]))[::-1]
- else:
- prefix = ''
-
- items = [s.lstrip(ESCAPE_CHARS) for s in items]
- return prefix+os.path.commonprefix(items)
-
-def is_letter_or_number(char):
- """ Returns whether the specified unicode character is a letter or a number.
- """
- cat = category(char)
- return cat.startswith('L') or cat.startswith('N')
-
-#-----------------------------------------------------------------------------
-# Classes
-#-----------------------------------------------------------------------------
-
-class ConsoleWidget(MetaQObjectHasTraits('NewBase', (LoggingConfigurable, QtGui.QWidget), {})):
- """ An abstract base class for console-type widgets. This class has
- functionality for:
-
- * Maintaining a prompt and editing region
- * Providing the traditional Unix-style console keyboard shortcuts
- * Performing tab completion
- * Paging text
- * Handling ANSI escape codes
-
- ConsoleWidget also provides a number of utility methods that will be
- convenient to implementors of a console-style widget.
- """
-
- #------ Configuration ------------------------------------------------------
-
- ansi_codes = Bool(True, config=True,
- help="Whether to process ANSI escape codes."
- )
- buffer_size = Integer(500, config=True,
- help="""
- The maximum number of lines of text before truncation. Specifying a
- non-positive number disables text truncation (not recommended).
- """
- )
- execute_on_complete_input = Bool(True, config=True,
- help="""Whether to automatically execute on syntactically complete input.
-
- If False, Shift-Enter is required to submit each execution.
- Disabling this is mainly useful for non-Python kernels,
- where the completion check would be wrong.
- """
- )
- gui_completion = Enum(['plain', 'droplist', 'ncurses'], config=True,
- default_value = 'ncurses',
- help="""
- The type of completer to use. Valid values are:
-
- 'plain' : Show the available completion as a text list
- Below the editing area.
- 'droplist': Show the completion in a drop down list navigable
- by the arrow keys, and from which you can select
- completion by pressing Return.
- 'ncurses' : Show the completion as a text list which is navigable by
- `tab` and arrow keys.
- """
- )
- # NOTE: this value can only be specified during initialization.
- kind = Enum(['plain', 'rich'], default_value='plain', config=True,
- help="""
- The type of underlying text widget to use. Valid values are 'plain',
- which specifies a QPlainTextEdit, and 'rich', which specifies a
- QTextEdit.
- """
- )
- # NOTE: this value can only be specified during initialization.
- paging = Enum(['inside', 'hsplit', 'vsplit', 'custom', 'none'],
- default_value='inside', config=True,
- help="""
- The type of paging to use. Valid values are:
-
- 'inside'
- The widget pages like a traditional terminal.
- 'hsplit'
- When paging is requested, the widget is split horizontally. The top
- pane contains the console, and the bottom pane contains the paged text.
- 'vsplit'
- Similar to 'hsplit', except that a vertical splitter is used.
- 'custom'
- No action is taken by the widget beyond emitting a
- 'custom_page_requested(str)' signal.
- 'none'
- The text is written directly to the console.
- """)
-
- font_family = Unicode(config=True,
- help="""The font family to use for the console.
- On OSX this defaults to Monaco, on Windows the default is
- Consolas with fallback of Courier, and on other platforms
- the default is Monospace.
- """)
- def _font_family_default(self):
- if sys.platform == 'win32':
- # Consolas ships with Vista/Win7, fallback to Courier if needed
- return 'Consolas'
- elif sys.platform == 'darwin':
- # OSX always has Monaco, no need for a fallback
- return 'Monaco'
- else:
- # Monospace should always exist, no need for a fallback
- return 'Monospace'
-
- font_size = Integer(config=True,
- help="""The font size. If unconfigured, Qt will be entrusted
- with the size of the font.
- """)
-
- width = Integer(81, config=True,
- help="""The width of the console at start time in number
- of characters (will double with `hsplit` paging)
- """)
-
- height = Integer(25, config=True,
- help="""The height of the console at start time in number
- of characters (will double with `vsplit` paging)
- """)
-
- # Whether to override ShortcutEvents for the keybindings defined by this
- # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
- # priority (when it has focus) over, e.g., window-level menu shortcuts.
- override_shortcuts = Bool(False)
-
- # ------ Custom Qt Widgets -------------------------------------------------
-
- # For other projects to easily override the Qt widgets used by the console
- # (e.g. Spyder)
- custom_control = None
- custom_page_control = None
-
- #------ Signals ------------------------------------------------------------
-
- # Signals that indicate ConsoleWidget state.
- copy_available = QtCore.Signal(bool)
- redo_available = QtCore.Signal(bool)
- undo_available = QtCore.Signal(bool)
-
- # Signal emitted when paging is needed and the paging style has been
- # specified as 'custom'.
- custom_page_requested = QtCore.Signal(object)
-
- # Signal emitted when the font is changed.
- font_changed = QtCore.Signal(QtGui.QFont)
-
- #------ Protected class variables ------------------------------------------
-
- # control handles
- _control = None
- _page_control = None
- _splitter = None
-
- # When the control key is down, these keys are mapped.
- _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
- QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
- QtCore.Qt.Key_A : QtCore.Qt.Key_Home,
- QtCore.Qt.Key_P : QtCore.Qt.Key_Up,
- QtCore.Qt.Key_N : QtCore.Qt.Key_Down,
- QtCore.Qt.Key_H : QtCore.Qt.Key_Backspace, }
- if not sys.platform == 'darwin':
- # On OS X, Ctrl-E already does the right thing, whereas End moves the
- # cursor to the bottom of the buffer.
- _ctrl_down_remap[QtCore.Qt.Key_E] = QtCore.Qt.Key_End
-
- # The shortcuts defined by this widget. We need to keep track of these to
- # support 'override_shortcuts' above.
- _shortcuts = set(_ctrl_down_remap.keys()) | \
- { QtCore.Qt.Key_C, QtCore.Qt.Key_G, QtCore.Qt.Key_O,
- QtCore.Qt.Key_V }
-
- _temp_buffer_filled = False
-
- #---------------------------------------------------------------------------
- # 'QObject' interface
- #---------------------------------------------------------------------------
-
- def __init__(self, parent=None, **kw):
- """ Create a ConsoleWidget.
-
- Parameters
- ----------
- parent : QWidget, optional [default None]
- The parent for this widget.
- """
- QtGui.QWidget.__init__(self, parent)
- LoggingConfigurable.__init__(self, **kw)
-
- # While scrolling the pager on Mac OS X, it tears badly. The
- # NativeGesture is platform and perhaps build-specific hence
- # we take adequate precautions here.
- self._pager_scroll_events = [QtCore.QEvent.Wheel]
- if hasattr(QtCore.QEvent, 'NativeGesture'):
- self._pager_scroll_events.append(QtCore.QEvent.NativeGesture)
-
- # Create the layout and underlying text widget.
- layout = QtGui.QStackedLayout(self)
- layout.setContentsMargins(0, 0, 0, 0)
- self._control = self._create_control()
- if self.paging in ('hsplit', 'vsplit'):
- self._splitter = QtGui.QSplitter()
- if self.paging == 'hsplit':
- self._splitter.setOrientation(QtCore.Qt.Horizontal)
- else:
- self._splitter.setOrientation(QtCore.Qt.Vertical)
- self._splitter.addWidget(self._control)
- layout.addWidget(self._splitter)
- else:
- layout.addWidget(self._control)
-
- # Create the paging widget, if necessary.
- if self.paging in ('inside', 'hsplit', 'vsplit'):
- self._page_control = self._create_page_control()
- if self._splitter:
- self._page_control.hide()
- self._splitter.addWidget(self._page_control)
- else:
- layout.addWidget(self._page_control)
-
- # Initialize protected variables. Some variables contain useful state
- # information for subclasses; they should be considered read-only.
- self._append_before_prompt_pos = 0
- self._ansi_processor = QtAnsiCodeProcessor()
- if self.gui_completion == 'ncurses':
- self._completion_widget = CompletionHtml(self)
- elif self.gui_completion == 'droplist':
- self._completion_widget = CompletionWidget(self)
- elif self.gui_completion == 'plain':
- self._completion_widget = CompletionPlain(self)
-
- self._continuation_prompt = '> '
- self._continuation_prompt_html = None
- self._executing = False
- self._filter_resize = False
- self._html_exporter = HtmlExporter(self._control)
- self._input_buffer_executing = ''
- self._input_buffer_pending = ''
- self._kill_ring = QtKillRing(self._control)
- self._prompt = ''
- self._prompt_html = None
- self._prompt_pos = 0
- self._prompt_sep = ''
- self._reading = False
- self._reading_callback = None
- self._tab_width = 8
-
- # List of strings pending to be appended as plain text in the widget.
- # The text is not immediately inserted when available to not
- # choke the Qt event loop with paint events for the widget in
- # case of lots of output from kernel.
- self._pending_insert_text = []
-
- # Timer to flush the pending stream messages. The interval is adjusted
- # later based on actual time taken for flushing a screen (buffer_size)
- # of output text.
- self._pending_text_flush_interval = QtCore.QTimer(self._control)
- self._pending_text_flush_interval.setInterval(100)
- self._pending_text_flush_interval.setSingleShot(True)
- self._pending_text_flush_interval.timeout.connect(
- self._on_flush_pending_stream_timer)
-
- # Set a monospaced font.
- self.reset_font()
-
- # Configure actions.
- action = QtGui.QAction('Print', None)
- action.setEnabled(True)
- printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
- if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
- # Only override the default if there is a collision.
- # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
- printkey = "Ctrl+Shift+P"
- action.setShortcut(printkey)
- action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
- action.triggered.connect(self.print_)
- self.addAction(action)
- self.print_action = action
-
- action = QtGui.QAction('Save as HTML/XML', None)
- action.setShortcut(QtGui.QKeySequence.Save)
- action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
- action.triggered.connect(self.export_html)
- self.addAction(action)
- self.export_action = action
-
- action = QtGui.QAction('Select All', None)
- action.setEnabled(True)
- selectall = QtGui.QKeySequence(QtGui.QKeySequence.SelectAll)
- if selectall.matches("Ctrl+A") and sys.platform != 'darwin':
- # Only override the default if there is a collision.
- # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
- selectall = "Ctrl+Shift+A"
- action.setShortcut(selectall)
- action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
- action.triggered.connect(self.select_all)
- self.addAction(action)
- self.select_all_action = action
-
- self.increase_font_size = QtGui.QAction("Bigger Font",
- self,
- shortcut=QtGui.QKeySequence.ZoomIn,
- shortcutContext=QtCore.Qt.WidgetWithChildrenShortcut,
- statusTip="Increase the font size by one point",
- triggered=self._increase_font_size)
- self.addAction(self.increase_font_size)
-
- self.decrease_font_size = QtGui.QAction("Smaller Font",
- self,
- shortcut=QtGui.QKeySequence.ZoomOut,
- shortcutContext=QtCore.Qt.WidgetWithChildrenShortcut,
- statusTip="Decrease the font size by one point",
- triggered=self._decrease_font_size)
- self.addAction(self.decrease_font_size)
-
- self.reset_font_size = QtGui.QAction("Normal Font",
- self,
- shortcut="Ctrl+0",
- shortcutContext=QtCore.Qt.WidgetWithChildrenShortcut,
- statusTip="Restore the Normal font size",
- triggered=self.reset_font)
- self.addAction(self.reset_font_size)
-
- # Accept drag and drop events here. Drops were already turned off
- # in self._control when that widget was created.
- self.setAcceptDrops(True)
-
- #---------------------------------------------------------------------------
- # Drag and drop support
- #---------------------------------------------------------------------------
-
- def dragEnterEvent(self, e):
- if e.mimeData().hasUrls():
- # The link action should indicate to that the drop will insert
- # the file anme.
- e.setDropAction(QtCore.Qt.LinkAction)
- e.accept()
- elif e.mimeData().hasText():
- # By changing the action to copy we don't need to worry about
- # the user accidentally moving text around in the widget.
- e.setDropAction(QtCore.Qt.CopyAction)
- e.accept()
-
- def dragMoveEvent(self, e):
- if e.mimeData().hasUrls():
- pass
- elif e.mimeData().hasText():
- cursor = self._control.cursorForPosition(e.pos())
- if self._in_buffer(cursor.position()):
- e.setDropAction(QtCore.Qt.CopyAction)
- self._control.setTextCursor(cursor)
- else:
- e.setDropAction(QtCore.Qt.IgnoreAction)
- e.accept()
-
- def dropEvent(self, e):
- if e.mimeData().hasUrls():
- self._keep_cursor_in_buffer()
- cursor = self._control.textCursor()
- filenames = [url.toLocalFile() for url in e.mimeData().urls()]
- text = ', '.join("'" + f.replace("'", "'\"'\"'") + "'"
- for f in filenames)
- self._insert_plain_text_into_buffer(cursor, text)
- elif e.mimeData().hasText():
- cursor = self._control.cursorForPosition(e.pos())
- if self._in_buffer(cursor.position()):
- text = e.mimeData().text()
- self._insert_plain_text_into_buffer(cursor, text)
-
- def eventFilter(self, obj, event):
- """ Reimplemented to ensure a console-like behavior in the underlying
- text widgets.
- """
- etype = event.type()
- if etype == QtCore.QEvent.KeyPress:
-
- # Re-map keys for all filtered widgets.
- key = event.key()
- if self._control_key_down(event.modifiers()) and \
- key in self._ctrl_down_remap:
- new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
- self._ctrl_down_remap[key],
- QtCore.Qt.NoModifier)
- QtGui.qApp.sendEvent(obj, new_event)
- return True
-
- elif obj == self._control:
- return self._event_filter_console_keypress(event)
-
- elif obj == self._page_control:
- return self._event_filter_page_keypress(event)
-
- # Make middle-click paste safe.
- elif etype == QtCore.QEvent.MouseButtonRelease and \
- event.button() == QtCore.Qt.MidButton and \
- obj == self._control.viewport():
- cursor = self._control.cursorForPosition(event.pos())
- self._control.setTextCursor(cursor)
- self.paste(QtGui.QClipboard.Selection)
- return True
-
- # Manually adjust the scrollbars *after* a resize event is dispatched.
- elif etype == QtCore.QEvent.Resize and not self._filter_resize:
- self._filter_resize = True
- QtGui.qApp.sendEvent(obj, event)
- self._adjust_scrollbars()
- self._filter_resize = False
- return True
-
- # Override shortcuts for all filtered widgets.
- elif etype == QtCore.QEvent.ShortcutOverride and \
- self.override_shortcuts and \
- self._control_key_down(event.modifiers()) and \
- event.key() in self._shortcuts:
- event.accept()
-
- # Handle scrolling of the vsplit pager. This hack attempts to solve
- # problems with tearing of the help text inside the pager window. This
- # happens only on Mac OS X with both PySide and PyQt. This fix isn't
- # perfect but makes the pager more usable.
- elif etype in self._pager_scroll_events and \
- obj == self._page_control:
- self._page_control.repaint()
- return True
-
- elif etype == QtCore.QEvent.MouseMove:
- anchor = self._control.anchorAt(event.pos())
- QtGui.QToolTip.showText(event.globalPos(), anchor)
-
- return super(ConsoleWidget, self).eventFilter(obj, event)
-
- #---------------------------------------------------------------------------
- # 'QWidget' interface
- #---------------------------------------------------------------------------
-
- def sizeHint(self):
- """ Reimplemented to suggest a size that is 80 characters wide and
- 25 lines high.
- """
- font_metrics = QtGui.QFontMetrics(self.font)
- margin = (self._control.frameWidth() +
- self._control.document().documentMargin()) * 2
- style = self.style()
- splitwidth = style.pixelMetric(QtGui.QStyle.PM_SplitterWidth)
-
- # Note 1: Despite my best efforts to take the various margins into
- # account, the width is still coming out a bit too small, so we include
- # a fudge factor of one character here.
- # Note 2: QFontMetrics.maxWidth is not used here or anywhere else due
- # to a Qt bug on certain Mac OS systems where it returns 0.
- width = font_metrics.width(' ') * self.width + margin
- width += style.pixelMetric(QtGui.QStyle.PM_ScrollBarExtent)
- if self.paging == 'hsplit':
- width = width * 2 + splitwidth
-
- height = font_metrics.height() * self.height + margin
- if self.paging == 'vsplit':
- height = height * 2 + splitwidth
-
- return QtCore.QSize(width, height)
-
- #---------------------------------------------------------------------------
- # 'ConsoleWidget' public interface
- #---------------------------------------------------------------------------
-
- include_other_output = Bool(False, config=True,
- help="""Whether to include output from clients
- other than this one sharing the same kernel.
-
- Outputs are not displayed until enter is pressed.
- """
- )
-
- def can_copy(self):
- """ Returns whether text can be copied to the clipboard.
- """
- return self._control.textCursor().hasSelection()
-
- def can_cut(self):
- """ Returns whether text can be cut to the clipboard.
- """
- cursor = self._control.textCursor()
- return (cursor.hasSelection() and
- self._in_buffer(cursor.anchor()) and
- self._in_buffer(cursor.position()))
-
- def can_paste(self):
- """ Returns whether text can be pasted from the clipboard.
- """
- if self._control.textInteractionFlags() & QtCore.Qt.TextEditable:
- return bool(QtGui.QApplication.clipboard().text())
- return False
-
- def clear(self, keep_input=True):
- """ Clear the console.
-
- Parameters
- ----------
- keep_input : bool, optional (default True)
- If set, restores the old input buffer if a new prompt is written.
- """
- if self._executing:
- self._control.clear()
- else:
- if keep_input:
- input_buffer = self.input_buffer
- self._control.clear()
- self._show_prompt()
- if keep_input:
- self.input_buffer = input_buffer
-
- def copy(self):
- """ Copy the currently selected text to the clipboard.
- """
- self.layout().currentWidget().copy()
-
- def copy_anchor(self, anchor):
- """ Copy anchor text to the clipboard
- """
- QtGui.QApplication.clipboard().setText(anchor)
-
- def cut(self):
- """ Copy the currently selected text to the clipboard and delete it
- if it's inside the input buffer.
- """
- self.copy()
- if self.can_cut():
- self._control.textCursor().removeSelectedText()
-
- def execute(self, source=None, hidden=False, interactive=False):
- """ Executes source or the input buffer, possibly prompting for more
- input.
-
- Parameters
- ----------
- source : str, optional
-
- The source to execute. If not specified, the input buffer will be
- used. If specified and 'hidden' is False, the input buffer will be
- replaced with the source before execution.
-
- hidden : bool, optional (default False)
-
- If set, no output will be shown and the prompt will not be modified.
- In other words, it will be completely invisible to the user that
- an execution has occurred.
-
- interactive : bool, optional (default False)
-
- Whether the console is to treat the source as having been manually
- entered by the user. The effect of this parameter depends on the
- subclass implementation.
-
- Raises
- ------
- RuntimeError
- If incomplete input is given and 'hidden' is True. In this case,
- it is not possible to prompt for more input.
-
- Returns
- -------
- A boolean indicating whether the source was executed.
- """
- # WARNING: The order in which things happen here is very particular, in
- # large part because our syntax highlighting is fragile. If you change
- # something, test carefully!
-
- # Decide what to execute.
- if source is None:
- source = self.input_buffer
- if not hidden:
- # A newline is appended later, but it should be considered part
- # of the input buffer.
- source += '\n'
- elif not hidden:
- self.input_buffer = source
-
- # Execute the source or show a continuation prompt if it is incomplete.
- if self.execute_on_complete_input:
- complete = self._is_complete(source, interactive)
- else:
- complete = not interactive
- if hidden:
- if complete or not self.execute_on_complete_input:
- self._execute(source, hidden)
- else:
- error = 'Incomplete noninteractive input: "%s"'
- raise RuntimeError(error % source)
- else:
- if complete:
- self._append_plain_text('\n')
- self._input_buffer_executing = self.input_buffer
- self._executing = True
- self._prompt_finished()
-
- # The maximum block count is only in effect during execution.
- # This ensures that _prompt_pos does not become invalid due to
- # text truncation.
- self._control.document().setMaximumBlockCount(self.buffer_size)
-
- # Setting a positive maximum block count will automatically
- # disable the undo/redo history, but just to be safe:
- self._control.setUndoRedoEnabled(False)
-
- # Perform actual execution.
- self._execute(source, hidden)
-
- else:
- # Do this inside an edit block so continuation prompts are
- # removed seamlessly via undo/redo.
- cursor = self._get_end_cursor()
- cursor.beginEditBlock()
- cursor.insertText('\n')
- self._insert_continuation_prompt(cursor)
- cursor.endEditBlock()
-
- # Do not do this inside the edit block. It works as expected
- # when using a QPlainTextEdit control, but does not have an
- # effect when using a QTextEdit. I believe this is a Qt bug.
- self._control.moveCursor(QtGui.QTextCursor.End)
-
- return complete
-
- def export_html(self):
- """ Shows a dialog to export HTML/XML in various formats.
- """
- self._html_exporter.export()
-
- def _get_input_buffer(self, force=False):
- """ The text that the user has entered entered at the current prompt.
-
- If the console is currently executing, the text that is executing will
- always be returned.
- """
- # If we're executing, the input buffer may not even exist anymore due to
- # the limit imposed by 'buffer_size'. Therefore, we store it.
- if self._executing and not force:
- return self._input_buffer_executing
-
- cursor = self._get_end_cursor()
- cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
- input_buffer = cursor.selection().toPlainText()
-
- # Strip out continuation prompts.
- return input_buffer.replace('\n' + self._continuation_prompt, '\n')
-
- def _set_input_buffer(self, string):
- """ Sets the text in the input buffer.
-
- If the console is currently executing, this call has no *immediate*
- effect. When the execution is finished, the input buffer will be updated
- appropriately.
- """
- # If we're executing, store the text for later.
- if self._executing:
- self._input_buffer_pending = string
- return
-
- # Remove old text.
- cursor = self._get_end_cursor()
- cursor.beginEditBlock()
- cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
- cursor.removeSelectedText()
-
- # Insert new text with continuation prompts.
- self._insert_plain_text_into_buffer(self._get_prompt_cursor(), string)
- cursor.endEditBlock()
- self._control.moveCursor(QtGui.QTextCursor.End)
-
- input_buffer = property(_get_input_buffer, _set_input_buffer)
-
- def _get_font(self):
- """ The base font being used by the ConsoleWidget.
- """
- return self._control.document().defaultFont()
-
- def _set_font(self, font):
- """ Sets the base font for the ConsoleWidget to the specified QFont.
- """
- font_metrics = QtGui.QFontMetrics(font)
- self._control.setTabStopWidth(self.tab_width * font_metrics.width(' '))
-
- self._completion_widget.setFont(font)
- self._control.document().setDefaultFont(font)
- if self._page_control:
- self._page_control.document().setDefaultFont(font)
-
- self.font_changed.emit(font)
-
- font = property(_get_font, _set_font)
-
- def open_anchor(self, anchor):
- """ Open selected anchor in the default webbrowser
- """
- webbrowser.open( anchor )
-
- def paste(self, mode=QtGui.QClipboard.Clipboard):
- """ Paste the contents of the clipboard into the input region.
-
- Parameters
- ----------
- mode : QClipboard::Mode, optional [default QClipboard::Clipboard]
-
- Controls which part of the system clipboard is used. This can be
- used to access the selection clipboard in X11 and the Find buffer
- in Mac OS. By default, the regular clipboard is used.
- """
- if self._control.textInteractionFlags() & QtCore.Qt.TextEditable:
- # Make sure the paste is safe.
- self._keep_cursor_in_buffer()
- cursor = self._control.textCursor()
-
- # Remove any trailing newline, which confuses the GUI and forces the
- # user to backspace.
- text = QtGui.QApplication.clipboard().text(mode).rstrip()
- self._insert_plain_text_into_buffer(cursor, dedent(text))
-
- def print_(self, printer = None):
- """ Print the contents of the ConsoleWidget to the specified QPrinter.
- """
- if (not printer):
- printer = QtGui.QPrinter()
- if(QtGui.QPrintDialog(printer).exec_() != QtGui.QDialog.Accepted):
- return
- self._control.print_(printer)
-
- def prompt_to_top(self):
- """ Moves the prompt to the top of the viewport.
- """
- if not self._executing:
- prompt_cursor = self._get_prompt_cursor()
- if self._get_cursor().blockNumber() < prompt_cursor.blockNumber():
- self._set_cursor(prompt_cursor)
- self._set_top_cursor(prompt_cursor)
-
- def redo(self):
- """ Redo the last operation. If there is no operation to redo, nothing
- happens.
- """
- self._control.redo()
-
- def reset_font(self):
- """ Sets the font to the default fixed-width font for this platform.
- """
- if sys.platform == 'win32':
- # Consolas ships with Vista/Win7, fallback to Courier if needed
- fallback = 'Courier'
- elif sys.platform == 'darwin':
- # OSX always has Monaco
- fallback = 'Monaco'
- else:
- # Monospace should always exist
- fallback = 'Monospace'
- font = get_font(self.font_family, fallback)
- if self.font_size:
- font.setPointSize(self.font_size)
- else:
- font.setPointSize(QtGui.qApp.font().pointSize())
- font.setStyleHint(QtGui.QFont.TypeWriter)
- self._set_font(font)
-
- def change_font_size(self, delta):
- """Change the font size by the specified amount (in points).
- """
- font = self.font
- size = max(font.pointSize() + delta, 1) # minimum 1 point
- font.setPointSize(size)
- self._set_font(font)
-
- def _increase_font_size(self):
- self.change_font_size(1)
-
- def _decrease_font_size(self):
- self.change_font_size(-1)
-
- def select_all(self):
- """ Selects all the text in the buffer.
- """
- self._control.selectAll()
-
- def _get_tab_width(self):
- """ The width (in terms of space characters) for tab characters.
- """
- return self._tab_width
-
- def _set_tab_width(self, tab_width):
- """ Sets the width (in terms of space characters) for tab characters.
- """
- font_metrics = QtGui.QFontMetrics(self.font)
- self._control.setTabStopWidth(tab_width * font_metrics.width(' '))
-
- self._tab_width = tab_width
-
- tab_width = property(_get_tab_width, _set_tab_width)
-
- def undo(self):
- """ Undo the last operation. If there is no operation to undo, nothing
- happens.
- """
- self._control.undo()
-
- #---------------------------------------------------------------------------
- # 'ConsoleWidget' abstract interface
- #---------------------------------------------------------------------------
-
- def _is_complete(self, source, interactive):
- """ Returns whether 'source' can be executed. When triggered by an
- Enter/Return key press, 'interactive' is True; otherwise, it is
- False.
- """
- raise NotImplementedError
-
- def _execute(self, source, hidden):
- """ Execute 'source'. If 'hidden', do not show any output.
- """
- raise NotImplementedError
-
- def _prompt_started_hook(self):
- """ Called immediately after a new prompt is displayed.
- """
- pass
-
- def _prompt_finished_hook(self):
- """ Called immediately after a prompt is finished, i.e. when some input
- will be processed and a new prompt displayed.
- """
- pass
-
- def _up_pressed(self, shift_modifier):
- """ Called when the up key is pressed. Returns whether to continue
- processing the event.
- """
- return True
-
- def _down_pressed(self, shift_modifier):
- """ Called when the down key is pressed. Returns whether to continue
- processing the event.
- """
- return True
-
- def _tab_pressed(self):
- """ Called when the tab key is pressed. Returns whether to continue
- processing the event.
- """
- return False
-
- #--------------------------------------------------------------------------
- # 'ConsoleWidget' protected interface
- #--------------------------------------------------------------------------
-
- def _append_custom(self, insert, input, before_prompt=False, *args, **kwargs):
- """ A low-level method for appending content to the end of the buffer.
-
- If 'before_prompt' is enabled, the content will be inserted before the
- current prompt, if there is one.
- """
- # Determine where to insert the content.
- cursor = self._control.textCursor()
- if before_prompt and (self._reading or not self._executing):
- self._flush_pending_stream()
- cursor.setPosition(self._append_before_prompt_pos)
- else:
- if insert != self._insert_plain_text:
- self._flush_pending_stream()
- cursor.movePosition(QtGui.QTextCursor.End)
- start_pos = cursor.position()
-
- # Perform the insertion.
- result = insert(cursor, input, *args, **kwargs)
-
- # Adjust the prompt position if we have inserted before it. This is safe
- # because buffer truncation is disabled when not executing.
- if before_prompt and (self._reading or not self._executing):
- diff = cursor.position() - start_pos
- self._append_before_prompt_pos += diff
- self._prompt_pos += diff
-
- return result
-
- def _append_block(self, block_format=None, before_prompt=False):
- """ Appends an new QTextBlock to the end of the console buffer.
- """
- self._append_custom(self._insert_block, block_format, before_prompt)
-
- def _append_html(self, html, before_prompt=False):
- """ Appends HTML at the end of the console buffer.
- """
- self._append_custom(self._insert_html, html, before_prompt)
-
- def _append_html_fetching_plain_text(self, html, before_prompt=False):
- """ Appends HTML, then returns the plain text version of it.
- """
- return self._append_custom(self._insert_html_fetching_plain_text,
- html, before_prompt)
-
- def _append_plain_text(self, text, before_prompt=False):
- """ Appends plain text, processing ANSI codes if enabled.
- """
- self._append_custom(self._insert_plain_text, text, before_prompt)
-
- def _cancel_completion(self):
- """ If text completion is progress, cancel it.
- """
- self._completion_widget.cancel_completion()
-
- def _clear_temporary_buffer(self):
- """ Clears the "temporary text" buffer, i.e. all the text following
- the prompt region.
- """
- # Select and remove all text below the input buffer.
- cursor = self._get_prompt_cursor()
- prompt = self._continuation_prompt.lstrip()
- if(self._temp_buffer_filled):
- self._temp_buffer_filled = False
- while cursor.movePosition(QtGui.QTextCursor.NextBlock):
- temp_cursor = QtGui.QTextCursor(cursor)
- temp_cursor.select(QtGui.QTextCursor.BlockUnderCursor)
- text = temp_cursor.selection().toPlainText().lstrip()
- if not text.startswith(prompt):
- break
- else:
- # We've reached the end of the input buffer and no text follows.
- return
- cursor.movePosition(QtGui.QTextCursor.Left) # Grab the newline.
- cursor.movePosition(QtGui.QTextCursor.End,
- QtGui.QTextCursor.KeepAnchor)
- cursor.removeSelectedText()
-
- # After doing this, we have no choice but to clear the undo/redo
- # history. Otherwise, the text is not "temporary" at all, because it
- # can be recalled with undo/redo. Unfortunately, Qt does not expose
- # fine-grained control to the undo/redo system.
- if self._control.isUndoRedoEnabled():
- self._control.setUndoRedoEnabled(False)
- self._control.setUndoRedoEnabled(True)
-
- def _complete_with_items(self, cursor, items):
- """ Performs completion with 'items' at the specified cursor location.
- """
- self._cancel_completion()
-
- if len(items) == 1:
- cursor.setPosition(self._control.textCursor().position(),
- QtGui.QTextCursor.KeepAnchor)
- cursor.insertText(items[0])
-
- elif len(items) > 1:
- current_pos = self._control.textCursor().position()
- prefix = commonprefix(items)
- if prefix:
- cursor.setPosition(current_pos, QtGui.QTextCursor.KeepAnchor)
- cursor.insertText(prefix)
- current_pos = cursor.position()
-
- cursor.movePosition(QtGui.QTextCursor.Left, n=len(prefix))
- self._completion_widget.show_items(cursor, items)
-
-
- def _fill_temporary_buffer(self, cursor, text, html=False):
- """fill the area below the active editting zone with text"""
-
- current_pos = self._control.textCursor().position()
-
- cursor.beginEditBlock()
- self._append_plain_text('\n')
- self._page(text, html=html)
- cursor.endEditBlock()
-
- cursor.setPosition(current_pos)
- self._control.moveCursor(QtGui.QTextCursor.End)
- self._control.setTextCursor(cursor)
-
- self._temp_buffer_filled = True
-
-
- def _context_menu_make(self, pos):
- """ Creates a context menu for the given QPoint (in widget coordinates).
- """
- menu = QtGui.QMenu(self)
-
- self.cut_action = menu.addAction('Cut', self.cut)
- self.cut_action.setEnabled(self.can_cut())
- self.cut_action.setShortcut(QtGui.QKeySequence.Cut)
-
- self.copy_action = menu.addAction('Copy', self.copy)
- self.copy_action.setEnabled(self.can_copy())
- self.copy_action.setShortcut(QtGui.QKeySequence.Copy)
-
- self.paste_action = menu.addAction('Paste', self.paste)
- self.paste_action.setEnabled(self.can_paste())
- self.paste_action.setShortcut(QtGui.QKeySequence.Paste)
-
- anchor = self._control.anchorAt(pos)
- if anchor:
- menu.addSeparator()
- self.copy_link_action = menu.addAction(
- 'Copy Link Address', lambda: self.copy_anchor(anchor=anchor))
- self.open_link_action = menu.addAction(
- 'Open Link', lambda: self.open_anchor(anchor=anchor))
-
- menu.addSeparator()
- menu.addAction(self.select_all_action)
-
- menu.addSeparator()
- menu.addAction(self.export_action)
- menu.addAction(self.print_action)
-
- return menu
-
- def _control_key_down(self, modifiers, include_command=False):
- """ Given a KeyboardModifiers flags object, return whether the Control
- key is down.
-
- Parameters
- ----------
- include_command : bool, optional (default True)
- Whether to treat the Command key as a (mutually exclusive) synonym
- for Control when in Mac OS.
- """
- # Note that on Mac OS, ControlModifier corresponds to the Command key
- # while MetaModifier corresponds to the Control key.
- if sys.platform == 'darwin':
- down = include_command and (modifiers & QtCore.Qt.ControlModifier)
- return bool(down) ^ bool(modifiers & QtCore.Qt.MetaModifier)
- else:
- return bool(modifiers & QtCore.Qt.ControlModifier)
-
- def _create_control(self):
- """ Creates and connects the underlying text widget.
- """
- # Create the underlying control.
- if self.custom_control:
- control = self.custom_control()
- elif self.kind == 'plain':
- control = QtGui.QPlainTextEdit()
- elif self.kind == 'rich':
- control = QtGui.QTextEdit()
- control.setAcceptRichText(False)
- control.setMouseTracking(True)
-
- # Prevent the widget from handling drops, as we already provide
- # the logic in this class.
- control.setAcceptDrops(False)
-
- # Install event filters. The filter on the viewport is needed for
- # mouse events.
- control.installEventFilter(self)
- control.viewport().installEventFilter(self)
-
- # Connect signals.
- control.customContextMenuRequested.connect(
- self._custom_context_menu_requested)
- control.copyAvailable.connect(self.copy_available)
- control.redoAvailable.connect(self.redo_available)
- control.undoAvailable.connect(self.undo_available)
-
- # Hijack the document size change signal to prevent Qt from adjusting
- # the viewport's scrollbar. We are relying on an implementation detail
- # of Q(Plain)TextEdit here, which is potentially dangerous, but without
- # this functionality we cannot create a nice terminal interface.
- layout = control.document().documentLayout()
- layout.documentSizeChanged.disconnect()
- layout.documentSizeChanged.connect(self._adjust_scrollbars)
-
- # Configure the control.
- control.setAttribute(QtCore.Qt.WA_InputMethodEnabled, True)
- control.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
- control.setReadOnly(True)
- control.setUndoRedoEnabled(False)
- control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
- return control
-
- def _create_page_control(self):
- """ Creates and connects the underlying paging widget.
- """
- if self.custom_page_control:
- control = self.custom_page_control()
- elif self.kind == 'plain':
- control = QtGui.QPlainTextEdit()
- elif self.kind == 'rich':
- control = QtGui.QTextEdit()
- control.installEventFilter(self)
- viewport = control.viewport()
- viewport.installEventFilter(self)
- control.setReadOnly(True)
- control.setUndoRedoEnabled(False)
- control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
- return control
-
- def _event_filter_console_keypress(self, event):
- """ Filter key events for the underlying text widget to create a
- console-like interface.
- """
- intercepted = False
- cursor = self._control.textCursor()
- position = cursor.position()
- key = event.key()
- ctrl_down = self._control_key_down(event.modifiers())
- alt_down = event.modifiers() & QtCore.Qt.AltModifier
- shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
-
- #------ Special sequences ----------------------------------------------
-
- if event.matches(QtGui.QKeySequence.Copy):
- self.copy()
- intercepted = True
-
- elif event.matches(QtGui.QKeySequence.Cut):
- self.cut()
- intercepted = True
-
- elif event.matches(QtGui.QKeySequence.Paste):
- self.paste()
- intercepted = True
-
- #------ Special modifier logic -----------------------------------------
-
- elif key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
- intercepted = True
-
- # Special handling when tab completing in text mode.
- self._cancel_completion()
-
- if self._in_buffer(position):
- # Special handling when a reading a line of raw input.
- if self._reading:
- self._append_plain_text('\n')
- self._reading = False
- if self._reading_callback:
- self._reading_callback()
-
- # If the input buffer is a single line or there is only
- # whitespace after the cursor, execute. Otherwise, split the
- # line with a continuation prompt.
- elif not self._executing:
- cursor.movePosition(QtGui.QTextCursor.End,
- QtGui.QTextCursor.KeepAnchor)
- at_end = len(cursor.selectedText().strip()) == 0
- single_line = (self._get_end_cursor().blockNumber() ==
- self._get_prompt_cursor().blockNumber())
- if (at_end or shift_down or single_line) and not ctrl_down:
- self.execute(interactive = not shift_down)
- else:
- # Do this inside an edit block for clean undo/redo.
- cursor.beginEditBlock()
- cursor.setPosition(position)
- cursor.insertText('\n')
- self._insert_continuation_prompt(cursor)
- cursor.endEditBlock()
-
- # Ensure that the whole input buffer is visible.
- # FIXME: This will not be usable if the input buffer is
- # taller than the console widget.
- self._control.moveCursor(QtGui.QTextCursor.End)
- self._control.setTextCursor(cursor)
-
- #------ Control/Cmd modifier -------------------------------------------
-
- elif ctrl_down:
- if key == QtCore.Qt.Key_G:
- self._keyboard_quit()
- intercepted = True
-
- elif key == QtCore.Qt.Key_K:
- if self._in_buffer(position):
- cursor.clearSelection()
- cursor.movePosition(QtGui.QTextCursor.EndOfLine,
- QtGui.QTextCursor.KeepAnchor)
- if not cursor.hasSelection():
- # Line deletion (remove continuation prompt)
- cursor.movePosition(QtGui.QTextCursor.NextBlock,
- QtGui.QTextCursor.KeepAnchor)
- cursor.movePosition(QtGui.QTextCursor.Right,
- QtGui.QTextCursor.KeepAnchor,
- len(self._continuation_prompt))
- self._kill_ring.kill_cursor(cursor)
- self._set_cursor(cursor)
- intercepted = True
-
- elif key == QtCore.Qt.Key_L:
- self.prompt_to_top()
- intercepted = True
-
- elif key == QtCore.Qt.Key_O:
- if self._page_control and self._page_control.isVisible():
- self._page_control.setFocus()
- intercepted = True
-
- elif key == QtCore.Qt.Key_U:
- if self._in_buffer(position):
- cursor.clearSelection()
- start_line = cursor.blockNumber()
- if start_line == self._get_prompt_cursor().blockNumber():
- offset = len(self._prompt)
- else:
- offset = len(self._continuation_prompt)
- cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
- QtGui.QTextCursor.KeepAnchor)
- cursor.movePosition(QtGui.QTextCursor.Right,
- QtGui.QTextCursor.KeepAnchor, offset)
- self._kill_ring.kill_cursor(cursor)
- self._set_cursor(cursor)
- intercepted = True
-
- elif key == QtCore.Qt.Key_Y:
- self._keep_cursor_in_buffer()
- self._kill_ring.yank()
- intercepted = True
-
- elif key in (QtCore.Qt.Key_Backspace, QtCore.Qt.Key_Delete):
- if key == QtCore.Qt.Key_Backspace:
- cursor = self._get_word_start_cursor(position)
- else: # key == QtCore.Qt.Key_Delete
- cursor = self._get_word_end_cursor(position)
- cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
- self._kill_ring.kill_cursor(cursor)
- intercepted = True
-
- elif key == QtCore.Qt.Key_D:
- if len(self.input_buffer) == 0:
- self.exit_requested.emit(self)
- else:
- new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
- QtCore.Qt.Key_Delete,
- QtCore.Qt.NoModifier)
- QtGui.qApp.sendEvent(self._control, new_event)
- intercepted = True
-
- #------ Alt modifier ---------------------------------------------------
-
- elif alt_down:
- if key == QtCore.Qt.Key_B:
- self._set_cursor(self._get_word_start_cursor(position))
- intercepted = True
-
- elif key == QtCore.Qt.Key_F:
- self._set_cursor(self._get_word_end_cursor(position))
- intercepted = True
-
- elif key == QtCore.Qt.Key_Y:
- self._kill_ring.rotate()
- intercepted = True
-
- elif key == QtCore.Qt.Key_Backspace:
- cursor = self._get_word_start_cursor(position)
- cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
- self._kill_ring.kill_cursor(cursor)
- intercepted = True
-
- elif key == QtCore.Qt.Key_D:
- cursor = self._get_word_end_cursor(position)
- cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
- self._kill_ring.kill_cursor(cursor)
- intercepted = True
-
- elif key == QtCore.Qt.Key_Delete:
- intercepted = True
-
- elif key == QtCore.Qt.Key_Greater:
- self._control.moveCursor(QtGui.QTextCursor.End)
- intercepted = True
-
- elif key == QtCore.Qt.Key_Less:
- self._control.setTextCursor(self._get_prompt_cursor())
- intercepted = True
-
- #------ No modifiers ---------------------------------------------------
-
- else:
- if shift_down:
- anchormode = QtGui.QTextCursor.KeepAnchor
- else:
- anchormode = QtGui.QTextCursor.MoveAnchor
-
- if key == QtCore.Qt.Key_Escape:
- self._keyboard_quit()
- intercepted = True
-
- elif key == QtCore.Qt.Key_Up:
- if self._reading or not self._up_pressed(shift_down):
- intercepted = True
- else:
- prompt_line = self._get_prompt_cursor().blockNumber()
- intercepted = cursor.blockNumber() <= prompt_line
-
- elif key == QtCore.Qt.Key_Down:
- if self._reading or not self._down_pressed(shift_down):
- intercepted = True
- else:
- end_line = self._get_end_cursor().blockNumber()
- intercepted = cursor.blockNumber() == end_line
-
- elif key == QtCore.Qt.Key_Tab:
- if not self._reading:
- if self._tab_pressed():
- # real tab-key, insert four spaces
- cursor.insertText(' '*4)
- intercepted = True
-
- elif key == QtCore.Qt.Key_Left:
-
- # Move to the previous line
- line, col = cursor.blockNumber(), cursor.columnNumber()
- if line > self._get_prompt_cursor().blockNumber() and \
- col == len(self._continuation_prompt):
- self._control.moveCursor(QtGui.QTextCursor.PreviousBlock,
- mode=anchormode)
- self._control.moveCursor(QtGui.QTextCursor.EndOfBlock,
- mode=anchormode)
- intercepted = True
-
- # Regular left movement
- else:
- intercepted = not self._in_buffer(position - 1)
-
- elif key == QtCore.Qt.Key_Right:
- original_block_number = cursor.blockNumber()
- self._control.moveCursor(QtGui.QTextCursor.Right,
- mode=anchormode)
- if cursor.blockNumber() != original_block_number:
- self._control.moveCursor(QtGui.QTextCursor.Right,
- n=len(self._continuation_prompt),
- mode=anchormode)
- intercepted = True
-
- elif key == QtCore.Qt.Key_Home:
- start_line = cursor.blockNumber()
- if start_line == self._get_prompt_cursor().blockNumber():
- start_pos = self._prompt_pos
- else:
- cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
- QtGui.QTextCursor.KeepAnchor)
- start_pos = cursor.position()
- start_pos += len(self._continuation_prompt)
- cursor.setPosition(position)
- if shift_down and self._in_buffer(position):
- cursor.setPosition(start_pos, QtGui.QTextCursor.KeepAnchor)
- else:
- cursor.setPosition(start_pos)
- self._set_cursor(cursor)
- intercepted = True
-
- elif key == QtCore.Qt.Key_Backspace:
-
- # Line deletion (remove continuation prompt)
- line, col = cursor.blockNumber(), cursor.columnNumber()
- if not self._reading and \
- col == len(self._continuation_prompt) and \
- line > self._get_prompt_cursor().blockNumber():
- cursor.beginEditBlock()
- cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
- QtGui.QTextCursor.KeepAnchor)
- cursor.removeSelectedText()
- cursor.deletePreviousChar()
- cursor.endEditBlock()
- intercepted = True
-
- # Regular backwards deletion
- else:
- anchor = cursor.anchor()
- if anchor == position:
- intercepted = not self._in_buffer(position - 1)
- else:
- intercepted = not self._in_buffer(min(anchor, position))
-
- elif key == QtCore.Qt.Key_Delete:
-
- # Line deletion (remove continuation prompt)
- if not self._reading and self._in_buffer(position) and \
- cursor.atBlockEnd() and not cursor.hasSelection():
- cursor.movePosition(QtGui.QTextCursor.NextBlock,
- QtGui.QTextCursor.KeepAnchor)
- cursor.movePosition(QtGui.QTextCursor.Right,
- QtGui.QTextCursor.KeepAnchor,
- len(self._continuation_prompt))
- cursor.removeSelectedText()
- intercepted = True
-
- # Regular forwards deletion:
- else:
- anchor = cursor.anchor()
- intercepted = (not self._in_buffer(anchor) or
- not self._in_buffer(position))
-
- # Don't move the cursor if Control/Cmd is pressed to allow copy-paste
- # using the keyboard in any part of the buffer. Also, permit scrolling
- # with Page Up/Down keys. Finally, if we're executing, don't move the
- # cursor (if even this made sense, we can't guarantee that the prompt
- # position is still valid due to text truncation).
- if not (self._control_key_down(event.modifiers(), include_command=True)
- or key in (QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown)
- or (self._executing and not self._reading)):
- self._keep_cursor_in_buffer()
-
- return intercepted
-
- def _event_filter_page_keypress(self, event):
- """ Filter key events for the paging widget to create console-like
- interface.
- """
- key = event.key()
- ctrl_down = self._control_key_down(event.modifiers())
- alt_down = event.modifiers() & QtCore.Qt.AltModifier
-
- if ctrl_down:
- if key == QtCore.Qt.Key_O:
- self._control.setFocus()
- intercept = True
-
- elif alt_down:
- if key == QtCore.Qt.Key_Greater:
- self._page_control.moveCursor(QtGui.QTextCursor.End)
- intercepted = True
-
- elif key == QtCore.Qt.Key_Less:
- self._page_control.moveCursor(QtGui.QTextCursor.Start)
- intercepted = True
-
- elif key in (QtCore.Qt.Key_Q, QtCore.Qt.Key_Escape):
- if self._splitter:
- self._page_control.hide()
- self._control.setFocus()
- else:
- self.layout().setCurrentWidget(self._control)
- # re-enable buffer truncation after paging
- self._control.document().setMaximumBlockCount(self.buffer_size)
- return True
-
- elif key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return,
- QtCore.Qt.Key_Tab):
- new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
- QtCore.Qt.Key_PageDown,
- QtCore.Qt.NoModifier)
- QtGui.qApp.sendEvent(self._page_control, new_event)
- return True
-
- elif key == QtCore.Qt.Key_Backspace:
- new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
- QtCore.Qt.Key_PageUp,
- QtCore.Qt.NoModifier)
- QtGui.qApp.sendEvent(self._page_control, new_event)
- return True
-
- # vi/less -like key bindings
- elif key == QtCore.Qt.Key_J:
- new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
- QtCore.Qt.Key_Down,
- QtCore.Qt.NoModifier)
- QtGui.qApp.sendEvent(self._page_control, new_event)
- return True
-
- # vi/less -like key bindings
- elif key == QtCore.Qt.Key_K:
- new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
- QtCore.Qt.Key_Up,
- QtCore.Qt.NoModifier)
- QtGui.qApp.sendEvent(self._page_control, new_event)
- return True
-
- return False
-
- def _on_flush_pending_stream_timer(self):
- """ Flush the pending stream output and change the
- prompt position appropriately.
- """
- cursor = self._control.textCursor()
- cursor.movePosition(QtGui.QTextCursor.End)
- pos = cursor.position()
- self._flush_pending_stream()
- cursor.movePosition(QtGui.QTextCursor.End)
- diff = cursor.position() - pos
- if diff > 0:
- self._prompt_pos += diff
- self._append_before_prompt_pos += diff
-
- def _flush_pending_stream(self):
- """ Flush out pending text into the widget. """
- text = self._pending_insert_text
- self._pending_insert_text = []
- buffer_size = self._control.document().maximumBlockCount()
- if buffer_size > 0:
- text = self._get_last_lines_from_list(text, buffer_size)
- text = ''.join(text)
- t = time.time()
- self._insert_plain_text(self._get_end_cursor(), text, flush=True)
- # Set the flush interval to equal the maximum time to update text.
- self._pending_text_flush_interval.setInterval(max(100,
- (time.time()-t)*1000))
-
- def _format_as_columns(self, items, separator=' '):
- """ Transform a list of strings into a single string with columns.
-
- Parameters
- ----------
- items : sequence of strings
- The strings to process.
-
- separator : str, optional [default is two spaces]
- The string that separates columns.
-
- Returns
- -------
- The formatted string.
- """
- # Calculate the number of characters available.
- width = self._control.viewport().width()
- char_width = QtGui.QFontMetrics(self.font).width(' ')
- displaywidth = max(10, (width / char_width) - 1)
-
- return columnize(items, separator, displaywidth)
-
- def _get_block_plain_text(self, block):
- """ Given a QTextBlock, return its unformatted text.
- """
- cursor = QtGui.QTextCursor(block)
- cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
- cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
- QtGui.QTextCursor.KeepAnchor)
- return cursor.selection().toPlainText()
-
- def _get_cursor(self):
- """ Convenience method that returns a cursor for the current position.
- """
- return self._control.textCursor()
-
- def _get_end_cursor(self):
- """ Convenience method that returns a cursor for the last character.
- """
- cursor = self._control.textCursor()
- cursor.movePosition(QtGui.QTextCursor.End)
- return cursor
-
- def _get_input_buffer_cursor_column(self):
- """ Returns the column of the cursor in the input buffer, excluding the
- contribution by the prompt, or -1 if there is no such column.
- """
- prompt = self._get_input_buffer_cursor_prompt()
- if prompt is None:
- return -1
- else:
- cursor = self._control.textCursor()
- return cursor.columnNumber() - len(prompt)
-
- def _get_input_buffer_cursor_line(self):
- """ Returns the text of the line of the input buffer that contains the
- cursor, or None if there is no such line.
- """
- prompt = self._get_input_buffer_cursor_prompt()
- if prompt is None:
- return None
- else:
- cursor = self._control.textCursor()
- text = self._get_block_plain_text(cursor.block())
- return text[len(prompt):]
-
- def _get_input_buffer_cursor_pos(self):
- """Return the cursor position within the input buffer."""
- cursor = self._control.textCursor()
- cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
- input_buffer = cursor.selection().toPlainText()
-
- # Don't count continuation prompts
- return len(input_buffer.replace('\n' + self._continuation_prompt, '\n'))
-
- def _get_input_buffer_cursor_prompt(self):
- """ Returns the (plain text) prompt for line of the input buffer that
- contains the cursor, or None if there is no such line.
- """
- if self._executing:
- return None
- cursor = self._control.textCursor()
- if cursor.position() >= self._prompt_pos:
- if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
- return self._prompt
- else:
- return self._continuation_prompt
- else:
- return None
-
- def _get_last_lines(self, text, num_lines, return_count=False):
- """ Return last specified number of lines of text (like `tail -n`).
- If return_count is True, returns a tuple of clipped text and the
- number of lines in the clipped text.
- """
- pos = len(text)
- if pos < num_lines:
- if return_count:
- return text, text.count('\n') if return_count else text
- else:
- return text
- i = 0
- while i < num_lines:
- pos = text.rfind('\n', None, pos)
- if pos == -1:
- pos = None
- break
- i += 1
- if return_count:
- return text[pos:], i
- else:
- return text[pos:]
-
- def _get_last_lines_from_list(self, text_list, num_lines):
- """ Return the list of text clipped to last specified lines.
- """
- ret = []
- lines_pending = num_lines
- for text in reversed(text_list):
- text, lines_added = self._get_last_lines(text, lines_pending,
- return_count=True)
- ret.append(text)
- lines_pending -= lines_added
- if lines_pending <= 0:
- break
- return ret[::-1]
-
- def _get_prompt_cursor(self):
- """ Convenience method that returns a cursor for the prompt position.
- """
- cursor = self._control.textCursor()
- cursor.setPosition(self._prompt_pos)
- return cursor
-
- def _get_selection_cursor(self, start, end):
- """ Convenience method that returns a cursor with text selected between
- the positions 'start' and 'end'.
- """
- cursor = self._control.textCursor()
- cursor.setPosition(start)
- cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
- return cursor
-
- def _get_word_start_cursor(self, position):
- """ Find the start of the word to the left the given position. If a
- sequence of non-word characters precedes the first word, skip over
- them. (This emulates the behavior of bash, emacs, etc.)
- """
- document = self._control.document()
- position -= 1
- while position >= self._prompt_pos and \
- not is_letter_or_number(document.characterAt(position)):
- position -= 1
- while position >= self._prompt_pos and \
- is_letter_or_number(document.characterAt(position)):
- position -= 1
- cursor = self._control.textCursor()
- cursor.setPosition(position + 1)
- return cursor
-
- def _get_word_end_cursor(self, position):
- """ Find the end of the word to the right the given position. If a
- sequence of non-word characters precedes the first word, skip over
- them. (This emulates the behavior of bash, emacs, etc.)
- """
- document = self._control.document()
- end = self._get_end_cursor().position()
- while position < end and \
- not is_letter_or_number(document.characterAt(position)):
- position += 1
- while position < end and \
- is_letter_or_number(document.characterAt(position)):
- position += 1
- cursor = self._control.textCursor()
- cursor.setPosition(position)
- return cursor
-
- def _insert_continuation_prompt(self, cursor):
- """ Inserts new continuation prompt using the specified cursor.
- """
- if self._continuation_prompt_html is None:
- self._insert_plain_text(cursor, self._continuation_prompt)
- else:
- self._continuation_prompt = self._insert_html_fetching_plain_text(
- cursor, self._continuation_prompt_html)
-
- def _insert_block(self, cursor, block_format=None):
- """ Inserts an empty QTextBlock using the specified cursor.
- """
- if block_format is None:
- block_format = QtGui.QTextBlockFormat()
- cursor.insertBlock(block_format)
-
- def _insert_html(self, cursor, html):
- """ Inserts HTML using the specified cursor in such a way that future
- formatting is unaffected.
- """
- cursor.beginEditBlock()
- cursor.insertHtml(html)
-
- # After inserting HTML, the text document "remembers" it's in "html
- # mode", which means that subsequent calls adding plain text will result
- # in unwanted formatting, lost tab characters, etc. The following code
- # hacks around this behavior, which I consider to be a bug in Qt, by
- # (crudely) resetting the document's style state.
- cursor.movePosition(QtGui.QTextCursor.Left,
- QtGui.QTextCursor.KeepAnchor)
- if cursor.selection().toPlainText() == ' ':
- cursor.removeSelectedText()
- else:
- cursor.movePosition(QtGui.QTextCursor.Right)
- cursor.insertText(' ', QtGui.QTextCharFormat())
- cursor.endEditBlock()
-
- def _insert_html_fetching_plain_text(self, cursor, html):
- """ Inserts HTML using the specified cursor, then returns its plain text
- version.
- """
- cursor.beginEditBlock()
- cursor.removeSelectedText()
-
- start = cursor.position()
- self._insert_html(cursor, html)
- end = cursor.position()
- cursor.setPosition(start, QtGui.QTextCursor.KeepAnchor)
- text = cursor.selection().toPlainText()
-
- cursor.setPosition(end)
- cursor.endEditBlock()
- return text
-
- def _insert_plain_text(self, cursor, text, flush=False):
- """ Inserts plain text using the specified cursor, processing ANSI codes
- if enabled.
- """
- # maximumBlockCount() can be different from self.buffer_size in
- # case input prompt is active.
- buffer_size = self._control.document().maximumBlockCount()
-
- if (self._executing and not flush and
- self._pending_text_flush_interval.isActive() and
- cursor.position() == self._get_end_cursor().position()):
- # Queue the text to insert in case it is being inserted at end
- self._pending_insert_text.append(text)
- if buffer_size > 0:
- self._pending_insert_text = self._get_last_lines_from_list(
- self._pending_insert_text, buffer_size)
- return
-
- if self._executing and not self._pending_text_flush_interval.isActive():
- self._pending_text_flush_interval.start()
-
- # Clip the text to last `buffer_size` lines.
- if buffer_size > 0:
- text = self._get_last_lines(text, buffer_size)
-
- cursor.beginEditBlock()
- if self.ansi_codes:
- for substring in self._ansi_processor.split_string(text):
- for act in self._ansi_processor.actions:
-
- # Unlike real terminal emulators, we don't distinguish
- # between the screen and the scrollback buffer. A screen
- # erase request clears everything.
- if act.action == 'erase' and act.area == 'screen':
- cursor.select(QtGui.QTextCursor.Document)
- cursor.removeSelectedText()
-
- # Simulate a form feed by scrolling just past the last line.
- elif act.action == 'scroll' and act.unit == 'page':
- cursor.insertText('\n')
- cursor.endEditBlock()
- self._set_top_cursor(cursor)
- cursor.joinPreviousEditBlock()
- cursor.deletePreviousChar()
-
- elif act.action == 'carriage-return':
- cursor.movePosition(
- cursor.StartOfLine, cursor.KeepAnchor)
-
- elif act.action == 'beep':
- QtGui.qApp.beep()
-
- elif act.action == 'backspace':
- if not cursor.atBlockStart():
- cursor.movePosition(
- cursor.PreviousCharacter, cursor.KeepAnchor)
-
- elif act.action == 'newline':
- cursor.movePosition(cursor.EndOfLine)
-
- format = self._ansi_processor.get_format()
-
- selection = cursor.selectedText()
- if len(selection) == 0:
- cursor.insertText(substring, format)
- elif substring is not None:
- # BS and CR are treated as a change in print
- # position, rather than a backwards character
- # deletion for output equivalence with (I)Python
- # terminal.
- if len(substring) >= len(selection):
- cursor.insertText(substring, format)
- else:
- old_text = selection[len(substring):]
- cursor.insertText(substring + old_text, format)
- cursor.movePosition(cursor.PreviousCharacter,
- cursor.KeepAnchor, len(old_text))
- else:
- cursor.insertText(text)
- cursor.endEditBlock()
-
- def _insert_plain_text_into_buffer(self, cursor, text):
- """ Inserts text into the input buffer using the specified cursor (which
- must be in the input buffer), ensuring that continuation prompts are
- inserted as necessary.
- """
- lines = text.splitlines(True)
- if lines:
- if lines[-1].endswith('\n'):
- # If the text ends with a newline, add a blank line so a new
- # continuation prompt is produced.
- lines.append('')
- cursor.beginEditBlock()
- cursor.insertText(lines[0])
- for line in lines[1:]:
- if self._continuation_prompt_html is None:
- cursor.insertText(self._continuation_prompt)
- else:
- self._continuation_prompt = \
- self._insert_html_fetching_plain_text(
- cursor, self._continuation_prompt_html)
- cursor.insertText(line)
- cursor.endEditBlock()
-
- def _in_buffer(self, position=None):
- """ Returns whether the current cursor (or, if specified, a position) is
- inside the editing region.
- """
- cursor = self._control.textCursor()
- if position is None:
- position = cursor.position()
- else:
- cursor.setPosition(position)
- line = cursor.blockNumber()
- prompt_line = self._get_prompt_cursor().blockNumber()
- if line == prompt_line:
- return position >= self._prompt_pos
- elif line > prompt_line:
- cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
- prompt_pos = cursor.position() + len(self._continuation_prompt)
- return position >= prompt_pos
- return False
-
- def _keep_cursor_in_buffer(self):
- """ Ensures that the cursor is inside the editing region. Returns
- whether the cursor was moved.
- """
- moved = not self._in_buffer()
- if moved:
- cursor = self._control.textCursor()
- cursor.movePosition(QtGui.QTextCursor.End)
- self._control.setTextCursor(cursor)
- return moved
-
- def _keyboard_quit(self):
- """ Cancels the current editing task ala Ctrl-G in Emacs.
- """
- if self._temp_buffer_filled :
- self._cancel_completion()
- self._clear_temporary_buffer()
- else:
- self.input_buffer = ''
-
- def _page(self, text, html=False):
- """ Displays text using the pager if it exceeds the height of the
- viewport.
-
- Parameters
- ----------
- html : bool, optional (default False)
- If set, the text will be interpreted as HTML instead of plain text.
- """
- line_height = QtGui.QFontMetrics(self.font).height()
- minlines = self._control.viewport().height() / line_height
- if self.paging != 'none' and \
- re.match("(?:[^\n]*\n){%i}" % minlines, text):
- if self.paging == 'custom':
- self.custom_page_requested.emit(text)
- else:
- # disable buffer truncation during paging
- self._control.document().setMaximumBlockCount(0)
- self._page_control.clear()
- cursor = self._page_control.textCursor()
- if html:
- self._insert_html(cursor, text)
- else:
- self._insert_plain_text(cursor, text)
- self._page_control.moveCursor(QtGui.QTextCursor.Start)
-
- self._page_control.viewport().resize(self._control.size())
- if self._splitter:
- self._page_control.show()
- self._page_control.setFocus()
- else:
- self.layout().setCurrentWidget(self._page_control)
- elif html:
- self._append_html(text)
- else:
- self._append_plain_text(text)
-
- def _set_paging(self, paging):
- """
- Change the pager to `paging` style.
-
- Parameters
- ----------
- paging : string
- Either "hsplit", "vsplit", or "inside"
- """
- if self._splitter is None:
- raise NotImplementedError("""can only switch if --paging=hsplit or
- --paging=vsplit is used.""")
- if paging == 'hsplit':
- self._splitter.setOrientation(QtCore.Qt.Horizontal)
- elif paging == 'vsplit':
- self._splitter.setOrientation(QtCore.Qt.Vertical)
- elif paging == 'inside':
- raise NotImplementedError("""switching to 'inside' paging not
- supported yet.""")
- else:
- raise ValueError("unknown paging method '%s'" % paging)
- self.paging = paging
-
- def _prompt_finished(self):
- """ Called immediately after a prompt is finished, i.e. when some input
- will be processed and a new prompt displayed.
- """
- self._control.setReadOnly(True)
- self._prompt_finished_hook()
-
- def _prompt_started(self):
- """ Called immediately after a new prompt is displayed.
- """
- # Temporarily disable the maximum block count to permit undo/redo and
- # to ensure that the prompt position does not change due to truncation.
- self._control.document().setMaximumBlockCount(0)
- self._control.setUndoRedoEnabled(True)
-
- # Work around bug in QPlainTextEdit: input method is not re-enabled
- # when read-only is disabled.
- self._control.setReadOnly(False)
- self._control.setAttribute(QtCore.Qt.WA_InputMethodEnabled, True)
-
- if not self._reading:
- self._executing = False
- self._prompt_started_hook()
-
- # If the input buffer has changed while executing, load it.
- if self._input_buffer_pending:
- self.input_buffer = self._input_buffer_pending
- self._input_buffer_pending = ''
-
- self._control.moveCursor(QtGui.QTextCursor.End)
-
- def _readline(self, prompt='', callback=None):
- """ Reads one line of input from the user.
-
- Parameters
- ----------
- prompt : str, optional
- The prompt to print before reading the line.
-
- callback : callable, optional
- A callback to execute with the read line. If not specified, input is
- read *synchronously* and this method does not return until it has
- been read.
-
- Returns
- -------
- If a callback is specified, returns nothing. Otherwise, returns the
- input string with the trailing newline stripped.
- """
- if self._reading:
- raise RuntimeError('Cannot read a line. Widget is already reading.')
-
- if not callback and not self.isVisible():
- # If the user cannot see the widget, this function cannot return.
- raise RuntimeError('Cannot synchronously read a line if the widget '
- 'is not visible!')
-
- self._reading = True
- self._show_prompt(prompt, newline=False)
-
- if callback is None:
- self._reading_callback = None
- while self._reading:
- QtCore.QCoreApplication.processEvents()
- return self._get_input_buffer(force=True).rstrip('\n')
-
- else:
- self._reading_callback = lambda: \
- callback(self._get_input_buffer(force=True).rstrip('\n'))
-
- def _set_continuation_prompt(self, prompt, html=False):
- """ Sets the continuation prompt.
-
- Parameters
- ----------
- prompt : str
- The prompt to show when more input is needed.
-
- html : bool, optional (default False)
- If set, the prompt will be inserted as formatted HTML. Otherwise,
- the prompt will be treated as plain text, though ANSI color codes
- will be handled.
- """
- if html:
- self._continuation_prompt_html = prompt
- else:
- self._continuation_prompt = prompt
- self._continuation_prompt_html = None
-
- def _set_cursor(self, cursor):
- """ Convenience method to set the current cursor.
- """
- self._control.setTextCursor(cursor)
-
- def _set_top_cursor(self, cursor):
- """ Scrolls the viewport so that the specified cursor is at the top.
- """
- scrollbar = self._control.verticalScrollBar()
- scrollbar.setValue(scrollbar.maximum())
- original_cursor = self._control.textCursor()
- self._control.setTextCursor(cursor)
- self._control.ensureCursorVisible()
- self._control.setTextCursor(original_cursor)
-
- def _show_prompt(self, prompt=None, html=False, newline=True):
- """ Writes a new prompt at the end of the buffer.
-
- Parameters
- ----------
- prompt : str, optional
- The prompt to show. If not specified, the previous prompt is used.
-
- html : bool, optional (default False)
- Only relevant when a prompt is specified. If set, the prompt will
- be inserted as formatted HTML. Otherwise, the prompt will be treated
- as plain text, though ANSI color codes will be handled.
-
- newline : bool, optional (default True)
- If set, a new line will be written before showing the prompt if
- there is not already a newline at the end of the buffer.
- """
- # Save the current end position to support _append*(before_prompt=True).
- self._flush_pending_stream()
- cursor = self._get_end_cursor()
- self._append_before_prompt_pos = cursor.position()
-
- # Insert a preliminary newline, if necessary.
- if newline and cursor.position() > 0:
- cursor.movePosition(QtGui.QTextCursor.Left,
- QtGui.QTextCursor.KeepAnchor)
- if cursor.selection().toPlainText() != '\n':
- self._append_block()
- self._append_before_prompt_pos += 1
-
- # Write the prompt.
- self._append_plain_text(self._prompt_sep)
- if prompt is None:
- if self._prompt_html is None:
- self._append_plain_text(self._prompt)
- else:
- self._append_html(self._prompt_html)
- else:
- if html:
- self._prompt = self._append_html_fetching_plain_text(prompt)
- self._prompt_html = prompt
- else:
- self._append_plain_text(prompt)
- self._prompt = prompt
- self._prompt_html = None
-
- self._flush_pending_stream()
- self._prompt_pos = self._get_end_cursor().position()
- self._prompt_started()
-
- #------ Signal handlers ----------------------------------------------------
-
- def _adjust_scrollbars(self):
- """ Expands the vertical scrollbar beyond the range set by Qt.
- """
- # This code is adapted from _q_adjustScrollbars in qplaintextedit.cpp
- # and qtextedit.cpp.
- document = self._control.document()
- scrollbar = self._control.verticalScrollBar()
- viewport_height = self._control.viewport().height()
- if isinstance(self._control, QtGui.QPlainTextEdit):
- maximum = max(0, document.lineCount() - 1)
- step = viewport_height / self._control.fontMetrics().lineSpacing()
- else:
- # QTextEdit does not do line-based layout and blocks will not in
- # general have the same height. Therefore it does not make sense to
- # attempt to scroll in line height increments.
- maximum = document.size().height()
- step = viewport_height
- diff = maximum - scrollbar.maximum()
- scrollbar.setRange(0, maximum)
- scrollbar.setPageStep(step)
-
- # Compensate for undesirable scrolling that occurs automatically due to
- # maximumBlockCount() text truncation.
- if diff < 0 and document.blockCount() == document.maximumBlockCount():
- scrollbar.setValue(scrollbar.value() + diff)
-
- def _custom_context_menu_requested(self, pos):
- """ Shows a context menu at the given QPoint (in widget coordinates).
- """
- menu = self._context_menu_make(pos)
- menu.exec_(self._control.mapToGlobal(pos))
diff --git a/jupyter_qtconsole/console/frontend_widget.py b/jupyter_qtconsole/console/frontend_widget.py
deleted file mode 100644
index 61dfc64..0000000
--- a/jupyter_qtconsole/console/frontend_widget.py
+++ /dev/null
@@ -1,811 +0,0 @@
-"""Frontend widget for the Qt Console"""
-
-# Copyright (c) IPython Development Team.
-# Distributed under the terms of the Modified BSD License.
-
-from __future__ import print_function
-
-from collections import namedtuple
-import sys
-import uuid
-
-from IPython.external import qt
-from IPython.external.qt import QtCore, QtGui
-from IPython.utils import py3compat
-from IPython.utils.importstring import import_item
-
-from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
-from IPython.core.inputtransformer import classic_prompt
-from IPython.core.oinspect import call_tip
-from IPython.qt.base_frontend_mixin import BaseFrontendMixin
-from IPython.utils.traitlets import Any, Bool, Instance, Unicode, DottedObjectName
-from .bracket_matcher import BracketMatcher
-from .call_tip_widget import CallTipWidget
-from .history_console_widget import HistoryConsoleWidget
-from .pygments_highlighter import PygmentsHighlighter
-
-
-class FrontendHighlighter(PygmentsHighlighter):
- """ A PygmentsHighlighter that understands and ignores prompts.
- """
-
- def __init__(self, frontend, lexer=None):
- super(FrontendHighlighter, self).__init__(frontend._control.document(), lexer=lexer)
- self._current_offset = 0
- self._frontend = frontend
- self.highlighting_on = False
-
- def highlightBlock(self, string):
- """ Highlight a block of text. Reimplemented to highlight selectively.
- """
- if not self.highlighting_on:
- return
-
- # The input to this function is a unicode string that may contain
- # paragraph break characters, non-breaking spaces, etc. Here we acquire
- # the string as plain text so we can compare it.
- current_block = self.currentBlock()
- string = self._frontend._get_block_plain_text(current_block)
-
- # Decide whether to check for the regular or continuation prompt.
- if current_block.contains(self._frontend._prompt_pos):
- prompt = self._frontend._prompt
- else:
- prompt = self._frontend._continuation_prompt
-
- # Only highlight if we can identify a prompt, but make sure not to
- # highlight the prompt.
- if string.startswith(prompt):
- self._current_offset = len(prompt)
- string = string[len(prompt):]
- super(FrontendHighlighter, self).highlightBlock(string)
-
- def rehighlightBlock(self, block):
- """ Reimplemented to temporarily enable highlighting if disabled.
- """
- old = self.highlighting_on
- self.highlighting_on = True
- super(FrontendHighlighter, self).rehighlightBlock(block)
- self.highlighting_on = old
-
- def setFormat(self, start, count, format):
- """ Reimplemented to highlight selectively.
- """
- start += self._current_offset
- super(FrontendHighlighter, self).setFormat(start, count, format)
-
-
-class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
- """ A Qt frontend for a generic Python kernel.
- """
-
- # The text to show when the kernel is (re)started.
- banner = Unicode(config=True)
- kernel_banner = Unicode()
- # Whether to show the banner
- _display_banner = Bool(False)
-
- # An option and corresponding signal for overriding the default kernel
- # interrupt behavior.
- custom_interrupt = Bool(False)
- custom_interrupt_requested = QtCore.Signal()
-
- # An option and corresponding signals for overriding the default kernel
- # restart behavior.
- custom_restart = Bool(False)
- custom_restart_kernel_died = QtCore.Signal(float)
- custom_restart_requested = QtCore.Signal()
-
- # Whether to automatically show calltips on open-parentheses.
- enable_calltips = Bool(True, config=True,
- help="Whether to draw information calltips on open-parentheses.")
-
- clear_on_kernel_restart = Bool(True, config=True,
- help="Whether to clear the console when the kernel is restarted")
-
- confirm_restart = Bool(True, config=True,
- help="Whether to ask for user confirmation when restarting kernel")
-
- lexer_class = DottedObjectName(config=True,
- help="The pygments lexer class to use."
- )
- def _lexer_class_changed(self, name, old, new):
- lexer_class = import_item(new)
- self.lexer = lexer_class()
-
- def _lexer_class_default(self):
- if py3compat.PY3:
- return 'pygments.lexers.Python3Lexer'
- else:
- return 'pygments.lexers.PythonLexer'
-
- lexer = Any()
- def _lexer_default(self):
- lexer_class = import_item(self.lexer_class)
- return lexer_class()
-
- # Emitted when a user visible 'execute_request' has been submitted to the
- # kernel from the FrontendWidget. Contains the code to be executed.
- executing = QtCore.Signal(object)
-
- # Emitted when a user-visible 'execute_reply' has been received from the
- # kernel and processed by the FrontendWidget. Contains the response message.
- executed = QtCore.Signal(object)
-
- # Emitted when an exit request has been received from the kernel.
- exit_requested = QtCore.Signal(object)
-
- # Protected class variables.
- _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
- logical_line_transforms=[],
- python_line_transforms=[],
- )
- _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
- _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
- _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
- _input_splitter_class = InputSplitter
- _local_kernel = False
- _highlighter = Instance(FrontendHighlighter, allow_none=True)
-
- #---------------------------------------------------------------------------
- # 'object' interface
- #---------------------------------------------------------------------------
-
- def __init__(self, *args, **kw):
- super(FrontendWidget, self).__init__(*args, **kw)
- # FIXME: remove this when PySide min version is updated past 1.0.7
- # forcefully disable calltips if PySide is < 1.0.7, because they crash
- if qt.QT_API == qt.QT_API_PYSIDE:
- import PySide
- if PySide.__version_info__ < (1,0,7):
- self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
- self.enable_calltips = False
-
- # FrontendWidget protected variables.
- self._bracket_matcher = BracketMatcher(self._control)
- self._call_tip_widget = CallTipWidget(self._control)
- self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
- self._hidden = False
- self._highlighter = FrontendHighlighter(self, lexer=self.lexer)
- self._input_splitter = self._input_splitter_class()
- self._kernel_manager = None
- self._kernel_client = None
- self._request_info = {}
- self._request_info['execute'] = {};
- self._callback_dict = {}
- self._display_banner = True
-
- # Configure the ConsoleWidget.
- self.tab_width = 4
- self._set_continuation_prompt('... ')
-
- # Configure the CallTipWidget.
- self._call_tip_widget.setFont(self.font)
- self.font_changed.connect(self._call_tip_widget.setFont)
-
- # Configure actions.
- action = self._copy_raw_action
- key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
- action.setEnabled(False)
- action.setShortcut(QtGui.QKeySequence(key))
- action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
- action.triggered.connect(self.copy_raw)
- self.copy_available.connect(action.setEnabled)
- self.addAction(action)
-
- # Connect signal handlers.
- document = self._control.document()
- document.contentsChange.connect(self._document_contents_change)
-
- # Set flag for whether we are connected via localhost.
- self._local_kernel = kw.get('local_kernel',
- FrontendWidget._local_kernel)
-
- # Whether or not a clear_output call is pending new output.
- self._pending_clearoutput = False
-
- #---------------------------------------------------------------------------
- # 'ConsoleWidget' public interface
- #---------------------------------------------------------------------------
-
- def copy(self):
- """ Copy the currently selected text to the clipboard, removing prompts.
- """
- if self._page_control is not None and self._page_control.hasFocus():
- self._page_control.copy()
- elif self._control.hasFocus():
- text = self._control.textCursor().selection().toPlainText()
- if text:
- was_newline = text[-1] == '\n'
- text = self._prompt_transformer.transform_cell(text)
- if not was_newline: # user doesn't need newline
- text = text[:-1]
- QtGui.QApplication.clipboard().setText(text)
- else:
- self.log.debug("frontend widget : unknown copy target")
-
- #---------------------------------------------------------------------------
- # 'ConsoleWidget' abstract interface
- #---------------------------------------------------------------------------
-
- def _is_complete(self, source, interactive):
- """ Returns whether 'source' can be completely processed and a new
- prompt created. When triggered by an Enter/Return key press,
- 'interactive' is True; otherwise, it is False.
- """
- self._input_splitter.reset()
- try:
- complete = self._input_splitter.push(source)
- except SyntaxError:
- return True
- if interactive:
- complete = not self._input_splitter.push_accepts_more()
- return complete
-
- def _execute(self, source, hidden):
- """ Execute 'source'. If 'hidden', do not show any output.
-
- See parent class :meth:`execute` docstring for full details.
- """
- msg_id = self.kernel_client.execute(source, hidden)
- self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
- self._hidden = hidden
- if not hidden:
- self.executing.emit(source)
-
- def _prompt_started_hook(self):
- """ Called immediately after a new prompt is displayed.
- """
- if not self._reading:
- self._highlighter.highlighting_on = True
-
- def _prompt_finished_hook(self):
- """ Called immediately after a prompt is finished, i.e. when some input
- will be processed and a new prompt displayed.
- """
- # Flush all state from the input splitter so the next round of
- # reading input starts with a clean buffer.
- self._input_splitter.reset()
-
- if not self._reading:
- self._highlighter.highlighting_on = False
-
- def _tab_pressed(self):
- """ Called when the tab key is pressed. Returns whether to continue
- processing the event.
- """
- # Perform tab completion if:
- # 1) The cursor is in the input buffer.
- # 2) There is a non-whitespace character before the cursor.
- text = self._get_input_buffer_cursor_line()
- if text is None:
- return False
- complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
- if complete:
- self._complete()
- return not complete
-
- #---------------------------------------------------------------------------
- # 'ConsoleWidget' protected interface
- #---------------------------------------------------------------------------
-
- def _context_menu_make(self, pos):
- """ Reimplemented to add an action for raw copy.
- """
- menu = super(FrontendWidget, self)._context_menu_make(pos)
- for before_action in menu.actions():
- if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
- QtGui.QKeySequence.ExactMatch:
- menu.insertAction(before_action, self._copy_raw_action)
- break
- return menu
-
- def request_interrupt_kernel(self):
- if self._executing:
- self.interrupt_kernel()
-
- def request_restart_kernel(self):
- message = 'Are you sure you want to restart the kernel?'
- self.restart_kernel(message, now=False)
-
- def _event_filter_console_keypress(self, event):
- """ Reimplemented for execution interruption and smart backspace.
- """
- key = event.key()
- if self._control_key_down(event.modifiers(), include_command=False):
-
- if key == QtCore.Qt.Key_C and self._executing:
- self.request_interrupt_kernel()
- return True
-
- elif key == QtCore.Qt.Key_Period:
- self.request_restart_kernel()
- return True
-
- elif not event.modifiers() & QtCore.Qt.AltModifier:
-
- # Smart backspace: remove four characters in one backspace if:
- # 1) everything left of the cursor is whitespace
- # 2) the four characters immediately left of the cursor are spaces
- if key == QtCore.Qt.Key_Backspace:
- col = self._get_input_buffer_cursor_column()
- cursor = self._control.textCursor()
- if col > 3 and not cursor.hasSelection():
- text = self._get_input_buffer_cursor_line()[:col]
- if text.endswith(' ') and not text.strip():
- cursor.movePosition(QtGui.QTextCursor.Left,
- QtGui.QTextCursor.KeepAnchor, 4)
- cursor.removeSelectedText()
- return True
-
- return super(FrontendWidget, self)._event_filter_console_keypress(event)
-
- def _insert_continuation_prompt(self, cursor):
- """ Reimplemented for auto-indentation.
- """
- super(FrontendWidget, self)._insert_continuation_prompt(cursor)
- cursor.insertText(' ' * self._input_splitter.indent_spaces)
-
- #---------------------------------------------------------------------------
- # 'BaseFrontendMixin' abstract interface
- #---------------------------------------------------------------------------
- def _handle_clear_output(self, msg):
- """Handle clear output messages."""
- if self.include_output(msg):
- wait = msg['content'].get('wait', True)
- if wait:
- self._pending_clearoutput = True
- else:
- self.clear_output()
-
- def _silent_exec_callback(self, expr, callback):
- """Silently execute `expr` in the kernel and call `callback` with reply
-
- the `expr` is evaluated silently in the kernel (without) output in
- the frontend. Call `callback` with the
- `repr
%s
" % msg,
- before_prompt=False
- )
-
- def _handle_kernel_died(self, since_last_heartbeat):
- """Handle the kernel's death (if we do not own the kernel).
- """
- self.log.warn("kernel died: %s", since_last_heartbeat)
- if self.custom_restart:
- self.custom_restart_kernel_died.emit(since_last_heartbeat)
- else:
- self._kernel_restarted_message(died=True)
- self.reset()
-
- def _handle_kernel_restarted(self, died=True):
- """Notice that the autorestarter restarted the kernel.
-
- There's nothing to do but show a message.
- """
- self.log.warn("kernel restarted")
- self._kernel_restarted_message(died=died)
- self.reset()
-
- def _handle_inspect_reply(self, rep):
- """Handle replies for call tips."""
- self.log.debug("oinfo: %s", rep.get('content', ''))
- cursor = self._get_cursor()
- info = self._request_info.get('call_tip')
- if info and info.id == rep['parent_header']['msg_id'] and \
- info.pos == cursor.position():
- content = rep['content']
- if content.get('status') == 'ok' and content.get('found', False):
- self._call_tip_widget.show_inspect_data(content)
-
- def _handle_execute_result(self, msg):
- """ Handle display hook output.
- """
- self.log.debug("execute_result: %s", msg.get('content', ''))
- if self.include_output(msg):
- self.flush_clearoutput()
- text = msg['content']['data']
- self._append_plain_text(text + '\n', before_prompt=True)
-
- def _handle_stream(self, msg):
- """ Handle stdout, stderr, and stdin.
- """
- self.log.debug("stream: %s", msg.get('content', ''))
- if self.include_output(msg):
- self.flush_clearoutput()
- self.append_stream(msg['content']['text'])
-
- def _handle_shutdown_reply(self, msg):
- """ Handle shutdown signal, only if from other console.
- """
- self.log.info("shutdown: %s", msg.get('content', ''))
- restart = msg.get('content', {}).get('restart', False)
- if not self._hidden and not self.from_here(msg):
- # got shutdown reply, request came from session other than ours
- if restart:
- # someone restarted the kernel, handle it
- self._handle_kernel_restarted(died=False)
- else:
- # kernel was shutdown permanently
- # this triggers exit_requested if the kernel was local,
- # and a dialog if the kernel was remote,
- # so we don't suddenly clear the qtconsole without asking.
- if self._local_kernel:
- self.exit_requested.emit(self)
- else:
- title = self.window().windowTitle()
- reply = QtGui.QMessageBox.question(self, title,
- "Kernel has been shutdown permanently. "
- "Close the Console?",
- QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
- if reply == QtGui.QMessageBox.Yes:
- self.exit_requested.emit(self)
-
- def _handle_status(self, msg):
- """Handle status message"""
- # This is where a busy/idle indicator would be triggered,
- # when we make one.
- state = msg['content'].get('execution_state', '')
- if state == 'starting':
- # kernel started while we were running
- if self._executing:
- self._handle_kernel_restarted(died=True)
- elif state == 'idle':
- pass
- elif state == 'busy':
- pass
-
- def _started_channels(self):
- """ Called when the KernelManager channels have started listening or
- when the frontend is assigned an already listening KernelManager.
- """
- self.reset(clear=True)
-
- #---------------------------------------------------------------------------
- # 'FrontendWidget' public interface
- #---------------------------------------------------------------------------
-
- def copy_raw(self):
- """ Copy the currently selected text to the clipboard without attempting
- to remove prompts or otherwise alter the text.
- """
- self._control.copy()
-
- def execute_file(self, path, hidden=False):
- """ Attempts to execute file with 'path'. If 'hidden', no output is
- shown.
- """
- self.execute('execfile(%r)' % path, hidden=hidden)
-
- def interrupt_kernel(self):
- """ Attempts to interrupt the running kernel.
-
- Also unsets _reading flag, to avoid runtime errors
- if raw_input is called again.
- """
- if self.custom_interrupt:
- self._reading = False
- self.custom_interrupt_requested.emit()
- elif self.kernel_manager:
- self._reading = False
- self.kernel_manager.interrupt_kernel()
- else:
- self._append_plain_text('Cannot interrupt a kernel I did not start.\n')
-
- def reset(self, clear=False):
- """ Resets the widget to its initial state if ``clear`` parameter
- is True, otherwise
- prints a visual indication of the fact that the kernel restarted, but
- does not clear the traces from previous usage of the kernel before it
- was restarted. With ``clear=True``, it is similar to ``%clear``, but
- also re-writes the banner and aborts execution if necessary.
- """
- if self._executing:
- self._executing = False
- self._request_info['execute'] = {}
- self._reading = False
- self._highlighter.highlighting_on = False
-
- if clear:
- self._control.clear()
- if self._display_banner:
- self._append_plain_text(self.banner)
- if self.kernel_banner:
- self._append_plain_text(self.kernel_banner)
-
- # update output marker for stdout/stderr, so that startup
- # messages appear after banner:
- self._append_before_prompt_pos = self._get_cursor().position()
- self._show_interpreter_prompt()
-
- def restart_kernel(self, message, now=False):
- """ Attempts to restart the running kernel.
- """
- # FIXME: now should be configurable via a checkbox in the dialog. Right
- # now at least the heartbeat path sets it to True and the manual restart
- # to False. But those should just be the pre-selected states of a
- # checkbox that the user could override if so desired. But I don't know
- # enough Qt to go implementing the checkbox now.
-
- if self.custom_restart:
- self.custom_restart_requested.emit()
- return
-
- if self.kernel_manager:
- # Pause the heart beat channel to prevent further warnings.
- self.kernel_client.hb_channel.pause()
-
- # Prompt the user to restart the kernel. Un-pause the heartbeat if
- # they decline. (If they accept, the heartbeat will be un-paused
- # automatically when the kernel is restarted.)
- if self.confirm_restart:
- buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
- result = QtGui.QMessageBox.question(self, 'Restart kernel?',
- message, buttons)
- do_restart = result == QtGui.QMessageBox.Yes
- else:
- # confirm_restart is False, so we don't need to ask user
- # anything, just do the restart
- do_restart = True
- if do_restart:
- try:
- self.kernel_manager.restart_kernel(now=now)
- except RuntimeError as e:
- self._append_plain_text(
- 'Error restarting kernel: %s\n' % e,
- before_prompt=True
- )
- else:
- self._append_html("
Restarting kernel...\n
",
- before_prompt=True,
- )
- else:
- self.kernel_client.hb_channel.unpause()
-
- else:
- self._append_plain_text(
- 'Cannot restart a Kernel I did not start\n',
- before_prompt=True
- )
-
- def append_stream(self, text):
- """Appends text to the output stream."""
- # Most consoles treat tabs as being 8 space characters. Convert tabs
- # to spaces so that output looks as expected regardless of this
- # widget's tab width.
- text = text.expandtabs(8)
- self._append_plain_text(text, before_prompt=True)
- self._control.moveCursor(QtGui.QTextCursor.End)
-
- def flush_clearoutput(self):
- """If a clearoutput is pending, execute it."""
- if self._pending_clearoutput:
- self._pending_clearoutput = False
- self.clear_output()
-
- def clear_output(self):
- """Clears the current line of output."""
- cursor = self._control.textCursor()
- cursor.beginEditBlock()
- cursor.movePosition(cursor.StartOfLine, cursor.KeepAnchor)
- cursor.insertText('')
- cursor.endEditBlock()
-
- #---------------------------------------------------------------------------
- # 'FrontendWidget' protected interface
- #---------------------------------------------------------------------------
-
- def _auto_call_tip(self):
- """Trigger call tip automatically on open parenthesis
-
- Call tips can be requested explcitly with `_call_tip`.
- """
- cursor = self._get_cursor()
- cursor.movePosition(QtGui.QTextCursor.Left)
- if cursor.document().characterAt(cursor.position()) == '(':
- # trigger auto call tip on open paren
- self._call_tip()
-
- def _call_tip(self):
- """Shows a call tip, if appropriate, at the current cursor location."""
- # Decide if it makes sense to show a call tip
- if not self.enable_calltips or not self.kernel_client.shell_channel.is_alive():
- return False
- cursor_pos = self._get_input_buffer_cursor_pos()
- code = self.input_buffer
- # Send the metadata request to the kernel
- msg_id = self.kernel_client.inspect(code, cursor_pos)
- pos = self._get_cursor().position()
- self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
- return True
-
- def _complete(self):
- """ Performs completion at the current cursor location.
- """
- # Send the completion request to the kernel
- msg_id = self.kernel_client.complete(
- code=self.input_buffer,
- cursor_pos=self._get_input_buffer_cursor_pos(),
- )
- pos = self._get_cursor().position()
- info = self._CompletionRequest(msg_id, pos)
- self._request_info['complete'] = info
-
- def _process_execute_abort(self, msg):
- """ Process a reply for an aborted execution request.
- """
- self._append_plain_text("ERROR: execution aborted\n")
-
- def _process_execute_error(self, msg):
- """ Process a reply for an execution request that resulted in an error.
- """
- content = msg['content']
- # If a SystemExit is passed along, this means exit() was called - also
- # all the ipython %exit magic syntax of '-k' to be used to keep
- # the kernel running
- if content['ename']=='SystemExit':
- keepkernel = content['evalue']=='-k' or content['evalue']=='True'
- self._keep_kernel_on_exit = keepkernel
- self.exit_requested.emit(self)
- else:
- traceback = ''.join(content['traceback'])
- self._append_plain_text(traceback)
-
- def _process_execute_ok(self, msg):
- """ Process a reply for a successful execution request.
- """
- payload = msg['content']['payload']
- for item in payload:
- if not self._process_execute_payload(item):
- warning = 'Warning: received unknown payload of type %s'
- print(warning % repr(item['source']))
-
- def _process_execute_payload(self, item):
- """ Process a single payload item from the list of payload items in an
- execution reply. Returns whether the payload was handled.
- """
- # The basic FrontendWidget doesn't handle payloads, as they are a
- # mechanism for going beyond the standard Python interpreter model.
- return False
-
- def _show_interpreter_prompt(self):
- """ Shows a prompt for the interpreter.
- """
- self._show_prompt('>>> ')
-
- def _show_interpreter_prompt_for_reply(self, msg):
- """ Shows a prompt for the interpreter given an 'execute_reply' message.
- """
- self._show_interpreter_prompt()
-
- #------ Signal handlers ----------------------------------------------------
-
- def _document_contents_change(self, position, removed, added):
- """ Called whenever the document's content changes. Display a call tip
- if appropriate.
- """
- # Calculate where the cursor should be *after* the change:
- position += added
-
- document = self._control.document()
- if position == self._get_cursor().position():
- self._auto_call_tip()
-
- #------ Trait default initializers -----------------------------------------
-
- def _banner_default(self):
- """ Returns the standard Python banner.
- """
- banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
- '"license" for more information.'
- return banner % (sys.version, sys.platform)
diff --git a/jupyter_qtconsole/console/history_console_widget.py b/jupyter_qtconsole/console/history_console_widget.py
deleted file mode 100644
index e4af363..0000000
--- a/jupyter_qtconsole/console/history_console_widget.py
+++ /dev/null
@@ -1,305 +0,0 @@
-# System library imports
-from IPython.external.qt import QtGui
-
-# Local imports
-from IPython.utils.py3compat import unicode_type
-from IPython.utils.traitlets import Bool
-from .console_widget import ConsoleWidget
-
-
-class HistoryConsoleWidget(ConsoleWidget):
- """ A ConsoleWidget that keeps a history of the commands that have been
- executed and provides a readline-esque interface to this history.
- """
-
- #------ Configuration ------------------------------------------------------
-
- # If enabled, the input buffer will become "locked" to history movement when
- # an edit is made to a multi-line input buffer. To override the lock, use
- # Shift in conjunction with the standard history cycling keys.
- history_lock = Bool(False, config=True)
-
- #---------------------------------------------------------------------------
- # 'object' interface
- #---------------------------------------------------------------------------
-
- def __init__(self, *args, **kw):
- super(HistoryConsoleWidget, self).__init__(*args, **kw)
-
- # HistoryConsoleWidget protected variables.
- self._history = []
- self._history_edits = {}
- self._history_index = 0
- self._history_prefix = ''
-
- #---------------------------------------------------------------------------
- # 'ConsoleWidget' public interface
- #---------------------------------------------------------------------------
-
- def execute(self, source=None, hidden=False, interactive=False):
- """ Reimplemented to the store history.
- """
- if not hidden:
- history = self.input_buffer if source is None else source
-
- executed = super(HistoryConsoleWidget, self).execute(
- source, hidden, interactive)
-
- if executed and not hidden:
- # Save the command unless it was an empty string or was identical
- # to the previous command.
- history = history.rstrip()
- if history and (not self._history or self._history[-1] != history):
- self._history.append(history)
-
- # Emulate readline: reset all history edits.
- self._history_edits = {}
-
- # Move the history index to the most recent item.
- self._history_index = len(self._history)
-
- return executed
-
- #---------------------------------------------------------------------------
- # 'ConsoleWidget' abstract interface
- #---------------------------------------------------------------------------
-
- def _up_pressed(self, shift_modifier):
- """ Called when the up key is pressed. Returns whether to continue
- processing the event.
- """
- prompt_cursor = self._get_prompt_cursor()
- if self._get_cursor().blockNumber() == prompt_cursor.blockNumber():
- # Bail out if we're locked.
- if self._history_locked() and not shift_modifier:
- return False
-
- # Set a search prefix based on the cursor position.
- col = self._get_input_buffer_cursor_column()
- input_buffer = self.input_buffer
- # use the *shortest* of the cursor column and the history prefix
- # to determine if the prefix has changed
- n = min(col, len(self._history_prefix))
-
- # prefix changed, restart search from the beginning
- if (self._history_prefix[:n] != input_buffer[:n]):
- self._history_index = len(self._history)
-
- # the only time we shouldn't set the history prefix
- # to the line up to the cursor is if we are already
- # in a simple scroll (no prefix),
- # and the cursor is at the end of the first line
-
- # check if we are at the end of the first line
- c = self._get_cursor()
- current_pos = c.position()
- c.movePosition(QtGui.QTextCursor.EndOfLine)
- at_eol = (c.position() == current_pos)
-
- if self._history_index == len(self._history) or \
- not (self._history_prefix == '' and at_eol) or \
- not (self._get_edited_history(self._history_index)[:col] == input_buffer[:col]):
- self._history_prefix = input_buffer[:col]
-
- # Perform the search.
- self.history_previous(self._history_prefix,
- as_prefix=not shift_modifier)
-
- # Go to the first line of the prompt for seemless history scrolling.
- # Emulate readline: keep the cursor position fixed for a prefix
- # search.
- cursor = self._get_prompt_cursor()
- if self._history_prefix:
- cursor.movePosition(QtGui.QTextCursor.Right,
- n=len(self._history_prefix))
- else:
- cursor.movePosition(QtGui.QTextCursor.EndOfLine)
- self._set_cursor(cursor)
-
- return False
-
- return True
-
- def _down_pressed(self, shift_modifier):
- """ Called when the down key is pressed. Returns whether to continue
- processing the event.
- """
- end_cursor = self._get_end_cursor()
- if self._get_cursor().blockNumber() == end_cursor.blockNumber():
- # Bail out if we're locked.
- if self._history_locked() and not shift_modifier:
- return False
-
- # Perform the search.
- replaced = self.history_next(self._history_prefix,
- as_prefix=not shift_modifier)
-
- # Emulate readline: keep the cursor position fixed for a prefix
- # search. (We don't need to move the cursor to the end of the buffer
- # in the other case because this happens automatically when the
- # input buffer is set.)
- if self._history_prefix and replaced:
- cursor = self._get_prompt_cursor()
- cursor.movePosition(QtGui.QTextCursor.Right,
- n=len(self._history_prefix))
- self._set_cursor(cursor)
-
- return False
-
- return True
-
- #---------------------------------------------------------------------------
- # 'HistoryConsoleWidget' public interface
- #---------------------------------------------------------------------------
-
- def history_previous(self, substring='', as_prefix=True):
- """ If possible, set the input buffer to a previous history item.
-
- Parameters
- ----------
- substring : str, optional
- If specified, search for an item with this substring.
- as_prefix : bool, optional
- If True, the substring must match at the beginning (default).
-
- Returns
- -------
- Whether the input buffer was changed.
- """
- index = self._history_index
- replace = False
- while index > 0:
- index -= 1
- history = self._get_edited_history(index)
- if (as_prefix and history.startswith(substring)) \
- or (not as_prefix and substring in history):
- replace = True
- break
-
- if replace:
- self._store_edits()
- self._history_index = index
- self.input_buffer = history
-
- return replace
-
- def history_next(self, substring='', as_prefix=True):
- """ If possible, set the input buffer to a subsequent history item.
-
- Parameters
- ----------
- substring : str, optional
- If specified, search for an item with this substring.
- as_prefix : bool, optional
- If True, the substring must match at the beginning (default).
-
- Returns
- -------
- Whether the input buffer was changed.
- """
- index = self._history_index
- replace = False
- while index < len(self._history):
- index += 1
- history = self._get_edited_history(index)
- if (as_prefix and history.startswith(substring)) \
- or (not as_prefix and substring in history):
- replace = True
- break
-
- if replace:
- self._store_edits()
- self._history_index = index
- self.input_buffer = history
-
- return replace
-
- def history_tail(self, n=10):
- """ Get the local history list.
-
- Parameters
- ----------
- n : int
- The (maximum) number of history items to get.
- """
- return self._history[-n:]
-
- def _request_update_session_history_length(self):
- msg_id = self.kernel_client.execute('',
- silent=True,
- user_expressions={
- 'hlen':'len(get_ipython().history_manager.input_hist_raw)',
- }
- )
- self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'save_magic')
-
- def _handle_execute_reply(self, msg):
- """ Handles replies for code execution, here only session history length
- """
- msg_id = msg['parent_header']['msg_id']
- info = self._request_info['execute'].pop(msg_id,None)
- if info and info.kind == 'save_magic' and not self._hidden:
- content = msg['content']
- status = content['status']
- if status == 'ok':
- self._max_session_history = int(
- content['user_expressions']['hlen']['data']['text/plain']
- )
-
- def save_magic(self):
- # update the session history length
- self._request_update_session_history_length()
-
- file_name,extFilter = QtGui.QFileDialog.getSaveFileName(self,
- "Enter A filename",
- filter='Python File (*.py);; All files (*.*)'
- )
-
- # let's the user search/type for a file name, while the history length
- # is fetched
-
- if file_name:
- hist_range, ok = QtGui.QInputDialog.getText(self,
- 'Please enter an interval of command to save',
- 'Saving commands:',
- text=str('1-'+str(self._max_session_history))
- )
- if ok:
- self.execute("%save"+" "+file_name+" "+str(hist_range))
-
- #---------------------------------------------------------------------------
- # 'HistoryConsoleWidget' protected interface
- #---------------------------------------------------------------------------
-
- def _history_locked(self):
- """ Returns whether history movement is locked.
- """
- return (self.history_lock and
- (self._get_edited_history(self._history_index) !=
- self.input_buffer) and
- (self._get_prompt_cursor().blockNumber() !=
- self._get_end_cursor().blockNumber()))
-
- def _get_edited_history(self, index):
- """ Retrieves a history item, possibly with temporary edits.
- """
- if index in self._history_edits:
- return self._history_edits[index]
- elif index == len(self._history):
- return unicode_type()
- return self._history[index]
-
- def _set_history(self, history):
- """ Replace the current history with a sequence of history items.
- """
- self._history = list(history)
- self._history_edits = {}
- self._history_index = len(self._history)
-
- def _store_edits(self):
- """ If there are edits to the current input buffer, store them.
- """
- current = self.input_buffer
- if self._history_index == len(self._history) or \
- self._history[self._history_index] != current:
- self._history_edits[self._history_index] = current
diff --git a/jupyter_qtconsole/console/ipython_widget.py b/jupyter_qtconsole/console/ipython_widget.py
deleted file mode 100644
index 930413b..0000000
--- a/jupyter_qtconsole/console/ipython_widget.py
+++ /dev/null
@@ -1,594 +0,0 @@
-"""A FrontendWidget that emulates the interface of the console IPython.
-
-This supports the additional functionality provided by the IPython kernel.
-"""
-
-# Copyright (c) IPython Development Team.
-# Distributed under the terms of the Modified BSD License.
-
-from collections import namedtuple
-import os.path
-import re
-from subprocess import Popen
-import sys
-import time
-from textwrap import dedent
-
-from IPython.external.qt import QtCore, QtGui
-
-from IPython.core.inputsplitter import IPythonInputSplitter
-from IPython.core.release import version
-from IPython.core.inputtransformer import ipy_prompt
-from IPython.utils.traitlets import Bool, Unicode
-from .frontend_widget import FrontendWidget
-from . import styles
-
-#-----------------------------------------------------------------------------
-# Constants
-#-----------------------------------------------------------------------------
-
-# Default strings to build and display input and output prompts (and separators
-# in between)
-default_in_prompt = 'In [%i]: '
-default_out_prompt = 'Out[%i]: '
-default_input_sep = '\n'
-default_output_sep = ''
-default_output_sep2 = ''
-
-# Base path for most payload sources.
-zmq_shell_source = 'IPython.kernel.zmq.zmqshell.ZMQInteractiveShell'
-
-if sys.platform.startswith('win'):
- default_editor = 'notepad'
-else:
- default_editor = ''
-
-#-----------------------------------------------------------------------------
-# IPythonWidget class
-#-----------------------------------------------------------------------------
-
-class IPythonWidget(FrontendWidget):
- """ A FrontendWidget for an IPython kernel.
- """
-
- # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
- # an editor is needed for a file. This overrides 'editor' and 'editor_line'
- # settings.
- custom_edit = Bool(False)
- custom_edit_requested = QtCore.Signal(object, object)
-
- editor = Unicode(default_editor, config=True,
- help="""
- A command for invoking a system text editor. If the string contains a
- {filename} format specifier, it will be used. Otherwise, the filename
- will be appended to the end the command.
- """)
-
- editor_line = Unicode(config=True,
- help="""
- The editor command to use when a specific line number is requested. The
- string should contain two format specifiers: {line} and {filename}. If
- this parameter is not specified, the line number option to the %edit
- magic will be ignored.
- """)
-
- style_sheet = Unicode(config=True,
- help="""
- A CSS stylesheet. The stylesheet can contain classes for:
- 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
- 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
- 3. IPython: .error, .in-prompt, .out-prompt, etc
- """)
-
- syntax_style = Unicode(config=True,
- help="""
- If not empty, use this Pygments style for syntax highlighting.
- Otherwise, the style sheet is queried for Pygments style
- information.
- """)
-
- # Prompts.
- in_prompt = Unicode(default_in_prompt, config=True)
- out_prompt = Unicode(default_out_prompt, config=True)
- input_sep = Unicode(default_input_sep, config=True)
- output_sep = Unicode(default_output_sep, config=True)
- output_sep2 = Unicode(default_output_sep2, config=True)
-
- # FrontendWidget protected class variables.
- _input_splitter_class = IPythonInputSplitter
- _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[ipy_prompt()],
- logical_line_transforms=[],
- python_line_transforms=[],
- )
-
- # IPythonWidget protected class variables.
- _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
- _payload_source_edit = 'edit_magic'
- _payload_source_exit = 'ask_exit'
- _payload_source_next_input = 'set_next_input'
- _payload_source_page = 'page'
- _retrying_history_request = False
- _starting = False
-
- #---------------------------------------------------------------------------
- # 'object' interface
- #---------------------------------------------------------------------------
-
- def __init__(self, *args, **kw):
- super(IPythonWidget, self).__init__(*args, **kw)
-
- # IPythonWidget protected variables.
- self._payload_handlers = {
- self._payload_source_edit : self._handle_payload_edit,
- self._payload_source_exit : self._handle_payload_exit,
- self._payload_source_page : self._handle_payload_page,
- self._payload_source_next_input : self._handle_payload_next_input }
- self._previous_prompt_obj = None
- self._keep_kernel_on_exit = None
-
- # Initialize widget styling.
- if self.style_sheet:
- self._style_sheet_changed()
- self._syntax_style_changed()
- else:
- self.set_default_style()
-
- self._guiref_loaded = False
-
- #---------------------------------------------------------------------------
- # 'BaseFrontendMixin' abstract interface
- #---------------------------------------------------------------------------
- def _handle_complete_reply(self, rep):
- """ Reimplemented to support IPython's improved completion machinery.
- """
- self.log.debug("complete: %s", rep.get('content', ''))
- cursor = self._get_cursor()
- info = self._request_info.get('complete')
- if info and info.id == rep['parent_header']['msg_id'] and \
- info.pos == cursor.position():
- content = rep['content']
- matches = content['matches']
- start = content['cursor_start']
- end = content['cursor_end']
-
- start = max(start, 0)
- end = max(end, start)
-
- # Move the control's cursor to the desired end point
- cursor_pos = self._get_input_buffer_cursor_pos()
- if end < cursor_pos:
- cursor.movePosition(QtGui.QTextCursor.Left,
- n=(cursor_pos - end))
- elif end > cursor_pos:
- cursor.movePosition(QtGui.QTextCursor.Right,
- n=(end - cursor_pos))
- # This line actually applies the move to control's cursor
- self._control.setTextCursor(cursor)
-
- offset = end - start
- # Move the local cursor object to the start of the match and
- # complete.
- cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
- self._complete_with_items(cursor, matches)
-
- def _handle_execute_reply(self, msg):
- """ Reimplemented to support prompt requests.
- """
- msg_id = msg['parent_header'].get('msg_id')
- info = self._request_info['execute'].get(msg_id)
- if info and info.kind == 'prompt':
- content = msg['content']
- if content['status'] == 'aborted':
- self._show_interpreter_prompt()
- else:
- number = content['execution_count'] + 1
- self._show_interpreter_prompt(number)
- self._request_info['execute'].pop(msg_id)
- else:
- super(IPythonWidget, self)._handle_execute_reply(msg)
-
- def _handle_history_reply(self, msg):
- """ Implemented to handle history tail replies, which are only supported
- by the IPython kernel.
- """
- content = msg['content']
- if 'history' not in content:
- self.log.error("History request failed: %r"%content)
- if content.get('status', '') == 'aborted' and \
- not self._retrying_history_request:
- # a *different* action caused this request to be aborted, so
- # we should try again.
- self.log.error("Retrying aborted history request")
- # prevent multiple retries of aborted requests:
- self._retrying_history_request = True
- # wait out the kernel's queue flush, which is currently timed at 0.1s
- time.sleep(0.25)
- self.kernel_client.history(hist_access_type='tail',n=1000)
- else:
- self._retrying_history_request = False
- return
- # reset retry flag
- self._retrying_history_request = False
- history_items = content['history']
- self.log.debug("Received history reply with %i entries", len(history_items))
- items = []
- last_cell = u""
- for _, _, cell in history_items:
- cell = cell.rstrip()
- if cell != last_cell:
- items.append(cell)
- last_cell = cell
- self._set_history(items)
-
- def _insert_other_input(self, cursor, content):
- """Insert function for input from other frontends"""
- cursor.beginEditBlock()
- start = cursor.position()
- n = content.get('execution_count', 0)
- cursor.insertText('\n')
- self._insert_html(cursor, self._make_in_prompt(n))
- cursor.insertText(content['code'])
- self._highlighter.rehighlightBlock(cursor.block())
- cursor.endEditBlock()
-
- def _handle_execute_input(self, msg):
- """Handle an execute_input message"""
- self.log.debug("execute_input: %s", msg.get('content', ''))
- if self.include_output(msg):
- self._append_custom(self._insert_other_input, msg['content'], before_prompt=True)
-
-
- def _handle_execute_result(self, msg):
- """Reimplemented for IPython-style "display hook"."""
- if self.include_output(msg):
- self.flush_clearoutput()
- content = msg['content']
- prompt_number = content.get('execution_count', 0)
- data = content['data']
- if 'text/plain' in data:
- self._append_plain_text(self.output_sep, True)
- self._append_html(self._make_out_prompt(prompt_number), True)
- text = data['text/plain']
- # If the repr is multiline, make sure we start on a new line,
- # so that its lines are aligned.
- if "\n" in text and not self.output_sep.endswith("\n"):
- self._append_plain_text('\n', True)
- self._append_plain_text(text + self.output_sep2, True)
-
- def _handle_display_data(self, msg):
- """The base handler for the ``display_data`` message."""
- # For now, we don't display data from other frontends, but we
- # eventually will as this allows all frontends to monitor the display
- # data. But we need to figure out how to handle this in the GUI.
- if self.include_output(msg):
- self.flush_clearoutput()
- data = msg['content']['data']
- metadata = msg['content']['metadata']
- # In the regular IPythonWidget, we simply print the plain text
- # representation.
- if 'text/plain' in data:
- text = data['text/plain']
- self._append_plain_text(text, True)
- # This newline seems to be needed for text and html output.
- self._append_plain_text(u'\n', True)
-
- def _handle_kernel_info_reply(self, rep):
- """Handle kernel info replies."""
- content = rep['content']
- if not self._guiref_loaded:
- if content.get('implementation') == 'ipython':
- self._load_guiref_magic()
- self._guiref_loaded = True
-
- self.kernel_banner = content.get('banner', '')
- if self._starting:
- # finish handling started channels
- self._starting = False
- super(IPythonWidget, self)._started_channels()
-
- def _started_channels(self):
- """Reimplemented to make a history request and load %guiref."""
- self._starting = True
- # The reply will trigger %guiref load provided language=='python'
- self.kernel_client.kernel_info()
-
- self.kernel_client.history(hist_access_type='tail', n=1000)
-
- def _load_guiref_magic(self):
- """Load %guiref magic."""
- self.kernel_client.execute('\n'.join([
- "try:",
- " _usage",
- "except:",
- " from IPython.core import usage as _usage",
- " get_ipython().register_magic_function(_usage.page_guiref, 'line', 'guiref')",
- " del _usage",
- ]), silent=True)
-
- #---------------------------------------------------------------------------
- # 'ConsoleWidget' public interface
- #---------------------------------------------------------------------------
-
- #---------------------------------------------------------------------------
- # 'FrontendWidget' public interface
- #---------------------------------------------------------------------------
-
- def execute_file(self, path, hidden=False):
- """ Reimplemented to use the 'run' magic.
- """
- # Use forward slashes on Windows to avoid escaping each separator.
- if sys.platform == 'win32':
- path = os.path.normpath(path).replace('\\', '/')
-
- # Perhaps we should not be using %run directly, but while we
- # are, it is necessary to quote or escape filenames containing spaces
- # or quotes.
-
- # In earlier code here, to minimize escaping, we sometimes quoted the
- # filename with single quotes. But to do this, this code must be
- # platform-aware, because run uses shlex rather than python string
- # parsing, so that:
- # * In Win: single quotes can be used in the filename without quoting,
- # and we cannot use single quotes to quote the filename.
- # * In *nix: we can escape double quotes in a double quoted filename,
- # but can't escape single quotes in a single quoted filename.
-
- # So to keep this code non-platform-specific and simple, we now only
- # use double quotes to quote filenames, and escape when needed:
- if ' ' in path or "'" in path or '"' in path:
- path = '"%s"' % path.replace('"', '\\"')
- self.execute('%%run %s' % path, hidden=hidden)
-
- #---------------------------------------------------------------------------
- # 'FrontendWidget' protected interface
- #---------------------------------------------------------------------------
-
- def _process_execute_error(self, msg):
- """ Reimplemented for IPython-style traceback formatting.
- """
- content = msg['content']
- traceback = '\n'.join(content['traceback']) + '\n'
- if False:
- # FIXME: For now, tracebacks come as plain text, so we can't use
- # the html renderer yet. Once we refactor ultratb to produce
- # properly styled tracebacks, this branch should be the default
- traceback = traceback.replace(' ', ' ')
- traceback = traceback.replace('\n', '
')
-
- ename = content['ename']
- ename_styled = '%s' % ename
- traceback = traceback.replace(ename, ename_styled)
-
- self._append_html(traceback)
- else:
- # This is the fallback for now, using plain text with ansi escapes
- self._append_plain_text(traceback)
-
- def _process_execute_payload(self, item):
- """ Reimplemented to dispatch payloads to handler methods.
- """
- handler = self._payload_handlers.get(item['source'])
- if handler is None:
- # We have no handler for this type of payload, simply ignore it
- return False
- else:
- handler(item)
- return True
-
- def _show_interpreter_prompt(self, number=None):
- """ Reimplemented for IPython-style prompts.
- """
- # If a number was not specified, make a prompt number request.
- if number is None:
- msg_id = self.kernel_client.execute('', silent=True)
- info = self._ExecutionRequest(msg_id, 'prompt')
- self._request_info['execute'][msg_id] = info
- return
-
- # Show a new prompt and save information about it so that it can be
- # updated later if the prompt number turns out to be wrong.
- self._prompt_sep = self.input_sep
- self._show_prompt(self._make_in_prompt(number), html=True)
- block = self._control.document().lastBlock()
- length = len(self._prompt)
- self._previous_prompt_obj = self._PromptBlock(block, length, number)
-
- # Update continuation prompt to reflect (possibly) new prompt length.
- self._set_continuation_prompt(
- self._make_continuation_prompt(self._prompt), html=True)
-
- def _show_interpreter_prompt_for_reply(self, msg):
- """ Reimplemented for IPython-style prompts.
- """
- # Update the old prompt number if necessary.
- content = msg['content']
- # abort replies do not have any keys:
- if content['status'] == 'aborted':
- if self._previous_prompt_obj:
- previous_prompt_number = self._previous_prompt_obj.number
- else:
- previous_prompt_number = 0
- else:
- previous_prompt_number = content['execution_count']
- if self._previous_prompt_obj and \
- self._previous_prompt_obj.number != previous_prompt_number:
- block = self._previous_prompt_obj.block
-
- # Make sure the prompt block has not been erased.
- if block.isValid() and block.text():
-
- # Remove the old prompt and insert a new prompt.
- cursor = QtGui.QTextCursor(block)
- cursor.movePosition(QtGui.QTextCursor.Right,
- QtGui.QTextCursor.KeepAnchor,
- self._previous_prompt_obj.length)
- prompt = self._make_in_prompt(previous_prompt_number)
- self._prompt = self._insert_html_fetching_plain_text(
- cursor, prompt)
-
- # When the HTML is inserted, Qt blows away the syntax
- # highlighting for the line, so we need to rehighlight it.
- self._highlighter.rehighlightBlock(cursor.block())
-
- self._previous_prompt_obj = None
-
- # Show a new prompt with the kernel's estimated prompt number.
- self._show_interpreter_prompt(previous_prompt_number + 1)
-
- #---------------------------------------------------------------------------
- # 'IPythonWidget' interface
- #---------------------------------------------------------------------------
-
- def set_default_style(self, colors='lightbg'):
- """ Sets the widget style to the class defaults.
-
- Parameters
- ----------
- colors : str, optional (default lightbg)
- Whether to use the default IPython light background or dark
- background or B&W style.
- """
- colors = colors.lower()
- if colors=='lightbg':
- self.style_sheet = styles.default_light_style_sheet
- self.syntax_style = styles.default_light_syntax_style
- elif colors=='linux':
- self.style_sheet = styles.default_dark_style_sheet
- self.syntax_style = styles.default_dark_syntax_style
- elif colors=='nocolor':
- self.style_sheet = styles.default_bw_style_sheet
- self.syntax_style = styles.default_bw_syntax_style
- else:
- raise KeyError("No such color scheme: %s"%colors)
-
- #---------------------------------------------------------------------------
- # 'IPythonWidget' protected interface
- #---------------------------------------------------------------------------
-
- def _edit(self, filename, line=None):
- """ Opens a Python script for editing.
-
- Parameters
- ----------
- filename : str
- A path to a local system file.
-
- line : int, optional
- A line of interest in the file.
- """
- if self.custom_edit:
- self.custom_edit_requested.emit(filename, line)
- elif not self.editor:
- self._append_plain_text('No default editor available.\n'
- 'Specify a GUI text editor in the `IPythonWidget.editor` '
- 'configurable to enable the %edit magic')
- else:
- try:
- filename = '"%s"' % filename
- if line and self.editor_line:
- command = self.editor_line.format(filename=filename,
- line=line)
- else:
- try:
- command = self.editor.format()
- except KeyError:
- command = self.editor.format(filename=filename)
- else:
- command += ' ' + filename
- except KeyError:
- self._append_plain_text('Invalid editor command.\n')
- else:
- try:
- Popen(command, shell=True)
- except OSError:
- msg = 'Opening editor with command "%s" failed.\n'
- self._append_plain_text(msg % command)
-
- def _make_in_prompt(self, number):
- """ Given a prompt number, returns an HTML In prompt.
- """
- try:
- body = self.in_prompt % number
- except TypeError:
- # allow in_prompt to leave out number, e.g. '>>> '
- from xml.sax.saxutils import escape
- body = escape(self.in_prompt)
- return '%s' % body
-
- def _make_continuation_prompt(self, prompt):
- """ Given a plain text version of an In prompt, returns an HTML
- continuation prompt.
- """
- end_chars = '...: '
- space_count = len(prompt.lstrip('\n')) - len(end_chars)
- body = ' ' * space_count + end_chars
- return '%s' % body
-
- def _make_out_prompt(self, number):
- """ Given a prompt number, returns an HTML Out prompt.
- """
- try:
- body = self.out_prompt % number
- except TypeError:
- # allow out_prompt to leave out number, e.g. '<<< '
- from xml.sax.saxutils import escape
- body = escape(self.out_prompt)
- return '%s' % body
-
- #------ Payload handlers --------------------------------------------------
-
- # Payload handlers with a generic interface: each takes the opaque payload
- # dict, unpacks it and calls the underlying functions with the necessary
- # arguments.
-
- def _handle_payload_edit(self, item):
- self._edit(item['filename'], item['line_number'])
-
- def _handle_payload_exit(self, item):
- self._keep_kernel_on_exit = item['keepkernel']
- self.exit_requested.emit(self)
-
- def _handle_payload_next_input(self, item):
- self.input_buffer = item['text']
-
- def _handle_payload_page(self, item):
- # Since the plain text widget supports only a very small subset of HTML
- # and we have no control over the HTML source, we only page HTML
- # payloads in the rich text widget.
- data = item['data']
- if 'text/html' in data and self.kind == 'rich':
- self._page(data['text/html'], html=True)
- else:
- self._page(data['text/plain'], html=False)
-
- #------ Trait change handlers --------------------------------------------
-
- def _style_sheet_changed(self):
- """ Set the style sheets of the underlying widgets.
- """
- self.setStyleSheet(self.style_sheet)
- if self._control is not None:
- self._control.document().setDefaultStyleSheet(self.style_sheet)
- bg_color = self._control.palette().window().color()
- self._ansi_processor.set_background_color(bg_color)
-
- if self._page_control is not None:
- self._page_control.document().setDefaultStyleSheet(self.style_sheet)
-
-
-
- def _syntax_style_changed(self):
- """ Set the style for the syntax highlighter.
- """
- if self._highlighter is None:
- # ignore premature calls
- return
- if self.syntax_style:
- self._highlighter.set_style(self.syntax_style)
- else:
- self._highlighter.set_style_sheet(self.style_sheet)
-
- #------ Trait default initializers -----------------------------------------
-
- def _banner_default(self):
- return "IPython QtConsole {version}\n".format(version=version)
diff --git a/jupyter_qtconsole/console/kill_ring.py b/jupyter_qtconsole/console/kill_ring.py
deleted file mode 100644
index 7f8cce6..0000000
--- a/jupyter_qtconsole/console/kill_ring.py
+++ /dev/null
@@ -1,128 +0,0 @@
-""" A generic Emacs-style kill ring, as well as a Qt-specific version.
-"""
-#-----------------------------------------------------------------------------
-# Imports
-#-----------------------------------------------------------------------------
-
-# System library imports
-from IPython.external.qt import QtCore, QtGui
-
-#-----------------------------------------------------------------------------
-# Classes
-#-----------------------------------------------------------------------------
-
-class KillRing(object):
- """ A generic Emacs-style kill ring.
- """
-
- def __init__(self):
- self.clear()
-
- def clear(self):
- """ Clears the kill ring.
- """
- self._index = -1
- self._ring = []
-
- def kill(self, text):
- """ Adds some killed text to the ring.
- """
- self._ring.append(text)
-
- def yank(self):
- """ Yank back the most recently killed text.
-
- Returns
- -------
- A text string or None.
- """
- self._index = len(self._ring)
- return self.rotate()
-
- def rotate(self):
- """ Rotate the kill ring, then yank back the new top.
-
- Returns
- -------
- A text string or None.
- """
- self._index -= 1
- if self._index >= 0:
- return self._ring[self._index]
- return None
-
-class QtKillRing(QtCore.QObject):
- """ A kill ring attached to Q[Plain]TextEdit.
- """
-
- #--------------------------------------------------------------------------
- # QtKillRing interface
- #--------------------------------------------------------------------------
-
- def __init__(self, text_edit):
- """ Create a kill ring attached to the specified Qt text edit.
- """
- assert isinstance(text_edit, (QtGui.QTextEdit, QtGui.QPlainTextEdit))
- super(QtKillRing, self).__init__()
-
- self._ring = KillRing()
- self._prev_yank = None
- self._skip_cursor = False
- self._text_edit = text_edit
-
- text_edit.cursorPositionChanged.connect(self._cursor_position_changed)
-
- def clear(self):
- """ Clears the kill ring.
- """
- self._ring.clear()
- self._prev_yank = None
-
- def kill(self, text):
- """ Adds some killed text to the ring.
- """
- self._ring.kill(text)
-
- def kill_cursor(self, cursor):
- """ Kills the text selected by the give cursor.
- """
- text = cursor.selectedText()
- if text:
- cursor.removeSelectedText()
- self.kill(text)
-
- def yank(self):
- """ Yank back the most recently killed text.
- """
- text = self._ring.yank()
- if text:
- self._skip_cursor = True
- cursor = self._text_edit.textCursor()
- cursor.insertText(text)
- self._prev_yank = text
-
- def rotate(self):
- """ Rotate the kill ring, then yank back the new top.
- """
- if self._prev_yank:
- text = self._ring.rotate()
- if text:
- self._skip_cursor = True
- cursor = self._text_edit.textCursor()
- cursor.movePosition(QtGui.QTextCursor.Left,
- QtGui.QTextCursor.KeepAnchor,
- n = len(self._prev_yank))
- cursor.insertText(text)
- self._prev_yank = text
-
- #--------------------------------------------------------------------------
- # Protected interface
- #--------------------------------------------------------------------------
-
- #------ Signal handlers ----------------------------------------------------
-
- def _cursor_position_changed(self):
- if self._skip_cursor:
- self._skip_cursor = False
- else:
- self._prev_yank = None
diff --git a/jupyter_qtconsole/console/magic_helper.py b/jupyter_qtconsole/console/magic_helper.py
deleted file mode 100644
index 54680b5..0000000
--- a/jupyter_qtconsole/console/magic_helper.py
+++ /dev/null
@@ -1,211 +0,0 @@
-"""MagicHelper - dockable widget showing magic commands for the MainWindow
-"""
-
-# Copyright (c) IPython Development Team.
-# Distributed under the terms of the Modified BSD License.
-
-#-----------------------------------------------------------------------------
-# Imports
-#-----------------------------------------------------------------------------
-
-# stdlib imports
-import json
-import re
-import sys
-
-# System library imports
-from IPython.external.qt import QtGui,QtCore
-
-from IPython.core.magic import magic_escapes
-
-class MagicHelper(QtGui.QDockWidget):
- """MagicHelper - dockable widget for convenient search and running of
- magic command for IPython QtConsole.
- """
-
- #---------------------------------------------------------------------------
- # signals
- #---------------------------------------------------------------------------
-
- pasteRequested = QtCore.Signal(str, name = 'pasteRequested')
- """This signal is emitted when user wants to paste selected magic
- command into the command line.
- """
-
- runRequested = QtCore.Signal(str, name = 'runRequested')
- """This signal is emitted when user wants to execute selected magic command
- """
-
- readyForUpdate = QtCore.Signal(name = 'readyForUpdate')
- """This signal is emitted when MagicHelper is ready to be populated.
- Since kernel querying mechanisms are out of scope of this class,
- it expects its owner to invoke MagicHelper.populate_magic_helper()
- as a reaction on this event.
- """
-
- #---------------------------------------------------------------------------
- # constructor
- #---------------------------------------------------------------------------
-
- def __init__(self, name, parent):
- super(MagicHelper, self).__init__(name, parent)
-
- self.data = None
-
- class MinListWidget(QtGui.QListWidget):
- """Temp class to overide the default QListWidget size hint
- in order to make MagicHelper narrow
- """
- def sizeHint(self):
- s = QtCore.QSize()
- s.setHeight(super(MinListWidget,self).sizeHint().height())
- s.setWidth(self.sizeHintForColumn(0))
- return s
-
- # construct content
- self.frame = QtGui.QFrame()
- self.search_label = QtGui.QLabel("Search:")
- self.search_line = QtGui.QLineEdit()
- self.search_class = QtGui.QComboBox()
- self.search_list = MinListWidget()
- self.paste_button = QtGui.QPushButton("Paste")
- self.run_button = QtGui.QPushButton("Run")
-
- # layout all the widgets
- main_layout = QtGui.QVBoxLayout()
- search_layout = QtGui.QHBoxLayout()
- search_layout.addWidget(self.search_label)
- search_layout.addWidget(self.search_line, 10)
- main_layout.addLayout(search_layout)
- main_layout.addWidget(self.search_class)
- main_layout.addWidget(self.search_list, 10)
- action_layout = QtGui.QHBoxLayout()
- action_layout.addWidget(self.paste_button)
- action_layout.addWidget(self.run_button)
- main_layout.addLayout(action_layout)
-
- self.frame.setLayout(main_layout)
- self.setWidget(self.frame)
-
- # connect all the relevant signals to handlers
- self.visibilityChanged[bool].connect( self._update_magic_helper )
- self.search_class.activated[int].connect(
- self.class_selected
- )
- self.search_line.textChanged[str].connect(
- self.search_changed
- )
- self.search_list.itemDoubleClicked.connect(
- self.paste_requested
- )
- self.paste_button.clicked[bool].connect(
- self.paste_requested
- )
- self.run_button.clicked[bool].connect(
- self.run_requested
- )
-
- #---------------------------------------------------------------------------
- # implementation
- #---------------------------------------------------------------------------
-
- def _update_magic_helper(self, visible):
- """Start update sequence.
- This method is called when MagicHelper becomes visible. It clears
- the content and emits readyForUpdate signal. The owner of the
- instance is expected to invoke populate_magic_helper() when magic
- info is available.
- """
- if not visible or self.data is not None:
- return
- self.data = {}
- self.search_class.clear()
- self.search_class.addItem("Populating...")
- self.search_list.clear()
- self.readyForUpdate.emit()
-
- def populate_magic_helper(self, data):
- """Expects data returned by lsmagics query from kernel.
- Populates the search_class and search_list with relevant items.
- """
- self.search_class.clear()
- self.search_list.clear()
-
- self.data = data['data'].get('application/json', {})
-
- self.search_class.addItem('All Magics', 'any')
- classes = set()
-
- for mtype in sorted(self.data):
- subdict = self.data[mtype]
- for name in sorted(subdict):
- classes.add(subdict[name])
-
- for cls in sorted(classes):
- label = re.sub("([a-zA-Z]+)([A-Z][a-z])","\g<1> \g<2>", cls)
- self.search_class.addItem(label, cls)
-
- self.filter_magic_helper('.', 'any')
-
- def class_selected(self, index):
- """Handle search_class selection changes
- """
- item = self.search_class.itemData(index)
- regex = self.search_line.text()
- self.filter_magic_helper(regex = regex, cls = item)
-
- def search_changed(self, search_string):
- """Handle search_line text changes.
- The text is interpreted as a regular expression
- """
- item = self.search_class.itemData(
- self.search_class.currentIndex()
- )
- self.filter_magic_helper(regex = search_string, cls = item)
-
- def _get_current_search_item(self, item = None):
- """Retrieve magic command currently selected in the search_list
- """
- text = None
- if not isinstance(item, QtGui.QListWidgetItem):
- item = self.search_list.currentItem()
- text = item.text()
- return text
-
- def paste_requested(self, item = None):
- """Emit pasteRequested signal with currently selected item text
- """
- text = self._get_current_search_item(item)
- if text is not None:
- self.pasteRequested.emit(text)
-
- def run_requested(self, item = None):
- """Emit runRequested signal with currently selected item text
- """
- text = self._get_current_search_item(item)
- if text is not None:
- self.runRequested.emit(text)
-
- def filter_magic_helper(self, regex, cls):
- """Update search_list with magic commands whose text match
- regex and class match cls.
- If cls equals 'any' - any class matches.
- """
- if regex == "" or regex is None:
- regex = '.'
- if cls is None:
- cls = 'any'
-
- self.search_list.clear()
- for mtype in sorted(self.data):
- subdict = self.data[mtype]
- prefix = magic_escapes[mtype]
-
- for name in sorted(subdict):
- mclass = subdict[name]
- pmagic = prefix + name
-
- if (re.match(regex, name) or re.match(regex, pmagic)) and \
- (cls == 'any' or cls == mclass):
- self.search_list.addItem(pmagic)
-
diff --git a/jupyter_qtconsole/console/mainwindow.py b/jupyter_qtconsole/console/mainwindow.py
deleted file mode 100644
index 6a04542..0000000
--- a/jupyter_qtconsole/console/mainwindow.py
+++ /dev/null
@@ -1,928 +0,0 @@
-"""The Qt MainWindow for the QtConsole
-
-This is a tabbed pseudo-terminal of IPython sessions, with a menu bar for
-common actions.
-"""
-
-# Copyright (c) IPython Development Team.
-# Distributed under the terms of the Modified BSD License.
-
-
-#-----------------------------------------------------------------------------
-# Imports
-#-----------------------------------------------------------------------------
-
-# stdlib imports
-import json
-import re
-import sys
-import webbrowser
-from threading import Thread
-
-# System library imports
-from IPython.external.qt import QtGui,QtCore
-
-from IPython.core.magic import magic_escapes
-
-def background(f):
- """call a function in a simple thread, to prevent blocking"""
- t = Thread(target=f)
- t.start()
- return t
-
-#-----------------------------------------------------------------------------
-# Classes
-#-----------------------------------------------------------------------------
-
-class MainWindow(QtGui.QMainWindow):
-
- #---------------------------------------------------------------------------
- # 'object' interface
- #---------------------------------------------------------------------------
-
- def __init__(self, app,
- confirm_exit=True,
- new_frontend_factory=None, slave_frontend_factory=None,
- ):
- """ Create a tabbed MainWindow for managing IPython FrontendWidgets
-
- Parameters
- ----------
-
- app : reference to QApplication parent
- confirm_exit : bool, optional
- Whether we should prompt on close of tabs
- new_frontend_factory : callable
- A callable that returns a new IPythonWidget instance, attached to
- its own running kernel.
- slave_frontend_factory : callable
- A callable that takes an existing IPythonWidget, and returns a new
- IPythonWidget instance, attached to the same kernel.
- """
-
- super(MainWindow, self).__init__()
- self._kernel_counter = 0
- self._app = app
- self.confirm_exit = confirm_exit
- self.new_frontend_factory = new_frontend_factory
- self.slave_frontend_factory = slave_frontend_factory
-
- self.tab_widget = QtGui.QTabWidget(self)
- self.tab_widget.setDocumentMode(True)
- self.tab_widget.setTabsClosable(True)
- self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
-
- self.setCentralWidget(self.tab_widget)
- # hide tab bar at first, since we have no tabs:
- self.tab_widget.tabBar().setVisible(False)
- # prevent focus in tab bar
- self.tab_widget.setFocusPolicy(QtCore.Qt.NoFocus)
-
- def update_tab_bar_visibility(self):
- """ update visibility of the tabBar depending of the number of tab
-
- 0 or 1 tab, tabBar hidden
- 2+ tabs, tabBar visible
-
- send a self.close if number of tab ==0
-
- need to be called explicitly, or be connected to tabInserted/tabRemoved
- """
- if self.tab_widget.count() <= 1:
- self.tab_widget.tabBar().setVisible(False)
- else:
- self.tab_widget.tabBar().setVisible(True)
- if self.tab_widget.count()==0 :
- self.close()
-
- @property
- def next_kernel_id(self):
- """constantly increasing counter for kernel IDs"""
- c = self._kernel_counter
- self._kernel_counter += 1
- return c
-
- @property
- def active_frontend(self):
- return self.tab_widget.currentWidget()
-
- def create_tab_with_new_frontend(self):
- """create a new frontend and attach it to a new tab"""
- widget = self.new_frontend_factory()
- self.add_tab_with_frontend(widget)
-
- def create_tab_with_current_kernel(self):
- """create a new frontend attached to the same kernel as the current tab"""
- current_widget = self.tab_widget.currentWidget()
- current_widget_index = self.tab_widget.indexOf(current_widget)
- current_widget_name = self.tab_widget.tabText(current_widget_index)
- widget = self.slave_frontend_factory(current_widget)
- if 'slave' in current_widget_name:
- # don't keep stacking slaves
- name = current_widget_name
- else:
- name = '(%s) slave' % current_widget_name
- self.add_tab_with_frontend(widget,name=name)
-
- def close_tab(self,current_tab):
- """ Called when you need to try to close a tab.
-
- It takes the number of the tab to be closed as argument, or a reference
- to the widget inside this tab
- """
-
- # let's be sure "tab" and "closing widget" are respectively the index
- # of the tab to close and a reference to the frontend to close
- if type(current_tab) is not int :
- current_tab = self.tab_widget.indexOf(current_tab)
- closing_widget=self.tab_widget.widget(current_tab)
-
-
- # when trying to be closed, widget might re-send a request to be
- # closed again, but will be deleted when event will be processed. So
- # need to check that widget still exists and skip if not. One example
- # of this is when 'exit' is sent in a slave tab. 'exit' will be
- # re-sent by this function on the master widget, which ask all slave
- # widgets to exit
- if closing_widget is None:
- return
-
- #get a list of all slave widgets on the same kernel.
- slave_tabs = self.find_slave_widgets(closing_widget)
-
- keepkernel = None #Use the prompt by default
- if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
- keepkernel = closing_widget._keep_kernel_on_exit
- # If signal sent by exit magic (_keep_kernel_on_exit, exist and not None)
- # we set local slave tabs._hidden to True to avoid prompting for kernel
- # restart when they get the signal. and then "forward" the 'exit'
- # to the main window
- if keepkernel is not None:
- for tab in slave_tabs:
- tab._hidden = True
- if closing_widget in slave_tabs:
- try :
- self.find_master_tab(closing_widget).execute('exit')
- except AttributeError:
- self.log.info("Master already closed or not local, closing only current tab")
- self.tab_widget.removeTab(current_tab)
- self.update_tab_bar_visibility()
- return
-
- kernel_client = closing_widget.kernel_client
- kernel_manager = closing_widget.kernel_manager
-
- if keepkernel is None and not closing_widget._confirm_exit:
- # don't prompt, just terminate the kernel if we own it
- # or leave it alone if we don't
- keepkernel = closing_widget._existing
- if keepkernel is None: #show prompt
- if kernel_client and kernel_client.channels_running:
- title = self.window().windowTitle()
- cancel = QtGui.QMessageBox.Cancel
- okay = QtGui.QMessageBox.Ok
- if closing_widget._may_close:
- msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
- info = "Would you like to quit the Kernel and close all attached Consoles as well?"
- justthis = QtGui.QPushButton("&No, just this Tab", self)
- justthis.setShortcut('N')
- closeall = QtGui.QPushButton("&Yes, close all", self)
- closeall.setShortcut('Y')
- # allow ctrl-d ctrl-d exit, like in terminal
- closeall.setShortcut('Ctrl+D')
- box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
- title, msg)
- box.setInformativeText(info)
- box.addButton(cancel)
- box.addButton(justthis, QtGui.QMessageBox.NoRole)
- box.addButton(closeall, QtGui.QMessageBox.YesRole)
- box.setDefaultButton(closeall)
- box.setEscapeButton(cancel)
- pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
- box.setIconPixmap(pixmap)
- reply = box.exec_()
- if reply == 1: # close All
- for slave in slave_tabs:
- background(slave.kernel_client.stop_channels)
- self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
- kernel_manager.shutdown_kernel()
- self.tab_widget.removeTab(current_tab)
- background(kernel_client.stop_channels)
- elif reply == 0: # close Console
- if not closing_widget._existing:
- # Have kernel: don't quit, just close the tab
- closing_widget.execute("exit True")
- self.tab_widget.removeTab(current_tab)
- background(kernel_client.stop_channels)
- else:
- reply = QtGui.QMessageBox.question(self, title,
- "Are you sure you want to close this Console?"+
- "\nThe Kernel and other Consoles will remain active.",
- okay|cancel,
- defaultButton=okay
- )
- if reply == okay:
- self.tab_widget.removeTab(current_tab)
- elif keepkernel: #close console but leave kernel running (no prompt)
- self.tab_widget.removeTab(current_tab)
- background(kernel_client.stop_channels)
- else: #close console and kernel (no prompt)
- self.tab_widget.removeTab(current_tab)
- if kernel_client and kernel_client.channels_running:
- for slave in slave_tabs:
- background(slave.kernel_client.stop_channels)
- self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
- if kernel_manager:
- kernel_manager.shutdown_kernel()
- background(kernel_client.stop_channels)
-
- self.update_tab_bar_visibility()
-
- def add_tab_with_frontend(self,frontend,name=None):
- """ insert a tab with a given frontend in the tab bar, and give it a name
-
- """
- if not name:
- name = 'kernel %i' % self.next_kernel_id
- self.tab_widget.addTab(frontend,name)
- self.update_tab_bar_visibility()
- self.make_frontend_visible(frontend)
- frontend.exit_requested.connect(self.close_tab)
-
- def next_tab(self):
- self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
-
- def prev_tab(self):
- self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
-
- def make_frontend_visible(self,frontend):
- widget_index=self.tab_widget.indexOf(frontend)
- if widget_index > 0 :
- self.tab_widget.setCurrentIndex(widget_index)
-
- def find_master_tab(self,tab,as_list=False):
- """
- Try to return the frontend that owns the kernel attached to the given widget/tab.
-
- Only finds frontend owned by the current application. Selection
- based on port of the kernel might be inaccurate if several kernel
- on different ip use same port number.
-
- This function does the conversion tabNumber/widget if needed.
- Might return None if no master widget (non local kernel)
- Will crash IPython if more than 1 masterWidget
-
- When asList set to True, always return a list of widget(s) owning
- the kernel. The list might be empty or containing several Widget.
- """
-
- #convert from/to int/richIpythonWidget if needed
- if isinstance(tab, int):
- tab = self.tab_widget.widget(tab)
- km=tab.kernel_client
-
- #build list of all widgets
- widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
-
- # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
- # And should have a _may_close attribute
- filtered_widget_list = [ widget for widget in widget_list if
- widget.kernel_client.connection_file == km.connection_file and
- hasattr(widget,'_may_close') ]
- # the master widget is the one that may close the kernel
- master_widget= [ widget for widget in filtered_widget_list if widget._may_close]
- if as_list:
- return master_widget
- assert(len(master_widget)<=1 )
- if len(master_widget)==0:
- return None
-
- return master_widget[0]
-
- def find_slave_widgets(self,tab):
- """return all the frontends that do not own the kernel attached to the given widget/tab.
-
- Only find frontends owned by the current application. Selection
- based on connection file of the kernel.
-
- This function does the conversion tabNumber/widget if needed.
- """
- #convert from/to int/richIpythonWidget if needed
- if isinstance(tab, int):
- tab = self.tab_widget.widget(tab)
- km=tab.kernel_client
-
- #build list of all widgets
- widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
-
- # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
- filtered_widget_list = ( widget for widget in widget_list if
- widget.kernel_client.connection_file == km.connection_file)
- # Get a list of all widget owning the same kernel and removed it from
- # the previous cadidate. (better using sets ?)
- master_widget_list = self.find_master_tab(tab, as_list=True)
- slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
-
- return slave_list
-
- # Populate the menu bar with common actions and shortcuts
- def add_menu_action(self, menu, action, defer_shortcut=False):
- """Add action to menu as well as self
-
- So that when the menu bar is invisible, its actions are still available.
-
- If defer_shortcut is True, set the shortcut context to widget-only,
- where it will avoid conflict with shortcuts already bound to the
- widgets themselves.
- """
- menu.addAction(action)
- self.addAction(action)
-
- if defer_shortcut:
- action.setShortcutContext(QtCore.Qt.WidgetShortcut)
-
- def init_menu_bar(self):
- #create menu in the order they should appear in the menu bar
- self.init_file_menu()
- self.init_edit_menu()
- self.init_view_menu()
- self.init_kernel_menu()
- self.init_magic_menu()
- self.init_window_menu()
- self.init_help_menu()
-
- def init_file_menu(self):
- self.file_menu = self.menuBar().addMenu("&File")
-
- self.new_kernel_tab_act = QtGui.QAction("New Tab with &New kernel",
- self,
- shortcut="Ctrl+T",
- triggered=self.create_tab_with_new_frontend)
- self.add_menu_action(self.file_menu, self.new_kernel_tab_act)
-
- self.slave_kernel_tab_act = QtGui.QAction("New Tab with Sa&me kernel",
- self,
- shortcut="Ctrl+Shift+T",
- triggered=self.create_tab_with_current_kernel)
- self.add_menu_action(self.file_menu, self.slave_kernel_tab_act)
-
- self.file_menu.addSeparator()
-
- self.close_action=QtGui.QAction("&Close Tab",
- self,
- shortcut=QtGui.QKeySequence.Close,
- triggered=self.close_active_frontend
- )
- self.add_menu_action(self.file_menu, self.close_action)
-
- self.export_action=QtGui.QAction("&Save to HTML/XHTML",
- self,
- shortcut=QtGui.QKeySequence.Save,
- triggered=self.export_action_active_frontend
- )
- self.add_menu_action(self.file_menu, self.export_action, True)
-
- self.file_menu.addSeparator()
-
- printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
- if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
- # Only override the default if there is a collision.
- # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
- printkey = "Ctrl+Shift+P"
- self.print_action = QtGui.QAction("&Print",
- self,
- shortcut=printkey,
- triggered=self.print_action_active_frontend)
- self.add_menu_action(self.file_menu, self.print_action, True)
-
- if sys.platform != 'darwin':
- # OSX always has Quit in the Application menu, only add it
- # to the File menu elsewhere.
-
- self.file_menu.addSeparator()
-
- self.quit_action = QtGui.QAction("&Quit",
- self,
- shortcut=QtGui.QKeySequence.Quit,
- triggered=self.close,
- )
- self.add_menu_action(self.file_menu, self.quit_action)
-
-
- def init_edit_menu(self):
- self.edit_menu = self.menuBar().addMenu("&Edit")
-
- self.undo_action = QtGui.QAction("&Undo",
- self,
- shortcut=QtGui.QKeySequence.Undo,
- statusTip="Undo last action if possible",
- triggered=self.undo_active_frontend
- )
- self.add_menu_action(self.edit_menu, self.undo_action)
-
- self.redo_action = QtGui.QAction("&Redo",
- self,
- shortcut=QtGui.QKeySequence.Redo,
- statusTip="Redo last action if possible",
- triggered=self.redo_active_frontend)
- self.add_menu_action(self.edit_menu, self.redo_action)
-
- self.edit_menu.addSeparator()
-
- self.cut_action = QtGui.QAction("&Cut",
- self,
- shortcut=QtGui.QKeySequence.Cut,
- triggered=self.cut_active_frontend
- )
- self.add_menu_action(self.edit_menu, self.cut_action, True)
-
- self.copy_action = QtGui.QAction("&Copy",
- self,
- shortcut=QtGui.QKeySequence.Copy,
- triggered=self.copy_active_frontend
- )
- self.add_menu_action(self.edit_menu, self.copy_action, True)
-
- self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
- self,
- shortcut="Ctrl+Shift+C",
- triggered=self.copy_raw_active_frontend
- )
- self.add_menu_action(self.edit_menu, self.copy_raw_action, True)
-
- self.paste_action = QtGui.QAction("&Paste",
- self,
- shortcut=QtGui.QKeySequence.Paste,
- triggered=self.paste_active_frontend
- )
- self.add_menu_action(self.edit_menu, self.paste_action, True)
-
- self.edit_menu.addSeparator()
-
- selectall = QtGui.QKeySequence(QtGui.QKeySequence.SelectAll)
- if selectall.matches("Ctrl+A") and sys.platform != 'darwin':
- # Only override the default if there is a collision.
- # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
- selectall = "Ctrl+Shift+A"
- self.select_all_action = QtGui.QAction("Select &All",
- self,
- shortcut=selectall,
- triggered=self.select_all_active_frontend
- )
- self.add_menu_action(self.edit_menu, self.select_all_action, True)
-
-
- def init_view_menu(self):
- self.view_menu = self.menuBar().addMenu("&View")
-
- if sys.platform != 'darwin':
- # disable on OSX, where there is always a menu bar
- self.toggle_menu_bar_act = QtGui.QAction("Toggle &Menu Bar",
- self,
- shortcut="Ctrl+Shift+M",
- statusTip="Toggle visibility of menubar",
- triggered=self.toggle_menu_bar)
- self.add_menu_action(self.view_menu, self.toggle_menu_bar_act)
-
- fs_key = "Ctrl+Meta+F" if sys.platform == 'darwin' else "F11"
- self.full_screen_act = QtGui.QAction("&Full Screen",
- self,
- shortcut=fs_key,
- statusTip="Toggle between Fullscreen and Normal Size",
- triggered=self.toggleFullScreen)
- self.add_menu_action(self.view_menu, self.full_screen_act)
-
- self.view_menu.addSeparator()
-
- self.increase_font_size = QtGui.QAction("Zoom &In",
- self,
- shortcut=QtGui.QKeySequence.ZoomIn,
- triggered=self.increase_font_size_active_frontend
- )
- self.add_menu_action(self.view_menu, self.increase_font_size, True)
-
- self.decrease_font_size = QtGui.QAction("Zoom &Out",
- self,
- shortcut=QtGui.QKeySequence.ZoomOut,
- triggered=self.decrease_font_size_active_frontend
- )
- self.add_menu_action(self.view_menu, self.decrease_font_size, True)
-
- self.reset_font_size = QtGui.QAction("Zoom &Reset",
- self,
- shortcut="Ctrl+0",
- triggered=self.reset_font_size_active_frontend
- )
- self.add_menu_action(self.view_menu, self.reset_font_size, True)
-
- self.view_menu.addSeparator()
-
- self.clear_action = QtGui.QAction("&Clear Screen",
- self,
- shortcut='Ctrl+L',
- statusTip="Clear the console",
- triggered=self.clear_magic_active_frontend)
- self.add_menu_action(self.view_menu, self.clear_action)
-
- self.pager_menu = self.view_menu.addMenu("&Pager")
-
- hsplit_action = QtGui.QAction(".. &Horizontal Split",
- self,
- triggered=lambda: self.set_paging_active_frontend('hsplit'))
-
- vsplit_action = QtGui.QAction(" : &Vertical Split",
- self,
- triggered=lambda: self.set_paging_active_frontend('vsplit'))
-
- inside_action = QtGui.QAction(" &Inside Pager",
- self,
- triggered=lambda: self.set_paging_active_frontend('inside'))
-
- self.pager_menu.addAction(hsplit_action)
- self.pager_menu.addAction(vsplit_action)
- self.pager_menu.addAction(inside_action)
-
- def init_kernel_menu(self):
- self.kernel_menu = self.menuBar().addMenu("&Kernel")
- # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl
- # keep the signal shortcuts to ctrl, rather than
- # platform-default like we do elsewhere.
-
- ctrl = "Meta" if sys.platform == 'darwin' else "Ctrl"
-
- self.interrupt_kernel_action = QtGui.QAction("&Interrupt current Kernel",
- self,
- triggered=self.interrupt_kernel_active_frontend,
- shortcut=ctrl+"+C",
- )
- self.add_menu_action(self.kernel_menu, self.interrupt_kernel_action)
-
- self.restart_kernel_action = QtGui.QAction("&Restart current Kernel",
- self,
- triggered=self.restart_kernel_active_frontend,
- shortcut=ctrl+"+.",
- )
- self.add_menu_action(self.kernel_menu, self.restart_kernel_action)
-
- self.kernel_menu.addSeparator()
-
- self.confirm_restart_kernel_action = QtGui.QAction("&Confirm kernel restart",
- self,
- checkable=True,
- checked=self.active_frontend.confirm_restart,
- triggered=self.toggle_confirm_restart_active_frontend
- )
-
- self.add_menu_action(self.kernel_menu, self.confirm_restart_kernel_action)
- self.tab_widget.currentChanged.connect(self.update_restart_checkbox)
-
- def init_magic_menu(self):
- self.magic_menu = self.menuBar().addMenu("&Magic")
-
- self.add_menu_action(self.magic_menu,
- self.magic_helper.toggleViewAction())
-
- self.magic_menu_separator = self.magic_menu.addSeparator()
-
- self.reset_action = QtGui.QAction("&Reset",
- self,
- statusTip="Clear all variables from workspace",
- triggered=self.reset_magic_active_frontend)
- self.add_menu_action(self.magic_menu, self.reset_action)
-
- self.history_action = QtGui.QAction("&History",
- self,
- statusTip="show command history",
- triggered=self.history_magic_active_frontend)
- self.add_menu_action(self.magic_menu, self.history_action)
-
- self.save_action = QtGui.QAction("E&xport History ",
- self,
- statusTip="Export History as Python File",
- triggered=self.save_magic_active_frontend)
- self.add_menu_action(self.magic_menu, self.save_action)
-
- self.who_action = QtGui.QAction("&Who",
- self,
- statusTip="List interactive variables",
- triggered=self.who_magic_active_frontend)
- self.add_menu_action(self.magic_menu, self.who_action)
-
- self.who_ls_action = QtGui.QAction("Wh&o ls",
- self,
- statusTip="Return a list of interactive variables",
- triggered=self.who_ls_magic_active_frontend)
- self.add_menu_action(self.magic_menu, self.who_ls_action)
-
- self.whos_action = QtGui.QAction("Who&s",
- self,
- statusTip="List interactive variables with details",
- triggered=self.whos_magic_active_frontend)
- self.add_menu_action(self.magic_menu, self.whos_action)
-
- def init_window_menu(self):
- self.window_menu = self.menuBar().addMenu("&Window")
- if sys.platform == 'darwin':
- # add min/maximize actions to OSX, which lacks default bindings.
- self.minimizeAct = QtGui.QAction("Mini&mize",
- self,
- shortcut="Ctrl+m",
- statusTip="Minimize the window/Restore Normal Size",
- triggered=self.toggleMinimized)
- # maximize is called 'Zoom' on OSX for some reason
- self.maximizeAct = QtGui.QAction("&Zoom",
- self,
- shortcut="Ctrl+Shift+M",
- statusTip="Maximize the window/Restore Normal Size",
- triggered=self.toggleMaximized)
-
- self.add_menu_action(self.window_menu, self.minimizeAct)
- self.add_menu_action(self.window_menu, self.maximizeAct)
- self.window_menu.addSeparator()
-
- prev_key = "Ctrl+Shift+Left" if sys.platform == 'darwin' else "Ctrl+PgUp"
- self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
- self,
- shortcut=prev_key,
- statusTip="Select previous tab",
- triggered=self.prev_tab)
- self.add_menu_action(self.window_menu, self.prev_tab_act)
-
- next_key = "Ctrl+Shift+Right" if sys.platform == 'darwin' else "Ctrl+PgDown"
- self.next_tab_act = QtGui.QAction("Ne&xt Tab",
- self,
- shortcut=next_key,
- statusTip="Select next tab",
- triggered=self.next_tab)
- self.add_menu_action(self.window_menu, self.next_tab_act)
-
- def init_help_menu(self):
- # please keep the Help menu in Mac Os even if empty. It will
- # automatically contain a search field to search inside menus and
- # please keep it spelled in English, as long as Qt Doesn't support
- # a QAction.MenuRole like HelpMenuRole otherwise it will lose
- # this search field functionality
-
- self.help_menu = self.menuBar().addMenu("&Help")
-
-
- # Help Menu
-
- self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
- self,
- triggered=self.intro_active_frontend
- )
- self.add_menu_action(self.help_menu, self.intro_active_frontend_action)
-
- self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
- self,
- triggered=self.quickref_active_frontend
- )
- self.add_menu_action(self.help_menu, self.quickref_active_frontend_action)
-
- self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
- self,
- triggered=self.guiref_active_frontend
- )
- self.add_menu_action(self.help_menu, self.guiref_active_frontend_action)
-
- self.onlineHelpAct = QtGui.QAction("Open Online &Help",
- self,
- triggered=self._open_online_help)
- self.add_menu_action(self.help_menu, self.onlineHelpAct)
-
- def init_magic_helper(self):
- from .magic_helper import MagicHelper
-
- self.magic_helper = MagicHelper("Show Magics", self)
-
- self.magic_helper.setAllowedAreas(QtCore.Qt.LeftDockWidgetArea |
- QtCore.Qt.RightDockWidgetArea)
- self.magic_helper.setVisible(False)
-
- self.magic_helper.pasteRequested[str].connect(
- self.magic_helper_paste_requested
- )
- self.magic_helper.runRequested[str].connect(
- self.magic_helper_run_requested
- )
- self.magic_helper.readyForUpdate.connect(
- self.magic_helper_update_requested
- )
-
- self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.magic_helper)
-
- def _set_active_frontend_focus(self):
- # this is a hack, self.active_frontend._control seems to be
- # a private member. Unfortunately this is the only method
- # to set focus reliably
- QtCore.QTimer.singleShot(200, self.active_frontend._control.setFocus)
-
- def magic_helper_paste_requested(self, text = None):
- if text is not None:
- self.active_frontend.input_buffer = text
- self._set_active_frontend_focus()
-
- def magic_helper_run_requested(self, text = None):
- if text is not None:
- self.active_frontend.execute(text)
- self._set_active_frontend_focus()
-
- def magic_helper_update_requested(self):
- def _handle_data(data):
- if not data:
- return
-
- if data['status'] != 'ok':
- self.log.warn(
- "%%lsmagic user-expression failed: {}".format(data)
- )
- return
- self.magic_helper.populate_magic_helper(data)
-
- self.active_frontend._silent_exec_callback(
- 'get_ipython().magic("lsmagic")',
- _handle_data
- )
-
- # minimize/maximize/fullscreen actions:
-
- def toggle_menu_bar(self):
- menu_bar = self.menuBar()
- if menu_bar.isVisible():
- menu_bar.setVisible(False)
- else:
- menu_bar.setVisible(True)
-
- def toggleMinimized(self):
- if not self.isMinimized():
- self.showMinimized()
- else:
- self.showNormal()
-
- def _open_online_help(self):
- filename="http://ipython.org/ipython-doc/stable/index.html"
- webbrowser.open(filename, new=1, autoraise=True)
-
- def toggleMaximized(self):
- if not self.isMaximized():
- self.showMaximized()
- else:
- self.showNormal()
-
- # Min/Max imizing while in full screen give a bug
- # when going out of full screen, at least on OSX
- def toggleFullScreen(self):
- if not self.isFullScreen():
- self.showFullScreen()
- if sys.platform == 'darwin':
- self.maximizeAct.setEnabled(False)
- self.minimizeAct.setEnabled(False)
- else:
- self.showNormal()
- if sys.platform == 'darwin':
- self.maximizeAct.setEnabled(True)
- self.minimizeAct.setEnabled(True)
-
- def set_paging_active_frontend(self, paging):
- self.active_frontend._set_paging(paging)
-
- def close_active_frontend(self):
- self.close_tab(self.active_frontend)
-
- def restart_kernel_active_frontend(self):
- self.active_frontend.request_restart_kernel()
-
- def interrupt_kernel_active_frontend(self):
- self.active_frontend.request_interrupt_kernel()
-
- def toggle_confirm_restart_active_frontend(self):
- widget = self.active_frontend
- widget.confirm_restart = not widget.confirm_restart
- self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
-
- def update_restart_checkbox(self):
- if self.active_frontend is None:
- return
- widget = self.active_frontend
- self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
-
- def cut_active_frontend(self):
- widget = self.active_frontend
- if widget.can_cut():
- widget.cut()
-
- def copy_active_frontend(self):
- widget = self.active_frontend
- widget.copy()
-
- def copy_raw_active_frontend(self):
- self.active_frontend._copy_raw_action.trigger()
-
- def paste_active_frontend(self):
- widget = self.active_frontend
- if widget.can_paste():
- widget.paste()
-
- def undo_active_frontend(self):
- self.active_frontend.undo()
-
- def redo_active_frontend(self):
- self.active_frontend.redo()
-
- def reset_magic_active_frontend(self):
- self.active_frontend.execute("%reset")
-
- def history_magic_active_frontend(self):
- self.active_frontend.execute("%history")
-
- def save_magic_active_frontend(self):
- self.active_frontend.save_magic()
-
- def clear_magic_active_frontend(self):
- self.active_frontend.execute("%clear")
-
- def who_magic_active_frontend(self):
- self.active_frontend.execute("%who")
-
- def who_ls_magic_active_frontend(self):
- self.active_frontend.execute("%who_ls")
-
- def whos_magic_active_frontend(self):
- self.active_frontend.execute("%whos")
-
- def print_action_active_frontend(self):
- self.active_frontend.print_action.trigger()
-
- def export_action_active_frontend(self):
- self.active_frontend.export_action.trigger()
-
- def select_all_active_frontend(self):
- self.active_frontend.select_all_action.trigger()
-
- def increase_font_size_active_frontend(self):
- self.active_frontend.increase_font_size.trigger()
-
- def decrease_font_size_active_frontend(self):
- self.active_frontend.decrease_font_size.trigger()
-
- def reset_font_size_active_frontend(self):
- self.active_frontend.reset_font_size.trigger()
-
- def guiref_active_frontend(self):
- self.active_frontend.execute("%guiref")
-
- def intro_active_frontend(self):
- self.active_frontend.execute("?")
-
- def quickref_active_frontend(self):
- self.active_frontend.execute("%quickref")
- #---------------------------------------------------------------------------
- # QWidget interface
- #---------------------------------------------------------------------------
-
- def closeEvent(self, event):
- """ Forward the close event to every tabs contained by the windows
- """
- if self.tab_widget.count() == 0:
- # no tabs, just close
- event.accept()
- return
- # Do Not loop on the widget count as it change while closing
- title = self.window().windowTitle()
- cancel = QtGui.QMessageBox.Cancel
- okay = QtGui.QMessageBox.Ok
- accept_role = QtGui.QMessageBox.AcceptRole
-
- if self.confirm_exit:
- if self.tab_widget.count() > 1:
- msg = "Close all tabs, stop all kernels, and Quit?"
- else:
- msg = "Close console, stop kernel, and Quit?"
- info = "Kernels not started here (e.g. notebooks) will be left alone."
- closeall = QtGui.QPushButton("&Quit", self)
- closeall.setShortcut('Q')
- box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
- title, msg)
- box.setInformativeText(info)
- box.addButton(cancel)
- box.addButton(closeall, QtGui.QMessageBox.YesRole)
- box.setDefaultButton(closeall)
- box.setEscapeButton(cancel)
- pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
- box.setIconPixmap(pixmap)
- reply = box.exec_()
- else:
- reply = okay
-
- if reply == cancel:
- event.ignore()
- return
- if reply == okay or reply == accept_role:
- while self.tab_widget.count() >= 1:
- # prevent further confirmations:
- widget = self.active_frontend
- widget._confirm_exit = False
- self.close_tab(widget)
- event.accept()
-
diff --git a/jupyter_qtconsole/console/pygments_highlighter.py b/jupyter_qtconsole/console/pygments_highlighter.py
deleted file mode 100644
index 35b6255..0000000
--- a/jupyter_qtconsole/console/pygments_highlighter.py
+++ /dev/null
@@ -1,232 +0,0 @@
-# System library imports.
-from IPython.external.qt import QtGui
-from pygments.formatters.html import HtmlFormatter
-from pygments.lexer import RegexLexer, _TokenType, Text, Error
-from pygments.lexers import PythonLexer
-from pygments.styles import get_style_by_name
-
-# Local imports
-from IPython.utils.py3compat import string_types
-
-def get_tokens_unprocessed(self, text, stack=('root',)):
- """ Split ``text`` into (tokentype, text) pairs.
-
- Monkeypatched to store the final stack on the object itself.
-
- The `text` parameter this gets passed is only the current line, so to
- highlight things like multiline strings correctly, we need to retrieve
- the state from the previous line (this is done in PygmentsHighlighter,
- below), and use it to continue processing the current line.
- """
- pos = 0
- tokendefs = self._tokens
- if hasattr(self, '_saved_state_stack'):
- statestack = list(self._saved_state_stack)
- else:
- statestack = list(stack)
- statetokens = tokendefs[statestack[-1]]
- while 1:
- for rexmatch, action, new_state in statetokens:
- m = rexmatch(text, pos)
- if m:
- if action is not None:
- if type(action) is _TokenType:
- yield pos, action, m.group()
- else:
- for item in action(self, m):
- yield item
- pos = m.end()
- if new_state is not None:
- # state transition
- if isinstance(new_state, tuple):
- for state in new_state:
- if state == '#pop':
- statestack.pop()
- elif state == '#push':
- statestack.append(statestack[-1])
- else:
- statestack.append(state)
- elif isinstance(new_state, int):
- # pop
- del statestack[new_state:]
- elif new_state == '#push':
- statestack.append(statestack[-1])
- else:
- assert False, "wrong state def: %r" % new_state
- statetokens = tokendefs[statestack[-1]]
- break
- else:
- try:
- if text[pos] == '\n':
- # at EOL, reset state to "root"
- pos += 1
- statestack = ['root']
- statetokens = tokendefs['root']
- yield pos, Text, u'\n'
- continue
- yield pos, Error, text[pos]
- pos += 1
- except IndexError:
- break
- self._saved_state_stack = list(statestack)
-
-# Monkeypatch!
-RegexLexer.get_tokens_unprocessed = get_tokens_unprocessed
-
-
-class PygmentsBlockUserData(QtGui.QTextBlockUserData):
- """ Storage for the user data associated with each line.
- """
-
- syntax_stack = ('root',)
-
- def __init__(self, **kwds):
- for key, value in kwds.items():
- setattr(self, key, value)
- QtGui.QTextBlockUserData.__init__(self)
-
- def __repr__(self):
- attrs = ['syntax_stack']
- kwds = ', '.join([ '%s=%r' % (attr, getattr(self, attr))
- for attr in attrs ])
- return 'PygmentsBlockUserData(%s)' % kwds
-
-
-class PygmentsHighlighter(QtGui.QSyntaxHighlighter):
- """ Syntax highlighter that uses Pygments for parsing. """
-
- #---------------------------------------------------------------------------
- # 'QSyntaxHighlighter' interface
- #---------------------------------------------------------------------------
-
- def __init__(self, parent, lexer=None):
- super(PygmentsHighlighter, self).__init__(parent)
-
- self._document = self.document()
- self._formatter = HtmlFormatter(nowrap=True)
- self._lexer = lexer if lexer else PythonLexer()
- self.set_style('default')
-
- def highlightBlock(self, string):
- """ Highlight a block of text.
- """
- prev_data = self.currentBlock().previous().userData()
- if prev_data is not None:
- self._lexer._saved_state_stack = prev_data.syntax_stack
- elif hasattr(self._lexer, '_saved_state_stack'):
- del self._lexer._saved_state_stack
-
- # Lex the text using Pygments
- index = 0
- for token, text in self._lexer.get_tokens(string):
- length = len(text)
- self.setFormat(index, length, self._get_format(token))
- index += length
-
- if hasattr(self._lexer, '_saved_state_stack'):
- data = PygmentsBlockUserData(
- syntax_stack=self._lexer._saved_state_stack)
- self.currentBlock().setUserData(data)
- # Clean up for the next go-round.
- del self._lexer._saved_state_stack
-
- #---------------------------------------------------------------------------
- # 'PygmentsHighlighter' interface
- #---------------------------------------------------------------------------
-
- def set_style(self, style):
- """ Sets the style to the specified Pygments style.
- """
- if isinstance(style, string_types):
- style = get_style_by_name(style)
- self._style = style
- self._clear_caches()
-
- def set_style_sheet(self, stylesheet):
- """ Sets a CSS stylesheet. The classes in the stylesheet should
- correspond to those generated by:
-
- pygmentize -S
-