mwxlib 1.0.0__py3-none-any.whl → 1.7.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mwx/nutshell.py CHANGED
@@ -1,9 +1,10 @@
1
1
  #! python3
2
2
  """mwxlib Nautilus in the shell.
3
3
  """
4
+ from contextlib import contextmanager
4
5
  from functools import wraps
5
6
  from importlib import import_module
6
- from contextlib import contextmanager
7
+ from pathlib import Path
7
8
  from pprint import pformat
8
9
  from bdb import BdbQuit
9
10
  import traceback
@@ -26,32 +27,28 @@ from wx.py.shell import Shell
26
27
  from wx.py.editwindow import EditWindow
27
28
 
28
29
  from .utilus import funcall as _F
29
- from .utilus import ignore
30
- from .utilus import split_words, split_paren, split_tokens, find_modules
30
+ from .utilus import typename, fix_fnchars
31
+ from .utilus import split_words, split_parts, split_tokens, find_modules
31
32
  from .framework import CtrlInterface, AuiNotebook, Menu
32
33
 
33
34
 
34
- ## URL pattern (flag = re.M | re.A)
35
- url_re = r"https?://[\w/:%#$&?()~.=+-]+"
35
+ ## URL pattern (flag = re.M | re.A).
36
+ # url_re = r"https?://[\w/:%#$&?()~.=+-]+"
37
+ url_re = r"https?://[\w/:%#$&?!@~.,;=+-]+" # excluding ()
36
38
 
37
- ## no-file pattern
38
- nofile_re = r'[\/:*?"<>|]'
39
-
40
- ## Python syntax pattern
39
+ ## Python syntax patterns.
41
40
  py_indent_re = r"if|else|elif|for|while|with|def|class|try|except|finally"
42
41
  py_outdent_re = r"else:|elif\s+.*:|except(\s+.*)?:|finally:"
43
42
  py_closing_re = r"break|pass|return|raise|continue"
44
43
 
45
- ## Python interp traceback pattern
46
- py_error_re = r' +File "(.*?)", line ([0-9]+)'
47
- py_frame_re = r" +file '(.*?)', line ([0-9]+)"
48
- py_where_re = r'> +([^*?"<>|\r\n]+?):([0-9]+)'
49
- py_break_re = r'at ([^*?"<>|\r\n]+?):([0-9]+)'
44
+ ## Python traceback pattern.
45
+ py_trace_re = r' +File "(.*?)", line (\d+)'
50
46
 
51
- ## Custom constants in wx.stc
47
+ ## Custom constants in wx.stc.
52
48
  stc.STC_STYLE_CARETLINE = 40
53
49
  stc.STC_STYLE_ANNOTATION = 41
54
50
 
51
+
55
52
  class Stylus:
56
53
  py_log_mode = {
57
54
  stc.STC_STYLE_DEFAULT : "fore:#7f7f7f,back:#ffffb8,size:9,face:MS Gothic",
@@ -59,19 +56,20 @@ class Stylus:
59
56
  stc.STC_STYLE_BRACELIGHT : "fore:#000000,back:#ffffb8,bold",
60
57
  stc.STC_STYLE_BRACEBAD : "fore:#000000,back:#ff0000,bold",
61
58
  stc.STC_STYLE_CONTROLCHAR : "size:6",
62
- stc.STC_STYLE_CARETLINE : "fore:#000000,back:#ffff7f,size:2", # optional
63
- stc.STC_STYLE_ANNOTATION : "fore:#7f0000,back:#ff7f7f", # optional
59
+ stc.STC_STYLE_INDENTGUIDE : "",
60
+ stc.STC_STYLE_CARETLINE : "fore:#000000,back:#ffff7f,size:2", # optional
61
+ stc.STC_STYLE_ANNOTATION : "fore:#7f0000,back:#ff7f7f", # optional
64
62
  stc.STC_P_DEFAULT : "fore:#000000",
65
63
  stc.STC_P_OPERATOR : "fore:#000000",
66
64
  stc.STC_P_IDENTIFIER : "fore:#000000",
67
65
  stc.STC_P_COMMENTLINE : "fore:#007f7f,back:#ffcfcf",
68
66
  stc.STC_P_COMMENTBLOCK : "fore:#007f7f,back:#ffcfcf,eol",
69
67
  stc.STC_P_NUMBER : "fore:#7f0000",
70
- stc.STC_P_STRINGEOL : "fore:#000000,back:#ffcfcf",
71
68
  stc.STC_P_CHARACTER : "fore:#7f7f7f",
72
69
  stc.STC_P_STRING : "fore:#7f7f7f",
73
70
  stc.STC_P_TRIPLE : "fore:#7f7f7f",
74
71
  stc.STC_P_TRIPLEDOUBLE : "fore:#7f7f7f",
72
+ stc.STC_P_STRINGEOL : "fore:#000000,back:#ffcfcf,eol",
75
73
  stc.STC_P_CLASSNAME : "fore:#7f00ff,bold",
76
74
  stc.STC_P_DEFNAME : "fore:#0000ff,bold",
77
75
  stc.STC_P_WORD : "fore:#0000ff",
@@ -85,19 +83,20 @@ class Stylus:
85
83
  stc.STC_STYLE_BRACELIGHT : "fore:#000000,back:#cccccc,bold",
86
84
  stc.STC_STYLE_BRACEBAD : "fore:#000000,back:#ff0000,bold",
87
85
  stc.STC_STYLE_CONTROLCHAR : "size:6",
88
- stc.STC_STYLE_CARETLINE : "fore:#000000,back:#f0f0ff,size:2", # optional
89
- stc.STC_STYLE_ANNOTATION : "fore:#7f0000,back:#ff7f7f", # optional
86
+ stc.STC_STYLE_INDENTGUIDE : "",
87
+ stc.STC_STYLE_CARETLINE : "fore:#000000,back:#f0f0ff,size:2", # optional
88
+ stc.STC_STYLE_ANNOTATION : "fore:#7f0000,back:#ff7f7f", # optional
90
89
  stc.STC_P_DEFAULT : "fore:#000000",
91
90
  stc.STC_P_OPERATOR : "fore:#000000",
92
91
  stc.STC_P_IDENTIFIER : "fore:#000000",
93
92
  stc.STC_P_COMMENTLINE : "fore:#007f00,back:#f0fff0",
94
93
  stc.STC_P_COMMENTBLOCK : "fore:#007f00,back:#f0fff0,eol",
95
94
  stc.STC_P_NUMBER : "fore:#e02000",
96
- stc.STC_P_STRINGEOL : "fore:#7f7f7f,back:#ffc0c0,eol",
97
95
  stc.STC_P_CHARACTER : "fore:#7f7f7f",
98
96
  stc.STC_P_STRING : "fore:#7f7f7f",
99
97
  stc.STC_P_TRIPLE : "fore:#7f7f7f",
100
98
  stc.STC_P_TRIPLEDOUBLE : "fore:#7f7f7f",
99
+ stc.STC_P_STRINGEOL : "fore:#7f7f7f,back:#ffc0c0,eol",
101
100
  stc.STC_P_CLASSNAME : "fore:#7f00ff,bold",
102
101
  stc.STC_P_DEFNAME : "fore:#0000ff,bold",
103
102
  stc.STC_P_WORD : "fore:#0000ff",
@@ -111,19 +110,20 @@ class Stylus:
111
110
  stc.STC_STYLE_BRACELIGHT : "fore:#ffffff,back:#202020,bold",
112
111
  stc.STC_STYLE_BRACEBAD : "fore:#ffffff,back:#ff0000,bold",
113
112
  stc.STC_STYLE_CONTROLCHAR : "size:6",
114
- stc.STC_STYLE_CARETLINE : "fore:#ffffff,back:#123460,size:2", # optional
115
- stc.STC_STYLE_ANNOTATION : "fore:#7f0000,back:#ff7f7f", # optional
113
+ stc.STC_STYLE_INDENTGUIDE : "",
114
+ stc.STC_STYLE_CARETLINE : "fore:#ffffff,back:#123460,size:2", # optional
115
+ stc.STC_STYLE_ANNOTATION : "fore:#7f0000,back:#ff7f7f", # optional
116
116
  stc.STC_P_DEFAULT : "fore:#cccccc",
117
117
  stc.STC_P_OPERATOR : "fore:#cccccc",
118
118
  stc.STC_P_IDENTIFIER : "fore:#cccccc",
119
119
  stc.STC_P_COMMENTLINE : "fore:#42c18c,back:#004040",
120
120
  stc.STC_P_COMMENTBLOCK : "fore:#42c18c,back:#004040,eol",
121
121
  stc.STC_P_NUMBER : "fore:#ffc080",
122
- stc.STC_P_STRINGEOL : "fore:#cccccc,back:#004040,eol",
123
122
  stc.STC_P_CHARACTER : "fore:#a0a0a0",
124
- stc.STC_P_STRING : "fore:#a0a0a0",
125
- stc.STC_P_TRIPLE : "fore:#a0a0a0,back:#004040",
126
- stc.STC_P_TRIPLEDOUBLE : "fore:#a0a0a0,back:#004040",
123
+ stc.STC_P_STRING : "fore:#a0c0ff",
124
+ stc.STC_P_TRIPLE : "fore:#a0a0a0",
125
+ stc.STC_P_TRIPLEDOUBLE : "fore:#a0c0ff",
126
+ stc.STC_P_STRINGEOL : "fore:#cccccc,back:#400000,eol",
127
127
  stc.STC_P_CLASSNAME : "fore:#61d6d6,bold",
128
128
  stc.STC_P_DEFNAME : "fore:#3a96ff,bold",
129
129
  stc.STC_P_WORD : "fore:#80c0ff",
@@ -136,7 +136,7 @@ def skip(evt):
136
136
  evt.Skip()
137
137
 
138
138
 
139
- def can_edit(f):
139
+ def editable(f):
140
140
  @wraps(f)
141
141
  def _f(self, *v, **kw):
142
142
  if self.CanEdit():
@@ -147,10 +147,14 @@ def can_edit(f):
147
147
  def ask(f, prompt="Enter value", type=str):
148
148
  """Get response from the user using a dialog box."""
149
149
  @wraps(f)
150
- def _f(*v):
151
- with wx.TextEntryDialog(None, prompt, f.__name__) as dlg:
150
+ def _f(evt, value=''):
151
+ with wx.TextEntryDialog(None, prompt, f.__name__, value) as dlg:
152
152
  if dlg.ShowModal() == wx.ID_OK:
153
- return f(type(dlg.Value))
153
+ value = dlg.Value
154
+ try:
155
+ return f(type(value))
156
+ except ValueError:
157
+ return _f(evt, value) # show the prompt again
154
158
  return _f
155
159
 
156
160
 
@@ -170,15 +174,15 @@ class AutoCompInterfaceMixin:
170
174
  """
171
175
  history = [] # used in history-comp mode
172
176
  modules = set() # used in module-comp mode
173
- fragmwords = set(keyword.kwlist + dir(builtins)) # used in text-comp mode
174
-
177
+ fragmwords = set(keyword.kwlist + dir(builtins)) # used in text-comp mode
178
+
175
179
  def __init__(self):
176
180
  ## cf. sys.modules
177
181
  if not self.modules:
178
182
  force = wx.GetKeyState(wx.WXK_CONTROL)\
179
183
  & wx.GetKeyState(wx.WXK_SHIFT)
180
184
  AutoCompInterfaceMixin.modules = set(find_modules(force))
181
-
185
+
182
186
  def CallTipShow(self, pos, tip, N=11):
183
187
  """Show a call tip containing a definition near position pos.
184
188
 
@@ -191,11 +195,14 @@ class AutoCompInterfaceMixin:
191
195
  lines[N+1:] = ["\n...(snip) This tips are too long... "
192
196
  "Click to show more details."]
193
197
  tip = '\n'.join(lines)
194
- self._calltips[-1] = True # snipped (needs to be shown)
198
+ self._calltips[-1] = True # snipped (needs to be shown)
195
199
  super().CallTipShow(pos, tip)
196
-
200
+
197
201
  def autoCallTipShow(self, command, insertcalltip=True):
198
- """Display argument spec and docstring in a popup window."""
202
+ """Display argument spec and docstring in a popup window.
203
+
204
+ (override) Fix cursor position on calltip insertion.
205
+ """
199
206
  if self.CallTipActive():
200
207
  self.CallTipCancel()
201
208
 
@@ -204,50 +211,50 @@ class AutoCompInterfaceMixin:
204
211
  dispatcher.send(signal='Shell.calltip', sender=self, calltip=tip)
205
212
  p = self.cpos
206
213
  if argspec and insertcalltip:
207
- self.AddText(argspec + ')') # 挿入後のカーソル位置は変化しない
208
- self.SetSelection(self.cpos, p) # selection back
214
+ self.AddText(argspec + ')')
215
+ self.cpos = p # selection backward to the point
209
216
  if tip:
210
217
  ## In case there isn't enough room, only go back to bol fallback.
211
218
  tippos = max(self.bol, p - len(name) - 1)
212
219
  self.CallTipShow(tippos, tip)
213
-
220
+
214
221
  def call_helpDoc(self, evt):
215
222
  """Show help:str for the selected topic."""
216
223
  if self.CallTipActive():
217
224
  self.CallTipCancel()
218
225
 
219
- text = next(self.gen_text_at_caret(), None)
226
+ text = self.SelectedText or self.expr_at_caret or self.line_at_caret
220
227
  if text:
221
228
  text = introspect.getRoot(text, terminator='(')
222
229
  try:
223
230
  obj = self.eval(text)
224
231
  self.help(obj)
225
232
  except Exception as e:
226
- self.message("- {} : {!r}".format(e, text))
227
-
233
+ self.message(e)
234
+
228
235
  def call_helpTip(self, evt):
229
236
  """Show a calltip for the selected function."""
230
237
  if self.CallTipActive():
231
238
  self.CallTipCancel()
232
239
 
233
- text = next(self.gen_text_at_caret(), None)
240
+ text = self.SelectedText or self.expr_at_caret or self.line_at_caret
234
241
  if text:
235
242
  p = self.cpos
236
243
  self.autoCallTipShow(text,
237
- p == self.eol and self.get_char(p-1) == '(') # => CallTipShow
238
-
244
+ p == self.eol and self.get_char(p-1) == '(') # => CallTipShow
245
+
239
246
  def on_completion_forward(self, evt):
240
247
  if not self.AutoCompActive():
241
248
  self.handler('quit', evt)
242
249
  return
243
250
  self._on_completion(1)
244
-
251
+
245
252
  def on_completion_backward(self, evt):
246
253
  if not self.AutoCompActive():
247
254
  self.handler('quit', evt)
248
255
  return
249
256
  self._on_completion(-1)
250
-
257
+
251
258
  def _on_completion(self, step=0):
252
259
  """Show completion with selection."""
253
260
  try:
@@ -258,18 +265,18 @@ class AutoCompInterfaceMixin:
258
265
  n = len(self.__comp_hint)
259
266
  p = self.cpos
260
267
  if not self.SelectedText:
261
- p, q, sty = self.get_following_atom(p) # word-right-selection
268
+ p, q, sty = self.get_following_atom(p) # word-right-selection
262
269
  if sty == 'word':
263
270
  self.anchor = q
264
271
  with self.off_undocollection():
265
272
  self.ReplaceSelection(word[n:])
266
- self.cpos = p # backward selection to the point
273
+ self.cpos = p # selection backward to the point
267
274
  self.__comp_ind = j
268
275
  except IndexError:
269
276
  self.message("No completion words")
270
-
277
+
271
278
  def _gen_autocomp(self, j, hint, words, sep=' ', mode=True):
272
- ## Prepare on_completion_forward/backward
279
+ ## Prepare on_completion_forward/backward.
273
280
  self.__comp_ind = j
274
281
  self.__comp_hint = hint
275
282
  self.__comp_words = words
@@ -279,12 +286,20 @@ class AutoCompInterfaceMixin:
279
286
  elif words:
280
287
  self.AutoCompSetSeparator(ord(sep))
281
288
  self.AutoCompShow(len(hint), sep.join(words))
282
-
289
+
283
290
  def _get_words_hint(self):
284
291
  cmdl = self.GetTextRange(self.bol, self.cpos)
285
- text = next(split_words(cmdl, reverse=1), '')
286
- return text.rpartition('.') # -> text, sep, hint
287
-
292
+ if cmdl.endswith(' '): # 前の文字が空白の場合はスキップする
293
+ text = ''
294
+ else:
295
+ text = next(split_words(cmdl, reverse=1), '')
296
+ return text.rpartition('.') # -> text, sep, hint
297
+
298
+ def clear_autocomp(self, evt):
299
+ if self.AutoCompActive():
300
+ self.AutoCompCancel()
301
+ self.message("")
302
+
288
303
  def call_history_comp(self, evt):
289
304
  """Called when history-comp mode."""
290
305
  if not self.CanEdit():
@@ -293,19 +308,19 @@ class AutoCompInterfaceMixin:
293
308
 
294
309
  cmdl = self.GetTextRange(self.bol, self.cpos)
295
310
  if cmdl.isspace() or self.bol != self.bolc:
296
- self.handler('skip', evt) # [tab pressed] => on_indent_line
311
+ self.handler('skip', evt) # [tab pressed] => on_indent_line
297
312
  return
298
313
 
299
314
  hint = cmdl.strip()
300
315
  ls = [x.replace('\n', os.linesep + sys.ps2)
301
- for x in self.history if x.startswith(hint)] # case-sensitive match
302
- words = sorted(set(ls), key=ls.index, reverse=0) # keep order, no duplication
316
+ for x in self.history if x.startswith(hint)] # case-sensitive match
317
+ words = sorted(set(ls), key=ls.index, reverse=0) # keep order, no duplication
303
318
 
304
- ## the latest history stacks in the head of the list (time-descending)
319
+ ## The latest history stacks in the head of the list (time-descending).
305
320
  self._gen_autocomp(0, hint, words, mode=False)
306
321
  self.message("[history] {} candidates matched"
307
322
  " with {!r}".format(len(words), hint))
308
-
323
+
309
324
  def call_text_autocomp(self, evt):
310
325
  """Called when text-comp mode."""
311
326
  if not self.CanEdit():
@@ -313,15 +328,17 @@ class AutoCompInterfaceMixin:
313
328
  return
314
329
 
315
330
  cmdl = self.GetTextRange(self.bol, self.cpos)
316
- hint = re.search(r"[\w.]*$", cmdl).group(0) # extract the last word
331
+ hint = re.search(r"[\w.]*$", cmdl).group(0) # extract the last word
317
332
 
318
- ls = [x for x in self.fragmwords if x.startswith(hint)] # case-sensitive match
319
- words = sorted(ls, key=lambda s:s.upper())
333
+ ## ls = [x for x in self.fragmwords if x.startswith(hint)] # case-sensitive match
334
+ q = hint.lower()
335
+ ls = [x for x in self.fragmwords if x.lower().startswith(q)] # case-insensitive match
336
+ words = sorted(ls, key=lambda s: s.upper())
320
337
 
321
338
  self._gen_autocomp(0, hint, words)
322
339
  self.message("[text] {} candidates matched"
323
340
  " with {!r}".format(len(words), hint))
324
-
341
+
325
342
  def call_module_autocomp(self, evt, force=False):
326
343
  """Called when module-comp mode."""
327
344
  if not self.CanEdit():
@@ -332,13 +349,14 @@ class AutoCompInterfaceMixin:
332
349
  if not hints.endswith(' '):
333
350
  h = hints.strip()
334
351
  if not h.endswith(','):
335
- lh = h.split(',')[-1].strip() # 'x, y, z|' last hint after ','
336
- if ' ' not in lh: # 'x, y as|' contains no spaces.
352
+ lh = h.split(',')[-1].strip() # 'x, y, z|' last hint after ','
353
+ if ' ' not in lh: # 'x, y as|' contains no spaces.
337
354
  return lh
338
355
 
339
356
  cmdl = self.GetTextRange(self.bol, self.cpos)
340
- hint = re.search(r"[\w.]*$", cmdl).group(0) # extract the last word
357
+ hint = re.search(r"[\w.]*$", cmdl).group(0) # extract the last word including dots
341
358
  try:
359
+ ## from * import ...
342
360
  if (m := re.match(r"from\s+([\w.]+)\s+import\s+(.*)", cmdl)):
343
361
  text, hints = m.groups()
344
362
  if not _continue(hints) and not force:
@@ -354,17 +372,18 @@ class AutoCompInterfaceMixin:
354
372
  self.message("\b failed.", e)
355
373
  return
356
374
  else:
357
- ## Add unimported module names.
358
- p = "{}.{}".format(text, hint)
359
- keys = [x[len(text)+1:] for x in self.modules if x.startswith(p)]
375
+ ## Add unimported module names (case-insensitive match).
376
+ q = f"{text}.{hint}".lower()
377
+ keys = [x[len(text)+1:] for x in self.modules if x.lower().startswith(q)]
360
378
  modules.update(k for k in keys if '.' not in k)
361
-
379
+ ## import ...
362
380
  elif (m := re.match(r"(import|from)\s+(.*)", cmdl)):
363
381
  text, hints = m.groups()
364
382
  if not _continue(hints) and not force:
365
383
  self.message("[module]>>> waiting for key input...")
366
384
  return
367
385
  modules = self.modules
386
+ ## Module X.Y.Z
368
387
  else:
369
388
  text, sep, hint = self._get_words_hint()
370
389
  obj = self.eval(text)
@@ -372,7 +391,7 @@ class AutoCompInterfaceMixin:
372
391
 
373
392
  P = re.compile(hint)
374
393
  p = re.compile(hint, re.I)
375
- words = sorted([x for x in modules if p.match(x)], key=lambda s:s.upper())
394
+ words = sorted([x for x in modules if p.match(x)], key=lambda s: s.upper())
376
395
 
377
396
  j = next((k for k, w in enumerate(words) if P.match(w)),
378
397
  next((k for k, w in enumerate(words) if p.match(w)), -1))
@@ -387,19 +406,24 @@ class AutoCompInterfaceMixin:
387
406
  self.message("- {} : {!r}".format(e, text))
388
407
  except Exception as e:
389
408
  self.message("- {} : {!r}".format(e, text))
390
-
409
+
391
410
  def call_word_autocomp(self, evt):
392
411
  """Called when word-comp mode."""
393
412
  if not self.CanEdit():
394
413
  self.handler('quit', evt)
395
414
  return
415
+
416
+ text, sep, hint = self._get_words_hint()
417
+ if not text:
418
+ self.handler('quit', evt)
419
+ self.message("- No autocompletion candidates or hints found.")
420
+ return
396
421
  try:
397
- text, sep, hint = self._get_words_hint()
422
+ ## dir = introspect.getAttributeNames # @TODO in wx ver 4.2.3
398
423
  obj = self.eval(text)
399
-
400
424
  P = re.compile(hint)
401
425
  p = re.compile(hint, re.I)
402
- words = sorted([x for x in dir(obj) if p.match(x)], key=lambda s:s.upper())
426
+ words = sorted([x for x in dir(obj) if p.match(x)], key=lambda s: s.upper())
403
427
 
404
428
  j = next((k for k, w in enumerate(words) if P.match(w)),
405
429
  next((k for k, w in enumerate(words) if p.match(w)), -1))
@@ -414,19 +438,24 @@ class AutoCompInterfaceMixin:
414
438
  self.message("- {} : {!r}".format(e, text))
415
439
  except Exception as e:
416
440
  self.message("- {} : {!r}".format(e, text))
417
-
441
+
418
442
  def call_apropos_autocomp(self, evt):
419
443
  """Called when apropos mode."""
420
444
  if not self.CanEdit():
421
445
  self.handler('quit', evt)
422
446
  return
447
+
448
+ text, sep, hint = self._get_words_hint()
449
+ if not text:
450
+ self.handler('quit', evt)
451
+ self.message("- No autocompletion candidates or hints found.")
452
+ return
423
453
  try:
424
- text, sep, hint = self._get_words_hint()
454
+ ## dir = introspect.getAttributeNames # @TODO in wx ver 4.2.3.
425
455
  obj = self.eval(text)
426
-
427
456
  P = re.compile(hint)
428
457
  p = re.compile(hint, re.I)
429
- words = sorted([x for x in dir(obj) if p.search(x)], key=lambda s:s.upper())
458
+ words = sorted([x for x in dir(obj) if p.search(x)], key=lambda s: s.upper())
430
459
 
