console_widget.py
552 lines
| 20.4 KiB
| text/x-python
|
PythonLexer
gvaroquaux
|
r1295 | # encoding: utf-8 | ||
""" | ||||
gvaroquaux
|
r1296 | A Wx widget to act as a console and input commands. | ||
This widget deals with prompts and provides an edit buffer | ||||
gvaroquaux
|
r1295 | restricted to after the last prompt. | ||
""" | ||||
__docformat__ = "restructuredtext en" | ||||
#------------------------------------------------------------------------------- | ||||
# Copyright (C) 2008 The IPython Development Team | ||||
# | ||||
# Distributed under the terms of the BSD License. The full license is | ||||
# in the file COPYING, distributed as part of this software. | ||||
#------------------------------------------------------------------------------- | ||||
#------------------------------------------------------------------------------- | ||||
# Imports | ||||
#------------------------------------------------------------------------------- | ||||
import wx | ||||
import wx.stc as stc | ||||
Gael Varoquaux
|
r1349 | from wx.py import editwindow | ||
Gael Varoquaux
|
r1618 | import time | ||
gvaroquaux
|
r1436 | import sys | ||
Laurent Dufrechou
|
r1835 | import string | ||
gvaroquaux
|
r1436 | LINESEP = '\n' | ||
if sys.platform == 'win32': | ||||
LINESEP = '\n\r' | ||||
Gael Varoquaux
|
r1349 | |||
gvaroquaux
|
r1295 | import re | ||
# FIXME: Need to provide an API for non user-generated display on the | ||||
# screen: this should not be editable by the user. | ||||
Laurent Dufrechou
|
r1834 | #------------------------------------------------------------------------------- | ||
# Constants | ||||
#------------------------------------------------------------------------------- | ||||
_COMPLETE_BUFFER_MARKER = 31 | ||||
_ERROR_MARKER = 30 | ||||
_INPUT_MARKER = 29 | ||||
gvaroquaux
|
r1295 | |||
gvaroquaux
|
r1436 | _DEFAULT_SIZE = 10 | ||
gvaroquaux
|
r1502 | if sys.platform == 'darwin': | ||
Gael Varoquaux
|
r1667 | _DEFAULT_SIZE = 12 | ||
Gael Varoquaux
|
r1349 | |||
_DEFAULT_STYLE = { | ||||
Laurent Dufrechou
|
r1835 | #background definition | ||
Gael Varoquaux
|
r1349 | 'stdout' : 'fore:#0000FF', | ||
'stderr' : 'fore:#007f00', | ||||
'trace' : 'fore:#FF0000', | ||||
'default' : 'size:%d' % _DEFAULT_SIZE, | ||||
Gael Varoquaux
|
r1491 | 'bracegood' : 'fore:#00AA00,back:#000000,bold', | ||
'bracebad' : 'fore:#FF0000,back:#000000,bold', | ||||
Gael Varoquaux
|
r1349 | |||
# properties for the various Python lexer styles | ||||
Gael Varoquaux
|
r1374 | 'comment' : 'fore:#007F00', | ||
'number' : 'fore:#007F7F', | ||||
'string' : 'fore:#7F007F,italic', | ||||
'char' : 'fore:#7F007F,italic', | ||||
'keyword' : 'fore:#00007F,bold', | ||||
'triple' : 'fore:#7F0000', | ||||
'tripledouble' : 'fore:#7F0000', | ||||
'class' : 'fore:#0000FF,bold,underline', | ||||
'def' : 'fore:#007F7F,bold', | ||||
'operator' : 'bold' | ||||
Gael Varoquaux
|
r1349 | } | ||
# new style numbers | ||||
_STDOUT_STYLE = 15 | ||||
_STDERR_STYLE = 16 | ||||
_TRACE_STYLE = 17 | ||||
Gael Varoquaux
|
r1373 | # system colors | ||
Gael Varoquaux
|
r1380 | #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND) | ||
Gael Varoquaux
|
r1373 | |||
gvaroquaux
|
r1295 | #------------------------------------------------------------------------------- | ||
# The console widget class | ||||
#------------------------------------------------------------------------------- | ||||
Gael Varoquaux
|
r1349 | class ConsoleWidget(editwindow.EditWindow): | ||
gvaroquaux
|
r1295 | """ Specialized styled text control view for console-like workflow. | ||
gvaroquaux
|
r1296 | |||
gvaroquaux
|
r1295 | This widget is mainly interested in dealing with the prompt and | ||
keeping the cursor inside the editing line. | ||||
""" | ||||
gvaroquaux
|
r1455 | # This is where the title captured from the ANSI escape sequences are | ||
# stored. | ||||
Gael Varoquaux
|
r1359 | title = 'Console' | ||
gvaroquaux
|
r1462 | # The buffer being edited. | ||
def _set_input_buffer(self, string): | ||||
self.SetSelection(self.current_prompt_pos, self.GetLength()) | ||||
self.ReplaceSelection(string) | ||||
self.GotoPos(self.GetLength()) | ||||
def _get_input_buffer(self): | ||||
""" Returns the text in current edit buffer. | ||||
""" | ||||
input_buffer = self.GetTextRange(self.current_prompt_pos, | ||||
self.GetLength()) | ||||
input_buffer = input_buffer.replace(LINESEP, '\n') | ||||
return input_buffer | ||||
input_buffer = property(_get_input_buffer, _set_input_buffer) | ||||
Gael Varoquaux
|
r1349 | style = _DEFAULT_STYLE.copy() | ||
gvaroquaux
|
r1295 | # Translation table from ANSI escape sequences to color. Override | ||
# this to specify your colors. | ||||
ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'], | ||||
'0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'], | ||||
'0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'], | ||||
'0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'], | ||||
'1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'], | ||||
'1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'], | ||||
Laurent Dufrechou
|
r1834 | '1;34': [12, 'LIGHT BLUE'], '1;35': [13, 'MEDIUM VIOLET RED'], | ||
gvaroquaux
|
r1295 | '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']} | ||
Laurent Dufrechou
|
r1834 | #we define platform specific fonts | ||
if wx.Platform == '__WXMSW__': | ||||
faces = { 'times': 'Times New Roman', | ||||
'mono' : 'Courier New', | ||||
'helv' : 'Arial', | ||||
'other': 'Comic Sans MS', | ||||
'size' : 10, | ||||
'size2': 8, | ||||
} | ||||
elif wx.Platform == '__WXMAC__': | ||||
faces = { 'times': 'Times New Roman', | ||||
'mono' : 'Monaco', | ||||
'helv' : 'Arial', | ||||
'other': 'Comic Sans MS', | ||||
'size' : 10, | ||||
'size2': 8, | ||||
} | ||||
else: | ||||
faces = { 'times': 'Times', | ||||
'mono' : 'Courier', | ||||
'helv' : 'Helvetica', | ||||
'other': 'new century schoolbook', | ||||
'size' : 10, | ||||
'size2': 8, | ||||
} | ||||
Gael Varoquaux
|
r1618 | # Store the last time a refresh was done | ||
_last_refresh_time = 0 | ||||
gvaroquaux
|
r1295 | #-------------------------------------------------------------------------- | ||
# Public API | ||||
#-------------------------------------------------------------------------- | ||||
Gael Varoquaux
|
r1349 | def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, | ||
gvaroquaux
|
r1630 | size=wx.DefaultSize, style=wx.WANTS_CHARS, ): | ||
Gael Varoquaux
|
r1349 | editwindow.EditWindow.__init__(self, parent, id, pos, size, style) | ||
Laurent Dufrechou
|
r1835 | self.configure_scintilla() | ||
Laurent Dufrechou
|
r1834 | self.enter_catched = False #this var track if 'enter' key as ever been processed | ||
#thus it will only be reallowed until key goes up | ||||
self.current_prompt_pos = 0 | ||||
Gael Varoquaux
|
r1349 | |||
self.Bind(wx.EVT_KEY_DOWN, self._on_key_down) | ||||
Gael Varoquaux
|
r1358 | self.Bind(wx.EVT_KEY_UP, self._on_key_up) | ||
Gael Varoquaux
|
r1349 | |||
gvaroquaux
|
r1449 | def write(self, text, refresh=True): | ||
gvaroquaux
|
r1295 | """ Write given text to buffer, while translating the ansi escape | ||
sequences. | ||||
""" | ||||
Gael Varoquaux
|
r1437 | # XXX: do not put print statements to sys.stdout/sys.stderr in | ||
# this method, the print statements will call this method, as | ||||
# you will end up with an infinit loop | ||||
Gael Varoquaux
|
r1359 | title = self.title_pat.split(text) | ||
Gael Varoquaux
|
r1393 | if len(title)>1: | ||
self.title = title[-2] | ||||
Gael Varoquaux
|
r1359 | |||
text = self.title_pat.sub('', text) | ||||
gvaroquaux
|
r1295 | segments = self.color_pat.split(text) | ||
segment = segments.pop(0) | ||||
gvaroquaux
|
r1436 | self.GotoPos(self.GetLength()) | ||
gvaroquaux
|
r1295 | self.StartStyling(self.GetLength(), 0xFF) | ||
Gael Varoquaux
|
r1451 | try: | ||
self.AppendText(segment) | ||||
except UnicodeDecodeError: | ||||
# XXX: Do I really want to skip the exception? | ||||
pass | ||||
Gael Varoquaux
|
r1359 | |||
gvaroquaux
|
r1295 | if segments: | ||
Gael Varoquaux
|
r1386 | for ansi_tag, text in zip(segments[::2], segments[1::2]): | ||
gvaroquaux
|
r1295 | self.StartStyling(self.GetLength(), 0xFF) | ||
Gael Varoquaux
|
r1451 | try: | ||
self.AppendText(text) | ||||
except UnicodeDecodeError: | ||||
# XXX: Do I really want to skip the exception? | ||||
pass | ||||
gvaroquaux
|
r1295 | |||
Gael Varoquaux
|
r1424 | if ansi_tag not in self.ANSI_STYLES: | ||
Gael Varoquaux
|
r1386 | style = 0 | ||
else: | ||||
style = self.ANSI_STYLES[ansi_tag][0] | ||||
gvaroquaux
|
r1295 | |||
Gael Varoquaux
|
r1386 | self.SetStyling(len(text), style) | ||
gvaroquaux
|
r1295 | |||
self.GotoPos(self.GetLength()) | ||||
gvaroquaux
|
r1449 | if refresh: | ||
Gael Varoquaux
|
r1618 | current_time = time.time() | ||
if current_time - self._last_refresh_time > 0.03: | ||||
gvaroquaux
|
r1653 | if sys.platform == 'win32': | ||
wx.SafeYield() | ||||
else: | ||||
wx.Yield() | ||||
gvaroquaux
|
r1651 | # self.ProcessEvent(wx.PaintEvent()) | ||
Gael Varoquaux
|
r1618 | self._last_refresh_time = current_time | ||
gvaroquaux
|
r1436 | |||
Gael Varoquaux
|
r1388 | |||
gvaroquaux
|
r1295 | def new_prompt(self, prompt): | ||
""" Prints a prompt at start of line, and move the start of the | ||||
current block there. | ||||
gvaroquaux
|
r1465 | The prompt can be given with ascii escape sequences. | ||
gvaroquaux
|
r1295 | """ | ||
Gael Varoquaux
|
r1495 | self.write(prompt, refresh=False) | ||
gvaroquaux
|
r1295 | # now we update our cursor giving end of prompt | ||
self.current_prompt_pos = self.GetLength() | ||||
self.current_prompt_line = self.GetCurrentLine() | ||||
Gael Varoquaux
|
r1361 | self.EnsureCaretVisible() | ||
gvaroquaux
|
r1295 | |||
gvaroquaux
|
r1455 | def scroll_to_bottom(self): | ||
maxrange = self.GetScrollRange(wx.VERTICAL) | ||||
self.ScrollLines(maxrange) | ||||
gvaroquaux
|
r1295 | |||
gvaroquaux
|
r1455 | def pop_completion(self, possibilities, offset=0): | ||
""" Pops up an autocompletion menu. Offset is the offset | ||||
in characters of the position at which the menu should | ||||
appear, relativ to the cursor. | ||||
""" | ||||
self.AutoCompSetIgnoreCase(False) | ||||
self.AutoCompSetAutoHide(False) | ||||
self.AutoCompSetMaxHeight(len(possibilities)) | ||||
self.AutoCompShow(offset, " ".join(possibilities)) | ||||
gvaroquaux
|
r1295 | |||
gvaroquaux
|
r1463 | def get_line_width(self): | ||
""" Return the width of the line in characters. | ||||
""" | ||||
return self.GetSize()[0]/self.GetCharWidth() | ||||
gvaroquaux
|
r1503 | #-------------------------------------------------------------------------- | ||
# EditWindow API | ||||
#-------------------------------------------------------------------------- | ||||
def OnUpdateUI(self, event): | ||||
""" Override the OnUpdateUI of the EditWindow class, to prevent | ||||
syntax highlighting both for faster redraw, and for more | ||||
consistent look and feel. | ||||
""" | ||||
Gael Varoquaux
|
r1380 | |||
gvaroquaux
|
r1455 | #-------------------------------------------------------------------------- | ||
Laurent Dufrechou
|
r1834 | # Styling API | ||
gvaroquaux
|
r1455 | #-------------------------------------------------------------------------- | ||
Laurent Dufrechou
|
r1835 | def configure_scintilla(self): | ||
p = self.style | ||||
#First we define the special background colors | ||||
if '_COMPLETE_BUFFER_BG' in p: | ||||
_COMPLETE_BUFFER_BG = p['_COMPLETE_BUFFER_BG'] | ||||
else: | ||||
_COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green | ||||
if '_INPUT_BUFFER_BG' in p: | ||||
_INPUT_BUFFER_BG = p['_INPUT_BUFFER_BG'] | ||||
else: | ||||
_INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow | ||||
if '_ERROR_BG' in p: | ||||
_ERROR_BG = p['_ERROR_BG'] | ||||
else: | ||||
_ERROR_BG = '#FFF1F1' # Nice red | ||||
Laurent Dufrechou
|
r1834 | # Marker for complete buffer. | ||
self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND, | ||||
Laurent Dufrechou
|
r1835 | background = _COMPLETE_BUFFER_BG) | ||
Laurent Dufrechou
|
r1834 | # Marker for current input buffer. | ||
self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND, | ||||
Laurent Dufrechou
|
r1835 | background = _INPUT_BUFFER_BG) | ||
Laurent Dufrechou
|
r1834 | # Marker for tracebacks. | ||
self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND, | ||||
Laurent Dufrechou
|
r1835 | background = _ERROR_BG) | ||
Laurent Dufrechou
|
r1834 | |||
gvaroquaux
|
r1455 | self.SetEOLMode(stc.STC_EOL_LF) | ||
# Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside | ||||
# the widget | ||||
self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN) | ||||
self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT) | ||||
# Also allow Ctrl Shift "=" for poor non US keyboard users. | ||||
self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT, | ||||
stc.STC_CMD_ZOOMIN) | ||||
# Keys: we need to clear some of the keys the that don't play | ||||
# well with a console. | ||||
self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL) | ||||
self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL) | ||||
self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL) | ||||
Gael Varoquaux
|
r1498 | self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL) | ||
gvaroquaux
|
r1455 | |||
self.SetEOLMode(stc.STC_EOL_CRLF) | ||||
self.SetWrapMode(stc.STC_WRAP_CHAR) | ||||
self.SetWrapMode(stc.STC_WRAP_WORD) | ||||
self.SetBufferedDraw(True) | ||||
Laurent Dufrechou
|
r1836 | |||
if 'antialiasing' in p: | ||||
self.SetUseAntiAliasing(p['antialiasing']) | ||||
else: | ||||
self.SetUseAntiAliasing(True) | ||||
gvaroquaux
|
r1455 | self.SetLayoutCache(stc.STC_CACHE_PAGE) | ||
self.SetUndoCollection(False) | ||||
self.SetUseTabs(True) | ||||
self.SetIndent(4) | ||||
self.SetTabWidth(4) | ||||
# we don't want scintilla's autocompletion to choose | ||||
# automaticaly out of a single choice list, as we pop it up | ||||
# automaticaly | ||||
self.AutoCompSetChooseSingle(False) | ||||
self.AutoCompSetMaxHeight(10) | ||||
Gael Varoquaux
|
r1493 | # XXX: this doesn't seem to have an effect. | ||
self.AutoCompSetFillUps('\n') | ||||
gvaroquaux
|
r1455 | |||
self.SetMargins(3, 3) #text is moved away from border with 3px | ||||
# Suppressing Scintilla margins | ||||
self.SetMarginWidth(0, 0) | ||||
self.SetMarginWidth(1, 0) | ||||
self.SetMarginWidth(2, 0) | ||||
# Xterm escape sequences | ||||
self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?') | ||||
self.title_pat = re.compile('\x1b]0;(.*?)\x07') | ||||
# styles | ||||
Laurent Dufrechou
|
r1834 | |||
Laurent Dufrechou
|
r1835 | if 'carret_color' in p: | ||
self.SetCaretForeground(p['carret_color']) | ||||
else: | ||||
self.SetCaretForeground('BLACK') | ||||
if 'background_color' in p: | ||||
background_color = p['background_color'] | ||||
else: | ||||
background_color = 'WHITE' | ||||
Laurent Dufrechou
|
r1834 | if 'default' in p: | ||
Laurent Dufrechou
|
r1835 | if 'back' not in p['default']: | ||
p['default']+=',back:%s' % background_color | ||||
if 'size' not in p['default']: | ||||
p['default']+=',size:%s' % self.faces['size'] | ||||
if 'face' not in p['default']: | ||||
p['default']+=',face:%s' % self.faces['mono'] | ||||
Laurent Dufrechou
|
r1834 | self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default']) | ||
else: | ||||
self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "fore:%s,back:%s,size:%d,face:%s" | ||||
Laurent Dufrechou
|
r1835 | % (self.ANSI_STYLES['0;30'][1], background_color, | ||
Laurent Dufrechou
|
r1834 | self.faces['size'], self.faces['mono'])) | ||
#all styles = default one | ||||
gvaroquaux
|
r1455 | self.StyleClearAll() | ||
Laurent Dufrechou
|
r1834 | |||
# XXX: two lines below are usefull if not using the lexer | ||||
#for style in self.ANSI_STYLES.values(): | ||||
# self.StyleSetSpec(style[0], "bold,fore:%s" % style[1]) | ||||
Laurent Dufrechou
|
r1835 | #prompt definition | ||
if 'prompt_in1' in p: | ||||
self.prompt_in1 = p['prompt_in1'] | ||||
else: | ||||
self.prompt_in1 = \ | ||||
'\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02' | ||||
if 'prompt_out' in p: | ||||
self.prompt_out = p['prompt_out'] | ||||
else: | ||||
self.prompt_out = \ | ||||
'\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02' | ||||
self.output_prompt_template = string.Template(self.prompt_out) | ||||
self.input_prompt_template = string.Template(self.prompt_in1) | ||||
Laurent Dufrechou
|
r1834 | if 'stdout' in p: | ||
self.StyleSetSpec(_STDOUT_STYLE, p['stdout']) | ||||
if 'stderr' in p: | ||||
self.StyleSetSpec(_STDERR_STYLE, p['stderr']) | ||||
if 'trace' in p: | ||||
self.StyleSetSpec(_TRACE_STYLE, p['trace']) | ||||
if 'bracegood' in p: | ||||
self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood']) | ||||
if 'bracebad' in p: | ||||
self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad']) | ||||
if 'comment' in p: | ||||
self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment']) | ||||
if 'number' in p: | ||||
self.StyleSetSpec(stc.STC_P_NUMBER, p['number']) | ||||
if 'string' in p: | ||||
self.StyleSetSpec(stc.STC_P_STRING, p['string']) | ||||
if 'char' in p: | ||||
self.StyleSetSpec(stc.STC_P_CHARACTER, p['char']) | ||||
if 'keyword' in p: | ||||
self.StyleSetSpec(stc.STC_P_WORD, p['keyword']) | ||||
if 'keyword' in p: | ||||
self.StyleSetSpec(stc.STC_P_WORD2, p['keyword']) | ||||
if 'triple' in p: | ||||
self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple']) | ||||
if 'tripledouble' in p: | ||||
self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble']) | ||||
if 'class' in p: | ||||
self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class']) | ||||
if 'def' in p: | ||||
self.StyleSetSpec(stc.STC_P_DEFNAME, p['def']) | ||||
if 'operator' in p: | ||||
self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator']) | ||||
if 'comment' in p: | ||||
self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment']) | ||||
Laurent Dufrechou
|
r1835 | #-------------------------------------------------------------------------- | ||
# Private API | ||||
#-------------------------------------------------------------------------- | ||||
Gael Varoquaux
|
r1349 | def _on_key_down(self, event, skip=True): | ||
gvaroquaux
|
r1295 | """ Key press callback used for correcting behavior for | ||
console-like interfaces: the cursor is constraint to be after | ||||
the last prompt. | ||||
Return True if event as been catched. | ||||
""" | ||||
Gael Varoquaux
|
r1363 | catched = True | ||
Gael Varoquaux
|
r1358 | # Intercept some specific keys. | ||
Gael Varoquaux
|
r1349 | if event.KeyCode == ord('L') and event.ControlDown() : | ||
self.scroll_to_bottom() | ||||
Gael Varoquaux
|
r1371 | elif event.KeyCode == ord('K') and event.ControlDown() : | ||
gvaroquaux
|
r1462 | self.input_buffer = '' | ||
Gael Varoquaux
|
r1498 | elif event.KeyCode == ord('A') and event.ControlDown() : | ||
self.GotoPos(self.GetLength()) | ||||
self.SetSelectionStart(self.current_prompt_pos) | ||||
self.SetSelectionEnd(self.GetCurrentPos()) | ||||
catched = True | ||||
elif event.KeyCode == ord('E') and event.ControlDown() : | ||||
self.GotoPos(self.GetLength()) | ||||
catched = True | ||||
Gael Varoquaux
|
r1490 | elif event.KeyCode == wx.WXK_PAGEUP: | ||
Gael Varoquaux
|
r1358 | self.ScrollPages(-1) | ||
Gael Varoquaux
|
r1490 | elif event.KeyCode == wx.WXK_PAGEDOWN: | ||
Gael Varoquaux
|
r1358 | self.ScrollPages(1) | ||
Gael Varoquaux
|
r1380 | elif event.KeyCode == wx.WXK_UP and event.ShiftDown(): | ||
self.ScrollLines(-1) | ||||
elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown(): | ||||
Gael Varoquaux
|
r1389 | self.ScrollLines(1) | ||
Gael Varoquaux
|
r1363 | else: | ||
catched = False | ||||
Gael Varoquaux
|
r1349 | |||
gvaroquaux
|
r1295 | if self.AutoCompActive(): | ||
event.Skip() | ||||
else: | ||||
Gael Varoquaux
|
r1358 | if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \ | ||
event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN): | ||||
catched = True | ||||
Laurent Dufrechou
|
r1835 | if not self.enter_catched: | ||
Laurent Dufrechou
|
r1834 | self.CallTipCancel() | ||
self.write('\n', refresh=False) | ||||
# Under windows scintilla seems to be doing funny stuff to the | ||||
# line returns here, but the getter for input_buffer filters | ||||
# this out. | ||||
if sys.platform == 'win32': | ||||
self.input_buffer = self.input_buffer | ||||
self._on_enter() | ||||
self.enter_catched = True | ||||
Gael Varoquaux
|
r1358 | |||
elif event.KeyCode == wx.WXK_HOME: | ||||
gvaroquaux
|
r1295 | if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN): | ||
self.GotoPos(self.current_prompt_pos) | ||||
catched = True | ||||
Gael Varoquaux
|
r1498 | elif event.Modifiers == wx.MOD_SHIFT: | ||
Gael Varoquaux
|
r1358 | # FIXME: This behavior is not ideal: if the selection | ||
# is already started, it will jump. | ||||
self.SetSelectionStart(self.current_prompt_pos) | ||||
self.SetSelectionEnd(self.GetCurrentPos()) | ||||
gvaroquaux
|
r1295 | catched = True | ||
Gael Varoquaux
|
r1349 | elif event.KeyCode == wx.WXK_UP: | ||
gvaroquaux
|
r1295 | if self.GetCurrentLine() > self.current_prompt_line: | ||
if self.GetCurrentLine() == self.current_prompt_line + 1 \ | ||||
and self.GetColumn(self.GetCurrentPos()) < \ | ||||
self.GetColumn(self.current_prompt_pos): | ||||
self.GotoPos(self.current_prompt_pos) | ||||
else: | ||||
event.Skip() | ||||
catched = True | ||||
Gael Varoquaux
|
r1349 | elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK): | ||
gvaroquaux
|
r1295 | if self.GetCurrentPos() > self.current_prompt_pos: | ||
event.Skip() | ||||
catched = True | ||||
if skip and not catched: | ||||
gvaroquaux
|
r1436 | # Put the cursor back in the edit region | ||
if self.GetCurrentPos() < self.current_prompt_pos: | ||||
self.GotoPos(self.current_prompt_pos) | ||||
else: | ||||
event.Skip() | ||||
gvaroquaux
|
r1295 | |||
return catched | ||||
Gael Varoquaux
|
r1358 | def _on_key_up(self, event, skip=True): | ||
""" If cursor is outside the editing region, put it back. | ||||
""" | ||||
event.Skip() | ||||
if self.GetCurrentPos() < self.current_prompt_pos: | ||||
self.GotoPos(self.current_prompt_pos) | ||||
Laurent Dufrechou
|
r1834 | self.enter_catched = False #we re-allow enter event processing | ||
Gael Varoquaux
|
r1358 | |||
gvaroquaux
|
r1295 | if __name__ == '__main__': | ||
# Some simple code to test the console widget. | ||||
class MainWindow(wx.Frame): | ||||
def __init__(self, parent, id, title): | ||||
wx.Frame.__init__(self, parent, id, title, size=(300,250)) | ||||
self._sizer = wx.BoxSizer(wx.VERTICAL) | ||||
self.console_widget = ConsoleWidget(self) | ||||
self._sizer.Add(self.console_widget, 1, wx.EXPAND) | ||||
self.SetSizer(self._sizer) | ||||
self.SetAutoLayout(1) | ||||
self.Show(True) | ||||
app = wx.PySimpleApp() | ||||
w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget') | ||||
w.SetSize((780, 460)) | ||||
w.Show() | ||||
app.MainLoop() | ||||