##// END OF EJS Templates
Start of Wx frontend. Console widget impemented....
gvaroquaux -
Show More
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,367 b''
1 # encoding: utf-8
2 """
3 A Wx widget that deals with prompts and provides an edit buffer
4 restricted to after the last prompt.
5 """
6
7 __docformat__ = "restructuredtext en"
8
9 #-------------------------------------------------------------------------------
10 # Copyright (C) 2008 The IPython Development Team
11 #
12 # Distributed under the terms of the BSD License. The full license is
13 # in the file COPYING, distributed as part of this software.
14 #-------------------------------------------------------------------------------
15
16 #-------------------------------------------------------------------------------
17 # Imports
18 #-------------------------------------------------------------------------------
19
20 import wx
21 import wx.stc as stc
22
23 import re
24
25 # FIXME: Need to provide an API for non user-generated display on the
26 # screen: this should not be editable by the user.
27
28 #-------------------------------------------------------------------------------
29 # The console widget class
30 #-------------------------------------------------------------------------------
31 class ConsoleWidget(stc.StyledTextCtrl):
32 """ Specialized styled text control view for console-like workflow.
33 This widget is mainly interested in dealing with the prompt and
34 keeping the cursor inside the editing line.
35 """
36
37 # Translation table from ANSI escape sequences to color. Override
38 # this to specify your colors.
39 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
40 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
41 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
42 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
43 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
44 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
45 '1;34': [12, 'LIGHT BLUE'], '1;35':
46 [13, 'MEDIUM VIOLET RED'],
47 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
48
49 # The color of the carret (call _apply_style() after setting)
50 carret_color = 'BLACK'
51
52
53 #--------------------------------------------------------------------------
54 # Public API
55 #--------------------------------------------------------------------------
56
57 def write(self, text):
58 """ Write given text to buffer, while translating the ansi escape
59 sequences.
60 """
61 segments = self.color_pat.split(text)
62 segment = segments.pop(0)
63 self.StartStyling(self.GetLength(), 0xFF)
64 self.AppendText(segment)
65
66 if segments:
67 ansi_tags = self.color_pat.findall(text)
68
69 for tag in ansi_tags:
70 i = segments.index(tag)
71 self.StartStyling(self.GetLength(), 0xFF)
72 self.AppendText(segments[i+1])
73
74 if tag != '0':
75 self.SetStyling(len(segments[i+1]),
76 self.ANSI_STYLES[tag][0])
77
78 segments.pop(i)
79
80 self.GotoPos(self.GetLength())
81
82
83 def new_prompt(self, prompt):
84 """ Prints a prompt at start of line, and move the start of the
85 current block there.
86
87 The prompt can be give with ascii escape sequences.
88 """
89 self.write(prompt)
90 # now we update our cursor giving end of prompt
91 self.current_prompt_pos = self.GetLength()
92 self.current_prompt_line = self.GetCurrentLine()
93
94 autoindent = self.indent * ' '
95 autoindent = autoindent.replace(' ','\t')
96 self.write(autoindent)
97
98
99 def replace_current_edit_buffer(self, text):
100 """ Replace currently entered command line with given text.
101 """
102 self.SetSelection(self.current_prompt_pos, self.GetLength())
103 self.ReplaceSelection(text)
104 self.GotoPos(self.GetLength())
105
106
107 def get_current_edit_buffer(self):
108 """ Returns the text in current edit buffer.
109 """
110 return self.GetTextRange(self.current_prompt_pos,
111 self.GetLength())
112
113
114 #--------------------------------------------------------------------------
115 # Private API
116 #--------------------------------------------------------------------------
117
118 def __init__(self, parent, pos=wx.DefaultPosition, ID=-1,
119 size=wx.DefaultSize, style=0,
120 autocomplete_mode='IPYTHON'):
121 """ Autocomplete_mode: Can be 'IPYTHON' or 'STC'
122 'IPYTHON' show autocompletion the ipython way
123 'STC" show it scintilla text control way
124 """
125 stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style)
126
127 #------ Scintilla configuration -----------------------------------
128
129 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
130 # the widget
131 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
132 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
133 # Also allow Ctrl Shift "=" for poor non US keyboard users.
134 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
135 stc.STC_CMD_ZOOMIN)
136
137 self.SetEOLMode(stc.STC_EOL_CRLF)
138 self.SetWrapMode(stc.STC_WRAP_CHAR)
139 self.SetWrapMode(stc.STC_WRAP_WORD)
140 self.SetBufferedDraw(True)
141 self.SetUseAntiAliasing(True)
142 self.SetLayoutCache(stc.STC_CACHE_PAGE)
143 self.SetUndoCollection(False)
144 self.SetUseTabs(True)
145 self.SetIndent(4)
146 self.SetTabWidth(4)
147
148 self.EnsureCaretVisible()
149
150 self.SetMargins(3, 3) #text is moved away from border with 3px
151 # Suppressing Scintilla margins
152 self.SetMarginWidth(0, 0)
153 self.SetMarginWidth(1, 0)
154 self.SetMarginWidth(2, 0)
155
156 self._apply_style()
157
158 self.indent = 0
159 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
160
161 # FIXME: we need to retrieve this from the interpreter.
162 self.prompt = \
163 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x026\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
164 self.new_prompt(self.prompt)
165
166 self.autocomplete_mode = autocomplete_mode
167
168 self.Bind(wx.EVT_KEY_DOWN, self._onKeypress)
169
170
171 def _apply_style(self):
172 """ Applies the colors for the different text elements and the
173 carret.
174 """
175 # FIXME: We need to do something for the fonts, but this is
176 # clearly not the right option.
177 #we define platform specific fonts
178 # if wx.Platform == '__WXMSW__':
179 # faces = { 'times': 'Times New Roman',
180 # 'mono' : 'Courier New',
181 # 'helv' : 'Arial',
182 # 'other': 'Comic Sans MS',
183 # 'size' : 10,
184 # 'size2': 8,
185 # }
186 # elif wx.Platform == '__WXMAC__':
187 # faces = { 'times': 'Times New Roman',
188 # 'mono' : 'Monaco',
189 # 'helv' : 'Arial',
190 # 'other': 'Comic Sans MS',
191 # 'size' : 10,
192 # 'size2': 8,
193 # }
194 # else:
195 # faces = { 'times': 'Times',
196 # 'mono' : 'Courier',
197 # 'helv' : 'Helvetica',
198 # 'other': 'new century schoolbook',
199 # 'size' : 10,
200 # 'size2': 8,
201 # }
202 # self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
203 # "fore:%s,back:%s,size:%d,face:%s"
204 # % (self.ANSI_STYLES['0;30'][1],
205 # self.background_color,
206 # faces['size'], faces['mono']))
207
208 self.SetCaretForeground(self.carret_color)
209
210 self.StyleClearAll()
211 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
212 "fore:#FF0000,back:#0000FF,bold")
213 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
214 "fore:#000000,back:#FF0000,bold")
215
216 for style in self.ANSI_STYLES.values():
217 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
218
219
220 def removeFromTo(self, from_pos, to_pos):
221 if from_pos < to_pos:
222 self.SetSelection(from_pos, to_pos)
223 self.DeleteBack()
224
225
226 def selectFromTo(self, from_pos, to_pos):
227 self.SetSelectionStart(from_pos)
228 self.SetSelectionEnd(to_pos)
229
230
231 def writeCompletion(self, possibilities):
232 if self.autocomplete_mode == 'IPYTHON':
233 max_len = len(max(possibilities, key=len))
234 max_symbol = ' '*max_len
235
236 #now we check how much symbol we can put on a line...
237 test_buffer = max_symbol + ' '*4
238
239 allowed_symbols = 80/len(test_buffer)
240 if allowed_symbols == 0:
241 allowed_symbols = 1
242
243 pos = 1
244 buf = ''
245 for symbol in possibilities:
246 #buf += symbol+'\n'#*spaces)
247 if pos < allowed_symbols:
248 spaces = max_len - len(symbol) + 4
249 buf += symbol+' '*spaces
250 pos += 1
251 else:
252 buf += symbol+'\n'
253 pos = 1
254 self.write(buf)
255 else:
256 possibilities.sort() # Python sorts are case sensitive
257 self.AutoCompSetIgnoreCase(False)
258 self.AutoCompSetAutoHide(False)
259 #let compute the length ot last word
260 splitter = [' ', '(', '[', '{']
261 last_word = self.get_current_edit_buffer()
262 for breaker in splitter:
263 last_word = last_word.split(breaker)[-1]
264 self.AutoCompShow(len(last_word), " ".join(possibilities))
265
266
267 def _onKeypress(self, event, skip=True):
268 """ Key press callback used for correcting behavior for
269 console-like interfaces: the cursor is constraint to be after
270 the last prompt.
271
272 Return True if event as been catched.
273 """
274 catched = False
275 if self.AutoCompActive():
276 event.Skip()
277 else:
278 if event.GetKeyCode() == wx.WXK_HOME:
279 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
280 self.GotoPos(self.current_prompt_pos)
281 catched = True
282
283 elif event.Modifiers == wx.MOD_SHIFT:
284 self.selectFromTo(self.current_prompt_pos,
285 self.GetCurrentPos())
286 catched = True
287
288 elif event.GetKeyCode() == wx.WXK_UP:
289 if self.GetCurrentLine() > self.current_prompt_line:
290 if self.GetCurrentLine() == self.current_prompt_line + 1 \
291 and self.GetColumn(self.GetCurrentPos()) < \
292 self.GetColumn(self.current_prompt_pos):
293 self.GotoPos(self.current_prompt_pos)
294 else:
295 event.Skip()
296 catched = True
297
298 elif event.GetKeyCode() in (wx.WXK_LEFT, wx.WXK_BACK):
299 if self.GetCurrentPos() > self.current_prompt_pos:
300 event.Skip()
301 catched = True
302
303 if skip and not catched:
304 event.Skip()
305
306 if event.GetKeyCode() not in (wx.WXK_PAGEUP, wx.WXK_PAGEDOWN)\
307 and event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN,
308 wx.MOD_SHIFT):
309 # If cursor is outside the editing region, put it back.
310 if self.GetCurrentPos() < self.current_prompt_pos:
311 self.GotoPos(self.current_prompt_pos)
312
313 return catched
314
315
316 def OnUpdateUI(self, evt):
317 # check for matching braces
318 braceAtCaret = -1
319 braceOpposite = -1
320 charBefore = None
321 caretPos = self.GetCurrentPos()
322
323 if caretPos > 0:
324 charBefore = self.GetCharAt(caretPos - 1)
325 styleBefore = self.GetStyleAt(caretPos - 1)
326
327 # check before
328 if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
329 braceAtCaret = caretPos - 1
330
331 # check after
332 if braceAtCaret < 0:
333 charAfter = self.GetCharAt(caretPos)
334 styleAfter = self.GetStyleAt(caretPos)
335
336 if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
337 braceAtCaret = caretPos
338
339 if braceAtCaret >= 0:
340 braceOpposite = self.BraceMatch(braceAtCaret)
341
342 if braceAtCaret != -1 and braceOpposite == -1:
343 self.BraceBadLight(braceAtCaret)
344 else:
345 self.BraceHighlight(braceAtCaret, braceOpposite)
346
347
348 if __name__ == '__main__':
349 # Some simple code to test the console widget.
350 class MainWindow(wx.Frame):
351 def __init__(self, parent, id, title):
352 wx.Frame.__init__(self, parent, id, title, size=(300,250))
353 self._sizer = wx.BoxSizer(wx.VERTICAL)
354 self.console_widget = ConsoleWidget(self)
355 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
356 self.SetSizer(self._sizer)
357 self.SetAutoLayout(1)
358 self.Show(True)
359
360 app = wx.PySimpleApp()
361 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
362 w.SetSize((780, 460))
363 w.Show()
364
365 app.MainLoop()
366
367
General Comments 0
You need to be logged in to leave comments. Login now