431
460
  j = next((k for k, w in enumerate(words) if P.match(w)),
432
461
  next((k for k, w in enumerate(words) if p.match(w)), -1))
@@ -450,8 +479,8 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
450
479
  This class is mixed-in ``wx.stc.StyledTextCtrl``.
451
480
  """
452
481
  def __init__(self):
453
- CtrlInterface.__init__(self)
454
482
  AutoCompInterfaceMixin.__init__(self)
483
+ CtrlInterface.__init__(self)
455
484
 
456
485
  def dispatch(evt):
457
486
  """Fork events to the parent."""
@@ -468,7 +497,7 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
468
497
  'pointer_unset' : [ None, dispatch ],
469
498
  },
470
499
  0 : {
471
- 'insert pressed' : (0, _F(self.over, None, doc="toggle-over")),
500
+ 'insert pressed' : (0, _F(self.over, mode=None, doc="toggle-over")),
472
501
  'C-left pressed' : (0, _F(self.WordLeft)),
473
502
  'C-right pressed' : (0, _F(self.WordRightEnd)),
474
503
  'C-S-left pressed' : (0, _F(self.selection_backward_word_or_paren)),
@@ -479,29 +508,29 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
479
508
  'C-e pressed' : (0, _F(self.end_of_line)),
480
509
  'M-a pressed' : (0, _F(self.back_to_indentation)),
481
510
  'M-e pressed' : (0, _F(self.end_of_line)),
482
- 'M-g pressed' : (0, ask(self.goto_line, "Line to goto:", lambda x:int(x)-1),
511
+ 'M-g pressed' : (0, ask(self.goto_line, "Line to goto:", lambda x: int(x)-1),
483
512
  _F(self.recenter)),
484
513
  'M-f pressed' : (10, _F(self.filter_text), self.on_itext_enter),
485
514
  'C-k pressed' : (0, _F(self.kill_line)),
486
515
  'C-S-c pressed' : (0, _F(self.Copy)),
487
516
  'C-S-v pressed' : (0, _F(self.Paste)),
488
517
  'C-l pressed' : (0, _F(self.recenter)),
489
- 'C-S-l pressed' : (0, _F(self.recenter)), # overrides delete-line
490
- 'C-S-f pressed' : (0, _F(self.set_mark)), # overrides mark
518
+ # 'C-S-l pressed' : (0, _F(self.recenter)), # overrides delete-line
519
+ # 'C-S-f pressed' : (0, _F(self.set_mark)), # overrides mark
491
520
  'C-space pressed' : (0, _F(self.set_mark)),
492
- 'C-S-space pressed' : (0, _F(self.set_pointer)),
493
- 'C-backspace pressed' : (0, skip),
521
+ 'C-S-space pressed' : (0, _F(self.toggle_pointer)),
522
+ 'C-backspace pressed' : (0, _F(self.backward_kill_word)),
494
523
  'S-backspace pressed' : (0, _F(self.backward_kill_line)),
524
+ 'C-delete pressed' : (0, _F(self.kill_word)),
525
+ 'S-delete pressed' : (0, _F(self.kill_line)),
495
526
  'C-tab pressed' : (0, _F(self.insert_space_like_tab)),
496
527
  'C-S-tab pressed' : (0, _F(self.delete_backward_space_like_tab)),
497
528
  'tab pressed' : (0, self.on_indent_line),
498
529
  'S-tab pressed' : (0, self.on_outdent_line),
499
- ## 'C-/ pressed' : (0, ), # cf. C-a home
500
- ## 'C-\ pressed' : (0, ), # cf. C-e end
530
+ # 'C-/ pressed' : (0, ), # cf. C-a home
531
+ # 'C-\ pressed' : (0, ), # cf. C-e end
501
532
  'C-; pressed' : (0, _F(self.comment_out_line)),
502
- 'C-S-; pressed' : (0, _F(self.comment_out_line)),
503
533
  'C-: pressed' : (0, _F(self.uncomment_line)),
504
- 'C-S-: pressed' : (0, _F(self.uncomment_line)),
505
534
  'select_line' : (11, self.on_linesel_begin),
506
535
  'select_lines' : (11, self.on_linesel_next),
507
536
  },
@@ -519,9 +548,9 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
519
548
  },
520
549
  })
521
550
 
522
- ## cf. wx.py.editwindow.EditWindow.OnUpdateUI => Check for brace matching
551
+ ## cf. wx.py.editwindow.EditWindow.OnUpdateUI => Check for brace matching.
523
552
  self.Bind(stc.EVT_STC_UPDATEUI,
524
- lambda v: self.match_paren()) # no skip
553
+ lambda v: self.match_paren()) # no skip
525
554
 
526
555
  def eof(evt):
527
556
  p = evt.Position
@@ -543,100 +572,100 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
543
572
  ss |= set(x for x in dir(obj) if x.startswith('__'))
544
573
  return ss
545
574
 
546
- ## Keyword(2) setting
575
+ ## Keyword(2) setting.
547
576
  self.SetLexer(stc.STC_LEX_PYTHON)
548
577
  self.SetKeyWords(0, ' '.join(keyword.kwlist))
549
578
  self.SetKeyWords(1, ' '.join(builtins.__dict__)
550
579
  + ' '.join(_dunders(type, int, float, str, bytes,
551
580
  tuple, list, range, operator,))
552
- + ' self this')
581
+ + ' self this shell')
553
582
 
554
- ## AutoComp setting
583
+ ## AutoComp setting.
555
584
  self.AutoCompSetAutoHide(False)
556
585
  self.AutoCompSetIgnoreCase(True)
557
586
  self.AutoCompSetMaxWidth(80)
558
587
  self.AutoCompSetMaxHeight(10)
559
588
 
560
- ## To prevent @filling crash (Never access to DropTarget)
561
- ## [BUG 4.1.1] Don't allow DnD of text, file, whatever.
589
+ ## To prevent @filling crash (Never access to DropTarget).
590
+ ## [BUG ver 4.1.1] Don't allow DnD of text, file, whatever.
562
591
  ## self.SetDropTarget(None)
563
592
 
564
593
  self.Bind(stc.EVT_STC_START_DRAG, self.OnDrag)
565
594
  self.Bind(stc.EVT_STC_DRAG_OVER, self.OnDragging)
566
595
  self.Bind(stc.EVT_STC_DO_DROP, self.OnDragged)
567
596
 
568
- ## Global style for all languages
597
+ ## Global style for all languages.
569
598
  ## font = wx.Font(9, wx.MODERN, wx.NORMAL, wx.NORMAL, False, "MS Gothic")
570
599
  ## self.StyleSetFont(stc.STC_STYLE_DEFAULT, font)
571
-
572
600
  ## self.StyleClearAll()
573
- ## self.SetSelForeground(True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT))
574
- ## self.SetSelBackground(True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT))
601
+ ## => STC_STYLE_DEFAULT に設定したスタイルを他のスタイルに全適用する.
575
602
 
576
- ## The magin style for line numbers and symbols
603
+ ## The magin style for line numbers and symbols.
577
604
  ## [0] for markers, 10 pixels wide, mask 0b11111
578
605
  ## [1] for numbers, 32 pixels wide, mask 0x01ffffff (~stc.STC_MASK_FOLDERS)
579
606
  ## [2] for borders, 1 pixels wide, mask 0xfe000000 ( stc.STC_MASK_FOLDERS)
580
-
607
+ ##
608
+ ## cf. `EditWindow.setDisplayLineNumbers`
609
+ ##
581
610
  ## 32 bit margin mask
582
611
  ## [0] 1111,1111,1111,1111,1111,1111,1111,1111 = -1 for all markers
583
612
  ## [1] 0000,0001,1111,1111,1111,1111,1111,1111 = 0x01ffffff for markers
584
613
  ## [2] 1111,1110,0000,0000,0000,0000,0000,0000 = 0xfe000000 for folders
585
614
 
586
615
  self.SetMarginType(0, stc.STC_MARGIN_SYMBOL)
587
- self.SetMarginMask(0, 0b00111) # mask for markers (0,1,2)
616
+ self.SetMarginMask(0, 0b00111) # mask for markers (0,1,2)
588
617
  self.SetMarginWidth(0, 10)
589
618
  self.SetMarginSensitive(0, False)
590
619
 
591
620
  self.SetMarginType(1, stc.STC_MARGIN_NUMBER)
592
- self.SetMarginMask(1, 0b11000) # mask for pointer (3,4)
621
+ self.SetMarginMask(1, 0b11000) # mask for pointer (3,4)
593
622
  self.SetMarginWidth(1, 32)
594
623
  self.SetMarginSensitive(1, False)
595
624
 
596
625
  self.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
597
- self.SetMarginMask(2, stc.STC_MASK_FOLDERS) # mask for folders
626
+ self.SetMarginMask(2, stc.STC_MASK_FOLDERS) # mask for folders
598
627
  self.SetMarginWidth(2, 1)
599
628
  self.SetMarginSensitive(2, False)
600
629
 
601
- self.SetMarginLeft(2) # +1 margin at the left
630
+ self.SetMarginLeft(2) # +1 margin at the left
602
631
 
603
- self.SetFoldFlags(0x10) # draw below if not expanded
632
+ self.SetFoldFlags(stc.STC_FOLDFLAG_LINEAFTER_CONTRACTED)
604
633
 
605
- self.SetProperty('fold', '1') # Enable folder property
634
+ self.SetProperty('fold', '1') # Enable folder property
606
635
 
607
636
  self.Bind(stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
608
637
  self.Bind(stc.EVT_STC_MARGIN_RIGHT_CLICK, self.OnMarginRClick)
609
638
 
610
- ## Custom markers
611
- self.MarkerDefine(0, stc.STC_MARK_CIRCLE, '#007ff0', '#007ff0') # o mark
612
- self.MarkerDefine(1, stc.STC_MARK_ARROW, '#000000', '#ffffff') # > arrow
613
- self.MarkerDefine(2, stc.STC_MARK_ARROW, '#7f0000', '#ff0000') # > red-arrow
614
- self.MarkerDefine(3, stc.STC_MARK_SHORTARROW, 'blue', 'gray') # -> pointer
615
- self.MarkerDefine(4, stc.STC_MARK_SHORTARROW, 'red', 'yellow') # -> red-pointer
639
+ ## Custom markers.
640
+ self.MarkerDefine(0, stc.STC_MARK_CIRCLE, '#007ff0', '#007ff0') # o mark
641
+ self.MarkerDefine(1, stc.STC_MARK_ARROW, '#000000', '#ffffff') # > arrow
642
+ self.MarkerDefine(2, stc.STC_MARK_ARROW, '#7f0000', '#ff0000') # > red-arrow
643
+ self.MarkerDefine(3, stc.STC_MARK_SHORTARROW, 'blue', 'gray') # -> pointer
644
+ self.MarkerDefine(4, stc.STC_MARK_SHORTARROW, 'red', 'yellow') # -> red-pointer
616
645
 
617
646
  v = ('white', 'black')
618
647
  self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_BOXMINUS, *v)
619
648
  self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_BOXPLUS, *v)
620
649
  self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, *v)
621
650
  self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNER, *v)
622
- ## self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_TCORNER, *v)
623
- ## self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_TCORNER, *v)
651
+ # self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_TCORNER, *v)
652
+ # self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_TCORNER, *v)
624
653
  self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_VLINE, *v)
625
654
  self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_VLINE, *v)
626
655
  self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_VLINE, *v)
627
656
 
628
- ## Custom indicators ([BUG] indicator=1 is reset when the buffer is udpated.)
657
+ ## Custom indicators ([BUG] indicator=1 is reset when the buffer is updated).
629
658
  ## [10-11] filter_text
630
- ## [2] URL for load_file
659
+ ## [2] URL
631
660
  ## [3] match_paren
632
661
  self.IndicatorSetStyle(10, stc.STC_INDIC_TEXTFORE)
633
662
  self.IndicatorSetForeground(10, "red")
634
663
 
635
664
  self.IndicatorSetStyle(11, stc.STC_INDIC_STRAIGHTBOX)
665
+ self.IndicatorSetForeground(11, "yellow")
636
666
  self.IndicatorSetUnder(11, True)
637
- self.IndicatorSetAlpha(11, 60)
638
- self.IndicatorSetOutlineAlpha(11, 60)
639
- self.IndicatorSetForeground(11, "light gray")
667
+ # self.IndicatorSetAlpha(11, 0xe8)
668
+ # self.IndicatorSetOutlineAlpha(11, 0)
640
669
 
641
670
  self.IndicatorSetStyle(2, stc.STC_INDIC_DOTS)
642
671
  self.IndicatorSetForeground(2, "light gray")
@@ -649,52 +678,50 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
649
678
  self.IndicatorSetStyle(3, stc.STC_INDIC_DOTS)
650
679
  self.IndicatorSetForeground(3, "light gray")
651
680
 
652
- ## Custom annotation
681
+ ## Custom annotation.
653
682
  self.AnnotationSetVisible(stc.STC_ANNOTATION_BOXED)
654
683
 
655
- ## Custom style of control-char, wrap-mode
656
- ## self.UseTabs = False
657
- ## self.ViewEOL = False
658
- ## self.ViewWhiteSpace = False
659
- ## self.TabWidth = 4
660
- ## self.EOLMode = stc.STC_EOL_CRLF
684
+ ## Custom style of control-char, wrap-mode.
685
+ # self.UseTabs = False
686
+ # self.ViewEOL = False
687
+ # self.ViewWhiteSpace = False
688
+ # self.TabWidth = 4
689
+ # self.EOLMode = stc.STC_EOL_CRLF
661
690
  self.WrapMode = stc.STC_WRAP_NONE
662
691
  self.WrapIndentMode = stc.STC_WRAPINDENT_SAME
663
692
  self.IndentationGuides = stc.STC_IV_LOOKFORWARD
664
693
 
665
694
  self.__mark = -1
666
695
  self.__stylus = {}
667
-
668
- def OnDrag(self, evt): #<wx._core.StyledTextEvent>
669
- EditorInterface.__dnd_from = evt.EventObject
670
- try:
671
- EditorInterface.__dnd_flag = (evt.Position < self.bolc) # force copy
672
- except AttributeError:
696
+
697
+ __dnd_flag = 0
698
+
699
+ def OnDrag(self, evt): #<wx._core.StyledTextEvent>
700
+ if isinstance(self, Shell):
701
+ EditorInterface.__dnd_flag = (evt.Position < self.bolc) # readonly
702
+ else:
673
703
  EditorInterface.__dnd_flag = 0
674
704
  evt.Skip()
675
-
676
- def OnDragging(self, evt): #<wx._core.StyledTextEvent>
677
- _from = EditorInterface.__dnd_from
678
- _to = evt.EventObject
679
- if isinstance(_from, Shell) and _from is not _to: # from shell to buffer
680
- wx.UIActionSimulator().KeyDown(wx.WXK_CONTROL) # force copy
681
- try:
682
- if evt.Position < self.bolc:
683
- evt.DragResult = wx.DragNone # Don't drop (as readonly)
705
+
706
+ def OnDragging(self, evt): #<wx._core.StyledTextEvent>
707
+ if isinstance(self, Shell):
708
+ if evt.Position < self.bolc: # target is readonly
709
+ evt.DragResult = wx.DragNone
684
710
  elif EditorInterface.__dnd_flag:
685
- evt.DragResult = wx.DragCopy # Don't move
686
- except AttributeError:
687
- pass
711
+ ## from shell to shell
712
+ evt.DragResult = wx.DragCopy if wx.GetKeyState(wx.WXK_CONTROL) else wx.DragNone
713
+ else:
714
+ if EditorInterface.__dnd_flag:
715
+ ## from shell to buffer
716
+ evt.DragResult = wx.DragCopy if wx.GetKeyState(wx.WXK_CONTROL) else wx.DragNone
688
717
  evt.Skip()
689
-
690
- def OnDragged(self, evt): #<wx._core.StyledTextEvent>
691
- EditorInterface.__dnd_from = None
718
+
719
+ def OnDragged(self, evt): #<wx._core.StyledTextEvent>
692
720
  EditorInterface.__dnd_flag = 0
693
- wx.UIActionSimulator().KeyUp(wx.WXK_CONTROL)
694
721
  evt.Skip()
695
-
722
+
696
723
  ## --------------------------------
697
- ## Marker attributes of the editor
724
+ ## Marker attributes of the editor.
698
725
  ## --------------------------------
699
726
  marker_names = {
700
727
  0: "mark",
@@ -703,112 +730,116 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
703
730
  3: "pointer",
704
731
  4: "red-pointer",
705
732
  }
706
-
733
+
707
734
  def get_marker(self, n):
708
735
  return self.MarkerNext(0, 1<<n)
709
-
736
+
710
737
  def set_marker(self, line, n):
711
738
  if line != -1:
712
739
  self.MarkerDeleteAll(n)
713
740
  self.add_marker(line, n)
714
741
  else:
715
742
  self.del_marker(n)
716
-
743
+
717
744
  def add_marker(self, line, n):
718
745
  if self.MarkerAdd(line, n):
719
- self.EnsureVisible(line) # expand if folded
746
+ self.EnsureVisible(line) # expand if folded
720
747
  self.handler('{}_set'.format(self.marker_names[n]), line)
721
-
748
+
722
749
  def del_marker(self, n):
723
750
  line = self.MarkerNext(0, 1<<n)
724
751
  if line != -1:
725
752
  self.MarkerDeleteAll(n)
726
753
  self.handler('{}_unset'.format(self.marker_names[n]), line)
727
-
754
+
728
755
  def goto_marker(self, markerMask, selection=False):
729
756
  line = self.MarkerNext(0, markerMask)
730
757
  if line != -1:
731
- self.EnsureVisible(line) # expand if folded
758
+ self.EnsureVisible(line) # expand if folded
732
759
  self.goto_line(line, selection)
733
760
  self.recenter()
734
-
761
+
735
762
  def goto_next_marker(self, markerMask, selection=False):
736
763
  line = self.MarkerNext(self.cline+1, markerMask)
737
764
  if line == -1:
738
765
  line = self.LineCount
739
766
  self.goto_line(line, selection)
740
-
767
+
741
768
  def goto_previous_marker(self, markerMask, selection=False):
742
769
  line = self.MarkerPrevious(self.cline-1, markerMask)
743
770
  if line == -1:
744
771
  line = 0
745
772
  self.goto_line(line, selection)
746
-
773
+
747
774
  white_arrow = property(
748
775
  lambda self: self.get_marker(1),
749
- lambda self,v: self.set_marker(v, 1), # [arrow_set]
750
- lambda self: self.del_marker(1)) # [arrow_unset]
751
-
776
+ lambda self, v: self.set_marker(v, 1), # [arrow_set]
777
+ lambda self: self.del_marker(1), # [arrow_unset]
778
+ doc="Arrow marker used to indicate success.")
779
+
752
780
  red_arrow = property(
753
781
  lambda self: self.get_marker(2),
754
- lambda self,v: self.set_marker(v, 2), # [red-arrow_set]
755
- lambda self: self.del_marker(2)) # [red-arrow_unset]
756
-
782
+ lambda self, v: self.set_marker(v, 2), # [red-arrow_set]
783
+ lambda self: self.del_marker(2), # [red-arrow_unset]
784
+ doc="Arrow marker used to indicate failure.")
785
+
757
786
  pointer = property(
758
787
  lambda self: self.get_marker(3),
759
- lambda self,v: self.set_marker(v, 3), # [pointer_set]
760
- lambda self: self.del_marker(3)) # [pointer_unset]
761
-
788
+ lambda self, v: self.set_marker(v, 3), # [pointer_set]
789
+ lambda self: self.del_marker(3), # [pointer_unset]
790
+ doc="Arrow marker used to indicate breakpoint.")
791
+
762
792
  red_pointer = property(
763
793
  lambda self: self.get_marker(4),
764
- lambda self,v: self.set_marker(v, 4), # [red-pointer_set]
765
- lambda self: self.del_marker(4)) # [red-pointer_unset]
766
-
794
+ lambda self, v: self.set_marker(v, 4), # [red-pointer_set]
795
+ lambda self: self.del_marker(4), # [red-pointer_unset]
796
+ doc="Arrow marker used to indicate exception.")
797
+
767
798
  @property
768
799
  def markline(self):
769
800
  return self.MarkerNext(0, 1<<0)
770
-
801
+
771
802
  @markline.setter
772
803
  def markline(self, v):
773
804
  if v != -1:
774
- self.mark = self.PositionFromLine(v) # [mark_set]
805
+ self.mark = self.PositionFromLine(v) # [mark_set]
775
806
  else:
776
- del self.mark # [mark_unset]
777
-
807
+ del self.mark # [mark_unset]
808
+
778
809
  @markline.deleter
779
810
  def markline(self):
780
811
  del self.mark
781
-
812
+
782
813
  @property
783
814
  def mark(self):
784
815
  return self.__mark
785
-
816
+
786
817
  @mark.setter
787
818
  def mark(self, v):
788
819
  if v != -1:
789
820
  self.__mark = v
790
821
  ln = self.LineFromPosition(v)
791
- self.set_marker(ln, 0) # [mark_set]
822
+ self.set_marker(ln, 0) # [mark_set]
792
823
  else:
793
824
  del self.mark
794
-
825
+
795
826
  @mark.deleter
796
827
  def mark(self):
797
828
  v = self.__mark
798
829
  if v != -1:
799
830
  self.__mark = -1
800
- self.del_marker(0) # [mark_unset]
801
-
831
+ self.del_marker(0) # [mark_unset]
832
+
802
833
  def set_mark(self):
803
834
  self.mark = self.cpos
804
-
805
- def set_pointer(self):
806
- if self.pointer == self.cline: # toggle
835
+
836
+ def toggle_pointer(self):
837
+ if self.pointer == self.cline: # toggle
807
838
  self.pointer = -1
808
839
  else:
809
- self.pointer = self.cline # reset
840
+ self.pointer = self.cline # reset
810
841
  self.red_pointer = -1
811
-
842
+
812
843
  def exchange_point_and_mark(self):
813
844
  p = self.cpos
814
845
  q = self.mark
@@ -818,13 +849,13 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
818
849
  self.mark = p
819
850
  else:
820
851
  self.message("No marks")
821
-
852
+
822
853
  ## --------------------------------
823
- ## Attributes of the editor
854
+ ## Attributes of the editor.
824
855
  ## --------------------------------
825
856
  py_styles = {
826
- stc.STC_P_DEFAULT : 'nil', # etc. space \r\n\\$\0 (non-identifier)
827
- stc.STC_P_OPERATOR : 'op', # ops. `@=+-/*%<>&|^~!?.,:;([{<>}])
857
+ stc.STC_P_DEFAULT : 'nil', # etc. space \r\n\\$\0 (non-identifier)
858
+ stc.STC_P_OPERATOR : 'op', # ops. `@=+-/*%<>&|^~!?.,:;([{<>}])
828
859
  stc.STC_P_COMMENTLINE : 'comment',
829
860
  stc.STC_P_COMMENTBLOCK : 'comment',
830
861
  stc.STC_P_NUMBER : 'suji',
@@ -840,7 +871,7 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
840
871
  stc.STC_P_CLASSNAME : 'class',
841
872
  stc.STC_P_DEFNAME : 'def',
842
873
  }
843
-
874
+
844
875
  def get_style(self, pos):
845
876
  c = self.get_char(pos)
846
877
  st = self.GetStyleAt(pos)
@@ -857,11 +888,11 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
857
888
  if c in "({[": return 'lparen'
858
889
  if c in ")}]": return 'rparen'
859
890
  return sty
860
-
891
+
861
892
  def get_char(self, pos):
862
- """Returns the character at the position."""
893
+ """Return the character at the given position."""
863
894
  return chr(self.GetCharAt(pos))
864
-
895
+
865
896
  def get_text(self, start, end):
866
897
  """Retrieve a range of text.
867
898
 
@@ -873,50 +904,53 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
873
904
  p = max(start, 0)
874
905
  q = min(end, self.TextLength)
875
906
  return self.GetTextRange(p, q)
