File: C:/Users/fred/anaconda3/Lib/site-packages/spyder/plugins/console/widgets/shell.py
# -*- coding: utf-8 -*-
#
# Copyright © Spyder Project Contributors
# Licensed under the terms of the MIT License
# (see spyder/__init__.py for details)
"""Shell widgets: base, python and terminal"""
# pylint: disable=C0103
# pylint: disable=R0903
# pylint: disable=R0911
# pylint: disable=R0201
# Standard library imports
import builtins
import keyword
import locale
import os
import os.path as osp
import re
import sys
import time
# Third party imports
from qtpy.compat import getsavefilename
from qtpy.QtCore import Property, Qt, QTimer, Signal, Slot
from qtpy.QtGui import QKeySequence, QTextCharFormat, QTextCursor
from qtpy.QtWidgets import QApplication, QMenu
# Local import
from spyder.config.base import _, get_conf_path, get_debug_level, STDERR
from spyder.config.manager import CONF
from spyder.py3compat import is_string, is_text_string, to_text_string
from spyder.utils import encoding
from spyder.utils.icon_manager import ima
from spyder.utils.qthelpers import (add_actions, create_action, keybinding,
restore_keyevent)
from spyder.widgets.mixins import (GetHelpMixin, SaveHistoryMixin,
TracebackLinksMixin, BrowseHistoryMixin)
from spyder.plugins.console.widgets.console import ConsoleBaseWidget
# Maximum number of lines to load
MAX_LINES = 1000
class ShellBaseWidget(ConsoleBaseWidget, SaveHistoryMixin,
BrowseHistoryMixin):
"""
Shell base widget
"""
sig_redirect_stdio_requested = Signal(bool)
sig_keyboard_interrupt = Signal()
execute = Signal(str)
sig_append_to_history_requested = Signal(str, str)
def __init__(self, parent, history_filename, profile=False,
initial_message=None, default_foreground_color=None,
error_foreground_color=None, traceback_foreground_color=None,
prompt_foreground_color=None, background_color=None):
"""
parent : specifies the parent widget
"""
ConsoleBaseWidget.__init__(self, parent)
SaveHistoryMixin.__init__(self, history_filename)
BrowseHistoryMixin.__init__(self)
# Prompt position: tuple (line, index)
self.current_prompt_pos = None
self.new_input_line = True
# History
assert is_text_string(history_filename)
self.history = self.load_history()
# Session
self.historylog_filename = CONF.get('main', 'historylog_filename',
get_conf_path('history.log'))
# Context menu
self.menu = None
self.setup_context_menu()
# Simple profiling test
self.profile = profile
# Buffer to increase performance of write/flush operations
self.__buffer = []
if initial_message:
self.__buffer.append(initial_message)
self.__timestamp = 0.0
self.__flushtimer = QTimer(self)
self.__flushtimer.setSingleShot(True)
self.__flushtimer.timeout.connect(self.flush)
# Give focus to widget
self.setFocus()
# Cursor width
self.setCursorWidth(CONF.get('main', 'cursor/width'))
# Adjustments to completion_widget to use it here
self.completion_widget.currentRowChanged.disconnect()
def toggle_wrap_mode(self, enable):
"""Enable/disable wrap mode"""
self.set_wrap_mode('character' if enable else None)
def set_font(self, font):
"""Set shell styles font"""
self.setFont(font)
self.set_pythonshell_font(font)
cursor = self.textCursor()
cursor.select(QTextCursor.Document)
charformat = QTextCharFormat()
charformat.setFontFamily(font.family())
charformat.setFontPointSize(font.pointSize())
cursor.mergeCharFormat(charformat)
#------ Context menu
def setup_context_menu(self):
"""Setup shell context menu"""
self.menu = QMenu(self)
self.cut_action = create_action(self, _("Cut"),
shortcut=keybinding('Cut'),
icon=ima.icon('editcut'),
triggered=self.cut)
self.copy_action = create_action(self, _("Copy"),
shortcut=keybinding('Copy'),
icon=ima.icon('editcopy'),
triggered=self.copy)
paste_action = create_action(self, _("Paste"),
shortcut=keybinding('Paste'),
icon=ima.icon('editpaste'),
triggered=self.paste)
save_action = create_action(self, _("Save history log..."),
icon=ima.icon('filesave'),
tip=_("Save current history log (i.e. all "
"inputs and outputs) in a text file"),
triggered=self.save_historylog)
self.delete_action = create_action(self, _("Delete"),
shortcut=keybinding('Delete'),
icon=ima.icon('editdelete'),
triggered=self.delete)
selectall_action = create_action(self, _("Select All"),
shortcut=keybinding('SelectAll'),
icon=ima.icon('selectall'),
triggered=self.selectAll)
add_actions(self.menu, (self.cut_action, self.copy_action,
paste_action, self.delete_action, None,
selectall_action, None, save_action) )
def contextMenuEvent(self, event):
"""Reimplement Qt method"""
state = self.has_selected_text()
self.copy_action.setEnabled(state)
self.cut_action.setEnabled(state)
self.delete_action.setEnabled(state)
self.menu.popup(event.globalPos())
event.accept()
#------ Input buffer
def get_current_line_from_cursor(self):
return self.get_text('cursor', 'eof')
def _select_input(self):
"""Select current line (without selecting console prompt)"""
line, index = self.get_position('eof')
if self.current_prompt_pos is None:
pline, pindex = line, index
else:
pline, pindex = self.current_prompt_pos
self.setSelection(pline, pindex, line, index)
@Slot()
def clear_terminal(self):
"""
Clear terminal window
Child classes reimplement this method to write prompt
"""
self.clear()
# The buffer being edited
def _set_input_buffer(self, text):
"""Set input buffer"""
if self.current_prompt_pos is not None:
self.replace_text(self.current_prompt_pos, 'eol', text)
else:
self.insert(text)
self.set_cursor_position('eof')
def _get_input_buffer(self):
"""Return input buffer"""
input_buffer = ''
if self.current_prompt_pos is not None:
input_buffer = self.get_text(self.current_prompt_pos, 'eol')
input_buffer = input_buffer.replace(os.linesep, '\n')
return input_buffer
input_buffer = Property("QString", _get_input_buffer, _set_input_buffer)
#------ Prompt
def new_prompt(self, prompt):
"""
Print a new prompt and save its (line, index) position
"""
if self.get_cursor_line_column()[1] != 0:
self.write('\n')
self.write(prompt, prompt=True)
# now we update our cursor giving end of prompt
self.current_prompt_pos = self.get_position('cursor')
self.ensureCursorVisible()
self.new_input_line = False
def check_selection(self):
"""
Check if selected text is r/w,
otherwise remove read-only parts of selection
"""
if self.current_prompt_pos is None:
self.set_cursor_position('eof')
else:
self.truncate_selection(self.current_prompt_pos)
#------ Copy / Keyboard interrupt
@Slot()
def copy(self):
"""Copy text to clipboard... or keyboard interrupt"""
if self.has_selected_text():
ConsoleBaseWidget.copy(self)
elif not sys.platform == 'darwin':
self.interrupt()
def interrupt(self):
"""Keyboard interrupt"""
self.sig_keyboard_interrupt.emit()
@Slot()
def cut(self):
"""Cut text"""
self.check_selection()
if self.has_selected_text():
ConsoleBaseWidget.cut(self)
@Slot()
def delete(self):
"""Remove selected text"""
self.check_selection()
if self.has_selected_text():
ConsoleBaseWidget.remove_selected_text(self)
@Slot()
def save_historylog(self):
"""Save current history log (all text in console)"""
title = _("Save history log")
self.sig_redirect_stdio_requested.emit(False)
filename, _selfilter = getsavefilename(self, title,
self.historylog_filename, "%s (*.log)" % _("History logs"))
self.sig_redirect_stdio_requested.emit(True)
if filename:
filename = osp.normpath(filename)
try:
encoding.write(to_text_string(self.get_text_with_eol()),
filename)
self.historylog_filename = filename
CONF.set('main', 'historylog_filename', filename)
except EnvironmentError:
pass
#------ Basic keypress event handler
def on_enter(self, command):
"""on_enter"""
self.execute_command(command)
def execute_command(self, command):
self.execute.emit(command)
self.add_to_history(command)
self.new_input_line = True
def on_new_line(self):
"""On new input line"""
self.set_cursor_position('eof')
self.current_prompt_pos = self.get_position('cursor')
self.new_input_line = False
@Slot()
def paste(self):
"""Reimplemented slot to handle multiline paste action"""
if self.new_input_line:
self.on_new_line()
ConsoleBaseWidget.paste(self)
def keyPressEvent(self, event):
"""
Reimplement Qt Method
Basic keypress event handler
(reimplemented in InternalShell to add more sophisticated features)
"""
if self.preprocess_keyevent(event):
# Event was accepted in self.preprocess_keyevent
return
self.postprocess_keyevent(event)
def preprocess_keyevent(self, event):
"""Pre-process keypress event:
return True if event is accepted, false otherwise"""
# Copy must be done first to be able to copy read-only text parts
# (otherwise, right below, we would remove selection
# if not on current line)
ctrl = event.modifiers() & Qt.ControlModifier
meta = event.modifiers() & Qt.MetaModifier # meta=ctrl in OSX
if event.key() == Qt.Key_C and \
((Qt.MetaModifier | Qt.ControlModifier) & event.modifiers()):
if meta and sys.platform == 'darwin':
self.interrupt()
elif ctrl:
self.copy()
event.accept()
return True
if self.new_input_line and ( len(event.text()) or event.key() in \
(Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right) ):
self.on_new_line()
return False
def postprocess_keyevent(self, event):
"""Post-process keypress event:
in InternalShell, this is method is called when shell is ready"""
event, text, key, ctrl, shift = restore_keyevent(event)
# Is cursor on the last line? and after prompt?
if len(text):
#XXX: Shouldn't it be: `if len(unicode(text).strip(os.linesep))` ?
if self.has_selected_text():
self.check_selection()
self.restrict_cursor_position(self.current_prompt_pos, 'eof')
cursor_position = self.get_position('cursor')
if key in (Qt.Key_Return, Qt.Key_Enter):
if self.is_cursor_on_last_line():
self._key_enter()
# add and run selection
else:
self.insert_text(self.get_selected_text(), at_end=True)
elif key == Qt.Key_Insert and not shift and not ctrl:
self.setOverwriteMode(not self.overwriteMode())
elif key == Qt.Key_Delete:
if self.has_selected_text():
self.check_selection()
self.remove_selected_text()
elif self.is_cursor_on_last_line():
self.stdkey_clear()
elif key == Qt.Key_Backspace:
self._key_backspace(cursor_position)
elif key == Qt.Key_Tab:
self._key_tab()
elif key == Qt.Key_Space and ctrl:
self._key_ctrl_space()
elif key == Qt.Key_Left:
if self.current_prompt_pos == cursor_position:
# Avoid moving cursor on prompt
return
method = self.extend_selection_to_next if shift \
else self.move_cursor_to_next
method('word' if ctrl else 'character', direction='left')
elif key == Qt.Key_Right:
if self.is_cursor_at_end():
return
method = self.extend_selection_to_next if shift \
else self.move_cursor_to_next
method('word' if ctrl else 'character', direction='right')
elif (key == Qt.Key_Home) or ((key == Qt.Key_Up) and ctrl):
self._key_home(shift, ctrl)
elif (key == Qt.Key_End) or ((key == Qt.Key_Down) and ctrl):
self._key_end(shift, ctrl)
elif key == Qt.Key_Up:
if not self.is_cursor_on_last_line():
self.set_cursor_position('eof')
y_cursor = self.get_coordinates(cursor_position)[1]
y_prompt = self.get_coordinates(self.current_prompt_pos)[1]
if y_cursor > y_prompt:
self.stdkey_up(shift)
else:
self.browse_history(backward=True)
elif key == Qt.Key_Down:
if not self.is_cursor_on_last_line():
self.set_cursor_position('eof')
y_cursor = self.get_coordinates(cursor_position)[1]
y_end = self.get_coordinates('eol')[1]
if y_cursor < y_end:
self.stdkey_down(shift)
else:
self.browse_history(backward=False)
elif key in (Qt.Key_PageUp, Qt.Key_PageDown):
#XXX: Find a way to do this programmatically instead of calling
# widget keyhandler (this won't work if the *event* is coming from
# the event queue - i.e. if the busy buffer is ever implemented)
ConsoleBaseWidget.keyPressEvent(self, event)
elif key == Qt.Key_Escape and shift:
self.clear_line()
elif key == Qt.Key_Escape:
self._key_escape()
elif key == Qt.Key_L and ctrl:
self.clear_terminal()
elif key == Qt.Key_V and ctrl:
self.paste()
elif key == Qt.Key_X and ctrl:
self.cut()
elif key == Qt.Key_Z and ctrl:
self.undo()
elif key == Qt.Key_Y and ctrl:
self.redo()
elif key == Qt.Key_A and ctrl:
self.selectAll()
elif key == Qt.Key_Question and not self.has_selected_text():
self._key_question(text)
elif key == Qt.Key_ParenLeft and not self.has_selected_text():
self._key_parenleft(text)
elif key == Qt.Key_Period and not self.has_selected_text():
self._key_period(text)
elif len(text) and not self.isReadOnly():
self.hist_wholeline = False
self.insert_text(text)
self._key_other(text)
else:
# Let the parent widget handle the key press event
ConsoleBaseWidget.keyPressEvent(self, event)
#------ Key handlers
def _key_enter(self):
command = self.input_buffer
self.insert_text('\n', at_end=True)
self.on_enter(command)
self.flush()
def _key_other(self, text):
raise NotImplementedError
def _key_backspace(self, cursor_position):
raise NotImplementedError
def _key_tab(self):
raise NotImplementedError
def _key_ctrl_space(self):
raise NotImplementedError
def _key_home(self, shift, ctrl):
if self.is_cursor_on_last_line():
self.stdkey_home(shift, ctrl, self.current_prompt_pos)
def _key_end(self, shift, ctrl):
if self.is_cursor_on_last_line():
self.stdkey_end(shift, ctrl)
def _key_pageup(self):
raise NotImplementedError
def _key_pagedown(self):
raise NotImplementedError
def _key_escape(self):
raise NotImplementedError
def _key_question(self, text):
raise NotImplementedError
def _key_parenleft(self, text):
raise NotImplementedError
def _key_period(self, text):
raise NotImplementedError
#------ History Management
def load_history(self):
"""Load history from a .py file in user home directory"""
if osp.isfile(self.history_filename):
# This is necessary to catch any error while reading or processing
# history_filename, which causes a crash at startup.
# Fixes spyder-ide/spyder#19850
try:
rawhistory, _ = encoding.readlines(self.history_filename)
rawhistory = [line.replace('\n', '') for line in rawhistory]
if rawhistory[1] != self.INITHISTORY[1]:
rawhistory[1] = self.INITHISTORY[1]
except Exception:
rawhistory = self.INITHISTORY
else:
rawhistory = self.INITHISTORY
history = [line for line in rawhistory \
if line and not line.startswith('#')]
# Truncating history to X entries:
while len(history) >= MAX_LINES:
del history[0]
while rawhistory[0].startswith('#'):
del rawhistory[0]
del rawhistory[0]
# Saving truncated history:
try:
encoding.writelines(rawhistory, self.history_filename)
except EnvironmentError:
pass
return history
#------ Simulation standards input/output
def write_error(self, text):
"""Simulate stderr"""
self.flush()
self.write(text, flush=True, error=True)
if get_debug_level():
STDERR.write(text)
def write(self, text, flush=False, error=False, prompt=False):
"""Simulate stdout and stderr"""
if prompt:
self.flush()
if not is_string(text):
# This test is useful to discriminate QStrings from decoded str
text = to_text_string(text)
self.__buffer.append(text)
ts = time.time()
if flush or prompt:
self.flush(error=error, prompt=prompt)
elif ts - self.__timestamp > 0.05:
self.flush(error=error)
self.__timestamp = ts
# Timer to flush strings cached by last write() operation in series
self.__flushtimer.start(50)
def flush(self, error=False, prompt=False):
"""Flush buffer, write text to console"""
# Fix for spyder-ide/spyder#2452
try:
text = "".join(self.__buffer)
except TypeError:
text = b"".join(self.__buffer)
try:
text = text.decode(locale.getdefaultlocale()[1])
except:
pass
self.__buffer = []
self.insert_text(text, at_end=True, error=error, prompt=prompt)
# The lines below are causing a hard crash when Qt generates
# internal warnings. We replaced them instead for self.update(),
# which prevents the crash.
# See spyder-ide/spyder#10893
# QCoreApplication.processEvents()
# self.repaint()
self.update()
# Clear input buffer:
self.new_input_line = True
#------ Text Insertion
def insert_text(self, text, at_end=False, error=False, prompt=False):
"""
Insert text at the current cursor position
or at the end of the command line
"""
if at_end:
# Insert text at the end of the command line
self.append_text_to_shell(text, error, prompt)
else:
# Insert text at current cursor position
ConsoleBaseWidget.insert_text(self, text)
#------ Re-implemented Qt Methods
def focusNextPrevChild(self, next):
"""
Reimplemented to stop Tab moving to the next window
"""
if next:
return False
return ConsoleBaseWidget.focusNextPrevChild(self, next)
#------ Drag and drop
def dragEnterEvent(self, event):
"""Drag and Drop - Enter event"""
event.setAccepted(event.mimeData().hasFormat("text/plain"))
def dragMoveEvent(self, event):
"""Drag and Drop - Move event"""
if (event.mimeData().hasFormat("text/plain")):
event.setDropAction(Qt.MoveAction)
event.accept()
else:
event.ignore()
def dropEvent(self, event):
"""Drag and Drop - Drop event"""
if (event.mimeData().hasFormat("text/plain")):
text = to_text_string(event.mimeData().text())
if self.new_input_line:
self.on_new_line()
self.insert_text(text, at_end=True)
self.setFocus()
event.setDropAction(Qt.MoveAction)
event.accept()
else:
event.ignore()
def drop_pathlist(self, pathlist):
"""Drop path list"""
raise NotImplementedError
# Example how to debug complex interclass call chains:
#
# from spyder.utils.debug import log_methods_calls
# log_methods_calls('log.log', ShellBaseWidget)
class PythonShellWidget(TracebackLinksMixin, ShellBaseWidget,
GetHelpMixin):
"""Python shell widget"""
QT_CLASS = ShellBaseWidget
INITHISTORY = ['# -*- coding: utf-8 -*-',
'# *** Spyder Python Console History Log ***',]
SEPARATOR = '%s##---(%s)---' % (os.linesep*2, time.ctime())
# --- Signals
# This signal emits a parsed error traceback text so we can then
# request opening the file that traceback comes from in the Editor.
sig_go_to_error_requested = Signal(str)
# Signal
sig_help_requested = Signal(dict)
"""
This signal is emitted to request help on a given object `name`.
Parameters
----------
help_data: dict
Example `{'name': str, 'ignore_unknown': bool}`.
"""
def __init__(self, parent, history_filename, profile=False, initial_message=None):
ShellBaseWidget.__init__(self, parent, history_filename,
profile=profile,
initial_message=initial_message)
TracebackLinksMixin.__init__(self)
GetHelpMixin.__init__(self)
# Local shortcuts
self.shortcuts = self.create_shortcuts()
def create_shortcuts(self):
array_inline = CONF.config_shortcut(
self.enter_array_inline,
context='array_builder',
name='enter array inline',
parent=self)
array_table = CONF.config_shortcut(
self.enter_array_table,
context='array_builder',
name='enter array table',
parent=self)
inspectsc = CONF.config_shortcut(
self.inspect_current_object,
context='Console',
name='Inspect current object',
parent=self)
clear_line_sc = CONF.config_shortcut(
self.clear_line,
context='Console',
name="Clear line",
parent=self,
)
clear_shell_sc = CONF.config_shortcut(
self.clear_terminal,
context='Console',
name="Clear shell",
parent=self,
)
return [inspectsc, array_inline, array_table, clear_line_sc,
clear_shell_sc]
def get_shortcut_data(self):
"""
Returns shortcut data, a list of tuples (shortcut, text, default)
shortcut (QShortcut or QAction instance)
text (string): action/shortcut description
default (string): default key sequence
"""
return [sc.data for sc in self.shortcuts]
#------ Context menu
def setup_context_menu(self):
"""Reimplements ShellBaseWidget method"""
ShellBaseWidget.setup_context_menu(self)
self.copy_without_prompts_action = create_action(
self,
_("Copy without prompts"),
icon=ima.icon('copywop'),
triggered=self.copy_without_prompts)
clear_line_action = create_action(
self,
_("Clear line"),
QKeySequence(CONF.get_shortcut('console', 'Clear line')),
icon=ima.icon('editdelete'),
tip=_("Clear line"),
triggered=self.clear_line)
clear_action = create_action(
self,
_("Clear shell"),
QKeySequence(CONF.get_shortcut('console', 'Clear shell')),
icon=ima.icon('editclear'),
tip=_("Clear shell contents ('cls' command)"),
triggered=self.clear_terminal)
add_actions(self.menu, (self.copy_without_prompts_action,
clear_line_action, clear_action))
def contextMenuEvent(self, event):
"""Reimplements ShellBaseWidget method"""
state = self.has_selected_text()
self.copy_without_prompts_action.setEnabled(state)
ShellBaseWidget.contextMenuEvent(self, event)
@Slot()
def copy_without_prompts(self):
"""Copy text to clipboard without prompts"""
text = self.get_selected_text()
lines = text.split(os.linesep)
for index, line in enumerate(lines):
if line.startswith('>>> ') or line.startswith('... '):
lines[index] = line[4:]
text = os.linesep.join(lines)
QApplication.clipboard().setText(text)
#------ Key handlers
def postprocess_keyevent(self, event):
"""Process keypress event"""
ShellBaseWidget.postprocess_keyevent(self, event)
def _key_other(self, text):
"""1 character key"""
if self.is_completion_widget_visible():
self.completion_text += text
def _key_backspace(self, cursor_position):
"""Action for Backspace key"""
if self.has_selected_text():
self.check_selection()
self.remove_selected_text()
elif self.current_prompt_pos == cursor_position:
# Avoid deleting prompt
return
elif self.is_cursor_on_last_line():
self.stdkey_backspace()
if self.is_completion_widget_visible():
# Removing only last character because if there was a selection
# the completion widget would have been canceled
self.completion_text = self.completion_text[:-1]
def _key_tab(self):
"""Action for TAB key"""
if self.is_cursor_on_last_line():
empty_line = not self.get_current_line_to_cursor().strip()
if empty_line:
self.stdkey_tab()
else:
self.show_code_completion()
def _key_ctrl_space(self):
"""Action for Ctrl+Space"""
if not self.is_completion_widget_visible():
self.show_code_completion()
def _key_pageup(self):
"""Action for PageUp key"""
pass
def _key_pagedown(self):
"""Action for PageDown key"""
pass
def _key_escape(self):
"""Action for ESCAPE key"""
if self.is_completion_widget_visible():
self.hide_completion_widget()
def _key_question(self, text):
"""Action for '?'"""
if self.get_current_line_to_cursor():
last_obj = self.get_last_obj()
if last_obj and not last_obj.isdigit():
self.show_object_info(last_obj)
self.insert_text(text)
# In case calltip and completion are shown at the same time:
if self.is_completion_widget_visible():
self.completion_text += '?'
def _key_parenleft(self, text):
"""Action for '('"""
self.hide_completion_widget()
if self.get_current_line_to_cursor():
last_obj = self.get_last_obj()
if last_obj and not last_obj.isdigit():
self.insert_text(text)
self.show_object_info(last_obj, call=True)
return
self.insert_text(text)
def _key_period(self, text):
"""Action for '.'"""
self.insert_text(text)
if self.codecompletion_auto:
# Enable auto-completion only if last token isn't a float
last_obj = self.get_last_obj()
if last_obj and not last_obj.isdigit():
self.show_code_completion()
#------ Paste
def paste(self):
"""Reimplemented slot to handle multiline paste action"""
text = to_text_string(QApplication.clipboard().text())
if len(text.splitlines()) > 1:
# Multiline paste
if self.new_input_line:
self.on_new_line()
self.remove_selected_text() # Remove selection, eventually
end = self.get_current_line_from_cursor()
lines = self.get_current_line_to_cursor() + text + end
self.clear_line()
self.execute_lines(lines)
self.move_cursor(-len(end))
else:
# Standard paste
ShellBaseWidget.paste(self)
# ------ Code Completion / Calltips
# Methods implemented in child class:
# (e.g. InternalShell)
def get_dir(self, objtxt):
"""Return dir(object)"""
raise NotImplementedError
def get_globals_keys(self):
"""Return shell globals() keys"""
raise NotImplementedError
def get_cdlistdir(self):
"""Return shell current directory list dir"""
raise NotImplementedError
def iscallable(self, objtxt):
"""Is object callable?"""
raise NotImplementedError
def get_arglist(self, objtxt):
"""Get func/method argument list"""
raise NotImplementedError
def get__doc__(self, objtxt):
"""Get object __doc__"""
raise NotImplementedError
def get_doc(self, objtxt):
"""Get object documentation dictionary"""
raise NotImplementedError
def get_source(self, objtxt):
"""Get object source"""
raise NotImplementedError
def is_defined(self, objtxt, force_import=False):
"""Return True if object is defined"""
raise NotImplementedError
def show_completion_widget(self, textlist):
"""Show completion widget"""
self.completion_widget.show_list(
textlist, automatic=False, position=None)
def hide_completion_widget(self, focus_to_parent=True):
"""Hide completion widget"""
self.completion_widget.hide(focus_to_parent=focus_to_parent)
def show_completion_list(self, completions, completion_text=""):
"""Display the possible completions"""
if not completions:
return
if not isinstance(completions[0], tuple):
completions = [(c, '') for c in completions]
if len(completions) == 1 and completions[0][0] == completion_text:
return
self.completion_text = completion_text
# Sorting completion list (entries starting with underscore are
# put at the end of the list):
underscore = set([(comp, t) for (comp, t) in completions
if comp.startswith('_')])
completions = sorted(set(completions) - underscore,
key=lambda x: str.lower(x[0]))
completions += sorted(underscore, key=lambda x: str.lower(x[0]))
self.show_completion_widget(completions)
def show_code_completion(self):
"""Display a completion list based on the current line"""
# Note: unicode conversion is needed only for ExternalShellBase
text = to_text_string(self.get_current_line_to_cursor())
last_obj = self.get_last_obj()
if not text:
return
obj_dir = self.get_dir(last_obj)
if last_obj and obj_dir and text.endswith('.'):
self.show_completion_list(obj_dir)
return
# Builtins and globals
if not text.endswith('.') and last_obj \
and re.match(r'[a-zA-Z_0-9]*$', last_obj):
b_k_g = dir(builtins)+self.get_globals_keys()+keyword.kwlist
for objname in b_k_g:
if objname.startswith(last_obj) and objname != last_obj:
self.show_completion_list(b_k_g, completion_text=last_obj)
return
else:
return
# Looking for an incomplete completion
if last_obj is None:
last_obj = text
dot_pos = last_obj.rfind('.')
if dot_pos != -1:
if dot_pos == len(last_obj)-1:
completion_text = ""
else:
completion_text = last_obj[dot_pos+1:]
last_obj = last_obj[:dot_pos]
completions = self.get_dir(last_obj)
if completions is not None:
self.show_completion_list(completions,
completion_text=completion_text)
return
# Looking for ' or ": filename completion
q_pos = max([text.rfind("'"), text.rfind('"')])
if q_pos != -1:
completions = self.get_cdlistdir()
if completions:
self.show_completion_list(completions,
completion_text=text[q_pos+1:])
return
#------ Drag'n Drop
def drop_pathlist(self, pathlist):
"""Drop path list"""
if pathlist:
files = ["r'%s'" % path for path in pathlist]
if len(files) == 1:
text = files[0]
else:
text = "[" + ", ".join(files) + "]"
if self.new_input_line:
self.on_new_line()
self.insert_text(text)
self.setFocus()
class TerminalWidget(ShellBaseWidget):
"""
Terminal widget
"""
COM = 'rem' if os.name == 'nt' else '#'
INITHISTORY = ['%s *** Spyder Terminal History Log ***' % COM, COM,]
SEPARATOR = '%s%s ---(%s)---' % (os.linesep*2, COM, time.ctime())
# This signal emits a parsed error traceback text so we can then
# request opening the file that traceback comes from in the Editor.
sig_go_to_error_requested = Signal(str)
def __init__(self, parent, history_filename, profile=False):
ShellBaseWidget.__init__(self, parent, history_filename, profile)
#------ Key handlers
def _key_other(self, text):
"""1 character key"""
pass
def _key_backspace(self, cursor_position):
"""Action for Backspace key"""
if self.has_selected_text():
self.check_selection()
self.remove_selected_text()
elif self.current_prompt_pos == cursor_position:
# Avoid deleting prompt
return
elif self.is_cursor_on_last_line():
self.stdkey_backspace()
def _key_tab(self):
"""Action for TAB key"""
if self.is_cursor_on_last_line():
self.stdkey_tab()
def _key_ctrl_space(self):
"""Action for Ctrl+Space"""
pass
def _key_escape(self):
"""Action for ESCAPE key"""
self.clear_line()
def _key_question(self, text):
"""Action for '?'"""
self.insert_text(text)
def _key_parenleft(self, text):
"""Action for '('"""
self.insert_text(text)
def _key_period(self, text):
"""Action for '.'"""
self.insert_text(text)
#------ Drag'n Drop
def drop_pathlist(self, pathlist):
"""Drop path list"""
if pathlist:
files = ['"%s"' % path for path in pathlist]
if len(files) == 1:
text = files[0]
else:
text = " ".join(files)
if self.new_input_line:
self.on_new_line()
self.insert_text(text)
self.setFocus()