##// END OF EJS Templates
Make the wx frontend well-behaved under windows.
gvaroquaux -
Show More
@@ -1,177 +1,176 b''
1 """
1 """
2 Base front end class for all line-oriented frontends.
2 Base front end class for all line-oriented frontends.
3
3
4 Currently this focuses on synchronous frontends.
4 Currently this focuses on synchronous frontends.
5 """
5 """
6 __docformat__ = "restructuredtext en"
6 __docformat__ = "restructuredtext en"
7
7
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
9 # Copyright (C) 2008 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14
14
15 #-------------------------------------------------------------------------------
15 #-------------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-------------------------------------------------------------------------------
17 #-------------------------------------------------------------------------------
18 import re
18 import re
19
19
20 import IPython
20 import IPython
21
21
22
23 from frontendbase import FrontEndBase
22 from frontendbase import FrontEndBase
24 from IPython.kernel.core.interpreter import Interpreter
23 from IPython.kernel.core.interpreter import Interpreter
25
24
26
27 def common_prefix(strings):
25 def common_prefix(strings):
28 ref = strings[0]
26 ref = strings[0]
29 prefix = ''
27 prefix = ''
30 for size in range(len(ref)):
28 for size in range(len(ref)):
31 test_prefix = ref[:size+1]
29 test_prefix = ref[:size+1]
32 for string in strings[1:]:
30 for string in strings[1:]:
33 if not string.startswith(test_prefix):
31 if not string.startswith(test_prefix):
34 return prefix
32 return prefix
35 prefix = test_prefix
33 prefix = test_prefix
36
34
37 return prefix
35 return prefix
38
36
39 #-------------------------------------------------------------------------------
37 #-------------------------------------------------------------------------------
40 # Base class for the line-oriented front ends
38 # Base class for the line-oriented front ends
41 #-------------------------------------------------------------------------------
39 #-------------------------------------------------------------------------------
42 class LineFrontEndBase(FrontEndBase):
40 class LineFrontEndBase(FrontEndBase):
43
41
44 # We need to keep the prompt number, to be able to increment
42 # We need to keep the prompt number, to be able to increment
45 # it when there is an exception.
43 # it when there is an exception.
46 prompt_number = 1
44 prompt_number = 1
47
45
48 # To bootstrap
46 # To bootstrap
49 last_result = dict(number=0)
47 last_result = dict(number=0)
50
48
51 #--------------------------------------------------------------------------
49 #--------------------------------------------------------------------------
52 # Public API
50 # Public API
53 #--------------------------------------------------------------------------
51 #--------------------------------------------------------------------------
54
52
55 def __init__(self, shell=None, history=None):
53 def __init__(self, shell=None, history=None):
56 if shell is None:
54 if shell is None:
57 shell = Interpreter()
55 shell = Interpreter()
58 FrontEndBase.__init__(self, shell=shell, history=history)
56 FrontEndBase.__init__(self, shell=shell, history=history)
59
57
60 #FIXME: print banner.
58 #FIXME: print banner.
61 banner = """IPython1 %s -- An enhanced Interactive Python.""" \
59 banner = """IPython1 %s -- An enhanced Interactive Python.""" \
62 % IPython.__version__
60 % IPython.__version__
63
61
64
62
65 def complete(self, line):
63 def complete(self, line):
66 """Complete line in engine's user_ns
64 """Complete line in engine's user_ns
67
65
68 Parameters
66 Parameters
69 ----------
67 ----------
70 line : string
68 line : string
71
69
72 Result
70 Result
73 ------
71 ------
74 The replacement for the line and the list of possible completions.
72 The replacement for the line and the list of possible completions.
75 """
73 """
76 completions = self.shell.complete(line)
74 completions = self.shell.complete(line)
77 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
75 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
78 if completions:
76 if completions:
79 prefix = common_prefix(completions)
77 prefix = common_prefix(completions)
80 residual = complete_sep.split(line)[:-1]
78 residual = complete_sep.split(line)[:-1]
81 line = line[:-len(residual)] + prefix
79 line = line[:-len(residual)] + prefix
82 return line, completions
80 return line, completions
83
81
84
82
85 def render_result(self, result):
83 def render_result(self, result):
86 if 'stdout' in result and result['stdout']:
84 if 'stdout' in result and result['stdout']:
87 self.write('\n' + result['stdout'])
85 self.write('\n' + result['stdout'])
88 if 'display' in result and result['display']:
86 if 'display' in result and result['display']:
89 self.write("%s%s\n" % (
87 self.write("%s%s\n" % (
90 self.output_prompt % result['number'],
88 self.output_prompt % result['number'],
91 result['display']['pprint']
89 result['display']['pprint']
92 ) )
90 ) )
93
91
94
92
95 def render_error(self, failure):
93 def render_error(self, failure):
96 self.insert_text('\n\n'+str(failure)+'\n\n')
94 self.insert_text('\n\n'+str(failure)+'\n\n')
97 return failure
95 return failure
98
96
99
97
100 def prefilter_input(self, string):
98 def prefilter_input(self, string):
101 string = string.replace('\r\n', '\n')
99 string = string.replace('\r\n', '\n')
102 string = string.replace('\t', 4*' ')
100 string = string.replace('\t', 4*' ')
103 # Clean the trailing whitespace
101 # Clean the trailing whitespace
104 string = '\n'.join(l.rstrip() for l in string.split('\n'))
102 string = '\n'.join(l.rstrip() for l in string.split('\n'))
105 return string
103 return string
106
104
107
105
108 def is_complete(self, string):
106 def is_complete(self, string):
109 if string in ('', '\n'):
107 if string in ('', '\n'):
110 return True
108 return True
111 elif ( len(self.get_current_edit_buffer().split('\n'))>2
109 elif ( len(self.get_current_edit_buffer().split('\n'))>2
112 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
110 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
113 return False
111 return False
114 else:
112 else:
115 # Add line returns here, to make sure that the statement is
113 # Add line returns here, to make sure that the statement is
116 # complete.
114 # complete.
117 return FrontEndBase.is_complete(self, string.rstrip() + '\n\n')
115 return FrontEndBase.is_complete(self, string.rstrip() + '\n\n')
118
116
119
117
120 def execute(self, python_string, raw_string=None):
118 def execute(self, python_string, raw_string=None):
121 """ Send the python_string to the interpreter, stores the
119 """ Send the python_string to the interpreter, stores the
122 raw_string in the history and starts a new prompt.
120 raw_string in the history and starts a new prompt.
123 """
121 """
124 if raw_string is None:
122 if raw_string is None:
125 raw_string = python_string
123 raw_string = python_string
126 # Create a false result, in case there is an exception
124 # Create a false result, in case there is an exception
127 self.last_result = dict(number=self.prompt_number)
125 self.last_result = dict(number=self.prompt_number)
128 try:
126 try:
129 self.history.input_cache[-1] = raw_string.rstrip()
127 self.history.input_cache[-1] = raw_string.rstrip()
130 result = self.shell.execute(python_string)
128 result = self.shell.execute(python_string)
131 self.last_result = result
129 self.last_result = result
132 self.render_result(result)
130 self.render_result(result)
133 except:
131 except:
134 self.show_traceback()
132 self.show_traceback()
135 finally:
133 finally:
136 self.after_execute()
134 self.after_execute()
137
135
138
136
139 def after_execute(self):
137 def after_execute(self):
140 """ All the operations required after an execution to put the
138 """ All the operations required after an execution to put the
141 terminal back in a shape where it is usable.
139 terminal back in a shape where it is usable.
142 """
140 """
143 self.prompt_number += 1
141 self.prompt_number += 1
144 self.new_prompt(self.prompt % (self.last_result['number'] + 1))
142 self.new_prompt(self.prompt % (self.last_result['number'] + 1))
145 # Start a new empty history entry
143 # Start a new empty history entry
146 self._add_history(None, '')
144 self._add_history(None, '')
147 self.history_cursor = len(self.history.input_cache) - 1
145 self.history_cursor = len(self.history.input_cache) - 1
148
146
149
147
150 def _on_enter(self):
148 def _on_enter(self):
151 """ Called when the return key is pressed in a line editing
149 """ Called when the return key is pressed in a line editing
152 buffer.
150 buffer.
153 """
151 """
154 current_buffer = self.get_current_edit_buffer()
152 current_buffer = self.get_current_edit_buffer()
155 cleaned_buffer = self.prefilter_input(current_buffer)
153 cleaned_buffer = self.prefilter_input(current_buffer)
156 if self.is_complete(cleaned_buffer):
154 if self.is_complete(cleaned_buffer):
157 self.execute(cleaned_buffer, raw_string=current_buffer)
155 self.execute(cleaned_buffer, raw_string=current_buffer)
158 else:
156 else:
159 self.write(self._get_indent_string(current_buffer[:-1]))
157 self.write(self._get_indent_string(
160 if current_buffer.rstrip().endswith(':'):
158 current_buffer[:-1]))
159 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
161 self.write('\t')
160 self.write('\t')
162
161
163
162
164 #--------------------------------------------------------------------------
163 #--------------------------------------------------------------------------
165 # Private API
164 # Private API
166 #--------------------------------------------------------------------------
165 #--------------------------------------------------------------------------
167
166
168 def _get_indent_string(self, string):
167 def _get_indent_string(self, string):
169 string = string.replace('\t', ' '*4)
168 string = string.replace('\t', ' '*4)
170 string = string.split('\n')[-1]
169 string = string.split('\n')[-1]
171 indent_chars = len(string) - len(string.lstrip())
170 indent_chars = len(string) - len(string.lstrip())
172 indent_string = '\t'*(indent_chars // 4) + \
171 indent_string = '\t'*(indent_chars // 4) + \
173 ' '*(indent_chars % 4)
172 ' '*(indent_chars % 4)
174
173
175 return indent_string
174 return indent_string
176
175
177
176
@@ -1,421 +1,430 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A Wx widget to act as a console and input commands.
3 A Wx widget to act as a console and input commands.
4
4
5 This widget deals with prompts and provides an edit buffer
5 This widget deals with prompts and provides an edit buffer
6 restricted to after the last prompt.
6 restricted to after the last prompt.
7 """
7 """
8
8
9 __docformat__ = "restructuredtext en"
9 __docformat__ = "restructuredtext en"
10
10
11 #-------------------------------------------------------------------------------
11 #-------------------------------------------------------------------------------
12 # Copyright (C) 2008 The IPython Development Team
12 # Copyright (C) 2008 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is
14 # Distributed under the terms of the BSD License. The full license is
15 # in the file COPYING, distributed as part of this software.
15 # in the file COPYING, distributed as part of this software.
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17
17
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21
21
22 import wx
22 import wx
23 import wx.stc as stc
23 import wx.stc as stc
24
24
25 from wx.py import editwindow
25 from wx.py import editwindow
26 import sys
27 LINESEP = '\n'
28 if sys.platform == 'win32':
29 LINESEP = '\n\r'
26
30
27 import re
31 import re
28
32
29 # FIXME: Need to provide an API for non user-generated display on the
33 # FIXME: Need to provide an API for non user-generated display on the
30 # screen: this should not be editable by the user.
34 # screen: this should not be editable by the user.
31
35
32 if wx.Platform == '__WXMSW__':
33 _DEFAULT_SIZE = 80
34 else:
35 _DEFAULT_SIZE = 10
36 _DEFAULT_SIZE = 10
36
37
37 _DEFAULT_STYLE = {
38 _DEFAULT_STYLE = {
38 'stdout' : 'fore:#0000FF',
39 'stdout' : 'fore:#0000FF',
39 'stderr' : 'fore:#007f00',
40 'stderr' : 'fore:#007f00',
40 'trace' : 'fore:#FF0000',
41 'trace' : 'fore:#FF0000',
41
42
42 'default' : 'size:%d' % _DEFAULT_SIZE,
43 'default' : 'size:%d' % _DEFAULT_SIZE,
43 'bracegood' : 'fore:#FFFFFF,back:#0000FF,bold',
44 'bracegood' : 'fore:#FFFFFF,back:#0000FF,bold',
44 'bracebad' : 'fore:#000000,back:#FF0000,bold',
45 'bracebad' : 'fore:#000000,back:#FF0000,bold',
45
46
46 # properties for the various Python lexer styles
47 # properties for the various Python lexer styles
47 'comment' : 'fore:#007F00',
48 'comment' : 'fore:#007F00',
48 'number' : 'fore:#007F7F',
49 'number' : 'fore:#007F7F',
49 'string' : 'fore:#7F007F,italic',
50 'string' : 'fore:#7F007F,italic',
50 'char' : 'fore:#7F007F,italic',
51 'char' : 'fore:#7F007F,italic',
51 'keyword' : 'fore:#00007F,bold',
52 'keyword' : 'fore:#00007F,bold',
52 'triple' : 'fore:#7F0000',
53 'triple' : 'fore:#7F0000',
53 'tripledouble' : 'fore:#7F0000',
54 'tripledouble' : 'fore:#7F0000',
54 'class' : 'fore:#0000FF,bold,underline',
55 'class' : 'fore:#0000FF,bold,underline',
55 'def' : 'fore:#007F7F,bold',
56 'def' : 'fore:#007F7F,bold',
56 'operator' : 'bold'
57 'operator' : 'bold'
57 }
58 }
58
59
59 # new style numbers
60 # new style numbers
60 _STDOUT_STYLE = 15
61 _STDOUT_STYLE = 15
61 _STDERR_STYLE = 16
62 _STDERR_STYLE = 16
62 _TRACE_STYLE = 17
63 _TRACE_STYLE = 17
63
64
64
65
65 # system colors
66 # system colors
66 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
67 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
67
68
68 #-------------------------------------------------------------------------------
69 #-------------------------------------------------------------------------------
69 # The console widget class
70 # The console widget class
70 #-------------------------------------------------------------------------------
71 #-------------------------------------------------------------------------------
71 class ConsoleWidget(editwindow.EditWindow):
72 class ConsoleWidget(editwindow.EditWindow):
72 """ Specialized styled text control view for console-like workflow.
73 """ Specialized styled text control view for console-like workflow.
73
74
74 This widget is mainly interested in dealing with the prompt and
75 This widget is mainly interested in dealing with the prompt and
75 keeping the cursor inside the editing line.
76 keeping the cursor inside the editing line.
76 """
77 """
77
78
78 title = 'Console'
79 title = 'Console'
79
80
80 style = _DEFAULT_STYLE.copy()
81 style = _DEFAULT_STYLE.copy()
81
82
82 # Translation table from ANSI escape sequences to color. Override
83 # Translation table from ANSI escape sequences to color. Override
83 # this to specify your colors.
84 # this to specify your colors.
84 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
85 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
85 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
86 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
86 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
87 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
87 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
88 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
88 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
89 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
89 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
90 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
90 '1;34': [12, 'LIGHT BLUE'], '1;35':
91 '1;34': [12, 'LIGHT BLUE'], '1;35':
91 [13, 'MEDIUM VIOLET RED'],
92 [13, 'MEDIUM VIOLET RED'],
92 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
93 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
93
94
94 # The color of the carret (call _apply_style() after setting)
95 # The color of the carret (call _apply_style() after setting)
95 carret_color = 'BLACK'
96 carret_color = 'BLACK'
96
97
97
98 #--------------------------------------------------------------------------
98 #--------------------------------------------------------------------------
99 # Public API
99 # Public API
100 #--------------------------------------------------------------------------
100 #--------------------------------------------------------------------------
101
101
102 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
102 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
103 size=wx.DefaultSize, style=0, ):
103 size=wx.DefaultSize, style=0, ):
104 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
104 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
105 self.configure_scintilla()
105 self.configure_scintilla()
106
106
107 # FIXME: we need to retrieve this from the interpreter.
107 # FIXME: we need to retrieve this from the interpreter.
108 self.prompt = \
108 self.prompt = \
109 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02%i\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
109 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02%i\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
110 self.new_prompt(self.prompt % 1)
110 self.new_prompt(self.prompt % 1)
111
111
112 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
112 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
113 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
113 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
114
114
115
115
116 def configure_scintilla(self):
116 def configure_scintilla(self):
117 self.SetEOLMode(stc.STC_EOL_LF)
118
117 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
119 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
118 # the widget
120 # the widget
119 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
121 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
120 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
122 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
121 # Also allow Ctrl Shift "=" for poor non US keyboard users.
123 # Also allow Ctrl Shift "=" for poor non US keyboard users.
122 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
124 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
123 stc.STC_CMD_ZOOMIN)
125 stc.STC_CMD_ZOOMIN)
124
126
125 #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT,
127 #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT,
126 # stc.STC_CMD_PAGEUP)
128 # stc.STC_CMD_PAGEUP)
127
129
128 #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT,
130 #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT,
129 # stc.STC_CMD_PAGEDOWN)
131 # stc.STC_CMD_PAGEDOWN)
130
132
131 # Keys: we need to clear some of the keys the that don't play
133 # Keys: we need to clear some of the keys the that don't play
132 # well with a console.
134 # well with a console.
133 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
135 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
134 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
136 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
135 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
137 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
136
138
137
139
138 self.SetEOLMode(stc.STC_EOL_CRLF)
140 self.SetEOLMode(stc.STC_EOL_CRLF)
139 self.SetWrapMode(stc.STC_WRAP_CHAR)
141 self.SetWrapMode(stc.STC_WRAP_CHAR)
140 self.SetWrapMode(stc.STC_WRAP_WORD)
142 self.SetWrapMode(stc.STC_WRAP_WORD)
141 self.SetBufferedDraw(True)
143 self.SetBufferedDraw(True)
142 self.SetUseAntiAliasing(True)
144 self.SetUseAntiAliasing(True)
143 self.SetLayoutCache(stc.STC_CACHE_PAGE)
145 self.SetLayoutCache(stc.STC_CACHE_PAGE)
144 self.SetUndoCollection(False)
146 self.SetUndoCollection(False)
145 self.SetUseTabs(True)
147 self.SetUseTabs(True)
146 self.SetIndent(4)
148 self.SetIndent(4)
147 self.SetTabWidth(4)
149 self.SetTabWidth(4)
148
150
149 self.EnsureCaretVisible()
151 self.EnsureCaretVisible()
150 # we don't want scintilla's autocompletion to choose
152 # we don't want scintilla's autocompletion to choose
151 # automaticaly out of a single choice list, as we pop it up
153 # automaticaly out of a single choice list, as we pop it up
152 # automaticaly
154 # automaticaly
153 self.AutoCompSetChooseSingle(False)
155 self.AutoCompSetChooseSingle(False)
154 self.AutoCompSetMaxHeight(10)
156 self.AutoCompSetMaxHeight(10)
155
157
156 self.SetMargins(3, 3) #text is moved away from border with 3px
158 self.SetMargins(3, 3) #text is moved away from border with 3px
157 # Suppressing Scintilla margins
159 # Suppressing Scintilla margins
158 self.SetMarginWidth(0, 0)
160 self.SetMarginWidth(0, 0)
159 self.SetMarginWidth(1, 0)
161 self.SetMarginWidth(1, 0)
160 self.SetMarginWidth(2, 0)
162 self.SetMarginWidth(2, 0)
161
163
162 self._apply_style()
164 self._apply_style()
163
165
164 # Xterm escape sequences
166 # Xterm escape sequences
165 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
167 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
166 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
168 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
167
169
168 #self.SetEdgeMode(stc.STC_EDGE_LINE)
170 #self.SetEdgeMode(stc.STC_EDGE_LINE)
169 #self.SetEdgeColumn(80)
171 #self.SetEdgeColumn(80)
170
172
171 # styles
173 # styles
172 p = self.style
174 p = self.style
173 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
175 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
174 self.StyleClearAll()
176 self.StyleClearAll()
175 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
177 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
176 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
178 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
177 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
179 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
178
180
179 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
181 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
180 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
182 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
181 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
183 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
182 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
184 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
183 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
185 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
184 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
186 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
185 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
187 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
186 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
188 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
187 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
189 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
188 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
190 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
189 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
191 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
190 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
192 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
191 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
193 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
192 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
194 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
193
195
194
196
195 def write(self, text):
197 def write(self, text):
196 """ Write given text to buffer, while translating the ansi escape
198 """ Write given text to buffer, while translating the ansi escape
197 sequences.
199 sequences.
198 """
200 """
199 # XXX: do not put print statements in this method, the print
201 # XXX: do not put print statements in this method, the print
200 # statements will call this method, and you will end up with
202 # statements will call this method, and you will end up with
201 # an infinit loop
203 # an infinit loop
202 title = self.title_pat.split(text)
204 title = self.title_pat.split(text)
203 if len(title)>1:
205 if len(title)>1:
204 self.title = title[-2]
206 self.title = title[-2]
205
207
206 text = self.title_pat.sub('', text)
208 text = self.title_pat.sub('', text)
207 segments = self.color_pat.split(text)
209 segments = self.color_pat.split(text)
208 segment = segments.pop(0)
210 segment = segments.pop(0)
211 self.GotoPos(self.GetLength())
209 self.StartStyling(self.GetLength(), 0xFF)
212 self.StartStyling(self.GetLength(), 0xFF)
210 self.AppendText(segment)
213 self.AppendText(segment)
211
214
212 if segments:
215 if segments:
213 for ansi_tag, text in zip(segments[::2], segments[1::2]):
216 for ansi_tag, text in zip(segments[::2], segments[1::2]):
214 self.StartStyling(self.GetLength(), 0xFF)
217 self.StartStyling(self.GetLength(), 0xFF)
215 self.AppendText(text)
218 self.AppendText(text)
216
219
217 if ansi_tag not in self.ANSI_STYLES:
220 if ansi_tag not in self.ANSI_STYLES:
218 style = 0
221 style = 0
219 else:
222 else:
220 style = self.ANSI_STYLES[ansi_tag][0]
223 style = self.ANSI_STYLES[ansi_tag][0]
221
224
222 self.SetStyling(len(text), style)
225 self.SetStyling(len(text), style)
223
226
224 self.GotoPos(self.GetLength())
227 self.GotoPos(self.GetLength())
225 wx.Yield()
228 wx.Yield()
226
229
227
230
228 def new_prompt(self, prompt):
231 def new_prompt(self, prompt):
229 """ Prints a prompt at start of line, and move the start of the
232 """ Prints a prompt at start of line, and move the start of the
230 current block there.
233 current block there.
231
234
232 The prompt can be give with ascii escape sequences.
235 The prompt can be give with ascii escape sequences.
233 """
236 """
234 self.write(prompt)
237 self.write(prompt)
235 # now we update our cursor giving end of prompt
238 # now we update our cursor giving end of prompt
236 self.current_prompt_pos = self.GetLength()
239 self.current_prompt_pos = self.GetLength()
237 self.current_prompt_line = self.GetCurrentLine()
240 self.current_prompt_line = self.GetCurrentLine()
238 wx.Yield()
241 wx.Yield()
239 self.EnsureCaretVisible()
242 self.EnsureCaretVisible()
240
243
241
244
242 def replace_current_edit_buffer(self, text):
245 def replace_current_edit_buffer(self, text):
243 """ Replace currently entered command line with given text.
246 """ Replace currently entered command line with given text.
244 """
247 """
245 self.SetSelection(self.current_prompt_pos, self.GetLength())
248 self.SetSelection(self.current_prompt_pos, self.GetLength())
246 self.ReplaceSelection(text)
249 self.ReplaceSelection(text)
247 self.GotoPos(self.GetLength())
250 self.GotoPos(self.GetLength())
248
251
249
252
250 def get_current_edit_buffer(self):
253 def get_current_edit_buffer(self):
251 """ Returns the text in current edit buffer.
254 """ Returns the text in current edit buffer.
252 """
255 """
253 return self.GetTextRange(self.current_prompt_pos,
256 current_edit_buffer = self.GetTextRange(self.current_prompt_pos,
254 self.GetLength())
257 self.GetLength())
258 current_edit_buffer = current_edit_buffer.replace(LINESEP, '\n')
259 return current_edit_buffer
255
260
256
261
257 #--------------------------------------------------------------------------
262 #--------------------------------------------------------------------------
258 # Private API
263 # Private API
259 #--------------------------------------------------------------------------
264 #--------------------------------------------------------------------------
260
265
261 def _apply_style(self):
266 def _apply_style(self):
262 """ Applies the colors for the different text elements and the
267 """ Applies the colors for the different text elements and the
263 carret.
268 carret.
264 """
269 """
265 self.SetCaretForeground(self.carret_color)
270 self.SetCaretForeground(self.carret_color)
266
271
267 #self.StyleClearAll()
272 #self.StyleClearAll()
268 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
273 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
269 "fore:#FF0000,back:#0000FF,bold")
274 "fore:#FF0000,back:#0000FF,bold")
270 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
275 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
271 "fore:#000000,back:#FF0000,bold")
276 "fore:#000000,back:#FF0000,bold")
272
277
273 for style in self.ANSI_STYLES.values():
278 for style in self.ANSI_STYLES.values():
274 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
279 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
275
280
276
281
277 def write_completion(self, possibilities):
282 def write_completion(self, possibilities):
278 # FIXME: This is non Wx specific and needs to be moved into
283 # FIXME: This is non Wx specific and needs to be moved into
279 # the base class.
284 # the base class.
280 current_buffer = self.get_current_edit_buffer()
285 current_buffer = self.get_current_edit_buffer()
281
286
282 self.write('\n')
287 self.write('\n')
283 max_len = len(max(possibilities, key=len)) + 1
288 max_len = len(max(possibilities, key=len)) + 1
284
289
285 #now we check how much symbol we can put on a line...
290 #now we check how much symbol we can put on a line...
286 chars_per_line = self.GetSize()[0]/self.GetCharWidth()
291 chars_per_line = self.GetSize()[0]/self.GetCharWidth()
287 symbols_per_line = max(1, chars_per_line/max_len)
292 symbols_per_line = max(1, chars_per_line/max_len)
288
293
289 pos = 1
294 pos = 1
290 buf = []
295 buf = []
291 for symbol in possibilities:
296 for symbol in possibilities:
292 if pos < symbols_per_line:
297 if pos < symbols_per_line:
293 buf.append(symbol.ljust(max_len))
298 buf.append(symbol.ljust(max_len))
294 pos += 1
299 pos += 1
295 else:
300 else:
296 buf.append(symbol.rstrip() +'\n')
301 buf.append(symbol.rstrip() + '\n')
297 pos = 1
302 pos = 1
298 self.write(''.join(buf))
303 self.write(''.join(buf))
299 self.new_prompt(self.prompt % (self.last_result['number'] + 1))
304 self.new_prompt(self.prompt % (self.last_result['number'] + 1))
300 self.replace_current_edit_buffer(current_buffer)
305 self.replace_current_edit_buffer(current_buffer)
301
306
302
307
303 def pop_completion(self, possibilities, offset=0):
308 def pop_completion(self, possibilities, offset=0):
304 """ Pops up an autocompletion menu. Offset is the offset
309 """ Pops up an autocompletion menu. Offset is the offset
305 in characters of the position at which the menu should
310 in characters of the position at which the menu should
306 appear, relativ to the cursor.
311 appear, relativ to the cursor.
307 """
312 """
308 self.AutoCompSetIgnoreCase(False)
313 self.AutoCompSetIgnoreCase(False)
309 self.AutoCompSetAutoHide(False)
314 self.AutoCompSetAutoHide(False)
310 self.AutoCompSetMaxHeight(len(possibilities))
315 self.AutoCompSetMaxHeight(len(possibilities))
311 self.AutoCompShow(offset, " ".join(possibilities))
316 self.AutoCompShow(offset, " ".join(possibilities))
312
317
313
318
314 def scroll_to_bottom(self):
319 def scroll_to_bottom(self):
315 maxrange = self.GetScrollRange(wx.VERTICAL)
320 maxrange = self.GetScrollRange(wx.VERTICAL)
316 self.ScrollLines(maxrange)
321 self.ScrollLines(maxrange)
317
322
318
323
319 def _on_enter(self):
320 """ Called when the return key is hit.
321 """
322 pass
323
324
325 def _on_key_down(self, event, skip=True):
324 def _on_key_down(self, event, skip=True):
326 """ Key press callback used for correcting behavior for
325 """ Key press callback used for correcting behavior for
327 console-like interfaces: the cursor is constraint to be after
326 console-like interfaces: the cursor is constraint to be after
328 the last prompt.
327 the last prompt.
329
328
330 Return True if event as been catched.
329 Return True if event as been catched.
331 """
330 """
332 catched = True
331 catched = True
333 # Intercept some specific keys.
332 # Intercept some specific keys.
334 if event.KeyCode == ord('L') and event.ControlDown() :
333 if event.KeyCode == ord('L') and event.ControlDown() :
335 self.scroll_to_bottom()
334 self.scroll_to_bottom()
336 elif event.KeyCode == ord('K') and event.ControlDown() :
335 elif event.KeyCode == ord('K') and event.ControlDown() :
337 self.replace_current_edit_buffer('')
336 self.replace_current_edit_buffer('')
338 elif event.KeyCode == wx.WXK_PAGEUP and event.ShiftDown():
337 elif event.KeyCode == wx.WXK_PAGEUP and event.ShiftDown():
339 self.ScrollPages(-1)
338 self.ScrollPages(-1)
340 elif event.KeyCode == wx.WXK_PAGEDOWN and event.ShiftDown():
339 elif event.KeyCode == wx.WXK_PAGEDOWN and event.ShiftDown():
341 self.ScrollPages(1)
340 self.ScrollPages(1)
342 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
341 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
343 self.ScrollLines(-1)
342 self.ScrollLines(-1)
344 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
343 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
345 self.ScrollLines(1)
344 self.ScrollLines(1)
346 else:
345 else:
347 catched = False
346 catched = False
348
347
349 if self.AutoCompActive():
348 if self.AutoCompActive():
350 event.Skip()
349 event.Skip()
351 else:
350 else:
352 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
351 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
353 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
352 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
354 catched = True
353 catched = True
355 self.CallTipCancel()
354 self.CallTipCancel()
356 self.write('\n')
355 self.write('\n')
356 # Under windows scintilla seems to be doing funny stuff to the
357 # line returns here, but get_current_edit_buffer filters this
358 # out.
359 if sys.platform == 'win32':
360 self.replace_current_edit_buffer(
361 self.get_current_edit_buffer())
357 self._on_enter()
362 self._on_enter()
358
363
359 elif event.KeyCode == wx.WXK_HOME:
364 elif event.KeyCode == wx.WXK_HOME:
360 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
365 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
361 self.GotoPos(self.current_prompt_pos)
366 self.GotoPos(self.current_prompt_pos)
362 catched = True
367 catched = True
363
368
364 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
369 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
365 # FIXME: This behavior is not ideal: if the selection
370 # FIXME: This behavior is not ideal: if the selection
366 # is already started, it will jump.
371 # is already started, it will jump.
367 self.SetSelectionStart(self.current_prompt_pos)
372 self.SetSelectionStart(self.current_prompt_pos)
368 self.SetSelectionEnd(self.GetCurrentPos())
373 self.SetSelectionEnd(self.GetCurrentPos())
369 catched = True
374 catched = True
370
375
371 elif event.KeyCode == wx.WXK_UP:
376 elif event.KeyCode == wx.WXK_UP:
372 if self.GetCurrentLine() > self.current_prompt_line:
377 if self.GetCurrentLine() > self.current_prompt_line:
373 if self.GetCurrentLine() == self.current_prompt_line + 1 \
378 if self.GetCurrentLine() == self.current_prompt_line + 1 \
374 and self.GetColumn(self.GetCurrentPos()) < \
379 and self.GetColumn(self.GetCurrentPos()) < \
375 self.GetColumn(self.current_prompt_pos):
380 self.GetColumn(self.current_prompt_pos):
376 self.GotoPos(self.current_prompt_pos)
381 self.GotoPos(self.current_prompt_pos)
377 else:
382 else:
378 event.Skip()
383 event.Skip()
379 catched = True
384 catched = True
380
385
381 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
386 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
382 if self.GetCurrentPos() > self.current_prompt_pos:
387 if self.GetCurrentPos() > self.current_prompt_pos:
383 event.Skip()
388 event.Skip()
384 catched = True
389 catched = True
385
390
386 if skip and not catched:
391 if skip and not catched:
392 # Put the cursor back in the edit region
393 if self.GetCurrentPos() < self.current_prompt_pos:
394 self.GotoPos(self.current_prompt_pos)
395 else:
387 event.Skip()
396 event.Skip()
388
397
389 return catched
398 return catched
390
399
391
400
392 def _on_key_up(self, event, skip=True):
401 def _on_key_up(self, event, skip=True):
393 """ If cursor is outside the editing region, put it back.
402 """ If cursor is outside the editing region, put it back.
394 """
403 """
395 event.Skip()
404 event.Skip()
396 if self.GetCurrentPos() < self.current_prompt_pos:
405 if self.GetCurrentPos() < self.current_prompt_pos:
397 self.GotoPos(self.current_prompt_pos)
406 self.GotoPos(self.current_prompt_pos)
398
407
399
408
400
409
401
410
402 if __name__ == '__main__':
411 if __name__ == '__main__':
403 # Some simple code to test the console widget.
412 # Some simple code to test the console widget.
404 class MainWindow(wx.Frame):
413 class MainWindow(wx.Frame):
405 def __init__(self, parent, id, title):
414 def __init__(self, parent, id, title):
406 wx.Frame.__init__(self, parent, id, title, size=(300,250))
415 wx.Frame.__init__(self, parent, id, title, size=(300,250))
407 self._sizer = wx.BoxSizer(wx.VERTICAL)
416 self._sizer = wx.BoxSizer(wx.VERTICAL)
408 self.console_widget = ConsoleWidget(self)
417 self.console_widget = ConsoleWidget(self)
409 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
418 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
410 self.SetSizer(self._sizer)
419 self.SetSizer(self._sizer)
411 self.SetAutoLayout(1)
420 self.SetAutoLayout(1)
412 self.Show(True)
421 self.Show(True)
413
422
414 app = wx.PySimpleApp()
423 app = wx.PySimpleApp()
415 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
424 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
416 w.SetSize((780, 460))
425 w.SetSize((780, 460))
417 w.Show()
426 w.Show()
418
427
419 app.MainLoop()
428 app.MainLoop()
420
429
421
430
@@ -1,271 +1,277 b''
1 # encoding: utf-8 -*- test-case-name:
1 # encoding: utf-8 -*- test-case-name:
2 # FIXME: Need to add tests.
2 # FIXME: Need to add tests.
3 # ipython1.frontend.cocoa.tests.test_cocoa_frontend -*-
3 # ipython1.frontend.cocoa.tests.test_cocoa_frontend -*-
4
4
5 """Classes to provide a Wx frontend to the
5 """Classes to provide a Wx frontend to the
6 IPython.kernel.core.interpreter.
6 IPython.kernel.core.interpreter.
7
7
8 """
8 """
9
9
10 __docformat__ = "restructuredtext en"
10 __docformat__ = "restructuredtext en"
11
11
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13 # Copyright (C) 2008 The IPython Development Team
13 # Copyright (C) 2008 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-------------------------------------------------------------------------------
17 #-------------------------------------------------------------------------------
18
18
19 #-------------------------------------------------------------------------------
19 #-------------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-------------------------------------------------------------------------------
21 #-------------------------------------------------------------------------------
22
22
23
23
24 import wx
24 import wx
25 import re
25 import re
26 from wx import stc
26 from wx import stc
27 from console_widget import ConsoleWidget
27 from console_widget import ConsoleWidget
28 import __builtin__
28 import __builtin__
29 from time import sleep
29 from time import sleep
30
30
31 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
31 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
32
32
33 #_COMMAND_BG = '#FAFAF1' # Nice green
33 #_COMMAND_BG = '#FAFAF1' # Nice green
34 _RUNNING_BUFFER_BG = '#FDFFD3' # Nice yellow
34 _RUNNING_BUFFER_BG = '#FDFFD3' # Nice yellow
35 _ERROR_BG = '#FFF1F1' # Nice red
35 _ERROR_BG = '#FFF1F1' # Nice red
36
36
37 _RUNNING_BUFFER_MARKER = 31
37 _RUNNING_BUFFER_MARKER = 31
38 _ERROR_MARKER = 30
38 _ERROR_MARKER = 30
39
39
40 #-------------------------------------------------------------------------------
40 #-------------------------------------------------------------------------------
41 # Classes to implement the Wx frontend
41 # Classes to implement the Wx frontend
42 #-------------------------------------------------------------------------------
42 #-------------------------------------------------------------------------------
43 class WxController(PrefilterFrontEnd, ConsoleWidget):
43 class WxController(PrefilterFrontEnd, ConsoleWidget):
44
44
45 output_prompt = \
45 output_prompt = \
46 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
46 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
47
47
48 debug = True
49
48 #--------------------------------------------------------------------------
50 #--------------------------------------------------------------------------
49 # Public API
51 # Public API
50 #--------------------------------------------------------------------------
52 #--------------------------------------------------------------------------
51
53
52 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
54 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
53 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
55 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
54 *args, **kwds):
56 *args, **kwds):
55 """ Create Shell instance.
57 """ Create Shell instance.
56 """
58 """
57 ConsoleWidget.__init__(self, parent, id, pos, size, style)
59 ConsoleWidget.__init__(self, parent, id, pos, size, style)
58 PrefilterFrontEnd.__init__(self)
60 PrefilterFrontEnd.__init__(self)
59
61
60 # Capture Character keys
62 # Capture Character keys
61 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
63 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
62
64
63 # Marker for running buffer.
65 # Marker for running buffer.
64 self.MarkerDefine(_RUNNING_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
66 self.MarkerDefine(_RUNNING_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
65 background=_RUNNING_BUFFER_BG)
67 background=_RUNNING_BUFFER_BG)
66 # Marker for tracebacks.
68 # Marker for tracebacks.
67 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
69 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
68 background=_ERROR_BG)
70 background=_ERROR_BG)
69
71
70
72
71
72 def do_completion(self):
73 def do_completion(self):
73 """ Do code completion.
74 """ Do code completion.
74 """
75 """
75 line = self.get_current_edit_buffer()
76 line = self.get_current_edit_buffer()
76 new_line, completions = self.complete(line)
77 new_line, completions = self.complete(line)
77 if len(completions)>1:
78 if len(completions)>1:
78 self.write_completion(completions)
79 self.write_completion(completions)
79 self.replace_current_edit_buffer(new_line)
80 self.replace_current_edit_buffer(new_line)
80
81
81
82
82 def do_calltip(self):
83 def do_calltip(self):
83 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
84 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
84 symbol = self.get_current_edit_buffer()
85 symbol = self.get_current_edit_buffer()
85 symbol_string = separators.split(symbol)[-1]
86 symbol_string = separators.split(symbol)[-1]
86 base_symbol_string = symbol_string.split('.')[0]
87 base_symbol_string = symbol_string.split('.')[0]
87 if base_symbol_string in self.shell.user_ns:
88 if base_symbol_string in self.shell.user_ns:
88 symbol = self.shell.user_ns[base_symbol_string]
89 symbol = self.shell.user_ns[base_symbol_string]
89 elif base_symbol_string in self.shell.user_global_ns:
90 elif base_symbol_string in self.shell.user_global_ns:
90 symbol = self.shell.user_global_ns[base_symbol_string]
91 symbol = self.shell.user_global_ns[base_symbol_string]
91 elif base_symbol_string in __builtin__.__dict__:
92 elif base_symbol_string in __builtin__.__dict__:
92 symbol = __builtin__.__dict__[base_symbol_string]
93 symbol = __builtin__.__dict__[base_symbol_string]
93 else:
94 else:
94 return False
95 return False
95 for name in symbol_string.split('.')[1:] + ['__doc__']:
96 for name in symbol_string.split('.')[1:] + ['__doc__']:
96 symbol = getattr(symbol, name)
97 symbol = getattr(symbol, name)
97 try:
98 try:
98 self.AutoCompCancel()
99 self.AutoCompCancel()
99 wx.Yield()
100 wx.Yield()
100 self.CallTipShow(self.GetCurrentPos(), symbol)
101 self.CallTipShow(self.GetCurrentPos(), symbol)
101 except:
102 except:
102 # The retrieve symbol couldn't be converted to a string
103 # The retrieve symbol couldn't be converted to a string
103 pass
104 pass
104
105
105
106
106 def popup_completion(self, create=False):
107 def popup_completion(self, create=False):
107 """ Updates the popup completion menu if it exists. If create is
108 """ Updates the popup completion menu if it exists. If create is
108 true, open the menu.
109 true, open the menu.
109 """
110 """
110 line = self.get_current_edit_buffer()
111 line = self.get_current_edit_buffer()
111 if (self.AutoCompActive() and not line[-1] == '.') \
112 if (self.AutoCompActive() and not line[-1] == '.') \
112 or create==True:
113 or create==True:
113 suggestion, completions = self.complete(line)
114 suggestion, completions = self.complete(line)
114 offset=0
115 offset=0
115 if completions:
116 if completions:
116 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
117 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
117 residual = complete_sep.split(line)[-1]
118 residual = complete_sep.split(line)[-1]
118 offset = len(residual)
119 offset = len(residual)
119 self.pop_completion(completions, offset=offset)
120 self.pop_completion(completions, offset=offset)
120
121
121
122
122 def raw_input(self, prompt):
123 def raw_input(self, prompt):
123 """ A replacement from python's raw_input.
124 """ A replacement from python's raw_input.
124 """
125 """
125 self.new_prompt(prompt)
126 self.new_prompt(prompt)
126 self.waiting = True
127 self.waiting = True
127 self.__old_on_enter = self._on_enter
128 self.__old_on_enter = self._on_enter
128 def my_on_enter():
129 def my_on_enter():
129 self.waiting = False
130 self.waiting = False
130 self._on_enter = my_on_enter
131 self._on_enter = my_on_enter
131 # XXX: Busy waiting, ugly.
132 # XXX: Busy waiting, ugly.
132 while self.waiting:
133 while self.waiting:
133 wx.Yield()
134 wx.Yield()
134 sleep(0.1)
135 sleep(0.1)
135 self._on_enter = self.__old_on_enter
136 self._on_enter = self.__old_on_enter
136 return self.get_current_edit_buffer().rstrip('\n')
137 return self.get_current_edit_buffer().rstrip('\n')
137
138
138
139
139 def execute(self, python_string, raw_string=None):
140 def execute(self, python_string, raw_string=None):
140 self.CallTipCancel()
141 self.CallTipCancel()
141 self._cursor = wx.BusyCursor()
142 self._cursor = wx.BusyCursor()
142 if raw_string is None:
143 if raw_string is None:
143 raw_string = python_string
144 raw_string = python_string
144 end_line = self.current_prompt_line \
145 end_line = self.current_prompt_line \
145 + max(1, len(raw_string.split('\n'))-1)
146 + max(1, len(raw_string.split('\n'))-1)
146 for i in range(self.current_prompt_line, end_line):
147 for i in range(self.current_prompt_line, end_line):
147 self.MarkerAdd(i, _RUNNING_BUFFER_MARKER)
148 self.MarkerAdd(i, _RUNNING_BUFFER_MARKER)
148 # Update the display:
149 # Update the display:
149 wx.Yield()
150 wx.Yield()
150 ## Remove the trailing "\n" for cleaner display
151 ## Remove the trailing "\n" for cleaner display
151 #self.SetSelection(self.GetLength()-1, self.GetLength())
152 #self.SetSelection(self.GetLength()-1, self.GetLength())
152 #self.ReplaceSelection('')
153 #self.ReplaceSelection('')
153 self.GotoPos(self.GetLength())
154 self.GotoPos(self.GetLength())
154 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
155 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
155
156
156
157
157 def capture_output(self):
158 def capture_output(self):
158 self.__old_raw_input = __builtin__.raw_input
159 self.__old_raw_input = __builtin__.raw_input
159 __builtin__.raw_input = self.raw_input
160 __builtin__.raw_input = self.raw_input
160 PrefilterFrontEnd.capture_output(self)
161 PrefilterFrontEnd.capture_output(self)
161
162
162
163
163 def release_output(self):
164 def release_output(self):
164 __builtin__.raw_input = self.__old_raw_input
165 __builtin__.raw_input = self.__old_raw_input
165 PrefilterFrontEnd.capture_output(self)
166 PrefilterFrontEnd.capture_output(self)
166
167
167
168
168 def after_execute(self):
169 def after_execute(self):
169 PrefilterFrontEnd.after_execute(self)
170 PrefilterFrontEnd.after_execute(self)
170 if hasattr(self, '_cursor'):
171 if hasattr(self, '_cursor'):
171 del self._cursor
172 del self._cursor
172
173
173
174
174 def show_traceback(self):
175 def show_traceback(self):
175 start_line = self.GetCurrentLine()
176 start_line = self.GetCurrentLine()
176 PrefilterFrontEnd.show_traceback(self)
177 PrefilterFrontEnd.show_traceback(self)
177 wx.Yield()
178 wx.Yield()
178 for i in range(start_line, self.GetCurrentLine()):
179 for i in range(start_line, self.GetCurrentLine()):
179 self.MarkerAdd(i, _ERROR_MARKER)
180 self.MarkerAdd(i, _ERROR_MARKER)
180
181
181
182
182 #--------------------------------------------------------------------------
183 #--------------------------------------------------------------------------
183 # Private API
184 # Private API
184 #--------------------------------------------------------------------------
185 #--------------------------------------------------------------------------
185
186
186
187
187 def _on_key_down(self, event, skip=True):
188 def _on_key_down(self, event, skip=True):
188 """ Capture the character events, let the parent
189 """ Capture the character events, let the parent
189 widget handle them, and put our logic afterward.
190 widget handle them, and put our logic afterward.
190 """
191 """
191 current_line_number = self.GetCurrentLine()
192 current_line_number = self.GetCurrentLine()
192 # Calltips
193 # Calltips
193 if event.KeyCode == ord('('):
194 if event.KeyCode == ord('('):
194 event.Skip()
195 event.Skip()
195 self.do_calltip()
196 self.do_calltip()
196 elif self.AutoCompActive():
197 elif self.AutoCompActive():
197 event.Skip()
198 event.Skip()
198 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
199 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
199 wx.CallAfter(self.popup_completion, create=True)
200 wx.CallAfter(self.popup_completion, create=True)
200 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
201 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
201 wx.WXK_RIGHT):
202 wx.WXK_RIGHT):
202 wx.CallAfter(self.popup_completion)
203 wx.CallAfter(self.popup_completion)
203 else:
204 else:
204 # Up history
205 # Up history
205 if event.KeyCode == wx.WXK_UP and (
206 if event.KeyCode == wx.WXK_UP and (
206 ( current_line_number == self.current_prompt_line and
207 ( current_line_number == self.current_prompt_line and
207 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
208 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
208 or event.ControlDown() ):
209 or event.ControlDown() ):
209 new_buffer = self.get_history_previous(
210 new_buffer = self.get_history_previous(
210 self.get_current_edit_buffer())
211 self.get_current_edit_buffer())
211 if new_buffer is not None:
212 if new_buffer is not None:
212 self.replace_current_edit_buffer(new_buffer)
213 self.replace_current_edit_buffer(new_buffer)
213 if self.GetCurrentLine() > self.current_prompt_line:
214 if self.GetCurrentLine() > self.current_prompt_line:
214 # Go to first line, for seemless history up.
215 # Go to first line, for seemless history up.
215 self.GotoPos(self.current_prompt_pos)
216 self.GotoPos(self.current_prompt_pos)
216 # Down history
217 # Down history
217 elif event.KeyCode == wx.WXK_DOWN and (
218 elif event.KeyCode == wx.WXK_DOWN and (
218 ( current_line_number == self.LineCount -1 and
219 ( current_line_number == self.LineCount -1 and
219 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
220 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
220 or event.ControlDown() ):
221 or event.ControlDown() ):
221 new_buffer = self.get_history_next()
222 new_buffer = self.get_history_next()
222 if new_buffer is not None:
223 if new_buffer is not None:
223 self.replace_current_edit_buffer(new_buffer)
224 self.replace_current_edit_buffer(new_buffer)
224 # Tab-completion
225 # Tab-completion
225 elif event.KeyCode == ord('\t'):
226 elif event.KeyCode == ord('\t'):
226 last_line = self.get_current_edit_buffer().split('\n')[-1]
227 last_line = self.get_current_edit_buffer().split('\n')[-1]
227 if not re.match(r'^\s*$', last_line):
228 if not re.match(r'^\s*$', last_line):
228 self.do_completion()
229 self.do_completion()
229 else:
230 else:
230 event.Skip()
231 event.Skip()
231 else:
232 else:
232 ConsoleWidget._on_key_down(self, event, skip=skip)
233 ConsoleWidget._on_key_down(self, event, skip=skip)
233
234
234
235
235 def _on_key_up(self, event, skip=True):
236 def _on_key_up(self, event, skip=True):
236 if event.KeyCode == 59:
237 if event.KeyCode == 59:
237 # Intercepting '.'
238 # Intercepting '.'
238 event.Skip()
239 event.Skip()
239 self.popup_completion(create=True)
240 self.popup_completion(create=True)
240 else:
241 else:
241 ConsoleWidget._on_key_up(self, event, skip=skip)
242 ConsoleWidget._on_key_up(self, event, skip=skip)
242
243
244 def _on_enter(self):
245 if self.debug:
246 import sys
247 print >>sys.__stdout__, repr(self.get_current_edit_buffer())
248 PrefilterFrontEnd._on_enter(self)
243
249
244 def _set_title(self, title):
250 def _set_title(self, title):
245 return self.Parent.SetTitle(title)
251 return self.Parent.SetTitle(title)
246
252
247 def _get_title(self):
253 def _get_title(self):
248 return self.Parent.GetTitle()
254 return self.Parent.GetTitle()
249
255
250 title = property(_get_title, _set_title)
256 title = property(_get_title, _set_title)
251
257
252
258
253 if __name__ == '__main__':
259 if __name__ == '__main__':
254 class MainWindow(wx.Frame):
260 class MainWindow(wx.Frame):
255 def __init__(self, parent, id, title):
261 def __init__(self, parent, id, title):
256 wx.Frame.__init__(self, parent, id, title, size=(300,250))
262 wx.Frame.__init__(self, parent, id, title, size=(300,250))
257 self._sizer = wx.BoxSizer(wx.VERTICAL)
263 self._sizer = wx.BoxSizer(wx.VERTICAL)
258 self.shell = WxController(self)
264 self.shell = WxController(self)
259 self._sizer.Add(self.shell, 1, wx.EXPAND)
265 self._sizer.Add(self.shell, 1, wx.EXPAND)
260 self.SetSizer(self._sizer)
266 self.SetSizer(self._sizer)
261 self.SetAutoLayout(1)
267 self.SetAutoLayout(1)
262 self.Show(True)
268 self.Show(True)
263
269
264 app = wx.PySimpleApp()
270 app = wx.PySimpleApp()
265 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
271 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
266 frame.shell.SetFocus()
272 frame.shell.SetFocus()
267 frame.SetSize((680, 460))
273 frame.SetSize((680, 460))
268 self = frame.shell
274 self = frame.shell
269
275
270 app.MainLoop()
276 app.MainLoop()
271
277
General Comments 0
You need to be logged in to leave comments. Login now