876
-
907
+
877
908
  anchor = property(
878
909
  lambda self: self.GetAnchor(),
879
- lambda self,v: self.SetAnchor(v))
880
-
910
+ lambda self, v: self.SetAnchor(v),
911
+ doc="Position of the opposite end of the selection to the caret.")
912
+
881
913
  cpos = property(
882
914
  lambda self: self.GetCurrentPos(),
883
- lambda self,v: self.SetCurrentPos(v))
884
-
915
+ lambda self, v: self.SetCurrentPos(v),
916
+ doc="Position of the caret.")
917
+
885
918
  cline = property(
886
919
  lambda self: self.GetCurrentLine(),
887
- lambda self,v: self.SetCurrentPos(self.PositionFromLine(v)))
888
-
920
+ lambda self, v: self.SetCurrentPos(self.PositionFromLine(v)),
921
+ doc="Line number of the line with the caret.")
922
+
889
923
  @property
890
924
  def bol(self):
891
925
  """Beginning of line."""
892
926
  text, lp = self.CurLine
893
927
  return self.cpos - lp
894
-
928
+
895
929
  @property
896
930
  def eol(self):
897
931
  """End of line."""
898
932
  text, lp = self.CurLine
899
- text = text.strip('\r\n') # remove linesep: '\r' and '\n'
933
+ text = text.strip('\r\n') # remove linesep: '\r' and '\n'
900
934
  return (self.cpos - lp + len(text.encode()))
901
-
935
+
902
936
  @property
903
937
  def line_at_caret(self):
904
938
  """Text of the range (bol, eol) at the caret-line."""
905
939
  return self.GetTextRange(self.bol, self.eol)
906
-
940
+
907
941
  @property
908
942
  def expr_at_caret(self):
909
943
  """A syntax unit (expression) at the caret-line."""
910
944
  p = q = self.cpos
911
- lsty = self.get_style(p-1)
912
- rsty = self.get_style(p)
913
- if lsty == rsty == 'moji': # inside string
914
- ## styles = {'moji'}
945
+ lst = self.get_style(p-1)
946
+ rst = self.get_style(p)
947
+ if lst == rst == 'moji': # inside string
948
+ # styles = {'moji'}
915
949
  return ''
916
- elif lsty == 'suji' or rsty == 'suji':
950
+ elif lst == 'suji' or rst == 'suji':
917
951
  styles = {'suji'}
918
- elif lsty in ('word', 'dot', 'moji', 'rparen')\
919
- or rsty in ('word', 'dot', 'moji', 'lparen'):
952
+ elif lst in ('word', 'dot', 'moji', 'rparen')\
953
+ or rst in ('word', 'dot', 'moji', 'lparen'):
920
954
  styles = {'word', 'dot', 'moji', 'paren'}
921
955
  else:
922
956
  return ''
@@ -929,7 +963,7 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
929
963
  if sty not in styles:
930
964
  break
931
965
  return self.GetTextRange(start, end).strip()
932
-
966
+
933
967
  @property
934
968
  def topic_at_caret(self):
935
969
  """Topic word at the caret or selected substring.
@@ -940,75 +974,77 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
940
974
  return topic
941
975
  with self.save_excursion():
942
976
  p = q = self.cpos
943
- if self.get_char(p-1).isalnum():
977
+ cp = self.GetTextRange(self.PositionBefore(p), p) # cf. self.get_char(p-1)
978
+ if cp.isidentifier() or cp.isalnum():
944
979
  self.WordLeft()
945
980
  p = self.cpos
946
- if self.get_char(q).isalnum():
981
+ cq = self.GetTextRange(q, self.PositionAfter(q)) # cf. self.get_char(q)
982
+ if cq.isidentifier() or cq.isalnum():
947
983
  self.WordRightEnd()
948
984
  q = self.cpos
949
985
  return self.GetTextRange(p, q)
950
-
986
+
951
987
  ## --------------------------------
952
- ## Python syntax and indentation
988
+ ## Python syntax and indentation.
953
989
  ## --------------------------------
954
-
990
+
955
991
  def on_indent_line(self, evt):
956
992
  if self.SelectedText:
957
993
  evt.Skip()
958
994
  else:
959
995
  self.py_indent_line()
960
-
996
+
961
997
  def on_outdent_line(self, evt):
962
998
  if self.SelectedText:
963
999
  evt.Skip()
964
1000
  else:
965
1001
  self.py_outdent_line()
966
-
967
- @can_edit
1002
+
1003
+ @editable
968
1004
  def py_indent_line(self):
969
1005
  """Indent the current line."""
970
- text = self.line_at_caret # w/ no-prompt
971
- lstr = text.lstrip() # w/ no-indent
1006
+ text = self.line_at_caret # w/ no-prompt
1007
+ lstr = text.lstrip() # w/ no-indent
972
1008
  p = self.bol + len(text) - len(lstr)
973
1009
  offset = max(0, self.cpos - p)
974
- indent = self.py_current_indent() # check current/previous line
1010
+ indent = self.py_current_indent() # check current/previous line
975
1011
  if indent >= 0:
976
1012
  self.Replace(self.bol, p, ' '*indent)
977
1013
  self.goto_char(self.bol + indent + offset)
978
-
979
- @can_edit
1014
+
1015
+ @editable
980
1016
  def py_outdent_line(self):
981
1017
  """Outdent the current line."""
982
- text = self.line_at_caret # w/ no-prompt
983
- lstr = text.lstrip() # w/ no-indent
1018
+ text = self.line_at_caret # w/ no-prompt
1019
+ lstr = text.lstrip() # w/ no-indent
984
1020
  p = self.bol + len(text) - len(lstr)
985
1021
  offset = max(0, self.cpos - p)
986
1022
  indent = len(text) - len(lstr) - 4
987
1023
  if indent >= 0:
988
1024
  self.Replace(self.bol, p, ' '*indent)
989
1025
  self.goto_char(self.bol + indent + offset)
990
-
1026
+
991
1027
  def py_current_indent(self):
992
1028
  """Calculate indent spaces from previous line."""
993
1029
  text = self.GetLine(self.cline - 1)
994
- indent = self.py_calc_indentation(text) # check previous line
1030
+ indent = self.py_calc_indentation(text) # check previous line
995
1031
  text = self.GetLine(self.cline)
996
- lstr, _indent = self.py_strip_indents(text) # check current line
1032
+ lstr, _indent = self.py_strip_indents(text) # check current line
997
1033
  if re.match(py_outdent_re, lstr):
998
1034
  indent -= 4
999
1035
  return indent
1000
-
1036
+
1001
1037
  def py_electric_indent(self):
1002
1038
  """Calculate indent spaces for the following line."""
1003
- ## [BUG 4.2.0] The last char is replaced with b'\x00'.
1039
+ ## [BUG ver 4.2.0] The last char is replaced with b'\x00'.
1004
1040
  ## text, lp = self.CurLineRaw
1005
1041
  ## return self.py_calc_indentation(text[:lp].decode())
1006
1042
  text, lp = self.CurLine
1007
1043
  return self.py_calc_indentation(text[:lp])
1008
-
1044
+
1009
1045
  @classmethod
1010
1046
  def py_calc_indentation(self, text):
1011
- """Returns indent spaces for the command text."""
1047
+ """Return indent spaces for the command text."""
1012
1048
  text = self.py_strip_comments(text)
1013
1049
  lstr, indent = self.py_strip_indents(text)
1014
1050
  text = text.rstrip()
@@ -1019,33 +1055,33 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1019
1055
  if re.match(py_closing_re, lstr):
1020
1056
  return indent - 4
1021
1057
  return indent
1022
-
1058
+
1023
1059
  @classmethod
1024
1060
  def py_strip_indents(self, text):
1025
- """Returns left-stripped text and the number of indent spaces."""
1026
- text = self.py_strip_prompts(text) # cf. shell.lstripPrompt(text)
1061
+ """Return left-stripped text and the number of indent spaces."""
1062
+ text = self.py_strip_prompts(text) # cf. shell.lstripPrompt(text)
1027
1063
  lstr = text.lstrip(' \t')
1028
1064
  indent = len(text) - len(lstr)
1029
1065
  return lstr, indent
1030
-
1066
+
1031
1067
  @classmethod
1032
1068
  def py_strip_prompts(self, text):
1033
- """Returns text without a leading prompt."""
1069
+ """Return text without a leading prompt."""
1034
1070
  for ps in (sys.ps1, sys.ps2, sys.ps3):
1035
1071
  if text.startswith(ps):
1036
1072
  text = text[len(ps):]
1037
1073
  break
1038
1074
  return text
1039
-
1075
+
1040
1076
  @classmethod
1041
1077
  def py_strip_comments(self, text):
1042
- """Returns text without comments."""
1078
+ """Return text without comments."""
1043
1079
  return ''.join(split_tokens(text, comment=False))
1044
-
1080
+
1045
1081
  ## --------------------------------
1046
- ## Fold / Unfold functions
1082
+ ## Fold / Unfold functions.
1047
1083
  ## --------------------------------
1048
-
1084
+
1049
1085
  def show_folder(self, show=True):
1050
1086
  """Show folder margin.
1051
1087
 
@@ -1067,8 +1103,8 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1067
1103
  self.SetMarginSensitive(2, False)
1068
1104
  self.SetFoldMarginColour(True, 'black')
1069
1105
  self.SetFoldMarginHiColour(True, 'black')
1070
-
1071
- def OnMarginClick(self, evt): #<wx._stc.StyledTextEvent>
1106
+
1107
+ def OnMarginClick(self, evt): #<wx._stc.StyledTextEvent>
1072
1108
  lc = self.LineFromPosition(evt.Position)
1073
1109
  level = self.GetFoldLevel(lc) ^ stc.STC_FOLDLEVELBASE
1074
1110
  ## `level` indicates indent-level number
@@ -1079,8 +1115,8 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1079
1115
  self.handler('select_lines', evt)
1080
1116
  else:
1081
1117
  self.handler('select_line', evt)
1082
-
1083
- def OnMarginRClick(self, evt): #<wx._stc.StyledTextEvent>
1118
+
1119
+ def OnMarginRClick(self, evt): #<wx._stc.StyledTextEvent>
1084
1120
  """Popup context menu."""
1085
1121
  def _Icon(key):
1086
1122
  return wx.ArtProvider.GetBitmap(key, size=(16,16))
@@ -1092,26 +1128,26 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1092
1128
  (wx.ID_UP, "&Expand ALL", _Icon(wx.ART_PLUS),
1093
1129
  lambda v: self.FoldAll(1)),
1094
1130
  ])
1095
-
1131
+
1096
1132
  def toggle_fold(self, lc):
1097
1133
  """Similar to ToggleFold, but the top header containing
1098
1134
  the specified line switches between expanded and contracted.
1099
1135
  """
1100
1136
  while 1:
1101
- la = self.GetFoldParent(lc) # get folding root
1137
+ la = self.GetFoldParent(lc) # get folding root
1102
1138
  if la == -1:
1103
1139
  break
1104
1140
  lc = la
1105
1141
  self.ToggleFold(lc)
1106
- self.EnsureLineOnScreen(lc)
1142
+ self.ensureLineOnScreen(lc)
1107
1143
  return lc
1108
-
1109
- def get_region(self, line):
1144
+
1145
+ def get_indent_region(self, line):
1110
1146
  """Line numbers of folding head and tail containing the line."""
1111
1147
  lc = line
1112
1148
  le = lc + 1
1113
1149
  while 1:
1114
- la = self.GetFoldParent(lc) # get folding root
1150
+ la = self.GetFoldParent(lc) # get folding root
1115
1151
  if la == -1:
1116
1152
  break
1117
1153
  lc = la
@@ -1121,38 +1157,38 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1121
1157
  break
1122
1158
  le += 1
1123
1159
  return lc, le
1124
-
1160
+
1125
1161
  def on_linesel_begin(self, evt):
1126
1162
  """Called when a line of text selection begins."""
1127
- self.cpos = self.anchor = evt.Position #<select_line>
1163
+ self.cpos = self.anchor = evt.Position #<select_line>
1128
1164
  self.CaptureMouse()
1129
1165
  evt.Skip()
1130
-
1166
+
1131
1167
  def on_linesel_next(self, evt):
1132
1168
  """Called when next line of text selection begins."""
1133
- self.cpos = evt.Position #<select_lines>
1169
+ self.cpos = evt.Position #<select_lines>
1134
1170
  self.CaptureMouse()
1135
1171
  evt.Skip()
1136
-
1172
+
1137
1173
  def on_linesel_motion(self, evt):
1138
1174
  """Called when a line of text selection is changing."""
1139
1175
  self.cpos = self.PositionFromPoint(evt.Position)
1140
1176
  self.EnsureCaretVisible()
1141
1177
  evt.Skip()
1142
-
1178
+
1143
1179
  def on_linesel_end(self, evt):
1144
1180
  """Called when a line of text selection ends."""
1145
1181
  if self.HasCapture():
1146
1182
  self.ReleaseMouse()
1147
1183
  evt.Skip()
1148
-
1184
+
1149
1185
  ## --------------------------------
1150
- ## Preferences / Appearance
1186
+ ## Preferences / Appearance.
1151
1187
  ## --------------------------------
1152
-
1188
+
1153
1189
  def get_stylus(self):
1154
1190
  return self.__stylus
1155
-
1191
+
1156
1192
  def set_stylus(self, spec=None, **kwargs):
1157
1193
  """Set style spec for wx.stc.StyleSetSpec."""
1158
1194
  spec = spec and spec.copy() or {}
@@ -1165,33 +1201,38 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1165
1201
  def _map(sc):
1166
1202
  return dict(kv.partition(':')[::2] for kv in sc.split(',') if kv)
1167
1203
 
1168
- ## Apply the default style first
1204
+ ## Apply the default style first.
1169
1205
  default = spec.pop(stc.STC_STYLE_DEFAULT, '')
1170
1206
  if default:
1171
1207
  self.StyleSetSpec(stc.STC_STYLE_DEFAULT, default)
1172
1208
  self.StyleClearAll()
1173
1209
 
1174
- ## Add style to the folding margin
1210
+ self.SetSelForeground(True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT))
1211
+ self.SetSelBackground(True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT))
1212
+
1213
+ ## Add style to the folding margin.
1175
1214
  item = _map(spec.get(stc.STC_STYLE_LINENUMBER, ''))
1176
1215
  if item:
1177
- ## Set colors used as a chequeboard pattern,
1178
- ## lo (back) one of the colors
1179
- ## hi (fore) the other color
1216
+ ## Set colors used as a chequeboard pattern.
1217
+ ## - lo (back) one of the colors
1218
+ ## - hi (fore) the other color
1180
1219
  self.BackgroundColour = item.get('back')
1181
1220
  self.ForegroundColour = item.get('fore')
1182
1221
  if self.GetMarginWidth(2) > 1:
1183
- ## 12 pixel chequeboard, fore being default colour
1222
+ ## 12 pixel chequeboard, fore being default colour.
1184
1223
  self.SetFoldMarginColour(True, item.get('back'))
1185
1224
  self.SetFoldMarginHiColour(True, 'light gray')
1186
1225
  else:
1187
- ## one pixel solid line, the same colour as the line number
1226
+ ## One pixel solid line, the same colour as the line number.
1188
1227
  self.SetFoldMarginColour(True, item.get('fore'))
1189
1228
  self.SetFoldMarginHiColour(True, item.get('fore'))
1190
1229
 
1191
- ## Custom style for caret and line colour
1230
+ self.SetCaretLineVisible(0)
1231
+ self.SetCaretForeground(_map(default).get('fore'))
1232
+
1233
+ ## Custom style for caret and line colour.
1192
1234
  item = _map(spec.pop(stc.STC_STYLE_CARETLINE, ''))
1193
1235
  if item:
1194
- self.SetCaretLineVisible(0)
1195
1236
  if 'fore' in item:
1196
1237
  self.SetCaretForeground(item['fore'])
1197
1238
  if 'back' in item:
@@ -1203,10 +1244,10 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1203
1244
  if 'bold' in item:
1204
1245
  self.SetCaretStyle(stc.STC_CARETSTYLE_BLOCK)
1205
1246
 
1206
- ## Apply the rest of the style
1247
+ ## Apply the rest of the style.
1207
1248
  for key, value in spec.items():
1208
1249
  self.StyleSetSpec(key, value)
1209
-
1250
+
1210
1251
  def match_paren(self):
1211
1252
  self.SetIndicatorCurrent(3)
1212
1253
  self.IndicatorClearRange(0, self.TextLength)
@@ -1214,7 +1255,7 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1214
1255
  if self.get_char(p-1) in ")}]>":
1215
1256
  q = self.BraceMatch(p-1)
1216
1257
  if q != -1:
1217
- self.BraceHighlight(q, p-1) # matched the preceding char
1258
+ self.BraceHighlight(q, p-1) # matched the preceding char
1218
1259
  self.IndicatorFillRange(q, p-q)
1219
1260
  return q
1220
1261
  else:
@@ -1222,20 +1263,20 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1222
1263
  elif self.get_char(p) in "({[<":
1223
1264
  q = self.BraceMatch(p)
1224
1265
  if q != -1:
1225
- self.BraceHighlight(p, q) # matched the following char
1266
+ self.BraceHighlight(p, q) # matched the following char
1226
1267
  self.IndicatorFillRange(p, q-p+1)
1227
1268
  return q
1228
1269
  else:
1229
1270
  self.BraceBadLight(p)
1230
1271
  else:
1231
- self.BraceHighlight(-1,-1) # no highlight
1232
-
1272
+ self.BraceHighlight(-1,-1) # no highlight
1273
+
1233
1274
  def over(self, mode=1):
1234
1275
  """Set insert or overtype.
1235
1276
  mode in {0:insert, 1:over, None:toggle}
1236
1277
  """
1237
1278
  self.Overtype = mode if mode is not None else not self.Overtype
1238
-
1279
+
1239
1280
  def wrap(self, mode=1):
1240
1281
  """Set whether text is word wrapped.
1241
1282
 
@@ -1243,78 +1284,141 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1243
1284
  3:whitespace-wrap, None:toggle}
1244
1285
  """
1245
1286
  self.WrapMode = mode if mode is not None else not self.WrapMode
1246
-
1287
+
1247
1288
  def recenter(self, ln=None):
1248
1289
  """Scroll the cursor line to the center of screen.
1249
1290
  If ln=0, the cursor moves to the top of the screen.
1250
1291
  If ln=-1 (ln=n-1), moves to the bottom
1251
1292
  """
1252
- n = self.LinesOnScreen() # lines completely visible
1253
- m = n//2 if ln is None else ln % n if ln < n else n # ln[0:n]
1293
+ n = self.LinesOnScreen() # lines completely visible
1294
+ m = n//2 if ln is None else ln % n if ln < n else n # ln[0:n]
1254
1295
  vl = self._calc_vline(self.cline)
1255
1296
  self.ScrollToLine(vl - m)
1256
-
1297
+
1257
1298
  def _calc_vline(self, line):
1258
1299
  """Virtual line number in the buffer window."""
1259
1300
  pos = self.PositionFromLine(line)
1260
1301
  w, h = self.PointFromPosition(pos)
1261
- return self.FirstVisibleLine + h//self.TextHeight(0)
1262
-
1263
- def EnsureLineOnScreen(self, line):
1302
+ return self.FirstVisibleLine + h // self.TextHeight(line)
1303
+
1304
+ def ensureLineOnScreen(self, line):
1264
1305
  """Ensure a particular line is visible by scrolling the buffer
1265
1306
  without expanding any header line hiding it.
1266
1307
  """
1267
- n = self.LinesOnScreen() # lines completely visible
1308
+ n = self.LinesOnScreen() # lines completely visible
1268
1309
  hl = self.FirstVisibleLine
1269
1310
  vl = self._calc_vline(line)
1270
1311
  if vl < hl:
1271
1312
  self.ScrollToLine(vl)
1272
1313
  elif vl > hl + n - 1:
1273
1314
  self.ScrollToLine(vl - n + 1)
1274
-
1275
- def EnsureLineMoreOnScreen(self, line, offset=0):
1315
+
1316
+ def ensureLineMoreOnScreen(self, line, offset=0):
1276
1317
  """Ensure a particular line is visible by scrolling the buffer
1277
1318
  without expanding any header line hiding it.
1278
1319
  If the line is at the screen edge, recenter it.
1279
1320
  """
1280
- n = self.LinesOnScreen() # lines completely visible
1321
+ n = self.LinesOnScreen() # lines completely visible
1281
1322
  hl = self.FirstVisibleLine
1282
1323
  vl = self._calc_vline(line)
1283
1324
  if not hl + offset < vl < hl + n - 1 - offset:
