##// END OF EJS Templates
Improve tab-completion.
Improve tab-completion.

File last commit:

r1373:4fd1ad0e
r1373:4fd1ad0e
Show More
console_widget.py
406 lines | 14.5 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
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.
Gael Varoquaux
Update wx frontend.
r1349 if wx.Platform == '__WXMSW__':
_DEFAULT_SIZE = 80
else:
_DEFAULT_SIZE = 10
_DEFAULT_STYLE = {
'stdout' : 'fore:#0000FF',
'stderr' : 'fore:#007f00',
'trace' : 'fore:#FF0000',
'default' : 'size:%d' % _DEFAULT_SIZE,
'bracegood' : 'fore:#FFFFFF,back:#0000FF,bold',
'bracebad' : 'fore:#000000,back:#FF0000,bold',
# properties for the various Python lexer styles
'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',
}
# new style numbers
_STDOUT_STYLE = 15
_STDERR_STYLE = 16
_TRACE_STYLE = 17
Gael Varoquaux
Improve tab-completion.
r1373 # system colors
SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
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.
"""
Gael Varoquaux
Capture Xterm title sequence.
r1359 title = 'Console'
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'
#--------------------------------------------------------------------------
# Public API
#--------------------------------------------------------------------------
Gael Varoquaux
Update wx frontend.
r1349 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
size=wx.DefaultSize, style=0,
Gael Varoquaux
Improve tab-completion.
r1373 autocomplete_mode='popup'):
""" Autocomplete_mode: Can be 'popup' or 'text'
'text' show autocompletion in the text buffer
'popup' show it with a dropdown popup
Gael Varoquaux
Update wx frontend.
r1349 """
editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
self.configure_scintilla()
# FIXME: we need to retrieve this from the interpreter.
self.prompt = \
'\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02%i\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
self.new_prompt(self.prompt % 1)
self.autocomplete_mode = autocomplete_mode
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
def configure_scintilla(self):
# 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)
Gael Varoquaux
History is now working. + misc keybindings.
r1358 #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT,
# stc.STC_CMD_PAGEUP)
Gael Varoquaux
Update wx frontend.
r1349
Gael Varoquaux
History is now working. + misc keybindings.
r1358 #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT,
# stc.STC_CMD_PAGEDOWN)
Gael Varoquaux
Update wx frontend.
r1349
# 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)
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)
self.EnsureCaretVisible()
Gael Varoquaux
Correct small history bug. Add busy cursor.
r1371 # Tell autocompletion to choose automaticaly out of a single
# choice list
self.AutoCompSetChooseSingle(True)
self.AutoCompSetMaxHeight(10)
Gael Varoquaux
Update wx frontend.
r1349 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()
Gael Varoquaux
Capture Xterm title sequence.
r1359
# Xterm escape sequences
Gael Varoquaux
Update wx frontend.
r1349 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
Gael Varoquaux
Capture Xterm title sequence.
r1359 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
Gael Varoquaux
Update wx frontend.
r1349
#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_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'])
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 def write(self, text):
""" Write given text to buffer, while translating the ansi escape
sequences.
"""
Gael Varoquaux
Capture Xterm title sequence.
r1359 title = self.title_pat.split(text)
if len(title)>0:
self.title = title[-1]
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)
self.StartStyling(self.GetLength(), 0xFF)
self.AppendText(segment)
Gael Varoquaux
Capture Xterm title sequence.
r1359
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 if segments:
ansi_tags = self.color_pat.findall(text)
for tag in ansi_tags:
i = segments.index(tag)
self.StartStyling(self.GetLength(), 0xFF)
self.AppendText(segments[i+1])
if tag != '0':
self.SetStyling(len(segments[i+1]),
self.ANSI_STYLES[tag][0])
segments.pop(i)
self.GotoPos(self.GetLength())
def new_prompt(self, prompt):
""" Prints a prompt at start of line, and move the start of the
current block there.
The prompt can be give with ascii escape sequences.
"""
self.write(prompt)
# 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 wx.Yield()
self.EnsureCaretVisible()
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295
def replace_current_edit_buffer(self, text):
""" Replace currently entered command line with given text.
"""
self.SetSelection(self.current_prompt_pos, self.GetLength())
self.ReplaceSelection(text)
self.GotoPos(self.GetLength())
def get_current_edit_buffer(self):
""" Returns the text in current edit buffer.
"""
return self.GetTextRange(self.current_prompt_pos,
self.GetLength())
#--------------------------------------------------------------------------
# Private API
#--------------------------------------------------------------------------
def _apply_style(self):
""" Applies the colors for the different text elements and the
carret.
"""
self.SetCaretForeground(self.carret_color)
Gael Varoquaux
Improve tab-completion.
r1373 #self.StyleClearAll()
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 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])
Gael Varoquaux
Improve tab-completion.
r1373 def write_completion(self, possibilities, mode=None):
if mode=='text' or self.autocomplete_mode == 'text':
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 max_len = len(max(possibilities, key=len))
Gael Varoquaux
Improve tab-completion.
r1373 current_buffer = self.get_current_edit_buffer()
self.write('\n')
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 for symbol in possibilities:
Gael Varoquaux
Improve tab-completion.
r1373 self.write(symbol.ljust(max_len))
self.new_prompt(self.prompt % (self.last_result['number'] + 1))
self.replace_current_edit_buffer(current_buffer)
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 else:
Gael Varoquaux
Improve tab-completion.
r1373 #possibilities.sort() # Python sorts are case sensitive
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 self.AutoCompSetIgnoreCase(False)
self.AutoCompSetAutoHide(False)
Gael Varoquaux
Traceback capture now working.
r1360 #let compute the length ot text)last word
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 splitter = [' ', '(', '[', '{']
last_word = self.get_current_edit_buffer()
for breaker in splitter:
last_word = last_word.split(breaker)[-1]
Gael Varoquaux
Correct small history bug. Add busy cursor.
r1371 self.AutoCompSetMaxHeight(len(possibilities))
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295 self.AutoCompShow(len(last_word), " ".join(possibilities))
Gael Varoquaux
Update wx frontend.
r1349
def scroll_to_bottom(self):
maxrange = self.GetScrollRange(wx.VERTICAL)
self.ScrollLines(maxrange)
Gael Varoquaux
Rework multiline input
r1361
Gael Varoquaux
Traceback capture now working.
r1360 def _on_enter(self):
Gael Varoquaux
History is now working. + misc keybindings.
r1358 """ Called when the return key is hit.
"""
pass
gvaroquaux
Start of Wx frontend. Console widget impemented....
r1295
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() :
Gael Varoquaux
Added "ctr-K" to delete current buffer.
r1363 self.replace_current_edit_buffer('')
Gael Varoquaux
History is now working. + misc keybindings.
r1358 elif event.KeyCode == wx.WXK_PAGEUP and event.ShiftDown():
self.ScrollPages(-1)
elif event.KeyCode == wx.WXK_PAGEDOWN and event.ShiftDown():
self.ScrollPages(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
Make code execution more robust.
r1362 self.write('\n')
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
Update wx frontend.
r1349 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
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:
event.Skip()
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()