##// END OF EJS Templates
Merge with upstream
Merge with upstream

File last commit:

r1667:98a3eb15
r1728:42f19a07 merge
Show More
console_widget.py
436 lines | 16.0 KiB | text/x-python | PythonLexer
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 # encoding: utf-8
"""
gvaroquaux
Docstrings tweaks.
r1296 A Wx widget to act as a console and input commands.
This widget deals with prompts and provides an edit buffer
gvaroquaux
Start of Wx frontend. Console widget impemented....
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
Update wx frontend.
r1349 from wx.py import editwindow
Gael Varoquaux
Make synchronous printing work better.
r1618 import time
gvaroquaux
Make the wx frontend well-behaved under windows.
r1436 import sys
LINESEP = '\n'
if sys.platform == 'win32':
LINESEP = '\n\r'
Gael Varoquaux
Update wx frontend.
r1349
gvaroquaux
Start of Wx frontend. Console widget impemented....
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.
gvaroquaux
Make the wx frontend well-behaved under windows.
r1436 _DEFAULT_SIZE = 10
gvaroquaux
Better fonts on MacOSX....
r1502 if sys.platform == 'darwin':
Gael Varoquaux
Fix the default size under MacOSX.
r1667 _DEFAULT_SIZE = 12
Gael Varoquaux
Update wx frontend.
r1349
_DEFAULT_STYLE = {
'stdout' : 'fore:#0000FF',
'stderr' : 'fore:#007f00',
'trace' : 'fore:#FF0000',
'default' : 'size:%d' % _DEFAULT_SIZE,
Gael Varoquaux
Get matching parenthesis to display well.
r1491 'bracegood' : 'fore:#00AA00,back:#000000,bold',
'bracebad' : 'fore:#FF0000,back:#000000,bold',
Gael Varoquaux
Update wx frontend.
r1349
# properties for the various Python lexer styles
Gael Varoquaux
Nice background color for already entered code....
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
Update wx frontend.
r1349 }
# new style numbers
_STDOUT_STYLE = 15
_STDERR_STYLE = 16
_TRACE_STYLE = 17
Gael Varoquaux
Improve tab-completion.
r1373 # system colors
Gael Varoquaux
Usability tweaks. Better auto tab completion. Terminal size more...
r1380 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
Gael Varoquaux
Improve tab-completion.
r1373
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 #-------------------------------------------------------------------------------
# The console widget class
#-------------------------------------------------------------------------------
Gael Varoquaux
Update wx frontend.
r1349 class ConsoleWidget(editwindow.EditWindow):
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 """ Specialized styled text control view for console-like workflow.
gvaroquaux
Docstrings tweaks.
r1296
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 This widget is mainly interested in dealing with the prompt and
keeping the cursor inside the editing line.
"""
gvaroquaux
Clean up code, names, and docstrings.
r1455 # This is where the title captured from the ANSI escape sequences are
# stored.
Gael Varoquaux
Capture Xterm title sequence.
r1359 title = 'Console'
gvaroquaux
More code reuse between GUI-independant frontend and Wx frontend: getting...
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
Update wx frontend.
r1349 style = _DEFAULT_STYLE.copy()
gvaroquaux
Start of Wx frontend. Console widget impemented....
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'],
'1;34': [12, 'LIGHT BLUE'], '1;35':
[13, 'MEDIUM VIOLET RED'],
'1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
# The color of the carret (call _apply_style() after setting)
carret_color = 'BLACK'
Gael Varoquaux
Make synchronous printing work better.
r1618 # Store the last time a refresh was done
_last_refresh_time = 0
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 #--------------------------------------------------------------------------
# Public API
#--------------------------------------------------------------------------
Gael Varoquaux
Update wx frontend.
r1349 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
gvaroquaux
Misspelling in a docstring.
r1630 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
Gael Varoquaux
Update wx frontend.
r1349 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
gvaroquaux
Clean up code, names, and docstrings.
r1455 self._configure_scintilla()
Gael Varoquaux
Update wx frontend.
r1349
self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
Gael Varoquaux
History is now working. + misc keybindings.
r1358 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
Gael Varoquaux
Update wx frontend.
r1349
gvaroquaux
Make process execution work under windows.
r1449 def write(self, text, refresh=True):
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 """ Write given text to buffer, while translating the ansi escape
sequences.
"""
Gael Varoquaux
First cut of subprocess execution with redirection of stdin/stdout.
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
Capture Xterm title sequence.
r1359 title = self.title_pat.split(text)
Gael Varoquaux
Clean up the title-setting code.
r1393 if len(title)>1:
self.title = title[-2]
Gael Varoquaux
Capture Xterm title sequence.
r1359
text = self.title_pat.sub('', text)
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 segments = self.color_pat.split(text)
segment = segments.pop(0)
gvaroquaux
Make the wx frontend well-behaved under windows.
r1436 self.GotoPos(self.GetLength())
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 self.StartStyling(self.GetLength(), 0xFF)
Gael Varoquaux
Fix for Unicode characters when executing processes. Also fix typo in...
r1451 try:
self.AppendText(segment)
except UnicodeDecodeError:
# XXX: Do I really want to skip the exception?
pass
Gael Varoquaux
Capture Xterm title sequence.
r1359
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 if segments:
Gael Varoquaux
Correct styling of ANSI patterns.
r1386 for ansi_tag, text in zip(segments[::2], segments[1::2]):
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 self.StartStyling(self.GetLength(), 0xFF)
Gael Varoquaux
Fix for Unicode characters when executing processes. Also fix typo in...
r1451 try:
self.AppendText(text)
except UnicodeDecodeError:
# XXX: Do I really want to skip the exception?
pass
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295
Gael Varoquaux
Tweak magics.
r1424 if ansi_tag not in self.ANSI_STYLES:
Gael Varoquaux
Correct styling of ANSI patterns.
r1386 style = 0
else:
style = self.ANSI_STYLES[ansi_tag][0]
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295
Gael Varoquaux
Correct styling of ANSI patterns.
r1386 self.SetStyling(len(text), style)
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295
self.GotoPos(self.GetLength())
gvaroquaux
Make process execution work under windows.
r1449 if refresh:
Gael Varoquaux
Make synchronous printing work better.
r1618 current_time = time.time()
if current_time - self._last_refresh_time > 0.03:
gvaroquaux
Another attempt at fixing raw_input/program execution under windows...
r1653 if sys.platform == 'win32':
wx.SafeYield()
else:
wx.Yield()
gvaroquaux
Tweak to have windows show tracebacks.
r1651 # self.ProcessEvent(wx.PaintEvent())
Gael Varoquaux
Make synchronous printing work better.
r1618 self._last_refresh_time = current_time
gvaroquaux
Make the wx frontend well-behaved under windows.
r1436
Gael Varoquaux
Deal with raw_input.
r1388
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 def new_prompt(self, prompt):
""" Prints a prompt at start of line, and move the start of the
current block there.
gvaroquaux
Spelling
r1465 The prompt can be given with ascii escape sequences.
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 """
Gael Varoquaux
Add a banner.
r1495 self.write(prompt, refresh=False)
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 # now we update our cursor giving end of prompt
self.current_prompt_pos = self.GetLength()
self.current_prompt_line = self.GetCurrentLine()
Gael Varoquaux
Rework multiline input
r1361 self.EnsureCaretVisible()
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295
gvaroquaux
Clean up code, names, and docstrings.
r1455 def scroll_to_bottom(self):
maxrange = self.GetScrollRange(wx.VERTICAL)
self.ScrollLines(maxrange)
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295
gvaroquaux
Clean up code, names, and docstrings.
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
Start of Wx frontend. Console widget impemented....
r1295
gvaroquaux
Abstract completion mechanism outside of wx-specific code.
r1463 def get_line_width(self):
""" Return the width of the line in characters.
"""
return self.GetSize()[0]/self.GetCharWidth()
gvaroquaux
Tweaks to make line-display faster.
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
Usability tweaks. Better auto tab completion. Terminal size more...
r1380
gvaroquaux
Clean up code, names, and docstrings.
r1455 #--------------------------------------------------------------------------
# Private API
#--------------------------------------------------------------------------
def _apply_style(self):
""" Applies the colors for the different text elements and the
carret.
Gael Varoquaux
Usability tweaks. Better auto tab completion. Terminal size more...
r1380 """
gvaroquaux
Clean up code, names, and docstrings.
r1455 self.SetCaretForeground(self.carret_color)
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295
gvaroquaux
Clean up code, names, and docstrings.
r1455 #self.StyleClearAll()
self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
"fore:#FF0000,back:#0000FF,bold")
self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
"fore:#000000,back:#FF0000,bold")
for style in self.ANSI_STYLES.values():
self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
def _configure_scintilla(self):
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
Add better keybindings.
r1498 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
gvaroquaux
Clean up code, names, and docstrings.
r1455
self.SetEOLMode(stc.STC_EOL_CRLF)
self.SetWrapMode(stc.STC_WRAP_CHAR)
self.SetWrapMode(stc.STC_WRAP_WORD)
self.SetBufferedDraw(True)
self.SetUseAntiAliasing(True)
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
Better tab-completion when the autocomp menu is displayed.
r1493 # XXX: this doesn't seem to have an effect.
self.AutoCompSetFillUps('\n')
gvaroquaux
Clean up code, names, and docstrings.
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)
self._apply_style()
# Xterm escape sequences
self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
self.title_pat = re.compile('\x1b]0;(.*?)\x07')
#self.SetEdgeMode(stc.STC_EDGE_LINE)
#self.SetEdgeColumn(80)
# styles
p = self.style
self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
self.StyleClearAll()
self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
self.StyleSetSpec(_TRACE_STYLE, p['trace'])
self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
self.StyleSetSpec(stc.STC_P_STRING, p['string'])
self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
Gael Varoquaux
Update wx frontend.
r1349
def _on_key_down(self, event, skip=True):
gvaroquaux
Start of Wx frontend. Console widget impemented....
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
Added "ctr-K" to delete current buffer.
r1363 catched = True
Gael Varoquaux
History is now working. + misc keybindings.
r1358 # Intercept some specific keys.
Gael Varoquaux
Update wx frontend.
r1349 if event.KeyCode == ord('L') and event.ControlDown() :
self.scroll_to_bottom()
Gael Varoquaux
Correct small history bug. Add busy cursor.
r1371 elif event.KeyCode == ord('K') and event.ControlDown() :
gvaroquaux
More code reuse between GUI-independant frontend and Wx frontend: getting...
r1462 self.input_buffer = ''
Gael Varoquaux
Add better keybindings.
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
Make pageup work.
r1490 elif event.KeyCode == wx.WXK_PAGEUP:
Gael Varoquaux
History is now working. + misc keybindings.
r1358 self.ScrollPages(-1)
Gael Varoquaux
Make pageup work.
r1490 elif event.KeyCode == wx.WXK_PAGEDOWN:
Gael Varoquaux
History is now working. + misc keybindings.
r1358 self.ScrollPages(1)
Gael Varoquaux
Usability tweaks. Better auto tab completion. Terminal size more...
r1380 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
self.ScrollLines(-1)
elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
Gael Varoquaux
Minor bug.
r1389 self.ScrollLines(1)
Gael Varoquaux
Added "ctr-K" to delete current buffer.
r1363 else:
catched = False
Gael Varoquaux
Update wx frontend.
r1349
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 if self.AutoCompActive():
event.Skip()
else:
Gael Varoquaux
History is now working. + misc keybindings.
r1358 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
catched = True
Gael Varoquaux
Synchronous stdout/stderr output.
r1383 self.CallTipCancel()
gvaroquaux
Fix segfaults under windows.
r1479 self.write('\n', refresh=False)
gvaroquaux
Make the wx frontend well-behaved under windows.
r1436 # Under windows scintilla seems to be doing funny stuff to the
gvaroquaux
More code reuse between GUI-independant frontend and Wx frontend: getting...
r1462 # line returns here, but the getter for input_buffer filters
# this out.
gvaroquaux
Make the wx frontend well-behaved under windows.
r1436 if sys.platform == 'win32':
gvaroquaux
More code reuse between GUI-independant frontend and Wx frontend: getting...
r1462 self.input_buffer = self.input_buffer
Gael Varoquaux
History is now working. + misc keybindings.
r1358 self._on_enter()
elif event.KeyCode == wx.WXK_HOME:
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
self.GotoPos(self.current_prompt_pos)
catched = True
Gael Varoquaux
Add better keybindings.
r1498 elif event.Modifiers == wx.MOD_SHIFT:
Gael Varoquaux
History is now working. + misc keybindings.
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
Start of Wx frontend. Console widget impemented....
r1295 catched = True
Gael Varoquaux
Update wx frontend.
r1349 elif event.KeyCode == wx.WXK_UP:
gvaroquaux
Start of Wx frontend. Console widget impemented....
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
Update wx frontend.
r1349 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 if self.GetCurrentPos() > self.current_prompt_pos:
event.Skip()
catched = True
if skip and not catched:
gvaroquaux
Make the wx frontend well-behaved under windows.
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
Start of Wx frontend. Console widget impemented....
r1295
return catched
Gael Varoquaux
History is now working. + misc keybindings.
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)
gvaroquaux
Start of Wx frontend. Console widget impemented....
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()