1284
1325
  self.ScrollToLine(vl - n//2)
1285
-
1326
+
1286
1327
  ## --------------------------------
1287
- ## Search functions
1328
+ ## Search functions.
1288
1329
  ## --------------------------------
1289
-
1330
+
1331
+ def DoFindNext(self, findData):
1332
+ """Find the search text defined in `findData`.
1333
+
1334
+ If found, selects the matched text and scrolls to its line.
1335
+ Typically called from `wx.EVT_FIND` event handlers.
1336
+
1337
+ (override) Enables whole word search.
1338
+ Returns the match position if found, -1 otherwise.
1339
+ """
1340
+ flags = 0
1341
+ if findData.Flags & wx.FR_MATCHCASE: flags |= stc.STC_FIND_MATCHCASE
1342
+ if findData.Flags & wx.FR_WHOLEWORD: flags |= stc.STC_FIND_WHOLEWORD
1343
+ self.SetSearchFlags(flags)
1344
+
1345
+ backward = not (findData.Flags & wx.FR_DOWN)
1346
+ findstring = findData.FindString.encode()
1347
+ if backward:
1348
+ self.TargetStart = self.anchor # backward anchor
1349
+ self.TargetEnd = 0
1350
+ else:
1351
+ self.TargetStart = self.cpos # forward anchor
1352
+ self.TargetEnd = self.TextLength
1353
+ loc = self.SearchInTarget(findstring)
1354
+
1355
+ ## If it wasn't found then restart at beginning.
1356
+ if loc == -1:
1357
+ self.TargetStart = self.TextLength if backward else 0
1358
+ loc = self.SearchInTarget(findstring)
1359
+
1360
+ ## Was it still not found?
1361
+ if loc == -1:
1362
+ # wx.MessageBox("Unable to find the search text any more.",
1363
+ # "Not found!", wx.OK|wx.ICON_INFORMATION)
1364
+ wx.Bell()
1365
+ pass
1366
+ else:
1367
+ self.SetSelection(loc, loc + len(findstring))
1368
+ self.EnsureVisible(self.cline) # expand if folded
1369
+ self.EnsureCaretVisible()
1370
+ return loc
1371
+
1372
+ def DoReplaceNext(self, findData):
1373
+ if self.SelectedText == findData.FindString:
1374
+ if self.CanEdit():
1375
+ self.ReplaceSelection(findData.ReplaceString)
1376
+ return self.DoFindNext(findData)
1377
+
1378
+ def DoReplaceAll(self, findData):
1379
+ with self.save_excursion():
1380
+ locs = [-1]
1381
+ count = 0
1382
+ while 1:
1383
+ loc = self.DoFindNext(findData)
1384
+ if loc in locs:
1385
+ break
1386
+ locs.append(loc)
1387
+ self.TargetStart = self.anchor
1388
+ self.TargetEnd = self.cpos
1389
+ if self.CanEdit():
1390
+ self.ReplaceTarget(findData.ReplaceString)
1391
+ count += 1
1392
+ return count
1393
+
1290
1394
  def get_right_paren(self, p):
1291
- if self.get_char(p) in "({[<": # left-parentheses, <
1395
+ if self.get_char(p) in "({[<": # left-parentheses, <
1292
1396
  q = self.BraceMatch(p)
1293
1397
  return q if q < 0 else q+1
1294
-
1398
+
1295
1399
  def get_left_paren(self, p):
1296
- if self.get_char(p-1) in ")}]>": # right-parentheses, >
1400
+ if self.get_char(p-1) in ")}]>": # right-parentheses, >
1297
1401
  q = self.BraceMatch(p-1)
1298
1402
  return q
1299
-
1403
+
1300
1404
  def get_right_quotation(self, p):
1301
1405
  st = self.get_style(p)
1302
1406
  if st == 'moji':
1303
- if self.get_style(p-1) == 'moji': # inside string
1407
+ if self.get_style(p-1) == 'moji': # inside string
1304
1408
  return
1305
1409
  while self.get_style(p) == st and p < self.TextLength:
1306
1410
  p += 1
1307
1411
  return p
1308
-
1412
+
1309
1413
  def get_left_quotation(self, p):
1310
1414
  st = self.get_style(p-1)
1311
1415
  if st == 'moji':
1312
- if self.get_style(p) == 'moji': # inside string
1416
+ if self.get_style(p) == 'moji': # inside string
1313
1417
  return
1314
1418
  while self.get_style(p-1) == st and p > 0:
1315
1419
  p -= 1
1316
1420
  return p
1317
-
1421
+
1318
1422
  def get_following_atom(self, p):
1319
1423
  q = p
1320
1424
  st = self.get_style(p)
@@ -1322,15 +1426,15 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1322
1426
  q = self.BraceMatch(p)
1323
1427
  if q == -1:
1324
1428
  q = self.TextLength
1325
- st = None # no closing paren
1429
+ st = None # no closing paren
1326
1430
  else:
1327
1431
  q += 1
1328
- st = 'paren' # closed
1432
+ st = 'paren' # closed
1329
1433
  else:
1330
1434
  while self.get_style(q) == st and q < self.TextLength:
1331
1435
  q += 1
1332
1436
  return p, q, st
1333
-
1437
+
1334
1438
  def get_preceding_atom(self, p):
1335
1439
  q = p
1336
1440
  st = self.get_style(p-1)
@@ -1338,14 +1442,14 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1338
1442
  p = self.BraceMatch(p-1)
1339
1443
  if p == -1:
1340
1444
  p = 0
1341
- st = None # no closing paren
1445
+ st = None # no closing paren
1342
1446
  else:
1343
- st = 'paren' # closed
1447
+ st = 'paren' # closed
1344
1448
  else:
1345
1449
  while self.get_style(p-1) == st and p > 0:
1346
1450
  p -= 1
1347
1451
  return p, q, st
1348
-
1452
+
1349
1453
  def grep_forward(self, pattern, flags=re.M):
1350
1454
  orig = self.eol if (self.markline == self.cline) else self.cpos
1351
1455
  text = self.GetTextRange(orig, self.TextLength)
@@ -1357,7 +1461,7 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1357
1461
  self.mark = self.cpos
1358
1462
  self.EnsureVisible(self.cline)
1359
1463
  yield err
1360
-
1464
+
1361
1465
  def grep_backward(self, pattern, flags=re.M):
1362
1466
  text = self.GetTextRange(0, self.cpos)
1363
1467
  errs = re.finditer(pattern, text, flags)
@@ -1368,47 +1472,43 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1368
1472
  self.mark = self.cpos
1369
1473
  self.EnsureVisible(self.cline)
1370
1474
  yield err
1371
-
1475
+
1372
1476
  def grep(self, pattern, flags=re.M):
1373
1477
  yield from re.finditer(pattern.encode(), self.TextRaw, flags)
1374
-
1375
- def search_text(self, text):
1376
- """Yields raw-positions where `text` is found."""
1377
- word = text.encode()
1378
- raw = self.TextRaw
1379
- pos = -1
1380
- while 1:
1381
- pos = raw.find(word, pos+1)
1382
- if pos < 0:
1383
- break
1384
- yield pos
1385
-
1386
- def filter_text(self, text=None):
1478
+
1479
+ def filter_text(self):
1387
1480
  """Show indicators for the selected text."""
1388
1481
  self.__itextlines = []
1389
1482
  for i in (10, 11,):
1390
1483
  self.SetIndicatorCurrent(i)
1391
1484
  self.IndicatorClearRange(0, self.TextLength)
1392
- if text is None:
1393
- text = self.topic_at_caret
1485
+ text = self.topic_at_caret
1394
1486
  if not text:
1395
1487
  self.message("No words")
1396
1488
  return
1397
-
1398
- lw = len(text.encode()) # for multi-byte string
1489
+ wholeword = (not self.SelectedText # Enable or disable whole word search.
1490
+ and text.isascii()) # whole word search is for ascii only. (単語境界が不明確のため)
1491
+ pattern = re.escape(text)
1492
+ if wholeword:
1493
+ pattern = rf"\b{pattern}\b"
1399
1494
  lines = []
1400
- for p in self.search_text(text):
1495
+ for m in self.grep(pattern):
1496
+ p, q = m.span()
1401
1497
  lines.append(self.LineFromPosition(p))
1402
1498
  for i in (10, 11,):
1403
1499
  self.SetIndicatorCurrent(i)
1404
- self.IndicatorFillRange(p, lw)
1405
- self.__itextlines = sorted(set(lines)) # keep order, no duplication
1500
+ self.IndicatorFillRange(p, q-p)
1501
+ self.__itextlines = sorted(set(lines)) # keep order, no duplication
1406
1502
  self.message("{}: {} found".format(text, len(lines)))
1407
1503
  try:
1408
1504
  self.TopLevelParent.findData.FindString = text
1505
+ if wholeword:
1506
+ self.TopLevelParent.findData.Flags |= wx.FR_WHOLEWORD
1507
+ else:
1508
+ self.TopLevelParent.findData.Flags &= ~wx.FR_WHOLEWORD
1409
1509
  except AttributeError:
1410
1510
  pass
1411
-
1511
+
1412
1512
  def on_itext_enter(self, evt):
1413
1513
  """Called when entering filter_text mode."""
1414
1514
  if not self.__itextlines:
@@ -1418,22 +1518,22 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1418
1518
  def _format(ln):
1419
1519
  return "{:4d} {}".format(ln+1, self.GetLine(ln).strip())
1420
1520
 
1421
- ## pts = self.StyleGetSize(stc.STC_STYLE_DEFAULT)
1422
- ## self.StyleSetSize(stc.STC_STYLE_DEFAULT, pts-1)
1521
+ # pts = self.StyleGetSize(stc.STC_STYLE_DEFAULT)
1522
+ # self.StyleSetSize(stc.STC_STYLE_DEFAULT, pts-1)
1423
1523
 
1424
1524
  self.AutoCompSetSeparator(ord('\n'))
1425
1525
  self.AutoCompShow(0, '\n'.join(map(_format, self.__itextlines)))
1426
1526
  self.AutoCompSelect("{:4d}".format(self.cline+1))
1427
1527
  self.Bind(stc.EVT_STC_AUTOCOMP_SELECTION, self.on_itext_selection)
1428
1528
 
1429
- ## self.StyleSetSize(stc.STC_STYLE_DEFAULT, pts)
1430
-
1529
+ # self.StyleSetSize(stc.STC_STYLE_DEFAULT, pts)
1530
+
1431
1531
  def on_itext_exit(self, evt):
1432
1532
  """Called when exiting filter_text mode."""
1433
1533
  if self.AutoCompActive():
1434
1534
  self.AutoCompCancel()
1435
1535
  self.Unbind(stc.EVT_STC_AUTOCOMP_SELECTION, handler=self.on_itext_selection)
1436
-
1536
+
1437
1537
  def on_itext_selection(self, evt):
1438
1538
  """Called when filter_text is selected."""
1439
1539
  i = self.AutoCompGetCurrent()
@@ -1441,15 +1541,15 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1441
1541
  evt.Skip()
1442
1542
  return
1443
1543
  line = self.__itextlines[i]
1444
- self.EnsureVisible(line) # expand if folded
1544
+ self.EnsureVisible(line) # expand if folded
1445
1545
  self.goto_line(line)
1446
1546
  self.recenter()
1447
1547
  self.on_itext_exit(evt)
1448
-
1548
+
1449
1549
  ## --------------------------------
1450
1550
  ## goto / skip / selection / etc.
1451
1551
  ## --------------------------------
1452
-
1552
+
1453
1553
  def goto_char(self, pos, selection=False, interactive=False):
1454
1554
  """Goto char position with selection."""
1455
1555
  if pos is None or pos < 0:
@@ -1464,7 +1564,7 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1464
1564
 
1465
1565
  if interactive:
1466
1566
  ## Update the caret position/status manually.
1467
- ## To update caret status, shake L/R w/o modifier
1567
+ ## To update caret status, shake L/R w/o modifier.
1468
1568
  ## Don't do this if selection is active.
1469
1569
  vk = wx.UIActionSimulator()
1470
1570
  modkeys = [k for k in (wx.WXK_CONTROL, wx.WXK_ALT, wx.WXK_SHIFT)
@@ -1478,74 +1578,74 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1478
1578
  vk.KeyDown(wx.WXK_LEFT)
1479
1579
  vk.KeyDown(wx.WXK_RIGHT)
1480
1580
  for k in modkeys:
1481
- vk.KeyDown(k) # restore modifier key state
1581
+ vk.KeyDown(k) # restore modifier key state
1482
1582
  return True
1483
-
1583
+
1484
1584
  def goto_line(self, ln, selection=False):
1485
1585
  """Goto line with selection."""
1486
1586
  if ln is None or ln < 0:
1487
1587
  return
1488
- ## if ln < 0:
1489
- ## ln += self.LineCount
1588
+ # if ln < 0:
1589
+ # ln += self.LineCount
1490
1590
  if selection:
1491
1591
  self.cline = ln
1492
1592
  else:
1493
1593
  self.GotoLine(ln)
1494
1594
  return True
1495
-
1595
+
1496
1596
  def skip_chars_forward(self, chars):
1497
1597
  p = self.cpos
1498
1598
  while self.get_char(p) in chars and p < self.TextLength:
1499
1599
  p += 1
1500
1600
  self.goto_char(p)
1501
-
1601
+
1502
1602
  def skip_chars_backward(self, chars):
1503
1603
  p = self.cpos
1504
1604
  while self.get_char(p-1) in chars and p > 0:
1505
1605
  p -= 1
1506
1606
  self.goto_char(p)
1507
-
1607
+
1508
1608
  def back_to_indentation(self):
1509
- text = self.line_at_caret # w/ no-prompt
1510
- lstr = text.lstrip() # w/ no-indent
1609
+ text = self.line_at_caret # w/ no-prompt
1610
+ lstr = text.lstrip() # w/ no-indent
1511
1611
  p = self.bol + len(text) - len(lstr)
1512
1612
  self.goto_char(p, interactive=True)
1513
1613
  self.ScrollToColumn(0)
1514
-
1614
+
1515
1615
  def beginning_of_line(self):
1516
1616
  self.goto_char(self.bol, interactive=True)
1517
1617
  self.ScrollToColumn(0)
1518
-
1618
+
1519
1619
  def end_of_line(self):
1520
1620
  self.goto_char(self.eol, interactive=True)
1521
-
1621
+
1522
1622
  def beginning_of_buffer(self):
1523
1623
  self.mark = self.cpos
1524
1624
  self.goto_char(0, interactive=True)
1525
-
1625
+
1526
1626
  def end_of_buffer(self):
1527
1627
  self.mark = self.cpos
1528
1628
  self.goto_char(self.TextLength, interactive=True)
1529
-
1629
+
1530
1630
  def goto_matched_paren(self):
1531
1631
  p = self.cpos
1532
1632
  return (self.goto_char(self.get_left_paren(p))
1533
1633
  or self.goto_char(self.get_right_paren(p))
1534
1634
  or self.goto_char(self.get_left_quotation(p))
1535
1635
  or self.goto_char(self.get_right_quotation(p)))
1536
-
1636
+
1537
1637
  def selection_forward_word_or_paren(self):
1538
1638
  p = self.cpos
1539
1639
  return (self.goto_char(self.get_right_paren(p), selection=True)
1540
1640
  or self.goto_char(self.get_right_quotation(p), selection=True)
1541
1641
  or self.WordRightEndExtend())
1542
-
1642
+
1543
1643
  def selection_backward_word_or_paren(self):
1544
1644
  p = self.cpos
1545
1645
  return (self.goto_char(self.get_left_paren(p), selection=True)
1546
1646
  or self.goto_char(self.get_left_quotation(p), selection=True)
1547
1647
  or self.WordLeftExtend())
1548
-
1648
+
1549
1649
  @contextmanager
1550
1650
  def save_excursion(self):
1551
1651
  """Save buffer excursion."""
@@ -1558,7 +1658,7 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1558
1658
  self.GotoPos(p)
1559
1659
  self.ScrollToLine(vpos)
1560
1660
  self.SetXOffset(hpos)
1561
-
1661
+
1562
1662
  @contextmanager
1563
1663
  def pre_selection(self):
1564
1664
  """Save buffer cpos and anchor."""
@@ -1571,7 +1671,7 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1571
1671
  self.cpos = p
1572
1672
  else:
1573
1673
  self.anchor = q
1574
-
1674
+
1575
1675
  @contextmanager
1576
1676
  def save_attributes(self, **kwargs):
1577
1677
  """Save buffer attributes (e.g. ReadOnly=False)."""
@@ -1583,50 +1683,48 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1583
1683
  finally:
1584
1684
  for k, v in kwargs.items():
1585
1685
  setattr(self, k, v)
1586
-
1686
+
1587
1687
  def off_readonly(self):
1588
1688
  """Disables buffer read-only lock temporarily."""
1589
1689
  return self.save_attributes(ReadOnly=False)
1590
-
1690
+
1591
1691
  def off_undocollection(self):
1592
1692
  """Disables buffer undo stack temporarily."""
1593
1693
  return self.save_attributes(UndoCollection=False)
1594
-
1694
+
1595
1695
  ## --------------------------------
1596
- ## Edit: comment / insert / kill
1696
+ ## Edit: comment / insert / kill.
1597
1697
  ## --------------------------------
1598
- comment_prefix = "## "
1599
-
1600
- @can_edit
1698
+ comment_prefix = "#"
1699
+
1700
+ @editable
1601
1701
  def comment_out_selection(self, from_=None, to_=None):
1602
1702
  """Comment out the selected text."""
1603
1703
  if from_ is not None: self.anchor = from_
1604
1704
  if to_ is not None: self.cpos = to_
1605
- prefix = self.comment_prefix
1606
1705
  with self.pre_selection():
1607
- text = re.sub("^", prefix, self.SelectedText, flags=re.M)
1608
- ## Don't comment out the last (blank) line.
1609
- lines = text.splitlines()
1610
- if len(lines) > 1 and lines[-1].endswith(prefix):
1611
- text = text[:-len(prefix)]
1706
+ text = re.sub("^", self.comment_prefix + ' ', self.SelectedText, flags=re.M)
1707
+ ## Don't comment out the last (blank) line in a multiline selection.
1708
+ if '\n' in text:
1709
+ text = text.rstrip(self.comment_prefix + ' ')
1612
1710
  self.ReplaceSelection(text)
1613
-
1614
- @can_edit
1711
+
1712
+ @editable
1615
1713
  def uncomment_selection(self, from_=None, to_=None):
1616
1714
  """Uncomment the selected text."""
1617
1715
  if from_ is not None: self.anchor = from_
1618
1716
  if to_ is not None: self.cpos = to_
1619
1717
  with self.pre_selection():
1620
- text = re.sub("^#+ ", "", self.SelectedText, flags=re.M)
1718
+ text = re.sub(f"^{self.comment_prefix}+ ", "", self.SelectedText, flags=re.M)
1621
1719
  if text != self.SelectedText:
1622
1720
  self.ReplaceSelection(text)
1623
-
1624
- @can_edit
1721
+
1722
+ @editable
1625
1723
  def comment_out_line(self):
1626
1724
  if self.SelectedText:
1627
1725
  self.comment_out_selection()
1628
1726
  else:
1629
- ## align with current or previous indent position
1727
+ ## Align with current or previous indent position.
1630
1728
  self.back_to_indentation()
1631
1729
  text = self.GetLine(self.cline - 1)
1632
1730
  lstr, j = self.py_strip_indents(text)
@@ -1636,8 +1734,8 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1636
1734
  self.goto_char(self.bol + min(j, k))
1637
1735
  self.comment_out_selection(self.cpos, self.eol)
1638
1736
  self.LineDown()
1639
-
1640
- @can_edit
1737
+
1738
+ @editable
1641
1739
  def uncomment_line(self):
1642
1740
  if self.SelectedText:
1643
1741
  self.uncomment_selection()
@@ -1645,36 +1743,58 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1645
1743
  self.back_to_indentation()
1646
1744
  self.uncomment_selection(self.cpos, self.eol)
1647
1745
  self.LineDown()
1648
-
1649
- @can_edit
1746
+
1747
+ @editable
1650
1748
  def eat_white_forward(self):
1651
1749
  p = self.cpos
1652
1750
  self.skip_chars_forward(' \t')
1653
1751
  self.Replace(p, self.cpos, '')
1654
-
1655
- @can_edit
1752
+
1753
+ @editable
1656
1754
  def eat_white_backward(self):
1657
1755
  p = self.cpos
1658
1756
  self.skip_chars_backward(' \t')
1659
1757
  self.Replace(max(self.cpos, self.bol), p, '')
1660
-
1661
- @can_edit
1758
+
1759
+ @editable
1760
+ def kill_word(self):
1761
+ if not self.SelectedText:
1762
+ self.WordRightEndExtend()
1763
+ self.ReplaceSelection('')
1764
+
1765
+ @editable
1766
+ def backward_kill_word(self):
1767
+ if not self.SelectedText:
1768
+ self.WordLeftExtend()
1769
+ self.ReplaceSelection('')
1770
+
1771
+ @editable
1662
1772
  def kill_line(self):
1663
- p = self.eol
1664
- if p == self.cpos: # caret at end of line
1665
- if self.get_char(p) == '\r': p += 1
1666
- if self.get_char(p) == '\n': p += 1
1667
- self.Replace(self.cpos, p, '')
1668
-
1669
- @can_edit
1773
+ if not self.SelectedText:
1774
+ p = self.cpos
1775
+ if p == self.eol:
1776
+ ## self.WordRightEndExtend() # Select cr/lf chunks.
1777
+ if self.get_char(p) == '\r': p += 1
1778
+ if self.get_char(p) == '\n': p += 1
1779
+ self.cpos = p
1780
+ else:
1781
+ self.cpos = self.eol
1782
+ self.ReplaceSelection('')
1783
+
1784
+ @editable
1670
1785
  def backward_kill_line(self):
1671
- p = self.bol
1672
- if p == self.cpos > 0: # caret at beginning of line
1673
- if self.get_char(p-1) == '\n': p -= 1
1674
- if self.get_char(p-1) == '\r': p -= 1
1675
- self.Replace(p, self.cpos, '')
1676
-
1677
- @can_edit
1786
+ if not self.SelectedText:
1787
+ p = self.cpos
1788
+ if p == self.bol:
1789
+ ## self.WordLeftExtend() # Select cr/lf chunks.
1790
+ if self.get_char(p-1) == '\n': p -= 1
1791
+ if self.get_char(p-1) == '\r': p -= 1
1792
+ self.cpos = p
1793
+ else:
1794
+ self.cpos = self.bol
1795
+ self.ReplaceSelection('')
1796
+
1797
+ @editable
1678
1798
  def insert_space_like_tab(self):
1679
1799
  """Insert half-width spaces forward as if feeling like [tab].
1680
1800
  タブの気持ちになって半角スペースを入力する
@@ -1682,15 +1802,15 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1682
1802
  self.eat_white_forward()
1683
1803
  _text, lp = self.CurLine
1684
1804
  self.WriteText(' ' * (4 - lp % 4))
1685
-
1686
- @can_edit
1805
+
1806
+ @editable
1687
1807
  def delete_backward_space_like_tab(self):
1688
1808
  """Delete half-width spaces backward as if feeling like [S-tab].
1689
1809
  シフト+タブの気持ちになって半角スペースを消す
1690
1810
  """
1691
1811
  self.eat_white_forward()
1692
1812
  _text, lp = self.CurLine
1693
- for i in range(lp % 4 or 4):
1813
+ for _i in range(lp % 4 or 4):
1694
1814
  p = self.cpos
1695
1815
  if p == self.bol or self.get_char(p-1) != ' ':
1696
1816
  break
@@ -1700,52 +1820,71 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1700
1820
 
1701
1821
  class Buffer(EditorInterface, EditWindow):
1702
1822
  """Python code buffer.
1703
-
1704
- Attributes:
1705
- name : buffer-name (basename)
1706
- filename : buffer-file-name
1707
- code : code object
1708
1823
  """
1709
1824
  @property
1710
1825
  def message(self):
1711
1826
  return self.parent.message
1712
-
1827
+
1713
1828
  @property
1714
1829
  def name(self):
1830
+ """buffer-name (basename)."""
1715
1831
  return os.path.basename(self.__filename or '')
1716
-
1717
- Name = name # page.window.Name for save/loadPerspective
1718
-
1832
+
1833
+ Name = name # page.window.Name for save/loadPerspective
1834
+
1719
1835
  @property
1720
1836
  def filename(self):
1837
+ """buffer-file-name."""
1721
1838
  return self.__filename
1722
-
1839
+
1723
1840
  def update_filestamp(self, fn):
1724
- if fn and os.path.isfile(fn):
1725
- self.__mtime = os.path.getmtime(fn) # update timestamp (modified time)
1726
- else:
1727
- self.__mtime = None
1841
+ self.__path = Path(fn)
1842
+ try:
1843
+ self.__mtime = os.path.getmtime(self.__path) # update timestamp (modified time)
1844
+ except FileNotFoundError:
1845
+ self.__mtime = False # valid path (but not found)
1846
+ except OSError:
1847
+ if re.match(url_re, fn):
1848
+ self.__mtime = -1
1849
+ else:
1850
+ self.__mtime = None # *invalid path*
1728
1851
  if self.__filename != fn:
1729
1852
  self.__filename = fn
1730
1853
  self.update_caption()
1731
-
1854
+
1732
1855
  @property
1733
1856
  def mtdelta(self):
1734
1857
  """Timestamp delta (for checking external mod).
1735
1858
 
1736
1859
  Returns:
1737
- None : no file
1738
- = 0 : a file
1739
- > 0 : a file edited externally
1740
- < 0 : a url file
1860
+ None: no file
1861
+ = 0: a file (even if not found)
1862
+ > 0: a file edited externally
1863
+ < 0: a url file
1741
1864
  """
1742
- fn = self.filename
1743
- if os.path.isfile(fn):
1744
- return os.path.getmtime(fn) - self.__mtime
1745
- if re.match(url_re, fn):
1746
- return -1
1747
- return None
1748
-
1865
+ try:
1866
+ mtime = os.path.getmtime(self.__path)
1867
+ return mtime - self.__mtime
1868
+ except FileNotFoundError:
1869
+ self.__mtime = False # valid path (but not found)
1870
+ except OSError:
1871
+ pass
1872
+ return self.__mtime
1873
+
1874
+ @property
1875
+ def need_buffer_save(self):
1876
+ """Return whether the buffer should be saved.
1877
+ The file has been modified internally.
1878
+ """
1879
+ return self.mtdelta is not None and self.IsModified()
1880
+
1881
+ @property
1882
+ def need_buffer_load(self):
1883
+ """Return whether the buffer should be loaded.
1884
+ The file has been modified externally.
1885
+ """
1886
+ return self.mtdelta is not None and self.mtdelta > 0
1887
+
1749
1888
  @property
1750
1889
  def caption_prefix(self):
1751
1890
  prefix = ''
@@ -1760,7 +1899,7 @@ class Buffer(EditorInterface, EditWindow):
1760
1899
  if prefix:
1761
1900
  prefix += ' '
1762
1901
  return prefix
1763
-
1902
+
1764
1903
  def update_caption(self):
1765
1904
  caption = self.caption_prefix + self.name
1766
1905
  try:
@@ -1768,31 +1907,7 @@ class Buffer(EditorInterface, EditWindow):
1768
1907
  self.parent.handler('buffer_caption_updated', self)
1769
1908
  except AttributeError:
1770
1909
  pass
1771
-
1772
- @property
1773
- def need_buffer_save(self):
1774
- """Returns whether the buffer should be saved.
1775
- The file has been modified internally.
1776
- """
1777
- return self.mtdelta is not None and self.IsModified()
1778
-
1779
- @property
1780
- def need_buffer_load(self):
1781
- """Returns whether the buffer should be loaded.
1782
- The file has been modified externally.
1783
- """
1784
- return self.mtdelta is not None and self.mtdelta > 0
1785
-
1786
- def pre_command_hook(self, evt):
1787
- self.parent.handler(self.handler.current_event, evt)
1788
- return EditorInterface.pre_command_hook(self, evt)
1789
- pre_command_hook.__name__ = str('pre_command_dispatch') # alias
1790
-
1791
- def post_command_hook(self, evt):
1792
- self.parent.handler(self.handler.current_event, evt)
1793
- return EditorInterface.post_command_hook(self, evt)
1794
- post_command_hook.__name__ = str('post_command_dispatch') # alias
1795
-
1910
+
1796
1911
  def __init__(self, parent, filename, **kwargs):
1797
1912
  EditWindow.__init__(self, parent, **kwargs)
1798
1913
  EditorInterface.__init__(self)
@@ -1802,7 +1917,7 @@ class Buffer(EditorInterface, EditWindow):
1802
1917
  self.update_filestamp(filename)
1803
1918
  self.code = None
1804
1919
 
1805
- self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdate) # skip to brace matching
1920
+ self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdate) # skip to brace matching
1806
1921
  self.Bind(stc.EVT_STC_CALLTIP_CLICK, self.OnCallTipClick)
1807
1922
  self.Bind(stc.EVT_STC_INDICATOR_CLICK, self.OnIndicatorClick)
1808
1923
 
@@ -1823,19 +1938,10 @@ class Buffer(EditorInterface, EditWindow):
1823
1938
 
1824
1939
  def clear(evt):
1825
1940
  ## """Clear selection and message, no skip."""
1826
- ## *do not* clear autocomp, so that the event can skip to AutoComp properly.
1827
- if self.CanEdit():
1828
- with self.off_undocollection():
1829
- self.ReplaceSelection("")
1830
- self.message("")
1831
-
1832
- def clear_autocomp(evt):
1833
- ## """Clear autocomp, selection, and message."""
1834
- if self.AutoCompActive():
1835
- self.AutoCompCancel()
1941
+ ## *DO NOT* clear autocomp, so that the event can skip to AutoComp properly.
1836
1942
  if self.CanEdit():
1837
1943
  with self.off_undocollection():
1838
- self.ReplaceSelection("")
1944
+ self.ReplaceSelection('')
1839
1945
  self.message("")
1840
1946
 
1841
1947
  def fork(evt):
@@ -1845,16 +1951,16 @@ class Buffer(EditorInterface, EditWindow):
1845
1951
  """Fork events to the parent."""
1846
1952
  self.parent.handler(self.handler.current_event, evt)
1847
1953
 
1848
- ## Note: Key events are not propagated from Buffer to EditorBook.
1954
+ ## Note: Mouse events are not propagated from Buffer to EditorBook.
1849
1955
  ## They are explicitly dispatched from buffer.handler to editor.handler.
1850
1956
 
1851
1957
  self.handler.update({ # DNA<Buffer>
1852
1958
  None : {
1853
1959
  'buffer_saved' : [ None, dispatch ],
1854
1960
  'buffer_loaded' : [ None, dispatch ],
1855
- 'buffer_modified' : [ None, dispatch, self.on_modified ],
1856
- 'buffer_activated' : [ None, dispatch, self.on_activated ],
1857
- 'buffer_inactivated' : [ None, dispatch, self.on_inactivated ],
1961
+ 'buffer_modified' : [ None, dispatch, self.on_buffer_modified ],
1962
+ 'buffer_activated' : [ None, dispatch, self.on_buffer_activated ],
1963
+ 'buffer_inactivated' : [ None, dispatch, self.on_buffer_inactivated ],
1858
1964
  'buffer_region_executed' : [ None, dispatch ],
1859
1965
  },
1860
1966
  -1 : { # original action of the EditWindow
@@ -1865,20 +1971,24 @@ class Buffer(EditorInterface, EditWindow):
1865
1971
  '*[LR]win pressed' : (-1, ),
1866
1972
  },
1867
1973
  0 : { # Normal mode
1868
- '* pressed' : (0, skip, dispatch),
1974
+ '* pressed' : (0, skip),
1869
1975
  '* released' : (0, skip, dispatch),
1976
+ '*button* pressed' : (0, skip, dispatch),
1977
+ 'Lbutton pressed' : (0, self.on_left_down),
1870
1978
  'escape pressed' : (-1, self.on_enter_escmap),
1871
1979
  'C-h pressed' : (0, self.call_helpTip),
1872
1980
  '. pressed' : (2, self.OnEnterDot),
1981
+ 'C-. pressed' : (2, self.call_word_autocomp),
1982
+ 'C-/ pressed' : (3, self.call_apropos_autocomp),
1873
1983
  'M-. pressed' : (2, self.call_word_autocomp),
1874
1984
  'M-/ pressed' : (3, self.call_apropos_autocomp),
1875
1985
  },
1876
1986
  2 : { # word auto completion AS-mode
1877
- 'quit' : (0, clear_autocomp),
1878
- '* pressed' : (0, clear_autocomp, fork),
1987
+ 'quit' : (0, self.clear_autocomp),
1988
+ '* pressed' : (0, self.clear_autocomp, fork),
1879
1989
  'tab pressed' : (0, clear, skip),
1880
1990
  'enter pressed' : (0, clear, skip),
1881
- 'escape pressed' : (0, clear_autocomp),
1991
+ 'escape pressed' : (0, clear, skip),
1882
1992
  'up pressed' : (2, skip, self.on_completion_backward),
1883
1993
  'down pressed' : (2, skip, self.on_completion_forward),
1884
1994
  '*left pressed' : (2, skip),
@@ -1893,7 +2003,7 @@ class Buffer(EditorInterface, EditWindow):
1893
2003
  '*delete pressed' : (2, skip),
1894
2004
  '*backspace pressed' : (2, skip),
1895
2005
  '*backspace released' : (2, self.call_word_autocomp),
1896
- 'C-S-backspace pressed' : (2, ),
2006
+ '*S-backspace pressed' : (0, self.clear_autocomp, fork),
1897
2007
  '*alt pressed' : (2, ),
1898
2008
  '*ctrl pressed' : (2, ),
1899
2009
  '*shift pressed' : (2, ),
@@ -1901,11 +2011,11 @@ class Buffer(EditorInterface, EditWindow):
1901
2011
  '*f[0-9]* pressed' : (2, ),
1902
2012
  },
1903
2013
  3 : { # apropos auto completion AS-mode
1904
- 'quit' : (0, clear_autocomp),
1905
- '* pressed' : (0, clear_autocomp, fork),
2014
+ 'quit' : (0, self.clear_autocomp),
2015
+ '* pressed' : (0, self.clear_autocomp, fork),
1906
2016
  'tab pressed' : (0, clear, skip),
1907
2017
  'enter pressed' : (0, clear, skip),
1908
- 'escape pressed' : (0, clear_autocomp),
2018
+ 'escape pressed' : (0, clear, skip),
1909
2019
  'up pressed' : (3, skip, self.on_completion_backward),
1910
2020
  'down pressed' : (3, skip, self.on_completion_forward),
1911
2021
  '*left pressed' : (3, skip),
@@ -1920,7 +2030,7 @@ class Buffer(EditorInterface, EditWindow):
1920
2030
  '*delete pressed' : (3, skip),
1921
2031
  '*backspace pressed' : (3, skip),
1922
2032
  '*backspace released' : (3, self.call_apropos_autocomp),
1923
- 'C-S-backspace pressed' : (3, ),
2033
+ '*S-backspace pressed' : (0, self.clear_autocomp, fork),
1924
2034
  '*alt pressed' : (3, ),
1925
2035
  '*ctrl pressed' : (3, ),
1926
2036
  '*shift pressed' : (3, ),
@@ -1931,103 +2041,118 @@ class Buffer(EditorInterface, EditWindow):
1931
2041
 
1932
2042
  self.show_folder()
1933
2043
  self.set_stylus(Stylus.py_text_mode)
1934
-
2044
+
1935
2045
  def __contains__(self, code):
1936
2046
  if inspect.iscode(code) and self.code:
1937
2047
  return code is self.code\
1938
2048
  or code in self.code.co_consts
1939
-
2049
+
1940
2050
  def trace_position(self):
1941
2051
  _text, lp = self.CurLine
1942
2052
  self.message("{:>6d}:{} ({})".format(self.cline, lp, self.cpos), pane=-1)
1943
-
1944
- def OnUpdate(self, evt): #<wx._stc.StyledTextEvent>
2053
+
2054
+ def OnUpdate(self, evt): #<wx._stc.StyledTextEvent>
1945
2055
  if evt.Updated & (stc.STC_UPDATE_SELECTION | stc.STC_UPDATE_CONTENT):
1946
2056
  self.trace_position()
1947
2057
  if evt.Updated & stc.STC_UPDATE_CONTENT:
1948
2058
  self.handler('buffer_modified', self)
1949
2059
  evt.Skip()
1950
-
1951
- def OnCallTipClick(self, evt): #<wx._stc.StyledTextEvent>
2060
+
2061
+ def OnCallTipClick(self, evt): #<wx._stc.StyledTextEvent>
1952
2062
  if self.CallTipActive():
1953
2063
  self.CallTipCancel()
1954
2064
  pos, tip, more = self._calltips
1955
2065
  if more:
1956
2066
  self.CallTipShow(pos, tip, N=None)
1957
2067
  evt.Skip()
1958
-
1959
- def OnIndicatorClick(self, evt): #<wx._stc.StyledTextEvent>
2068
+
2069
+ def OnIndicatorClick(self, evt): #<wx._stc.StyledTextEvent>
1960
2070
  if self.SelectedText or not wx.GetKeyState(wx.WXK_CONTROL):
1961
- ## Processing text selection, dragging, or dragging+
2071
+ ## Processing text selection or dragging.
1962
2072
  evt.Skip()
1963
2073
  return
2074
+
1964
2075
  pos = evt.Position
2076
+ self.goto_char(pos)
1965
2077
  i = 2
1966
- if self.IndicatorValueAt(i, pos): # [C-indic click]
2078
+ if self.IndicatorValueAt(i, pos): # [C-indic click]
1967
2079
  p = self.IndicatorStart(i, pos)
1968
2080
  q = self.IndicatorEnd(i, pos)
1969
2081
  url = self.GetTextRange(p, q).strip()
1970
- self.message("URL {!r}".format(url))
1971
- if wx.GetKeyState(wx.WXK_SHIFT): # [C-S-indic click]
2082
+ if wx.GetKeyState(wx.WXK_SHIFT): # [C-S-indic click]
1972
2083
  import webbrowser
1973
2084
  return webbrowser.open(url)
1974
2085
  else:
1975
2086
  ## Note: post-call for the confirmation dialog.
1976
2087
  wx.CallAfter(self.parent.load_file, url)
1977
- self.anchor = pos # Clear selection
1978
-
1979
- def on_modified(self, buf):
2088
+
2089
+ def on_buffer_modified(self, buf):
1980
2090
  """Called when the buffer is modified."""
1981
2091
  self.SetIndicatorCurrent(2)
1982
2092
  self.IndicatorClearRange(0, self.TextLength)
1983
2093
  for m in self.grep(url_re):
1984
2094
  p, q = m.span()
1985
2095
  self.IndicatorFillRange(p, q-p)
1986
-
2096
+
1987
2097
  def OnSavePointLeft(self, evt):
1988
2098
  self.update_caption()
1989
2099
  evt.Skip()
1990
-
2100
+
1991
2101
  def OnSavePointReached(self, evt):
1992
2102
  self.update_caption()
1993
2103
  evt.Skip()
1994
-
2104
+
1995
2105
  def OnEnterDot(self, evt):
2106
+ if not self.CanEdit():
2107
+ self.handler('quit', evt)
2108
+ return
1996
2109
  p = self.cpos
1997
- st = self.get_style(p-1)
2110
+ lst = self.get_style(p-1)
1998
2111
  rst = self.get_style(p)
1999
- if st not in ('moji', 'word', 'rparen') or rst == 'word':
2000
- self.handler('quit', evt) # don't enter autocomp
2112
+ if lst not in ('moji', 'word', 'rparen') or rst == 'word':
2113
+ self.handler('quit', evt) # Don't enter autocomp
2001
2114
  evt.Skip()
2002
-
2003
- def on_activated(self, buf):
2115
+
2116
+ def on_buffer_activated(self, buf):
2004
2117
  """Called when the buffer is activated."""
2005
2118
  self.update_caption()
2006
2119
  self.trace_position()
2007
-
2008
- def on_inactivated(self, buf):
2120
+
2121
+ def on_buffer_inactivated(self, buf):
2009
2122
  """Called when the buffer is inactivated."""
2010
2123
  pass
2011
-
2124
+
2125
+ def on_left_down(self, evt):
2126
+ pos = self.PositionFromPoint(evt.Position)
2127
+ ln = self.LineFromPosition(pos)
2128
+ ann_text = self.AnnotationGetText(ln)
2129
+ if ann_text:
2130
+ if pos == self.GetLineEndPosition(ln): # Check eol (not clicked yet).
2131
+ if wx.TheClipboard.Open():
2132
+ wx.TheClipboard.SetData(wx.TextDataObject(ann_text))
2133
+ wx.TheClipboard.Close()
2134
+ self.message("Annotation copied.")
2135
+ self.AnnotationClearLine(ln)
2136
+ evt.Skip()
2137
+
2012
2138
  def on_enter_escmap(self, evt):
2013
2139
  self.message("ESC-")
2014
-
2140
+
2015
2141
  def on_exit_escmap(self, evt):
2016
2142
  self.message("ESC {}".format(evt.key))
2017
2143
  self.AnnotationClearAll()
2018
-
2144
+
2019
2145
  ## --------------------------------
2020
- ## File I/O
2146
+ ## File I/O.
2021
2147
  ## --------------------------------
2022
-
2023
- def _load_textfile(self, text, filename):
2148
+
2149
+ def _load_textfile(self, text):
2024
2150
  with self.off_readonly():
2025
2151
  self.Text = text
2026
2152
  self.EmptyUndoBuffer()
2027
2153
  self.SetSavePoint()
2028
- self.update_filestamp(filename)
2029
2154
  self.handler('buffer_loaded', self)
2030
-
2155
+
2031
2156
  def _load_file(self, filename):
2032
2157
  """Wrapped method of LoadFile."""
2033
2158
  if self.LoadFile(filename):
@@ -2037,7 +2162,7 @@ class Buffer(EditorInterface, EditWindow):
2037
2162
  self.handler('buffer_loaded', self)
2038
2163
  return True
2039
2164
  return False
2040
-
2165
+
2041
2166
  def _save_file(self, filename):
2042
2167
  """Wrapped method of SaveFile."""
2043
2168
  if self.SaveFile(filename):
@@ -2046,7 +2171,7 @@ class Buffer(EditorInterface, EditWindow):
2046
2171
  self.handler('buffer_saved', self)
2047
2172
  return True
2048
2173
  return False
2049
-
2174
+
2050
2175
  def LoadFile(self, filename):
2051
2176
  """Load the contents of file into the editor.
2052
2177
 
@@ -2056,7 +2181,7 @@ class Buffer(EditorInterface, EditWindow):
2056
2181
  with self.off_readonly():
2057
2182
  self.Text = i.read()
2058
2183
  return True
2059
-
2184
+
2060
2185
  def SaveFile(self, filename):
2061
2186
  """Write the contents of the editor to file.
2062
2187
 
@@ -2065,37 +2190,38 @@ class Buffer(EditorInterface, EditWindow):
2065
2190
  with open(filename, "w", encoding='utf-8', newline='') as o:
2066
2191
  o.write(self.Text)
2067
2192
  return True
2068
-
2193
+
2069
2194
  ## --------------------------------
2070
- ## Python eval / exec
2195
+ ## Python eval / exec.
2071
2196
  ## --------------------------------
2072
-
2197
+
2073
2198
  @property
2074
- def locals(self): # internal use only
2199
+ def locals(self): # internal use only
2075
2200
  try:
2076
2201
  return self.parent.parent.current_shell.locals
2077
2202
  except AttributeError:
2078
2203
  return None
2079
-
2204
+
2080
2205
  @property
2081
- def globals(self): # internal use only
2206
+ def globals(self): # internal use only
2082
2207
  try:
2083
2208
  return self.parent.parent.current_shell.globals
2084
2209
  except AttributeError:
2085
2210
  return None
2086
-
2211
+
2087
2212
  def eval(self, text):
2088
- return eval(text, self.globals, self.locals) # using current shell namespace
2089
-
2213
+ return eval(text, self.globals, self.locals) # using current shell namespace
2214
+
2090
2215
  def exec(self, text):
2091
- exec(text, self.globals, self.locals) # using current shell namespace
2216
+ exec(text, self.globals, self.locals) # using current shell namespace
2092
2217
  dispatcher.send(signal='Interpreter.push',
2093
2218
  sender=self, command=None, more=False)
2094
-
2095
- def gen_text_at_caret(self):
2096
- """Generates the selected text,
2097
- otherwise the line or expression at the caret.
2098
- """
2219
+
2220
+ def eval_line(self):
2221
+ """Evaluate the selected word or line and show calltips."""
2222
+ if self.CallTipActive():
2223
+ self.CallTipCancel()
2224
+
2099
2225
  def _gen_text():
2100
2226
  text = self.SelectedText
2101
2227
  if text:
@@ -2103,49 +2229,44 @@ class Buffer(EditorInterface, EditWindow):
2103
2229
  else:
2104
2230
  yield self.line_at_caret
2105
2231
  yield self.expr_at_caret
2106
- return filter(None, _gen_text())
2107
-
2108
- def eval_line(self):
2109
- if self.CallTipActive():
2110
- self.CallTipCancel()
2111
2232
 
2112
- status = "No words"
2113
- for text in self.gen_text_at_caret():
2233
+ for text in _gen_text():
2114
2234
  try:
2115
2235
  obj = eval(text, self.globals, self.locals)
2116
2236
  except Exception as e:
2117
- status = "- {} : {!r}".format(e, text)
2237
+ self.message(e)
2118
2238
  else:
2119
2239
  self.CallTipShow(self.cpos, pformat(obj))
2120
2240
  self.message(text)
2121
2241
  return
2122
- self.message(status)
2123
-
2242
+ if not text:
2243
+ self.message("No words")
2244
+
2124
2245
  def exec_region(self):
2246
+ """Execute a region of code."""
2125
2247
  try:
2126
2248
  code = compile(self.Text, self.filename, "exec")
2127
2249
  exec(code, self.globals, self.locals)
2128
2250
  dispatcher.send(signal='Interpreter.push',
2129
2251
  sender=self, command=None, more=False)
2130
2252
  except BdbQuit:
2131
- self.red_pointer = self.cline
2132
2253
  pass
2133
2254
  except Exception as e:
2134
2255
  msg = traceback.format_exc()
2135
- err = re.findall(py_error_re, msg, re.M)
2256
+ err = re.findall(py_trace_re, msg, re.M)
2136
2257
  lines = [int(ln) for fn, ln in err if fn == self.filename]
2137
2258
  if lines:
2138
2259
  lx = lines[-1] - 1
2139
2260
  self.red_arrow = lx
2140
2261
  self.goto_line(lx)
2141
- self.EnsureVisible(lx) # expand if folded
2262
+ self.EnsureVisible(lx) # expand if folded
2142
2263
  self.EnsureCaretVisible()
2143
2264
  self.AnnotationSetStyle(lx, stc.STC_STYLE_ANNOTATION)
2144
2265
  self.AnnotationSetText(lx, msg)
2145
2266
  self.message(e)
2146
2267
  else:
2147
2268
  self.code = code
2148
- del self.pointer # Reset pointer (debugger hook point).
2269
+ del self.pointer # Reset pointer (debugger hook point).
2149
2270
  del self.red_arrow
2150
2271
  self.handler('buffer_region_executed', self)
2151
2272
  self.message("Evaluated {!r} successfully.".format(self.filename))
@@ -2156,16 +2277,16 @@ class EditorBook(AuiNotebook, CtrlInterface):
2156
2277
  """Python code editor.
2157
2278
 
2158
2279
  Args:
2159
- name : Window.Name (e.g. 'Scratch')
2280
+ name: Window.Name (e.g. 'Scratch')
2160
2281
 
2161
2282
  Attributes:
2162
- default_name : default buffer name (e.g. '*scratch*')
2163
- default_buffer : default buffer
2283
+ default_name: default buffer name (e.g. '*scratch*')
2284
+ default_buffer: default buffer
2164
2285
  """
2165
2286
  @property
2166
2287
  def message(self):
2167
2288
  return self.parent.message
2168
-
2289
+
2169
2290
  def __init__(self, parent, name="book", **kwargs):
2170
2291
  kwargs.setdefault('style',
2171
2292
  (aui.AUI_NB_DEFAULT_STYLE | aui.AUI_NB_TOP)
@@ -2177,12 +2298,11 @@ class EditorBook(AuiNotebook, CtrlInterface):
2177
2298
  ## So we set the tabs' height to zero to hide them.
2178
2299
  self.TabCtrlHeight = 0
2179
2300
 
2180
- self.defaultBufferStyle = dict(
2181
- ReadOnly = False,
2182
- )
2183
- self.parent = parent #: parent<ShellFrame> is not Parent<AuiNotebook>
2301
+ self.defaultBufferStyle = {}
2302
+
2303
+ self.parent = parent # parent<ShellFrame> is not Parent<AuiNotebook>
2184
2304
  self.Name = name
2185
- self.default_name = "*{}*".format(name.lower()) # e.g. '*scratch*'
2305
+ self.default_name = "*{}*".format(name.lower()) # e.g. '*scratch*'
2186
2306
  self.default_buffer = self.create_buffer(self.default_name)
2187
2307
 
2188
2308
  self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.OnPageClose)
@@ -2204,29 +2324,29 @@ class EditorBook(AuiNotebook, CtrlInterface):
2204
2324
  'buffer_loaded' : [ None, dispatch ],
2205
2325
  'buffer_deleted' : [ None, dispatch ],
2206
2326
  'buffer_modified' : [ None, dispatch ],
2207
- 'buffer_activated' : [ None, dispatch, self.on_activated ],
2208
- 'buffer_inactivated' : [ None, dispatch, self.on_inactivated ],
2327
+ 'buffer_activated' : [ None, dispatch, self.on_buffer_activated ],
2328
+ 'buffer_inactivated' : [ None, dispatch, self.on_buffer_inactivated ],
2209
2329
  'buffer_caption_updated' : [ None, dispatch ],
2210
- '*button* pressed' : [ None, dispatch, skip ],
2211
- '*button* released' : [ None, dispatch, skip ],
2212
2330
  },
2213
2331
  0 : { # Normal mode
2332
+ '* pressed' : (0, skip),
2333
+ '* released' : (0, skip, dispatch),
2334
+ '*button* pressed' : (0, skip, dispatch),
2214
2335
  'M-up pressed' : (0, _F(self.previous_buffer)),
2215
2336
  'M-down pressed' : (0, _F(self.next_buffer)),
2216
2337
  },
2217
2338
  })
2218
-
2339
+
2219
2340
  def OnDestroy(self, evt):
2220
2341
  obj = evt.EventObject
2221
2342
  if isinstance(obj, Buffer):
2222
2343
  self.handler('buffer_deleted', obj)
2223
2344
  evt.Skip()
2224
-
2225
- def OnPageClose(self, evt): #<wx._aui.AuiNotebookEvent>
2226
- nb = evt.EventObject
2227
- buf = nb.all_buffers[evt.Selection]
2345
+
2346
+ def OnPageClose(self, evt): #<wx._aui.AuiNotebookEvent>
2347
+ buf = self.GetPage(evt.Selection)
2228
2348
  if buf.need_buffer_save:
2229
- if wx.MessageBox( # Confirm close.
2349
+ if wx.MessageBox( # Confirm closing the buffer.
2230
2350
  "You are closing unsaved content.\n\n"
2231
2351
  "The changes will be discarded.\n"
2232
2352
  "Continue closing?",
@@ -2236,17 +2356,17 @@ class EditorBook(AuiNotebook, CtrlInterface):
2236
2356
  evt.Veto()
2237
2357
  return
2238
2358
  evt.Skip()
2239
-
2240
- def OnPageClosed(self, evt): #<wx._aui.AuiNotebookEvent>
2359
+
2360
+ def OnPageClosed(self, evt): #<wx._aui.AuiNotebookEvent>
2241
2361
  if self.PageCount == 0:
2242
2362
  self.new_buffer()
2243
2363
  evt.Skip()
2244
-
2364
+
2245
2365
  def set_attributes(self, buf=None, **kwargs):
2246
2366
  """Set multiple properties at once to the buffer(s).
2247
2367
 
2248
2368
  Args:
2249
- buf : a buffer to apply (if None, applies to all buffers).
2369
+ buf: a buffer to apply (if None, applies to all buffers).
2250
2370
  **kwargs: default style.
2251
2371
 
2252
2372
  ReadOnly = False
@@ -2268,29 +2388,36 @@ class EditorBook(AuiNotebook, CtrlInterface):
2268
2388
  _setattribute(buf, kwargs)
2269
2389
  else:
2270
2390
  self.defaultBufferStyle.update(kwargs)
2271
- for buf in self.all_buffers:
2391
+ for buf in self.get_all_buffers():
2272
2392
  _setattribute(buf, self.defaultBufferStyle)
2273
-
2274
- def on_activated(self, buf):
2393
+
2394
+ def on_buffer_activated(self, buf):
2275
2395
  """Called when the buffer is activated."""
2276
2396
  title = "{} file: {}".format(self.Name, buf.filename)
2277
2397
  self.parent.handler('title_window', title)
2278
-
2279
- def on_inactivated(self, buf):
2398
+
2399
+ def on_buffer_inactivated(self, buf):
2280
2400
  """Called when the buffer is inactivated."""
2281
2401
  pass
2282
-
2402
+
2283
2403
  ## --------------------------------
2284
- ## Buffer list controls
2404
+ ## Buffer list controls.
2285
2405
  ## --------------------------------
2286
-
2287
- @property
2288
- def all_buffers(self):
2289
- """Returns all buffer pages.
2290
- cf. equiv. AuiNotebook.all_pages
2291
- """
2292
- return [self.GetPage(j) for j in range(self.PageCount)]
2293
-
2406
+
2407
+ def get_all_buffers(self, fn=None):
2408
+ """Yields all buffers with specified fn:filename or code."""
2409
+ if fn is None:
2410
+ yield from self.get_pages(Buffer)
2411
+ elif isinstance(fn, str):
2412
+ g = os.path.realpath(fn)
2413
+ for buf in self.get_pages(Buffer):
2414
+ if fn == buf.filename or g == os.path.realpath(buf.filename):
2415
+ yield buf
2416
+ else:
2417
+ for buf in self.get_pages(Buffer):
2418
+ if fn is buf or fn in buf: # check code
2419
+ yield buf
2420
+
2294
2421
  @property
2295
2422
  def menu(self):
2296
2423
  """Yields context menu."""
@@ -2300,86 +2427,91 @@ class EditorBook(AuiNotebook, CtrlInterface):
2300
2427
  lambda v: buf.SetFocus(),
2301
2428
  lambda v: v.Check(buf is self.buffer))
2302
2429
 
2303
- return (_menu(j+1, x) for j, x in enumerate(self.all_buffers))
2304
-
2430
+ return (_menu(j+1, x) for j, x in enumerate(self.get_all_buffers()))
2431
+
2305
2432
  @property
2306
2433
  def buffer(self):
2307
- """Returns the currently selected page or None."""
2434
+ """Return the currently selected page or None."""
2308
2435
  return self.CurrentPage
2309
-
2436
+
2310
2437
  def find_buffer(self, fn):
2311
- """Find buffer with specified fn:filename or code."""
2312
- if isinstance(fn, str):
2313
- g = os.path.realpath(fn)
2314
- for buf in self.all_buffers:
2315
- if fn == buf.filename or g == os.path.realpath(buf.filename):
2316
- return buf
2317
- else:
2318
- for buf in self.all_buffers:
2319
- if fn is buf or fn in buf: # check code
2320
- return buf
2321
-
2438
+ """Find a buffer with specified fn:filename or code."""
2439
+ return next(self.get_all_buffers(fn), None)
2440
+
2322
2441
  def swap_buffer(self, buf, lineno=0):
2323
2442
  self.swap_page(buf)
2324
2443
  if lineno:
2325
2444
  buf.markline = lineno - 1
2326
2445
  buf.goto_marker(1)
2327
-
2446
+
2328
2447
  def create_buffer(self, filename, index=None):
2329
2448
  """Create a new buffer (internal use only)."""
2330
- try:
2331
- self.Freeze()
2449
+ with wx.FrozenWindow(self):
2332
2450
  buf = Buffer(self, filename, style=wx.BORDER_DEFAULT)
2333
2451
  self.set_attributes(buf, **self.defaultBufferStyle)
2334
2452
  if index is None:
2335
2453
  index = self.PageCount
2336
- self.InsertPage(index, buf, buf.name)
2454
+ self.InsertPage(index, buf, buf.name) # => [buffer_activated]
2337
2455
  self.handler('buffer_new', buf)
2338
2456
  return buf
2339
- finally:
2340
- self.Thaw()
2341
-
2457
+
2342
2458
  def new_buffer(self):
2343
2459
  """Create a new default buffer."""
2344
2460
  buf = self.default_buffer
2345
- if not buf or buf.mtdelta is not None: # is saved?
2346
- buf = self.create_buffer(self.default_name, index=0)
2461
+ if not buf or buf.mtdelta is not None: # is saved?
2462
+ buf = self.create_buffer(self.default_name)
2347
2463
  self.default_buffer = buf
2348
2464
  else:
2349
2465
  buf.ClearAll()
2350
- ## buf.EmptyUndoBuffer()
2466
+ # buf.EmptyUndoBuffer()
2351
2467
  buf.SetFocus()
2352
2468
  return buf
2353
-
2469
+
2354
2470
  def delete_buffer(self, buf=None):
2355
2471
  """Pop the current buffer from the buffer list."""
2356
2472
  if not buf:
2357
2473
  buf = self.buffer
2358
2474
  j = self.GetPageIndex(buf)
2359
2475
  if j != -1:
2360
- self.DeletePage(j) # the focus moves
2361
- if not self.buffer: # no buffers
2362
- self.new_buffer()
2363
-
2476
+ self.DeletePage(j) # the focus moves
2477
+ if not self.buffer: # no buffers
2478
+ wx.CallAfter(self.new_buffer) # Note: post-call to avoid a crash.
2479
+
2364
2480
  def delete_all_buffers(self):
2365
2481
  """Initialize list of buffers."""
2366
2482
  self.DeleteAllPages()
2367
- self.new_buffer()
2368
-
2483
+ wx.CallAfter(self.new_buffer) # Note: post-call to avoid a crash.
2484
+
2369
2485
  def next_buffer(self):
2370
- self.Selection += 1
2371
-
2486
+ if self.Selection < self.PageCount - 1:
2487
+ self.Selection += 1
2488
+ else:
2489
+ books = list(self.Parent.get_pages(type(self)))
2490
+ k = books.index(self)
2491
+ if k < len(books) - 1:
2492
+ other_editor = books[k+1]
2493
+ other_editor.Selection = 0
2494
+ other_editor.CurrentPage.SetFocus()
2495
+
2372
2496
  def previous_buffer(self):
2373
- self.Selection -= 1
2374
-
2497
+ if self.Selection > 0:
2498
+ self.Selection -= 1
2499
+ else:
2500
+ books = list(self.Parent.get_pages(type(self)))
2501
+ k = books.index(self)
2502
+ if k > 0:
2503
+ other_editor = books[k-1]
2504
+ other_editor.Selection = other_editor.PageCount - 1
2505
+ other_editor.CurrentPage.SetFocus()
2506
+
2375
2507
  ## --------------------------------
2376
- ## File I/O
2508
+ ## File I/O.
2377
2509
  ## --------------------------------
2378
2510
  wildcards = [
2379
2511
  "PY files (*.py)|*.py",
2380
2512
  "ALL files (*.*)|*.*",
2381
2513
  ]
2382
-
2514
+
2383
2515
  def load_cache(self, filename, lineno=0):
2384
2516
  """Load a file from cache using linecache.
2385
2517
  Note:
@@ -2395,12 +2527,13 @@ class EditorBook(AuiNotebook, CtrlInterface):
2395
2527
  elif not buf.need_buffer_load:
2396
2528
  self.swap_buffer(buf, lineno)
2397
2529
  return True
2398
- buf._load_textfile(''.join(lines), filename)
2530
+ buf._load_textfile(''.join(lines))
2531
+ buf.update_filestamp(filename)
2399
2532
  self.swap_buffer(buf, lineno)
2400
2533
  return True
2401
2534
  return False
2402
-
2403
- def load_file(self, filename, lineno=0, verbose=True):
2535
+
2536
+ def load_file(self, filename, lineno=0, verbose=True, **kwargs):
2404
2537
  """Load a file into an existing or new buffer.
2405
2538
  The requests module is required to use URL extension.
2406
2539
  """
@@ -2408,7 +2541,7 @@ class EditorBook(AuiNotebook, CtrlInterface):
2408
2541
  if not buf:
2409
2542
  buf = self.create_buffer("*temp file*")
2410
2543
  elif buf.need_buffer_save and verbose:
2411
- if wx.MessageBox( # Confirm load.
2544
+ if wx.MessageBox( # Confirm loading the buffer.
2412
2545
  "You are leaving unsaved content.\n\n"
2413
2546
  "The changes will be discarded.\n"
2414
2547
  "Continue loading?",
@@ -2422,22 +2555,23 @@ class EditorBook(AuiNotebook, CtrlInterface):
2422
2555
  try:
2423
2556
  if re.match(url_re, filename):
2424
2557
  import requests
2425
- res = requests.get(filename, timeout=3.0)
2426
- if res.status_code == requests.codes.ok:
2427
- buf._load_textfile(res.text, filename)
2558
+ kwargs.setdefault('timeout', 3.0)
2559
+ res = requests.get(filename, **kwargs)
2560
+ if res.status_code == requests.codes.OK:
2561
+ buf._load_textfile(res.text)
2562
+ buf.update_filestamp(filename)
2428
2563
  self.swap_buffer(buf, lineno)
2429
2564
  return True
2430
- ## return False
2431
- raise Exception("The requested URL was not found")
2565
+ res.raise_for_status() # raise HTTP error; don't catch here.
2432
2566
  if buf._load_file(filename):
2433
2567
  self.swap_buffer(buf, lineno)
2434
2568
  return True
2435
2569
  return False
2436
- except Exception as e:
2437
- self.post_message(f"Failed to load {filename!r}.", e)
2570
+ except (OSError, UnicodeDecodeError, ModuleNotFoundError) as e:
2571
+ self.post_message("Failed to load:", e)
2438
2572
  self.delete_buffer(buf)
2439
2573
  return False
2440
-
2574
+
2441
2575
  def find_file(self, filename=None):
2442
2576
  """Open the specified file."""
2443
2577
  if not filename:
@@ -2446,21 +2580,22 @@ class EditorBook(AuiNotebook, CtrlInterface):
2446
2580
  wildcard='|'.join(self.wildcards),
2447
2581
  style=wx.FD_OPEN|wx.FD_MULTIPLE) as dlg:
2448
2582
  if dlg.ShowModal() == wx.ID_OK:
2449
- for fn in dlg.Paths:
2450
- self.find_file(fn)
2451
- return
2452
- if not self.load_file(filename):
2583
+ return all([self.find_file(fn) for fn in dlg.Paths])
2584
+ return None
2585
+ retval = self.load_file(filename)
2586
+ if retval == False: # noqa # to check if not None
2453
2587
  buf = self.create_buffer(filename)
2454
- buf._Buffer__mtime = 0 # => need_buffer_save
2455
2588
  self.swap_buffer(buf)
2456
-
2589
+ self.post_message("New file.")
2590
+ return retval
2591
+
2457
2592
  def save_file(self, filename, buf=None, verbose=True):
2458
2593
  """Save the current buffer to a file.
2459
2594
  """
2460
2595
  buf = buf or self.buffer
2461
2596
  if buf.need_buffer_load and verbose:
2462
2597
  self.swap_buffer(buf)
2463
- if wx.MessageBox( # Confirm save.
2598
+ if wx.MessageBox( # Confirm saving the buffer.
2464
2599
  "The file has been modified externally.\n\n"
2465
2600
  "The contents of the file will be overwritten.\n"
2466
2601
  "Continue saving?",
@@ -2474,10 +2609,10 @@ class EditorBook(AuiNotebook, CtrlInterface):
2474
2609
  self.default_buffer = None
2475
2610
  return True
2476
2611
  return False
2477
- except Exception as e:
2478
- self.post_message(f"Failed to save {filename!r}.", e)
2612
+ except (OSError, UnicodeDecodeError) as e:
2613
+ self.post_message("Failed to save:", e)
2479
2614
  return False
2480
-
2615
+
2481
2616
  def load_buffer(self, buf=None):
2482
2617
  """Confirm the load with the dialog."""
2483
2618
  buf = buf or self.buffer
@@ -2490,7 +2625,7 @@ class EditorBook(AuiNotebook, CtrlInterface):
2490
2625
  return None
2491
2626
  else:
2492
2627
  return self.load_file(buf.filename, buf.markline+1)
2493
-
2628
+
2494
2629
  def save_buffer(self, buf=None):
2495
2630
  """Confirm the save with the dialog."""
2496
2631
  buf = buf or self.buffer
@@ -2503,28 +2638,28 @@ class EditorBook(AuiNotebook, CtrlInterface):
2503
2638
  return None
2504
2639
  else:
2505
2640
  return self.save_file(buf.filename, buf)
2506
-
2641
+
2507
2642
  def save_buffer_as(self, buf=None):
2508
2643
  """Confirm the saveas with the dialog."""
2509
2644
  buf = buf or self.buffer
2510
2645
  with wx.FileDialog(self, "Save buffer as",
2511
2646
  defaultDir=os.path.dirname(self.buffer.filename),
2512
- defaultFile=re.sub(r'[\/:*?"<>|]', '_', buf.name),
2647
+ defaultFile=fix_fnchars(buf.name),
2513
2648
  wildcard='|'.join(self.wildcards),
2514
2649
  style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) as dlg:
2515
2650
  if dlg.ShowModal() == wx.ID_OK:
2516
2651
  return self.save_file(dlg.Path, buf)
2517
-
2652
+
2518
2653
  def save_all_buffers(self):
2519
- for buf in self.all_buffers:
2654
+ for buf in self.get_all_buffers():
2520
2655
  if buf.need_buffer_save:
2521
2656
  self.save_buffer(buf)
2522
-
2657
+
2523
2658
  def kill_buffer(self, buf=None):
2524
2659
  """Confirm the close with the dialog."""
2525
2660
  buf = buf or self.buffer
2526
2661
  if buf.need_buffer_save:
2527
- if wx.MessageBox( # Confirm close.
2662
+ if wx.MessageBox( # Confirm closing the buffer.
2528
2663
  "You are closing unsaved content.\n\n"
2529
2664
  "The changes will be discarded.\n"
2530
2665
  "Continue closing?",
@@ -2532,12 +2667,12 @@ class EditorBook(AuiNotebook, CtrlInterface):
2532
2667
  style=wx.YES_NO|wx.ICON_INFORMATION) != wx.YES:
2533
2668
  self.post_message("The close has been canceled.")
2534
2669
  return None
2535
- wx.CallAfter(self.delete_buffer, buf)
2536
-
2670
+ self.delete_buffer(buf)
2671
+
2537
2672
  def kill_all_buffers(self):
2538
- for buf in self.all_buffers:
2673
+ for buf in self.get_all_buffers():
2539
2674
  if buf.need_buffer_save:
2540
- if wx.MessageBox( # Confirm close.
2675
+ if wx.MessageBox( # Confirm closing the buffer.
2541
2676
  "You are closing unsaved content.\n\n"
2542
2677
  "The changes will be discarded.\n"
2543
2678
  "Continue closing?",
@@ -2545,7 +2680,7 @@ class EditorBook(AuiNotebook, CtrlInterface):
2545
2680
  style=wx.YES_NO|wx.ICON_INFORMATION) != wx.YES:
2546
2681
  self.post_message("The close has been canceled.")
2547
2682
  return None
2548
- wx.CallAfter(self.delete_all_buffers)
2683
+ self.delete_all_buffers()
2549
2684
 
2550
2685
 
2551
2686
  class Interpreter(interpreter.Interpreter):
@@ -2556,7 +2691,7 @@ class Interpreter(interpreter.Interpreter):
2556
2691
 
2557
2692
  self.parent = interpShell
2558
2693
  self.globals = self.locals
2559
-
2694
+
2560
2695
  def runcode(self, code):
2561
2696
  """Execute a code object.
2562
2697
 
@@ -2569,10 +2704,10 @@ class Interpreter(interpreter.Interpreter):
2569
2704
  except Exception:
2570
2705
  self.showtraceback()
2571
2706
  finally:
2572
- ## ex. KeyboardInterrupt:
2707
+ ## ex. KeyboardInterrupt
2573
2708
  if wx.IsBusy():
2574
2709
  wx.EndBusyCursor()
2575
-
2710
+
2576
2711
  def showtraceback(self):
2577
2712
  """Display the exception that just occurred.
2578
2713
 
@@ -2589,7 +2724,7 @@ class Interpreter(interpreter.Interpreter):
2589
2724
  self.parent.handler('interp_error', v)
2590
2725
  except AttributeError:
2591
2726
  pass
2592
-
2727
+
2593
2728
  def showsyntaxerror(self, filename=None, **kwargs):
2594
2729
  """Display the syntax error that just occurred.
2595
2730
 
@@ -2602,22 +2737,6 @@ class Interpreter(interpreter.Interpreter):
2602
2737
  self.parent.handler('interp_error', v)
2603
2738
  except AttributeError:
2604
2739
  pass
2605
-
2606
- @ignore(DeprecationWarning)
2607
- def getCallTip(self, command='', *args, **kwargs):
2608
- """Return call tip text for a command.
2609
-
2610
- (override) Ignore DeprecationWarning: for function,
2611
- `formatargspec` is deprecated since Python 3.5.
2612
- (override) Ignore ValueError: no signature found for builtin
2613
- if the unwrapped function is a builtin function.
2614
- """
2615
- ## In 4.2.1, DeprecationWarning was fixed.
2616
- ## In 4.2.2, ValueError was fixed.
2617
- try:
2618
- return interpreter.Interpreter.getCallTip(self, command, *args, **kwargs)
2619
- except ValueError:
2620
- return interpreter.Interpreter.getCallTip(self) # dummy
2621
2740
 
2622
2741
 
2623
2742
  class Nautilus(EditorInterface, Shell):
@@ -2639,10 +2758,9 @@ class Nautilus(EditorInterface, Shell):
2639
2758
 
2640
2759
  - quoteback : x`y --> y=x | x`y`z --> z=y=x
2641
2760
  - pullback : x@y --> y(x) | x@y@z --> z(y(x))
2642
- - apropos : x.y? [not] p --> shows apropos (not-)matched by predicates p
2643
- equiv. apropos(x, y [,ignorecase ?:True,??:False] [,pred=p])
2644
- ``y`` can contain regular expressions except for a dot.
2645
- ``y`` can contain abbreviations: \\a:[a-z], \\A:[A-Z] .
2761
+ - apropos : x.y? [not] p --> shows items (not) matched by predicate p
2762
+ equiv. apropos(x, y [,ignorecase ?:True,??:False] [,pred=p]).
2763
+ ``y`` can contain regular expressions, but no dots or backslashes.
2646
2764
  ``p`` can be atom, callable, type (e.g., int, str, ...),
2647
2765
  and any predicates such as inspect.isclass.
2648
2766
 
@@ -2657,15 +2775,15 @@ class Nautilus(EditorInterface, Shell):
2657
2775
 
2658
2776
  C-up : [0] retrieve previous history
2659
2777
  C-down : [0] retrieve next history
2660
- C-j, M-j : [0] tooltip of eval (for the selected or focused word)
2661
- C-h, M-h : [0] calltip of help (for the selected or focused func)
2778
+ C-j : [0] tooltip of eval (for the selected or focused word)
2779
+ C-h M-h : [0] calltip of help (for the selected or focused func)
2662
2780
  TAB : [1] history-comp
2663
2781
  M-p : [1] retrieve previous history in history-comp mode
2664
2782
  M-n : [1] retrieve next history in history-comp mode
2665
- M-. : [2] word-comp
2666
- M-/ : [3] apropos-comp
2667
- M-, : [4] text-comp
2668
- M-m : [5] module-comp
2783
+ M-. C-. : [2] word-comp
2784
+ M-/ C-/ : [3] apropos-comp
2785
+ M-, C-, : [4] text-comp
2786
+ M-m C-m : [5] module-comp
2669
2787
 
2670
2788
  Autocomps are incremental when pressed any alnums,
2671
2789
  and decremental when backspace.
@@ -2690,17 +2808,17 @@ class Nautilus(EditorInterface, Shell):
2690
2808
  @property
2691
2809
  def message(self):
2692
2810
  return self.parent.message
2693
-
2811
+
2694
2812
  @property
2695
2813
  def target(self):
2696
2814
  return self.__target
2697
-
2815
+
2698
2816
  @target.setter
2699
2817
  def target(self, obj):
2700
2818
  """Reset the shell target object; Rename the parent title.
2701
2819
  """
2702
2820
  if not hasattr(obj, '__dict__'):
2703
- raise TypeError("primitive objects cannot be targeted")
2821
+ raise TypeError("invalid target")
2704
2822
 
2705
2823
  self.__target = obj
2706
2824
  self.locals = obj.__dict__
@@ -2709,38 +2827,39 @@ class Nautilus(EditorInterface, Shell):
2709
2827
  try:
2710
2828
  obj.self = obj
2711
2829
  obj.this = inspect.getmodule(obj)
2712
- obj.shell = self # overwrite the facade <wx.py.shell.ShellFacade>
2830
+ obj.shell = self # Overwrite the facade <wx.py.shell.ShellFacade>.
2831
+ ## obj.__name__ = typename(obj) # A namespace for ghost in the shell. cf. exec_region
2713
2832
  except AttributeError:
2714
2833
  pass
2715
2834
  self.parent.handler('title_window', obj)
2716
-
2835
+
2717
2836
  @property
2718
2837
  def locals(self):
2719
2838
  return self.interp.locals
2720
-
2839
+
2721
2840
  @locals.setter
2722
- def locals(self, v): # internal use only
2841
+ def locals(self, v): # internal use only
2723
2842
  self.interp.locals = v
2724
-
2843
+
2725
2844
  @locals.deleter
2726
- def locals(self): # internal use only
2845
+ def locals(self): # internal use only
2727
2846
  self.interp.locals = self.__target.__dict__
2728
-
2847
+
2729
2848
  @property
2730
2849
  def globals(self):
2731
2850
  return self.interp.globals
2732
-
2851
+
2733
2852
  @globals.setter
2734
- def globals(self, v): # internal use only
2853
+ def globals(self, v): # internal use only
2735
2854
  self.interp.globals = v
2736
-
2855
+
2737
2856
  @globals.deleter
2738
- def globals(self): # internal use only
2857
+ def globals(self): # internal use only
2739
2858
  self.interp.globals = self.__target.__dict__
2740
2859
  self.interp.globals.update(self.__globals)
2741
-
2860
+
2742
2861
  __globals = {}
2743
-
2862
+
2744
2863
  def __init__(self, parent, target, name="root",
2745
2864
  introText=None,
2746
2865
  startupScript=None,
@@ -2748,22 +2867,22 @@ class Nautilus(EditorInterface, Shell):
2748
2867
  **kwargs):
2749
2868
  Shell.__init__(self, parent,
2750
2869
  locals=target.__dict__,
2751
- interpShell=self, # **kwds of InterpClass
2870
+ interpShell=self, # **kwds of InterpClass
2752
2871
  InterpClass=Interpreter,
2753
2872
  introText=introText,
2754
2873
  startupScript=startupScript,
2755
- execStartupScript=execStartupScript, # executes ~/.py
2874
+ execStartupScript=execStartupScript, # executes ~/.py
2756
2875
  **kwargs)
2757
2876
  EditorInterface.__init__(self)
2758
2877
 
2759
- self.parent = parent #: parent<ShellFrame> is not Parent<AuiNotebook>
2878
+ self.parent = parent # parent<ShellFrame> is not Parent<AuiNotebook>
2760
2879
  self.target = target
2761
2880
  self.Name = name
2762
2881
 
2763
2882
  wx.py.shell.USE_MAGIC = True
2764
- wx.py.shell.magic = self.magic # called when USE_MAGIC
2883
+ wx.py.shell.magic = self.magic # called when USE_MAGIC
2765
2884
 
2766
- self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdate) # skip to brace matching
2885
+ self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdate) # skip to brace matching
2767
2886
  self.Bind(stc.EVT_STC_CALLTIP_CLICK, self.OnCallTipClick)
2768
2887
 
2769
2888
  self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
@@ -2782,19 +2901,10 @@ class Nautilus(EditorInterface, Shell):
2782
2901
 
2783
2902
  def clear(evt):
2784
2903
  ## """Clear selection and message, no skip."""
2785
- ## *do not* clear autocomp, so that the event can skip to AutoComp properly.
2786
- if self.CanEdit():
2787
- with self.off_undocollection():
2788
- self.ReplaceSelection("")
2789
- self.message("")
2790
-
2791
- def clear_autocomp(evt):
2792
- ## """Clear autocomp, selection, and message."""
2793
- if self.AutoCompActive():
2794
- self.AutoCompCancel()
2904
+ ## *DO NOT* clear autocomp, so that the event can skip to AutoComp properly.
2795
2905
  if self.CanEdit():
2796
2906
  with self.off_undocollection():
2797
- self.ReplaceSelection("")
2907
+ self.ReplaceSelection('')
2798
2908
  self.message("")
2799
2909
 
2800
2910
  def fork(evt):
@@ -2807,12 +2917,10 @@ class Nautilus(EditorInterface, Shell):
2807
2917
  self.handler.update({ # DNA<Nautilus>
2808
2918
  None : {
2809
2919
  'interp_error' : [ None, self.on_interp_error ],
2810
- 'shell_deleted' : [ None, dispatch, self.on_deleted ],
2920
+ 'shell_deleted' : [ None, dispatch, self.on_shell_deleted ],
2811
2921
  'shell_modified' : [ None, dispatch ],
2812
- 'shell_activated' : [ None, dispatch, self.on_activated ],
2813
- 'shell_inactivated' : [ None, dispatch, self.on_inactivated ],
2814
- '*button* pressed' : [ None, dispatch, skip ],
2815
- '*button* released' : [ None, dispatch, skip ],
2922
+ 'shell_activated' : [ None, dispatch, self.on_shell_activated ],
2923
+ 'shell_inactivated' : [ None, dispatch, self.on_shell_inactivated ],
2816
2924
  },
2817
2925
  -1 : { # original action of the wx.py.shell
2818
2926
  '* pressed' : (0, skip, self.on_exit_escmap),
@@ -2829,36 +2937,39 @@ class Nautilus(EditorInterface, Shell):
2829
2937
  },
2830
2938
  0 : { # Normal mode
2831
2939
  '* pressed' : (0, skip),
2832
- '* released' : (0, skip),
2940
+ '* released' : (0, skip, dispatch),
2941
+ '*button* pressed' : (0, skip, dispatch),
2833
2942
  'escape pressed' : (-1, self.on_enter_escmap),
2834
2943
  'space pressed' : (0, self.OnSpace),
2835
2944
  '*backspace pressed' : (0, self.OnBackspace),
2836
- 'C-backspace pressed' : (0, _F(self.backward_kill_word)),
2837
- 'S-backspace pressed' : (0, _F(self.backward_kill_line)),
2838
2945
  'enter pressed' : (0, self.OnEnter),
2839
2946
  'C-enter pressed' : (0, _F(self.insertLineBreak)),
2840
2947
  'C-S-enter pressed' : (0, _F(self.insertLineBreak)),
2841
- '*enter pressed' : (0, ), # -> OnShowCompHistory 無効
2948
+ '*enter pressed' : (0, ), # -> OnShowCompHistory 無効
2842
2949
  'C-[ pressed' : (0, _F(self.goto_previous_mark_arrow)),
2843
2950
  'C-S-[ pressed' : (0, _F(self.goto_previous_mark_arrow, selection=1)),
2844
2951
  'C-] pressed' : (0, _F(self.goto_next_mark_arrow)),
2845
2952
  'C-S-] pressed' : (0, _F(self.goto_next_mark_arrow, selection=1)),
2846
2953
  'M-up pressed' : (0, _F(self.goto_previous_white_arrow)),
2847
2954
  'M-down pressed' : (0, _F(self.goto_next_white_arrow)),
2848
- # 'C-c pressed' : (0, skip), # -> spec-map
2849
- 'C-S-c pressed' : (0, skip), # -> Copy selected text, retaining prompts.
2955
+ # 'C-c pressed' : (0, skip), # -> spec-map
2956
+ 'C-S-c pressed' : (0, skip), # -> Copy selected text, retaining prompts.
2850
2957
  'C-v pressed' : (0, _F(self.Paste)),
2851
2958
  'C-S-v pressed' : (0, _F(self.Paste, rectangle=1)),
2852
2959
  'S-insert pressed' : (0, _F(self.Paste)),
2853
2960
  'C-S-insert pressed' : (0, _F(self.Paste, rectangle=1)),
2854
- 'C-j pressed' : (0, self.eval_line),
2855
- 'M-j pressed' : (0, self.exec_region),
2961
+ 'C-j pressed' : (0, _F(self.eval_line)),
2962
+ 'M-j pressed' : (0, _F(self.exec_region)),
2856
2963
  'C-h pressed' : (0, self.call_helpTip),
2857
2964
  'M-h pressed' : (0, self.call_helpDoc),
2858
- '. pressed' : (2, self.OnEnterDot),
2859
2965
  'tab pressed' : (1, self.call_history_comp),
2860
2966
  'M-p pressed' : (1, self.call_history_comp),
2861
2967
  'M-n pressed' : (1, self.call_history_comp),
2968
+ '. pressed' : (2, self.OnEnterDot),
2969
+ 'C-. pressed' : (2, self.call_word_autocomp),
2970
+ 'C-/ pressed' : (3, self.call_apropos_autocomp),
2971
+ 'C-, pressed' : (4, self.call_text_autocomp),
2972
+ 'C-m pressed' : (5, self.call_module_autocomp),
2862
2973
  'M-. pressed' : (2, self.call_word_autocomp),
2863
2974
  'M-/ pressed' : (3, self.call_apropos_autocomp),
2864
2975
  'M-, pressed' : (4, self.call_text_autocomp),
@@ -2874,10 +2985,10 @@ class Nautilus(EditorInterface, Shell):
2874
2985
  'S-left released' : (1, self.call_history_comp),
2875
2986
  'S-right pressed' : (1, skip),
2876
2987
  'S-right released' : (1, self.call_history_comp),
2877
- 'tab pressed' : (1, _F(self._on_completion, 1)), # 古いヒストリへ進む
2878
- 'S-tab pressed' : (1, _F(self._on_completion, -1)), # 新しいヒストリへ戻る
2879
- 'M-p pressed' : (1, _F(self._on_completion, 1)),
2880
- 'M-n pressed' : (1, _F(self._on_completion, -1)),
2988
+ 'tab pressed' : (1, _F(self._on_completion, step=1)), # 古いヒストリへ進む
2989
+ 'S-tab pressed' : (1, _F(self._on_completion, step=-1)), # 新しいヒストリへ戻る
2990
+ 'M-p pressed' : (1, _F(self._on_completion, step=1)),
2991
+ 'M-n pressed' : (1, _F(self._on_completion, step=-1)),
2881
2992
  '[a-z0-9_] pressed' : (1, skip),
2882
2993
  '[a-z0-9_] released' : (1, self.call_history_comp),
2883
2994
  'S-[a-z\\] pressed' : (1, skip),
@@ -2890,11 +3001,11 @@ class Nautilus(EditorInterface, Shell):
2890
3001
  '*f[0-9]* pressed' : (1, ),
2891
3002
  },
2892
3003
  2 : { # word auto completion AS-mode
2893
- 'quit' : (0, clear_autocomp),
2894
- '* pressed' : (0, clear_autocomp, fork),
3004
+ 'quit' : (0, self.clear_autocomp),
3005
+ '* pressed' : (0, self.clear_autocomp, fork),
2895
3006
  'tab pressed' : (0, clear, skip),
2896
3007
  'enter pressed' : (0, clear, skip),
2897
- 'escape pressed' : (0, clear_autocomp),
3008
+ 'escape pressed' : (0, clear, skip),
2898
3009
  'up pressed' : (2, skip, self.on_completion_backward),
2899
3010
  'down pressed' : (2, skip, self.on_completion_forward),
2900
3011
  '*left pressed' : (2, skip),
@@ -2909,8 +3020,8 @@ class Nautilus(EditorInterface, Shell):
2909
3020
  '*delete pressed' : (2, skip),
2910
3021
  '*backspace pressed' : (2, self.OnBackspace),
2911
3022
  '*backspace released' : (2, self.call_word_autocomp),
2912
- 'C-S-backspace pressed' : (2, ),
2913
- 'C-j pressed' : (2, self.eval_line),
3023
+ '*S-backspace pressed' : (0, self.clear_autocomp, fork),
3024
+ 'C-j pressed' : (2, _F(self.eval_line)),
2914
3025
  '*alt pressed' : (2, ),
2915
3026
  '*ctrl pressed' : (2, ),
2916
3027
  '*shift pressed' : (2, ),
@@ -2918,11 +3029,11 @@ class Nautilus(EditorInterface, Shell):
2918
3029
  '*f[0-9]* pressed' : (2, ),
2919
3030
  },
2920
3031
  3 : { # apropos auto completion AS-mode
2921
- 'quit' : (0, clear_autocomp),
2922
- '* pressed' : (0, clear_autocomp, fork),
3032
+ 'quit' : (0, self.clear_autocomp),
3033
+ '* pressed' : (0, self.clear_autocomp, fork),
2923
3034
  'tab pressed' : (0, clear, skip),
2924
3035
  'enter pressed' : (0, clear, skip),
2925
- 'escape pressed' : (0, clear_autocomp),
3036
+ 'escape pressed' : (0, clear, skip),
2926
3037
  'up pressed' : (3, skip, self.on_completion_backward),
2927
3038
  'down pressed' : (3, skip, self.on_completion_forward),
2928
3039
  '*left pressed' : (3, skip),
@@ -2937,8 +3048,8 @@ class Nautilus(EditorInterface, Shell):
2937
3048
  '*delete pressed' : (3, skip),
2938
3049
  '*backspace pressed' : (3, self.OnBackspace),
2939
3050
  '*backspace released' : (3, self.call_apropos_autocomp),
2940
- 'C-S-backspace pressed' : (3, ),
2941
- 'C-j pressed' : (3, self.eval_line),
3051
+ '*S-backspace pressed' : (0, self.clear_autocomp, fork),
3052
+ 'C-j pressed' : (3, _F(self.eval_line)),
2942
3053
  '*alt pressed' : (3, ),
2943
3054
  '*ctrl pressed' : (3, ),
2944
3055
  '*shift pressed' : (3, ),
@@ -2946,11 +3057,11 @@ class Nautilus(EditorInterface, Shell):
2946
3057
  '*f[0-9]* pressed' : (3, ),
2947
3058
  },
2948
3059
  4 : { # text auto completion AS-mode
2949
- 'quit' : (0, clear_autocomp),
2950
- '* pressed' : (0, clear_autocomp, fork),
3060
+ 'quit' : (0, self.clear_autocomp),
3061
+ '* pressed' : (0, self.clear_autocomp, fork),
2951
3062
  'tab pressed' : (0, clear, skip),
2952
3063
  'enter pressed' : (0, clear, skip),
2953
- 'escape pressed' : (0, clear_autocomp),
3064
+ 'escape pressed' : (0, clear, skip),
2954
3065
  'up pressed' : (4, skip, self.on_completion_backward),
2955
3066
  'down pressed' : (4, skip, self.on_completion_forward),
2956
3067
  '*left pressed' : (4, skip),
@@ -2965,8 +3076,8 @@ class Nautilus(EditorInterface, Shell):
2965
3076
  '*delete pressed' : (4, skip),
2966
3077
  '*backspace pressed' : (4, self.OnBackspace),
2967
3078
  '*backspace released' : (4, self.call_text_autocomp),
2968
- 'C-S-backspace pressed' : (4, ),
2969
- 'C-j pressed' : (4, self.eval_line),
3079
+ '*S-backspace pressed' : (0, self.clear_autocomp, fork),
3080
+ 'C-j pressed' : (4, _F(self.eval_line)),
2970
3081
  '*alt pressed' : (4, ),
2971
3082
  '*ctrl pressed' : (4, ),
2972
3083
  '*shift pressed' : (4, ),
@@ -2974,11 +3085,11 @@ class Nautilus(EditorInterface, Shell):
2974
3085
  '*f[0-9]* pressed' : (4, ),
2975
3086
  },
2976
3087
  5 : { # module auto completion AS-mode
2977
- 'quit' : (0, clear_autocomp),
2978
- '* pressed' : (0, clear_autocomp, fork),
3088
+ 'quit' : (0, self.clear_autocomp),
3089
+ '* pressed' : (0, self.clear_autocomp, fork),
2979
3090
  'tab pressed' : (0, clear, skip),
2980
3091
  'enter pressed' : (0, clear, skip),
2981
- 'escape pressed' : (0, clear_autocomp),
3092
+ 'escape pressed' : (0, clear, skip),
2982
3093
  'up pressed' : (5, skip, self.on_completion_backward),
2983
3094
  'down pressed' : (5, skip, self.on_completion_forward),
2984
3095
  '*left pressed' : (5, skip),
@@ -2990,11 +3101,12 @@ class Nautilus(EditorInterface, Shell):
2990
3101
  'S-[a-z\\] pressed' : (5, skip),
2991
3102
  'S-[a-z\\] released' : (5, self.call_module_autocomp),
2992
3103
  '\\ released' : (5, self.call_module_autocomp),
3104
+ 'C-m pressed' : (5, _F(self.call_module_autocomp, force=1)),
2993
3105
  'M-m pressed' : (5, _F(self.call_module_autocomp, force=1)),
2994
3106
  '*delete pressed' : (5, skip),
2995
3107
  '*backspace pressed' : (5, self.OnBackspace),
2996
3108
  '*backspace released' : (5, self.call_module_autocomp),
2997
- 'C-S-backspace pressed' : (5, ),
3109
+ '*S-backspace pressed' : (0, self.clear_autocomp, fork),
2998
3110
  '*alt pressed' : (5, ),
2999
3111
  '*ctrl pressed' : (5, ),
3000
3112
  '*shift pressed' : (5, ),
@@ -3007,22 +3119,22 @@ class Nautilus(EditorInterface, Shell):
3007
3119
  self.show_folder()
3008
3120
  self.set_stylus(Stylus.py_shell_mode)
3009
3121
 
3010
- ## delete unnecessary arrows at startup
3122
+ ## Delete unnecessary arrows at startup.
3011
3123
  del self.white_arrow
3012
3124
  del self.red_arrow
3013
3125
 
3014
3126
  self.__text = ''
3015
-
3127
+
3016
3128
  def trace_position(self):
3017
3129
  _text, lp = self.CurLine
3018
3130
  self.message("{:>6d}:{} ({})".format(self.cline, lp, self.cpos), pane=-1)
3019
-
3131
+
3020
3132
  def OnDestroy(self, evt):
3021
3133
  if evt.EventObject is self:
3022
3134
  self.handler('shell_deleted', self)
3023
3135
  evt.Skip()
3024
-
3025
- def OnUpdate(self, evt): #<wx._stc.StyledTextEvent>
3136
+
3137
+ def OnUpdate(self, evt): #<wx._stc.StyledTextEvent>
3026
3138
  if evt.Updated & (stc.STC_UPDATE_SELECTION | stc.STC_UPDATE_CONTENT):
3027
3139
  self.trace_position()
3028
3140
  if self.handler.current_state == 0:
@@ -3031,18 +3143,18 @@ class Nautilus(EditorInterface, Shell):
3031
3143
  name, argspec, tip = self.interp.getCallTip(text)
3032
3144
  if tip:
3033
3145
  tip = tip.splitlines()[0]
3034
- self.message(tip) # clear if no tip
3146
+ self.message(tip) # clear if no tip
3035
3147
  self.__text = text
3036
3148
  if evt.Updated & stc.STC_UPDATE_CONTENT:
3037
3149
  self.handler('shell_modified', self)
3038
3150
  evt.Skip()
3039
-
3151
+
3040
3152
  def OnCallTipClick(self, evt):
3041
3153
  if self.CallTipActive():
3042
3154
  self.CallTipCancel()
3043
3155
  self.parent.handler('add_help', self._calltips[1])
3044
3156
  evt.Skip()
3045
-
3157
+
3046
3158
  def OnSpace(self, evt):
3047
3159
  """Called when space pressed."""
3048
3160
  if not self.CanEdit():
@@ -3052,133 +3164,142 @@ class Nautilus(EditorInterface, Shell):
3052
3164
  or re.match(r"from\s*$", cmdl)\
3053
3165
  or re.match(r"from\s+([\w.]+)\s+import\s*", cmdl):
3054
3166
  self.ReplaceSelection(' ')
3055
- self.handler('M-m pressed', None) # => call_module_autocomp
3167
+ self.handler('M-m pressed', None) # => call_module_autocomp
3056
3168
  return
3057
3169
  evt.Skip()
3058
-
3170
+
3059
3171
  def OnBackspace(self, evt):
3060
3172
  """Called when backspace pressed.
3061
3173
  Backspace-guard from autocomp eating over a prompt whitespace.
3062
3174
  """
3063
3175
  if self.cpos == self.bolc:
3064
- self.handler('quit', evt) # don't eat backward prompt
3176
+ self.handler('quit', evt) # Don't eat backward prompt
3065
3177
  return
3066
3178
  evt.Skip()
3067
-
3068
- @can_edit
3069
- def backward_kill_line(self):
3070
- p = max(self.bol, self.bolc) # for debugger mode: bol <= bolc
3071
- text, lp = self.CurLine
3072
- if text[:lp] == sys.ps2:
3073
- self.Replace(p - lp, p, '') # eats ps2:prompt
3074
- return
3075
- if p == self.cpos > 0: # caret at beginning of line
3076
- if self.get_char(p-1) == '\n': p -= 1
3077
- if self.get_char(p-1) == '\r': p -= 1
3078
- self.Replace(p, self.cpos, '')
3079
-
3080
- @can_edit
3081
- def backward_kill_word(self):
3082
- p = self.cpos
3083
- text, lp = self.CurLine
3084
- if text[:lp] == sys.ps2:
3085
- self.goto_char(p - lp) # skips ps2:prompt
3086
- self.WordLeft()
3087
- q = max(self.bol, self.bolc) # for debugger mode: bol <= bolc
3088
- if self.cpos < q:
3089
- self.goto_char(q) # Don't skip back prompt
3090
- self.Replace(self.cpos, p, '')
3091
-
3179
+
3180
+ @editable
3181
+ def backward_kill_word(self): # (override)
3182
+ if not self.SelectedText:
3183
+ if self.cpos <= self.bolc:
3184
+ return
3185
+ text, lp = self.CurLine
3186
+ if text[:lp] == sys.ps2:
3187
+ self.cpos -= lp # Select ps2:prompt
3188
+ self.WordLeftExtend() # Select cr/lf chunks
3189
+ else:
3190
+ self.WordLeftExtend()
3191
+ q = max(self.bol, self.bolc) # for debugger mode: bol <= bolc
3192
+ if self.cpos < q:
3193
+ self.cpos = q # Don't skip back ps2:prompt
3194
+ self.ReplaceSelection('')
3195
+
3196
+ @editable
3197
+ def backward_kill_line(self): # (override)
3198
+ if not self.SelectedText:
3199
+ if self.cpos <= self.bolc:
3200
+ return
3201
+ text, lp = self.CurLine
3202
+ if text[:lp] == sys.ps2:
3203
+ self.cpos -= lp # Select ps2:prompt
3204
+ self.WordLeftExtend() # Select cr/lf chunks
3205
+ else:
3206
+ q = max(self.bol, self.bolc) # for debugger mode: bol <= bolc
3207
+ if self.cpos > q:
3208
+ self.cpos = q
3209
+ else:
3210
+ self.WordLeftExtend() # Select cr/lf chunks
3211
+ self.ReplaceSelection('')
3212
+
3092
3213
  def OnEnter(self, evt):
3093
3214
  """Called when enter pressed."""
3094
3215
  if not self.CanEdit():
3095
- self.goto_char(self.eolc) # go to end of command line
3216
+ self.goto_char(self.eolc) # go to end of command line
3096
3217
  return
3097
- if self.AutoCompActive(): # skip to auto completion
3218
+ if self.AutoCompActive(): # skip to auto completion
3098
3219
  evt.Skip()
3099
3220
  return
3100
3221
  if self.CallTipActive():
3101
3222
  self.CallTipCancel()
3102
3223
 
3103
- ## skip to wx.py.magic if text begins with !(sx), ?(info), and ??(help)
3224
+ ## Skip to wx.py.magic if text begins with !(sx), ?(info), and ??(help).
3104
3225
  text = self.cmdline
3105
3226
  if not text or text[0] in '!?':
3106
3227
  evt.Skip()
3107
3228
  return
3108
3229
 
3109
- ## cast magic for `@? (Note: PY35 supports @(matmul)-operator)
3230
+ ## Cast magic for `@? (Note: PY35 supports @(matmul)-operator).
3110
3231
  tokens = list(split_words(text))
3111
3232
  if any(x in tokens for x in '`@?$'):
3112
3233
  cmd = self.magic_interpret(tokens)
3113
3234
  if '\n' in cmd:
3114
- self.Execute(cmd) # => multi-line commands
3235
+ self.Execute(cmd) # => multi-line commands
3115
3236
  else:
3116
- self.run(cmd, verbose=0, prompt=0) # => push(cmd)
3237
+ self.run(cmd, verbose=0, prompt=0) # => push(cmd)
3117
3238
  return
3118
3239
 
3119
3240
  self.exec_cmdline()
3120
- ## evt.Skip() # => processLine
3121
-
3241
+ # evt.Skip() # => processLine
3242
+
3122
3243
  def OnEnterDot(self, evt):
3123
3244
  """Called when dot [.] pressed."""
3124
3245
  if not self.CanEdit():
3125
3246
  self.handler('quit', evt)
3126
3247
  return
3127
3248
  p = self.cpos
3128
- st = self.get_style(p-1)
3249
+ lst = self.get_style(p-1)
3129
3250
  rst = self.get_style(p)
3130
3251
  if p == self.bolc:
3131
- self.ReplaceSelection('self') # replace [.] --> [self.]
3132
- elif st in ('nil', 'space', 'op', 'sep', 'lparen'):
3252
+ self.ReplaceSelection('self') # replace [.] --> [self.]
3253
+ elif lst in ('space', 'sep', 'lparen'):
3133
3254
  self.ReplaceSelection('self')
3134
- elif st not in ('moji', 'word', 'rparen') or rst == 'word':
3135
- self.handler('quit', evt) # don't enter autocomp
3255
+ elif lst not in ('moji', 'word', 'rparen') or rst == 'word':
3256
+ self.handler('quit', evt) # Don't enter autocomp
3136
3257
  evt.Skip()
3137
-
3258
+
3138
3259
  def on_enter_escmap(self, evt):
3139
3260
  self.__caret_mode = self.CaretPeriod
3140
3261
  self.CaretPeriod = 0
3141
3262
  self.message("ESC-")
3142
-
3263
+
3143
3264
  def on_exit_escmap(self, evt):
3144
3265
  self.CaretPeriod = self.__caret_mode
3145
3266
  self.message("ESC {}".format(evt.key))
3146
- if self.eolc < self.bolc: # check if prompt is in valid state
3267
+ if self.eolc < self.bolc: # Check if prompt is in valid state.
3147
3268
  self.goto_char(self.eolc)
3148
- self.promptPosEnd = 0
3269
+ self.promptPosEnd = 0 # Enabale write(prompt).
3149
3270
  self.prompt()
3150
3271
  self.AnnotationClearAll()
3151
-
3272
+
3152
3273
  def on_enter_notemode(self, evt):
3153
3274
  self.noteMode = True
3154
3275
  self.__caret_mode = self.CaretForeground
3155
3276
  self.CaretForeground = 'red'
3156
3277
  self.message("Note mode")
3157
-
3278
+
3158
3279
  def on_exit_notemode(self, evt):
3159
3280
  self.noteMode = False
3160
3281
  self.CaretForeground = self.__caret_mode
3161
3282
  self.goto_char(self.eolc)
3162
- self.promptPosEnd = 0
3283
+ self.promptPosEnd = 0 # Enabale write(prompt).
3163
3284
  self.prompt()
3164
3285
  self.message("")
3165
-
3286
+
3166
3287
  def goto_next_white_arrow(self):
3167
- self.goto_next_marker(0b010) # next white-arrow
3168
-
3288
+ self.goto_next_marker(0b010) # next white-arrow
3289
+
3169
3290
  def goto_previous_white_arrow(self):
3170
- self.goto_previous_marker(0b010) # previous white-arrow
3171
-
3291
+ self.goto_previous_marker(0b010) # previous white-arrow
3292
+
3172
3293
  def goto_next_mark_arrow(self, selection=False):
3173
- self.goto_next_marker(0b110, selection) # next white/red-arrow
3174
-
3294
+ self.goto_next_marker(0b110, selection) # next white/red-arrow
3295
+
3175
3296
  def goto_previous_mark_arrow(self, selection=False):
3176
- self.goto_previous_marker(0b110, selection) # previous white/red-arrow
3177
-
3297
+ self.goto_previous_marker(0b110, selection) # previous white/red-arrow
3298
+
3178
3299
  ## --------------------------------
3179
- ## Magic caster of the shell
3300
+ ## Magic caster of the shell.
3180
3301
  ## --------------------------------
3181
-
3302
+
3182
3303
  @classmethod
3183
3304
  def magic(self, cmd):
3184
3305
  """Called before command pushed.
@@ -3190,7 +3311,7 @@ class Nautilus(EditorInterface, Shell):
3190
3311
  elif cmd[0] == '?': cmd = 'info({})'.format(cmd[1:])
3191
3312
  elif cmd[0] == '!': cmd = 'sx({!r})'.format(cmd[1:])
3192
3313
  return cmd
3193
-
3314
+
3194
3315
  @classmethod
3195
3316
  def magic_interpret(self, tokens):
3196
3317
  """Called when [Enter] command, or eval-time for tooltip.
@@ -3205,8 +3326,8 @@ class Nautilus(EditorInterface, Shell):
3205
3326
  Note:
3206
3327
  This is called before run, execute, and original magic.
3207
3328
  """
3208
- sep1 = "`@=;\r\n#" # [`] no ops, no spaces, no comma
3209
- sep2 = "`@=+-/*%<>&|^~,; \t\r\n#" # [@] ops, delims, and whitespaces
3329
+ sep1 = "`@=;\r\n#" # [`] no ops, no spaces, no comma
3330
+ sep2 = "`@=+-/*%<>&|^~,; \t\r\n#" # [@] ops, delims, and whitespaces
3210
3331
 
3211
3332
  def _popiter(ls, f):
3212
3333
  pred = f if callable(f) else re.compile(f).match
@@ -3222,7 +3343,7 @@ class Nautilus(EditorInterface, Shell):
3222
3343
  for i, c in enumerate(tokens):
3223
3344
  rest = tokens[i+1:]
3224
3345
 
3225
- if c == '@' and not lhs.strip() and '\n' in rest: # @decor
3346
+ if c == '@' and not lhs.strip() and '\n' in rest: # @decor
3226
3347
  pass
3227
3348
 
3228
3349
  elif c == '@':
@@ -3232,16 +3353,21 @@ class Nautilus(EditorInterface, Shell):
3232
3353
  ## func(a,b,c) @debug --> func,a,b,c @debug
3233
3354
  if rhs in ("debug", "profile", "timeit"):
3234
3355
  if lhs[-1] in ')':
3235
- L, R = split_paren(lhs, reverse=1)
3356
+ R = next(split_parts(lhs, reverse=1))
3357
+ L = lhs[:-len(R)]
3236
3358
  if not L:
3237
3359
  lhs = "{!r}".format(R[1:-1])
3238
- elif R:
3360
+ else:
3239
3361
  lhs = "{}, {}".format(L, R[1:-1])
3240
3362
 
3241
3363
  ## @(y1,,,yn) --> @partial(y1,,,yn)
3242
3364
  elif rhs.startswith('('):
3243
3365
  rhs = re.sub(r"^\((.*)\)", r"partial(\1)", rhs, flags=re.S)
3244
3366
 
3367
+ ## obj @.method --> (obj).method
3368
+ elif rhs.startswith('.'):
3369
+ return self.magic_interpret([f"({lhs}){rhs}"] + rest)
3370
+
3245
3371
  return self.magic_interpret([f"{rhs}({lhs})"] + rest)
3246
3372
 
3247
3373
  if c == '`':
@@ -3260,17 +3386,17 @@ class Nautilus(EditorInterface, Shell):
3260
3386
  return lhs + c + self.magic_interpret(rest)
3261
3387
 
3262
3388
  if c == sys.ps2.strip():
3263
- rhs = ''.join(_popiter(rest, "[ \t\r\n]")) # feed
3389
+ rhs = ''.join(_popiter(rest, "[ \t\r\n]")) # feed
3264
3390
  return lhs + c + rhs + self.magic_interpret(rest)
3265
3391
 
3266
3392
  if c.startswith('#'):
3267
- rhs = ''.join(_popiter(rest, "[^\r\n]")) # skip comment
3393
+ rhs = ''.join(_popiter(rest, "[^\r\n]")) # skip comment
3268
3394
  return lhs + c + rhs + self.magic_interpret(rest)
3269
3395
 
3270
- lhs += c # store in lhs; no more processing
3396
+ lhs += c # store in lhs; no more processing
3271
3397
  return lhs
3272
-
3273
- def on_deleted(self, shell):
3398
+
3399
+ def on_shell_deleted(self, shell):
3274
3400
  """Called before shell:self is killed.
3275
3401
  Delete target shell to prevent referencing the dead shell.
3276
3402
  """
@@ -3278,26 +3404,26 @@ class Nautilus(EditorInterface, Shell):
3278
3404
  obj = self.target
3279
3405
  try:
3280
3406
  if not obj.shell:
3281
- del obj.shell # delete the facade <wx.py.shell.ShellFacade>
3407
+ del obj.shell # delete the facade <wx.py.shell.ShellFacade>
3282
3408
  except AttributeError:
3283
3409
  pass
3284
3410
  wx.CallAfter(_del)
3285
-
3286
- def on_activated(self, shell):
3411
+
3412
+ def on_shell_activated(self, shell):
3287
3413
  """Called when the shell:self is activated.
3288
- Reset localvars assigned for the shell target.
3414
+ Reset localvars assigned for the shell target. cf. target.setter.
3289
3415
  """
3290
3416
  self.trace_position()
3291
3417
  obj = self.target
3292
3418
  try:
3293
3419
  obj.self = obj
3294
3420
  obj.this = inspect.getmodule(obj)
3295
- obj.shell = self # overwrite the facade <wx.py.shell.ShellFacade>
3421
+ obj.shell = self # Overwrite the facade <wx.py.shell.ShellFacade>
3296
3422
  except AttributeError:
3297
3423
  pass
3298
3424
  self.parent.handler('title_window', obj)
3299
-
3300
- def on_inactivated(self, shell):
3425
+
3426
+ def on_shell_inactivated(self, shell):
3301
3427
  """Called when shell:self is inactivated.
3302
3428
  Remove target localvars assigned for the shell target.
3303
3429
  """
@@ -3305,7 +3431,7 @@ class Nautilus(EditorInterface, Shell):
3305
3431
  self.AutoCompCancel()
3306
3432
  if self.CallTipActive():
3307
3433
  self.CallTipCancel()
3308
-
3434
+
3309
3435
  def on_text_input(self, text):
3310
3436
  """Called when [Enter] text (before push).
3311
3437
  Mark points, reset history point, etc.
@@ -3316,7 +3442,7 @@ class Nautilus(EditorInterface, Shell):
3316
3442
  if text.rstrip():
3317
3443
  self.__eolc_mark = self.eolc
3318
3444
  self.historyIndex = -1
3319
-
3445
+
3320
3446
  def on_text_output(self, text):
3321
3447
  """Called when [Enter] text (after push).
3322
3448
  Set markers at the last command line.
@@ -3325,28 +3451,28 @@ class Nautilus(EditorInterface, Shell):
3325
3451
  Argument `text` is raw output:str with no magic cast.
3326
3452
  """
3327
3453
  ln = self.LineFromPosition(self.bolc)
3328
- err = re.findall(py_error_re, text, re.M)
3329
- self.add_marker(ln, 1 if not err else 2) # 1:white-arrow 2:red-arrow
3454
+ err = re.findall(py_trace_re, text, re.M)
3455
+ self.add_marker(ln, 1 if not err else 2) # 1:white-arrow 2:red-arrow
3330
3456
  return (not err)
3331
-
3457
+
3332
3458
  def on_interp_error(self, e):
3333
3459
  ln = self.LineFromPosition(self.bolc)
3334
3460
  self.red_pointer = ln + e.lineno - 1
3335
-
3461
+
3336
3462
  ## --------------------------------
3337
- ## Attributes of the shell
3463
+ ## Attributes of the shell.
3338
3464
  ## --------------------------------
3339
-
3465
+
3340
3466
  @property
3341
3467
  def bolc(self):
3342
- "Beginning of command-line."
3468
+ """Beginning of command-line."""
3343
3469
  return self.promptPosEnd
3344
-
3470
+
3345
3471
  @property
3346
3472
  def eolc(self):
3347
- "End of command-line."
3473
+ """End of command-line."""
3348
3474
  return self.TextLength
3349
-
3475
+
3350
3476
  @property
3351
3477
  def bol(self):
3352
3478
  """Beginning of line (override) excluding prompt."""
@@ -3356,30 +3482,35 @@ class Nautilus(EditorInterface, Shell):
3356
3482
  lp -= len(ps)
3357
3483
  break
3358
3484
  return self.cpos - lp
3359
-
3485
+
3360
3486
  @property
3361
3487
  def cmdline(self):
3362
3488
  """Full multi-line command in the current prompt."""
3363
3489
  return self.GetTextRange(self.bolc, self.eolc)
3364
-
3490
+
3365
3491
  ## cf. getCommand() -> caret-line that starts with a prompt
3366
3492
  ## cf. getMultilineCommand() -> caret-multi-line that starts with a prompt
3367
- ## [BUG 4.1.1] Don't use for current prompt --> Fixed in 4.2.0.
3368
-
3369
- def getMultilineCommand(self):
3493
+ ## [BUG ver 4.1.1] Don't use for current prompt --> Fixed in wx ver 4.2.0.
3494
+
3495
+ def getMultilineCommand(self, rstrip=True):
3370
3496
  """Extract a multi-line command which starts with a prompt.
3371
3497
 
3372
3498
  (override) Don't remove trailing ps2 + spaces.
3373
3499
  Don't invoke ``GotoLine``.
3374
3500
  """
3375
- region = self.get_region(self.cline)
3501
+ region = self.get_command_region(self.cline)
3376
3502
  if region:
3377
3503
  p, q = (self.PositionFromLine(x) for x in region)
3378
3504
  p += len(sys.ps1)
3379
- return self.GetTextRange(p, q)
3505
+ command = self.GetTextRange(p, q).rstrip(os.linesep) # remove the last cr/lf
3506
+ if rstrip:
3507
+ command = command.replace(os.linesep + sys.ps2, '\n')
3508
+ command = command.rstrip()
3509
+ command = command.replace('\n', os.linesep + sys.ps2)
3510
+ return command
3380
3511
  return ''
3381
-
3382
- def get_region(self, line):
3512
+
3513
+ def get_command_region(self, line):
3383
3514
  """Line numbers of prompt head and tail containing the line."""
3384
3515
  lc = line
3385
3516
  le = lc + 1
@@ -3388,7 +3519,7 @@ class Nautilus(EditorInterface, Shell):
3388
3519
  if not text.startswith(sys.ps2):
3389
3520
  break
3390
3521
  lc -= 1
3391
- if not text.startswith(sys.ps1): # bad region
3522
+ if not text.startswith(sys.ps1): # bad region
3392
3523
  return None
3393
3524
  while le < self.LineCount:
3394
3525
  text = self.GetLine(le)
@@ -3396,11 +3527,11 @@ class Nautilus(EditorInterface, Shell):
3396
3527
  break
3397
3528
  le += 1
3398
3529
  return lc, le
3399
-
3530
+
3400
3531
  ## --------------------------------
3401
- ## Execution methods of the shell
3532
+ ## Execution methods of the shell.
3402
3533
  ## --------------------------------
3403
-
3534
+
3404
3535
  def push(self, command, **kwargs):
3405
3536
  """Send command to the interpreter for execution.
3406
3537
 
@@ -3408,7 +3539,7 @@ class Nautilus(EditorInterface, Shell):
3408
3539
  """
3409
3540
  self.on_text_input(command)
3410
3541
  Shell.push(self, command, **kwargs)
3411
-
3542
+
3412
3543
  def addHistory(self, command):
3413
3544
  """Add command to the command history.
3414
3545
 
@@ -3418,7 +3549,7 @@ class Nautilus(EditorInterface, Shell):
3418
3549
  if not command:
3419
3550
  return
3420
3551
 
3421
- ## この段階では push された直後で,次のようになっている
3552
+ ## この段階では push された直後で,次のようになっている.
3422
3553
  ## bolc : beginning of command-line
3423
3554
  ## eolc : end of the output-buffer
3424
3555
  try:
@@ -3426,7 +3557,7 @@ class Nautilus(EditorInterface, Shell):
3426
3557
  output = self.GetTextRange(self.__eolc_mark, self.eolc)
3427
3558
 
3428
3559
  input = self.regulate_cmd(input)
3429
- Shell.addHistory(self, input) # => self.history
3560
+ Shell.addHistory(self, input) # => self.history
3430
3561
 
3431
3562
  noerr = self.on_text_output(output)
3432
3563
  if noerr:
@@ -3435,18 +3566,30 @@ class Nautilus(EditorInterface, Shell):
3435
3566
  command = self.fixLineEndings(command)
3436
3567
  self.parent.handler('add_log', command + os.linesep, noerr)
3437
3568
  except AttributeError:
3438
- ## execStartupScript 実行時は出力先 (owner) が存在しない
3439
- ## shell.__init__ よりも先に実行される
3569
+ ## execStartupScript 実行時は出力先 (owner) が存在しない.
3570
+ ## shell.__init__ よりも先に実行される.
3440
3571
  pass
3441
-
3572
+
3573
+ def setBuiltinKeywords(self):
3574
+ """Create pseudo keywords as part of builtins.
3575
+
3576
+ (override) Don't add `close`, `exit` and `quit` to builtins.
3577
+ """
3578
+ from wx.py.path import ls, cd, pwd, sx
3579
+ builtins.cd = cd
3580
+ builtins.ls = ls
3581
+ builtins.pwd = pwd
3582
+ builtins.sx = sx
3583
+
3442
3584
  def execStartupScript(self, su):
3443
3585
  """Execute the user's PYTHONSTARTUP script if they have one.
3444
3586
 
3445
3587
  (override) Add globals when executing su:startupScript.
3446
- Fix history point.
3588
+ Don't add '_f' to globals when executing su:startupScript.
3589
+ Don't add intro text in the history.
3447
3590
  """
3448
- keys = set(self.locals.keys()) # check for locals map changes
3449
- self.promptPosEnd = self.TextLength # fix history point
3591
+ keys = set(self.locals.keys()) # to check for locals map changes.
3592
+ self.promptPosEnd = self.TextLength # Fix history point
3450
3593
  if su and os.path.isfile(su):
3451
3594
  self.push("print('Startup script executed:', {0!r})\n".format(su))
3452
3595
  self.push("with open({0!r}) as _f: exec(_f.read())\n".format(su))
@@ -3456,31 +3599,31 @@ class Nautilus(EditorInterface, Shell):
3456
3599
  self.push("")
3457
3600
  self.interp.startupScript = None
3458
3601
  self.__globals = {k: self.locals[k] for k in (self.locals.keys() - keys)}
3459
-
3602
+
3460
3603
  def Paste(self, rectangle=False):
3461
3604
  """Replace selection with clipboard contents.
3462
3605
 
3463
3606
  (override) Remove ps1 and ps2 from the multi-line command to paste.
3464
3607
  Add offset in paste-rectangle mode.
3465
- Don't relplace the last crlf to ps.
3608
+ Don't relplace the last cr/lf to ps.
3466
3609
  """
3467
3610
  if self.CanPaste() and wx.TheClipboard.Open():
3468
3611
  data = wx.TextDataObject()
3469
3612
  if wx.TheClipboard.GetData(data):
3470
3613
  command = data.GetText()
3471
- ## command = command.rstrip()
3614
+ # command = command.rstrip()
3472
3615
  command = self.fixLineEndings(command)
3473
3616
  command = self.regulate_cmd(command)
3474
3617
  ps = sys.ps2
3475
3618
  _text, lp = self.CurLine
3476
3619
  if rectangle:
3477
- ps += ' ' * (lp - len(ps)) # add offset
3620
+ ps += ' ' * (lp - len(ps)) # add offset
3478
3621
  if lp == 0:
3479
- command = ps + command # paste-line
3622
+ command = ps + command # paste-line
3480
3623
  command = command.replace('\n', os.linesep + ps)
3481
3624
  self.ReplaceSelection(command)
3482
3625
  wx.TheClipboard.Close()
3483
-
3626
+
3484
3627
  def regulate_cmd(self, text):
3485
3628
  """Regulate text to executable command.
3486
3629
 
@@ -3489,53 +3632,53 @@ class Nautilus(EditorInterface, Shell):
3489
3632
  The eol-code (cr/lf) is not fixed.
3490
3633
  Call self.fixLineEndings in advance as necessary.
3491
3634
  """
3492
- text = self.lstripPrompt(text) # strip a leading prompt
3635
+ text = self.lstripPrompt(text) # strip a leading prompt
3493
3636
  lf = '\n'
3494
3637
  return (text.replace(os.linesep + sys.ps1, lf)
3495
3638
  .replace(os.linesep + sys.ps2, lf)
3496
3639
  .replace(os.linesep, lf))
3497
-
3640
+
3498
3641
  def clear(self):
3499
3642
  """Delete all text (override) put new prompt."""
3500
3643
  self.ClearAll()
3501
3644
  self.promptPosStart = 0
3502
3645
  self.promptPosEnd = 0
3503
3646
  self.prompt()
3504
-
3647
+
3505
3648
  def write(self, text, pos=None):
3506
3649
  """Display text in the shell.
3507
3650
 
3508
- (override) Append text if it is writable at the position.
3651
+ (override) Append text if it is writable at the given position.
3509
3652
  """
3510
3653
  if pos is not None:
3511
3654
  if pos < 0:
3512
- pos += self.TextLength + 1 # Counts end-of-buffer (+1:\0)
3655
+ pos += self.TextLength + 1 # Counts end-of-buffer (+1:\0)
3513
3656
  self.goto_char(pos)
3514
3657
  if self.CanEdit():
3515
- Shell.write(self, text) # => AddText
3516
-
3658
+ Shell.write(self, text) # => AddText
3659
+
3517
3660
  ## input = classmethod(Shell.ask)
3518
-
3661
+
3519
3662
  def info(self, obj):
3520
3663
  """Short information."""
3521
3664
  doc = inspect.getdoc(obj)\
3522
3665
  or "No information about {}".format(obj)
3523
- self.parent.handler('add_help', doc) or print(doc)
3524
-
3666
+ self.parent.handler('add_help', doc, typename(obj)) or print(doc)
3667
+
3525
3668
  def help(self, obj):
3526
3669
  """Full description."""
3527
3670
  doc = pydoc.plain(pydoc.render_doc(obj))\
3528
3671
  or "No description about {}".format(obj)
3529
- self.parent.handler('add_help', doc) or print(doc)
3530
-
3672
+ self.parent.handler('add_help', doc, typename(obj)) or print(doc)
3673
+
3531
3674
  def eval(self, text):
3532
3675
  return eval(text, self.globals, self.locals)
3533
-
3676
+
3534
3677
  def exec(self, text):
3535
3678
  exec(text, self.globals, self.locals)
3536
3679
  dispatcher.send(signal='Interpreter.push',
3537
3680
  sender=self, command=None, more=False)
3538
-
3681
+
3539
3682
  def exec_cmdline(self):
3540
3683
  """Execute command-line directly.
3541
3684
 
@@ -3556,14 +3699,14 @@ class Nautilus(EditorInterface, Shell):
3556
3699
  continue
3557
3700
  line = self.lstripPrompt(lines)
3558
3701
  lstr = line.lstrip()
3559
- if (lstr and lstr == line # no indent
3560
- and not lstr.startswith('#') # no comment
3561
- and not re.match(py_outdent_re, lstr)): # no outdent pattern
3702
+ if (lstr and lstr == line # no indent
3703
+ and not lstr.startswith('#') # no comment
3704
+ and not re.match(py_outdent_re, lstr)): # no outdent pattern
3562
3705
  if cmd:
3563
- commands.append(cmd) # Add stacked commands to the list
3706
+ commands.append(cmd) # Add stacked commands to the list
3564
3707
  cmd = line
3565
3708
  else:
3566
- cmd += lines # multi-line command
3709
+ cmd += lines # multi-line command
3567
3710
  lines = ''
3568
3711
  commands.append(cmd + lines)
3569
3712
 
@@ -3587,56 +3730,37 @@ class Nautilus(EditorInterface, Shell):
3587
3730
  for cmd in commands:
3588
3731
  self.write(cmd)
3589
3732
  self.processLine()
3590
-
3591
- ## --------------------------------
3592
- ## Autocomp actions of the shell
3593
- ## --------------------------------
3594
-
3595
- def autoCallTipShow(self, command, insertcalltip=True, forceCallTip=False):
3596
- """Display argument spec and docstring in a popup window.
3733
+
3734
+ def eval_line(self):
3735
+ """Evaluate the selected word or line and show calltips."""
3736
+ if self.CallTipActive():
3737
+ self.CallTipCancel()
3597
3738
 
3598
- (override) Swap anchors to not scroll to the end of the line.
3599
- """
3600
- Shell.autoCallTipShow(self, command, insertcalltip, forceCallTip)
3601
- self.cpos, self.anchor = self.anchor, self.cpos
3602
- self.EnsureCaretVisible()
3603
-
3604
- def gen_text_at_caret(self):
3605
- """Generates the selected text,
3606
- otherwise the line or expression at the caret.
3607
- (override) Generates command line (that starts with a prompt).
3608
- """
3609
3739
  def _gen_text():
3610
3740
  text = self.SelectedText
3611
3741
  if text:
3612
3742
  yield text
3613
3743
  else:
3614
- yield self.getCommand() # self.line_at_caret
3744
+ yield self.line_at_caret
3615
3745
  yield self.expr_at_caret
3616
- return filter(None, _gen_text())
3617
-
3618
- def eval_line(self, evt):
3619
- """Evaluate the selected word or line."""
3620
- if self.CallTipActive():
3621
- self.CallTipCancel()
3622
3746
 
3623
- status = "No words"
3624
- for text in self.gen_text_at_caret():
3747
+ for text in _gen_text():
3625
3748
  tokens = split_words(text)
3626
3749
  try:
3627
3750
  cmd = self.magic_interpret(tokens)
3628
3751
  cmd = self.regulate_cmd(cmd)
3629
3752
  obj = self.eval(cmd)
3630
3753
  except Exception as e:
3631
- status = "- {} : {!r}".format(e, text)
3754
+ self.message(e)
3632
3755
  else:
3633
3756
  self.CallTipShow(self.cpos, pformat(obj))
3634
3757
  self.message(cmd)
3635
3758
  return
3636
- self.message(status)
3637
-
3638
- def exec_region(self, evt):
3639
- """Execute the the selected region."""
3759
+ if not text:
3760
+ self.message("No words")
3761
+
3762
+ def exec_region(self):
3763
+ """Execute a region of code."""
3640
3764
  if self.CallTipActive():
3641
3765
  self.CallTipCancel()
3642
3766
 
@@ -3651,10 +3775,10 @@ class Nautilus(EditorInterface, Shell):
3651
3775
  self.exec(code)
3652
3776
  except Exception as e:
3653
3777
  msg = traceback.format_exc()
3654
- err = re.findall(py_error_re, msg, re.M)
3778
+ err = re.findall(py_trace_re, msg, re.M)
3655
3779
  lines = [int(ln) for fn, ln in err if fn == filename]
3656
3780
  if lines:
3657
- region = self.get_region(self.cline)
3781
+ region = self.get_command_region(self.cline)
3658
3782
  lx = region[0] + lines[-1] - 1
3659
3783
  self.red_pointer = lx
3660
3784
  self.message(e)