Uploaded Test files
This commit is contained in:
parent
f584ad9d97
commit
2e81cb7d99
16627 changed files with 2065359 additions and 102444 deletions
1
venv/Lib/site-packages/qtconsole/__init__.py
Normal file
1
venv/Lib/site-packages/qtconsole/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from ._version import version_info, __version__
|
3
venv/Lib/site-packages/qtconsole/__main__.py
Normal file
3
venv/Lib/site-packages/qtconsole/__main__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
if __name__ == '__main__':
|
||||
from qtconsole.qtconsoleapp import main
|
||||
main()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
venv/Lib/site-packages/qtconsole/__pycache__/svg.cpython-36.pyc
Normal file
BIN
venv/Lib/site-packages/qtconsole/__pycache__/svg.cpython-36.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
venv/Lib/site-packages/qtconsole/__pycache__/util.cpython-36.pyc
Normal file
BIN
venv/Lib/site-packages/qtconsole/__pycache__/util.cpython-36.pyc
Normal file
Binary file not shown.
2
venv/Lib/site-packages/qtconsole/_version.py
Normal file
2
venv/Lib/site-packages/qtconsole/_version.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
version_info = (4, 7, 7)
|
||||
__version__ = '.'.join(map(str, version_info))
|
393
venv/Lib/site-packages/qtconsole/ansi_code_processor.py
Normal file
393
venv/Lib/site-packages/qtconsole/ansi_code_processor.py
Normal file
|
@ -0,0 +1,393 @@
|
|||
""" Utilities for processing ANSI escape codes and special ASCII characters.
|
||||
"""
|
||||
#-----------------------------------------------------------------------------
|
||||
# Imports
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# Standard library imports
|
||||
from collections import namedtuple
|
||||
import re
|
||||
|
||||
# System library imports
|
||||
from qtpy import QtGui
|
||||
|
||||
# Local imports
|
||||
from ipython_genutils.py3compat import string_types
|
||||
from qtconsole.styles import dark_style
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Constants and datatypes
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# An action for erase requests (ED and EL commands).
|
||||
EraseAction = namedtuple('EraseAction', ['action', 'area', 'erase_to'])
|
||||
|
||||
# An action for cursor move requests (CUU, CUD, CUF, CUB, CNL, CPL, CHA, CUP,
|
||||
# and HVP commands).
|
||||
# FIXME: Not implemented in AnsiCodeProcessor.
|
||||
MoveAction = namedtuple('MoveAction', ['action', 'dir', 'unit', 'count'])
|
||||
|
||||
# An action for scroll requests (SU and ST) and form feeds.
|
||||
ScrollAction = namedtuple('ScrollAction', ['action', 'dir', 'unit', 'count'])
|
||||
|
||||
# An action for the carriage return character
|
||||
CarriageReturnAction = namedtuple('CarriageReturnAction', ['action'])
|
||||
|
||||
# An action for the \n character
|
||||
NewLineAction = namedtuple('NewLineAction', ['action'])
|
||||
|
||||
# An action for the beep character
|
||||
BeepAction = namedtuple('BeepAction', ['action'])
|
||||
|
||||
# An action for backspace
|
||||
BackSpaceAction = namedtuple('BackSpaceAction', ['action'])
|
||||
|
||||
# Regular expressions.
|
||||
CSI_COMMANDS = 'ABCDEFGHJKSTfmnsu'
|
||||
CSI_SUBPATTERN = '\[(.*?)([%s])' % CSI_COMMANDS
|
||||
OSC_SUBPATTERN = '\](.*?)[\x07\x1b]'
|
||||
ANSI_PATTERN = ('\x01?\x1b(%s|%s)\x02?' % \
|
||||
(CSI_SUBPATTERN, OSC_SUBPATTERN))
|
||||
ANSI_OR_SPECIAL_PATTERN = re.compile('(\a|\b|\r(?!\n)|\r?\n)|(?:%s)' % ANSI_PATTERN)
|
||||
SPECIAL_PATTERN = re.compile('([\f])')
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Classes
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
class AnsiCodeProcessor(object):
|
||||
""" Translates special ASCII characters and ANSI escape codes into readable
|
||||
attributes. It also supports a few non-standard, xterm-specific codes.
|
||||
"""
|
||||
|
||||
# Whether to increase intensity or set boldness for SGR code 1.
|
||||
# (Different terminals handle this in different ways.)
|
||||
bold_text_enabled = False
|
||||
|
||||
# We provide an empty default color map because subclasses will likely want
|
||||
# to use a custom color format.
|
||||
default_color_map = {}
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# AnsiCodeProcessor interface
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
def __init__(self):
|
||||
self.actions = []
|
||||
self.color_map = self.default_color_map.copy()
|
||||
self.reset_sgr()
|
||||
|
||||
def reset_sgr(self):
|
||||
""" Reset graphics attributs to their default values.
|
||||
"""
|
||||
self.intensity = 0
|
||||
self.italic = False
|
||||
self.bold = False
|
||||
self.underline = False
|
||||
self.foreground_color = None
|
||||
self.background_color = None
|
||||
|
||||
def split_string(self, string):
|
||||
""" Yields substrings for which the same escape code applies.
|
||||
"""
|
||||
self.actions = []
|
||||
start = 0
|
||||
|
||||
# strings ending with \r are assumed to be ending in \r\n since
|
||||
# \n is appended to output strings automatically. Accounting
|
||||
# for that, here.
|
||||
last_char = '\n' if len(string) > 0 and string[-1] == '\n' else None
|
||||
string = string[:-1] if last_char is not None else string
|
||||
|
||||
for match in ANSI_OR_SPECIAL_PATTERN.finditer(string):
|
||||
raw = string[start:match.start()]
|
||||
substring = SPECIAL_PATTERN.sub(self._replace_special, raw)
|
||||
if substring or self.actions:
|
||||
yield substring
|
||||
self.actions = []
|
||||
start = match.end()
|
||||
|
||||
groups = [g for g in match.groups() if (g is not None)]
|
||||
g0 = groups[0]
|
||||
if g0 == '\a':
|
||||
self.actions.append(BeepAction('beep'))
|
||||
yield None
|
||||
self.actions = []
|
||||
elif g0 == '\r':
|
||||
self.actions.append(CarriageReturnAction('carriage-return'))
|
||||
yield None
|
||||
self.actions = []
|
||||
elif g0 == '\b':
|
||||
self.actions.append(BackSpaceAction('backspace'))
|
||||
yield None
|
||||
self.actions = []
|
||||
elif g0 == '\n' or g0 == '\r\n':
|
||||
self.actions.append(NewLineAction('newline'))
|
||||
yield g0
|
||||
self.actions = []
|
||||
else:
|
||||
params = [ param for param in groups[1].split(';') if param ]
|
||||
if g0.startswith('['):
|
||||
# Case 1: CSI code.
|
||||
try:
|
||||
params = list(map(int, params))
|
||||
except ValueError:
|
||||
# Silently discard badly formed codes.
|
||||
pass
|
||||
else:
|
||||
self.set_csi_code(groups[2], params)
|
||||
|
||||
elif g0.startswith(']'):
|
||||
# Case 2: OSC code.
|
||||
self.set_osc_code(params)
|
||||
|
||||
raw = string[start:]
|
||||
substring = SPECIAL_PATTERN.sub(self._replace_special, raw)
|
||||
if substring or self.actions:
|
||||
yield substring
|
||||
|
||||
if last_char is not None:
|
||||
self.actions.append(NewLineAction('newline'))
|
||||
yield last_char
|
||||
|
||||
def set_csi_code(self, command, params=[]):
|
||||
""" Set attributes based on CSI (Control Sequence Introducer) code.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
command : str
|
||||
The code identifier, i.e. the final character in the sequence.
|
||||
|
||||
params : sequence of integers, optional
|
||||
The parameter codes for the command.
|
||||
"""
|
||||
if command == 'm': # SGR - Select Graphic Rendition
|
||||
if params:
|
||||
self.set_sgr_code(params)
|
||||
else:
|
||||
self.set_sgr_code([0])
|
||||
|
||||
elif (command == 'J' or # ED - Erase Data
|
||||
command == 'K'): # EL - Erase in Line
|
||||
code = params[0] if params else 0
|
||||
if 0 <= code <= 2:
|
||||
area = 'screen' if command == 'J' else 'line'
|
||||
if code == 0:
|
||||
erase_to = 'end'
|
||||
elif code == 1:
|
||||
erase_to = 'start'
|
||||
elif code == 2:
|
||||
erase_to = 'all'
|
||||
self.actions.append(EraseAction('erase', area, erase_to))
|
||||
|
||||
elif (command == 'S' or # SU - Scroll Up
|
||||
command == 'T'): # SD - Scroll Down
|
||||
dir = 'up' if command == 'S' else 'down'
|
||||
count = params[0] if params else 1
|
||||
self.actions.append(ScrollAction('scroll', dir, 'line', count))
|
||||
|
||||
def set_osc_code(self, params):
|
||||
""" Set attributes based on OSC (Operating System Command) parameters.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
params : sequence of str
|
||||
The parameters for the command.
|
||||
"""
|
||||
try:
|
||||
command = int(params.pop(0))
|
||||
except (IndexError, ValueError):
|
||||
return
|
||||
|
||||
if command == 4:
|
||||
# xterm-specific: set color number to color spec.
|
||||
try:
|
||||
color = int(params.pop(0))
|
||||
spec = params.pop(0)
|
||||
self.color_map[color] = self._parse_xterm_color_spec(spec)
|
||||
except (IndexError, ValueError):
|
||||
pass
|
||||
|
||||
def set_sgr_code(self, params):
|
||||
""" Set attributes based on SGR (Select Graphic Rendition) codes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
params : sequence of ints
|
||||
A list of SGR codes for one or more SGR commands. Usually this
|
||||
sequence will have one element per command, although certain
|
||||
xterm-specific commands requires multiple elements.
|
||||
"""
|
||||
# Always consume the first parameter.
|
||||
if not params:
|
||||
return
|
||||
code = params.pop(0)
|
||||
|
||||
if code == 0:
|
||||
self.reset_sgr()
|
||||
elif code == 1:
|
||||
if self.bold_text_enabled:
|
||||
self.bold = True
|
||||
else:
|
||||
self.intensity = 1
|
||||
elif code == 2:
|
||||
self.intensity = 0
|
||||
elif code == 3:
|
||||
self.italic = True
|
||||
elif code == 4:
|
||||
self.underline = True
|
||||
elif code == 22:
|
||||
self.intensity = 0
|
||||
self.bold = False
|
||||
elif code == 23:
|
||||
self.italic = False
|
||||
elif code == 24:
|
||||
self.underline = False
|
||||
elif code >= 30 and code <= 37:
|
||||
self.foreground_color = code - 30
|
||||
elif code == 38 and params:
|
||||
_color_type = params.pop(0)
|
||||
if _color_type == 5 and params:
|
||||
# xterm-specific: 256 color support.
|
||||
self.foreground_color = params.pop(0)
|
||||
elif _color_type == 2:
|
||||
# 24bit true colour support.
|
||||
self.foreground_color = params[:3]
|
||||
params[:3] = []
|
||||
elif code == 39:
|
||||
self.foreground_color = None
|
||||
elif code >= 40 and code <= 47:
|
||||
self.background_color = code - 40
|
||||
elif code == 48 and params:
|
||||
_color_type = params.pop(0)
|
||||
if _color_type == 5 and params:
|
||||
# xterm-specific: 256 color support.
|
||||
self.background_color = params.pop(0)
|
||||
elif _color_type == 2:
|
||||
# 24bit true colour support.
|
||||
self.background_color = params[:3]
|
||||
params[:3] = []
|
||||
elif code == 49:
|
||||
self.background_color = None
|
||||
|
||||
# Recurse with unconsumed parameters.
|
||||
self.set_sgr_code(params)
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Protected interface
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
def _parse_xterm_color_spec(self, spec):
|
||||
if spec.startswith('rgb:'):
|
||||
return tuple(map(lambda x: int(x, 16), spec[4:].split('/')))
|
||||
elif spec.startswith('rgbi:'):
|
||||
return tuple(map(lambda x: int(float(x) * 255),
|
||||
spec[5:].split('/')))
|
||||
elif spec == '?':
|
||||
raise ValueError('Unsupported xterm color spec')
|
||||
return spec
|
||||
|
||||
def _replace_special(self, match):
|
||||
special = match.group(1)
|
||||
if special == '\f':
|
||||
self.actions.append(ScrollAction('scroll', 'down', 'page', 1))
|
||||
return ''
|
||||
|
||||
|
||||
class QtAnsiCodeProcessor(AnsiCodeProcessor):
|
||||
""" Translates ANSI escape codes into QTextCharFormats.
|
||||
"""
|
||||
|
||||
# A map from ANSI color codes to SVG color names or RGB(A) tuples.
|
||||
darkbg_color_map = {
|
||||
0 : 'black', # black
|
||||
1 : 'darkred', # red
|
||||
2 : 'darkgreen', # green
|
||||
3 : 'brown', # yellow
|
||||
4 : 'darkblue', # blue
|
||||
5 : 'darkviolet', # magenta
|
||||
6 : 'steelblue', # cyan
|
||||
7 : 'grey', # white
|
||||
8 : 'grey', # black (bright)
|
||||
9 : 'red', # red (bright)
|
||||
10 : 'lime', # green (bright)
|
||||
11 : 'yellow', # yellow (bright)
|
||||
12 : 'deepskyblue', # blue (bright)
|
||||
13 : 'magenta', # magenta (bright)
|
||||
14 : 'cyan', # cyan (bright)
|
||||
15 : 'white' } # white (bright)
|
||||
|
||||
# Set the default color map for super class.
|
||||
default_color_map = darkbg_color_map.copy()
|
||||
|
||||
def get_color(self, color, intensity=0):
|
||||
""" Returns a QColor for a given color code or rgb list, or None if one
|
||||
cannot be constructed.
|
||||
"""
|
||||
|
||||
if isinstance(color, int):
|
||||
# Adjust for intensity, if possible.
|
||||
if color < 8 and intensity > 0:
|
||||
color += 8
|
||||
constructor = self.color_map.get(color, None)
|
||||
elif isinstance(color, (tuple, list)):
|
||||
constructor = color
|
||||
else:
|
||||
return None
|
||||
|
||||
if isinstance(constructor, string_types):
|
||||
# If this is an X11 color name, we just hope there is a close SVG
|
||||
# color name. We could use QColor's static method
|
||||
# 'setAllowX11ColorNames()', but this is global and only available
|
||||
# on X11. It seems cleaner to aim for uniformity of behavior.
|
||||
return QtGui.QColor(constructor)
|
||||
|
||||
elif isinstance(constructor, (tuple, list)):
|
||||
return QtGui.QColor(*constructor)
|
||||
|
||||
return None
|
||||
|
||||
def get_format(self):
|
||||
""" Returns a QTextCharFormat that encodes the current style attributes.
|
||||
"""
|
||||
format = QtGui.QTextCharFormat()
|
||||
|
||||
# Set foreground color
|
||||
qcolor = self.get_color(self.foreground_color, self.intensity)
|
||||
if qcolor is not None:
|
||||
format.setForeground(qcolor)
|
||||
|
||||
# Set background color
|
||||
qcolor = self.get_color(self.background_color, self.intensity)
|
||||
if qcolor is not None:
|
||||
format.setBackground(qcolor)
|
||||
|
||||
# Set font weight/style options
|
||||
if self.bold:
|
||||
format.setFontWeight(QtGui.QFont.Bold)
|
||||
else:
|
||||
format.setFontWeight(QtGui.QFont.Normal)
|
||||
format.setFontItalic(self.italic)
|
||||
format.setFontUnderline(self.underline)
|
||||
|
||||
return format
|
||||
|
||||
def set_background_color(self, style):
|
||||
"""
|
||||
Given a syntax style, attempt to set a color map that will be
|
||||
aesthetically pleasing.
|
||||
"""
|
||||
# Set a new default color map.
|
||||
self.default_color_map = self.darkbg_color_map.copy()
|
||||
|
||||
if not dark_style(style):
|
||||
# Colors appropriate for a terminal with a light background. For
|
||||
# now, only use non-bright colors...
|
||||
for i in range(8):
|
||||
self.default_color_map[i + 8] = self.default_color_map[i]
|
||||
|
||||
# ...and replace white with black.
|
||||
self.default_color_map[7] = self.default_color_map[15] = 'black'
|
||||
|
||||
# Update the current color map with the new defaults.
|
||||
self.color_map.update(self.default_color_map)
|
158
venv/Lib/site-packages/qtconsole/base_frontend_mixin.py
Normal file
158
venv/Lib/site-packages/qtconsole/base_frontend_mixin.py
Normal file
|
@ -0,0 +1,158 @@
|
|||
"""Defines a convenient mix-in class for implementing Qt frontends."""
|
||||
|
||||
|
||||
class BaseFrontendMixin(object):
|
||||
""" A mix-in class for implementing Qt frontends.
|
||||
|
||||
To handle messages of a particular type, frontends need only define an
|
||||
appropriate handler method. For example, to handle 'stream' messaged, define
|
||||
a '_handle_stream(msg)' method.
|
||||
"""
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# 'BaseFrontendMixin' concrete interface
|
||||
#---------------------------------------------------------------------------
|
||||
_kernel_client = None
|
||||
_kernel_manager = None
|
||||
|
||||
@property
|
||||
def kernel_client(self):
|
||||
"""Returns the current kernel client."""
|
||||
return self._kernel_client
|
||||
|
||||
@kernel_client.setter
|
||||
def kernel_client(self, kernel_client):
|
||||
"""Disconnect from the current kernel client (if any) and set a new
|
||||
kernel client.
|
||||
"""
|
||||
# Disconnect the old kernel client, if necessary.
|
||||
old_client = self._kernel_client
|
||||
if old_client is not None:
|
||||
old_client.started_channels.disconnect(self._started_channels)
|
||||
old_client.stopped_channels.disconnect(self._stopped_channels)
|
||||
|
||||
# Disconnect the old kernel client's channels.
|
||||
old_client.iopub_channel.message_received.disconnect(self._dispatch)
|
||||
old_client.shell_channel.message_received.disconnect(self._dispatch)
|
||||
old_client.stdin_channel.message_received.disconnect(self._dispatch)
|
||||
old_client.hb_channel.kernel_died.disconnect(
|
||||
self._handle_kernel_died)
|
||||
|
||||
# Handle the case where the old kernel client is still listening.
|
||||
if old_client.channels_running:
|
||||
self._stopped_channels()
|
||||
|
||||
# Set the new kernel client.
|
||||
self._kernel_client = kernel_client
|
||||
if kernel_client is None:
|
||||
return
|
||||
|
||||
# Connect the new kernel client.
|
||||
kernel_client.started_channels.connect(self._started_channels)
|
||||
kernel_client.stopped_channels.connect(self._stopped_channels)
|
||||
|
||||
# Connect the new kernel client's channels.
|
||||
kernel_client.iopub_channel.message_received.connect(self._dispatch)
|
||||
kernel_client.shell_channel.message_received.connect(self._dispatch)
|
||||
kernel_client.stdin_channel.message_received.connect(self._dispatch)
|
||||
# hb_channel
|
||||
kernel_client.hb_channel.kernel_died.connect(self._handle_kernel_died)
|
||||
|
||||
# Handle the case where the kernel client started channels before
|
||||
# we connected.
|
||||
if kernel_client.channels_running:
|
||||
self._started_channels()
|
||||
|
||||
@property
|
||||
def kernel_manager(self):
|
||||
"""The kernel manager, if any"""
|
||||
return self._kernel_manager
|
||||
|
||||
@kernel_manager.setter
|
||||
def kernel_manager(self, kernel_manager):
|
||||
old_man = self._kernel_manager
|
||||
if old_man is not None:
|
||||
old_man.kernel_restarted.disconnect(self._handle_kernel_restarted)
|
||||
|
||||
self._kernel_manager = kernel_manager
|
||||
if kernel_manager is None:
|
||||
return
|
||||
|
||||
kernel_manager.kernel_restarted.connect(self._handle_kernel_restarted)
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# 'BaseFrontendMixin' abstract interface
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
def _handle_kernel_died(self, since_last_heartbeat):
|
||||
""" This is called when the ``kernel_died`` signal is emitted.
|
||||
|
||||
This method is called when the kernel heartbeat has not been
|
||||
active for a certain amount of time.
|
||||
This is a strictly passive notification -
|
||||
the kernel is likely being restarted by its KernelManager.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
since_last_heartbeat : float
|
||||
The time since the heartbeat was last received.
|
||||
"""
|
||||
|
||||
def _handle_kernel_restarted(self):
|
||||
""" This is called when the ``kernel_restarted`` signal is emitted.
|
||||
|
||||
This method is called when the kernel has been restarted by the
|
||||
autorestart mechanism.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
since_last_heartbeat : float
|
||||
The time since the heartbeat was last received.
|
||||
"""
|
||||
def _started_kernel(self):
|
||||
"""Called when the KernelManager starts (or restarts) the kernel subprocess.
|
||||
Channels may or may not be running at this point.
|
||||
"""
|
||||
|
||||
def _started_channels(self):
|
||||
""" Called when the KernelManager channels have started listening or
|
||||
when the frontend is assigned an already listening KernelManager.
|
||||
"""
|
||||
|
||||
def _stopped_channels(self):
|
||||
""" Called when the KernelManager channels have stopped listening or
|
||||
when a listening KernelManager is removed from the frontend.
|
||||
"""
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# 'BaseFrontendMixin' protected interface
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
def _dispatch(self, msg):
|
||||
""" Calls the frontend handler associated with the message type of the
|
||||
given message.
|
||||
"""
|
||||
msg_type = msg['header']['msg_type']
|
||||
handler = getattr(self, '_handle_' + msg_type, None)
|
||||
if handler:
|
||||
handler(msg)
|
||||
|
||||
def from_here(self, msg):
|
||||
"""Return whether a message is from this session"""
|
||||
session_id = self._kernel_client.session.session
|
||||
return msg['parent_header'].get("session", session_id) == session_id
|
||||
|
||||
def include_output(self, msg):
|
||||
"""Return whether we should include a given output message"""
|
||||
if self._hidden:
|
||||
return False
|
||||
from_here = self.from_here(msg)
|
||||
if msg['msg_type'] == 'execute_input':
|
||||
# only echo inputs not from here
|
||||
return self.include_other_output and not from_here
|
||||
|
||||
if self.include_other_output:
|
||||
return True
|
||||
else:
|
||||
return from_here
|
||||
|
100
venv/Lib/site-packages/qtconsole/bracket_matcher.py
Normal file
100
venv/Lib/site-packages/qtconsole/bracket_matcher.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
""" Provides bracket matching for Q[Plain]TextEdit widgets.
|
||||
"""
|
||||
|
||||
# System library imports
|
||||
from qtpy import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class BracketMatcher(QtCore.QObject):
|
||||
""" Matches square brackets, braces, and parentheses based on cursor
|
||||
position.
|
||||
"""
|
||||
|
||||
# Protected class variables.
|
||||
_opening_map = { '(':')', '{':'}', '[':']' }
|
||||
_closing_map = { ')':'(', '}':'{', ']':'[' }
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# 'QObject' interface
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
def __init__(self, text_edit):
|
||||
""" Create a call tip manager that is attached to the specified Qt
|
||||
text edit widget.
|
||||
"""
|
||||
assert isinstance(text_edit, (QtWidgets.QTextEdit, QtWidgets.QPlainTextEdit))
|
||||
super(BracketMatcher, self).__init__()
|
||||
|
||||
# The format to apply to matching brackets.
|
||||
self.format = QtGui.QTextCharFormat()
|
||||
self.format.setBackground(QtGui.QColor('silver'))
|
||||
|
||||
self._text_edit = text_edit
|
||||
text_edit.cursorPositionChanged.connect(self._cursor_position_changed)
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# Protected interface
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
def _find_match(self, position):
|
||||
""" Given a valid position in the text document, try to find the
|
||||
position of the matching bracket. Returns -1 if unsuccessful.
|
||||
"""
|
||||
# Decide what character to search for and what direction to search in.
|
||||
document = self._text_edit.document()
|
||||
start_char = document.characterAt(position)
|
||||
search_char = self._opening_map.get(start_char)
|
||||
if search_char:
|
||||
increment = 1
|
||||
else:
|
||||
search_char = self._closing_map.get(start_char)
|
||||
if search_char:
|
||||
increment = -1
|
||||
else:
|
||||
return -1
|
||||
|
||||
# Search for the character.
|
||||
char = start_char
|
||||
depth = 0
|
||||
while position >= 0 and position < document.characterCount():
|
||||
if char == start_char:
|
||||
depth += 1
|
||||
elif char == search_char:
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
break
|
||||
position += increment
|
||||
char = document.characterAt(position)
|
||||
else:
|
||||
position = -1
|
||||
return position
|
||||
|
||||
def _selection_for_character(self, position):
|
||||
""" Convenience method for selecting a character.
|
||||
"""
|
||||
selection = QtWidgets.QTextEdit.ExtraSelection()
|
||||
cursor = self._text_edit.textCursor()
|
||||
cursor.setPosition(position)
|
||||
cursor.movePosition(QtGui.QTextCursor.NextCharacter,
|
||||
QtGui.QTextCursor.KeepAnchor)
|
||||
selection.cursor = cursor
|
||||
selection.format = self.format
|
||||
return selection
|
||||
|
||||
#------ Signal handlers ----------------------------------------------------
|
||||
|
||||
def _cursor_position_changed(self):
|
||||
""" Updates the document formatting based on the new cursor position.
|
||||
"""
|
||||
# Clear out the old formatting.
|
||||
self._text_edit.setExtraSelections([])
|
||||
|
||||
# Attempt to match a bracket for the new cursor position.
|
||||
cursor = self._text_edit.textCursor()
|
||||
if not cursor.hasSelection():
|
||||
position = cursor.position() - 1
|
||||
match_position = self._find_match(position)
|
||||
if match_position != -1:
|
||||
extra_selections = [ self._selection_for_character(pos)
|
||||
for pos in (position, match_position) ]
|
||||
self._text_edit.setExtraSelections(extra_selections)
|
265
venv/Lib/site-packages/qtconsole/call_tip_widget.py
Normal file
265
venv/Lib/site-packages/qtconsole/call_tip_widget.py
Normal file
|
@ -0,0 +1,265 @@
|
|||
# Standard library imports
|
||||
import re
|
||||
from unicodedata import category
|
||||
|
||||
# System library imports
|
||||
from qtpy import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class CallTipWidget(QtWidgets.QLabel):
|
||||
""" Shows call tips by parsing the current text of Q[Plain]TextEdit.
|
||||
"""
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# 'QObject' interface
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
def __init__(self, text_edit):
|
||||
""" Create a call tip manager that is attached to the specified Qt
|
||||
text edit widget.
|
||||
"""
|
||||
assert isinstance(text_edit, (QtWidgets.QTextEdit, QtWidgets.QPlainTextEdit))
|
||||
super(CallTipWidget, self).__init__(None, QtCore.Qt.ToolTip)
|
||||
|
||||
self._hide_timer = QtCore.QBasicTimer()
|
||||
self._text_edit = text_edit
|
||||
|
||||
self.setFont(text_edit.document().defaultFont())
|
||||
self.setForegroundRole(QtGui.QPalette.ToolTipText)
|
||||
self.setBackgroundRole(QtGui.QPalette.ToolTipBase)
|
||||
self.setPalette(QtWidgets.QToolTip.palette())
|
||||
|
||||
self.setAlignment(QtCore.Qt.AlignLeft)
|
||||
self.setIndent(1)
|
||||
self.setFrameStyle(QtWidgets.QFrame.NoFrame)
|
||||
self.setMargin(1 + self.style().pixelMetric(
|
||||
QtWidgets.QStyle.PM_ToolTipLabelFrameWidth, None, self))
|
||||
self.setWindowOpacity(self.style().styleHint(
|
||||
QtWidgets.QStyle.SH_ToolTipLabel_Opacity, None, self, None) / 255.0)
|
||||
self.setWordWrap(True)
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
""" Reimplemented to hide on certain key presses and on text edit focus
|
||||
changes.
|
||||
"""
|
||||
if obj == self._text_edit:
|
||||
etype = event.type()
|
||||
|
||||
if etype == QtCore.QEvent.KeyPress:
|
||||
key = event.key()
|
||||
if key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
|
||||
self.hide()
|
||||
elif key == QtCore.Qt.Key_Escape:
|
||||
self.hide()
|
||||
return True
|
||||
|
||||
elif etype == QtCore.QEvent.FocusOut:
|
||||
self.hide()
|
||||
|
||||
elif etype == QtCore.QEvent.Enter:
|
||||
self._hide_timer.stop()
|
||||
|
||||
elif etype == QtCore.QEvent.Leave:
|
||||
self._leave_event_hide()
|
||||
|
||||
return super(CallTipWidget, self).eventFilter(obj, event)
|
||||
|
||||
def timerEvent(self, event):
|
||||
""" Reimplemented to hide the widget when the hide timer fires.
|
||||
"""
|
||||
if event.timerId() == self._hide_timer.timerId():
|
||||
self._hide_timer.stop()
|
||||
self.hide()
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# 'QWidget' interface
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
def enterEvent(self, event):
|
||||
""" Reimplemented to cancel the hide timer.
|
||||
"""
|
||||
super(CallTipWidget, self).enterEvent(event)
|
||||
self._hide_timer.stop()
|
||||
|
||||
def hideEvent(self, event):
|
||||
""" Reimplemented to disconnect signal handlers and event filter.
|
||||
"""
|
||||
super(CallTipWidget, self).hideEvent(event)
|
||||
# This fixes issue jupyter/qtconsole#383
|
||||
try:
|
||||
self._text_edit.cursorPositionChanged.disconnect(
|
||||
self._cursor_position_changed)
|
||||
except TypeError:
|
||||
pass
|
||||
self._text_edit.removeEventFilter(self)
|
||||
|
||||
def leaveEvent(self, event):
|
||||
""" Reimplemented to start the hide timer.
|
||||
"""
|
||||
super(CallTipWidget, self).leaveEvent(event)
|
||||
self._leave_event_hide()
|
||||
|
||||
def paintEvent(self, event):
|
||||
""" Reimplemented to paint the background panel.
|
||||
"""
|
||||
painter = QtWidgets.QStylePainter(self)
|
||||
option = QtWidgets.QStyleOptionFrame()
|
||||
option.initFrom(self)
|
||||
painter.drawPrimitive(QtWidgets.QStyle.PE_PanelTipLabel, option)
|
||||
painter.end()
|
||||
|
||||
super(CallTipWidget, self).paintEvent(event)
|
||||
|
||||
def setFont(self, font):
|
||||
""" Reimplemented to allow use of this method as a slot.
|
||||
"""
|
||||
super(CallTipWidget, self).setFont(font)
|
||||
|
||||
def showEvent(self, event):
|
||||
""" Reimplemented to connect signal handlers and event filter.
|
||||
"""
|
||||
super(CallTipWidget, self).showEvent(event)
|
||||
self._text_edit.cursorPositionChanged.connect(
|
||||
self._cursor_position_changed)
|
||||
self._text_edit.installEventFilter(self)
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# 'CallTipWidget' interface
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
def show_inspect_data(self, content, maxlines=20):
|
||||
"""Show inspection data as a tooltip"""
|
||||
data = content.get('data', {})
|
||||
text = data.get('text/plain', '')
|
||||
match = re.match("(?:[^\n]*\n){%i}" % maxlines, text)
|
||||
if match:
|
||||
text = text[:match.end()] + '\n[Documentation continues...]'
|
||||
|
||||
return self.show_tip(self._format_tooltip(text))
|
||||
|
||||
def show_tip(self, tip):
|
||||
""" Attempts to show the specified tip at the current cursor location.
|
||||
"""
|
||||
# Attempt to find the cursor position at which to show the call tip.
|
||||
text_edit = self._text_edit
|
||||
document = text_edit.document()
|
||||
cursor = text_edit.textCursor()
|
||||
search_pos = cursor.position() - 1
|
||||
self._start_position, _ = self._find_parenthesis(search_pos,
|
||||
forward=False)
|
||||
if self._start_position == -1:
|
||||
return False
|
||||
|
||||
# Set the text and resize the widget accordingly.
|
||||
self.setText(tip)
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
# Locate and show the widget. Place the tip below the current line
|
||||
# unless it would be off the screen. In that case, decide the best
|
||||
# location based trying to minimize the area that goes off-screen.
|
||||
padding = 3 # Distance in pixels between cursor bounds and tip box.
|
||||
cursor_rect = text_edit.cursorRect(cursor)
|
||||
screen_rect = QtWidgets.QApplication.instance().desktop().screenGeometry(text_edit)
|
||||
point = text_edit.mapToGlobal(cursor_rect.bottomRight())
|
||||
point.setY(point.y() + padding)
|
||||
tip_height = self.size().height()
|
||||
tip_width = self.size().width()
|
||||
|
||||
vertical = 'bottom'
|
||||
horizontal = 'Right'
|
||||
if point.y() + tip_height > screen_rect.height() + screen_rect.y():
|
||||
point_ = text_edit.mapToGlobal(cursor_rect.topRight())
|
||||
# If tip is still off screen, check if point is in top or bottom
|
||||
# half of screen.
|
||||
if point_.y() - tip_height < padding:
|
||||
# If point is in upper half of screen, show tip below it.
|
||||
# otherwise above it.
|
||||
if 2*point.y() < screen_rect.height():
|
||||
vertical = 'bottom'
|
||||
else:
|
||||
vertical = 'top'
|
||||
else:
|
||||
vertical = 'top'
|
||||
if point.x() + tip_width > screen_rect.width() + screen_rect.x():
|
||||
point_ = text_edit.mapToGlobal(cursor_rect.topRight())
|
||||
# If tip is still off-screen, check if point is in the right or
|
||||
# left half of the screen.
|
||||
if point_.x() - tip_width < padding:
|
||||
if 2*point.x() < screen_rect.width():
|
||||
horizontal = 'Right'
|
||||
else:
|
||||
horizontal = 'Left'
|
||||
else:
|
||||
horizontal = 'Left'
|
||||
pos = getattr(cursor_rect, '%s%s' %(vertical, horizontal))
|
||||
point = text_edit.mapToGlobal(pos())
|
||||
point.setY(point.y() + padding)
|
||||
if vertical == 'top':
|
||||
point.setY(point.y() - tip_height)
|
||||
if horizontal == 'Left':
|
||||
point.setX(point.x() - tip_width - padding)
|
||||
|
||||
self.move(point)
|
||||
self.show()
|
||||
return True
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# Protected interface
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
def _find_parenthesis(self, position, forward=True):
|
||||
""" If 'forward' is True (resp. False), proceed forwards
|
||||
(resp. backwards) through the line that contains 'position' until an
|
||||
unmatched closing (resp. opening) parenthesis is found. Returns a
|
||||
tuple containing the position of this parenthesis (or -1 if it is
|
||||
not found) and the number commas (at depth 0) found along the way.
|
||||
"""
|
||||
commas = depth = 0
|
||||
document = self._text_edit.document()
|
||||
char = document.characterAt(position)
|
||||
# Search until a match is found or a non-printable character is
|
||||
# encountered.
|
||||
while category(char) != 'Cc' and position > 0:
|
||||
if char == ',' and depth == 0:
|
||||
commas += 1
|
||||
elif char == ')':
|
||||
if forward and depth == 0:
|
||||
break
|
||||
depth += 1
|
||||
elif char == '(':
|
||||
if not forward and depth == 0:
|
||||
break
|
||||
depth -= 1
|
||||
position += 1 if forward else -1
|
||||
char = document.characterAt(position)
|
||||
else:
|
||||
position = -1
|
||||
return position, commas
|
||||
|
||||
def _leave_event_hide(self):
|
||||
""" Hides the tooltip after some time has passed (assuming the cursor is
|
||||
not over the tooltip).
|
||||
"""
|
||||
if (not self._hide_timer.isActive() and
|
||||
# If Enter events always came after Leave events, we wouldn't need
|
||||
# this check. But on Mac OS, it sometimes happens the other way
|
||||
# around when the tooltip is created.
|
||||
QtWidgets.QApplication.instance().topLevelAt(QtGui.QCursor.pos()) != self):
|
||||
self._hide_timer.start(300, self)
|
||||
|
||||
def _format_tooltip(self, doc):
|
||||
doc = re.sub(r'\033\[(\d|;)+?m', '', doc)
|
||||
return doc
|
||||
|
||||
#------ Signal handlers ----------------------------------------------------
|
||||
|
||||
def _cursor_position_changed(self):
|
||||
""" Updates the tip based on user cursor movement.
|
||||
"""
|
||||
cursor = self._text_edit.textCursor()
|
||||
if cursor.position() <= self._start_position:
|
||||
self.hide()
|
||||
else:
|
||||
position, commas = self._find_parenthesis(self._start_position + 1)
|
||||
if position != -1:
|
||||
self.hide()
|
71
venv/Lib/site-packages/qtconsole/client.py
Normal file
71
venv/Lib/site-packages/qtconsole/client.py
Normal file
|
@ -0,0 +1,71 @@
|
|||
""" Defines a KernelClient that provides signals and slots.
|
||||
"""
|
||||
import atexit
|
||||
import errno
|
||||
from threading import Thread
|
||||
import time
|
||||
|
||||
import zmq
|
||||
# import ZMQError in top-level namespace, to avoid ugly attribute-error messages
|
||||
# during garbage collection of threads at exit:
|
||||
from zmq import ZMQError
|
||||
from zmq.eventloop import ioloop, zmqstream
|
||||
|
||||
from qtpy import QtCore
|
||||
|
||||
# Local imports
|
||||
from traitlets import Type, Instance
|
||||
from jupyter_client.channels import HBChannel
|
||||
from jupyter_client import KernelClient
|
||||
from jupyter_client.channels import InvalidPortNumber
|
||||
from jupyter_client.threaded import ThreadedKernelClient, ThreadedZMQSocketChannel
|
||||
|
||||
from .kernel_mixins import QtKernelClientMixin
|
||||
from .util import SuperQObject
|
||||
|
||||
class QtHBChannel(SuperQObject, HBChannel):
|
||||
# A longer timeout than the base class
|
||||
time_to_dead = 3.0
|
||||
|
||||
# Emitted when the kernel has died.
|
||||
kernel_died = QtCore.Signal(object)
|
||||
|
||||
def call_handlers(self, since_last_heartbeat):
|
||||
""" Reimplemented to emit signals instead of making callbacks.
|
||||
"""
|
||||
# Emit the generic signal.
|
||||
self.kernel_died.emit(since_last_heartbeat)
|
||||
|
||||
from jupyter_client import protocol_version_info
|
||||
|
||||
major_protocol_version = protocol_version_info[0]
|
||||
|
||||
class QtZMQSocketChannel(ThreadedZMQSocketChannel,SuperQObject):
|
||||
"""A ZMQ socket emitting a Qt signal when a message is received."""
|
||||
message_received = QtCore.Signal(object)
|
||||
|
||||
def process_events(self):
|
||||
""" Process any pending GUI events.
|
||||
"""
|
||||
QtCore.QCoreApplication.instance().processEvents()
|
||||
|
||||
|
||||
def call_handlers(self, msg):
|
||||
"""This method is called in the ioloop thread when a message arrives.
|
||||
|
||||
It is important to remember that this method is called in the thread
|
||||
so that some logic must be done to ensure that the application level
|
||||
handlers are called in the application thread.
|
||||
"""
|
||||
# Emit the generic signal.
|
||||
self.message_received.emit(msg)
|
||||
|
||||
|
||||
class QtKernelClient(QtKernelClientMixin, ThreadedKernelClient):
|
||||
""" A KernelClient that provides signals and slots.
|
||||
"""
|
||||
|
||||
iopub_channel_class = Type(QtZMQSocketChannel)
|
||||
shell_channel_class = Type(QtZMQSocketChannel)
|
||||
stdin_channel_class = Type(QtZMQSocketChannel)
|
||||
hb_channel_class = Type(QtHBChannel)
|
274
venv/Lib/site-packages/qtconsole/comms.py
Normal file
274
venv/Lib/site-packages/qtconsole/comms.py
Normal file
|
@ -0,0 +1,274 @@
|
|||
"""
|
||||
Based on
|
||||
https://github.com/jupyter/notebook/blob/master/notebook/static/services/kernels/comm.js
|
||||
https://github.com/ipython/ipykernel/blob/master/ipykernel/comm/manager.py
|
||||
https://github.com/ipython/ipykernel/blob/master/ipykernel/comm/comm.py
|
||||
|
||||
|
||||
Which are distributed under the terms of the Modified BSD License.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from traitlets.config import LoggingConfigurable
|
||||
|
||||
from ipython_genutils.importstring import import_item
|
||||
from ipython_genutils.py3compat import string_types
|
||||
|
||||
import uuid
|
||||
|
||||
from qtpy import QtCore
|
||||
from qtconsole.util import MetaQObjectHasTraits, SuperQObject
|
||||
|
||||
|
||||
class CommManager(MetaQObjectHasTraits(
|
||||
'NewBase', (LoggingConfigurable, SuperQObject), {})):
|
||||
"""
|
||||
Manager for Comms in the Frontend
|
||||
"""
|
||||
|
||||
def __init__(self, kernel_client, *args, **kwargs):
|
||||
super(CommManager, self).__init__(*args, **kwargs)
|
||||
self.comms = {}
|
||||
self.targets = {}
|
||||
if kernel_client:
|
||||
self.init_kernel_client(kernel_client)
|
||||
|
||||
def init_kernel_client(self, kernel_client):
|
||||
"""
|
||||
connect the kernel, and register message handlers
|
||||
"""
|
||||
self.kernel_client = kernel_client
|
||||
kernel_client.iopub_channel.message_received.connect(self._dispatch)
|
||||
|
||||
@QtCore.Slot(object)
|
||||
def _dispatch(self, msg):
|
||||
"""Dispatch messages"""
|
||||
msg_type = msg['header']['msg_type']
|
||||
handled_msg_types = ['comm_open', 'comm_msg', 'comm_close']
|
||||
if msg_type in handled_msg_types:
|
||||
getattr(self, msg_type)(msg)
|
||||
|
||||
def new_comm(self, target_name, data=None, metadata=None,
|
||||
comm_id=None, buffers=None):
|
||||
"""
|
||||
Create a new Comm, register it, and open its Kernel-side counterpart
|
||||
Mimics the auto-registration in `Comm.__init__` in the Jupyter Comm.
|
||||
|
||||
argument comm_id is optional
|
||||
"""
|
||||
comm = Comm(target_name, self.kernel_client, comm_id)
|
||||
self.register_comm(comm)
|
||||
try:
|
||||
comm.open(data, metadata, buffers)
|
||||
except Exception:
|
||||
self.unregister_comm(comm)
|
||||
raise
|
||||
return comm
|
||||
|
||||
def register_target(self, target_name, f):
|
||||
"""Register a callable f for a given target name
|
||||
|
||||
f will be called with two arguments when a comm_open message is
|
||||
received with `target`:
|
||||
|
||||
- the Comm instance
|
||||
- the `comm_open` message itself.
|
||||
|
||||
f can be a Python callable or an import string for one.
|
||||
"""
|
||||
if isinstance(f, string_types):
|
||||
f = import_item(f)
|
||||
|
||||
self.targets[target_name] = f
|
||||
|
||||
def unregister_target(self, target_name, f):
|
||||
"""Unregister a callable registered with register_target"""
|
||||
return self.targets.pop(target_name)
|
||||
|
||||
def register_comm(self, comm):
|
||||
"""Register a new comm"""
|
||||
comm_id = comm.comm_id
|
||||
comm.kernel_client = self.kernel_client
|
||||
self.comms[comm_id] = comm
|
||||
comm.sig_is_closing.connect(self.unregister_comm)
|
||||
return comm_id
|
||||
|
||||
@QtCore.Slot(object)
|
||||
def unregister_comm(self, comm):
|
||||
"""Unregister a comm, and close its counterpart."""
|
||||
# unlike get_comm, this should raise a KeyError
|
||||
comm.sig_is_closing.disconnect(self.unregister_comm)
|
||||
self.comms.pop(comm.comm_id)
|
||||
|
||||
def get_comm(self, comm_id, closing=False):
|
||||
"""Get a comm with a particular id
|
||||
|
||||
Returns the comm if found, otherwise None.
|
||||
|
||||
This will not raise an error,
|
||||
it will log messages if the comm cannot be found.
|
||||
If the comm is closing, it might already have closed,
|
||||
so this is ignored.
|
||||
"""
|
||||
try:
|
||||
return self.comms[comm_id]
|
||||
except KeyError:
|
||||
if closing:
|
||||
return
|
||||
self.log.warning("No such comm: %s", comm_id)
|
||||
# don't create the list of keys if debug messages aren't enabled
|
||||
if self.log.isEnabledFor(logging.DEBUG):
|
||||
self.log.debug("Current comms: %s", list(self.comms.keys()))
|
||||
|
||||
# comm message handlers
|
||||
def comm_open(self, msg):
|
||||
"""Handler for comm_open messages"""
|
||||
content = msg['content']
|
||||
comm_id = content['comm_id']
|
||||
target_name = content['target_name']
|
||||
f = self.targets.get(target_name, None)
|
||||
|
||||
comm = Comm(target_name, self.kernel_client, comm_id)
|
||||
self.register_comm(comm)
|
||||
|
||||
if f is None:
|
||||
self.log.error("No such comm target registered: %s", target_name)
|
||||
else:
|
||||
try:
|
||||
f(comm, msg)
|
||||
return
|
||||
except Exception:
|
||||
self.log.error("Exception opening comm with target: %s",
|
||||
target_name, exc_info=True)
|
||||
|
||||
# Failure.
|
||||
try:
|
||||
comm.close()
|
||||
except Exception:
|
||||
self.log.error(
|
||||
"Could not close comm during `comm_open` failure "
|
||||
"clean-up. The comm may not have been opened yet.""",
|
||||
exc_info=True)
|
||||
|
||||
def comm_close(self, msg):
|
||||
"""Handler for comm_close messages"""
|
||||
content = msg['content']
|
||||
comm_id = content['comm_id']
|
||||
comm = self.get_comm(comm_id, closing=True)
|
||||
if comm is None:
|
||||
return
|
||||
|
||||
self.unregister_comm(comm)
|
||||
|
||||
try:
|
||||
comm.handle_close(msg)
|
||||
except Exception:
|
||||
self.log.error('Exception in comm_close for %s', comm_id,
|
||||
exc_info=True)
|
||||
|
||||
def comm_msg(self, msg):
|
||||
"""Handler for comm_msg messages"""
|
||||
content = msg['content']
|
||||
comm_id = content['comm_id']
|
||||
comm = self.get_comm(comm_id)
|
||||
if comm is None:
|
||||
return
|
||||
try:
|
||||
comm.handle_msg(msg)
|
||||
except Exception:
|
||||
self.log.error('Exception in comm_msg for %s', comm_id,
|
||||
exc_info=True)
|
||||
|
||||
|
||||
class Comm(MetaQObjectHasTraits(
|
||||
'NewBase', (LoggingConfigurable, SuperQObject), {})):
|
||||
"""
|
||||
Comm base class
|
||||
"""
|
||||
sig_is_closing = QtCore.Signal(object)
|
||||
|
||||
def __init__(self, target_name, kernel_client, comm_id=None,
|
||||
msg_callback=None, close_callback=None):
|
||||
"""
|
||||
Create a new comm. Must call open to use.
|
||||
"""
|
||||
super(Comm, self).__init__(target_name=target_name)
|
||||
self.target_name = target_name
|
||||
self.kernel_client = kernel_client
|
||||
if comm_id is None:
|
||||
comm_id = uuid.uuid1().hex
|
||||
self.comm_id = comm_id
|
||||
self._msg_callback = msg_callback
|
||||
self._close_callback = close_callback
|
||||
self._send_channel = self.kernel_client.shell_channel
|
||||
|
||||
def _send_msg(self, msg_type, content, data, metadata, buffers):
|
||||
"""
|
||||
Send a message on the shell channel.
|
||||
"""
|
||||
if data is None:
|
||||
data = {}
|
||||
if content is None:
|
||||
content = {}
|
||||
content['comm_id'] = self.comm_id
|
||||
content['data'] = data
|
||||
|
||||
msg = self.kernel_client.session.msg(
|
||||
msg_type, content, metadata=metadata)
|
||||
if buffers:
|
||||
msg['buffers'] = buffers
|
||||
return self._send_channel.send(msg)
|
||||
|
||||
# methods for sending messages
|
||||
def open(self, data=None, metadata=None, buffers=None):
|
||||
"""Open the kernel-side version of this comm"""
|
||||
return self._send_msg(
|
||||
'comm_open', {'target_name': self.target_name},
|
||||
data, metadata, buffers)
|
||||
|
||||
def send(self, data=None, metadata=None, buffers=None):
|
||||
"""Send a message to the kernel-side version of this comm"""
|
||||
return self._send_msg(
|
||||
'comm_msg', {}, data, metadata, buffers)
|
||||
|
||||
def close(self, data=None, metadata=None, buffers=None):
|
||||
"""Close the kernel-side version of this comm"""
|
||||
self.sig_is_closing.emit(self)
|
||||
return self._send_msg(
|
||||
'comm_close', {}, data, metadata, buffers)
|
||||
|
||||
# methods for registering callbacks for incoming messages
|
||||
|
||||
def on_msg(self, callback):
|
||||
"""Register a callback for comm_msg
|
||||
|
||||
Will be called with the `data` of any comm_msg messages.
|
||||
|
||||
Call `on_msg(None)` to disable an existing callback.
|
||||
"""
|
||||
self._msg_callback = callback
|
||||
|
||||
def on_close(self, callback):
|
||||
"""Register a callback for comm_close
|
||||
|
||||
Will be called with the `data` of the close message.
|
||||
|
||||
Call `on_close(None)` to disable an existing callback.
|
||||
"""
|
||||
self._close_callback = callback
|
||||
|
||||
# methods for handling incoming messages
|
||||
def handle_msg(self, msg):
|
||||
"""Handle a comm_msg message"""
|
||||
self.log.debug("handle_msg[%s](%s)", self.comm_id, msg)
|
||||
if self._msg_callback:
|
||||
return self._msg_callback(msg)
|
||||
|
||||
def handle_close(self, msg):
|
||||
"""Handle a comm_close message"""
|
||||
self.log.debug("handle_close[%s](%s)", self.comm_id, msg)
|
||||
if self._close_callback:
|
||||
return self._close_callback(msg)
|
||||
|
||||
|
||||
__all__ = ['CommManager']
|
376
venv/Lib/site-packages/qtconsole/completion_html.py
Normal file
376
venv/Lib/site-packages/qtconsole/completion_html.py
Normal file
|
@ -0,0 +1,376 @@
|
|||
"""A navigable completer for the qtconsole"""
|
||||
# coding : utf-8
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import ipython_genutils.text as text
|
||||
|
||||
from qtpy import QtCore, QtGui, QtWidgets
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# Return an HTML table with selected item in a special class
|
||||
#--------------------------------------------------------------------------
|
||||
def html_tableify(item_matrix, select=None, header=None , footer=None) :
|
||||
""" returnr a string for an html table"""
|
||||
if not item_matrix :
|
||||
return ''
|
||||
html_cols = []
|
||||
tds = lambda text : u'<td>'+text+u' </td>'
|
||||
trs = lambda text : u'<tr>'+text+u'</tr>'
|
||||
tds_items = [list(map(tds, row)) for row in item_matrix]
|
||||
if select :
|
||||
row, col = select
|
||||
tds_items[row][col] = u'<td class="inverted">'\
|
||||
+item_matrix[row][col]\
|
||||
+u' </td>'
|
||||
#select the right item
|
||||
html_cols = map(trs, (u''.join(row) for row in tds_items))
|
||||
head = ''
|
||||
foot = ''
|
||||
if header :
|
||||
head = (u'<tr>'\
|
||||
+''.join((u'<td>'+header+u'</td>')*len(item_matrix[0]))\
|
||||
+'</tr>')
|
||||
|
||||
if footer :
|
||||
foot = (u'<tr>'\
|
||||
+''.join((u'<td>'+footer+u'</td>')*len(item_matrix[0]))\
|
||||
+'</tr>')
|
||||
html = (u'<table class="completion" style="white-space:pre"'
|
||||
'cellspacing=0>' +
|
||||
head + (u''.join(html_cols)) + foot + u'</table>')
|
||||
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(QtWidgets.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, (QtWidgets.QTextEdit, QtWidgets.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, prefix_length=0):
|
||||
""" Shows the completion widget with 'items' at the position specified
|
||||
by 'cursor'.
|
||||
"""
|
||||
if not items :
|
||||
return
|
||||
# Move cursor to start of the prefix to replace it
|
||||
# when a item is selected
|
||||
cursor.movePosition(QtGui.QTextCursor.Left, n=prefix_length)
|
||||
self._start_position = cursor.position()
|
||||
self._consecutive_tab = 1
|
||||
# Calculate the number of characters available.
|
||||
width = self._text_edit.document().textWidth()
|
||||
char_width = self._console_widget._get_font_width()
|
||||
displaywidth = int(max(10, (width / char_width) - 1))
|
||||
items_m, ci = text.compute_item_matrix(items, empty=' ',
|
||||
displaywidth=displaywidth)
|
||||
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
|
||||
|
60
venv/Lib/site-packages/qtconsole/completion_plain.py
Normal file
60
venv/Lib/site-packages/qtconsole/completion_plain.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
"""A simple completer for the qtconsole"""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
from qtpy import QtCore, QtGui, QtWidgets
|
||||
import ipython_genutils.text as text
|
||||
|
||||
|
||||
class CompletionPlain(QtWidgets.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, (QtWidgets.QTextEdit, QtWidgets.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, prefix_length=0):
|
||||
""" Shows the completion widget with 'items' at the position specified
|
||||
by 'cursor'.
|
||||
"""
|
||||
if not items :
|
||||
return
|
||||
self.cancel_completion()
|
||||
strng = text.columnize(items)
|
||||
# Move cursor to start of the prefix to replace it
|
||||
# when a item is selected
|
||||
cursor.movePosition(QtGui.QTextCursor.Left, n=prefix_length)
|
||||
self._console_widget._fill_temporary_buffer(cursor, strng, html=False)
|
214
venv/Lib/site-packages/qtconsole/completion_widget.py
Normal file
214
venv/Lib/site-packages/qtconsole/completion_widget.py
Normal file
|
@ -0,0 +1,214 @@
|
|||
"""A dropdown completer widget for the qtconsole."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from qtpy import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class CompletionWidget(QtWidgets.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, (QtWidgets.QTextEdit, QtWidgets.QPlainTextEdit))
|
||||
super(CompletionWidget, self).__init__(parent=console_widget)
|
||||
|
||||
self._text_edit = text_edit
|
||||
self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
||||
self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||
self.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
|
||||
|
||||
# We need Popup style to ensure correct mouse interaction
|
||||
# (dialog would dissappear on mouse click with ToolTip style)
|
||||
self.setWindowFlags(QtCore.Qt.Popup)
|
||||
|
||||
self.setAttribute(QtCore.Qt.WA_StaticContents)
|
||||
original_policy = text_edit.focusPolicy()
|
||||
|
||||
self.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
text_edit.setFocusPolicy(original_policy)
|
||||
|
||||
# Ensure that the text edit keeps focus when widget is displayed.
|
||||
self.setFocusProxy(self._text_edit)
|
||||
|
||||
self.setFrameShadow(QtWidgets.QFrame.Plain)
|
||||
self.setFrameShape(QtWidgets.QFrame.StyledPanel)
|
||||
|
||||
self.itemActivated.connect(self._complete_current)
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
""" Reimplemented to handle mouse input and to auto-hide when the
|
||||
text edit loses focus.
|
||||
"""
|
||||
if obj is self:
|
||||
if event.type() == QtCore.QEvent.MouseButtonPress:
|
||||
pos = self.mapToGlobal(event.pos())
|
||||
target = QtWidgets.QApplication.widgetAt(pos)
|
||||
if (target and self.isAncestorOf(target) or target is self):
|
||||
return False
|
||||
else:
|
||||
self.cancel_completion()
|
||||
|
||||
return super(CompletionWidget, self).eventFilter(obj, event)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
key = event.key()
|
||||
if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter,
|
||||
QtCore.Qt.Key_Tab):
|
||||
self._complete_current()
|
||||
elif key == QtCore.Qt.Key_Escape:
|
||||
self.hide()
|
||||
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):
|
||||
return super(CompletionWidget, self).keyPressEvent(event)
|
||||
else:
|
||||
QtWidgets.QApplication.sendEvent(self._text_edit, event)
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# 'QWidget' interface
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
def hideEvent(self, event):
|
||||
""" Reimplemented to disconnect signal handlers and event filter.
|
||||
"""
|
||||
super(CompletionWidget, self).hideEvent(event)
|
||||
try:
|
||||
self._text_edit.cursorPositionChanged.disconnect(self._update_current)
|
||||
except TypeError:
|
||||
pass
|
||||
self.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.installEventFilter(self)
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# 'CompletionWidget' interface
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
def show_items(self, cursor, items, prefix_length=0):
|
||||
""" Shows the completion widget with 'items' at the position specified
|
||||
by 'cursor'.
|
||||
"""
|
||||
text_edit = self._text_edit
|
||||
point = self._get_top_left_position(cursor)
|
||||
self.clear()
|
||||
path_items = []
|
||||
for item in items:
|
||||
# Check if the item could refer to a file or dir. The replacing
|
||||
# of '"' is needed for items on Windows
|
||||
if (os.path.isfile(os.path.abspath(item.replace("\"", ""))) or
|
||||
os.path.isdir(os.path.abspath(item.replace("\"", "")))):
|
||||
path_items.append(item.replace("\"", ""))
|
||||
else:
|
||||
list_item = QtWidgets.QListWidgetItem()
|
||||
list_item.setData(QtCore.Qt.UserRole, item)
|
||||
# Need to split to only show last element of a dot completion
|
||||
list_item.setText(item.split(".")[-1])
|
||||
self.addItem(list_item)
|
||||
|
||||
common_prefix = os.path.dirname(os.path.commonprefix(path_items))
|
||||
for path_item in path_items:
|
||||
list_item = QtWidgets.QListWidgetItem()
|
||||
list_item.setData(QtCore.Qt.UserRole, path_item)
|
||||
if common_prefix:
|
||||
text = path_item.split(common_prefix)[-1]
|
||||
else:
|
||||
text = path_item
|
||||
list_item.setText(text)
|
||||
self.addItem(list_item)
|
||||
|
||||
height = self.sizeHint().height()
|
||||
screen_rect = QtWidgets.QApplication.desktop().availableGeometry(self)
|
||||
if (screen_rect.size().height() + screen_rect.y() -
|
||||
point.y() - height < 0):
|
||||
point = text_edit.mapToGlobal(text_edit.cursorRect().topRight())
|
||||
point.setY(point.y() - height)
|
||||
w = (self.sizeHintForColumn(0) +
|
||||
self.verticalScrollBar().sizeHint().width() +
|
||||
2 * self.frameWidth())
|
||||
self.setGeometry(point.x(), point.y(), w, height)
|
||||
|
||||
# Move cursor to start of the prefix to replace it
|
||||
# when a item is selected
|
||||
cursor.movePosition(QtGui.QTextCursor.Left, n=prefix_length)
|
||||
self._start_position = cursor.position()
|
||||
self.setCurrentRow(0)
|
||||
self.raise_()
|
||||
self.show()
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# Protected interface
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
def _get_top_left_position(self, cursor):
|
||||
""" Get top left position for this widget.
|
||||
"""
|
||||
point = self._text_edit.cursorRect(cursor).center()
|
||||
point_size = self._text_edit.font().pointSize()
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
delta = int((point_size * 1.20) ** 0.98)
|
||||
elif os.name == 'nt':
|
||||
delta = int((point_size * 1.20) ** 1.05)
|
||||
else:
|
||||
delta = int((point_size * 1.20) ** 0.98)
|
||||
|
||||
y = delta - (point_size / 2)
|
||||
point.setY(point.y() + y)
|
||||
point = self._text_edit.mapToGlobal(point)
|
||||
return point
|
||||
|
||||
def _complete_current(self):
|
||||
""" Perform the completion with the currently selected item.
|
||||
"""
|
||||
text = self.currentItem().data(QtCore.Qt.UserRole)
|
||||
self._current_text_cursor().insertText(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 and the
|
||||
position of the widget.
|
||||
"""
|
||||
# Update widget position
|
||||
cursor = self._text_edit.textCursor()
|
||||
point = self._get_top_left_position(cursor)
|
||||
self.move(point)
|
||||
|
||||
# Update current item
|
||||
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()
|
2468
venv/Lib/site-packages/qtconsole/console_widget.py
Normal file
2468
venv/Lib/site-packages/qtconsole/console_widget.py
Normal file
File diff suppressed because it is too large
Load diff
793
venv/Lib/site-packages/qtconsole/frontend_widget.py
Normal file
793
venv/Lib/site-packages/qtconsole/frontend_widget.py
Normal file
|
@ -0,0 +1,793 @@
|
|||
"""Frontend widget for the Qt Console"""
|
||||
|
||||
# Copyright (c) Jupyter Development Team
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from collections import namedtuple
|
||||
import sys
|
||||
import uuid
|
||||
import re
|
||||
|
||||
from qtpy import QtCore, QtGui, QtWidgets
|
||||
from ipython_genutils import py3compat
|
||||
from ipython_genutils.importstring import import_item
|
||||
|
||||
from qtconsole.base_frontend_mixin import BaseFrontendMixin
|
||||
from traitlets import Any, Bool, Instance, Unicode, DottedObjectName, default
|
||||
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
|
||||
self._classic_prompt_re = re.compile(
|
||||
r'^(%s)?([ \t]*>>> |^[ \t]*\.\.\. )' % re.escape(frontend.other_output_prefix)
|
||||
)
|
||||
self._ipy_prompt_re = re.compile(
|
||||
r'^(%s)?([ \t]*In \[\d+\]: |[ \t]*\ \ \ \.\.\.+: )' % re.escape(frontend.other_output_prefix)
|
||||
)
|
||||
|
||||
def transform_classic_prompt(self, line):
|
||||
"""Handle inputs that start with '>>> ' syntax."""
|
||||
|
||||
if not line or line.isspace():
|
||||
return line
|
||||
m = self._classic_prompt_re.match(line)
|
||||
if m:
|
||||
return line[len(m.group(0)):]
|
||||
else:
|
||||
return line
|
||||
|
||||
def transform_ipy_prompt(self, line):
|
||||
"""Handle inputs that start classic IPython prompt syntax."""
|
||||
|
||||
if not line or line.isspace():
|
||||
return line
|
||||
m = self._ipy_prompt_re.match(line)
|
||||
if m:
|
||||
return line[len(m.group(0)):]
|
||||
else:
|
||||
return line
|
||||
|
||||
def highlightBlock(self, string):
|
||||
""" Highlight a block of text. Reimplemented to highlight selectively.
|
||||
"""
|
||||
if not hasattr(self, 'highlighting_on') or 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)
|
||||
|
||||
# Only highlight if we can identify a prompt, but make sure not to
|
||||
# highlight the prompt.
|
||||
without_prompt = self.transform_ipy_prompt(string)
|
||||
diff = len(string) - len(without_prompt)
|
||||
if diff > 0:
|
||||
self._current_offset = diff
|
||||
super(FrontendHighlighter, self).highlightBlock(without_prompt)
|
||||
|
||||
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)
|
||||
|
||||
_CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
|
||||
_CompletionRequest = namedtuple('_CompletionRequest',
|
||||
['id', 'code', 'pos'])
|
||||
_ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
|
||||
_local_kernel = False
|
||||
_highlighter = Instance(FrontendHighlighter, allow_none=True)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# 'Object' interface
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def __init__(self, local_kernel=_local_kernel, *args, **kw):
|
||||
super(FrontendWidget, self).__init__(*args, **kw)
|
||||
# FrontendWidget protected variables.
|
||||
self._bracket_matcher = BracketMatcher(self._control)
|
||||
self._call_tip_widget = CallTipWidget(self._control)
|
||||
self._copy_raw_action = QtWidgets.QAction('Copy (Raw Text)', None)
|
||||
self._hidden = False
|
||||
self._highlighter = FrontendHighlighter(self, lexer=self.lexer)
|
||||
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 = 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:
|
||||
# Remove prompts.
|
||||
lines = text.splitlines()
|
||||
lines = map(self._highlighter.transform_classic_prompt, lines)
|
||||
lines = map(self._highlighter.transform_ipy_prompt, lines)
|
||||
text = '\n'.join(lines)
|
||||
# Needed to prevent errors when copying the prompt.
|
||||
# See issue 264
|
||||
try:
|
||||
was_newline = text[-1] == '\n'
|
||||
except IndexError:
|
||||
was_newline = False
|
||||
if was_newline: # user doesn't need newline
|
||||
text = text[:-1]
|
||||
QtWidgets.QApplication.clipboard().setText(text)
|
||||
else:
|
||||
self.log.debug("frontend widget : unknown copy target")
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# 'ConsoleWidget' abstract interface
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
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.
|
||||
"""
|
||||
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.
|
||||
# 3) There is no active selection.
|
||||
text = self._get_input_buffer_cursor_line()
|
||||
if text is None:
|
||||
return False
|
||||
non_ws_before = bool(text[:self._get_input_buffer_cursor_column()].strip())
|
||||
complete = non_ws_before and self._get_cursor().selectedText() == ''
|
||||
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)
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# '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 <http://docs.python.org/library/functions.html#repr> `_ as first argument
|
||||
|
||||
Parameters
|
||||
----------
|
||||
expr : string
|
||||
valid string to be executed by the kernel.
|
||||
callback : function
|
||||
function accepting one argument, as a string. The string will be
|
||||
the `repr` of the result of evaluating `expr`
|
||||
|
||||
The `callback` is called with the `repr()` of the result of `expr` as
|
||||
first argument. To get the object, do `eval()` on the passed value.
|
||||
|
||||
See Also
|
||||
--------
|
||||
_handle_exec_callback : private method, deal with calling callback with reply
|
||||
|
||||
"""
|
||||
|
||||
# generate uuid, which would be used as an indication of whether or
|
||||
# not the unique request originated from here (can use msg id ?)
|
||||
local_uuid = str(uuid.uuid1())
|
||||
msg_id = self.kernel_client.execute('',
|
||||
silent=True, user_expressions={ local_uuid:expr })
|
||||
self._callback_dict[local_uuid] = callback
|
||||
self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
|
||||
|
||||
def _handle_exec_callback(self, msg):
|
||||
"""Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
|
||||
|
||||
Parameters
|
||||
----------
|
||||
msg : raw message send by the kernel containing an `user_expressions`
|
||||
and having a 'silent_exec_callback' kind.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function will look for a `callback` associated with the
|
||||
corresponding message id. Association has been made by
|
||||
`_silent_exec_callback`. `callback` is then called with the `repr()`
|
||||
of the value of corresponding `user_expressions` as argument.
|
||||
`callback` is then removed from the known list so that any message
|
||||
coming again with the same id won't trigger it.
|
||||
"""
|
||||
user_exp = msg['content'].get('user_expressions')
|
||||
if not user_exp:
|
||||
return
|
||||
for expression in user_exp:
|
||||
if expression in self._callback_dict:
|
||||
self._callback_dict.pop(expression)(user_exp[expression])
|
||||
|
||||
def _handle_execute_reply(self, msg):
|
||||
""" Handles replies for code execution.
|
||||
"""
|
||||
self.log.debug("execute_reply: %s", msg.get('content', ''))
|
||||
msg_id = msg['parent_header']['msg_id']
|
||||
info = self._request_info['execute'].get(msg_id)
|
||||
# unset reading flag, because if execute finished, raw_input can't
|
||||
# still be pending.
|
||||
self._reading = False
|
||||
# Note: If info is NoneType, this is ignored
|
||||
if info and info.kind == 'user' and not self._hidden:
|
||||
# Make sure that all output from the SUB channel has been processed
|
||||
# before writing a new prompt.
|
||||
self.kernel_client.iopub_channel.flush()
|
||||
|
||||
# Reset the ANSI style information to prevent bad text in stdout
|
||||
# from messing up our colors. We're not a true terminal so we're
|
||||
# allowed to do this.
|
||||
if self.ansi_codes:
|
||||
self._ansi_processor.reset_sgr()
|
||||
|
||||
content = msg['content']
|
||||
status = content['status']
|
||||
if status == 'ok':
|
||||
self._process_execute_ok(msg)
|
||||
elif status == 'aborted':
|
||||
self._process_execute_abort(msg)
|
||||
|
||||
self._show_interpreter_prompt_for_reply(msg)
|
||||
self.executed.emit(msg)
|
||||
self._request_info['execute'].pop(msg_id)
|
||||
elif info and info.kind == 'silent_exec_callback' and not self._hidden:
|
||||
self._handle_exec_callback(msg)
|
||||
self._request_info['execute'].pop(msg_id)
|
||||
elif info and not self._hidden:
|
||||
raise RuntimeError("Unknown handler for %s" % info.kind)
|
||||
|
||||
def _handle_error(self, msg):
|
||||
""" Handle error messages.
|
||||
"""
|
||||
self._process_execute_error(msg)
|
||||
|
||||
def _handle_input_request(self, msg):
|
||||
""" Handle requests for raw_input.
|
||||
"""
|
||||
self.log.debug("input: %s", msg.get('content', ''))
|
||||
if self._hidden:
|
||||
raise RuntimeError('Request for raw input during hidden execution.')
|
||||
|
||||
# Make sure that all output from the SUB channel has been processed
|
||||
# before entering readline mode.
|
||||
self.kernel_client.iopub_channel.flush()
|
||||
|
||||
def callback(line):
|
||||
self._finalize_input_request()
|
||||
self.kernel_client.input(line)
|
||||
if self._reading:
|
||||
self.log.debug("Got second input request, assuming first was interrupted.")
|
||||
self._reading = False
|
||||
self._readline(msg['content']['prompt'], callback=callback, password=msg['content']['password'])
|
||||
|
||||
def _kernel_restarted_message(self, died=True):
|
||||
msg = "Kernel died, restarting" if died else "Kernel restarting"
|
||||
self._append_html("<br>%s<hr><br>" % 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.warning("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.warning("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.debug("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 = QtWidgets.QMessageBox.question(self, title,
|
||||
"Kernel has been shutdown permanently. "
|
||||
"Close the Console?",
|
||||
QtWidgets.QMessageBox.Yes,QtWidgets.QMessageBox.No)
|
||||
if reply == QtWidgets.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 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._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 = QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No
|
||||
result = QtWidgets.QMessageBox.question(self, 'Restart kernel?',
|
||||
message, buttons)
|
||||
do_restart = result == QtWidgets.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("<br>Restarting kernel...\n<hr><br>",
|
||||
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."""
|
||||
self._append_plain_text(text, before_prompt=True)
|
||||
|
||||
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.
|
||||
"""
|
||||
code = self.input_buffer
|
||||
cursor_pos = self._get_input_buffer_cursor_pos()
|
||||
# Send the completion request to the kernel
|
||||
msg_id = self.kernel_client.complete(code=code, cursor_pos=cursor_pos)
|
||||
info = self._CompletionRequest(msg_id, code, cursor_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'].get('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 -----------------------------------------
|
||||
|
||||
@default('banner')
|
||||
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)
|
261
venv/Lib/site-packages/qtconsole/history_console_widget.py
Normal file
261
venv/Lib/site-packages/qtconsole/history_console_widget.py
Normal file
|
@ -0,0 +1,261 @@
|
|||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
from qtpy import QtGui
|
||||
|
||||
from ipython_genutils.py3compat import unicode_type
|
||||
from 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 do_execute(self, source, complete, indent):
|
||||
""" Reimplemented to the store history. """
|
||||
history = self.input_buffer if source is None else source
|
||||
|
||||
super(HistoryConsoleWidget, self).do_execute(source, complete, indent)
|
||||
|
||||
if complete:
|
||||
# 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)
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# '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.
|
||||
pos = self._get_input_buffer_cursor_pos()
|
||||
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(pos, 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.EndOfBlock)
|
||||
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)[:pos] == input_buffer[:pos]):
|
||||
self._history_prefix = input_buffer[:pos]
|
||||
|
||||
# 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.EndOfBlock)
|
||||
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 history == self.input_buffer:
|
||||
continue
|
||||
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 history == self.input_buffer:
|
||||
continue
|
||||
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:]
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# '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
|
90
venv/Lib/site-packages/qtconsole/inprocess.py
Normal file
90
venv/Lib/site-packages/qtconsole/inprocess.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
""" Defines an in-process KernelManager with signals and slots.
|
||||
"""
|
||||
|
||||
from qtpy import QtCore
|
||||
from ipykernel.inprocess import (
|
||||
InProcessHBChannel, InProcessKernelClient, InProcessKernelManager,
|
||||
)
|
||||
from ipykernel.inprocess.channels import InProcessChannel
|
||||
|
||||
from traitlets import Type
|
||||
from .util import SuperQObject
|
||||
from .kernel_mixins import (
|
||||
QtKernelClientMixin, QtKernelManagerMixin,
|
||||
)
|
||||
from .rich_jupyter_widget import RichJupyterWidget
|
||||
|
||||
class QtInProcessChannel(SuperQObject, InProcessChannel):
|
||||
# Emitted when the channel is started.
|
||||
started = QtCore.Signal()
|
||||
|
||||
# Emitted when the channel is stopped.
|
||||
stopped = QtCore.Signal()
|
||||
|
||||
# Emitted when any message is received.
|
||||
message_received = QtCore.Signal(object)
|
||||
|
||||
def start(self):
|
||||
""" Reimplemented to emit signal.
|
||||
"""
|
||||
super(QtInProcessChannel, self).start()
|
||||
self.started.emit()
|
||||
|
||||
def stop(self):
|
||||
""" Reimplemented to emit signal.
|
||||
"""
|
||||
super(QtInProcessChannel, self).stop()
|
||||
self.stopped.emit()
|
||||
|
||||
def call_handlers_later(self, *args, **kwds):
|
||||
""" Call the message handlers later.
|
||||
"""
|
||||
do_later = lambda: self.call_handlers(*args, **kwds)
|
||||
QtCore.QTimer.singleShot(0, do_later)
|
||||
|
||||
def call_handlers(self, msg):
|
||||
self.message_received.emit(msg)
|
||||
|
||||
def process_events(self):
|
||||
""" Process any pending GUI events.
|
||||
"""
|
||||
QtCore.QCoreApplication.instance().processEvents()
|
||||
|
||||
def flush(self, timeout=1.0):
|
||||
""" Reimplemented to ensure that signals are dispatched immediately.
|
||||
"""
|
||||
super(QtInProcessChannel, self).flush()
|
||||
self.process_events()
|
||||
|
||||
|
||||
class QtInProcessHBChannel(SuperQObject, InProcessHBChannel):
|
||||
# This signal will never be fired, but it needs to exist
|
||||
kernel_died = QtCore.Signal()
|
||||
|
||||
|
||||
class QtInProcessKernelClient(QtKernelClientMixin, InProcessKernelClient):
|
||||
""" An in-process KernelManager with signals and slots.
|
||||
"""
|
||||
|
||||
iopub_channel_class = Type(QtInProcessChannel)
|
||||
shell_channel_class = Type(QtInProcessChannel)
|
||||
stdin_channel_class = Type(QtInProcessChannel)
|
||||
hb_channel_class = Type(QtInProcessHBChannel)
|
||||
|
||||
class QtInProcessKernelManager(QtKernelManagerMixin, InProcessKernelManager):
|
||||
client_class = __module__ + '.QtInProcessKernelClient'
|
||||
|
||||
|
||||
class QtInProcessRichJupyterWidget(RichJupyterWidget):
|
||||
""" An in-process Jupyter Widget that enables multiline editing
|
||||
"""
|
||||
|
||||
def _is_complete(self, source, interactive=True):
|
||||
shell = self.kernel_manager.kernel.shell
|
||||
status, indent_spaces = \
|
||||
shell.input_transformer_manager.check_complete(source)
|
||||
if indent_spaces is None:
|
||||
indent = ''
|
||||
else:
|
||||
indent = ' ' * indent_spaces
|
||||
return status != 'incomplete', indent
|
4
venv/Lib/site-packages/qtconsole/ipython_widget.py
Normal file
4
venv/Lib/site-packages/qtconsole/ipython_widget.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
import warnings
|
||||
warnings.warn("qtconsole.ipython_widget is deprecated; "
|
||||
"use qtconsole.jupyter_widget", DeprecationWarning)
|
||||
from .jupyter_widget import *
|
615
venv/Lib/site-packages/qtconsole/jupyter_widget.py
Normal file
615
venv/Lib/site-packages/qtconsole/jupyter_widget.py
Normal file
|
@ -0,0 +1,615 @@
|
|||
"""A FrontendWidget that emulates a repl for a Jupyter kernel.
|
||||
|
||||
This supports the additional functionality provided by Jupyter kernel.
|
||||
"""
|
||||
|
||||
# Copyright (c) Jupyter 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 warnings import warn
|
||||
|
||||
from qtpy import QtCore, QtGui
|
||||
|
||||
from IPython.lib.lexers import IPythonLexer, IPython3Lexer
|
||||
from pygments.lexers import get_lexer_by_name
|
||||
from pygments.util import ClassNotFound
|
||||
from qtconsole import __version__
|
||||
from traitlets import Bool, Unicode, observe, default
|
||||
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 [<span class="in-prompt-number">%i</span>]: '
|
||||
default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
|
||||
default_input_sep = '\n'
|
||||
default_output_sep = ''
|
||||
default_output_sep2 = ''
|
||||
|
||||
# Base path for most payload sources.
|
||||
zmq_shell_source = 'ipykernel.zmqshell.ZMQInteractiveShell'
|
||||
|
||||
if sys.platform.startswith('win'):
|
||||
default_editor = 'notepad'
|
||||
else:
|
||||
default_editor = ''
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# JupyterWidget class
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
class IPythonWidget(FrontendWidget):
|
||||
"""Dummy class for config inheritance. Destroyed below."""
|
||||
|
||||
|
||||
class JupyterWidget(IPythonWidget):
|
||||
"""A FrontendWidget for a Jupyter 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 GUI 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. To use a terminal text editor,
|
||||
the command should launch a new terminal, e.g.
|
||||
``"gnome-terminal -- vim"``.
|
||||
""")
|
||||
|
||||
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. QtConsole: .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)
|
||||
|
||||
# JupyterWidget 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(JupyterWidget, self).__init__(*args, **kw)
|
||||
|
||||
# JupyterWidget 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()
|
||||
|
||||
# Initialize language name.
|
||||
self.language_name = None
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# 'BaseFrontendMixin' abstract interface
|
||||
#
|
||||
# For JupyterWidget, override FrontendWidget methods which implement the
|
||||
# BaseFrontend Mixin abstract interface
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
def _handle_complete_reply(self, rep):
|
||||
"""Support Jupyter'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 == self._get_input_buffer_cursor_pos()
|
||||
and info.code == self.input_buffer):
|
||||
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):
|
||||
"""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(JupyterWidget, self)._handle_execute_reply(msg)
|
||||
|
||||
def _handle_history_reply(self, msg):
|
||||
""" Handle history tail replies, which are only supported
|
||||
by Jupyter kernels.
|
||||
"""
|
||||
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, remote=True):
|
||||
"""Insert function for input from other frontends"""
|
||||
n = content.get('execution_count', 0)
|
||||
prompt = self._make_in_prompt(n, remote=remote)
|
||||
cont_prompt = self._make_continuation_prompt(self._prompt, remote=remote)
|
||||
cursor.insertText('\n')
|
||||
for i, line in enumerate(content['code'].strip().split('\n')):
|
||||
if i == 0:
|
||||
self._insert_html(cursor, prompt)
|
||||
else:
|
||||
self._insert_html(cursor, cont_prompt)
|
||||
self._insert_plain_text(cursor, line + '\n')
|
||||
|
||||
# Update current prompt number
|
||||
self._update_prompt(n + 1)
|
||||
|
||||
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)
|
||||
elif not self._prompt:
|
||||
self._append_custom(
|
||||
self._insert_other_input, msg['content'],
|
||||
before_prompt=True, remote=False)
|
||||
|
||||
def _handle_execute_result(self, msg):
|
||||
"""Handle an execute_result message"""
|
||||
self.log.debug("execute_result: %s", msg.get('content', ''))
|
||||
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, before_prompt=True)
|
||||
self._append_html(
|
||||
self._make_out_prompt(prompt_number, remote=not self.from_here(msg)),
|
||||
before_prompt=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', before_prompt=True)
|
||||
self._append_plain_text(text + self.output_sep2, before_prompt=True)
|
||||
|
||||
if not self.from_here(msg):
|
||||
self._append_plain_text('\n', before_prompt=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 JupyterWidget, 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']
|
||||
self.language_name = content['language_info']['name']
|
||||
pygments_lexer = content['language_info'].get('pygments_lexer', '')
|
||||
|
||||
try:
|
||||
# Other kernels with pygments_lexer info will have to be
|
||||
# added here by hand.
|
||||
if pygments_lexer == 'ipython3':
|
||||
lexer = IPython3Lexer()
|
||||
elif pygments_lexer == 'ipython2':
|
||||
lexer = IPythonLexer()
|
||||
else:
|
||||
lexer = get_lexer_by_name(self.language_name)
|
||||
self._highlighter._lexer = lexer
|
||||
except ClassNotFound:
|
||||
pass
|
||||
|
||||
self.kernel_banner = content.get('banner', '')
|
||||
if self._starting:
|
||||
# finish handling started channels
|
||||
self._starting = False
|
||||
super(JupyterWidget, self)._started_channels()
|
||||
|
||||
def _started_channels(self):
|
||||
"""Make a history request"""
|
||||
self._starting = True
|
||||
self.kernel_client.kernel_info()
|
||||
self.kernel_client.history(hist_access_type='tail', n=1000)
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# 'FrontendWidget' protected interface
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
def _process_execute_error(self, msg):
|
||||
"""Handle an execute_error message"""
|
||||
self.log.debug("execute_error: %s", msg.get('content', ''))
|
||||
|
||||
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', '<br/>')
|
||||
|
||||
ename = content['ename']
|
||||
ename_styled = '<span class="error">%s</span>' % 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, before_prompt=not self.from_here(msg))
|
||||
|
||||
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 _update_prompt(self, new_prompt_number):
|
||||
"""Replace the last displayed prompt with a new one."""
|
||||
if self._previous_prompt_obj is None:
|
||||
return
|
||||
|
||||
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(new_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())
|
||||
|
||||
# Update the prompt cursor
|
||||
self._prompt_cursor.setPosition(cursor.position() - 1)
|
||||
|
||||
# Store the updated prompt.
|
||||
block = self._control.document().lastBlock()
|
||||
length = len(self._prompt)
|
||||
self._previous_prompt_obj = self._PromptBlock(block, length, new_prompt_number)
|
||||
|
||||
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:
|
||||
self._update_prompt(previous_prompt_number)
|
||||
self._previous_prompt_obj = None
|
||||
|
||||
# Show a new prompt with the kernel's estimated prompt number.
|
||||
self._show_interpreter_prompt(previous_prompt_number + 1)
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# 'JupyterWidget' 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 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)
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# 'JupyterWidget' 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 `JupyterWidget.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, remote=False):
|
||||
""" 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)
|
||||
if remote:
|
||||
body = self.other_output_prefix + body
|
||||
return '<span class="in-prompt">%s</span>' % body
|
||||
|
||||
def _make_continuation_prompt(self, prompt, remote=False):
|
||||
""" 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)
|
||||
if remote:
|
||||
space_count += len(self.other_output_prefix.rsplit('\n')[-1])
|
||||
body = ' ' * space_count + end_chars
|
||||
return '<span class="in-prompt">%s</span>' % body
|
||||
|
||||
def _make_out_prompt(self, number, remote=False):
|
||||
""" 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)
|
||||
if remote:
|
||||
body = self.other_output_prefix + body
|
||||
return '<span class="out-prompt">%s</span>' % 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 --------------------------------------------
|
||||
|
||||
@observe('style_sheet')
|
||||
def _style_sheet_changed(self, changed=None):
|
||||
""" 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)
|
||||
|
||||
if self._page_control is not None:
|
||||
self._page_control.document().setDefaultStyleSheet(self.style_sheet)
|
||||
|
||||
@observe('syntax_style')
|
||||
def _syntax_style_changed(self, changed=None):
|
||||
""" 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)
|
||||
self._ansi_processor.set_background_color(self.syntax_style)
|
||||
else:
|
||||
self._highlighter.set_style_sheet(self.style_sheet)
|
||||
|
||||
#------ Trait default initializers -----------------------------------------
|
||||
|
||||
@default('banner')
|
||||
def _banner_default(self):
|
||||
return "Jupyter QtConsole {version}\n".format(version=__version__)
|
||||
|
||||
|
||||
# Clobber IPythonWidget above:
|
||||
|
||||
class IPythonWidget(JupyterWidget):
|
||||
"""Deprecated class; use JupyterWidget."""
|
||||
def __init__(self, *a, **kw):
|
||||
warn("IPythonWidget is deprecated; use JupyterWidget",
|
||||
DeprecationWarning)
|
||||
super(IPythonWidget, self).__init__(*a, **kw)
|
56
venv/Lib/site-packages/qtconsole/kernel_mixins.py
Normal file
56
venv/Lib/site-packages/qtconsole/kernel_mixins.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
"""Defines a KernelManager that provides signals and slots."""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
from qtpy import QtCore
|
||||
|
||||
from traitlets import HasTraits, Type
|
||||
from .util import MetaQObjectHasTraits, SuperQObject
|
||||
from .comms import CommManager
|
||||
|
||||
|
||||
class QtKernelRestarterMixin(MetaQObjectHasTraits('NewBase', (HasTraits, SuperQObject), {})):
|
||||
|
||||
_timer = None
|
||||
|
||||
|
||||
class QtKernelManagerMixin(MetaQObjectHasTraits('NewBase', (HasTraits, SuperQObject), {})):
|
||||
""" A KernelClient that provides signals and slots.
|
||||
"""
|
||||
|
||||
kernel_restarted = QtCore.Signal()
|
||||
|
||||
|
||||
class QtKernelClientMixin(MetaQObjectHasTraits('NewBase', (HasTraits, SuperQObject), {})):
|
||||
""" A KernelClient that provides signals and slots.
|
||||
"""
|
||||
|
||||
# Emitted when the kernel client has started listening.
|
||||
started_channels = QtCore.Signal()
|
||||
|
||||
# Emitted when the kernel client has stopped listening.
|
||||
stopped_channels = QtCore.Signal()
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# 'KernelClient' interface
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(QtKernelClientMixin, self).__init__(*args, **kwargs)
|
||||
self.comm_manager = None
|
||||
#------ Channel management -------------------------------------------------
|
||||
|
||||
def start_channels(self, *args, **kw):
|
||||
""" Reimplemented to emit signal.
|
||||
"""
|
||||
super(QtKernelClientMixin, self).start_channels(*args, **kw)
|
||||
self.started_channels.emit()
|
||||
self.comm_manager = CommManager(parent=self, kernel_client=self)
|
||||
|
||||
def stop_channels(self):
|
||||
""" Reimplemented to emit signal.
|
||||
"""
|
||||
super(QtKernelClientMixin, self).stop_channels()
|
||||
self.stopped_channels.emit()
|
||||
self.comm_manager = None
|
128
venv/Lib/site-packages/qtconsole/kill_ring.py
Normal file
128
venv/Lib/site-packages/qtconsole/kill_ring.py
Normal file
|
@ -0,0 +1,128 @@
|
|||
""" A generic Emacs-style kill ring, as well as a Qt-specific version.
|
||||
"""
|
||||
#-----------------------------------------------------------------------------
|
||||
# Imports
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# System library imports
|
||||
from qtpy import QtCore, QtWidgets, 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, (QtWidgets.QTextEdit, QtWidgets.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
|
884
venv/Lib/site-packages/qtconsole/mainwindow.py
Normal file
884
venv/Lib/site-packages/qtconsole/mainwindow.py
Normal file
|
@ -0,0 +1,884 @@
|
|||
"""The Qt MainWindow for the QtConsole
|
||||
|
||||
This is a tabbed pseudo-terminal of Jupyter sessions, with a menu bar for
|
||||
common actions.
|
||||
"""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
import sys
|
||||
import webbrowser
|
||||
from threading import Thread
|
||||
|
||||
from jupyter_core.paths import jupyter_runtime_dir
|
||||
from pygments.styles import get_all_styles
|
||||
|
||||
from qtpy import QtGui, QtCore, QtWidgets
|
||||
from qtconsole import styles
|
||||
from qtconsole.jupyter_widget import JupyterWidget
|
||||
from qtconsole.usage import gui_reference
|
||||
|
||||
|
||||
def background(f):
|
||||
"""call a function in a simple thread, to prevent blocking"""
|
||||
t = Thread(target=f)
|
||||
t.start()
|
||||
return t
|
||||
|
||||
|
||||
class MainWindow(QtWidgets.QMainWindow):
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# 'object' interface
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
def __init__(self, app,
|
||||
confirm_exit=True,
|
||||
new_frontend_factory=None, slave_frontend_factory=None,
|
||||
connection_frontend_factory=None,
|
||||
):
|
||||
""" Create a tabbed MainWindow for managing 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 JupyterWidget instance, attached to
|
||||
its own running kernel.
|
||||
slave_frontend_factory : callable
|
||||
A callable that takes an existing JupyterWidget, and returns a new
|
||||
JupyterWidget instance, attached to the same kernel.
|
||||
"""
|
||||
|
||||
super(MainWindow, self).__init__()
|
||||
self._kernel_counter = 0
|
||||
self._external_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.connection_frontend_factory = connection_frontend_factory
|
||||
|
||||
self.tab_widget = QtWidgets.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 next_external_kernel_id(self):
|
||||
"""constantly increasing counter for external kernel IDs"""
|
||||
c = self._external_kernel_counter
|
||||
self._external_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 set_window_title(self):
|
||||
"""Set the title of the console window"""
|
||||
old_title = self.windowTitle()
|
||||
title, ok = QtWidgets.QInputDialog.getText(self,
|
||||
"Rename Window",
|
||||
"New title:",
|
||||
text=old_title)
|
||||
if ok:
|
||||
self.setWindowTitle(title)
|
||||
|
||||
def create_tab_with_existing_kernel(self):
|
||||
"""create a new frontend attached to an external kernel in a new tab"""
|
||||
connection_file, file_type = QtWidgets.QFileDialog.getOpenFileName(self,
|
||||
"Connect to Existing Kernel",
|
||||
jupyter_runtime_dir(),
|
||||
"Connection file (*.json)")
|
||||
if not connection_file:
|
||||
return
|
||||
widget = self.connection_frontend_factory(connection_file)
|
||||
name = "external {}".format(self.next_external_kernel_id)
|
||||
self.add_tab_with_frontend(widget, name=name)
|
||||
|
||||
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 set_tab_title(self):
|
||||
"""Set the title of the current tab"""
|
||||
old_title = self.tab_widget.tabText(self.tab_widget.currentIndex())
|
||||
title, ok = QtWidgets.QInputDialog.getText(self,
|
||||
"Rename Tab",
|
||||
"New title:",
|
||||
text=old_title)
|
||||
if ok:
|
||||
self.tab_widget.setTabText(self.tab_widget.currentIndex(), title)
|
||||
|
||||
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 = QtWidgets.QMessageBox.Cancel
|
||||
okay = QtWidgets.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 = QtWidgets.QPushButton("&No, just this Tab", self)
|
||||
justthis.setShortcut('N')
|
||||
closeall = QtWidgets.QPushButton("&Yes, close all", self)
|
||||
closeall.setShortcut('Y')
|
||||
# allow ctrl-d ctrl-d exit, like in terminal
|
||||
closeall.setShortcut('Ctrl+D')
|
||||
box = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Question,
|
||||
title, msg)
|
||||
box.setInformativeText(info)
|
||||
box.addButton(cancel)
|
||||
box.addButton(justthis, QtWidgets.QMessageBox.NoRole)
|
||||
box.addButton(closeall, QtWidgets.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 = QtWidgets.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 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_window_menu()
|
||||
self.init_help_menu()
|
||||
|
||||
def init_file_menu(self):
|
||||
self.file_menu = self.menuBar().addMenu("&File")
|
||||
|
||||
self.new_kernel_tab_act = QtWidgets.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 = QtWidgets.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.existing_kernel_tab_act = QtWidgets.QAction("New Tab with &Existing kernel",
|
||||
self,
|
||||
shortcut="Alt+T",
|
||||
triggered=self.create_tab_with_existing_kernel)
|
||||
self.add_menu_action(self.file_menu, self.existing_kernel_tab_act)
|
||||
|
||||
self.file_menu.addSeparator()
|
||||
|
||||
self.close_action=QtWidgets.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=QtWidgets.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 = QtWidgets.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 = QtWidgets.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 = QtWidgets.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 = QtWidgets.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 = QtWidgets.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 = QtWidgets.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 = QtWidgets.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 = QtWidgets.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 = QtWidgets.QAction("Select Cell/&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 = QtWidgets.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 = QtWidgets.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 = QtWidgets.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 = QtWidgets.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 = QtWidgets.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 = QtWidgets.QAction("&Clear Screen",
|
||||
self,
|
||||
shortcut='Ctrl+L',
|
||||
statusTip="Clear the console",
|
||||
triggered=self.clear_active_frontend)
|
||||
self.add_menu_action(self.view_menu, self.clear_action)
|
||||
|
||||
self.pager_menu = self.view_menu.addMenu("&Pager")
|
||||
|
||||
hsplit_action = QtWidgets.QAction(".. &Horizontal Split",
|
||||
self,
|
||||
triggered=lambda: self.set_paging_active_frontend('hsplit'))
|
||||
|
||||
vsplit_action = QtWidgets.QAction(" : &Vertical Split",
|
||||
self,
|
||||
triggered=lambda: self.set_paging_active_frontend('vsplit'))
|
||||
|
||||
inside_action = QtWidgets.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)
|
||||
|
||||
available_syntax_styles = self.get_available_syntax_styles()
|
||||
if len(available_syntax_styles) > 0:
|
||||
self.syntax_style_menu = self.view_menu.addMenu("&Syntax Style")
|
||||
style_group = QtWidgets.QActionGroup(self)
|
||||
for style in available_syntax_styles:
|
||||
action = QtWidgets.QAction("{}".format(style), self,
|
||||
triggered=lambda v,
|
||||
syntax_style=style:
|
||||
self.set_syntax_style(
|
||||
syntax_style=syntax_style))
|
||||
action.setCheckable(True)
|
||||
style_group.addAction(action)
|
||||
self.syntax_style_menu.addAction(action)
|
||||
if style == 'default':
|
||||
action.setChecked(True)
|
||||
self.syntax_style_menu.setDefaultAction(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 = QtWidgets.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 = QtWidgets.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 = QtWidgets.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_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 = QtWidgets.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 = QtWidgets.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+Alt+Left" if sys.platform == 'darwin' else "Ctrl+PgUp"
|
||||
self.prev_tab_act = QtWidgets.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+Alt+Right" if sys.platform == 'darwin' else "Ctrl+PgDown"
|
||||
self.next_tab_act = QtWidgets.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)
|
||||
|
||||
self.rename_window_act = QtWidgets.QAction("Rename &Window",
|
||||
self,
|
||||
shortcut="Alt+R",
|
||||
statusTip="Rename window",
|
||||
triggered=self.set_window_title)
|
||||
self.add_menu_action(self.window_menu, self.rename_window_act)
|
||||
|
||||
|
||||
self.rename_current_tab_act = QtWidgets.QAction("&Rename Current Tab",
|
||||
self,
|
||||
shortcut="Ctrl+R",
|
||||
statusTip="Rename current tab",
|
||||
triggered=self.set_tab_title)
|
||||
self.add_menu_action(self.window_menu, self.rename_current_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.help_action = QtWidgets.QAction("Show &QtConsole help", self,
|
||||
triggered=self._show_help)
|
||||
self.online_help_action = QtWidgets.QAction("Open online &help", self,
|
||||
triggered=self._open_online_help)
|
||||
self.add_menu_action(self.help_menu, self.help_action)
|
||||
self.add_menu_action(self.help_menu, self.online_help_action)
|
||||
|
||||
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)
|
||||
|
||||
# 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 _show_help(self):
|
||||
self.active_frontend._page(gui_reference)
|
||||
|
||||
def _open_online_help(self):
|
||||
webbrowser.open("https://qtconsole.readthedocs.io", 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 get_available_syntax_styles(self):
|
||||
"""Get a list with the syntax styles available."""
|
||||
styles = list(get_all_styles())
|
||||
return sorted(styles)
|
||||
|
||||
def set_syntax_style(self, syntax_style):
|
||||
"""Set up syntax style for the current console."""
|
||||
if syntax_style=='bw':
|
||||
colors='nocolor'
|
||||
elif styles.dark_style(syntax_style):
|
||||
colors='linux'
|
||||
else:
|
||||
colors='lightbg'
|
||||
self.active_frontend.syntax_style = syntax_style
|
||||
style_sheet = styles.sheet_from_template(syntax_style, colors)
|
||||
self.active_frontend.style_sheet = style_sheet
|
||||
self.active_frontend._syntax_style_changed()
|
||||
self.active_frontend._style_sheet_changed()
|
||||
self.active_frontend.reset(clear=True)
|
||||
self.active_frontend._execute("%colors linux", True)
|
||||
|
||||
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 clear_active_frontend(self):
|
||||
self.active_frontend.clear()
|
||||
|
||||
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 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()
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# 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 = QtWidgets.QMessageBox.Cancel
|
||||
okay = QtWidgets.QMessageBox.Ok
|
||||
accept_role = QtWidgets.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 = QtWidgets.QPushButton("&Quit", self)
|
||||
closeall.setShortcut('Q')
|
||||
box = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Question,
|
||||
title, msg)
|
||||
box.setInformativeText(info)
|
||||
box.addButton(cancel)
|
||||
box.addButton(closeall, QtWidgets.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()
|
53
venv/Lib/site-packages/qtconsole/manager.py
Normal file
53
venv/Lib/site-packages/qtconsole/manager.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
""" Defines a KernelClient that provides signals and slots.
|
||||
"""
|
||||
|
||||
from qtpy import QtCore
|
||||
|
||||
# Local imports
|
||||
from traitlets import Bool, DottedObjectName
|
||||
|
||||
from jupyter_client import KernelManager
|
||||
from jupyter_client.restarter import KernelRestarter
|
||||
|
||||
from .kernel_mixins import QtKernelManagerMixin, QtKernelRestarterMixin
|
||||
|
||||
|
||||
class QtKernelRestarter(KernelRestarter, QtKernelRestarterMixin):
|
||||
|
||||
def start(self):
|
||||
if self._timer is None:
|
||||
self._timer = QtCore.QTimer()
|
||||
self._timer.timeout.connect(self.poll)
|
||||
self._timer.start(round(self.time_to_dead * 1000))
|
||||
|
||||
def stop(self):
|
||||
self._timer.stop()
|
||||
|
||||
def poll(self):
|
||||
super(QtKernelRestarter, self).poll()
|
||||
|
||||
|
||||
class QtKernelManager(KernelManager, QtKernelManagerMixin):
|
||||
"""A KernelManager with Qt signals for restart"""
|
||||
|
||||
client_class = DottedObjectName('qtconsole.client.QtKernelClient')
|
||||
autorestart = Bool(True, config=True)
|
||||
|
||||
def start_restarter(self):
|
||||
if self.autorestart and self.has_kernel:
|
||||
if self._restarter is None:
|
||||
self._restarter = QtKernelRestarter(
|
||||
kernel_manager=self,
|
||||
parent=self,
|
||||
log=self.log,
|
||||
)
|
||||
self._restarter.add_callback(self._handle_kernel_restarted)
|
||||
self._restarter.start()
|
||||
|
||||
def stop_restarter(self):
|
||||
if self.autorestart:
|
||||
if self._restarter is not None:
|
||||
self._restarter.stop()
|
||||
|
||||
def _handle_kernel_restarted(self):
|
||||
self.kernel_restarted.emit()
|
242
venv/Lib/site-packages/qtconsole/pygments_highlighter.py
Normal file
242
venv/Lib/site-packages/qtconsole/pygments_highlighter.py
Normal file
|
@ -0,0 +1,242 @@
|
|||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
from qtpy import QtGui
|
||||
from qtconsole.qstringhelpers import qstring_length
|
||||
|
||||
from ipython_genutils.py3compat import PY3, string_types
|
||||
from pygments.formatters.html import HtmlFormatter
|
||||
from pygments.lexer import RegexLexer, _TokenType, Text, Error
|
||||
from pygments.lexers import PythonLexer, Python3Lexer
|
||||
from pygments.styles import get_style_by_name
|
||||
|
||||
|
||||
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.set_style('default')
|
||||
if lexer is not None:
|
||||
self._lexer = lexer
|
||||
else:
|
||||
if PY3:
|
||||
self._lexer = Python3Lexer()
|
||||
else:
|
||||
self._lexer = PythonLexer()
|
||||
|
||||
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 = qstring_length(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 <style> -f html
|
||||
|
||||
Note that 'set_style' and 'set_style_sheet' completely override each
|
||||
other, i.e. they cannot be used in conjunction.
|
||||
"""
|
||||
self._document.setDefaultStyleSheet(stylesheet)
|
||||
self._style = None
|
||||
self._clear_caches()
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Protected interface
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
def _clear_caches(self):
|
||||
""" Clear caches for brushes and formats.
|
||||
"""
|
||||
self._brushes = {}
|
||||
self._formats = {}
|
||||
|
||||
def _get_format(self, token):
|
||||
""" Returns a QTextCharFormat for token or None.
|
||||
"""
|
||||
if token in self._formats:
|
||||
return self._formats[token]
|
||||
|
||||
if self._style is None:
|
||||
result = self._get_format_from_document(token, self._document)
|
||||
else:
|
||||
result = self._get_format_from_style(token, self._style)
|
||||
|
||||
self._formats[token] = result
|
||||
return result
|
||||
|
||||
def _get_format_from_document(self, token, document):
|
||||
""" Returns a QTextCharFormat for token by
|
||||
"""
|
||||
code, html = next(self._formatter._format_lines([(token, u'dummy')]))
|
||||
self._document.setHtml(html)
|
||||
return QtGui.QTextCursor(self._document).charFormat()
|
||||
|
||||
def _get_format_from_style(self, token, style):
|
||||
""" Returns a QTextCharFormat for token by reading a Pygments style.
|
||||
"""
|
||||
result = QtGui.QTextCharFormat()
|
||||
for key, value in style.style_for_token(token).items():
|
||||
if value:
|
||||
if key == 'color':
|
||||
result.setForeground(self._get_brush(value))
|
||||
elif key == 'bgcolor':
|
||||
result.setBackground(self._get_brush(value))
|
||||
elif key == 'bold':
|
||||
result.setFontWeight(QtGui.QFont.Bold)
|
||||
elif key == 'italic':
|
||||
result.setFontItalic(True)
|
||||
elif key == 'underline':
|
||||
result.setUnderlineStyle(
|
||||
QtGui.QTextCharFormat.SingleUnderline)
|
||||
elif key == 'sans':
|
||||
result.setFontStyleHint(QtGui.QFont.SansSerif)
|
||||
elif key == 'roman':
|
||||
result.setFontStyleHint(QtGui.QFont.Times)
|
||||
elif key == 'mono':
|
||||
result.setFontStyleHint(QtGui.QFont.TypeWriter)
|
||||
return result
|
||||
|
||||
def _get_brush(self, color):
|
||||
""" Returns a brush for the color.
|
||||
"""
|
||||
result = self._brushes.get(color)
|
||||
if result is None:
|
||||
qcolor = self._get_color(color)
|
||||
result = QtGui.QBrush(qcolor)
|
||||
self._brushes[color] = result
|
||||
return result
|
||||
|
||||
def _get_color(self, color):
|
||||
""" Returns a QColor built from a Pygments color string.
|
||||
"""
|
||||
qcolor = QtGui.QColor()
|
||||
qcolor.setRgb(int(color[:2], base=16),
|
||||
int(color[2:4], base=16),
|
||||
int(color[4:6], base=16))
|
||||
return qcolor
|
||||
|
28
venv/Lib/site-packages/qtconsole/qstringhelpers.py
Normal file
28
venv/Lib/site-packages/qtconsole/qstringhelpers.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © Spyder Project Contributors
|
||||
# Licensed under the terms of the MIT License
|
||||
# (see spyder/__init__.py for details)
|
||||
|
||||
"""QString compatibility."""
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
PY2 = sys.version[0] == '2'
|
||||
|
||||
|
||||
def qstring_length(text):
|
||||
"""
|
||||
Tries to compute what the length of an utf16-encoded QString would be.
|
||||
"""
|
||||
if PY2:
|
||||
# I don't know what this is encoded in, so there is nothing I can do.
|
||||
return len(text)
|
||||
utf16_text = text.encode('utf16')
|
||||
length = len(utf16_text) // 2
|
||||
# Remove Byte order mark.
|
||||
# TODO: All unicode Non-characters should be removed
|
||||
if utf16_text[:2] in [b'\xff\xfe', b'\xff\xff', b'\xfe\xff']:
|
||||
length -= 1
|
||||
return length
|
461
venv/Lib/site-packages/qtconsole/qtconsoleapp.py
Normal file
461
venv/Lib/site-packages/qtconsole/qtconsoleapp.py
Normal file
|
@ -0,0 +1,461 @@
|
|||
""" A minimal application using the Qt console-style Jupyter frontend.
|
||||
|
||||
This is not a complete console app, as subprocess will not be able to receive
|
||||
input, there is no real readline support, among other limitations.
|
||||
"""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
from warnings import warn
|
||||
|
||||
# If run on Windows:
|
||||
#
|
||||
# 1. Install an exception hook which pops up a message box.
|
||||
# Pythonw.exe hides the console, so without this the application
|
||||
# silently fails to load.
|
||||
#
|
||||
# We always install this handler, because the expectation is for
|
||||
# qtconsole to bring up a GUI even if called from the console.
|
||||
# The old handler is called, so the exception is printed as well.
|
||||
# If desired, check for pythonw with an additional condition
|
||||
# (sys.executable.lower().find('pythonw.exe') >= 0).
|
||||
#
|
||||
# 2. Set AppUserModelID for Windows 7 and later so that qtconsole
|
||||
# uses its assigned taskbar icon instead of grabbing the one with
|
||||
# the same AppUserModelID
|
||||
#
|
||||
if os.name == 'nt':
|
||||
# 1.
|
||||
old_excepthook = sys.excepthook
|
||||
|
||||
# Exclude this from our autogenerated API docs.
|
||||
undoc = lambda func: func
|
||||
|
||||
@undoc
|
||||
def gui_excepthook(exctype, value, tb):
|
||||
try:
|
||||
import ctypes, traceback
|
||||
MB_ICONERROR = 0x00000010
|
||||
title = u'Error starting QtConsole'
|
||||
msg = u''.join(traceback.format_exception(exctype, value, tb))
|
||||
ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
|
||||
finally:
|
||||
# Also call the old exception hook to let it do
|
||||
# its thing too.
|
||||
old_excepthook(exctype, value, tb)
|
||||
|
||||
sys.excepthook = gui_excepthook
|
||||
|
||||
# 2.
|
||||
try:
|
||||
from ctypes import windll
|
||||
windll.shell32.SetCurrentProcessExplicitAppUserModelID("Jupyter.Qtconsole")
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
from qtpy import QtCore, QtGui, QtWidgets
|
||||
|
||||
from traitlets.config.application import boolean_flag
|
||||
from traitlets.config.application import catch_config_error
|
||||
from qtconsole.jupyter_widget import JupyterWidget
|
||||
from qtconsole.rich_jupyter_widget import RichJupyterWidget
|
||||
from qtconsole import styles, __version__
|
||||
from qtconsole.mainwindow import MainWindow
|
||||
from qtconsole.client import QtKernelClient
|
||||
from qtconsole.manager import QtKernelManager
|
||||
from traitlets import (
|
||||
Dict, Unicode, CBool, Any
|
||||
)
|
||||
|
||||
from jupyter_core.application import JupyterApp, base_flags, base_aliases
|
||||
from jupyter_client.consoleapp import (
|
||||
JupyterConsoleApp, app_aliases, app_flags,
|
||||
)
|
||||
|
||||
|
||||
from jupyter_client.localinterfaces import is_local_ip
|
||||
|
||||
|
||||
_examples = """
|
||||
jupyter qtconsole # start the qtconsole
|
||||
"""
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Aliases and Flags
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# FIXME: workaround bug in jupyter_client < 4.1 excluding base_flags,aliases
|
||||
flags = dict(base_flags)
|
||||
qt_flags = {
|
||||
'plain' : ({'JupyterQtConsoleApp' : {'plain' : True}},
|
||||
"Disable rich text support."),
|
||||
}
|
||||
qt_flags.update(boolean_flag(
|
||||
'banner', 'JupyterQtConsoleApp.display_banner',
|
||||
"Display a banner upon starting the QtConsole.",
|
||||
"Don't display a banner upon starting the QtConsole."
|
||||
))
|
||||
|
||||
# and app_flags from the Console Mixin
|
||||
qt_flags.update(app_flags)
|
||||
# add frontend flags to the full set
|
||||
flags.update(qt_flags)
|
||||
|
||||
# start with copy of base jupyter aliases
|
||||
aliases = dict(base_aliases)
|
||||
qt_aliases = dict(
|
||||
style = 'JupyterWidget.syntax_style',
|
||||
stylesheet = 'JupyterQtConsoleApp.stylesheet',
|
||||
|
||||
editor = 'JupyterWidget.editor',
|
||||
paging = 'ConsoleWidget.paging',
|
||||
)
|
||||
# and app_aliases from the Console Mixin
|
||||
qt_aliases.update(app_aliases)
|
||||
qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
|
||||
# add frontend aliases to the full set
|
||||
aliases.update(qt_aliases)
|
||||
|
||||
# get flags&aliases into sets, and remove a couple that
|
||||
# shouldn't be scrubbed from backend flags:
|
||||
qt_aliases = set(qt_aliases.keys())
|
||||
qt_flags = set(qt_flags.keys())
|
||||
|
||||
class JupyterQtConsoleApp(JupyterApp, JupyterConsoleApp):
|
||||
name = 'jupyter-qtconsole'
|
||||
version = __version__
|
||||
description = """
|
||||
The Jupyter QtConsole.
|
||||
|
||||
This launches a Console-style application using Qt. It is not a full
|
||||
console, in that launched terminal subprocesses will not be able to accept
|
||||
input.
|
||||
|
||||
"""
|
||||
examples = _examples
|
||||
|
||||
classes = [JupyterWidget] + JupyterConsoleApp.classes
|
||||
flags = Dict(flags)
|
||||
aliases = Dict(aliases)
|
||||
frontend_flags = Any(qt_flags)
|
||||
frontend_aliases = Any(qt_aliases)
|
||||
kernel_client_class = QtKernelClient
|
||||
kernel_manager_class = QtKernelManager
|
||||
|
||||
stylesheet = Unicode('', config=True,
|
||||
help="path to a custom CSS stylesheet")
|
||||
|
||||
hide_menubar = CBool(False, config=True,
|
||||
help="Start the console window with the menu bar hidden.")
|
||||
|
||||
maximize = CBool(False, config=True,
|
||||
help="Start the console window maximized.")
|
||||
|
||||
plain = CBool(False, config=True,
|
||||
help="Use a plaintext widget instead of rich text (plain can't print/save).")
|
||||
|
||||
display_banner = CBool(True, config=True,
|
||||
help="Whether to display a banner upon starting the QtConsole."
|
||||
)
|
||||
|
||||
def _plain_changed(self, name, old, new):
|
||||
kind = 'plain' if new else 'rich'
|
||||
self.config.ConsoleWidget.kind = kind
|
||||
if new:
|
||||
self.widget_factory = JupyterWidget
|
||||
else:
|
||||
self.widget_factory = RichJupyterWidget
|
||||
|
||||
# the factory for creating a widget
|
||||
widget_factory = Any(RichJupyterWidget)
|
||||
|
||||
def parse_command_line(self, argv=None):
|
||||
super(JupyterQtConsoleApp, self).parse_command_line(argv)
|
||||
self.build_kernel_argv(self.extra_args)
|
||||
|
||||
|
||||
def new_frontend_master(self):
|
||||
""" Create and return new frontend attached to new kernel, launched on localhost.
|
||||
"""
|
||||
kernel_manager = self.kernel_manager_class(
|
||||
connection_file=self._new_connection_file(),
|
||||
parent=self,
|
||||
autorestart=True,
|
||||
)
|
||||
# start the kernel
|
||||
kwargs = {}
|
||||
# FIXME: remove special treatment of IPython kernels
|
||||
if self.kernel_manager.ipykernel:
|
||||
kwargs['extra_arguments'] = self.kernel_argv
|
||||
kernel_manager.start_kernel(**kwargs)
|
||||
kernel_manager.client_factory = self.kernel_client_class
|
||||
kernel_client = kernel_manager.client()
|
||||
kernel_client.start_channels(shell=True, iopub=True)
|
||||
widget = self.widget_factory(config=self.config,
|
||||
local_kernel=True)
|
||||
self.init_colors(widget)
|
||||
widget.kernel_manager = kernel_manager
|
||||
widget.kernel_client = kernel_client
|
||||
widget._existing = False
|
||||
widget._may_close = True
|
||||
widget._confirm_exit = self.confirm_exit
|
||||
widget._display_banner = self.display_banner
|
||||
return widget
|
||||
|
||||
def new_frontend_connection(self, connection_file):
|
||||
"""Create and return a new frontend attached to an existing kernel.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
connection_file : str
|
||||
The connection_file path this frontend is to connect to
|
||||
"""
|
||||
kernel_client = self.kernel_client_class(
|
||||
connection_file=connection_file,
|
||||
config=self.config,
|
||||
)
|
||||
kernel_client.load_connection_file()
|
||||
kernel_client.start_channels()
|
||||
widget = self.widget_factory(config=self.config,
|
||||
local_kernel=False)
|
||||
self.init_colors(widget)
|
||||
widget._existing = True
|
||||
widget._may_close = False
|
||||
widget._confirm_exit = False
|
||||
widget._display_banner = self.display_banner
|
||||
widget.kernel_client = kernel_client
|
||||
widget.kernel_manager = None
|
||||
return widget
|
||||
|
||||
def new_frontend_slave(self, current_widget):
|
||||
"""Create and return a new frontend attached to an existing kernel.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
current_widget : JupyterWidget
|
||||
The JupyterWidget whose kernel this frontend is to share
|
||||
"""
|
||||
kernel_client = self.kernel_client_class(
|
||||
connection_file=current_widget.kernel_client.connection_file,
|
||||
config = self.config,
|
||||
)
|
||||
kernel_client.load_connection_file()
|
||||
kernel_client.start_channels()
|
||||
widget = self.widget_factory(config=self.config,
|
||||
local_kernel=False)
|
||||
self.init_colors(widget)
|
||||
widget._existing = True
|
||||
widget._may_close = False
|
||||
widget._confirm_exit = False
|
||||
widget._display_banner = self.display_banner
|
||||
widget.kernel_client = kernel_client
|
||||
widget.kernel_manager = current_widget.kernel_manager
|
||||
return widget
|
||||
|
||||
def init_qt_app(self):
|
||||
# separate from qt_elements, because it must run first
|
||||
self.app = QtWidgets.QApplication(['jupyter-qtconsole'])
|
||||
self.app.setApplicationName('jupyter-qtconsole')
|
||||
|
||||
def init_qt_elements(self):
|
||||
# Create the widget.
|
||||
|
||||
base_path = os.path.abspath(os.path.dirname(__file__))
|
||||
icon_path = os.path.join(base_path, 'resources', 'icon', 'JupyterConsole.svg')
|
||||
self.app.icon = QtGui.QIcon(icon_path)
|
||||
QtWidgets.QApplication.setWindowIcon(self.app.icon)
|
||||
|
||||
ip = self.ip
|
||||
local_kernel = (not self.existing) or is_local_ip(ip)
|
||||
self.widget = self.widget_factory(config=self.config,
|
||||
local_kernel=local_kernel)
|
||||
self.init_colors(self.widget)
|
||||
self.widget._existing = self.existing
|
||||
self.widget._may_close = not self.existing
|
||||
self.widget._confirm_exit = self.confirm_exit
|
||||
self.widget._display_banner = self.display_banner
|
||||
|
||||
self.widget.kernel_manager = self.kernel_manager
|
||||
self.widget.kernel_client = self.kernel_client
|
||||
self.window = MainWindow(self.app,
|
||||
confirm_exit=self.confirm_exit,
|
||||
new_frontend_factory=self.new_frontend_master,
|
||||
slave_frontend_factory=self.new_frontend_slave,
|
||||
connection_frontend_factory=self.new_frontend_connection,
|
||||
)
|
||||
self.window.log = self.log
|
||||
self.window.add_tab_with_frontend(self.widget)
|
||||
self.window.init_menu_bar()
|
||||
|
||||
# Ignore on OSX, where there is always a menu bar
|
||||
if sys.platform != 'darwin' and self.hide_menubar:
|
||||
self.window.menuBar().setVisible(False)
|
||||
|
||||
self.window.setWindowTitle('Jupyter QtConsole')
|
||||
|
||||
def init_colors(self, widget):
|
||||
"""Configure the coloring of the widget"""
|
||||
# Note: This will be dramatically simplified when colors
|
||||
# are removed from the backend.
|
||||
|
||||
# parse the colors arg down to current known labels
|
||||
cfg = self.config
|
||||
colors = cfg.ZMQInteractiveShell.colors if 'ZMQInteractiveShell.colors' in cfg else None
|
||||
style = cfg.JupyterWidget.syntax_style if 'JupyterWidget.syntax_style' in cfg else None
|
||||
sheet = cfg.JupyterWidget.style_sheet if 'JupyterWidget.style_sheet' in cfg else None
|
||||
|
||||
# find the value for colors:
|
||||
if colors:
|
||||
colors=colors.lower()
|
||||
if colors in ('lightbg', 'light'):
|
||||
colors='lightbg'
|
||||
elif colors in ('dark', 'linux'):
|
||||
colors='linux'
|
||||
else:
|
||||
colors='nocolor'
|
||||
elif style:
|
||||
if style=='bw':
|
||||
colors='nocolor'
|
||||
elif styles.dark_style(style):
|
||||
colors='linux'
|
||||
else:
|
||||
colors='lightbg'
|
||||
else:
|
||||
colors=None
|
||||
|
||||
# Configure the style
|
||||
if style:
|
||||
widget.style_sheet = styles.sheet_from_template(style, colors)
|
||||
widget.syntax_style = style
|
||||
widget._syntax_style_changed()
|
||||
widget._style_sheet_changed()
|
||||
elif colors:
|
||||
# use a default dark/light/bw style
|
||||
widget.set_default_style(colors=colors)
|
||||
|
||||
if self.stylesheet:
|
||||
# we got an explicit stylesheet
|
||||
if os.path.isfile(self.stylesheet):
|
||||
with open(self.stylesheet) as f:
|
||||
sheet = f.read()
|
||||
else:
|
||||
raise IOError("Stylesheet %r not found." % self.stylesheet)
|
||||
if sheet:
|
||||
widget.style_sheet = sheet
|
||||
widget._style_sheet_changed()
|
||||
|
||||
|
||||
def init_signal(self):
|
||||
"""allow clean shutdown on sigint"""
|
||||
signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
|
||||
# need a timer, so that QApplication doesn't block until a real
|
||||
# Qt event fires (can require mouse movement)
|
||||
# timer trick from http://stackoverflow.com/q/4938723/938949
|
||||
timer = QtCore.QTimer()
|
||||
# Let the interpreter run each 200 ms:
|
||||
timer.timeout.connect(lambda: None)
|
||||
timer.start(200)
|
||||
# hold onto ref, so the timer doesn't get cleaned up
|
||||
self._sigint_timer = timer
|
||||
|
||||
def _deprecate_config(self, cfg, old_name, new_name):
|
||||
"""Warn about deprecated config."""
|
||||
if old_name in cfg:
|
||||
self.log.warning(
|
||||
"Use %s in config, not %s. Outdated config:\n %s",
|
||||
new_name, old_name,
|
||||
'\n '.join(
|
||||
'{name}.{key} = {value!r}'.format(key=key, value=value,
|
||||
name=old_name)
|
||||
for key, value in self.config[old_name].items()
|
||||
)
|
||||
)
|
||||
cfg = cfg.copy()
|
||||
cfg[new_name].merge(cfg[old_name])
|
||||
return cfg
|
||||
|
||||
def _init_asyncio_patch(self):
|
||||
"""
|
||||
Same workaround fix as https://github.com/ipython/ipykernel/pull/456
|
||||
|
||||
Set default asyncio policy to be compatible with tornado
|
||||
Tornado 6 (at least) is not compatible with the default
|
||||
asyncio implementation on Windows
|
||||
Pick the older SelectorEventLoopPolicy on Windows
|
||||
if the known-incompatible default policy is in use.
|
||||
do this as early as possible to make it a low priority and overrideable
|
||||
ref: https://github.com/tornadoweb/tornado/issues/2608
|
||||
FIXME: if/when tornado supports the defaults in asyncio,
|
||||
remove and bump tornado requirement for py38
|
||||
"""
|
||||
if sys.platform.startswith("win") and sys.version_info >= (3, 8):
|
||||
import asyncio
|
||||
try:
|
||||
from asyncio import (
|
||||
WindowsProactorEventLoopPolicy,
|
||||
WindowsSelectorEventLoopPolicy,
|
||||
)
|
||||
except ImportError:
|
||||
pass
|
||||
# not affected
|
||||
else:
|
||||
if type(asyncio.get_event_loop_policy()) is WindowsProactorEventLoopPolicy:
|
||||
# WindowsProactorEventLoopPolicy is not compatible with tornado 6
|
||||
# fallback to the pre-3.8 default of Selector
|
||||
asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy())
|
||||
|
||||
@catch_config_error
|
||||
def initialize(self, argv=None):
|
||||
self._init_asyncio_patch()
|
||||
self.init_qt_app()
|
||||
super(JupyterQtConsoleApp, self).initialize(argv)
|
||||
if self._dispatching:
|
||||
return
|
||||
# handle deprecated renames
|
||||
for old_name, new_name in [
|
||||
('IPythonQtConsoleApp', 'JupyterQtConsole'),
|
||||
('IPythonWidget', 'JupyterWidget'),
|
||||
('RichIPythonWidget', 'RichJupyterWidget'),
|
||||
]:
|
||||
cfg = self._deprecate_config(self.config, old_name, new_name)
|
||||
if cfg:
|
||||
self.update_config(cfg)
|
||||
JupyterConsoleApp.initialize(self,argv)
|
||||
self.init_qt_elements()
|
||||
self.init_signal()
|
||||
|
||||
def start(self):
|
||||
super(JupyterQtConsoleApp, self).start()
|
||||
|
||||
# draw the window
|
||||
if self.maximize:
|
||||
self.window.showMaximized()
|
||||
else:
|
||||
self.window.show()
|
||||
self.window.raise_()
|
||||
|
||||
# Start the application main loop.
|
||||
self.app.exec_()
|
||||
|
||||
|
||||
class IPythonQtConsoleApp(JupyterQtConsoleApp):
|
||||
def __init__(self, *a, **kw):
|
||||
warn("IPythonQtConsoleApp is deprecated; use JupyterQtConsoleApp",
|
||||
DeprecationWarning)
|
||||
super(IPythonQtConsoleApp, self).__init__(*a, **kw)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Main entry point
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
def main():
|
||||
JupyterQtConsoleApp.launch_instance()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,569 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="512"
|
||||
height="512"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.48.2 r9819"
|
||||
sodipodi:docname="JupyterConsole.svg"
|
||||
inkscape:export-filename="JupyterConsole.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90">
|
||||
<defs
|
||||
id="defs4">
|
||||
<linearGradient
|
||||
id="linearGradient990">
|
||||
<stop
|
||||
id="stop992"
|
||||
offset="0"
|
||||
style="stop-color:#d4d4d4;stop-opacity:1;" />
|
||||
<stop
|
||||
style="stop-color:#f6f6f6;stop-opacity:1;"
|
||||
offset="0.18783081"
|
||||
id="stop998" />
|
||||
<stop
|
||||
style="stop-color:#a7a7a7;stop-opacity:1;"
|
||||
offset="0.33046141"
|
||||
id="stop994" />
|
||||
<stop
|
||||
id="stop1026"
|
||||
offset="0.66523069"
|
||||
style="stop-color:#919191;stop-opacity:1;" />
|
||||
<stop
|
||||
style="stop-color:#868686;stop-opacity:1;"
|
||||
offset="0.83261538"
|
||||
id="stop1028" />
|
||||
<stop
|
||||
id="stop1032"
|
||||
offset="0.92357516"
|
||||
style="stop-color:#868686;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop1030"
|
||||
offset="0.96787697"
|
||||
style="stop-color:#aaaaaa;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop996"
|
||||
offset="1"
|
||||
style="stop-color:#c2c2c2;stop-opacity:1;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient1621">
|
||||
<stop
|
||||
style="stop-color:#d4d4d4;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1623" />
|
||||
<stop
|
||||
style="stop-color:#d4d4d4;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop1625" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient826">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop828" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.69512194;"
|
||||
offset="1"
|
||||
id="stop830" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient826"
|
||||
id="linearGradient832"
|
||||
x1="105.70982"
|
||||
y1="518.53571"
|
||||
x2="757.14288"
|
||||
y2="248.53572"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.7551453,0,0,0.7551453,57.364381,318.43926)" />
|
||||
<style
|
||||
id="style1439"
|
||||
type="text/css">
|
||||
|
||||
@font-face { font-family:"Inconsolata";src:url("#FontID0") format(svg)}
|
||||
.fil0 {fill:#1F1A17}
|
||||
.fil2 {fill:#006633}
|
||||
.fil1 {fill:#1F1A17}
|
||||
.fnt1 {font-weight:500;font-size:3.5278;font-family:'Inconsolata'}
|
||||
.fnt0 {font-weight:500;font-size:6.35;font-family:'Inconsolata'}
|
||||
|
||||
</style>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1621"
|
||||
id="linearGradient1631"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="390.46347"
|
||||
y1="712.64929"
|
||||
x2="389.88318"
|
||||
y2="764.16711"
|
||||
gradientTransform="matrix(0.7551453,0,0,0.7551453,57.364381,318.43922)" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient990"
|
||||
id="linearGradient870"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.7551453,0,0,0.7551453,57.364381,318.43926)"
|
||||
x1="336.14798"
|
||||
y1="18.710255"
|
||||
x2="336.14798"
|
||||
y2="66.858391" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient990"
|
||||
id="linearGradient1012"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.7551453,0,0,0.7551453,57.364381,318.43926)"
|
||||
x1="291.68039"
|
||||
y1="511.74365"
|
||||
x2="291.68039"
|
||||
y2="564.10553" />
|
||||
<filter
|
||||
inkscape:collect="always"
|
||||
id="filter988">
|
||||
<feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="5.9071426"
|
||||
id="feGaussianBlur990" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="linearGradient4689">
|
||||
<stop
|
||||
style="stop-color:#5a9fd4;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop4691" />
|
||||
<stop
|
||||
style="stop-color:#306998;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop4693" />
|
||||
</linearGradient>
|
||||
<filter
|
||||
inkscape:collect="always"
|
||||
id="filter3988">
|
||||
<feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="2.9780484"
|
||||
id="feGaussianBlur3990" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4689"
|
||||
id="linearGradient4125"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="323.06018"
|
||||
y1="147.10051"
|
||||
x2="464.48874"
|
||||
y2="269.24338" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4689"
|
||||
id="linearGradient4127"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="323.06018"
|
||||
y1="147.10051"
|
||||
x2="464.48874"
|
||||
y2="269.24338" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4689"
|
||||
id="linearGradient4129"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="323.06018"
|
||||
y1="147.10051"
|
||||
x2="464.48874"
|
||||
y2="269.24338" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4689"
|
||||
id="linearGradient4131"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="323.06018"
|
||||
y1="147.10051"
|
||||
x2="464.48874"
|
||||
y2="269.24338" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4689"
|
||||
id="linearGradient4133"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="323.06018"
|
||||
y1="147.10051"
|
||||
x2="464.48874"
|
||||
y2="269.24338" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4689"
|
||||
id="linearGradient4135"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="323.06018"
|
||||
y1="147.10051"
|
||||
x2="464.48874"
|
||||
y2="269.24338" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4689"
|
||||
id="linearGradient4137"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="323.06018"
|
||||
y1="147.10051"
|
||||
x2="464.48874"
|
||||
y2="269.24338" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4689"
|
||||
id="linearGradient4139"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="486.50031"
|
||||
y1="184.54053"
|
||||
x2="496.16876"
|
||||
y2="248.36336" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4689"
|
||||
id="linearGradient4141"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="486.50031"
|
||||
y1="184.54053"
|
||||
x2="496.16876"
|
||||
y2="248.36336" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4689"
|
||||
id="linearGradient4143"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="485.7803"
|
||||
y1="185.98055"
|
||||
x2="496.88876"
|
||||
y2="249.08336" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4689"
|
||||
id="linearGradient4145"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="485.7803"
|
||||
y1="185.98055"
|
||||
x2="496.88876"
|
||||
y2="249.08336" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4689"
|
||||
id="linearGradient4147"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="484.3403"
|
||||
y1="182.38054"
|
||||
x2="495.44876"
|
||||
y2="243.32335" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4689"
|
||||
id="linearGradient4149"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="484.3403"
|
||||
y1="182.38054"
|
||||
x2="495.44876"
|
||||
y2="243.32335" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4689"
|
||||
id="linearGradient4151"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="323.06018"
|
||||
y1="147.10051"
|
||||
x2="147.68851"
|
||||
y2="293.00339" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.98994949"
|
||||
inkscape:cx="327.50118"
|
||||
inkscape:cy="215.30649"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:window-width="1245"
|
||||
inkscape:window-height="675"
|
||||
inkscape:window-x="47"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-55.203036,-282.24337)">
|
||||
<rect
|
||||
style="opacity:0.41800005;color:#000000;fill:#020202;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;filter:url(#filter988);enable-background:accumulate"
|
||||
id="rect1032"
|
||||
width="628.57141"
|
||||
height="552.85712"
|
||||
x="76.46875"
|
||||
y="220.12053"
|
||||
rx="0"
|
||||
ry="0"
|
||||
transform="matrix(0.76259826,0,0,0.76259826,12.765793,164.57423)" />
|
||||
<rect
|
||||
y="332.22418"
|
||||
x="71.162964"
|
||||
height="415.55746"
|
||||
width="473.45871"
|
||||
id="rect1629"
|
||||
style="color:#000000;fill:url(#linearGradient1631);fill-opacity:1;fill-rule:nonzero;stroke:#5b5b5b;stroke-width:1.51029062;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
<rect
|
||||
y="332.95441"
|
||||
x="71.774574"
|
||||
height="38.836063"
|
||||
width="472.50522"
|
||||
id="rect12"
|
||||
style="color:#000000;fill:url(#linearGradient870);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="rect797"
|
||||
d="m 71.774575,708.36947 472.505205,0 0,38.83606 -472.505205,0 z"
|
||||
style="color:#000000;fill:url(#linearGradient1012);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
<rect
|
||||
style="color:#000000;fill:#0c212d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="rect10"
|
||||
width="472.50522"
|
||||
height="338.7366"
|
||||
x="71.774574"
|
||||
y="369.63287" />
|
||||
<path
|
||||
style="opacity:0.231;color:#000000;fill:url(#linearGradient832);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
d="m 71.771204,369.62234 0,31.90891 0,1.86734 0,210.74249 C 185.0871,551.67384 349.48037,510.52371 535.90238,506.04065 c 2.79464,-0.0672 5.58165,-0.11401 8.37739,-0.16416 l 0,-102.4779 0,-1.86734 0,-31.90891 -472.508566,0 z"
|
||||
id="rect793"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path1030"
|
||||
d="m 71.774575,374.48737 472.505205,0 0,-4.85448 -472.505205,0 z"
|
||||
style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;opacity:0.75362319" />
|
||||
<path
|
||||
style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;opacity:0.76086957"
|
||||
d="m 71.774575,708.36947 472.505205,0 0,-4.85448 -472.505205,0 z"
|
||||
id="path1024"
|
||||
inkscape:connector-curvature="0" />
|
||||
<g
|
||||
id="g4082"
|
||||
transform="translate(0,4)">
|
||||
<g
|
||||
style="filter:url(#filter3988)"
|
||||
id="g3972"
|
||||
transform="matrix(0.99206275,0,0,0.99206275,13.445202,326.71769)">
|
||||
<g
|
||||
style="fill:url(#linearGradient4137);fill-opacity:1"
|
||||
id="g3974">
|
||||
<g
|
||||
id="text3976"
|
||||
style="font-size:147.90756226px;font-weight:normal;fill:url(#linearGradient4127);fill-rule:evenodd;font-family:Droid Sans Mono">
|
||||
<path
|
||||
id="path4066"
|
||||
style="font-size:147.90756226px;font-weight:normal;fill:url(#linearGradient4125);fill-rule:evenodd;font-family:Droid Sans Mono"
|
||||
d="m 355.96093,272.06912 -38.13242,0 0,-128.9858 38.13242,0 0,10.76086 -24.98829,0 0,107.39186 24.98829,0 0,10.83308"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="text3978"
|
||||
style="font-size:147.90756226px;font-weight:normal;fill:url(#linearGradient4131);fill-rule:evenodd;font-family:Droid Sans Mono">
|
||||
<path
|
||||
id="path4069"
|
||||
style="font-size:147.90756226px;font-weight:normal;fill:url(#linearGradient4129);fill-rule:evenodd;font-family:Droid Sans Mono"
|
||||
d="m 439.12013,261.23604 24.98829,0 0,-107.39186 -24.98829,0 0,-10.76086 38.13242,0 0,128.9858 -38.13242,0 0,-10.83308"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="text3980"
|
||||
style="font-size:147.90756226px;font-weight:normal;fill:url(#linearGradient4135);fill-rule:evenodd;font-family:Droid Sans Mono">
|
||||
<path
|
||||
id="path4072"
|
||||
style="font-size:147.90756226px;font-weight:normal;fill:url(#linearGradient4133);fill-rule:evenodd;font-family:Droid Sans Mono"
|
||||
d="m 514.87478,165.86889 c 6.11462,8e-5 9.17195,3.3463 9.17201,10.03865 -6e-5,6.6925 -3.05739,10.03871 -9.17201,10.03865 -6.1147,6e-5 -9.17203,-3.34615 -9.172,-10.03865 -3e-5,-6.69235 3.0573,-10.03857 9.172,-10.03865 m 0,63.26515 c 6.11462,2e-5 9.17195,3.34623 9.17201,10.03865 -6e-5,6.74058 -3.05739,10.11087 -9.17201,10.11087 -6.1147,0 -9.17203,-3.37029 -9.172,-10.11087 -3e-5,-6.69242 3.0573,-10.03863 9.172,-10.03865"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
style="fill:#ffffff"
|
||||
id="g3982">
|
||||
<g
|
||||
id="text3984"
|
||||
style="font-size:204.03166199px;font-weight:normal;fill:#ffffff;fill-rule:evenodd;font-family:Droid Sans Mono">
|
||||
<path
|
||||
id="path4075"
|
||||
style="font-size:204.03166199px;font-weight:normal;fill:#ffffff;fill-rule:evenodd;font-family:Droid Sans Mono"
|
||||
d="m 151.39749,272.06912 -77.308871,0 0,-12.25385 29.389331,-1.9925 0,-117.1588 -29.389331,-1.9925 0,-12.25386 77.308871,0 0,12.25386 -29.2897,1.9925 0,117.1588 29.2897,1.9925 0,12.25385"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4077"
|
||||
style="font-size:204.03166199px;font-weight:normal;fill:#ffffff;fill-rule:evenodd;font-family:Droid Sans Mono"
|
||||
d="m 210.07652,215.38259 0,56.68653 -18.53022,0 0,-145.65151 40.24843,0 c 34.13802,1.5e-4 51.20706,14.21328 51.20716,42.63943 -10e-5,14.54532 -4.61605,25.90254 -13.84785,34.0717 -9.16557,8.16929 -22.51528,12.25391 -40.04918,12.25385 l -19.02834,0 m 0,-15.74072 16.93622,0 c 13.15041,7e-5 22.54834,-2.39092 28.19383,-7.17299 5.71173,-4.78191 8.56764,-12.25376 8.56773,-22.41559 -9e-5,-18.5301 -11.22448,-27.7952 -33.67319,-27.79533 l -20.02459,0 0,57.38391"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="text3986"
|
||||
style="font-size:131.4621582px;font-weight:normal;fill:#ffffff;fill-rule:evenodd;font-family:Droid Sans Mono">
|
||||
<path
|
||||
id="path4080"
|
||||
style="font-size:131.4621582px;font-weight:normal;fill:#ffffff;fill-rule:evenodd;font-family:Droid Sans Mono"
|
||||
d="m 367.00874,170.0062 12.06781,0 16.81792,41.9806 c 3.50904,8.77272 5.41336,14.97779 5.71295,18.61524 l 0.38515,0 c 0.98421,-4.79287 2.90992,-11.04074 5.77714,-18.74363 l 15.34153,-41.85221 12.13201,0 -30.49049,79.66042 c -2.86722,7.44609 -6.20512,13.03065 -10.01372,16.75373 -3.80867,3.76581 -9.07228,5.64873 -15.79087,5.64876 -3.68027,-3e-5 -7.27493,-0.36378 -10.784,-1.09124 l 0,-9.30762 c 2.6532,0.5135 5.56316,0.77026 8.72991,0.77028 4.10817,-2e-5 7.2963,-0.87729 9.56438,-2.63181 2.31083,-1.75455 4.36493,-4.77151 6.16229,-9.05086 l 3.72305,-9.62857 -29.33506,-71.12309"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
style="fill:#000000"
|
||||
id="g3992"
|
||||
transform="matrix(0.99206275,0,0,0.99206275,15.645477,328.21773)">
|
||||
<g
|
||||
style="fill:#000000;fill-opacity:1"
|
||||
id="g3994">
|
||||
<g
|
||||
id="text3996"
|
||||
style="font-size:147.90756226px;font-weight:normal;fill:#000000;fill-rule:evenodd;font-family:Droid Sans Mono">
|
||||
<path
|
||||
id="path4049"
|
||||
style="font-size:147.90756226px;font-weight:normal;fill:#000000;fill-rule:evenodd;font-family:Droid Sans Mono"
|
||||
d="m 355.96093,272.06912 -38.13242,0 0,-128.9858 38.13242,0 0,10.76086 -24.98829,0 0,107.39186 24.98829,0 0,10.83308"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="text3998"
|
||||
style="font-size:147.90756226px;font-weight:normal;fill:#000000;fill-rule:evenodd;font-family:Droid Sans Mono">
|
||||
<path
|
||||
id="path4052"
|
||||
style="font-size:147.90756226px;font-weight:normal;fill:#000000;fill-rule:evenodd;font-family:Droid Sans Mono"
|
||||
d="m 439.12013,261.23604 24.98829,0 0,-107.39186 -24.98829,0 0,-10.76086 38.13242,0 0,128.9858 -38.13242,0 0,-10.83308"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="text4000"
|
||||
style="font-size:147.90756226px;font-weight:normal;fill:#000000;fill-rule:evenodd;font-family:Droid Sans Mono">
|
||||
<path
|
||||
id="path4055"
|
||||
style="font-size:147.90756226px;font-weight:normal;fill:#000000;fill-rule:evenodd;font-family:Droid Sans Mono"
|
||||
d="m 514.87478,165.86889 c 6.11462,8e-5 9.17195,3.3463 9.17201,10.03865 -6e-5,6.6925 -3.05739,10.03871 -9.17201,10.03865 -6.1147,6e-5 -9.17203,-3.34615 -9.172,-10.03865 -3e-5,-6.69235 3.0573,-10.03857 9.172,-10.03865 m 0,63.26515 c 6.11462,2e-5 9.17195,3.34623 9.17201,10.03865 -6e-5,6.74058 -3.05739,10.11087 -9.17201,10.11087 -6.1147,0 -9.17203,-3.37029 -9.172,-10.11087 -3e-5,-6.69242 3.0573,-10.03863 9.172,-10.03865"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
style="fill:#000000"
|
||||
id="g4002">
|
||||
<g
|
||||
id="text4004"
|
||||
style="font-size:204.03166199px;font-weight:normal;fill:#000000;fill-rule:evenodd;font-family:Droid Sans Mono">
|
||||
<path
|
||||
id="path4058"
|
||||
style="font-size:204.03166199px;font-weight:normal;fill:#000000;fill-rule:evenodd;font-family:Droid Sans Mono"
|
||||
d="m 151.39749,272.06912 -77.308871,0 0,-12.25385 29.389331,-1.9925 0,-117.1588 -29.389331,-1.9925 0,-12.25386 77.308871,0 0,12.25386 -29.2897,1.9925 0,117.1588 29.2897,1.9925 0,12.25385"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4060"
|
||||
style="font-size:204.03166199px;font-weight:normal;fill:#000000;fill-rule:evenodd;font-family:Droid Sans Mono"
|
||||
d="m 210.07652,215.38259 0,56.68653 -18.53022,0 0,-145.65151 40.24843,0 c 34.13802,1.5e-4 51.20706,14.21328 51.20716,42.63943 -10e-5,14.54532 -4.61605,25.90254 -13.84785,34.0717 -9.16557,8.16929 -22.51528,12.25391 -40.04918,12.25385 l -19.02834,0 m 0,-15.74072 16.93622,0 c 13.15041,7e-5 22.54834,-2.39092 28.19383,-7.17299 5.71173,-4.78191 8.56764,-12.25376 8.56773,-22.41559 -9e-5,-18.5301 -11.22448,-27.7952 -33.67319,-27.79533 l -20.02459,0 0,57.38391"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="text4006"
|
||||
style="font-size:131.4621582px;font-weight:normal;fill:#000000;fill-rule:evenodd;font-family:Droid Sans Mono">
|
||||
<path
|
||||
id="path4063"
|
||||
style="font-size:131.4621582px;font-weight:normal;fill:#000000;fill-rule:evenodd;font-family:Droid Sans Mono"
|
||||
d="m 367.00874,170.0062 12.06781,0 16.81792,41.9806 c 3.50904,8.77272 5.41336,14.97779 5.71295,18.61524 l 0.38515,0 c 0.98421,-4.79287 2.90992,-11.04074 5.77714,-18.74363 l 15.34153,-41.85221 12.13201,0 -30.49049,79.66042 c -2.86722,7.44609 -6.20512,13.03065 -10.01372,16.75373 -3.80867,3.76581 -9.07228,5.64873 -15.79087,5.64876 -3.68027,-3e-5 -7.27493,-0.36378 -10.784,-1.09124 l 0,-9.30762 c 2.6532,0.5135 5.56316,0.77026 8.72991,0.77028 4.10817,-2e-5 7.2963,-0.87729 9.56438,-2.63181 2.31083,-1.75455 4.36493,-4.77151 6.16229,-9.05086 l 3.72305,-9.62857 -29.33506,-71.12309"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.99206275,0,0,0.99206275,13.445202,326.71769)"
|
||||
style="fill:url(#linearGradient4151);fill-opacity:1"
|
||||
id="g3938">
|
||||
<g
|
||||
id="text111"
|
||||
style="font-size:147.90756226px;font-weight:normal;fill:url(#linearGradient4141);fill-rule:evenodd;font-family:Droid Sans Mono">
|
||||
<path
|
||||
id="path4037"
|
||||
style="font-size:147.90756226px;font-weight:normal;fill:url(#linearGradient4139);fill-rule:evenodd;font-family:Droid Sans Mono"
|
||||
d="m 355.96093,272.06912 -38.13242,0 0,-128.9858 38.13242,0 0,10.76086 -24.98829,0 0,107.39186 24.98829,0 0,10.83308"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="text113"
|
||||
style="font-size:147.90756226px;font-weight:normal;fill:url(#linearGradient4145);fill-rule:evenodd;font-family:Droid Sans Mono">
|
||||
<path
|
||||
id="path4043"
|
||||
style="font-size:147.90756226px;font-weight:normal;fill:url(#linearGradient4143);fill-rule:evenodd;font-family:Droid Sans Mono"
|
||||
d="m 439.12013,261.23604 24.98829,0 0,-107.39186 -24.98829,0 0,-10.76086 38.13242,0 0,128.9858 -38.13242,0 0,-10.83308"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="text115"
|
||||
style="font-size:147.90756226px;font-weight:normal;fill:url(#linearGradient4149);fill-rule:evenodd;font-family:Droid Sans Mono">
|
||||
<path
|
||||
id="path4046"
|
||||
style="font-size:147.90756226px;font-weight:normal;fill:url(#linearGradient4147);fill-rule:evenodd;font-family:Droid Sans Mono"
|
||||
d="m 514.87478,165.86889 c 6.11462,8e-5 9.17195,3.3463 9.17201,10.03865 -6e-5,6.6925 -3.05739,10.03871 -9.17201,10.03865 -6.1147,6e-5 -9.17203,-3.34615 -9.172,-10.03865 -3e-5,-6.69235 3.0573,-10.03857 9.172,-10.03865 m 0,63.26515 c 6.11462,2e-5 9.17195,3.34623 9.17201,10.03865 -6e-5,6.74058 -3.05739,10.11087 -9.17201,10.11087 -6.1147,0 -9.17203,-3.37029 -9.172,-10.11087 -3e-5,-6.69242 3.0573,-10.03863 9.172,-10.03865"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.99206275,0,0,0.99206275,13.445202,326.71769)"
|
||||
style="fill:#ffffff"
|
||||
id="g3945">
|
||||
<g
|
||||
id="text109"
|
||||
style="font-size:204.03166199px;font-weight:normal;fill:#ffffff;fill-rule:evenodd;font-family:Droid Sans Mono">
|
||||
<path
|
||||
id="path4032"
|
||||
style="font-size:204.03166199px;font-weight:normal;fill:#ffffff;fill-rule:evenodd;font-family:Droid Sans Mono"
|
||||
d="m 151.39749,272.06912 -77.308871,0 0,-12.25385 29.389331,-1.9925 0,-117.1588 -29.389331,-1.9925 0,-12.25386 77.308871,0 0,12.25386 -29.2897,1.9925 0,117.1588 29.2897,1.9925 0,12.25385"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4034"
|
||||
style="font-size:204.03166199px;font-weight:normal;fill:#ffffff;fill-rule:evenodd;font-family:Droid Sans Mono"
|
||||
d="m 210.07652,215.38259 0,56.68653 -18.53022,0 0,-145.65151 40.24843,0 c 34.13802,1.5e-4 51.20706,14.21328 51.20716,42.63943 -10e-5,14.54532 -4.61605,25.90254 -13.84785,34.0717 -9.16557,8.16929 -22.51528,12.25391 -40.04918,12.25385 l -19.02834,0 m 0,-15.74072 16.93622,0 c 13.15041,7e-5 22.54834,-2.39092 28.19383,-7.17299 5.71173,-4.78191 8.56764,-12.25376 8.56773,-22.41559 -9e-5,-18.5301 -11.22448,-27.7952 -33.67319,-27.79533 l -20.02459,0 0,57.38391"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="text117"
|
||||
style="font-size:131.4621582px;font-weight:normal;fill:#ffffff;fill-rule:evenodd;font-family:Droid Sans Mono">
|
||||
<path
|
||||
id="path4040"
|
||||
style="font-size:131.4621582px;font-weight:normal;fill:#ffffff;fill-rule:evenodd;font-family:Droid Sans Mono"
|
||||
d="m 367.00874,170.0062 12.06781,0 16.81792,41.9806 c 3.50904,8.77272 5.41336,14.97779 5.71295,18.61524 l 0.38515,0 c 0.98421,-4.79287 2.90992,-11.04074 5.77714,-18.74363 l 15.34153,-41.85221 12.13201,0 -30.49049,79.66042 c -2.86722,7.44609 -6.20512,13.03065 -10.01372,16.75373 -3.80867,3.76581 -9.07228,5.64873 -15.79087,5.64876 -3.68027,-3e-5 -7.27493,-0.36378 -10.784,-1.09124 l 0,-9.30762 c 2.6532,0.5135 5.56316,0.77026 8.72991,0.77028 4.10817,-2e-5 7.2963,-0.87729 9.56438,-2.63181 2.31083,-1.75455 4.36493,-4.77151 6.16229,-9.05086 l 3.72305,-9.62857 -29.33506,-71.12309"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 26 KiB |
4
venv/Lib/site-packages/qtconsole/rich_ipython_widget.py
Normal file
4
venv/Lib/site-packages/qtconsole/rich_ipython_widget.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
import warnings
|
||||
warnings.warn("qtconsole.rich_ipython_widget is deprecated; "
|
||||
"use qtconsole.rich_jupyter_widget", DeprecationWarning)
|
||||
from .rich_jupyter_widget import *
|
418
venv/Lib/site-packages/qtconsole/rich_jupyter_widget.py
Normal file
418
venv/Lib/site-packages/qtconsole/rich_jupyter_widget.py
Normal file
|
@ -0,0 +1,418 @@
|
|||
# Copyright (c) IPython Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
from base64 import b64decode
|
||||
import os
|
||||
import re
|
||||
from warnings import warn
|
||||
|
||||
from qtpy import QtCore, QtGui, QtWidgets
|
||||
|
||||
from ipython_genutils.path import ensure_dir_exists
|
||||
from traitlets import Bool
|
||||
from qtconsole.svg import save_svg, svg_to_clipboard, svg_to_image
|
||||
from .jupyter_widget import JupyterWidget
|
||||
|
||||
|
||||
try:
|
||||
from IPython.lib.latextools import latex_to_png
|
||||
except ImportError:
|
||||
latex_to_png = None
|
||||
|
||||
|
||||
class LatexError(Exception):
|
||||
"""Exception for Latex errors"""
|
||||
|
||||
|
||||
class RichIPythonWidget(JupyterWidget):
|
||||
"""Dummy class for config inheritance. Destroyed below."""
|
||||
|
||||
|
||||
class RichJupyterWidget(RichIPythonWidget):
|
||||
""" An JupyterWidget that supports rich text, including lists, images, and
|
||||
tables. Note that raw performance will be reduced compared to the plain
|
||||
text version.
|
||||
"""
|
||||
|
||||
# RichJupyterWidget protected class variables.
|
||||
_payload_source_plot = 'ipykernel.pylab.backend_payload.add_plot_payload'
|
||||
_jpg_supported = Bool(False)
|
||||
|
||||
# Used to determine whether a given html export attempt has already
|
||||
# displayed a warning about being unable to convert a png to svg.
|
||||
_svg_warning_displayed = False
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# 'object' interface
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
""" Create a RichJupyterWidget.
|
||||
"""
|
||||
kw['kind'] = 'rich'
|
||||
super(RichJupyterWidget, self).__init__(*args, **kw)
|
||||
|
||||
# Configure the ConsoleWidget HTML exporter for our formats.
|
||||
self._html_exporter.image_tag = self._get_image_tag
|
||||
|
||||
# Dictionary for resolving document resource names to SVG data.
|
||||
self._name_to_svg_map = {}
|
||||
|
||||
# Do we support jpg ?
|
||||
# it seems that sometime jpg support is a plugin of QT, so try to assume
|
||||
# it is not always supported.
|
||||
self._jpg_supported = 'jpeg' in QtGui.QImageReader.supportedImageFormats()
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# 'ConsoleWidget' public interface overides
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
def export_html(self):
|
||||
""" Shows a dialog to export HTML/XML in various formats.
|
||||
|
||||
Overridden in order to reset the _svg_warning_displayed flag prior
|
||||
to the export running.
|
||||
"""
|
||||
self._svg_warning_displayed = False
|
||||
super(RichJupyterWidget, self).export_html()
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# 'ConsoleWidget' protected interface
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
def _context_menu_make(self, pos):
|
||||
""" Reimplemented to return a custom context menu for images.
|
||||
"""
|
||||
format = self._control.cursorForPosition(pos).charFormat()
|
||||
name = format.stringProperty(QtGui.QTextFormat.ImageName)
|
||||
if name:
|
||||
menu = QtWidgets.QMenu(self)
|
||||
|
||||
menu.addAction('Copy Image', lambda: self._copy_image(name))
|
||||
menu.addAction('Save Image As...', lambda: self._save_image(name))
|
||||
menu.addSeparator()
|
||||
|
||||
svg = self._name_to_svg_map.get(name, None)
|
||||
if svg is not None:
|
||||
menu.addSeparator()
|
||||
menu.addAction('Copy SVG', lambda: svg_to_clipboard(svg))
|
||||
menu.addAction('Save SVG As...',
|
||||
lambda: save_svg(svg, self._control))
|
||||
else:
|
||||
menu = super(RichJupyterWidget, self)._context_menu_make(pos)
|
||||
return menu
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# 'BaseFrontendMixin' abstract interface
|
||||
#---------------------------------------------------------------------------
|
||||
def _pre_image_append(self, msg, prompt_number):
|
||||
"""Append the Out[] prompt and make the output nicer
|
||||
|
||||
Shared code for some the following if statement
|
||||
"""
|
||||
self._append_plain_text(self.output_sep, True)
|
||||
self._append_html(self._make_out_prompt(prompt_number), True)
|
||||
self._append_plain_text('\n', True)
|
||||
|
||||
def _handle_execute_result(self, msg):
|
||||
"""Overridden to handle rich data types, like SVG."""
|
||||
self.log.debug("execute_result: %s", msg.get('content', ''))
|
||||
if self.include_output(msg):
|
||||
self.flush_clearoutput()
|
||||
content = msg['content']
|
||||
prompt_number = content.get('execution_count', 0)
|
||||
data = content['data']
|
||||
metadata = msg['content']['metadata']
|
||||
if 'image/svg+xml' in data:
|
||||
self._pre_image_append(msg, prompt_number)
|
||||
self._append_svg(data['image/svg+xml'], True)
|
||||
self._append_html(self.output_sep2, True)
|
||||
elif 'image/png' in data:
|
||||
self._pre_image_append(msg, prompt_number)
|
||||
png = b64decode(data['image/png'].encode('ascii'))
|
||||
self._append_png(png, True, metadata=metadata.get('image/png',
|
||||
None))
|
||||
self._append_html(self.output_sep2, True)
|
||||
elif 'image/jpeg' in data and self._jpg_supported:
|
||||
self._pre_image_append(msg, prompt_number)
|
||||
jpg = b64decode(data['image/jpeg'].encode('ascii'))
|
||||
self._append_jpg(jpg, True, metadata=metadata.get('image/jpeg',
|
||||
None))
|
||||
self._append_html(self.output_sep2, True)
|
||||
elif 'text/latex' in data:
|
||||
self._pre_image_append(msg, prompt_number)
|
||||
try:
|
||||
self._append_latex(data['text/latex'], True)
|
||||
except LatexError:
|
||||
return super(RichJupyterWidget, self)._handle_display_data(msg)
|
||||
self._append_html(self.output_sep2, True)
|
||||
else:
|
||||
# Default back to the plain text representation.
|
||||
return super(RichJupyterWidget, self)._handle_execute_result(msg)
|
||||
|
||||
def _handle_display_data(self, msg):
|
||||
"""Overridden to handle rich data types, like SVG."""
|
||||
self.log.debug("display_data: %s", msg.get('content', ''))
|
||||
if self.include_output(msg):
|
||||
self.flush_clearoutput()
|
||||
data = msg['content']['data']
|
||||
metadata = msg['content']['metadata']
|
||||
# Try to use the svg or html representations.
|
||||
# FIXME: Is this the right ordering of things to try?
|
||||
self.log.debug("display: %s", msg.get('content', ''))
|
||||
if 'image/svg+xml' in data:
|
||||
svg = data['image/svg+xml']
|
||||
self._append_svg(svg, True)
|
||||
elif 'image/png' in data:
|
||||
# PNG data is base64 encoded as it passes over the network
|
||||
# in a JSON structure so we decode it.
|
||||
png = b64decode(data['image/png'].encode('ascii'))
|
||||
self._append_png(png, True, metadata=metadata.get('image/png', None))
|
||||
elif 'image/jpeg' in data and self._jpg_supported:
|
||||
jpg = b64decode(data['image/jpeg'].encode('ascii'))
|
||||
self._append_jpg(jpg, True, metadata=metadata.get('image/jpeg', None))
|
||||
elif 'text/latex' in data and latex_to_png:
|
||||
try:
|
||||
self._append_latex(data['text/latex'], True)
|
||||
except LatexError:
|
||||
return super(RichJupyterWidget, self)._handle_display_data(msg)
|
||||
else:
|
||||
# Default back to the plain text representation.
|
||||
return super(RichJupyterWidget, self)._handle_display_data(msg)
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# 'RichJupyterWidget' protected interface
|
||||
#---------------------------------------------------------------------------
|
||||
def _is_latex_math(self, latex):
|
||||
"""
|
||||
Determine if a Latex string is in math mode
|
||||
|
||||
This is the only mode supported by qtconsole
|
||||
"""
|
||||
basic_envs = ['math', 'displaymath']
|
||||
starable_envs = ['equation', 'eqnarray' 'multline', 'gather', 'align',
|
||||
'flalign', 'alignat']
|
||||
star_envs = [env + '*' for env in starable_envs]
|
||||
envs = basic_envs + starable_envs + star_envs
|
||||
|
||||
env_syntax = [r'\begin{{{0}}} \end{{{0}}}'.format(env).split() for env in envs]
|
||||
|
||||
math_syntax = [
|
||||
(r'\[', r'\]'), (r'\(', r'\)'),
|
||||
('$$', '$$'), ('$', '$'),
|
||||
]
|
||||
|
||||
for start, end in math_syntax + env_syntax:
|
||||
inner = latex[len(start):-len(end)]
|
||||
if start in inner or end in inner:
|
||||
return False
|
||||
if latex.startswith(start) and latex.endswith(end):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _append_latex(self, latex, before_prompt=False, metadata=None):
|
||||
""" Append latex data to the widget."""
|
||||
png = None
|
||||
|
||||
if self._is_latex_math(latex):
|
||||
png = latex_to_png(latex, wrap=False, backend='dvipng')
|
||||
|
||||
# Matplotlib only supports strings enclosed in dollar signs
|
||||
if png is None and latex.startswith('$') and latex.endswith('$'):
|
||||
# To avoid long and ugly errors, like the one reported in
|
||||
# spyder-ide/spyder#7619
|
||||
try:
|
||||
png = latex_to_png(latex, wrap=False, backend='matplotlib')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if png:
|
||||
self._append_png(png, before_prompt, metadata)
|
||||
else:
|
||||
raise LatexError
|
||||
|
||||
def _append_jpg(self, jpg, before_prompt=False, metadata=None):
|
||||
""" Append raw JPG data to the widget."""
|
||||
self._append_custom(self._insert_jpg, jpg, before_prompt, metadata=metadata)
|
||||
|
||||
def _append_png(self, png, before_prompt=False, metadata=None):
|
||||
""" Append raw PNG data to the widget.
|
||||
"""
|
||||
self._append_custom(self._insert_png, png, before_prompt, metadata=metadata)
|
||||
|
||||
def _append_svg(self, svg, before_prompt=False):
|
||||
""" Append raw SVG data to the widget.
|
||||
"""
|
||||
self._append_custom(self._insert_svg, svg, before_prompt)
|
||||
|
||||
def _add_image(self, image):
|
||||
""" Adds the specified QImage to the document and returns a
|
||||
QTextImageFormat that references it.
|
||||
"""
|
||||
document = self._control.document()
|
||||
name = str(image.cacheKey())
|
||||
document.addResource(QtGui.QTextDocument.ImageResource,
|
||||
QtCore.QUrl(name), image)
|
||||
format = QtGui.QTextImageFormat()
|
||||
format.setName(name)
|
||||
return format
|
||||
|
||||
def _copy_image(self, name):
|
||||
""" Copies the ImageResource with 'name' to the clipboard.
|
||||
"""
|
||||
image = self._get_image(name)
|
||||
QtWidgets.QApplication.clipboard().setImage(image)
|
||||
|
||||
def _get_image(self, name):
|
||||
""" Returns the QImage stored as the ImageResource with 'name'.
|
||||
"""
|
||||
document = self._control.document()
|
||||
image = document.resource(QtGui.QTextDocument.ImageResource,
|
||||
QtCore.QUrl(name))
|
||||
return image
|
||||
|
||||
def _get_image_tag(self, match, path = None, format = "png"):
|
||||
""" Return (X)HTML mark-up for the image-tag given by match.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
match : re.SRE_Match
|
||||
A match to an HTML image tag as exported by Qt, with
|
||||
match.group("Name") containing the matched image ID.
|
||||
|
||||
path : string|None, optional [default None]
|
||||
If not None, specifies a path to which supporting files may be
|
||||
written (e.g., for linked images). If None, all images are to be
|
||||
included inline.
|
||||
|
||||
format : "png"|"svg"|"jpg", optional [default "png"]
|
||||
Format for returned or referenced images.
|
||||
"""
|
||||
if format in ("png","jpg"):
|
||||
try:
|
||||
image = self._get_image(match.group("name"))
|
||||
except KeyError:
|
||||
return "<b>Couldn't find image %s</b>" % match.group("name")
|
||||
|
||||
if path is not None:
|
||||
ensure_dir_exists(path)
|
||||
relpath = os.path.basename(path)
|
||||
if image.save("%s/qt_img%s.%s" % (path, match.group("name"), format),
|
||||
"PNG"):
|
||||
return '<img src="%s/qt_img%s.%s">' % (relpath,
|
||||
match.group("name"),format)
|
||||
else:
|
||||
return "<b>Couldn't save image!</b>"
|
||||
else:
|
||||
ba = QtCore.QByteArray()
|
||||
buffer_ = QtCore.QBuffer(ba)
|
||||
buffer_.open(QtCore.QIODevice.WriteOnly)
|
||||
image.save(buffer_, format.upper())
|
||||
buffer_.close()
|
||||
return '<img src="data:image/%s;base64,\n%s\n" />' % (
|
||||
format,re.sub(r'(.{60})',r'\1\n', str(ba.toBase64().data().decode())))
|
||||
|
||||
elif format == "svg":
|
||||
try:
|
||||
svg = str(self._name_to_svg_map[match.group("name")])
|
||||
except KeyError:
|
||||
if not self._svg_warning_displayed:
|
||||
QtWidgets.QMessageBox.warning(self, 'Error converting PNG to SVG.',
|
||||
'Cannot convert PNG images to SVG, export with PNG figures instead. '
|
||||
'If you want to export matplotlib figures as SVG, add '
|
||||
'to your ipython config:\n\n'
|
||||
'\tc.InlineBackend.figure_format = \'svg\'\n\n'
|
||||
'And regenerate the figures.',
|
||||
QtWidgets.QMessageBox.Ok)
|
||||
self._svg_warning_displayed = True
|
||||
return ("<b>Cannot convert PNG images to SVG.</b> "
|
||||
"You must export this session with PNG images. "
|
||||
"If you want to export matplotlib figures as SVG, add to your config "
|
||||
"<span>c.InlineBackend.figure_format = 'svg'</span> "
|
||||
"and regenerate the figures.")
|
||||
|
||||
# Not currently checking path, because it's tricky to find a
|
||||
# cross-browser way to embed external SVG images (e.g., via
|
||||
# object or embed tags).
|
||||
|
||||
# Chop stand-alone header from matplotlib SVG
|
||||
offset = svg.find("<svg")
|
||||
assert(offset > -1)
|
||||
|
||||
return svg[offset:]
|
||||
|
||||
else:
|
||||
return '<b>Unrecognized image format</b>'
|
||||
|
||||
def _insert_jpg(self, cursor, jpg, metadata=None):
|
||||
""" Insert raw PNG data into the widget."""
|
||||
self._insert_img(cursor, jpg, 'jpg', metadata=metadata)
|
||||
|
||||
def _insert_png(self, cursor, png, metadata=None):
|
||||
""" Insert raw PNG data into the widget.
|
||||
"""
|
||||
self._insert_img(cursor, png, 'png', metadata=metadata)
|
||||
|
||||
def _insert_img(self, cursor, img, fmt, metadata=None):
|
||||
""" insert a raw image, jpg or png """
|
||||
if metadata:
|
||||
width = metadata.get('width', None)
|
||||
height = metadata.get('height', None)
|
||||
else:
|
||||
width = height = None
|
||||
try:
|
||||
image = QtGui.QImage()
|
||||
image.loadFromData(img, fmt.upper())
|
||||
if width and height:
|
||||
image = image.scaled(width, height,
|
||||
QtCore.Qt.IgnoreAspectRatio,
|
||||
QtCore.Qt.SmoothTransformation)
|
||||
elif width and not height:
|
||||
image = image.scaledToWidth(width, QtCore.Qt.SmoothTransformation)
|
||||
elif height and not width:
|
||||
image = image.scaledToHeight(height, QtCore.Qt.SmoothTransformation)
|
||||
except ValueError:
|
||||
self._insert_plain_text(cursor, 'Received invalid %s data.'%fmt)
|
||||
else:
|
||||
format = self._add_image(image)
|
||||
cursor.insertBlock()
|
||||
cursor.insertImage(format)
|
||||
cursor.insertBlock()
|
||||
|
||||
def _insert_svg(self, cursor, svg):
|
||||
""" Insert raw SVG data into the widet.
|
||||
"""
|
||||
try:
|
||||
image = svg_to_image(svg)
|
||||
except ValueError:
|
||||
self._insert_plain_text(cursor, 'Received invalid SVG data.')
|
||||
else:
|
||||
format = self._add_image(image)
|
||||
self._name_to_svg_map[format.name()] = svg
|
||||
cursor.insertBlock()
|
||||
cursor.insertImage(format)
|
||||
cursor.insertBlock()
|
||||
|
||||
def _save_image(self, name, format='PNG'):
|
||||
""" Shows a save dialog for the ImageResource with 'name'.
|
||||
"""
|
||||
dialog = QtWidgets.QFileDialog(self._control, 'Save Image')
|
||||
dialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
|
||||
dialog.setDefaultSuffix(format.lower())
|
||||
dialog.setNameFilter('%s file (*.%s)' % (format, format.lower()))
|
||||
if dialog.exec_():
|
||||
filename = dialog.selectedFiles()[0]
|
||||
image = self._get_image(name)
|
||||
image.save(filename, format)
|
||||
|
||||
|
||||
# Clobber RichIPythonWidget above:
|
||||
|
||||
class RichIPythonWidget(RichJupyterWidget):
|
||||
"""Deprecated class. Use RichJupyterWidget."""
|
||||
def __init__(self, *a, **kw):
|
||||
warn("RichIPythonWidget is deprecated, use RichJupyterWidget",
|
||||
DeprecationWarning)
|
||||
super(RichIPythonWidget, self).__init__(*a, **kw)
|
234
venv/Lib/site-packages/qtconsole/rich_text.py
Normal file
234
venv/Lib/site-packages/qtconsole/rich_text.py
Normal file
|
@ -0,0 +1,234 @@
|
|||
""" Defines classes and functions for working with Qt's rich text system.
|
||||
"""
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
|
||||
from qtpy import QtWidgets
|
||||
|
||||
from ipython_genutils import py3compat
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Constants
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# A regular expression for an HTML paragraph with no content.
|
||||
EMPTY_P_RE = re.compile(r'<p[^/>]*>\s*</p>')
|
||||
|
||||
# A regular expression for matching images in rich text HTML.
|
||||
# Note that this is overly restrictive, but Qt's output is predictable...
|
||||
IMG_RE = re.compile(r'<img src="(?P<name>[\d]+)" />')
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Classes
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
class HtmlExporter(object):
|
||||
""" A stateful HTML exporter for a Q(Plain)TextEdit.
|
||||
|
||||
This class is designed for convenient user interaction.
|
||||
"""
|
||||
|
||||
def __init__(self, control):
|
||||
""" Creates an HtmlExporter for the given Q(Plain)TextEdit.
|
||||
"""
|
||||
assert isinstance(control, (QtWidgets.QPlainTextEdit, QtWidgets.QTextEdit))
|
||||
self.control = control
|
||||
self.filename = 'ipython.html'
|
||||
self.image_tag = None
|
||||
self.inline_png = None
|
||||
|
||||
def export(self):
|
||||
""" Displays a dialog for exporting HTML generated by Qt's rich text
|
||||
system.
|
||||
|
||||
Returns
|
||||
-------
|
||||
The name of the file that was saved, or None if no file was saved.
|
||||
"""
|
||||
parent = self.control.window()
|
||||
dialog = QtWidgets.QFileDialog(parent, 'Save as...')
|
||||
dialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
|
||||
filters = [
|
||||
'HTML with PNG figures (*.html *.htm)',
|
||||
'XHTML with inline SVG figures (*.xhtml *.xml)'
|
||||
]
|
||||
dialog.setNameFilters(filters)
|
||||
if self.filename:
|
||||
dialog.selectFile(self.filename)
|
||||
root,ext = os.path.splitext(self.filename)
|
||||
if ext.lower() in ('.xml', '.xhtml'):
|
||||
dialog.selectNameFilter(filters[-1])
|
||||
|
||||
if dialog.exec_():
|
||||
self.filename = dialog.selectedFiles()[0]
|
||||
choice = dialog.selectedNameFilter()
|
||||
html = py3compat.cast_unicode(self.control.document().toHtml())
|
||||
|
||||
# Configure the exporter.
|
||||
if choice.startswith('XHTML'):
|
||||
exporter = export_xhtml
|
||||
else:
|
||||
# If there are PNGs, decide how to export them.
|
||||
inline = self.inline_png
|
||||
if inline is None and IMG_RE.search(html):
|
||||
dialog = QtWidgets.QDialog(parent)
|
||||
dialog.setWindowTitle('Save as...')
|
||||
layout = QtWidgets.QVBoxLayout(dialog)
|
||||
msg = "Exporting HTML with PNGs"
|
||||
info = "Would you like inline PNGs (single large html " \
|
||||
"file) or external image files?"
|
||||
checkbox = QtWidgets.QCheckBox("&Don't ask again")
|
||||
checkbox.setShortcut('D')
|
||||
ib = QtWidgets.QPushButton("&Inline")
|
||||
ib.setShortcut('I')
|
||||
eb = QtWidgets.QPushButton("&External")
|
||||
eb.setShortcut('E')
|
||||
box = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Question,
|
||||
dialog.windowTitle(), msg)
|
||||
box.setInformativeText(info)
|
||||
box.addButton(ib, QtWidgets.QMessageBox.NoRole)
|
||||
box.addButton(eb, QtWidgets.QMessageBox.YesRole)
|
||||
layout.setSpacing(0)
|
||||
layout.addWidget(box)
|
||||
layout.addWidget(checkbox)
|
||||
dialog.setLayout(layout)
|
||||
dialog.show()
|
||||
reply = box.exec_()
|
||||
dialog.hide()
|
||||
inline = (reply == 0)
|
||||
if checkbox.checkState():
|
||||
# Don't ask anymore; always use this choice.
|
||||
self.inline_png = inline
|
||||
exporter = lambda h, f, i: export_html(h, f, i, inline)
|
||||
|
||||
# Perform the export!
|
||||
try:
|
||||
return exporter(html, self.filename, self.image_tag)
|
||||
except Exception as e:
|
||||
msg = "Error exporting HTML to %s\n" % self.filename + str(e)
|
||||
reply = QtWidgets.QMessageBox.warning(parent, 'Error', msg,
|
||||
QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.Ok)
|
||||
|
||||
return None
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Functions
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def export_html(html, filename, image_tag = None, inline = True):
|
||||
""" Export the contents of the ConsoleWidget as HTML.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
html : unicode,
|
||||
A Python unicode string containing the Qt HTML to export.
|
||||
|
||||
filename : str
|
||||
The file to be saved.
|
||||
|
||||
image_tag : callable, optional (default None)
|
||||
Used to convert images. See ``default_image_tag()`` for information.
|
||||
|
||||
inline : bool, optional [default True]
|
||||
If True, include images as inline PNGs. Otherwise, include them as
|
||||
links to external PNG files, mimicking web browsers' "Web Page,
|
||||
Complete" behavior.
|
||||
"""
|
||||
if image_tag is None:
|
||||
image_tag = default_image_tag
|
||||
|
||||
if inline:
|
||||
path = None
|
||||
else:
|
||||
root,ext = os.path.splitext(filename)
|
||||
path = root + "_files"
|
||||
if os.path.isfile(path):
|
||||
raise OSError("%s exists, but is not a directory." % path)
|
||||
|
||||
with io.open(filename, 'w', encoding='utf-8') as f:
|
||||
html = fix_html(html)
|
||||
f.write(IMG_RE.sub(lambda x: image_tag(x, path = path, format = "png"),
|
||||
html))
|
||||
|
||||
|
||||
def export_xhtml(html, filename, image_tag=None):
|
||||
""" Export the contents of the ConsoleWidget as XHTML with inline SVGs.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
html : unicode,
|
||||
A Python unicode string containing the Qt HTML to export.
|
||||
|
||||
filename : str
|
||||
The file to be saved.
|
||||
|
||||
image_tag : callable, optional (default None)
|
||||
Used to convert images. See ``default_image_tag()`` for information.
|
||||
"""
|
||||
if image_tag is None:
|
||||
image_tag = default_image_tag
|
||||
|
||||
with io.open(filename, 'w', encoding='utf-8') as f:
|
||||
# Hack to make xhtml header -- note that we are not doing any check for
|
||||
# valid XML.
|
||||
offset = html.find("<html>")
|
||||
assert offset > -1, 'Invalid HTML string: no <html> tag.'
|
||||
html = (u'<html xmlns="http://www.w3.org/1999/xhtml">\n'+
|
||||
html[offset+6:])
|
||||
|
||||
html = fix_html(html)
|
||||
f.write(IMG_RE.sub(lambda x: image_tag(x, path = None, format = "svg"),
|
||||
html))
|
||||
|
||||
|
||||
def default_image_tag(match, path = None, format = "png"):
|
||||
""" Return (X)HTML mark-up for the image-tag given by match.
|
||||
|
||||
This default implementation merely removes the image, and exists mostly
|
||||
for documentation purposes. More information than is present in the Qt
|
||||
HTML is required to supply the images.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
match : re.SRE_Match
|
||||
A match to an HTML image tag as exported by Qt, with match.group("Name")
|
||||
containing the matched image ID.
|
||||
|
||||
path : string|None, optional [default None]
|
||||
If not None, specifies a path to which supporting files may be written
|
||||
(e.g., for linked images). If None, all images are to be included
|
||||
inline.
|
||||
|
||||
format : "png"|"svg", optional [default "png"]
|
||||
Format for returned or referenced images.
|
||||
"""
|
||||
return u''
|
||||
|
||||
|
||||
def fix_html(html):
|
||||
""" Transforms a Qt-generated HTML string into a standards-compliant one.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
html : unicode,
|
||||
A Python unicode string containing the Qt HTML.
|
||||
"""
|
||||
# A UTF-8 declaration is needed for proper rendering of some characters
|
||||
# (e.g., indented commands) when viewing exported HTML on a local system
|
||||
# (i.e., without seeing an encoding declaration in an HTTP header).
|
||||
# C.f. http://www.w3.org/International/O-charset for details.
|
||||
offset = html.find('<head>')
|
||||
if offset > -1:
|
||||
html = (html[:offset+6]+
|
||||
'\n<meta http-equiv="Content-Type" '+
|
||||
'content="text/html; charset=utf-8" />\n'+
|
||||
html[offset+6:])
|
||||
|
||||
# Replace empty paragraphs tags with line breaks.
|
||||
html = re.sub(EMPTY_P_RE, '<br/>', html)
|
||||
|
||||
return html
|
119
venv/Lib/site-packages/qtconsole/styles.py
Normal file
119
venv/Lib/site-packages/qtconsole/styles.py
Normal file
|
@ -0,0 +1,119 @@
|
|||
""" Style utilities, templates, and defaults for syntax highlighting widgets.
|
||||
"""
|
||||
#-----------------------------------------------------------------------------
|
||||
# Imports
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
from colorsys import rgb_to_hls
|
||||
from pygments.styles import get_style_by_name
|
||||
from pygments.token import Token
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Constants
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
default_template = '''\
|
||||
QPlainTextEdit, QTextEdit {
|
||||
background-color: %(bgcolor)s;
|
||||
background-clip: padding;
|
||||
color: %(fgcolor)s;
|
||||
selection-background-color: %(select)s;
|
||||
}
|
||||
.inverted {
|
||||
background-color: %(fgcolor)s;
|
||||
color: %(bgcolor)s;
|
||||
}
|
||||
.error { color: red; }
|
||||
.in-prompt-number { font-weight: bold; }
|
||||
.out-prompt-number { font-weight: bold; }
|
||||
'''
|
||||
|
||||
# The default light style sheet: black text on a white background.
|
||||
default_light_style_template = default_template + '''\
|
||||
.in-prompt { color: navy; }
|
||||
.out-prompt { color: darkred; }
|
||||
'''
|
||||
default_light_style_sheet = default_light_style_template%dict(
|
||||
bgcolor='white', fgcolor='black', select="#ccc")
|
||||
default_light_syntax_style = 'default'
|
||||
|
||||
# The default dark style sheet: white text on a black background.
|
||||
default_dark_style_template = default_template + '''\
|
||||
.in-prompt,
|
||||
.in-prompt-number { color: lime; }
|
||||
.out-prompt,
|
||||
.out-prompt-number { color: red; }
|
||||
'''
|
||||
default_dark_style_sheet = default_dark_style_template%dict(
|
||||
bgcolor='black', fgcolor='white', select="#555")
|
||||
default_dark_syntax_style = 'monokai'
|
||||
|
||||
# The default monochrome
|
||||
default_bw_style_sheet = default_template%dict(
|
||||
bgcolor='white', fgcolor='black', select="#ccc")
|
||||
default_bw_syntax_style = 'bw'
|
||||
|
||||
|
||||
def hex_to_rgb(color):
|
||||
"""Convert a hex color to rgb integer tuple."""
|
||||
if color.startswith('#'):
|
||||
color = color[1:]
|
||||
if len(color) == 3:
|
||||
color = ''.join([c*2 for c in color])
|
||||
if len(color) != 6:
|
||||
return False
|
||||
try:
|
||||
r = int(color[:2],16)
|
||||
g = int(color[2:4],16)
|
||||
b = int(color[4:],16)
|
||||
except ValueError:
|
||||
return False
|
||||
else:
|
||||
return r,g,b
|
||||
|
||||
def dark_color(color):
|
||||
"""Check whether a color is 'dark'.
|
||||
|
||||
Currently, this is simply whether the luminance is <50%"""
|
||||
rgb = hex_to_rgb(color)
|
||||
if rgb:
|
||||
return rgb_to_hls(*rgb)[1] < 128
|
||||
else: # default to False
|
||||
return False
|
||||
|
||||
def dark_style(stylename):
|
||||
"""Guess whether the background of the style with name 'stylename'
|
||||
counts as 'dark'."""
|
||||
return dark_color(get_style_by_name(stylename).background_color)
|
||||
|
||||
def get_colors(stylename):
|
||||
"""Construct the keys to be used building the base stylesheet
|
||||
from a templatee."""
|
||||
style = get_style_by_name(stylename)
|
||||
fgcolor = style.style_for_token(Token.Text)['color'] or ''
|
||||
if len(fgcolor) in (3,6):
|
||||
# could be 'abcdef' or 'ace' hex, which needs '#' prefix
|
||||
try:
|
||||
int(fgcolor, 16)
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
fgcolor = "#"+fgcolor
|
||||
|
||||
return dict(
|
||||
bgcolor = style.background_color,
|
||||
select = style.highlight_color,
|
||||
fgcolor = fgcolor
|
||||
)
|
||||
|
||||
def sheet_from_template(name, colors='lightbg'):
|
||||
"""Use one of the base templates, and set bg/fg/select colors."""
|
||||
colors = colors.lower()
|
||||
if colors=='lightbg':
|
||||
return default_light_style_template%get_colors(name)
|
||||
elif colors=='linux':
|
||||
return default_dark_style_template%get_colors(name)
|
||||
elif colors=='nocolor':
|
||||
return default_bw_style_sheet
|
||||
else:
|
||||
raise KeyError("No such color scheme: %s"%colors)
|
92
venv/Lib/site-packages/qtconsole/svg.py
Normal file
92
venv/Lib/site-packages/qtconsole/svg.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
""" Defines utility functions for working with SVG documents in Qt.
|
||||
"""
|
||||
|
||||
# System library imports.
|
||||
from qtpy import QtCore, QtGui, QtSvg, QtWidgets
|
||||
|
||||
# Our own imports
|
||||
from ipython_genutils.py3compat import unicode_type
|
||||
|
||||
def save_svg(string, parent=None):
|
||||
""" Prompts the user to save an SVG document to disk.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
string : basestring
|
||||
A Python string containing a SVG document.
|
||||
|
||||
parent : QWidget, optional
|
||||
The parent to use for the file dialog.
|
||||
|
||||
Returns
|
||||
-------
|
||||
The name of the file to which the document was saved, or None if the save
|
||||
was cancelled.
|
||||
"""
|
||||
if isinstance(string, unicode_type):
|
||||
string = string.encode('utf-8')
|
||||
|
||||
dialog = QtWidgets.QFileDialog(parent, 'Save SVG Document')
|
||||
dialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
|
||||
dialog.setDefaultSuffix('svg')
|
||||
dialog.setNameFilter('SVG document (*.svg)')
|
||||
if dialog.exec_():
|
||||
filename = dialog.selectedFiles()[0]
|
||||
f = open(filename, 'wb')
|
||||
try:
|
||||
f.write(string)
|
||||
finally:
|
||||
f.close()
|
||||
return filename
|
||||
return None
|
||||
|
||||
def svg_to_clipboard(string):
|
||||
""" Copy a SVG document to the clipboard.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
string : basestring
|
||||
A Python string containing a SVG document.
|
||||
"""
|
||||
if isinstance(string, unicode_type):
|
||||
string = string.encode('utf-8')
|
||||
|
||||
mime_data = QtCore.QMimeData()
|
||||
mime_data.setData('image/svg+xml', string)
|
||||
QtWidgets.QApplication.clipboard().setMimeData(mime_data)
|
||||
|
||||
def svg_to_image(string, size=None):
|
||||
""" Convert a SVG document to a QImage.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
string : basestring
|
||||
A Python string containing a SVG document.
|
||||
|
||||
size : QSize, optional
|
||||
The size of the image that is produced. If not specified, the SVG
|
||||
document's default size is used.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If an invalid SVG string is provided.
|
||||
|
||||
Returns
|
||||
-------
|
||||
A QImage of format QImage.Format_ARGB32.
|
||||
"""
|
||||
if isinstance(string, unicode_type):
|
||||
string = string.encode('utf-8')
|
||||
|
||||
renderer = QtSvg.QSvgRenderer(QtCore.QByteArray(string))
|
||||
if not renderer.isValid():
|
||||
raise ValueError('Invalid SVG data.')
|
||||
|
||||
if size is None:
|
||||
size = renderer.defaultSize()
|
||||
image = QtGui.QImage(size, QtGui.QImage.Format_ARGB32)
|
||||
image.fill(0)
|
||||
painter = QtGui.QPainter(image)
|
||||
renderer.render(painter)
|
||||
return image
|
6
venv/Lib/site-packages/qtconsole/tests/__init__.py
Normal file
6
venv/Lib/site-packages/qtconsole/tests/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
|
||||
import os
|
||||
import sys
|
||||
|
||||
no_display = (sys.platform not in ('darwin', 'win32') and
|
||||
os.environ.get('DISPLAY', '') == '')
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
610
venv/Lib/site-packages/qtconsole/tests/test_00_console_widget.py
Normal file
610
venv/Lib/site-packages/qtconsole/tests/test_00_console_widget.py
Normal file
|
@ -0,0 +1,610 @@
|
|||
import sys
|
||||
import unittest
|
||||
|
||||
from flaky import flaky
|
||||
import pytest
|
||||
from qtpy import QtCore, QtGui, QtWidgets
|
||||
from qtpy.QtTest import QTest
|
||||
|
||||
from qtconsole.console_widget import ConsoleWidget
|
||||
from qtconsole.qtconsoleapp import JupyterQtConsoleApp
|
||||
from . import no_display
|
||||
|
||||
if sys.version[0] == '2': # Python 2
|
||||
from IPython.core.inputsplitter import InputSplitter as TransformerManager
|
||||
else:
|
||||
from IPython.core.inputtransformer2 import TransformerManager
|
||||
|
||||
|
||||
SHELL_TIMEOUT = 20000
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def qtconsole(qtbot):
|
||||
"""Qtconsole fixture."""
|
||||
# Create a console
|
||||
console = JupyterQtConsoleApp()
|
||||
console.initialize(argv=[])
|
||||
|
||||
qtbot.addWidget(console.window)
|
||||
console.window.confirm_exit = False
|
||||
console.window.show()
|
||||
return console
|
||||
|
||||
|
||||
@flaky(max_runs=3)
|
||||
@pytest.mark.parametrize(
|
||||
"debug", [True, False])
|
||||
def test_scroll(qtconsole, qtbot, debug):
|
||||
"""
|
||||
Make sure the scrolling works.
|
||||
"""
|
||||
window = qtconsole.window
|
||||
shell = window.active_frontend
|
||||
control = shell._control
|
||||
scroll_bar = control.verticalScrollBar()
|
||||
|
||||
# Wait until the console is fully up
|
||||
qtbot.waitUntil(lambda: shell._prompt_html is not None,
|
||||
timeout=SHELL_TIMEOUT)
|
||||
|
||||
assert scroll_bar.value() == 0
|
||||
|
||||
# Define a function with loads of output
|
||||
# Check the outputs are working as well
|
||||
code = ["import time",
|
||||
"def print_numbers():",
|
||||
" for i in range(1000):",
|
||||
" print(i)",
|
||||
" time.sleep(.01)"]
|
||||
for line in code:
|
||||
qtbot.keyClicks(control, line)
|
||||
qtbot.keyClick(control, QtCore.Qt.Key_Enter)
|
||||
|
||||
with qtbot.waitSignal(shell.executed):
|
||||
qtbot.keyClick(control, QtCore.Qt.Key_Enter,
|
||||
modifier=QtCore.Qt.ShiftModifier)
|
||||
|
||||
def run_line(line, block=True):
|
||||
qtbot.keyClicks(control, line)
|
||||
if block:
|
||||
with qtbot.waitSignal(shell.executed):
|
||||
qtbot.keyClick(control, QtCore.Qt.Key_Enter,
|
||||
modifier=QtCore.Qt.ShiftModifier)
|
||||
else:
|
||||
qtbot.keyClick(control, QtCore.Qt.Key_Enter,
|
||||
modifier=QtCore.Qt.ShiftModifier)
|
||||
|
||||
if debug:
|
||||
# Enter debug
|
||||
run_line('%debug print()', block=False)
|
||||
qtbot.keyClick(control, QtCore.Qt.Key_Enter)
|
||||
# redefine run_line
|
||||
def run_line(line, block=True):
|
||||
qtbot.keyClicks(control, '!' + line)
|
||||
qtbot.keyClick(control, QtCore.Qt.Key_Enter,
|
||||
modifier=QtCore.Qt.ShiftModifier)
|
||||
if block:
|
||||
qtbot.waitUntil(
|
||||
lambda: control.toPlainText().strip(
|
||||
).split()[-1] == "ipdb>")
|
||||
|
||||
prev_position = scroll_bar.value()
|
||||
|
||||
# Create a bunch of inputs
|
||||
for i in range(20):
|
||||
run_line('a = 1')
|
||||
|
||||
assert scroll_bar.value() > prev_position
|
||||
|
||||
# Put the scroll bar higher and check it doesn't move
|
||||
prev_position = scroll_bar.value() + scroll_bar.pageStep() // 2
|
||||
scroll_bar.setValue(prev_position)
|
||||
|
||||
for i in range(2):
|
||||
run_line('a')
|
||||
|
||||
assert scroll_bar.value() == prev_position
|
||||
|
||||
# add more input and check it moved
|
||||
for i in range(10):
|
||||
run_line('a')
|
||||
|
||||
assert scroll_bar.value() > prev_position
|
||||
|
||||
prev_position = scroll_bar.value()
|
||||
|
||||
# Run the printing function
|
||||
run_line('print_numbers()', block=False)
|
||||
|
||||
qtbot.wait(1000)
|
||||
|
||||
# Check everything advances
|
||||
assert scroll_bar.value() > prev_position
|
||||
|
||||
# move up
|
||||
prev_position = scroll_bar.value() - scroll_bar.pageStep()
|
||||
scroll_bar.setValue(prev_position)
|
||||
|
||||
qtbot.wait(1000)
|
||||
|
||||
# Check position stayed the same
|
||||
assert scroll_bar.value() == prev_position
|
||||
|
||||
# reset position
|
||||
prev_position = scroll_bar.maximum() - (scroll_bar.pageStep() * 8) // 10
|
||||
scroll_bar.setValue(prev_position)
|
||||
|
||||
qtbot.wait(1000)
|
||||
assert scroll_bar.value() > prev_position
|
||||
|
||||
|
||||
@flaky(max_runs=3)
|
||||
def test_input(qtconsole, qtbot):
|
||||
"""
|
||||
Test input function
|
||||
"""
|
||||
window = qtconsole.window
|
||||
shell = window.active_frontend
|
||||
control = shell._control
|
||||
|
||||
# Wait until the console is fully up
|
||||
qtbot.waitUntil(lambda: shell._prompt_html is not None,
|
||||
timeout=SHELL_TIMEOUT)
|
||||
|
||||
with qtbot.waitSignal(shell.executed):
|
||||
shell.execute("import time")
|
||||
|
||||
if sys.version[0] == '2':
|
||||
input_function = 'raw_input'
|
||||
else:
|
||||
input_function = 'input'
|
||||
shell.execute("print(" + input_function + "('name: ')); time.sleep(3)")
|
||||
|
||||
qtbot.waitUntil(lambda: control.toPlainText().split()[-1] == 'name:')
|
||||
|
||||
qtbot.keyClicks(control, 'test')
|
||||
qtbot.keyClick(control, QtCore.Qt.Key_Enter)
|
||||
qtbot.waitUntil(lambda: not shell._reading)
|
||||
qtbot.keyClick(control, 'z', modifier=QtCore.Qt.ControlModifier)
|
||||
for i in range(10):
|
||||
qtbot.keyClick(control, QtCore.Qt.Key_Backspace)
|
||||
qtbot.waitUntil(lambda: shell._prompt_html is not None,
|
||||
timeout=SHELL_TIMEOUT)
|
||||
|
||||
assert 'name: test\ntest' in control.toPlainText()
|
||||
|
||||
|
||||
@flaky(max_runs=3)
|
||||
def test_debug(qtconsole, qtbot):
|
||||
"""
|
||||
Make sure the cursor works while debugging
|
||||
|
||||
It might not because the console is "_executing"
|
||||
"""
|
||||
window = qtconsole.window
|
||||
shell = window.active_frontend
|
||||
control = shell._control
|
||||
|
||||
# Wait until the console is fully up
|
||||
qtbot.waitUntil(lambda: shell._prompt_html is not None,
|
||||
timeout=SHELL_TIMEOUT)
|
||||
|
||||
# Enter execution
|
||||
code = "%debug range(1)"
|
||||
qtbot.keyClicks(control, code)
|
||||
qtbot.keyClick(control, QtCore.Qt.Key_Enter,
|
||||
modifier=QtCore.Qt.ShiftModifier)
|
||||
|
||||
qtbot.waitUntil(
|
||||
lambda: control.toPlainText().strip().split()[-1] == "ipdb>",
|
||||
timeout=SHELL_TIMEOUT)
|
||||
|
||||
# We should be able to move the cursor while debugging
|
||||
qtbot.keyClicks(control, "abd")
|
||||
qtbot.wait(100)
|
||||
qtbot.keyClick(control, QtCore.Qt.Key_Left)
|
||||
qtbot.keyClick(control, 'c')
|
||||
qtbot.wait(100)
|
||||
assert control.toPlainText().strip().split()[-1] == "abcd"
|
||||
|
||||
|
||||
@pytest.mark.skipif(no_display, reason="Doesn't work without a display")
|
||||
class TestConsoleWidget(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
""" Create the application for the test case.
|
||||
"""
|
||||
cls._app = QtWidgets.QApplication.instance()
|
||||
if cls._app is None:
|
||||
cls._app = QtWidgets.QApplication([])
|
||||
cls._app.setQuitOnLastWindowClosed(False)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
""" Exit the application.
|
||||
"""
|
||||
QtWidgets.QApplication.quit()
|
||||
|
||||
def assert_text_equal(self, cursor, text):
|
||||
cursor.select(cursor.Document)
|
||||
selection = cursor.selectedText()
|
||||
self.assertEqual(selection, text)
|
||||
|
||||
def test_special_characters(self):
|
||||
""" Are special characters displayed correctly?
|
||||
"""
|
||||
w = ConsoleWidget()
|
||||
cursor = w._get_prompt_cursor()
|
||||
|
||||
test_inputs = ['xyz\b\b=\n',
|
||||
'foo\b\nbar\n',
|
||||
'foo\b\nbar\r\n',
|
||||
'abc\rxyz\b\b=']
|
||||
expected_outputs = [u'x=z\u2029',
|
||||
u'foo\u2029bar\u2029',
|
||||
u'foo\u2029bar\u2029',
|
||||
'x=z']
|
||||
for i, text in enumerate(test_inputs):
|
||||
w._insert_plain_text(cursor, text)
|
||||
self.assert_text_equal(cursor, expected_outputs[i])
|
||||
# clear all the text
|
||||
cursor.insertText('')
|
||||
|
||||
def test_link_handling(self):
|
||||
noKeys = QtCore.Qt
|
||||
noButton = QtCore.Qt.MouseButton(0)
|
||||
noButtons = QtCore.Qt.MouseButtons(0)
|
||||
noModifiers = QtCore.Qt.KeyboardModifiers(0)
|
||||
MouseMove = QtCore.QEvent.MouseMove
|
||||
QMouseEvent = QtGui.QMouseEvent
|
||||
|
||||
w = ConsoleWidget()
|
||||
cursor = w._get_prompt_cursor()
|
||||
w._insert_html(cursor, '<a href="http://python.org">written in</a>')
|
||||
obj = w._control
|
||||
tip = QtWidgets.QToolTip
|
||||
self.assertEqual(tip.text(), u'')
|
||||
|
||||
# should be somewhere else
|
||||
elsewhereEvent = QMouseEvent(MouseMove, QtCore.QPoint(50,50),
|
||||
noButton, noButtons, noModifiers)
|
||||
w.eventFilter(obj, elsewhereEvent)
|
||||
self.assertEqual(tip.isVisible(), False)
|
||||
self.assertEqual(tip.text(), u'')
|
||||
# should be over text
|
||||
overTextEvent = QMouseEvent(MouseMove, QtCore.QPoint(1,5),
|
||||
noButton, noButtons, noModifiers)
|
||||
w.eventFilter(obj, overTextEvent)
|
||||
self.assertEqual(tip.isVisible(), True)
|
||||
self.assertEqual(tip.text(), "http://python.org")
|
||||
|
||||
# should still be over text
|
||||
stillOverTextEvent = QMouseEvent(MouseMove, QtCore.QPoint(1,5),
|
||||
noButton, noButtons, noModifiers)
|
||||
w.eventFilter(obj, stillOverTextEvent)
|
||||
self.assertEqual(tip.isVisible(), True)
|
||||
self.assertEqual(tip.text(), "http://python.org")
|
||||
|
||||
def test_width_height(self):
|
||||
# width()/height() QWidget properties should not be overridden.
|
||||
w = ConsoleWidget()
|
||||
self.assertEqual(w.width(), QtWidgets.QWidget.width(w))
|
||||
self.assertEqual(w.height(), QtWidgets.QWidget.height(w))
|
||||
|
||||
def test_prompt_cursors(self):
|
||||
"""Test the cursors that keep track of where the prompt begins and
|
||||
ends"""
|
||||
w = ConsoleWidget()
|
||||
w._prompt = 'prompt>'
|
||||
doc = w._control.document()
|
||||
|
||||
# Fill up the QTextEdit area with the maximum number of blocks
|
||||
doc.setMaximumBlockCount(10)
|
||||
for _ in range(9):
|
||||
w._append_plain_text('line\n')
|
||||
|
||||
# Draw the prompt, this should cause the first lines to be deleted
|
||||
w._show_prompt()
|
||||
self.assertEqual(doc.blockCount(), 10)
|
||||
|
||||
# _prompt_pos should be at the end of the document
|
||||
self.assertEqual(w._prompt_pos, w._get_end_pos())
|
||||
|
||||
# _append_before_prompt_pos should be at the beginning of the prompt
|
||||
self.assertEqual(w._append_before_prompt_pos,
|
||||
w._prompt_pos - len(w._prompt))
|
||||
|
||||
# insert some more text without drawing a new prompt
|
||||
w._append_plain_text('line\n')
|
||||
self.assertEqual(w._prompt_pos,
|
||||
w._get_end_pos() - len('line\n'))
|
||||
self.assertEqual(w._append_before_prompt_pos,
|
||||
w._prompt_pos - len(w._prompt))
|
||||
|
||||
# redraw the prompt
|
||||
w._show_prompt()
|
||||
self.assertEqual(w._prompt_pos, w._get_end_pos())
|
||||
self.assertEqual(w._append_before_prompt_pos,
|
||||
w._prompt_pos - len(w._prompt))
|
||||
|
||||
# insert some text before the prompt
|
||||
w._append_plain_text('line', before_prompt=True)
|
||||
self.assertEqual(w._prompt_pos, w._get_end_pos())
|
||||
self.assertEqual(w._append_before_prompt_pos,
|
||||
w._prompt_pos - len(w._prompt))
|
||||
|
||||
def test_select_all(self):
|
||||
w = ConsoleWidget()
|
||||
w._append_plain_text('Header\n')
|
||||
w._prompt = 'prompt>'
|
||||
w._show_prompt()
|
||||
control = w._control
|
||||
app = QtWidgets.QApplication.instance()
|
||||
|
||||
cursor = w._get_cursor()
|
||||
w._insert_plain_text_into_buffer(cursor, "if:\n pass")
|
||||
|
||||
cursor.clearSelection()
|
||||
control.setTextCursor(cursor)
|
||||
|
||||
# "select all" action selects cell first
|
||||
w.select_all_smart()
|
||||
QTest.keyClick(control, QtCore.Qt.Key_C, QtCore.Qt.ControlModifier)
|
||||
copied = app.clipboard().text()
|
||||
self.assertEqual(copied, 'if:\n> pass')
|
||||
|
||||
# # "select all" action triggered a second time selects whole document
|
||||
w.select_all_smart()
|
||||
QTest.keyClick(control, QtCore.Qt.Key_C, QtCore.Qt.ControlModifier)
|
||||
copied = app.clipboard().text()
|
||||
self.assertEqual(copied, 'Header\nprompt>if:\n> pass')
|
||||
|
||||
def test_keypresses(self):
|
||||
"""Test the event handling code for keypresses."""
|
||||
w = ConsoleWidget()
|
||||
w._append_plain_text('Header\n')
|
||||
w._prompt = 'prompt>'
|
||||
w._show_prompt()
|
||||
app = QtWidgets.QApplication.instance()
|
||||
control = w._control
|
||||
|
||||
# Test setting the input buffer
|
||||
w._set_input_buffer('test input')
|
||||
self.assertEqual(w._get_input_buffer(), 'test input')
|
||||
|
||||
# Ctrl+K kills input until EOL
|
||||
w._set_input_buffer('test input')
|
||||
c = control.textCursor()
|
||||
c.setPosition(c.position() - 3)
|
||||
control.setTextCursor(c)
|
||||
QTest.keyClick(control, QtCore.Qt.Key_K, QtCore.Qt.ControlModifier)
|
||||
self.assertEqual(w._get_input_buffer(), 'test in')
|
||||
|
||||
# Ctrl+V pastes
|
||||
w._set_input_buffer('test input ')
|
||||
app.clipboard().setText('pasted text')
|
||||
QTest.keyClick(control, QtCore.Qt.Key_V, QtCore.Qt.ControlModifier)
|
||||
self.assertEqual(w._get_input_buffer(), 'test input pasted text')
|
||||
self.assertEqual(control.document().blockCount(), 2)
|
||||
|
||||
# Paste should strip indentation
|
||||
w._set_input_buffer('test input ')
|
||||
app.clipboard().setText(' pasted text')
|
||||
QTest.keyClick(control, QtCore.Qt.Key_V, QtCore.Qt.ControlModifier)
|
||||
self.assertEqual(w._get_input_buffer(), 'test input pasted text')
|
||||
self.assertEqual(control.document().blockCount(), 2)
|
||||
|
||||
# Multiline paste, should also show continuation marks
|
||||
w._set_input_buffer('test input ')
|
||||
app.clipboard().setText('line1\nline2\nline3')
|
||||
QTest.keyClick(control, QtCore.Qt.Key_V, QtCore.Qt.ControlModifier)
|
||||
self.assertEqual(w._get_input_buffer(),
|
||||
'test input line1\nline2\nline3')
|
||||
self.assertEqual(control.document().blockCount(), 4)
|
||||
self.assertEqual(control.document().findBlockByNumber(1).text(),
|
||||
'prompt>test input line1')
|
||||
self.assertEqual(control.document().findBlockByNumber(2).text(),
|
||||
'> line2')
|
||||
self.assertEqual(control.document().findBlockByNumber(3).text(),
|
||||
'> line3')
|
||||
|
||||
# Multiline paste should strip indentation intelligently
|
||||
# in the case where pasted text has leading whitespace on first line
|
||||
# and we're pasting into indented position
|
||||
w._set_input_buffer(' ')
|
||||
app.clipboard().setText(' If 1:\n pass')
|
||||
QTest.keyClick(control, QtCore.Qt.Key_V, QtCore.Qt.ControlModifier)
|
||||
self.assertEqual(w._get_input_buffer(),
|
||||
' If 1:\n pass')
|
||||
|
||||
# Ctrl+Backspace should intelligently remove the last word
|
||||
w._set_input_buffer("foo = ['foo', 'foo', 'foo', \n"
|
||||
" 'bar', 'bar', 'bar']")
|
||||
QTest.keyClick(control, QtCore.Qt.Key_Backspace,
|
||||
QtCore.Qt.ControlModifier)
|
||||
self.assertEqual(w._get_input_buffer(),
|
||||
("foo = ['foo', 'foo', 'foo', \n"
|
||||
" 'bar', 'bar', '"))
|
||||
QTest.keyClick(control, QtCore.Qt.Key_Backspace,
|
||||
QtCore.Qt.ControlModifier)
|
||||
QTest.keyClick(control, QtCore.Qt.Key_Backspace,
|
||||
QtCore.Qt.ControlModifier)
|
||||
self.assertEqual(w._get_input_buffer(),
|
||||
("foo = ['foo', 'foo', 'foo', \n"
|
||||
" '"))
|
||||
QTest.keyClick(control, QtCore.Qt.Key_Backspace,
|
||||
QtCore.Qt.ControlModifier)
|
||||
self.assertEqual(w._get_input_buffer(),
|
||||
("foo = ['foo', 'foo', 'foo', \n"
|
||||
""))
|
||||
QTest.keyClick(control, QtCore.Qt.Key_Backspace,
|
||||
QtCore.Qt.ControlModifier)
|
||||
self.assertEqual(w._get_input_buffer(),
|
||||
"foo = ['foo', 'foo', 'foo',")
|
||||
|
||||
# Ctrl+Delete should intelligently remove the next word
|
||||
w._set_input_buffer("foo = ['foo', 'foo', 'foo', \n"
|
||||
" 'bar', 'bar', 'bar']")
|
||||
c = control.textCursor()
|
||||
c.setPosition(35)
|
||||
control.setTextCursor(c)
|
||||
QTest.keyClick(control, QtCore.Qt.Key_Delete,
|
||||
QtCore.Qt.ControlModifier)
|
||||
self.assertEqual(w._get_input_buffer(),
|
||||
("foo = ['foo', 'foo', ', \n"
|
||||
" 'bar', 'bar', 'bar']"))
|
||||
QTest.keyClick(control, QtCore.Qt.Key_Delete,
|
||||
QtCore.Qt.ControlModifier)
|
||||
self.assertEqual(w._get_input_buffer(),
|
||||
("foo = ['foo', 'foo', \n"
|
||||
" 'bar', 'bar', 'bar']"))
|
||||
QTest.keyClick(control, QtCore.Qt.Key_Delete,
|
||||
QtCore.Qt.ControlModifier)
|
||||
self.assertEqual(w._get_input_buffer(),
|
||||
"foo = ['foo', 'foo', 'bar', 'bar', 'bar']")
|
||||
w._set_input_buffer("foo = ['foo', 'foo', 'foo', \n"
|
||||
" 'bar', 'bar', 'bar']")
|
||||
c = control.textCursor()
|
||||
c.setPosition(48)
|
||||
control.setTextCursor(c)
|
||||
QTest.keyClick(control, QtCore.Qt.Key_Delete,
|
||||
QtCore.Qt.ControlModifier)
|
||||
self.assertEqual(w._get_input_buffer(),
|
||||
("foo = ['foo', 'foo', 'foo', \n"
|
||||
"'bar', 'bar', 'bar']"))
|
||||
|
||||
# Left and right keys should respect the continuation prompt
|
||||
w._set_input_buffer("line 1\n"
|
||||
"line 2\n"
|
||||
"line 3")
|
||||
c = control.textCursor()
|
||||
c.setPosition(20) # End of line 1
|
||||
control.setTextCursor(c)
|
||||
QTest.keyClick(control, QtCore.Qt.Key_Right)
|
||||
# Cursor should have moved after the continuation prompt
|
||||
self.assertEqual(control.textCursor().position(), 23)
|
||||
QTest.keyClick(control, QtCore.Qt.Key_Left)
|
||||
# Cursor should have moved to the end of the previous line
|
||||
self.assertEqual(control.textCursor().position(), 20)
|
||||
|
||||
# TODO: many more keybindings
|
||||
|
||||
def test_indent(self):
|
||||
"""Test the event handling code for indent/dedent keypresses ."""
|
||||
w = ConsoleWidget()
|
||||
w._append_plain_text('Header\n')
|
||||
w._prompt = 'prompt>'
|
||||
w._show_prompt()
|
||||
control = w._control
|
||||
|
||||
# TAB with multiline selection should block-indent
|
||||
w._set_input_buffer("")
|
||||
c = control.textCursor()
|
||||
pos=c.position()
|
||||
w._set_input_buffer("If 1:\n pass")
|
||||
c.setPosition(pos, QtGui.QTextCursor.KeepAnchor)
|
||||
control.setTextCursor(c)
|
||||
QTest.keyClick(control, QtCore.Qt.Key_Tab)
|
||||
self.assertEqual(w._get_input_buffer()," If 1:\n pass")
|
||||
|
||||
# TAB with multiline selection, should block-indent to next multiple
|
||||
# of 4 spaces, if first line has 0 < indent < 4
|
||||
w._set_input_buffer("")
|
||||
c = control.textCursor()
|
||||
pos=c.position()
|
||||
w._set_input_buffer(" If 2:\n pass")
|
||||
c.setPosition(pos, QtGui.QTextCursor.KeepAnchor)
|
||||
control.setTextCursor(c)
|
||||
QTest.keyClick(control, QtCore.Qt.Key_Tab)
|
||||
self.assertEqual(w._get_input_buffer()," If 2:\n pass")
|
||||
|
||||
# Shift-TAB with multiline selection should block-dedent
|
||||
w._set_input_buffer("")
|
||||
c = control.textCursor()
|
||||
pos=c.position()
|
||||
w._set_input_buffer(" If 3:\n pass")
|
||||
c.setPosition(pos, QtGui.QTextCursor.KeepAnchor)
|
||||
control.setTextCursor(c)
|
||||
QTest.keyClick(control, QtCore.Qt.Key_Backtab)
|
||||
self.assertEqual(w._get_input_buffer(),"If 3:\n pass")
|
||||
|
||||
def test_complete(self):
|
||||
class TestKernelClient(object):
|
||||
def is_complete(self, source):
|
||||
calls.append(source)
|
||||
return msg_id
|
||||
w = ConsoleWidget()
|
||||
cursor = w._get_prompt_cursor()
|
||||
w._execute = lambda *args: calls.append(args)
|
||||
w.kernel_client = TestKernelClient()
|
||||
msg_id = object()
|
||||
calls = []
|
||||
|
||||
# test incomplete statement (no _execute called, but indent added)
|
||||
w.execute("thing", interactive=True)
|
||||
self.assertEqual(calls, ["thing"])
|
||||
calls = []
|
||||
w._handle_is_complete_reply(
|
||||
dict(parent_header=dict(msg_id=msg_id),
|
||||
content=dict(status="incomplete", indent="!!!")))
|
||||
self.assert_text_equal(cursor, u"thing\u2029> !!!")
|
||||
self.assertEqual(calls, [])
|
||||
|
||||
# test complete statement (_execute called)
|
||||
msg_id = object()
|
||||
w.execute("else", interactive=True)
|
||||
self.assertEqual(calls, ["else"])
|
||||
calls = []
|
||||
w._handle_is_complete_reply(
|
||||
dict(parent_header=dict(msg_id=msg_id),
|
||||
content=dict(status="complete", indent="###")))
|
||||
self.assertEqual(calls, [("else", False)])
|
||||
calls = []
|
||||
self.assert_text_equal(cursor, u"thing\u2029> !!!else\u2029")
|
||||
|
||||
# test missing answer from is_complete
|
||||
msg_id = object()
|
||||
w.execute("done", interactive=True)
|
||||
self.assertEqual(calls, ["done"])
|
||||
calls = []
|
||||
self.assert_text_equal(cursor, u"thing\u2029> !!!else\u2029")
|
||||
w._trigger_is_complete_callback()
|
||||
self.assert_text_equal(cursor, u"thing\u2029> !!!else\u2029\u2029> ")
|
||||
|
||||
# assert that late answer isn't destroying anything
|
||||
w._handle_is_complete_reply(
|
||||
dict(parent_header=dict(msg_id=msg_id),
|
||||
content=dict(status="complete", indent="###")))
|
||||
self.assertEqual(calls, [])
|
||||
|
||||
def test_complete_python(self):
|
||||
"""Test that is_complete is working correctly for Python."""
|
||||
# Kernel client to test the responses of is_complete
|
||||
class TestIPyKernelClient(object):
|
||||
def is_complete(self, source):
|
||||
tm = TransformerManager()
|
||||
check_complete = tm.check_complete(source)
|
||||
responses.append(check_complete)
|
||||
|
||||
# Initialize widget
|
||||
responses = []
|
||||
w = ConsoleWidget()
|
||||
w._append_plain_text('Header\n')
|
||||
w._prompt = 'prompt>'
|
||||
w._show_prompt()
|
||||
w.kernel_client = TestIPyKernelClient()
|
||||
|
||||
# Execute incomplete statement inside a block
|
||||
code = '\n'.join(["if True:", " a = 1"])
|
||||
w._set_input_buffer(code)
|
||||
w.execute(interactive=True)
|
||||
assert responses == [('incomplete', 4)]
|
||||
|
||||
# Execute complete statement inside a block
|
||||
responses = []
|
||||
code = '\n'.join(["if True:", " a = 1\n\n"])
|
||||
w._set_input_buffer(code)
|
||||
w.execute(interactive=True)
|
||||
assert responses == [('complete', None)]
|
|
@ -0,0 +1,179 @@
|
|||
# Standard library imports
|
||||
import unittest
|
||||
|
||||
# Local imports
|
||||
from qtconsole.ansi_code_processor import AnsiCodeProcessor
|
||||
|
||||
|
||||
class TestAnsiCodeProcessor(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.processor = AnsiCodeProcessor()
|
||||
|
||||
def test_clear(self):
|
||||
""" Do control sequences for clearing the console work?
|
||||
"""
|
||||
string = '\x1b[2J\x1b[K'
|
||||
i = -1
|
||||
for i, substring in enumerate(self.processor.split_string(string)):
|
||||
if i == 0:
|
||||
self.assertEqual(len(self.processor.actions), 1)
|
||||
action = self.processor.actions[0]
|
||||
self.assertEqual(action.action, 'erase')
|
||||
self.assertEqual(action.area, 'screen')
|
||||
self.assertEqual(action.erase_to, 'all')
|
||||
elif i == 1:
|
||||
self.assertEqual(len(self.processor.actions), 1)
|
||||
action = self.processor.actions[0]
|
||||
self.assertEqual(action.action, 'erase')
|
||||
self.assertEqual(action.area, 'line')
|
||||
self.assertEqual(action.erase_to, 'end')
|
||||
else:
|
||||
self.fail('Too many substrings.')
|
||||
self.assertEqual(i, 1, 'Too few substrings.')
|
||||
|
||||
def test_colors(self):
|
||||
""" Do basic controls sequences for colors work?
|
||||
"""
|
||||
string = 'first\x1b[34mblue\x1b[0mlast'
|
||||
i = -1
|
||||
for i, substring in enumerate(self.processor.split_string(string)):
|
||||
if i == 0:
|
||||
self.assertEqual(substring, 'first')
|
||||
self.assertEqual(self.processor.foreground_color, None)
|
||||
elif i == 1:
|
||||
self.assertEqual(substring, 'blue')
|
||||
self.assertEqual(self.processor.foreground_color, 4)
|
||||
elif i == 2:
|
||||
self.assertEqual(substring, 'last')
|
||||
self.assertEqual(self.processor.foreground_color, None)
|
||||
else:
|
||||
self.fail('Too many substrings.')
|
||||
self.assertEqual(i, 2, 'Too few substrings.')
|
||||
|
||||
def test_colors_xterm(self):
|
||||
""" Do xterm-specific control sequences for colors work?
|
||||
"""
|
||||
string = '\x1b]4;20;rgb:ff/ff/ff\x1b' \
|
||||
'\x1b]4;25;rgbi:1.0/1.0/1.0\x1b'
|
||||
substrings = list(self.processor.split_string(string))
|
||||
desired = { 20 : (255, 255, 255),
|
||||
25 : (255, 255, 255) }
|
||||
self.assertEqual(self.processor.color_map, desired)
|
||||
|
||||
string = '\x1b[38;5;20m\x1b[48;5;25m'
|
||||
substrings = list(self.processor.split_string(string))
|
||||
self.assertEqual(self.processor.foreground_color, 20)
|
||||
self.assertEqual(self.processor.background_color, 25)
|
||||
|
||||
def test_true_color(self):
|
||||
"""Do 24bit True Color control sequences?
|
||||
"""
|
||||
string = '\x1b[38;2;255;100;0m\x1b[48;2;100;100;100m'
|
||||
substrings = list(self.processor.split_string(string))
|
||||
self.assertEqual(self.processor.foreground_color, [255, 100, 0])
|
||||
self.assertEqual(self.processor.background_color, [100, 100, 100])
|
||||
|
||||
def test_scroll(self):
|
||||
""" Do control sequences for scrolling the buffer work?
|
||||
"""
|
||||
string = '\x1b[5S\x1b[T'
|
||||
i = -1
|
||||
for i, substring in enumerate(self.processor.split_string(string)):
|
||||
if i == 0:
|
||||
self.assertEqual(len(self.processor.actions), 1)
|
||||
action = self.processor.actions[0]
|
||||
self.assertEqual(action.action, 'scroll')
|
||||
self.assertEqual(action.dir, 'up')
|
||||
self.assertEqual(action.unit, 'line')
|
||||
self.assertEqual(action.count, 5)
|
||||
elif i == 1:
|
||||
self.assertEqual(len(self.processor.actions), 1)
|
||||
action = self.processor.actions[0]
|
||||
self.assertEqual(action.action, 'scroll')
|
||||
self.assertEqual(action.dir, 'down')
|
||||
self.assertEqual(action.unit, 'line')
|
||||
self.assertEqual(action.count, 1)
|
||||
else:
|
||||
self.fail('Too many substrings.')
|
||||
self.assertEqual(i, 1, 'Too few substrings.')
|
||||
|
||||
def test_formfeed(self):
|
||||
""" Are formfeed characters processed correctly?
|
||||
"""
|
||||
string = '\f' # form feed
|
||||
self.assertEqual(list(self.processor.split_string(string)), [''])
|
||||
self.assertEqual(len(self.processor.actions), 1)
|
||||
action = self.processor.actions[0]
|
||||
self.assertEqual(action.action, 'scroll')
|
||||
self.assertEqual(action.dir, 'down')
|
||||
self.assertEqual(action.unit, 'page')
|
||||
self.assertEqual(action.count, 1)
|
||||
|
||||
def test_carriage_return(self):
|
||||
""" Are carriage return characters processed correctly?
|
||||
"""
|
||||
string = 'foo\rbar' # carriage return
|
||||
splits = []
|
||||
actions = []
|
||||
for split in self.processor.split_string(string):
|
||||
splits.append(split)
|
||||
actions.append([action.action for action in self.processor.actions])
|
||||
self.assertEqual(splits, ['foo', None, 'bar'])
|
||||
self.assertEqual(actions, [[], ['carriage-return'], []])
|
||||
|
||||
def test_carriage_return_newline(self):
|
||||
"""transform CRLF to LF"""
|
||||
string = 'foo\rbar\r\ncat\r\n\n' # carriage return and newline
|
||||
# only one CR action should occur, and '\r\n' should transform to '\n'
|
||||
splits = []
|
||||
actions = []
|
||||
for split in self.processor.split_string(string):
|
||||
splits.append(split)
|
||||
actions.append([action.action for action in self.processor.actions])
|
||||
self.assertEqual(splits, ['foo', None, 'bar', '\r\n', 'cat', '\r\n', '\n'])
|
||||
self.assertEqual(actions, [[], ['carriage-return'], [], ['newline'], [], ['newline'], ['newline']])
|
||||
|
||||
def test_beep(self):
|
||||
""" Are beep characters processed correctly?
|
||||
"""
|
||||
string = 'foo\abar' # bell
|
||||
splits = []
|
||||
actions = []
|
||||
for split in self.processor.split_string(string):
|
||||
splits.append(split)
|
||||
actions.append([action.action for action in self.processor.actions])
|
||||
self.assertEqual(splits, ['foo', None, 'bar'])
|
||||
self.assertEqual(actions, [[], ['beep'], []])
|
||||
|
||||
def test_backspace(self):
|
||||
""" Are backspace characters processed correctly?
|
||||
"""
|
||||
string = 'foo\bbar' # backspace
|
||||
splits = []
|
||||
actions = []
|
||||
for split in self.processor.split_string(string):
|
||||
splits.append(split)
|
||||
actions.append([action.action for action in self.processor.actions])
|
||||
self.assertEqual(splits, ['foo', None, 'bar'])
|
||||
self.assertEqual(actions, [[], ['backspace'], []])
|
||||
|
||||
def test_combined(self):
|
||||
""" Are CR and BS characters processed correctly in combination?
|
||||
|
||||
BS is treated as a change in print position, rather than a
|
||||
backwards character deletion. Therefore a BS at EOL is
|
||||
effectively ignored.
|
||||
"""
|
||||
string = 'abc\rdef\b' # CR and backspace
|
||||
splits = []
|
||||
actions = []
|
||||
for split in self.processor.split_string(string):
|
||||
splits.append(split)
|
||||
actions.append([action.action for action in self.processor.actions])
|
||||
self.assertEqual(splits, ['abc', None, 'def', None])
|
||||
self.assertEqual(actions, [[], ['carriage-return'], [], ['backspace']])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
31
venv/Lib/site-packages/qtconsole/tests/test_app.py
Normal file
31
venv/Lib/site-packages/qtconsole/tests/test_app.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
"""Test QtConsoleApp"""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import os
|
||||
import sys
|
||||
from subprocess import check_output
|
||||
|
||||
from jupyter_core import paths
|
||||
import pytest
|
||||
from traitlets.tests.utils import check_help_all_output
|
||||
|
||||
from . import no_display
|
||||
|
||||
|
||||
@pytest.mark.skipif(no_display, reason="Doesn't work without a display")
|
||||
def test_help_output():
|
||||
"""jupyter qtconsole --help-all works"""
|
||||
check_help_all_output('qtconsole')
|
||||
|
||||
|
||||
@pytest.mark.skipif(no_display, reason="Doesn't work without a display")
|
||||
@pytest.mark.skipif(os.environ.get('CI', None) is None,
|
||||
reason="Doesn't work outside of our CIs")
|
||||
def test_generate_config():
|
||||
"""jupyter qtconsole --generate-config"""
|
||||
config_dir = paths.jupyter_config_dir()
|
||||
check_output([sys.executable, '-m', 'qtconsole', '--generate-config'])
|
||||
assert os.path.isfile(os.path.join(config_dir,
|
||||
'jupyter_qtconsole_config.py'))
|
162
venv/Lib/site-packages/qtconsole/tests/test_comms.py
Normal file
162
venv/Lib/site-packages/qtconsole/tests/test_comms.py
Normal file
|
@ -0,0 +1,162 @@
|
|||
import time
|
||||
import sys
|
||||
|
||||
import unittest
|
||||
|
||||
from ipython_genutils.py3compat import PY3
|
||||
from jupyter_client.blocking.channels import Empty
|
||||
|
||||
from qtconsole.manager import QtKernelManager
|
||||
|
||||
PY2 = sys.version[0] == '2'
|
||||
if PY2:
|
||||
TimeoutError = RuntimeError
|
||||
|
||||
class Tests(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""Open a kernel."""
|
||||
self.kernel_manager = QtKernelManager()
|
||||
self.kernel_manager.start_kernel()
|
||||
self.kernel_client = self.kernel_manager.client()
|
||||
self.kernel_client.start_channels(shell=True, iopub=True)
|
||||
self.blocking_client = self.kernel_client.blocking_client()
|
||||
self.blocking_client.start_channels(shell=True, iopub=True)
|
||||
self.comm_manager = self.kernel_client.comm_manager
|
||||
|
||||
# Check if client is working
|
||||
self.blocking_client.execute('print(0)')
|
||||
try:
|
||||
self._get_next_msg()
|
||||
self._get_next_msg()
|
||||
except TimeoutError:
|
||||
# Maybe it works now?
|
||||
self.blocking_client.execute('print(0)')
|
||||
self._get_next_msg()
|
||||
self._get_next_msg()
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
"""Close the kernel."""
|
||||
if self.kernel_manager:
|
||||
self.kernel_manager.shutdown_kernel(now=True)
|
||||
if self.kernel_client:
|
||||
self.kernel_client.shutdown()
|
||||
|
||||
def _get_next_msg(self, timeout=10):
|
||||
# Get status messages
|
||||
timeout_time = time.time() + timeout
|
||||
msg_type = 'status'
|
||||
while msg_type == 'status':
|
||||
if timeout_time < time.time():
|
||||
raise TimeoutError
|
||||
try:
|
||||
msg = self.blocking_client.get_iopub_msg(timeout=3)
|
||||
msg_type = msg['header']['msg_type']
|
||||
except Empty:
|
||||
pass
|
||||
return msg
|
||||
|
||||
def test_kernel_to_frontend(self):
|
||||
"""Communicate from the kernel to the frontend."""
|
||||
comm_manager = self.comm_manager
|
||||
blocking_client = self.blocking_client
|
||||
|
||||
class DummyCommHandler():
|
||||
def __init__(self):
|
||||
comm_manager.register_target('test_api', self.comm_open)
|
||||
self.last_msg = None
|
||||
|
||||
def comm_open(self, comm, msg):
|
||||
comm.on_msg(self.comm_message)
|
||||
comm.on_close(self.comm_message)
|
||||
self.last_msg = msg['content']['data']
|
||||
self.comm = comm
|
||||
|
||||
def comm_message(self, msg):
|
||||
self.last_msg = msg['content']['data']
|
||||
|
||||
handler = DummyCommHandler()
|
||||
blocking_client.execute(
|
||||
"from ipykernel.comm import Comm\n"
|
||||
"comm = Comm(target_name='test_api', data='open')\n"
|
||||
"comm.send('message')\n"
|
||||
"comm.close('close')\n"
|
||||
"del comm\n"
|
||||
"print('Done')\n"
|
||||
)
|
||||
# Get input
|
||||
msg = self._get_next_msg()
|
||||
assert msg['header']['msg_type'] == 'execute_input'
|
||||
# Open comm
|
||||
msg = self._get_next_msg()
|
||||
assert msg['header']['msg_type'] == 'comm_open'
|
||||
comm_manager._dispatch(msg)
|
||||
assert handler.last_msg == 'open'
|
||||
assert handler.comm.comm_id == msg['content']['comm_id']
|
||||
# Get message
|
||||
msg = self._get_next_msg()
|
||||
assert msg['header']['msg_type'] == 'comm_msg'
|
||||
comm_manager._dispatch(msg)
|
||||
assert handler.last_msg == 'message'
|
||||
assert handler.comm.comm_id == msg['content']['comm_id']
|
||||
# Get close
|
||||
msg = self._get_next_msg()
|
||||
assert msg['header']['msg_type'] == 'comm_close'
|
||||
comm_manager._dispatch(msg)
|
||||
assert handler.last_msg == 'close'
|
||||
assert handler.comm.comm_id == msg['content']['comm_id']
|
||||
# Get close
|
||||
msg = self._get_next_msg()
|
||||
assert msg['header']['msg_type'] == 'stream'
|
||||
|
||||
def test_frontend_to_kernel(self):
|
||||
"""Communicate from the frontend to the kernel."""
|
||||
comm_manager = self.comm_manager
|
||||
blocking_client = self.blocking_client
|
||||
blocking_client.execute(
|
||||
"class DummyCommHandler():\n"
|
||||
" def __init__(self):\n"
|
||||
" get_ipython().kernel.comm_manager.register_target(\n"
|
||||
" 'test_api', self.comm_open)\n"
|
||||
" def comm_open(self, comm, msg):\n"
|
||||
" comm.on_msg(self.comm_message)\n"
|
||||
" comm.on_close(self.comm_message)\n"
|
||||
" print(msg['content']['data'])\n"
|
||||
" def comm_message(self, msg):\n"
|
||||
" print(msg['content']['data'])\n"
|
||||
"dummy = DummyCommHandler()\n"
|
||||
)
|
||||
# Get input
|
||||
msg = self._get_next_msg()
|
||||
assert msg['header']['msg_type'] == 'execute_input'
|
||||
# Open comm
|
||||
comm = comm_manager.new_comm('test_api', data='open')
|
||||
msg = self._get_next_msg()
|
||||
assert msg['header']['msg_type'] == 'stream'
|
||||
assert msg['content']['text'] == 'open\n'
|
||||
# Get message
|
||||
comm.send('message')
|
||||
msg = self._get_next_msg()
|
||||
assert msg['header']['msg_type'] == 'stream'
|
||||
assert msg['content']['text'] == 'message\n'
|
||||
# Get close
|
||||
comm.close('close')
|
||||
msg = self._get_next_msg()
|
||||
|
||||
# Received message has a header and parent header. The parent header has
|
||||
# the info about the close message type in Python 3
|
||||
if PY3:
|
||||
assert msg['parent_header']['msg_type'] == 'comm_close'
|
||||
assert msg['msg_type'] == 'stream'
|
||||
assert msg['content']['text'] == 'close\n'
|
||||
else:
|
||||
# For some reason ipykernel notifies me that it is closing,
|
||||
# even though I closed the comm
|
||||
assert msg['header']['msg_type'] == 'comm_close'
|
||||
assert comm.comm_id == msg['content']['comm_id']
|
||||
msg = self._get_next_msg()
|
||||
assert msg['header']['msg_type'] == 'stream'
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
|
@ -0,0 +1,98 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import tempfile
|
||||
import shutil
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
from qtpy import QtCore, QtWidgets
|
||||
from qtpy.QtTest import QTest
|
||||
from qtconsole.console_widget import ConsoleWidget
|
||||
from qtconsole.completion_widget import CompletionWidget
|
||||
from . import no_display
|
||||
|
||||
|
||||
class TemporaryDirectory(object):
|
||||
"""
|
||||
Context manager for tempfile.mkdtemp().
|
||||
This class is available in python +v3.2.
|
||||
See: https://gist.github.com/cpelley/10e2eeaf60dacc7956bb
|
||||
"""
|
||||
|
||||
def __enter__(self):
|
||||
self.dir_name = tempfile.mkdtemp()
|
||||
return self.dir_name
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
shutil.rmtree(self.dir_name)
|
||||
|
||||
|
||||
TemporaryDirectory = getattr(tempfile, 'TemporaryDirectory',
|
||||
TemporaryDirectory)
|
||||
|
||||
|
||||
@pytest.mark.skipif(no_display, reason="Doesn't work without a display")
|
||||
class TestCompletionWidget(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
""" Create the application for the test case.
|
||||
"""
|
||||
cls._app = QtWidgets.QApplication.instance()
|
||||
if cls._app is None:
|
||||
cls._app = QtWidgets.QApplication([])
|
||||
cls._app.setQuitOnLastWindowClosed(False)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
""" Exit the application.
|
||||
"""
|
||||
QtWidgets.QApplication.quit()
|
||||
|
||||
def setUp(self):
|
||||
""" Create the main widgets (ConsoleWidget)
|
||||
"""
|
||||
self.console = ConsoleWidget()
|
||||
self.text_edit = self.console._control
|
||||
|
||||
def test_droplist_completer_shows(self):
|
||||
w = CompletionWidget(self.console)
|
||||
w.show_items(self.text_edit.textCursor(), ["item1", "item2", "item3"])
|
||||
self.assertTrue(w.isVisible())
|
||||
|
||||
def test_droplist_completer_keyboard(self):
|
||||
w = CompletionWidget(self.console)
|
||||
w.show_items(self.text_edit.textCursor(), ["item1", "item2", "item3"])
|
||||
QTest.keyClick(w, QtCore.Qt.Key_PageDown)
|
||||
QTest.keyClick(w, QtCore.Qt.Key_Enter)
|
||||
self.assertEqual(self.text_edit.toPlainText(), "item3")
|
||||
|
||||
def test_droplist_completer_mousepick(self):
|
||||
leftButton = QtCore.Qt.LeftButton
|
||||
|
||||
w = CompletionWidget(self.console)
|
||||
w.show_items(self.text_edit.textCursor(), ["item1", "item2", "item3"])
|
||||
|
||||
QTest.mouseClick(w.viewport(), leftButton, pos=QtCore.QPoint(19, 8))
|
||||
QTest.mouseRelease(w.viewport(), leftButton, pos=QtCore.QPoint(19, 8))
|
||||
QTest.mouseDClick(w.viewport(), leftButton, pos=QtCore.QPoint(19, 8))
|
||||
|
||||
self.assertEqual(self.text_edit.toPlainText(), "item1")
|
||||
self.assertFalse(w.isVisible())
|
||||
|
||||
def test_common_path_complete(self):
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
items = [
|
||||
os.path.join(tmpdir, "common/common1/item1"),
|
||||
os.path.join(tmpdir, "common/common1/item2"),
|
||||
os.path.join(tmpdir, "common/common1/item3")]
|
||||
for item in items:
|
||||
os.makedirs(item)
|
||||
w = CompletionWidget(self.console)
|
||||
w.show_items(self.text_edit.textCursor(), items)
|
||||
self.assertEqual(w.currentItem().text(), '/item1')
|
||||
QTest.keyClick(w, QtCore.Qt.Key_Down)
|
||||
self.assertEqual(w.currentItem().text(), '/item2')
|
||||
QTest.keyClick(w, QtCore.Qt.Key_Down)
|
||||
self.assertEqual(w.currentItem().text(), '/item3')
|
|
@ -0,0 +1,96 @@
|
|||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
from qtpy import QtWidgets
|
||||
from qtconsole.frontend_widget import FrontendWidget
|
||||
from qtpy.QtTest import QTest
|
||||
from . import no_display
|
||||
|
||||
|
||||
@pytest.mark.skipif(no_display, reason="Doesn't work without a display")
|
||||
class TestFrontendWidget(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
""" Create the application for the test case.
|
||||
"""
|
||||
cls._app = QtWidgets.QApplication.instance()
|
||||
if cls._app is None:
|
||||
cls._app = QtWidgets.QApplication([])
|
||||
cls._app.setQuitOnLastWindowClosed(False)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
""" Exit the application.
|
||||
"""
|
||||
QtWidgets.QApplication.quit()
|
||||
|
||||
def test_transform_classic_prompt(self):
|
||||
""" Test detecting classic prompts.
|
||||
"""
|
||||
w = FrontendWidget(kind='rich')
|
||||
t = w._highlighter.transform_classic_prompt
|
||||
|
||||
# Base case
|
||||
self.assertEqual(t('>>> test'), 'test')
|
||||
self.assertEqual(t(' >>> test'), 'test')
|
||||
self.assertEqual(t('\t >>> test'), 'test')
|
||||
|
||||
# No prompt
|
||||
self.assertEqual(t(''), '')
|
||||
self.assertEqual(t('test'), 'test')
|
||||
|
||||
# Continuation prompt
|
||||
self.assertEqual(t('... test'), 'test')
|
||||
self.assertEqual(t(' ... test'), 'test')
|
||||
self.assertEqual(t(' ... test'), 'test')
|
||||
self.assertEqual(t('\t ... test'), 'test')
|
||||
|
||||
# Prompts that don't match the 'traditional' prompt
|
||||
self.assertEqual(t('>>>test'), '>>>test')
|
||||
self.assertEqual(t('>> test'), '>> test')
|
||||
self.assertEqual(t('...test'), '...test')
|
||||
self.assertEqual(t('.. test'), '.. test')
|
||||
|
||||
# Prefix indicating input from other clients
|
||||
self.assertEqual(t('[remote] >>> test'), 'test')
|
||||
|
||||
# Random other prefix
|
||||
self.assertEqual(t('[foo] >>> test'), '[foo] >>> test')
|
||||
|
||||
def test_transform_ipy_prompt(self):
|
||||
""" Test detecting IPython prompts.
|
||||
"""
|
||||
w = FrontendWidget(kind='rich')
|
||||
t = w._highlighter.transform_ipy_prompt
|
||||
|
||||
# In prompt
|
||||
self.assertEqual(t('In [1]: test'), 'test')
|
||||
self.assertEqual(t('In [2]: test'), 'test')
|
||||
self.assertEqual(t('In [10]: test'), 'test')
|
||||
self.assertEqual(t(' In [1]: test'), 'test')
|
||||
self.assertEqual(t('\t In [1]: test'), 'test')
|
||||
|
||||
# No prompt
|
||||
self.assertEqual(t(''), '')
|
||||
self.assertEqual(t('test'), 'test')
|
||||
|
||||
# Continuation prompt
|
||||
self.assertEqual(t(' ...: test'), 'test')
|
||||
self.assertEqual(t(' ...: test'), 'test')
|
||||
self.assertEqual(t(' ...: test'), 'test')
|
||||
self.assertEqual(t('\t ...: test'), 'test')
|
||||
|
||||
# Prompts that don't match the in-prompt
|
||||
self.assertEqual(t('In [1]:test'), 'In [1]:test')
|
||||
self.assertEqual(t('[1]: test'), '[1]: test')
|
||||
self.assertEqual(t('In: test'), 'In: test')
|
||||
self.assertEqual(t(': test'), ': test')
|
||||
self.assertEqual(t('...: test'), '...: test')
|
||||
|
||||
# Prefix indicating input from other clients
|
||||
self.assertEqual(t('[remote] In [1]: test'), 'test')
|
||||
|
||||
# Random other prefix
|
||||
self.assertEqual(t('[foo] In [1]: test'), '[foo] In [1]: test')
|
|
@ -0,0 +1,81 @@
|
|||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
from qtpy import QtWidgets
|
||||
from qtconsole.client import QtKernelClient
|
||||
from qtconsole.jupyter_widget import JupyterWidget
|
||||
from . import no_display
|
||||
from qtpy.QtTest import QTest
|
||||
|
||||
|
||||
@pytest.mark.skipif(no_display, reason="Doesn't work without a display")
|
||||
class TestJupyterWidget(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
""" Create the application for the test case.
|
||||
"""
|
||||
cls._app = QtWidgets.QApplication.instance()
|
||||
if cls._app is None:
|
||||
cls._app = QtWidgets.QApplication([])
|
||||
cls._app.setQuitOnLastWindowClosed(False)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
""" Exit the application.
|
||||
"""
|
||||
QtWidgets.QApplication.quit()
|
||||
|
||||
def test_stylesheet_changed(self):
|
||||
""" Test changing stylesheets.
|
||||
"""
|
||||
w = JupyterWidget(kind='rich')
|
||||
|
||||
# By default, the background is light. White text is rendered as black
|
||||
self.assertEqual(w._ansi_processor.get_color(15).name(), '#000000')
|
||||
|
||||
# Change to a dark colorscheme. White text is rendered as white
|
||||
w.syntax_style = 'monokai'
|
||||
self.assertEqual(w._ansi_processor.get_color(15).name(), '#ffffff')
|
||||
|
||||
def test_other_output(self):
|
||||
""" Test displaying output from other clients.
|
||||
"""
|
||||
w = JupyterWidget(kind='rich')
|
||||
w._append_plain_text('Header\n')
|
||||
w._show_interpreter_prompt(1)
|
||||
w.other_output_prefix = '[other] '
|
||||
w.syntax_style = 'default'
|
||||
control = w._control
|
||||
document = control.document()
|
||||
|
||||
msg = dict(
|
||||
execution_count=1,
|
||||
code='a = 1 + 1\nb = range(10)',
|
||||
)
|
||||
w._append_custom(w._insert_other_input, msg, before_prompt=True)
|
||||
|
||||
self.assertEqual(document.blockCount(), 6)
|
||||
self.assertEqual(document.toPlainText(), (
|
||||
u'Header\n'
|
||||
u'\n'
|
||||
u'[other] In [1]: a = 1 + 1\n'
|
||||
u' ...: b = range(10)\n'
|
||||
u'\n'
|
||||
u'In [2]: '
|
||||
))
|
||||
|
||||
# Check proper syntax highlighting
|
||||
self.assertEqual(document.toHtml(), (
|
||||
u'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">\n'
|
||||
u'<html><head><meta name="qrichtext" content="1" /><style type="text/css">\n'
|
||||
u'p, li { white-space: pre-wrap; }\n'
|
||||
u'</style></head><body style=" font-family:\'Monospace\'; font-size:9pt; font-weight:400; font-style:normal;">\n'
|
||||
u'<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Header</p>\n'
|
||||
u'<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>\n'
|
||||
u'<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" color:#000080;">[other] In [</span><span style=" font-weight:600; color:#000080;">1</span><span style=" color:#000080;">]:</span> a = 1 + 1</p>\n'
|
||||
u'<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" color:#000080;">\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0...:</span> b = range(10)</p>\n'
|
||||
u'<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>\n'
|
||||
u'<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" color:#000080;">In [</span><span style=" font-weight:600; color:#000080;">2</span><span style=" color:#000080;">]:</span> </p></body></html>'
|
||||
))
|
85
venv/Lib/site-packages/qtconsole/tests/test_kill_ring.py
Normal file
85
venv/Lib/site-packages/qtconsole/tests/test_kill_ring.py
Normal file
|
@ -0,0 +1,85 @@
|
|||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
from qtpy import QtGui, QtWidgets
|
||||
from qtconsole.kill_ring import KillRing, QtKillRing
|
||||
from . import no_display
|
||||
|
||||
|
||||
@pytest.mark.skipif(no_display, reason="Doesn't work without a display")
|
||||
class TestKillRing(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
""" Create the application for the test case.
|
||||
"""
|
||||
cls._app = QtWidgets.QApplication.instance()
|
||||
if cls._app is None:
|
||||
cls._app = QtWidgets.QApplication([])
|
||||
cls._app.setQuitOnLastWindowClosed(False)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
""" Exit the application.
|
||||
"""
|
||||
QtWidgets.QApplication.quit()
|
||||
|
||||
def test_generic(self):
|
||||
""" Does the generic kill ring work?
|
||||
"""
|
||||
ring = KillRing()
|
||||
self.assertTrue(ring.yank() is None)
|
||||
self.assertTrue(ring.rotate() is None)
|
||||
|
||||
ring.kill('foo')
|
||||
self.assertEqual(ring.yank(), 'foo')
|
||||
self.assertTrue(ring.rotate() is None)
|
||||
self.assertEqual(ring.yank(), 'foo')
|
||||
|
||||
ring.kill('bar')
|
||||
self.assertEqual(ring.yank(), 'bar')
|
||||
self.assertEqual(ring.rotate(), 'foo')
|
||||
|
||||
ring.clear()
|
||||
self.assertTrue(ring.yank() is None)
|
||||
self.assertTrue(ring.rotate() is None)
|
||||
|
||||
def test_qt_basic(self):
|
||||
""" Does the Qt kill ring work?
|
||||
"""
|
||||
text_edit = QtWidgets.QPlainTextEdit()
|
||||
ring = QtKillRing(text_edit)
|
||||
|
||||
ring.kill('foo')
|
||||
ring.kill('bar')
|
||||
ring.yank()
|
||||
ring.rotate()
|
||||
ring.yank()
|
||||
self.assertEqual(text_edit.toPlainText(), 'foobar')
|
||||
|
||||
text_edit.clear()
|
||||
ring.kill('baz')
|
||||
ring.yank()
|
||||
ring.rotate()
|
||||
ring.rotate()
|
||||
ring.rotate()
|
||||
self.assertEqual(text_edit.toPlainText(), 'foo')
|
||||
|
||||
def test_qt_cursor(self):
|
||||
""" Does the Qt kill ring maintain state with cursor movement?
|
||||
"""
|
||||
text_edit = QtWidgets.QPlainTextEdit()
|
||||
ring = QtKillRing(text_edit)
|
||||
|
||||
ring.kill('foo')
|
||||
ring.kill('bar')
|
||||
ring.yank()
|
||||
text_edit.moveCursor(QtGui.QTextCursor.Left)
|
||||
ring.rotate()
|
||||
self.assertEqual(text_edit.toPlainText(), 'bar')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import pytest
|
||||
pytest.main()
|
15
venv/Lib/site-packages/qtconsole/tests/test_styles.py
Normal file
15
venv/Lib/site-packages/qtconsole/tests/test_styles.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
import unittest
|
||||
|
||||
from qtconsole.styles import dark_color, dark_style
|
||||
|
||||
|
||||
class TestStyles(unittest.TestCase):
|
||||
def test_dark_color(self):
|
||||
self.assertTrue(dark_color('#000000')) # black
|
||||
self.assertTrue(not dark_color('#ffff66')) # bright yellow
|
||||
self.assertTrue(dark_color('#80807f')) # < 50% gray
|
||||
self.assertTrue(not dark_color('#808080')) # = 50% gray
|
||||
|
||||
def test_dark_style(self):
|
||||
self.assertTrue(dark_style('monokai'))
|
||||
self.assertTrue(not dark_style('default'))
|
194
venv/Lib/site-packages/qtconsole/usage.py
Normal file
194
venv/Lib/site-packages/qtconsole/usage.py
Normal file
|
@ -0,0 +1,194 @@
|
|||
"""
|
||||
Usage information for QtConsole
|
||||
"""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
|
||||
gui_reference = """\
|
||||
=====================
|
||||
The Jupyter QtConsole
|
||||
=====================
|
||||
|
||||
This console is designed to emulate the look, feel and workflow of a terminal
|
||||
environment. Beyond this basic design, the console also implements
|
||||
functionality not currently found in most terminal emulators. Some examples of
|
||||
these console enhancements are inline syntax highlighting, multiline editing,
|
||||
inline graphics, and others.
|
||||
|
||||
This quick reference document contains the basic information you'll need to
|
||||
know to make the most efficient use of it. For the various command line
|
||||
options available at startup, type ``jupyter qtconsole --help`` at the command
|
||||
line.
|
||||
|
||||
|
||||
Multiline editing
|
||||
=================
|
||||
|
||||
The graphical console is capable of true multiline editing, but it also tries
|
||||
to behave intuitively like a terminal when possible. If you are used to
|
||||
IPython's old terminal behavior, you should find the transition painless. If
|
||||
you learn to use a few basic keybindings, the console provides even greater
|
||||
efficiency.
|
||||
|
||||
For single expressions or indented blocks, the console behaves almost like the
|
||||
IPython terminal: single expressions are immediately evaluated, and indented
|
||||
blocks are evaluated once a single blank line is entered::
|
||||
|
||||
In [1]: print ("Hello Jupyter!") # Enter was pressed at the end of the line
|
||||
Hello Jupyter!
|
||||
|
||||
In [2]: for num in range(10):
|
||||
...: print(num)
|
||||
...:
|
||||
0 1 2 3 4 5 6 7 8 9
|
||||
|
||||
If you want to enter more than one expression in a single input block
|
||||
(something not possible in the terminal), you can use ``Control-Enter`` at the
|
||||
end of your first line instead of ``Enter``. At that point the console goes
|
||||
into 'cell mode' and even if your inputs are not indented, it will continue
|
||||
accepting lines until either you enter an extra blank line or
|
||||
you hit ``Shift-Enter`` (the key binding that forces execution). When a
|
||||
multiline cell is entered, the console analyzes it and executes its code producing
|
||||
an ``Out[n]`` prompt only for the last expression in it, while the rest of the
|
||||
cell is executed as if it was a script. An example should clarify this::
|
||||
|
||||
In [3]: x=1 # Hit Ctrl-Enter here
|
||||
...: y=2 # from now on, regular Enter is sufficient
|
||||
...: z=3
|
||||
...: x**2 # This does *not* produce an Out[] value
|
||||
...: x+y+z # Only the last expression does
|
||||
...:
|
||||
Out[3]: 6
|
||||
|
||||
The behavior where an extra blank line forces execution is only active if you
|
||||
are actually typing at the keyboard each line, and is meant to make it mimic
|
||||
the IPython terminal behavior. If you paste a long chunk of input (for example
|
||||
a long script copied form an editor or web browser), it can contain arbitrarily
|
||||
many intermediate blank lines and they won't cause any problems. As always,
|
||||
you can then make it execute by appending a blank line *at the end* or hitting
|
||||
``Shift-Enter`` anywhere within the cell.
|
||||
|
||||
With the up arrow key, you can retrieve previous blocks of input that contain
|
||||
multiple lines. You can move inside of a multiline cell like you would in any
|
||||
text editor. When you want it executed, the simplest thing to do is to hit the
|
||||
force execution key, ``Shift-Enter`` (though you can also navigate to the end
|
||||
and append a blank line by using ``Enter`` twice).
|
||||
|
||||
If you are editing a multiline cell and accidentally navigate out of it using the
|
||||
up or down arrow keys, the console clears the cell and replaces it with the
|
||||
contents of the cell which the up or down arrow key stopped on. If you wish to
|
||||
to undo this action, perhaps because of an accidental keypress, use the Undo
|
||||
keybinding, ``Control-z``, to restore the original cell.
|
||||
|
||||
|
||||
Key bindings
|
||||
============
|
||||
|
||||
The Jupyter QtConsole supports most of the basic Emacs line-oriented keybindings,
|
||||
in addition to some of its own.
|
||||
|
||||
The keybindings themselves are:
|
||||
|
||||
- ``Enter``: insert new line (may cause execution, see above).
|
||||
- ``Ctrl-Enter``: *force* new line, *never* causes execution.
|
||||
- ``Shift-Enter``: *force* execution regardless of where cursor is, no newline added.
|
||||
- ``Up``: step backwards through the history.
|
||||
- ``Down``: step forwards through the history.
|
||||
- ``Shift-Up``: search backwards through the history (like ``Control-r`` in bash).
|
||||
- ``Shift-Down``: search forwards through the history.
|
||||
- ``Control-c``: copy highlighted text to clipboard (prompts are automatically stripped).
|
||||
- ``Control-Shift-c``: copy highlighted text to clipboard (prompts are not stripped).
|
||||
- ``Control-v``: paste text from clipboard.
|
||||
- ``Control-z``: undo (retrieves lost text if you move out of a cell with the arrows).
|
||||
- ``Control-Shift-z``: redo.
|
||||
- ``Control-o``: move to 'other' area, between pager and terminal.
|
||||
- ``Control-l``: clear terminal.
|
||||
- ``Control-a``: go to beginning of line.
|
||||
- ``Control-e``: go to end of line.
|
||||
- ``Control-u``: kill from cursor to the begining of the line.
|
||||
- ``Control-k``: kill from cursor to the end of the line.
|
||||
- ``Control-y``: yank (paste)
|
||||
- ``Control-p``: previous line (like up arrow)
|
||||
- ``Control-n``: next line (like down arrow)
|
||||
- ``Control-f``: forward (like right arrow)
|
||||
- ``Control-b``: back (like left arrow)
|
||||
- ``Control-d``: delete next character, or exits if input is empty
|
||||
- ``Alt-<``: move to the beginning of the input region.
|
||||
- ``alt->``: move to the end of the input region.
|
||||
- ``Alt-d``: delete next word.
|
||||
- ``Alt-Backspace``: delete previous word.
|
||||
- ``Control-.``: force a kernel restart (a confirmation dialog appears).
|
||||
- ``Control-+``: increase font size.
|
||||
- ``Control--``: decrease font size.
|
||||
- ``Control-Alt-Space``: toggle full screen. (Command-Control-Space on Mac OS X)
|
||||
|
||||
The pager
|
||||
=========
|
||||
|
||||
The Jupyter QtConsole will show long blocks of text from many sources using a
|
||||
built-in pager. You can control where this pager appears with the ``--paging``
|
||||
command-line flag:
|
||||
|
||||
- ``inside`` [default]: the pager is overlaid on top of the main terminal. You
|
||||
must quit the pager to get back to the terminal (similar to how a pager such
|
||||
as ``less`` or ``more`` pagers behave).
|
||||
|
||||
- ``vsplit``: the console is made double height, and the pager appears on the
|
||||
bottom area when needed. You can view its contents while using the terminal.
|
||||
|
||||
- ``hsplit``: the console is made double width, and the pager appears on the
|
||||
right area when needed. You can view its contents while using the terminal.
|
||||
|
||||
- ``none``: the console displays output without paging.
|
||||
|
||||
If you use the vertical or horizontal paging modes, you can navigate between
|
||||
terminal and pager as follows:
|
||||
|
||||
- Tab key: goes from pager to terminal (but not the other way around).
|
||||
- Control-o: goes from one to another always.
|
||||
- Mouse: click on either.
|
||||
|
||||
In all cases, the ``q`` or ``Escape`` keys quit the pager (when used with the
|
||||
focus on the pager area).
|
||||
|
||||
Running subprocesses
|
||||
====================
|
||||
|
||||
When running a subprocess from the kernel, you can not interact with it as if
|
||||
it was running in a terminal. So anything that invokes a pager or expects
|
||||
you to type input into it will block and hang (you can kill it with ``Control-C``).
|
||||
|
||||
The console can use magics provided by the IPython kernel. These magics include
|
||||
``%less`` to page files (aliased to ``%more``),
|
||||
``%clear`` to clear the terminal, and ``%man`` on Linux/OSX. These cover the
|
||||
most common commands you'd want to call in your subshell and that would cause
|
||||
problems if invoked via ``!cmd``, but you need to be aware of this limitation.
|
||||
|
||||
Display
|
||||
=======
|
||||
|
||||
For example, if using the IPython kernel, there are functions available for
|
||||
object display:
|
||||
|
||||
|
||||
In [4]: from IPython.display import display
|
||||
|
||||
In [5]: from IPython.display import display_png, display_svg
|
||||
|
||||
Python objects can simply be passed to these functions and the appropriate
|
||||
representations will be displayed in the console as long as the objects know
|
||||
how to compute those representations. The easiest way of teaching objects how
|
||||
to format themselves in various representations is to define special methods
|
||||
such as: ``_repr_svg_`` and ``_repr_png_``. IPython's display formatters
|
||||
can also be given custom formatter functions for various types::
|
||||
|
||||
In [6]: ip = get_ipython()
|
||||
|
||||
In [7]: png_formatter = ip.display_formatter.formatters['image/png']
|
||||
|
||||
In [8]: png_formatter.for_type(Foo, foo_to_png)
|
||||
|
||||
For further details, see ``IPython.core.formatters``.
|
||||
"""
|
109
venv/Lib/site-packages/qtconsole/util.py
Normal file
109
venv/Lib/site-packages/qtconsole/util.py
Normal file
|
@ -0,0 +1,109 @@
|
|||
""" Defines miscellaneous Qt-related helper classes and functions.
|
||||
"""
|
||||
|
||||
import inspect
|
||||
|
||||
from qtpy import QtCore, QtGui
|
||||
|
||||
from ipython_genutils.py3compat import iteritems
|
||||
from traitlets import HasTraits, TraitType
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Metaclasses
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
MetaHasTraits = type(HasTraits)
|
||||
MetaQObject = type(QtCore.QObject)
|
||||
|
||||
class MetaQObjectHasTraits(MetaQObject, MetaHasTraits):
|
||||
""" A metaclass that inherits from the metaclasses of HasTraits and QObject.
|
||||
|
||||
Using this metaclass allows a class to inherit from both HasTraits and
|
||||
QObject. Using SuperQObject instead of QObject is highly recommended. See
|
||||
QtKernelManager for an example.
|
||||
"""
|
||||
def __new__(mcls, name, bases, classdict):
|
||||
# FIXME: this duplicates the code from MetaHasTraits.
|
||||
# I don't think a super() call will help me here.
|
||||
for k,v in iteritems(classdict):
|
||||
if isinstance(v, TraitType):
|
||||
v.name = k
|
||||
elif inspect.isclass(v):
|
||||
if issubclass(v, TraitType):
|
||||
vinst = v()
|
||||
vinst.name = k
|
||||
classdict[k] = vinst
|
||||
cls = MetaQObject.__new__(mcls, name, bases, classdict)
|
||||
return cls
|
||||
|
||||
def __init__(mcls, name, bases, classdict):
|
||||
# Note: super() did not work, so we explicitly call these.
|
||||
MetaQObject.__init__(mcls, name, bases, classdict)
|
||||
MetaHasTraits.__init__(mcls, name, bases, classdict)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Classes
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def superQ(QClass):
|
||||
""" Permits the use of super() in class hierarchies that contain Qt classes.
|
||||
|
||||
Unlike QObject, SuperQObject does not accept a QObject parent. If it did,
|
||||
super could not be emulated properly (all other classes in the heierarchy
|
||||
would have to accept the parent argument--they don't, of course, because
|
||||
they don't inherit QObject.)
|
||||
|
||||
This class is primarily useful for attaching signals to existing non-Qt
|
||||
classes. See QtKernelManagerMixin for an example.
|
||||
"""
|
||||
class SuperQClass(QClass):
|
||||
|
||||
def __new__(cls, *args, **kw):
|
||||
# We initialize QClass as early as possible. Without this, Qt complains
|
||||
# if SuperQClass is not the first class in the super class list.
|
||||
inst = QClass.__new__(cls)
|
||||
QClass.__init__(inst)
|
||||
return inst
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
# Emulate super by calling the next method in the MRO, if there is one.
|
||||
mro = self.__class__.mro()
|
||||
for qt_class in QClass.mro():
|
||||
mro.remove(qt_class)
|
||||
next_index = mro.index(SuperQClass) + 1
|
||||
if next_index < len(mro):
|
||||
mro[next_index].__init__(self, *args, **kw)
|
||||
|
||||
return SuperQClass
|
||||
|
||||
SuperQObject = superQ(QtCore.QObject)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Functions
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def get_font(family, fallback=None):
|
||||
"""Return a font of the requested family, using fallback as alternative.
|
||||
|
||||
If a fallback is provided, it is used in case the requested family isn't
|
||||
found. If no fallback is given, no alternative is chosen and Qt's internal
|
||||
algorithms may automatically choose a fallback font.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
family : str
|
||||
A font name.
|
||||
fallback : str
|
||||
A font name.
|
||||
|
||||
Returns
|
||||
-------
|
||||
font : QFont object
|
||||
"""
|
||||
font = QtGui.QFont(family)
|
||||
# Check whether we got what we wanted using QFontInfo, since exactMatch()
|
||||
# is overly strict and returns false in too many cases.
|
||||
font_info = QtGui.QFontInfo(font)
|
||||
if fallback is not None and font_info.family() != family:
|
||||
font = QtGui.QFont(fallback)
|
||||
return font
|
Loading…
Add table
Add a link
Reference in a new issue