mwxlib 1.0.0__py3-none-any.whl → 1.8.0__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:
@@ -351,20 +369,21 @@ class AutoCompInterfaceMixin:
351
369
  self.message("[module]>>> loading {}...".format(text))
352
370
  modules = set(dir(import_module(text)))
353
371
  except ImportError as e:
354
- self.message("\b failed.", e)
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."""
@@ -460,15 +489,15 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
460
489
  self.make_keymap('C-x')
461
490
  self.make_keymap('C-c')
462
491
 
463
- self.handler.update({ # DNA<EditorInterface>
492
+ self.handler.update({ # DNA<EditorInterface>
464
493
  None : {
465
- 'mark_set' : [ None, dispatch ],
466
- 'mark_unset' : [ None, dispatch ],
467
- 'pointer_set' : [ None, dispatch ],
468
- 'pointer_unset' : [ None, dispatch ],
494
+ 'mark_set' : [None, dispatch],
495
+ 'mark_unset' : [None, dispatch],
496
+ 'pointer_set' : [None, dispatch],
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,68 @@ 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
+ return os.path.getmtime(self.__path) - self.__mtime
1867
+ except OSError:
1868
+ pass
1869
+ return self.__mtime
1870
+
1871
+ @property
1872
+ def need_buffer_save(self):
1873
+ """Return whether the buffer should be saved.
1874
+ The file has been modified internally.
1875
+ """
1876
+ return self.mtdelta is not None and self.IsModified()
1877
+
1878
+ @property
1879
+ def need_buffer_load(self):
1880
+ """Return whether the buffer should be loaded.
1881
+ The file has been modified externally.
1882
+ """
1883
+ return self.mtdelta is not None and self.mtdelta > 0
1884
+
1749
1885
  @property
1750
1886
  def caption_prefix(self):
1751
1887
  prefix = ''
@@ -1760,7 +1896,7 @@ class Buffer(EditorInterface, EditWindow):
1760
1896
  if prefix:
1761
1897
  prefix += ' '
1762
1898
  return prefix
1763
-
1899
+
1764
1900
  def update_caption(self):
1765
1901
  caption = self.caption_prefix + self.name
1766
1902
  try:
@@ -1768,31 +1904,7 @@ class Buffer(EditorInterface, EditWindow):
1768
1904
  self.parent.handler('buffer_caption_updated', self)
1769
1905
  except AttributeError:
1770
1906
  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
-
1907
+
1796
1908
  def __init__(self, parent, filename, **kwargs):
1797
1909
  EditWindow.__init__(self, parent, **kwargs)
1798
1910
  EditorInterface.__init__(self)
@@ -1802,40 +1914,19 @@ class Buffer(EditorInterface, EditWindow):
1802
1914
  self.update_filestamp(filename)
1803
1915
  self.code = None
1804
1916
 
1805
- self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdate) # skip to brace matching
1917
+ self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdate) # skip to brace matching
1806
1918
  self.Bind(stc.EVT_STC_CALLTIP_CLICK, self.OnCallTipClick)
1807
1919
  self.Bind(stc.EVT_STC_INDICATOR_CLICK, self.OnIndicatorClick)
1808
1920
 
1809
1921
  self.Bind(stc.EVT_STC_SAVEPOINTLEFT, self.OnSavePointLeft)
1810
1922
  self.Bind(stc.EVT_STC_SAVEPOINTREACHED, self.OnSavePointReached)
1811
1923
 
1812
- def activate(evt):
1813
- if self:
1814
- self.handler('buffer_activated', self)
1815
- evt.Skip()
1816
- self.Bind(wx.EVT_SET_FOCUS, activate)
1817
-
1818
- def inactivate(evt):
1819
- if self:
1820
- self.handler('buffer_inactivated', self)
1821
- evt.Skip()
1822
- self.Bind(wx.EVT_KILL_FOCUS, inactivate)
1823
-
1824
1924
  def clear(evt):
1825
1925
  ## """Clear selection and message, no skip."""
1826
- ## *do not* clear autocomp, so that the event can skip to AutoComp properly.
1926
+ ## *DO NOT* clear autocomp, so that the event can skip to AutoComp properly.
1827
1927
  if self.CanEdit():
1828
1928
  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()
1836
- if self.CanEdit():
1837
- with self.off_undocollection():
1838
- self.ReplaceSelection("")
1929
+ self.ReplaceSelection('')
1839
1930
  self.message("")
1840
1931
 
1841
1932
  def fork(evt):
@@ -1845,40 +1936,46 @@ class Buffer(EditorInterface, EditWindow):
1845
1936
  """Fork events to the parent."""
1846
1937
  self.parent.handler(self.handler.current_event, evt)
1847
1938
 
1848
- ## Note: Key events are not propagated from Buffer to EditorBook.
1939
+ ## Note: Mouse events are not propagated from Buffer to EditorBook.
1849
1940
  ## They are explicitly dispatched from buffer.handler to editor.handler.
1850
1941
 
1851
- self.handler.update({ # DNA<Buffer>
1942
+ self.handler.update({ # DNA<Buffer>
1852
1943
  None : {
1853
- 'buffer_saved' : [ None, dispatch ],
1854
- '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 ],
1858
- 'buffer_region_executed' : [ None, dispatch ],
1944
+ 'focus_set' : [None, self.on_buffer_activate],
1945
+ 'focus_kill' : [None, self.on_buffer_inactivate],
1946
+ 'buffer_saved' : [None, dispatch],
1947
+ 'buffer_loaded' : [None, dispatch],
1948
+ 'buffer_modified' : [None, dispatch],
1949
+ 'buffer_activated' : [None, dispatch],
1950
+ 'buffer_inactivated' : [None, dispatch],
1951
+ 'buffer_region_executed' : [None, dispatch],
1859
1952
  },
1860
- -1 : { # original action of the EditWindow
1953
+ -1 : { # original action of the EditWindow
1861
1954
  '* pressed' : (0, skip, self.on_exit_escmap),
1862
1955
  '*alt pressed' : (-1, ),
1863
1956
  '*ctrl pressed' : (-1, ),
1864
1957
  '*shift pressed' : (-1, ),
1865
1958
  '*[LR]win pressed' : (-1, ),
1866
1959
  },
1867
- 0 : { # Normal mode
1868
- '* pressed' : (0, skip, dispatch),
1960
+ 0 : { # Normal mode
1961
+ '* pressed' : (0, skip),
1869
1962
  '* released' : (0, skip, dispatch),
1963
+ '*button* pressed' : (0, skip, dispatch),
1964
+ 'Lbutton pressed' : (0, self.on_left_down),
1870
1965
  'escape pressed' : (-1, self.on_enter_escmap),
1871
1966
  'C-h pressed' : (0, self.call_helpTip),
1872
1967
  '. pressed' : (2, self.OnEnterDot),
1968
+ 'C-. pressed' : (2, self.call_word_autocomp),
1969
+ 'C-/ pressed' : (3, self.call_apropos_autocomp),
1873
1970
  'M-. pressed' : (2, self.call_word_autocomp),
1874
1971
  'M-/ pressed' : (3, self.call_apropos_autocomp),
1875
1972
  },
1876
- 2 : { # word auto completion AS-mode
1877
- 'quit' : (0, clear_autocomp),
1878
- '* pressed' : (0, clear_autocomp, fork),
1973
+ 2 : { # word auto completion AS-mode
1974
+ 'quit' : (0, self.clear_autocomp),
1975
+ '* pressed' : (0, self.clear_autocomp, fork),
1879
1976
  'tab pressed' : (0, clear, skip),
1880
1977
  'enter pressed' : (0, clear, skip),
1881
- 'escape pressed' : (0, clear_autocomp),
1978
+ 'escape pressed' : (0, clear, skip),
1882
1979
  'up pressed' : (2, skip, self.on_completion_backward),
1883
1980
  'down pressed' : (2, skip, self.on_completion_forward),
1884
1981
  '*left pressed' : (2, skip),
@@ -1893,19 +1990,19 @@ class Buffer(EditorInterface, EditWindow):
1893
1990
  '*delete pressed' : (2, skip),
1894
1991
  '*backspace pressed' : (2, skip),
1895
1992
  '*backspace released' : (2, self.call_word_autocomp),
1896
- 'C-S-backspace pressed' : (2, ),
1993
+ '*S-backspace pressed' : (0, self.clear_autocomp, fork),
1897
1994
  '*alt pressed' : (2, ),
1898
1995
  '*ctrl pressed' : (2, ),
1899
1996
  '*shift pressed' : (2, ),
1900
1997
  '*[LR]win pressed' : (2, ),
1901
1998
  '*f[0-9]* pressed' : (2, ),
1902
1999
  },
1903
- 3 : { # apropos auto completion AS-mode
1904
- 'quit' : (0, clear_autocomp),
1905
- '* pressed' : (0, clear_autocomp, fork),
2000
+ 3 : { # apropos auto completion AS-mode
2001
+ 'quit' : (0, self.clear_autocomp),
2002
+ '* pressed' : (0, self.clear_autocomp, fork),
1906
2003
  'tab pressed' : (0, clear, skip),
1907
2004
  'enter pressed' : (0, clear, skip),
1908
- 'escape pressed' : (0, clear_autocomp),
2005
+ 'escape pressed' : (0, clear, skip),
1909
2006
  'up pressed' : (3, skip, self.on_completion_backward),
1910
2007
  'down pressed' : (3, skip, self.on_completion_forward),
1911
2008
  '*left pressed' : (3, skip),
@@ -1920,7 +2017,7 @@ class Buffer(EditorInterface, EditWindow):
1920
2017
  '*delete pressed' : (3, skip),
1921
2018
  '*backspace pressed' : (3, skip),
1922
2019
  '*backspace released' : (3, self.call_apropos_autocomp),
1923
- 'C-S-backspace pressed' : (3, ),
2020
+ '*S-backspace pressed' : (0, self.clear_autocomp, fork),
1924
2021
  '*alt pressed' : (3, ),
1925
2022
  '*ctrl pressed' : (3, ),
1926
2023
  '*shift pressed' : (3, ),
@@ -1931,103 +2028,116 @@ class Buffer(EditorInterface, EditWindow):
1931
2028
 
1932
2029
  self.show_folder()
1933
2030
  self.set_stylus(Stylus.py_text_mode)
1934
-
2031
+
1935
2032
  def __contains__(self, code):
1936
2033
  if inspect.iscode(code) and self.code:
1937
2034
  return code is self.code\
1938
2035
  or code in self.code.co_consts
1939
-
2036
+
1940
2037
  def trace_position(self):
1941
2038
  _text, lp = self.CurLine
1942
2039
  self.message("{:>6d}:{} ({})".format(self.cline, lp, self.cpos), pane=-1)
1943
-
1944
- def OnUpdate(self, evt): #<wx._stc.StyledTextEvent>
2040
+
2041
+ def OnUpdate(self, evt): # <wx._stc.StyledTextEvent>
1945
2042
  if evt.Updated & (stc.STC_UPDATE_SELECTION | stc.STC_UPDATE_CONTENT):
1946
2043
  self.trace_position()
1947
2044
  if evt.Updated & stc.STC_UPDATE_CONTENT:
2045
+ self.SetIndicatorCurrent(2)
2046
+ self.IndicatorClearRange(0, self.TextLength)
2047
+ for m in self.grep(url_re):
2048
+ p, q = m.span()
2049
+ self.IndicatorFillRange(p, q-p)
1948
2050
  self.handler('buffer_modified', self)
1949
2051
  evt.Skip()
1950
-
1951
- def OnCallTipClick(self, evt): #<wx._stc.StyledTextEvent>
2052
+
2053
+ def OnCallTipClick(self, evt): # <wx._stc.StyledTextEvent>
1952
2054
  if self.CallTipActive():
1953
2055
  self.CallTipCancel()
1954
2056
  pos, tip, more = self._calltips
1955
2057
  if more:
1956
2058
  self.CallTipShow(pos, tip, N=None)
1957
2059
  evt.Skip()
1958
-
1959
- def OnIndicatorClick(self, evt): #<wx._stc.StyledTextEvent>
2060
+
2061
+ def OnIndicatorClick(self, evt): # <wx._stc.StyledTextEvent>
1960
2062
  if self.SelectedText or not wx.GetKeyState(wx.WXK_CONTROL):
1961
- ## Processing text selection, dragging, or dragging+
2063
+ ## Processing text selection or dragging.
1962
2064
  evt.Skip()
1963
2065
  return
2066
+
1964
2067
  pos = evt.Position
2068
+ self.goto_char(pos)
1965
2069
  i = 2
1966
- if self.IndicatorValueAt(i, pos): # [C-indic click]
2070
+ if self.IndicatorValueAt(i, pos): # [C-indic click]
1967
2071
  p = self.IndicatorStart(i, pos)
1968
2072
  q = self.IndicatorEnd(i, pos)
1969
2073
  url = self.GetTextRange(p, q).strip()
1970
- self.message("URL {!r}".format(url))
1971
- if wx.GetKeyState(wx.WXK_SHIFT): # [C-S-indic click]
2074
+ if wx.GetKeyState(wx.WXK_SHIFT): # [C-S-indic click]
1972
2075
  import webbrowser
1973
2076
  return webbrowser.open(url)
1974
2077
  else:
1975
2078
  ## Note: post-call for the confirmation dialog.
1976
2079
  wx.CallAfter(self.parent.load_file, url)
1977
- self.anchor = pos # Clear selection
1978
-
1979
- def on_modified(self, buf):
1980
- """Called when the buffer is modified."""
1981
- self.SetIndicatorCurrent(2)
1982
- self.IndicatorClearRange(0, self.TextLength)
1983
- for m in self.grep(url_re):
1984
- p, q = m.span()
1985
- self.IndicatorFillRange(p, q-p)
1986
-
2080
+
1987
2081
  def OnSavePointLeft(self, evt):
1988
2082
  self.update_caption()
1989
2083
  evt.Skip()
1990
-
2084
+
1991
2085
  def OnSavePointReached(self, evt):
1992
2086
  self.update_caption()
1993
2087
  evt.Skip()
1994
-
2088
+
1995
2089
  def OnEnterDot(self, evt):
2090
+ if not self.CanEdit():
2091
+ self.handler('quit', evt)
2092
+ return
1996
2093
  p = self.cpos
1997
- st = self.get_style(p-1)
2094
+ lst = self.get_style(p-1)
1998
2095
  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
2096
+ if lst not in ('moji', 'word', 'rparen') or rst == 'word':
2097
+ self.handler('quit', evt) # Don't enter autocomp
2001
2098
  evt.Skip()
2002
-
2003
- def on_activated(self, buf):
2004
- """Called when the buffer is activated."""
2099
+
2100
+ def on_buffer_activate(self, evt):
2101
+ """Called when the buffer (self) is activated."""
2005
2102
  self.update_caption()
2006
2103
  self.trace_position()
2007
-
2008
- def on_inactivated(self, buf):
2009
- """Called when the buffer is inactivated."""
2010
- pass
2011
-
2104
+ self.handler('buffer_activated', self)
2105
+
2106
+ def on_buffer_inactivate(self, evt):
2107
+ """Called when the buffer (self) is inactivated."""
2108
+ self.handler('buffer_inactivated', self)
2109
+
2110
+ def on_left_down(self, evt):
2111
+ pos = self.PositionFromPoint(evt.Position)
2112
+ ln = self.LineFromPosition(pos)
2113
+ ann_text = self.AnnotationGetText(ln)
2114
+ if ann_text:
2115
+ if pos == self.GetLineEndPosition(ln): # Check eol (not clicked yet).
2116
+ if wx.TheClipboard.Open():
2117
+ wx.TheClipboard.SetData(wx.TextDataObject(ann_text))
2118
+ wx.TheClipboard.Close()
2119
+ self.message("Annotation copied.")
2120
+ self.AnnotationClearLine(ln)
2121
+ evt.Skip()
2122
+
2012
2123
  def on_enter_escmap(self, evt):
2013
2124
  self.message("ESC-")
2014
-
2125
+
2015
2126
  def on_exit_escmap(self, evt):
2016
2127
  self.message("ESC {}".format(evt.key))
2017
2128
  self.AnnotationClearAll()
2018
-
2129
+
2019
2130
  ## --------------------------------
2020
- ## File I/O
2131
+ ## File I/O.
2021
2132
  ## --------------------------------
2022
-
2023
- def _load_textfile(self, text, filename):
2133
+
2134
+ def _load_textfile(self, text):
2024
2135
  with self.off_readonly():
2025
2136
  self.Text = text
2026
2137
  self.EmptyUndoBuffer()
2027
2138
  self.SetSavePoint()
2028
- self.update_filestamp(filename)
2029
2139
  self.handler('buffer_loaded', self)
2030
-
2140
+
2031
2141
  def _load_file(self, filename):
2032
2142
  """Wrapped method of LoadFile."""
2033
2143
  if self.LoadFile(filename):
@@ -2037,7 +2147,7 @@ class Buffer(EditorInterface, EditWindow):
2037
2147
  self.handler('buffer_loaded', self)
2038
2148
  return True
2039
2149
  return False
2040
-
2150
+
2041
2151
  def _save_file(self, filename):
2042
2152
  """Wrapped method of SaveFile."""
2043
2153
  if self.SaveFile(filename):
@@ -2046,7 +2156,7 @@ class Buffer(EditorInterface, EditWindow):
2046
2156
  self.handler('buffer_saved', self)
2047
2157
  return True
2048
2158
  return False
2049
-
2159
+
2050
2160
  def LoadFile(self, filename):
2051
2161
  """Load the contents of file into the editor.
2052
2162
 
@@ -2056,7 +2166,7 @@ class Buffer(EditorInterface, EditWindow):
2056
2166
  with self.off_readonly():
2057
2167
  self.Text = i.read()
2058
2168
  return True
2059
-
2169
+
2060
2170
  def SaveFile(self, filename):
2061
2171
  """Write the contents of the editor to file.
2062
2172
 
@@ -2065,37 +2175,38 @@ class Buffer(EditorInterface, EditWindow):
2065
2175
  with open(filename, "w", encoding='utf-8', newline='') as o:
2066
2176
  o.write(self.Text)
2067
2177
  return True
2068
-
2178
+
2069
2179
  ## --------------------------------
2070
- ## Python eval / exec
2180
+ ## Python eval / exec.
2071
2181
  ## --------------------------------
2072
-
2182
+
2073
2183
  @property
2074
- def locals(self): # internal use only
2184
+ def locals(self): # internal use only
2075
2185
  try:
2076
2186
  return self.parent.parent.current_shell.locals
2077
2187
  except AttributeError:
2078
2188
  return None
2079
-
2189
+
2080
2190
  @property
2081
- def globals(self): # internal use only
2191
+ def globals(self): # internal use only
2082
2192
  try:
2083
2193
  return self.parent.parent.current_shell.globals
2084
2194
  except AttributeError:
2085
2195
  return None
2086
-
2196
+
2087
2197
  def eval(self, text):
2088
- return eval(text, self.globals, self.locals) # using current shell namespace
2089
-
2198
+ return eval(text, self.globals, self.locals) # using current shell namespace
2199
+
2090
2200
  def exec(self, text):
2091
- exec(text, self.globals, self.locals) # using current shell namespace
2201
+ exec(text, self.globals, self.locals) # using current shell namespace
2092
2202
  dispatcher.send(signal='Interpreter.push',
2093
2203
  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
- """
2204
+
2205
+ def eval_line(self):
2206
+ """Evaluate the selected word or line and show calltips."""
2207
+ if self.CallTipActive():
2208
+ self.CallTipCancel()
2209
+
2099
2210
  def _gen_text():
2100
2211
  text = self.SelectedText
2101
2212
  if text:
@@ -2103,49 +2214,44 @@ class Buffer(EditorInterface, EditWindow):
2103
2214
  else:
2104
2215
  yield self.line_at_caret
2105
2216
  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
2217
 
2112
- status = "No words"
2113
- for text in self.gen_text_at_caret():
2218
+ for text in _gen_text():
2114
2219
  try:
2115
2220
  obj = eval(text, self.globals, self.locals)
2116
2221
  except Exception as e:
2117
- status = "- {} : {!r}".format(e, text)
2222
+ self.message(e)
2118
2223
  else:
2119
2224
  self.CallTipShow(self.cpos, pformat(obj))
2120
2225
  self.message(text)
2121
2226
  return
2122
- self.message(status)
2123
-
2227
+ if not text:
2228
+ self.message("No words")
2229
+
2124
2230
  def exec_region(self):
2231
+ """Execute a region of code."""
2125
2232
  try:
2126
2233
  code = compile(self.Text, self.filename, "exec")
2127
2234
  exec(code, self.globals, self.locals)
2128
2235
  dispatcher.send(signal='Interpreter.push',
2129
2236
  sender=self, command=None, more=False)
2130
2237
  except BdbQuit:
2131
- self.red_pointer = self.cline
2132
2238
  pass
2133
2239
  except Exception as e:
2134
2240
  msg = traceback.format_exc()
2135
- err = re.findall(py_error_re, msg, re.M)
2241
+ err = re.findall(py_trace_re, msg, re.M)
2136
2242
  lines = [int(ln) for fn, ln in err if fn == self.filename]
2137
2243
  if lines:
2138
2244
  lx = lines[-1] - 1
2139
2245
  self.red_arrow = lx
2140
2246
  self.goto_line(lx)
2141
- self.EnsureVisible(lx) # expand if folded
2247
+ self.EnsureVisible(lx) # expand if folded
2142
2248
  self.EnsureCaretVisible()
2143
2249
  self.AnnotationSetStyle(lx, stc.STC_STYLE_ANNOTATION)
2144
2250
  self.AnnotationSetText(lx, msg)
2145
2251
  self.message(e)
2146
2252
  else:
2147
2253
  self.code = code
2148
- del self.pointer # Reset pointer (debugger hook point).
2254
+ del self.pointer # Reset pointer (debugger hook point).
2149
2255
  del self.red_arrow
2150
2256
  self.handler('buffer_region_executed', self)
2151
2257
  self.message("Evaluated {!r} successfully.".format(self.filename))
@@ -2156,16 +2262,16 @@ class EditorBook(AuiNotebook, CtrlInterface):
2156
2262
  """Python code editor.
2157
2263
 
2158
2264
  Args:
2159
- name : Window.Name (e.g. 'Scratch')
2265
+ name: Window.Name (e.g. 'Scratch')
2160
2266
 
2161
2267
  Attributes:
2162
- default_name : default buffer name (e.g. '*scratch*')
2163
- default_buffer : default buffer
2268
+ default_name: default buffer name (e.g. '*scratch*')
2269
+ default_buffer: default buffer
2164
2270
  """
2165
2271
  @property
2166
2272
  def message(self):
2167
2273
  return self.parent.message
2168
-
2274
+
2169
2275
  def __init__(self, parent, name="book", **kwargs):
2170
2276
  kwargs.setdefault('style',
2171
2277
  (aui.AUI_NB_DEFAULT_STYLE | aui.AUI_NB_TOP)
@@ -2177,12 +2283,11 @@ class EditorBook(AuiNotebook, CtrlInterface):
2177
2283
  ## So we set the tabs' height to zero to hide them.
2178
2284
  self.TabCtrlHeight = 0
2179
2285
 
2180
- self.defaultBufferStyle = dict(
2181
- ReadOnly = False,
2182
- )
2183
- self.parent = parent #: parent<ShellFrame> is not Parent<AuiNotebook>
2286
+ self.defaultBufferStyle = {}
2287
+
2288
+ self.parent = parent # parent<ShellFrame> is not Parent<AuiNotebook>
2184
2289
  self.Name = name
2185
- self.default_name = "*{}*".format(name.lower()) # e.g. '*scratch*'
2290
+ self.default_name = "*{}*".format(name.lower()) # e.g. '*scratch*'
2186
2291
  self.default_buffer = self.create_buffer(self.default_name)
2187
2292
 
2188
2293
  self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.OnPageClose)
@@ -2197,36 +2302,36 @@ class EditorBook(AuiNotebook, CtrlInterface):
2197
2302
  self.make_keymap('C-x')
2198
2303
  self.make_keymap('C-c')
2199
2304
 
2200
- self.handler.update({ # DNA<EditorBook>
2305
+ self.handler.update({ # DNA<EditorBook>
2201
2306
  None : {
2202
- 'buffer_new' : [ None, dispatch ],
2203
- 'buffer_saved' : [ None, dispatch ],
2204
- 'buffer_loaded' : [ None, dispatch ],
2205
- 'buffer_deleted' : [ None, dispatch ],
2206
- 'buffer_modified' : [ None, dispatch ],
2207
- 'buffer_activated' : [ None, dispatch, self.on_activated ],
2208
- 'buffer_inactivated' : [ None, dispatch, self.on_inactivated ],
2209
- 'buffer_caption_updated' : [ None, dispatch ],
2210
- '*button* pressed' : [ None, dispatch, skip ],
2211
- '*button* released' : [ None, dispatch, skip ],
2307
+ 'buffer_new' : [None, dispatch],
2308
+ 'buffer_saved' : [None, dispatch],
2309
+ 'buffer_loaded' : [None, dispatch],
2310
+ 'buffer_deleted' : [None, dispatch],
2311
+ 'buffer_modified' : [None, dispatch],
2312
+ 'buffer_activated' : [None, dispatch, self.on_buffer_activated],
2313
+ 'buffer_inactivated' : [None, dispatch, self.on_buffer_inactivated],
2314
+ 'buffer_caption_updated' : [None, dispatch],
2212
2315
  },
2213
- 0 : { # Normal mode
2316
+ 0 : { # Normal mode
2317
+ '* pressed' : (0, skip),
2318
+ '* released' : (0, skip, dispatch),
2319
+ '*button* pressed' : (0, skip, dispatch),
2214
2320
  'M-up pressed' : (0, _F(self.previous_buffer)),
2215
2321
  'M-down pressed' : (0, _F(self.next_buffer)),
2216
2322
  },
2217
2323
  })
2218
-
2324
+
2219
2325
  def OnDestroy(self, evt):
2220
2326
  obj = evt.EventObject
2221
2327
  if isinstance(obj, Buffer):
2222
2328
  self.handler('buffer_deleted', obj)
2223
2329
  evt.Skip()
2224
-
2225
- def OnPageClose(self, evt): #<wx._aui.AuiNotebookEvent>
2226
- nb = evt.EventObject
2227
- buf = nb.all_buffers[evt.Selection]
2330
+
2331
+ def OnPageClose(self, evt): # <wx._aui.AuiNotebookEvent>
2332
+ buf = self.GetPage(evt.Selection)
2228
2333
  if buf.need_buffer_save:
2229
- if wx.MessageBox( # Confirm close.
2334
+ if wx.MessageBox( # Confirm closing the buffer.
2230
2335
  "You are closing unsaved content.\n\n"
2231
2336
  "The changes will be discarded.\n"
2232
2337
  "Continue closing?",
@@ -2236,17 +2341,17 @@ class EditorBook(AuiNotebook, CtrlInterface):
2236
2341
  evt.Veto()
2237
2342
  return
2238
2343
  evt.Skip()
2239
-
2240
- def OnPageClosed(self, evt): #<wx._aui.AuiNotebookEvent>
2344
+
2345
+ def OnPageClosed(self, evt): # <wx._aui.AuiNotebookEvent>
2241
2346
  if self.PageCount == 0:
2242
2347
  self.new_buffer()
2243
2348
  evt.Skip()
2244
-
2349
+
2245
2350
  def set_attributes(self, buf=None, **kwargs):
2246
2351
  """Set multiple properties at once to the buffer(s).
2247
2352
 
2248
2353
  Args:
2249
- buf : a buffer to apply (if None, applies to all buffers).
2354
+ buf: a buffer to apply (if None, applies to all buffers).
2250
2355
  **kwargs: default style.
2251
2356
 
2252
2357
  ReadOnly = False
@@ -2268,29 +2373,36 @@ class EditorBook(AuiNotebook, CtrlInterface):
2268
2373
  _setattribute(buf, kwargs)
2269
2374
  else:
2270
2375
  self.defaultBufferStyle.update(kwargs)
2271
- for buf in self.all_buffers:
2376
+ for buf in self.get_all_buffers():
2272
2377
  _setattribute(buf, self.defaultBufferStyle)
2273
-
2274
- def on_activated(self, buf):
2378
+
2379
+ def on_buffer_activated(self, buf):
2275
2380
  """Called when the buffer is activated."""
2276
2381
  title = "{} file: {}".format(self.Name, buf.filename)
2277
2382
  self.parent.handler('title_window', title)
2278
-
2279
- def on_inactivated(self, buf):
2383
+
2384
+ def on_buffer_inactivated(self, buf):
2280
2385
  """Called when the buffer is inactivated."""
2281
2386
  pass
2282
-
2387
+
2283
2388
  ## --------------------------------
2284
- ## Buffer list controls
2389
+ ## Buffer list controls.
2285
2390
  ## --------------------------------
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
-
2391
+
2392
+ def get_all_buffers(self, fn=None):
2393
+ """Yields all buffers with specified fn:filename or code."""
2394
+ if fn is None:
2395
+ yield from self.get_pages(Buffer)
2396
+ elif isinstance(fn, str):
2397
+ g = os.path.realpath(fn)
2398
+ for buf in self.get_pages(Buffer):
2399
+ if fn == buf.filename or g == os.path.realpath(buf.filename):
2400
+ yield buf
2401
+ else:
2402
+ for buf in self.get_pages(Buffer):
2403
+ if fn is buf or fn in buf: # check code
2404
+ yield buf
2405
+
2294
2406
  @property
2295
2407
  def menu(self):
2296
2408
  """Yields context menu."""
@@ -2300,86 +2412,91 @@ class EditorBook(AuiNotebook, CtrlInterface):
2300
2412
  lambda v: buf.SetFocus(),
2301
2413
  lambda v: v.Check(buf is self.buffer))
2302
2414
 
2303
- return (_menu(j+1, x) for j, x in enumerate(self.all_buffers))
2304
-
2415
+ return (_menu(j+1, x) for j, x in enumerate(self.get_all_buffers()))
2416
+
2305
2417
  @property
2306
2418
  def buffer(self):
2307
- """Returns the currently selected page or None."""
2419
+ """Return the currently selected page or None."""
2308
2420
  return self.CurrentPage
2309
-
2421
+
2310
2422
  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
-
2423
+ """Find a buffer with specified fn:filename or code."""
2424
+ return next(self.get_all_buffers(fn), None)
2425
+
2322
2426
  def swap_buffer(self, buf, lineno=0):
2323
2427
  self.swap_page(buf)
2324
2428
  if lineno:
2325
2429
  buf.markline = lineno - 1
2326
2430
  buf.goto_marker(1)
2327
-
2431
+
2328
2432
  def create_buffer(self, filename, index=None):
2329
2433
  """Create a new buffer (internal use only)."""
2330
- try:
2331
- self.Freeze()
2434
+ with wx.FrozenWindow(self):
2332
2435
  buf = Buffer(self, filename, style=wx.BORDER_DEFAULT)
2333
2436
  self.set_attributes(buf, **self.defaultBufferStyle)
2334
2437
  if index is None:
2335
2438
  index = self.PageCount
2336
- self.InsertPage(index, buf, buf.name)
2439
+ self.InsertPage(index, buf, buf.name) # => [buffer_activated]
2337
2440
  self.handler('buffer_new', buf)
2338
2441
  return buf
2339
- finally:
2340
- self.Thaw()
2341
-
2442
+
2342
2443
  def new_buffer(self):
2343
2444
  """Create a new default buffer."""
2344
2445
  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)
2446
+ if not buf or buf.mtdelta is not None: # is saved?
2447
+ buf = self.create_buffer(self.default_name)
2347
2448
  self.default_buffer = buf
2348
2449
  else:
2349
2450
  buf.ClearAll()
2350
- ## buf.EmptyUndoBuffer()
2451
+ # buf.EmptyUndoBuffer()
2351
2452
  buf.SetFocus()
2352
2453
  return buf
2353
-
2454
+
2354
2455
  def delete_buffer(self, buf=None):
2355
2456
  """Pop the current buffer from the buffer list."""
2356
2457
  if not buf:
2357
2458
  buf = self.buffer
2358
2459
  j = self.GetPageIndex(buf)
2359
2460
  if j != -1:
2360
- self.DeletePage(j) # the focus moves
2361
- if not self.buffer: # no buffers
2362
- self.new_buffer()
2363
-
2461
+ self.DeletePage(j) # the focus moves
2462
+ if not self.buffer: # no buffers
2463
+ wx.CallAfter(self.new_buffer) # Note: post-call to avoid a crash.
2464
+
2364
2465
  def delete_all_buffers(self):
2365
2466
  """Initialize list of buffers."""
2366
2467
  self.DeleteAllPages()
2367
- self.new_buffer()
2368
-
2468
+ wx.CallAfter(self.new_buffer) # Note: post-call to avoid a crash.
2469
+
2369
2470
  def next_buffer(self):
2370
- self.Selection += 1
2371
-
2471
+ if self.Selection < self.PageCount - 1:
2472
+ self.Selection += 1
2473
+ else:
2474
+ books = list(self.Parent.get_pages(type(self)))
2475
+ k = books.index(self)
2476
+ if k < len(books) - 1:
2477
+ other_editor = books[k+1]
2478
+ other_editor.Selection = 0
2479
+ other_editor.CurrentPage.SetFocus()
2480
+
2372
2481
  def previous_buffer(self):
2373
- self.Selection -= 1
2374
-
2482
+ if self.Selection > 0:
2483
+ self.Selection -= 1
2484
+ else:
2485
+ books = list(self.Parent.get_pages(type(self)))
2486
+ k = books.index(self)
2487
+ if k > 0:
2488
+ other_editor = books[k-1]
2489
+ other_editor.Selection = other_editor.PageCount - 1
2490
+ other_editor.CurrentPage.SetFocus()
2491
+
2375
2492
  ## --------------------------------
2376
- ## File I/O
2493
+ ## File I/O.
2377
2494
  ## --------------------------------
2378
2495
  wildcards = [
2379
2496
  "PY files (*.py)|*.py",
2380
2497
  "ALL files (*.*)|*.*",
2381
2498
  ]
2382
-
2499
+
2383
2500
  def load_cache(self, filename, lineno=0):
2384
2501
  """Load a file from cache using linecache.
2385
2502
  Note:
@@ -2395,20 +2512,22 @@ class EditorBook(AuiNotebook, CtrlInterface):
2395
2512
  elif not buf.need_buffer_load:
2396
2513
  self.swap_buffer(buf, lineno)
2397
2514
  return True
2398
- buf._load_textfile(''.join(lines), filename)
2515
+ buf._load_textfile(''.join(lines))
2516
+ buf.update_filestamp(filename)
2399
2517
  self.swap_buffer(buf, lineno)
2400
2518
  return True
2401
2519
  return False
2402
-
2403
- def load_file(self, filename, lineno=0, verbose=True):
2520
+
2521
+ def load_file(self, filename, lineno=0, verbose=True, **kwargs):
2404
2522
  """Load a file into an existing or new buffer.
2405
- The requests module is required to use URL extension.
2523
+ Note:
2524
+ The 'requests' module is required to load from a URL.
2406
2525
  """
2407
2526
  buf = self.find_buffer(filename)
2408
2527
  if not buf:
2409
2528
  buf = self.create_buffer("*temp file*")
2410
2529
  elif buf.need_buffer_save and verbose:
2411
- if wx.MessageBox( # Confirm load.
2530
+ if wx.MessageBox( # Confirm loading the buffer.
2412
2531
  "You are leaving unsaved content.\n\n"
2413
2532
  "The changes will be discarded.\n"
2414
2533
  "Continue loading?",
@@ -2422,22 +2541,23 @@ class EditorBook(AuiNotebook, CtrlInterface):
2422
2541
  try:
2423
2542
  if re.match(url_re, filename):
2424
2543
  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)
2544
+ kwargs.setdefault('timeout', 3.0)
2545
+ res = requests.get(filename, **kwargs)
2546
+ if res.status_code == requests.codes.OK:
2547
+ buf._load_textfile(res.text)
2548
+ buf.update_filestamp(filename)
2428
2549
  self.swap_buffer(buf, lineno)
2429
2550
  return True
2430
- ## return False
2431
- raise Exception("The requested URL was not found")
2551
+ res.raise_for_status() # raise HTTP error; don't catch here.
2432
2552
  if buf._load_file(filename):
2433
2553
  self.swap_buffer(buf, lineno)
2434
2554
  return True
2435
2555
  return False
2436
- except Exception as e:
2437
- self.post_message(f"Failed to load {filename!r}.", e)
2556
+ except (OSError, UnicodeDecodeError, ModuleNotFoundError) as e:
2557
+ self.post_message("Failed to load;", e)
2438
2558
  self.delete_buffer(buf)
2439
2559
  return False
2440
-
2560
+
2441
2561
  def find_file(self, filename=None):
2442
2562
  """Open the specified file."""
2443
2563
  if not filename:
@@ -2446,21 +2566,22 @@ class EditorBook(AuiNotebook, CtrlInterface):
2446
2566
  wildcard='|'.join(self.wildcards),
2447
2567
  style=wx.FD_OPEN|wx.FD_MULTIPLE) as dlg:
2448
2568
  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):
2569
+ return all([self.find_file(fn) for fn in dlg.Paths])
2570
+ return None
2571
+ retval = self.load_file(filename)
2572
+ if retval == False: # noqa # to check if not None
2453
2573
  buf = self.create_buffer(filename)
2454
- buf._Buffer__mtime = 0 # => need_buffer_save
2455
2574
  self.swap_buffer(buf)
2456
-
2575
+ self.post_message("New file.")
2576
+ return retval
2577
+
2457
2578
  def save_file(self, filename, buf=None, verbose=True):
2458
- """Save the current buffer to a file.
2579
+ """Save the specified buffer to the given filename.
2459
2580
  """
2460
2581
  buf = buf or self.buffer
2461
2582
  if buf.need_buffer_load and verbose:
2462
2583
  self.swap_buffer(buf)
2463
- if wx.MessageBox( # Confirm save.
2584
+ if wx.MessageBox( # Confirm saving the buffer.
2464
2585
  "The file has been modified externally.\n\n"
2465
2586
  "The contents of the file will be overwritten.\n"
2466
2587
  "Continue saving?",
@@ -2474,12 +2595,12 @@ class EditorBook(AuiNotebook, CtrlInterface):
2474
2595
  self.default_buffer = None
2475
2596
  return True
2476
2597
  return False
2477
- except Exception as e:
2478
- self.post_message(f"Failed to save {filename!r}.", e)
2598
+ except (OSError, UnicodeDecodeError) as e:
2599
+ self.post_message("Failed to save;", e)
2479
2600
  return False
2480
-
2601
+
2481
2602
  def load_buffer(self, buf=None):
2482
- """Confirm the load with the dialog."""
2603
+ """Load the buffer; confirm the load with the dialog."""
2483
2604
  buf = buf or self.buffer
2484
2605
  dt = buf.mtdelta
2485
2606
  if dt is None:
@@ -2490,9 +2611,9 @@ class EditorBook(AuiNotebook, CtrlInterface):
2490
2611
  return None
2491
2612
  else:
2492
2613
  return self.load_file(buf.filename, buf.markline+1)
2493
-
2614
+
2494
2615
  def save_buffer(self, buf=None):
2495
- """Confirm the save with the dialog."""
2616
+ """Save the buffer."""
2496
2617
  buf = buf or self.buffer
2497
2618
  dt = buf.mtdelta
2498
2619
  if dt is None:
@@ -2503,28 +2624,29 @@ class EditorBook(AuiNotebook, CtrlInterface):
2503
2624
  return None
2504
2625
  else:
2505
2626
  return self.save_file(buf.filename, buf)
2506
-
2627
+
2507
2628
  def save_buffer_as(self, buf=None):
2508
- """Confirm the saveas with the dialog."""
2629
+ """Save the buffer as a new file."""
2509
2630
  buf = buf or self.buffer
2510
2631
  with wx.FileDialog(self, "Save buffer as",
2511
2632
  defaultDir=os.path.dirname(self.buffer.filename),
2512
- defaultFile=re.sub(r'[\/:*?"<>|]', '_', buf.name),
2633
+ defaultFile=fix_fnchars(buf.name),
2513
2634
  wildcard='|'.join(self.wildcards),
2514
2635
  style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) as dlg:
2515
2636
  if dlg.ShowModal() == wx.ID_OK:
2516
2637
  return self.save_file(dlg.Path, buf)
2517
-
2638
+
2518
2639
  def save_all_buffers(self):
2519
- for buf in self.all_buffers:
2640
+ """Save all buffers."""
2641
+ for buf in self.get_all_buffers():
2520
2642
  if buf.need_buffer_save:
2521
2643
  self.save_buffer(buf)
2522
-
2644
+
2523
2645
  def kill_buffer(self, buf=None):
2524
- """Confirm the close with the dialog."""
2646
+ """Delete the buffer; confirm the close with a dialog."""
2525
2647
  buf = buf or self.buffer
2526
2648
  if buf.need_buffer_save:
2527
- if wx.MessageBox( # Confirm close.
2649
+ if wx.MessageBox( # Confirm closing the buffer.
2528
2650
  "You are closing unsaved content.\n\n"
2529
2651
  "The changes will be discarded.\n"
2530
2652
  "Continue closing?",
@@ -2532,12 +2654,13 @@ class EditorBook(AuiNotebook, CtrlInterface):
2532
2654
  style=wx.YES_NO|wx.ICON_INFORMATION) != wx.YES:
2533
2655
  self.post_message("The close has been canceled.")
2534
2656
  return None
2535
- wx.CallAfter(self.delete_buffer, buf)
2536
-
2657
+ self.delete_buffer(buf)
2658
+
2537
2659
  def kill_all_buffers(self):
2538
- for buf in self.all_buffers:
2660
+ """Delete all buffers; confirm the close with a dialog."""
2661
+ for buf in self.get_all_buffers():
2539
2662
  if buf.need_buffer_save:
2540
- if wx.MessageBox( # Confirm close.
2663
+ if wx.MessageBox( # Confirm closing the buffer.
2541
2664
  "You are closing unsaved content.\n\n"
2542
2665
  "The changes will be discarded.\n"
2543
2666
  "Continue closing?",
@@ -2545,7 +2668,7 @@ class EditorBook(AuiNotebook, CtrlInterface):
2545
2668
  style=wx.YES_NO|wx.ICON_INFORMATION) != wx.YES:
2546
2669
  self.post_message("The close has been canceled.")
2547
2670
  return None
2548
- wx.CallAfter(self.delete_all_buffers)
2671
+ self.delete_all_buffers()
2549
2672
 
2550
2673
 
2551
2674
  class Interpreter(interpreter.Interpreter):
@@ -2556,7 +2679,7 @@ class Interpreter(interpreter.Interpreter):
2556
2679
 
2557
2680
  self.parent = interpShell
2558
2681
  self.globals = self.locals
2559
-
2682
+
2560
2683
  def runcode(self, code):
2561
2684
  """Execute a code object.
2562
2685
 
@@ -2569,10 +2692,10 @@ class Interpreter(interpreter.Interpreter):
2569
2692
  except Exception:
2570
2693
  self.showtraceback()
2571
2694
  finally:
2572
- ## ex. KeyboardInterrupt:
2695
+ ## ex. KeyboardInterrupt
2573
2696
  if wx.IsBusy():
2574
2697
  wx.EndBusyCursor()
2575
-
2698
+
2576
2699
  def showtraceback(self):
2577
2700
  """Display the exception that just occurred.
2578
2701
 
@@ -2589,7 +2712,7 @@ class Interpreter(interpreter.Interpreter):
2589
2712
  self.parent.handler('interp_error', v)
2590
2713
  except AttributeError:
2591
2714
  pass
2592
-
2715
+
2593
2716
  def showsyntaxerror(self, filename=None, **kwargs):
2594
2717
  """Display the syntax error that just occurred.
2595
2718
 
@@ -2602,22 +2725,6 @@ class Interpreter(interpreter.Interpreter):
2602
2725
  self.parent.handler('interp_error', v)
2603
2726
  except AttributeError:
2604
2727
  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
2728
 
2622
2729
 
2623
2730
  class Nautilus(EditorInterface, Shell):
@@ -2639,10 +2746,9 @@ class Nautilus(EditorInterface, Shell):
2639
2746
 
2640
2747
  - quoteback : x`y --> y=x | x`y`z --> z=y=x
2641
2748
  - 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] .
2749
+ - apropos : x.y? [not] p --> shows items (not) matched by predicate p
2750
+ equiv. apropos(x, y [,ignorecase ?:True,??:False] [,pred=p]).
2751
+ ``y`` can contain regular expressions, but no dots or backslashes.
2646
2752
  ``p`` can be atom, callable, type (e.g., int, str, ...),
2647
2753
  and any predicates such as inspect.isclass.
2648
2754
 
@@ -2657,15 +2763,15 @@ class Nautilus(EditorInterface, Shell):
2657
2763
 
2658
2764
  C-up : [0] retrieve previous history
2659
2765
  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)
2766
+ C-j : [0] tooltip of eval (for the selected or focused word)
2767
+ C-h M-h : [0] calltip of help (for the selected or focused func)
2662
2768
  TAB : [1] history-comp
2663
2769
  M-p : [1] retrieve previous history in history-comp mode
2664
2770
  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
2771
+ M-. C-. : [2] word-comp
2772
+ M-/ C-/ : [3] apropos-comp
2773
+ M-, C-, : [4] text-comp
2774
+ M-m C-m : [5] module-comp
2669
2775
 
2670
2776
  Autocomps are incremental when pressed any alnums,
2671
2777
  and decremental when backspace.
@@ -2690,17 +2796,17 @@ class Nautilus(EditorInterface, Shell):
2690
2796
  @property
2691
2797
  def message(self):
2692
2798
  return self.parent.message
2693
-
2799
+
2694
2800
  @property
2695
2801
  def target(self):
2696
2802
  return self.__target
2697
-
2803
+
2698
2804
  @target.setter
2699
2805
  def target(self, obj):
2700
2806
  """Reset the shell target object; Rename the parent title.
2701
2807
  """
2702
2808
  if not hasattr(obj, '__dict__'):
2703
- raise TypeError("primitive objects cannot be targeted")
2809
+ raise TypeError("invalid target")
2704
2810
 
2705
2811
  self.__target = obj
2706
2812
  self.locals = obj.__dict__
@@ -2709,38 +2815,39 @@ class Nautilus(EditorInterface, Shell):
2709
2815
  try:
2710
2816
  obj.self = obj
2711
2817
  obj.this = inspect.getmodule(obj)
2712
- obj.shell = self # overwrite the facade <wx.py.shell.ShellFacade>
2818
+ obj.shell = self # Overwrite the facade <wx.py.shell.ShellFacade>.
2819
+ ## obj.__name__ = typename(obj) # A namespace for ghost in the shell. cf. exec_region
2713
2820
  except AttributeError:
2714
2821
  pass
2715
2822
  self.parent.handler('title_window', obj)
2716
-
2823
+
2717
2824
  @property
2718
2825
  def locals(self):
2719
2826
  return self.interp.locals
2720
-
2827
+
2721
2828
  @locals.setter
2722
- def locals(self, v): # internal use only
2829
+ def locals(self, v): # internal use only
2723
2830
  self.interp.locals = v
2724
-
2831
+
2725
2832
  @locals.deleter
2726
- def locals(self): # internal use only
2833
+ def locals(self): # internal use only
2727
2834
  self.interp.locals = self.__target.__dict__
2728
-
2835
+
2729
2836
  @property
2730
2837
  def globals(self):
2731
2838
  return self.interp.globals
2732
-
2839
+
2733
2840
  @globals.setter
2734
- def globals(self, v): # internal use only
2841
+ def globals(self, v): # internal use only
2735
2842
  self.interp.globals = v
2736
-
2843
+
2737
2844
  @globals.deleter
2738
- def globals(self): # internal use only
2845
+ def globals(self): # internal use only
2739
2846
  self.interp.globals = self.__target.__dict__
2740
2847
  self.interp.globals.update(self.__globals)
2741
-
2848
+
2742
2849
  __globals = {}
2743
-
2850
+
2744
2851
  def __init__(self, parent, target, name="root",
2745
2852
  introText=None,
2746
2853
  startupScript=None,
@@ -2748,53 +2855,32 @@ class Nautilus(EditorInterface, Shell):
2748
2855
  **kwargs):
2749
2856
  Shell.__init__(self, parent,
2750
2857
  locals=target.__dict__,
2751
- interpShell=self, # **kwds of InterpClass
2858
+ interpShell=self, # **kwds of InterpClass
2752
2859
  InterpClass=Interpreter,
2753
2860
  introText=introText,
2754
2861
  startupScript=startupScript,
2755
- execStartupScript=execStartupScript, # executes ~/.py
2862
+ execStartupScript=execStartupScript, # executes ~/.py
2756
2863
  **kwargs)
2757
2864
  EditorInterface.__init__(self)
2758
2865
 
2759
- self.parent = parent #: parent<ShellFrame> is not Parent<AuiNotebook>
2866
+ self.parent = parent # parent<ShellFrame> is not Parent<AuiNotebook>
2760
2867
  self.target = target
2761
2868
  self.Name = name
2762
2869
 
2763
2870
  wx.py.shell.USE_MAGIC = True
2764
- wx.py.shell.magic = self.magic # called when USE_MAGIC
2871
+ wx.py.shell.magic = self.magic # called when USE_MAGIC
2765
2872
 
2766
- self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdate) # skip to brace matching
2873
+ self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdate) # skip to brace matching
2767
2874
  self.Bind(stc.EVT_STC_CALLTIP_CLICK, self.OnCallTipClick)
2768
2875
 
2769
2876
  self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
2770
2877
 
2771
- def activate(evt):
2772
- if self:
2773
- self.handler('shell_activated', self)
2774
- evt.Skip()
2775
- self.Bind(wx.EVT_SET_FOCUS, activate)
2776
-
2777
- def inactivate(evt):
2778
- if self:
2779
- self.handler('shell_inactivated', self)
2780
- evt.Skip()
2781
- self.Bind(wx.EVT_KILL_FOCUS, inactivate)
2782
-
2783
2878
  def clear(evt):
2784
2879
  ## """Clear selection and message, no skip."""
2785
- ## *do not* clear autocomp, so that the event can skip to AutoComp properly.
2880
+ ## *DO NOT* clear autocomp, so that the event can skip to AutoComp properly.
2786
2881
  if self.CanEdit():
2787
2882
  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()
2795
- if self.CanEdit():
2796
- with self.off_undocollection():
2797
- self.ReplaceSelection("")
2883
+ self.ReplaceSelection('')
2798
2884
  self.message("")
2799
2885
 
2800
2886
  def fork(evt):
@@ -2804,17 +2890,17 @@ class Nautilus(EditorInterface, Shell):
2804
2890
  """Fork events to the parent."""
2805
2891
  self.parent.handler(self.handler.current_event, evt)
2806
2892
 
2807
- self.handler.update({ # DNA<Nautilus>
2893
+ self.handler.update({ # DNA<Nautilus>
2808
2894
  None : {
2809
- 'interp_error' : [ None, self.on_interp_error ],
2810
- 'shell_deleted' : [ None, dispatch, self.on_deleted ],
2811
- '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 ],
2895
+ 'interp_error' : [None, self.on_interp_error],
2896
+ 'focus_set' : [None, self.on_shell_activate],
2897
+ 'focus_kill' : [None, self.on_shell_inactivate],
2898
+ 'shell_deleted' : [None, dispatch, self.on_shell_deleted],
2899
+ 'shell_modified' : [None, dispatch],
2900
+ 'shell_activated' : [None, dispatch],
2901
+ 'shell_inactivated' : [None, dispatch],
2816
2902
  },
2817
- -1 : { # original action of the wx.py.shell
2903
+ -1 : { # original action of the wx.py.shell
2818
2904
  '* pressed' : (0, skip, self.on_exit_escmap),
2819
2905
  '*alt pressed' : (-1, ),
2820
2906
  '*ctrl pressed' : (-1, ),
@@ -2822,49 +2908,52 @@ class Nautilus(EditorInterface, Shell):
2822
2908
  '*[LR]win pressed' : (-1, ),
2823
2909
  '*f12 pressed' : (-2, self.on_exit_escmap, self.on_enter_notemode),
2824
2910
  },
2825
- -2 : { # Note mode
2911
+ -2 : { # Note mode
2826
2912
  'C-g pressed' : (0, self.on_exit_notemode),
2827
2913
  '*f12 pressed' : (0, self.on_exit_notemode),
2828
2914
  'escape pressed' : (0, self.on_exit_notemode),
2829
2915
  },
2830
- 0 : { # Normal mode
2916
+ 0 : { # Normal mode
2831
2917
  '* pressed' : (0, skip),
2832
- '* released' : (0, skip),
2918
+ '* released' : (0, skip, dispatch),
2919
+ '*button* pressed' : (0, skip, dispatch),
2833
2920
  'escape pressed' : (-1, self.on_enter_escmap),
2834
2921
  'space pressed' : (0, self.OnSpace),
2835
2922
  '*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
2923
  'enter pressed' : (0, self.OnEnter),
2839
2924
  'C-enter pressed' : (0, _F(self.insertLineBreak)),
2840
2925
  'C-S-enter pressed' : (0, _F(self.insertLineBreak)),
2841
- '*enter pressed' : (0, ), # -> OnShowCompHistory 無効
2926
+ '*enter pressed' : (0, ), # -> OnShowCompHistory 無効
2842
2927
  'C-[ pressed' : (0, _F(self.goto_previous_mark_arrow)),
2843
2928
  'C-S-[ pressed' : (0, _F(self.goto_previous_mark_arrow, selection=1)),
2844
2929
  'C-] pressed' : (0, _F(self.goto_next_mark_arrow)),
2845
2930
  'C-S-] pressed' : (0, _F(self.goto_next_mark_arrow, selection=1)),
2846
2931
  'M-up pressed' : (0, _F(self.goto_previous_white_arrow)),
2847
2932
  '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.
2933
+ # 'C-c pressed' : (0, skip), # -> spec-map
2934
+ 'C-S-c pressed' : (0, skip), # -> Copy selected text, retaining prompts.
2850
2935
  'C-v pressed' : (0, _F(self.Paste)),
2851
2936
  'C-S-v pressed' : (0, _F(self.Paste, rectangle=1)),
2852
2937
  'S-insert pressed' : (0, _F(self.Paste)),
2853
2938
  '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),
2939
+ 'C-j pressed' : (0, _F(self.eval_line)),
2940
+ 'M-j pressed' : (0, _F(self.exec_region)),
2856
2941
  'C-h pressed' : (0, self.call_helpTip),
2857
2942
  'M-h pressed' : (0, self.call_helpDoc),
2858
- '. pressed' : (2, self.OnEnterDot),
2859
2943
  'tab pressed' : (1, self.call_history_comp),
2860
2944
  'M-p pressed' : (1, self.call_history_comp),
2861
2945
  'M-n pressed' : (1, self.call_history_comp),
2946
+ '. pressed' : (2, self.OnEnterDot),
2947
+ 'C-. pressed' : (2, self.call_word_autocomp),
2948
+ 'C-/ pressed' : (3, self.call_apropos_autocomp),
2949
+ 'C-, pressed' : (4, self.call_text_autocomp),
2950
+ 'C-m pressed' : (5, self.call_module_autocomp),
2862
2951
  'M-. pressed' : (2, self.call_word_autocomp),
2863
2952
  'M-/ pressed' : (3, self.call_apropos_autocomp),
2864
2953
  'M-, pressed' : (4, self.call_text_autocomp),
2865
2954
  'M-m pressed' : (5, self.call_module_autocomp),
2866
2955
  },
2867
- 1 : { # history auto completion S-mode
2956
+ 1 : { # history auto completion S-mode
2868
2957
  'quit' : (0, clear),
2869
2958
  'skip' : (0, self.on_indent_line),
2870
2959
  '* pressed' : (0, fork),
@@ -2874,10 +2963,10 @@ class Nautilus(EditorInterface, Shell):
2874
2963
  'S-left released' : (1, self.call_history_comp),
2875
2964
  'S-right pressed' : (1, skip),
2876
2965
  '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)),
2966
+ 'tab pressed' : (1, _F(self._on_completion, step=1)), # 古いヒストリへ進む
2967
+ 'S-tab pressed' : (1, _F(self._on_completion, step=-1)), # 新しいヒストリへ戻る
2968
+ 'M-p pressed' : (1, _F(self._on_completion, step=1)),
2969
+ 'M-n pressed' : (1, _F(self._on_completion, step=-1)),
2881
2970
  '[a-z0-9_] pressed' : (1, skip),
2882
2971
  '[a-z0-9_] released' : (1, self.call_history_comp),
2883
2972
  'S-[a-z\\] pressed' : (1, skip),
@@ -2889,12 +2978,12 @@ class Nautilus(EditorInterface, Shell):
2889
2978
  '*[LR]win pressed' : (1, ),
2890
2979
  '*f[0-9]* pressed' : (1, ),
2891
2980
  },
2892
- 2 : { # word auto completion AS-mode
2893
- 'quit' : (0, clear_autocomp),
2894
- '* pressed' : (0, clear_autocomp, fork),
2981
+ 2 : { # word auto completion AS-mode
2982
+ 'quit' : (0, self.clear_autocomp),
2983
+ '* pressed' : (0, self.clear_autocomp, fork),
2895
2984
  'tab pressed' : (0, clear, skip),
2896
2985
  'enter pressed' : (0, clear, skip),
2897
- 'escape pressed' : (0, clear_autocomp),
2986
+ 'escape pressed' : (0, clear, skip),
2898
2987
  'up pressed' : (2, skip, self.on_completion_backward),
2899
2988
  'down pressed' : (2, skip, self.on_completion_forward),
2900
2989
  '*left pressed' : (2, skip),
@@ -2909,20 +2998,20 @@ class Nautilus(EditorInterface, Shell):
2909
2998
  '*delete pressed' : (2, skip),
2910
2999
  '*backspace pressed' : (2, self.OnBackspace),
2911
3000
  '*backspace released' : (2, self.call_word_autocomp),
2912
- 'C-S-backspace pressed' : (2, ),
2913
- 'C-j pressed' : (2, self.eval_line),
3001
+ '*S-backspace pressed' : (0, self.clear_autocomp, fork),
3002
+ 'C-j pressed' : (2, _F(self.eval_line)),
2914
3003
  '*alt pressed' : (2, ),
2915
3004
  '*ctrl pressed' : (2, ),
2916
3005
  '*shift pressed' : (2, ),
2917
3006
  '*[LR]win pressed' : (2, ),
2918
3007
  '*f[0-9]* pressed' : (2, ),
2919
3008
  },
2920
- 3 : { # apropos auto completion AS-mode
2921
- 'quit' : (0, clear_autocomp),
2922
- '* pressed' : (0, clear_autocomp, fork),
3009
+ 3 : { # apropos auto completion AS-mode
3010
+ 'quit' : (0, self.clear_autocomp),
3011
+ '* pressed' : (0, self.clear_autocomp, fork),
2923
3012
  'tab pressed' : (0, clear, skip),
2924
3013
  'enter pressed' : (0, clear, skip),
2925
- 'escape pressed' : (0, clear_autocomp),
3014
+ 'escape pressed' : (0, clear, skip),
2926
3015
  'up pressed' : (3, skip, self.on_completion_backward),
2927
3016
  'down pressed' : (3, skip, self.on_completion_forward),
2928
3017
  '*left pressed' : (3, skip),
@@ -2937,20 +3026,20 @@ class Nautilus(EditorInterface, Shell):
2937
3026
  '*delete pressed' : (3, skip),
2938
3027
  '*backspace pressed' : (3, self.OnBackspace),
2939
3028
  '*backspace released' : (3, self.call_apropos_autocomp),
2940
- 'C-S-backspace pressed' : (3, ),
2941
- 'C-j pressed' : (3, self.eval_line),
3029
+ '*S-backspace pressed' : (0, self.clear_autocomp, fork),
3030
+ 'C-j pressed' : (3, _F(self.eval_line)),
2942
3031
  '*alt pressed' : (3, ),
2943
3032
  '*ctrl pressed' : (3, ),
2944
3033
  '*shift pressed' : (3, ),
2945
3034
  '*[LR]win pressed' : (3, ),
2946
3035
  '*f[0-9]* pressed' : (3, ),
2947
3036
  },
2948
- 4 : { # text auto completion AS-mode
2949
- 'quit' : (0, clear_autocomp),
2950
- '* pressed' : (0, clear_autocomp, fork),
3037
+ 4 : { # text auto completion AS-mode
3038
+ 'quit' : (0, self.clear_autocomp),
3039
+ '* pressed' : (0, self.clear_autocomp, fork),
2951
3040
  'tab pressed' : (0, clear, skip),
2952
3041
  'enter pressed' : (0, clear, skip),
2953
- 'escape pressed' : (0, clear_autocomp),
3042
+ 'escape pressed' : (0, clear, skip),
2954
3043
  'up pressed' : (4, skip, self.on_completion_backward),
2955
3044
  'down pressed' : (4, skip, self.on_completion_forward),
2956
3045
  '*left pressed' : (4, skip),
@@ -2965,20 +3054,20 @@ class Nautilus(EditorInterface, Shell):
2965
3054
  '*delete pressed' : (4, skip),
2966
3055
  '*backspace pressed' : (4, self.OnBackspace),
2967
3056
  '*backspace released' : (4, self.call_text_autocomp),
2968
- 'C-S-backspace pressed' : (4, ),
2969
- 'C-j pressed' : (4, self.eval_line),
3057
+ '*S-backspace pressed' : (0, self.clear_autocomp, fork),
3058
+ 'C-j pressed' : (4, _F(self.eval_line)),
2970
3059
  '*alt pressed' : (4, ),
2971
3060
  '*ctrl pressed' : (4, ),
2972
3061
  '*shift pressed' : (4, ),
2973
3062
  '*[LR]win pressed' : (4, ),
2974
3063
  '*f[0-9]* pressed' : (4, ),
2975
3064
  },
2976
- 5 : { # module auto completion AS-mode
2977
- 'quit' : (0, clear_autocomp),
2978
- '* pressed' : (0, clear_autocomp, fork),
3065
+ 5 : { # module auto completion AS-mode
3066
+ 'quit' : (0, self.clear_autocomp),
3067
+ '* pressed' : (0, self.clear_autocomp, fork),
2979
3068
  'tab pressed' : (0, clear, skip),
2980
3069
  'enter pressed' : (0, clear, skip),
2981
- 'escape pressed' : (0, clear_autocomp),
3070
+ 'escape pressed' : (0, clear, skip),
2982
3071
  'up pressed' : (5, skip, self.on_completion_backward),
2983
3072
  'down pressed' : (5, skip, self.on_completion_forward),
2984
3073
  '*left pressed' : (5, skip),
@@ -2990,11 +3079,12 @@ class Nautilus(EditorInterface, Shell):
2990
3079
  'S-[a-z\\] pressed' : (5, skip),
2991
3080
  'S-[a-z\\] released' : (5, self.call_module_autocomp),
2992
3081
  '\\ released' : (5, self.call_module_autocomp),
3082
+ 'C-m pressed' : (5, _F(self.call_module_autocomp, force=1)),
2993
3083
  'M-m pressed' : (5, _F(self.call_module_autocomp, force=1)),
2994
3084
  '*delete pressed' : (5, skip),
2995
3085
  '*backspace pressed' : (5, self.OnBackspace),
2996
3086
  '*backspace released' : (5, self.call_module_autocomp),
2997
- 'C-S-backspace pressed' : (5, ),
3087
+ '*S-backspace pressed' : (0, self.clear_autocomp, fork),
2998
3088
  '*alt pressed' : (5, ),
2999
3089
  '*ctrl pressed' : (5, ),
3000
3090
  '*shift pressed' : (5, ),
@@ -3007,22 +3097,22 @@ class Nautilus(EditorInterface, Shell):
3007
3097
  self.show_folder()
3008
3098
  self.set_stylus(Stylus.py_shell_mode)
3009
3099
 
3010
- ## delete unnecessary arrows at startup
3100
+ ## Delete unnecessary arrows at startup.
3011
3101
  del self.white_arrow
3012
3102
  del self.red_arrow
3013
3103
 
3014
3104
  self.__text = ''
3015
-
3105
+
3016
3106
  def trace_position(self):
3017
3107
  _text, lp = self.CurLine
3018
3108
  self.message("{:>6d}:{} ({})".format(self.cline, lp, self.cpos), pane=-1)
3019
-
3109
+
3020
3110
  def OnDestroy(self, evt):
3021
3111
  if evt.EventObject is self:
3022
3112
  self.handler('shell_deleted', self)
3023
3113
  evt.Skip()
3024
-
3025
- def OnUpdate(self, evt): #<wx._stc.StyledTextEvent>
3114
+
3115
+ def OnUpdate(self, evt): # <wx._stc.StyledTextEvent>
3026
3116
  if evt.Updated & (stc.STC_UPDATE_SELECTION | stc.STC_UPDATE_CONTENT):
3027
3117
  self.trace_position()
3028
3118
  if self.handler.current_state == 0:
@@ -3031,18 +3121,18 @@ class Nautilus(EditorInterface, Shell):
3031
3121
  name, argspec, tip = self.interp.getCallTip(text)
3032
3122
  if tip:
3033
3123
  tip = tip.splitlines()[0]
3034
- self.message(tip) # clear if no tip
3124
+ self.message(tip) # clear if no tip
3035
3125
  self.__text = text
3036
3126
  if evt.Updated & stc.STC_UPDATE_CONTENT:
3037
3127
  self.handler('shell_modified', self)
3038
3128
  evt.Skip()
3039
-
3129
+
3040
3130
  def OnCallTipClick(self, evt):
3041
3131
  if self.CallTipActive():
3042
3132
  self.CallTipCancel()
3043
3133
  self.parent.handler('add_help', self._calltips[1])
3044
3134
  evt.Skip()
3045
-
3135
+
3046
3136
  def OnSpace(self, evt):
3047
3137
  """Called when space pressed."""
3048
3138
  if not self.CanEdit():
@@ -3052,133 +3142,142 @@ class Nautilus(EditorInterface, Shell):
3052
3142
  or re.match(r"from\s*$", cmdl)\
3053
3143
  or re.match(r"from\s+([\w.]+)\s+import\s*", cmdl):
3054
3144
  self.ReplaceSelection(' ')
3055
- self.handler('M-m pressed', None) # => call_module_autocomp
3145
+ self.handler('M-m pressed', None) # => call_module_autocomp
3056
3146
  return
3057
3147
  evt.Skip()
3058
-
3148
+
3059
3149
  def OnBackspace(self, evt):
3060
3150
  """Called when backspace pressed.
3061
3151
  Backspace-guard from autocomp eating over a prompt whitespace.
3062
3152
  """
3063
3153
  if self.cpos == self.bolc:
3064
- self.handler('quit', evt) # don't eat backward prompt
3154
+ self.handler('quit', evt) # Don't eat backward prompt
3065
3155
  return
3066
3156
  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
-
3157
+
3158
+ @editable
3159
+ def backward_kill_word(self): # (override)
3160
+ if not self.SelectedText:
3161
+ if self.cpos <= self.bolc:
3162
+ return
3163
+ text, lp = self.CurLine
3164
+ if text[:lp] == sys.ps2:
3165
+ self.cpos -= lp # Select ps2:prompt
3166
+ self.WordLeftExtend() # Select cr/lf chunks
3167
+ else:
3168
+ self.WordLeftExtend()
3169
+ q = max(self.bol, self.bolc) # for debugger mode: bol <= bolc
3170
+ if self.cpos < q:
3171
+ self.cpos = q # Don't skip back ps2:prompt
3172
+ self.ReplaceSelection('')
3173
+
3174
+ @editable
3175
+ def backward_kill_line(self): # (override)
3176
+ if not self.SelectedText:
3177
+ if self.cpos <= self.bolc:
3178
+ return
3179
+ text, lp = self.CurLine
3180
+ if text[:lp] == sys.ps2:
3181
+ self.cpos -= lp # Select ps2:prompt
3182
+ self.WordLeftExtend() # Select cr/lf chunks
3183
+ else:
3184
+ q = max(self.bol, self.bolc) # for debugger mode: bol <= bolc
3185
+ if self.cpos > q:
3186
+ self.cpos = q
3187
+ else:
3188
+ self.WordLeftExtend() # Select cr/lf chunks
3189
+ self.ReplaceSelection('')
3190
+
3092
3191
  def OnEnter(self, evt):
3093
3192
  """Called when enter pressed."""
3094
3193
  if not self.CanEdit():
3095
- self.goto_char(self.eolc) # go to end of command line
3194
+ self.goto_char(self.eolc) # go to end of command line
3096
3195
  return
3097
- if self.AutoCompActive(): # skip to auto completion
3196
+ if self.AutoCompActive(): # skip to auto completion
3098
3197
  evt.Skip()
3099
3198
  return
3100
3199
  if self.CallTipActive():
3101
3200
  self.CallTipCancel()
3102
3201
 
3103
- ## skip to wx.py.magic if text begins with !(sx), ?(info), and ??(help)
3202
+ ## Skip to wx.py.magic if text begins with !(sx), ?(info), and ??(help).
3104
3203
  text = self.cmdline
3105
3204
  if not text or text[0] in '!?':
3106
3205
  evt.Skip()
3107
3206
  return
3108
3207
 
3109
- ## cast magic for `@? (Note: PY35 supports @(matmul)-operator)
3208
+ ## Cast magic for `@? (Note: PY35 supports @(matmul)-operator).
3110
3209
  tokens = list(split_words(text))
3111
3210
  if any(x in tokens for x in '`@?$'):
3112
3211
  cmd = self.magic_interpret(tokens)
3113
3212
  if '\n' in cmd:
3114
- self.Execute(cmd) # => multi-line commands
3213
+ self.Execute(cmd) # => multi-line commands
3115
3214
  else:
3116
- self.run(cmd, verbose=0, prompt=0) # => push(cmd)
3215
+ self.run(cmd, verbose=0, prompt=0) # => push(cmd)
3117
3216
  return
3118
3217
 
3119
3218
  self.exec_cmdline()
3120
- ## evt.Skip() # => processLine
3121
-
3219
+ # evt.Skip() # => processLine
3220
+
3122
3221
  def OnEnterDot(self, evt):
3123
3222
  """Called when dot [.] pressed."""
3124
3223
  if not self.CanEdit():
3125
3224
  self.handler('quit', evt)
3126
3225
  return
3127
3226
  p = self.cpos
3128
- st = self.get_style(p-1)
3227
+ lst = self.get_style(p-1)
3129
3228
  rst = self.get_style(p)
3130
3229
  if p == self.bolc:
3131
- self.ReplaceSelection('self') # replace [.] --> [self.]
3132
- elif st in ('nil', 'space', 'op', 'sep', 'lparen'):
3230
+ self.ReplaceSelection('self') # replace [.] --> [self.]
3231
+ elif lst in ('space', 'sep', 'lparen'):
3133
3232
  self.ReplaceSelection('self')
3134
- elif st not in ('moji', 'word', 'rparen') or rst == 'word':
3135
- self.handler('quit', evt) # don't enter autocomp
3233
+ elif lst not in ('moji', 'word', 'rparen') or rst == 'word':
3234
+ self.handler('quit', evt) # Don't enter autocomp
3136
3235
  evt.Skip()
3137
-
3236
+
3138
3237
  def on_enter_escmap(self, evt):
3139
3238
  self.__caret_mode = self.CaretPeriod
3140
3239
  self.CaretPeriod = 0
3141
3240
  self.message("ESC-")
3142
-
3241
+
3143
3242
  def on_exit_escmap(self, evt):
3144
3243
  self.CaretPeriod = self.__caret_mode
3145
3244
  self.message("ESC {}".format(evt.key))
3146
- if self.eolc < self.bolc: # check if prompt is in valid state
3245
+ if self.eolc < self.bolc: # Check if prompt is in valid state.
3147
3246
  self.goto_char(self.eolc)
3148
- self.promptPosEnd = 0
3247
+ self.promptPosEnd = 0 # Enabale write(prompt).
3149
3248
  self.prompt()
3150
3249
  self.AnnotationClearAll()
3151
-
3250
+
3152
3251
  def on_enter_notemode(self, evt):
3153
3252
  self.noteMode = True
3154
3253
  self.__caret_mode = self.CaretForeground
3155
3254
  self.CaretForeground = 'red'
3156
3255
  self.message("Note mode")
3157
-
3256
+
3158
3257
  def on_exit_notemode(self, evt):
3159
3258
  self.noteMode = False
3160
3259
  self.CaretForeground = self.__caret_mode
3161
3260
  self.goto_char(self.eolc)
3162
- self.promptPosEnd = 0
3261
+ self.promptPosEnd = 0 # Enabale write(prompt).
3163
3262
  self.prompt()
3164
3263
  self.message("")
3165
-
3264
+
3166
3265
  def goto_next_white_arrow(self):
3167
- self.goto_next_marker(0b010) # next white-arrow
3168
-
3266
+ self.goto_next_marker(0b010) # next white-arrow
3267
+
3169
3268
  def goto_previous_white_arrow(self):
3170
- self.goto_previous_marker(0b010) # previous white-arrow
3171
-
3269
+ self.goto_previous_marker(0b010) # previous white-arrow
3270
+
3172
3271
  def goto_next_mark_arrow(self, selection=False):
3173
- self.goto_next_marker(0b110, selection) # next white/red-arrow
3174
-
3272
+ self.goto_next_marker(0b110, selection) # next white/red-arrow
3273
+
3175
3274
  def goto_previous_mark_arrow(self, selection=False):
3176
- self.goto_previous_marker(0b110, selection) # previous white/red-arrow
3177
-
3275
+ self.goto_previous_marker(0b110, selection) # previous white/red-arrow
3276
+
3178
3277
  ## --------------------------------
3179
- ## Magic caster of the shell
3278
+ ## Magic caster of the shell.
3180
3279
  ## --------------------------------
3181
-
3280
+
3182
3281
  @classmethod
3183
3282
  def magic(self, cmd):
3184
3283
  """Called before command pushed.
@@ -3190,7 +3289,7 @@ class Nautilus(EditorInterface, Shell):
3190
3289
  elif cmd[0] == '?': cmd = 'info({})'.format(cmd[1:])
3191
3290
  elif cmd[0] == '!': cmd = 'sx({!r})'.format(cmd[1:])
3192
3291
  return cmd
3193
-
3292
+
3194
3293
  @classmethod
3195
3294
  def magic_interpret(self, tokens):
3196
3295
  """Called when [Enter] command, or eval-time for tooltip.
@@ -3205,8 +3304,8 @@ class Nautilus(EditorInterface, Shell):
3205
3304
  Note:
3206
3305
  This is called before run, execute, and original magic.
3207
3306
  """
3208
- sep1 = "`@=;\r\n#" # [`] no ops, no spaces, no comma
3209
- sep2 = "`@=+-/*%<>&|^~,; \t\r\n#" # [@] ops, delims, and whitespaces
3307
+ sep1 = "`@=;\r\n#" # [`] no ops, no spaces, no comma
3308
+ sep2 = "`@=+-/*%<>&|^~,; \t\r\n#" # [@] ops, delims, and whitespaces
3210
3309
 
3211
3310
  def _popiter(ls, f):
3212
3311
  pred = f if callable(f) else re.compile(f).match
@@ -3222,7 +3321,7 @@ class Nautilus(EditorInterface, Shell):
3222
3321
  for i, c in enumerate(tokens):
3223
3322
  rest = tokens[i+1:]
3224
3323
 
3225
- if c == '@' and not lhs.strip() and '\n' in rest: # @decor
3324
+ if c == '@' and not lhs.strip() and '\n' in rest: # @decor
3226
3325
  pass
3227
3326
 
3228
3327
  elif c == '@':
@@ -3232,16 +3331,21 @@ class Nautilus(EditorInterface, Shell):
3232
3331
  ## func(a,b,c) @debug --> func,a,b,c @debug
3233
3332
  if rhs in ("debug", "profile", "timeit"):
3234
3333
  if lhs[-1] in ')':
3235
- L, R = split_paren(lhs, reverse=1)
3334
+ R = next(split_parts(lhs, reverse=1))
3335
+ L = lhs[:-len(R)]
3236
3336
  if not L:
3237
3337
  lhs = "{!r}".format(R[1:-1])
3238
- elif R:
3338
+ else:
3239
3339
  lhs = "{}, {}".format(L, R[1:-1])
3240
3340
 
3241
3341
  ## @(y1,,,yn) --> @partial(y1,,,yn)
3242
3342
  elif rhs.startswith('('):
3243
3343
  rhs = re.sub(r"^\((.*)\)", r"partial(\1)", rhs, flags=re.S)
3244
3344
 
3345
+ ## obj @.method --> (obj).method
3346
+ elif rhs.startswith('.'):
3347
+ return self.magic_interpret([f"({lhs}){rhs}"] + rest)
3348
+
3245
3349
  return self.magic_interpret([f"{rhs}({lhs})"] + rest)
3246
3350
 
3247
3351
  if c == '`':
@@ -3260,17 +3364,17 @@ class Nautilus(EditorInterface, Shell):
3260
3364
  return lhs + c + self.magic_interpret(rest)
3261
3365
 
3262
3366
  if c == sys.ps2.strip():
3263
- rhs = ''.join(_popiter(rest, "[ \t\r\n]")) # feed
3367
+ rhs = ''.join(_popiter(rest, "[ \t\r\n]")) # feed
3264
3368
  return lhs + c + rhs + self.magic_interpret(rest)
3265
3369
 
3266
3370
  if c.startswith('#'):
3267
- rhs = ''.join(_popiter(rest, "[^\r\n]")) # skip comment
3371
+ rhs = ''.join(_popiter(rest, "[^\r\n]")) # skip comment
3268
3372
  return lhs + c + rhs + self.magic_interpret(rest)
3269
3373
 
3270
- lhs += c # store in lhs; no more processing
3374
+ lhs += c # store in lhs; no more processing
3271
3375
  return lhs
3272
-
3273
- def on_deleted(self, shell):
3376
+
3377
+ def on_shell_deleted(self, shell):
3274
3378
  """Called before shell:self is killed.
3275
3379
  Delete target shell to prevent referencing the dead shell.
3276
3380
  """
@@ -3278,34 +3382,36 @@ class Nautilus(EditorInterface, Shell):
3278
3382
  obj = self.target
3279
3383
  try:
3280
3384
  if not obj.shell:
3281
- del obj.shell # delete the facade <wx.py.shell.ShellFacade>
3385
+ del obj.shell # delete the facade <wx.py.shell.ShellFacade>
3282
3386
  except AttributeError:
3283
3387
  pass
3284
3388
  wx.CallAfter(_del)
3285
-
3286
- def on_activated(self, shell):
3287
- """Called when the shell:self is activated.
3288
- Reset localvars assigned for the shell target.
3389
+
3390
+ def on_shell_activate(self, evt):
3391
+ """Called when the shell (self) is activated.
3392
+ Reset localvars assigned for the shell target. cf. target.setter.
3289
3393
  """
3290
3394
  self.trace_position()
3291
3395
  obj = self.target
3292
3396
  try:
3293
3397
  obj.self = obj
3294
3398
  obj.this = inspect.getmodule(obj)
3295
- obj.shell = self # overwrite the facade <wx.py.shell.ShellFacade>
3399
+ obj.shell = self # Overwrite the facade <wx.py.shell.ShellFacade>
3296
3400
  except AttributeError:
3297
3401
  pass
3402
+ self.handler('shell_activated', self)
3298
3403
  self.parent.handler('title_window', obj)
3299
-
3300
- def on_inactivated(self, shell):
3301
- """Called when shell:self is inactivated.
3404
+
3405
+ def on_shell_inactivate(self, evt):
3406
+ """Called when the shell (self) is inactivated.
3302
3407
  Remove target localvars assigned for the shell target.
3303
3408
  """
3304
3409
  if self.AutoCompActive():
3305
3410
  self.AutoCompCancel()
3306
3411
  if self.CallTipActive():
3307
3412
  self.CallTipCancel()
3308
-
3413
+ self.handler('shell_inactivated', self)
3414
+
3309
3415
  def on_text_input(self, text):
3310
3416
  """Called when [Enter] text (before push).
3311
3417
  Mark points, reset history point, etc.
@@ -3316,7 +3422,7 @@ class Nautilus(EditorInterface, Shell):
3316
3422
  if text.rstrip():
3317
3423
  self.__eolc_mark = self.eolc
3318
3424
  self.historyIndex = -1
3319
-
3425
+
3320
3426
  def on_text_output(self, text):
3321
3427
  """Called when [Enter] text (after push).
3322
3428
  Set markers at the last command line.
@@ -3325,28 +3431,28 @@ class Nautilus(EditorInterface, Shell):
3325
3431
  Argument `text` is raw output:str with no magic cast.
3326
3432
  """
3327
3433
  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
3434
+ err = re.findall(py_trace_re, text, re.M)
3435
+ self.add_marker(ln, 1 if not err else 2) # 1:white-arrow 2:red-arrow
3330
3436
  return (not err)
3331
-
3332
- def on_interp_error(self, e):
3437
+
3438
+ def on_interp_error(self, value):
3333
3439
  ln = self.LineFromPosition(self.bolc)
3334
- self.red_pointer = ln + e.lineno - 1
3335
-
3440
+ self.red_pointer = ln + value.lineno - 1
3441
+
3336
3442
  ## --------------------------------
3337
- ## Attributes of the shell
3443
+ ## Attributes of the shell.
3338
3444
  ## --------------------------------
3339
-
3445
+
3340
3446
  @property
3341
3447
  def bolc(self):
3342
- "Beginning of command-line."
3448
+ """Beginning of command-line."""
3343
3449
  return self.promptPosEnd
3344
-
3450
+
3345
3451
  @property
3346
3452
  def eolc(self):
3347
- "End of command-line."
3453
+ """End of command-line."""
3348
3454
  return self.TextLength
3349
-
3455
+
3350
3456
  @property
3351
3457
  def bol(self):
3352
3458
  """Beginning of line (override) excluding prompt."""
@@ -3356,30 +3462,35 @@ class Nautilus(EditorInterface, Shell):
3356
3462
  lp -= len(ps)
3357
3463
  break
3358
3464
  return self.cpos - lp
3359
-
3465
+
3360
3466
  @property
3361
3467
  def cmdline(self):
3362
3468
  """Full multi-line command in the current prompt."""
3363
3469
  return self.GetTextRange(self.bolc, self.eolc)
3364
-
3470
+
3365
3471
  ## cf. getCommand() -> caret-line that starts with a prompt
3366
3472
  ## 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):
3473
+ ## [BUG ver 4.1.1] Don't use for current prompt --> Fixed in wx ver 4.2.0.
3474
+
3475
+ def getMultilineCommand(self, rstrip=True):
3370
3476
  """Extract a multi-line command which starts with a prompt.
3371
3477
 
3372
3478
  (override) Don't remove trailing ps2 + spaces.
3373
3479
  Don't invoke ``GotoLine``.
3374
3480
  """
3375
- region = self.get_region(self.cline)
3481
+ region = self.get_command_region(self.cline)
3376
3482
  if region:
3377
3483
  p, q = (self.PositionFromLine(x) for x in region)
3378
3484
  p += len(sys.ps1)
3379
- return self.GetTextRange(p, q)
3485
+ command = self.GetTextRange(p, q).rstrip(os.linesep) # remove the last cr/lf
3486
+ if rstrip:
3487
+ command = command.replace(os.linesep + sys.ps2, '\n')
3488
+ command = command.rstrip()
3489
+ command = command.replace('\n', os.linesep + sys.ps2)
3490
+ return command
3380
3491
  return ''
3381
-
3382
- def get_region(self, line):
3492
+
3493
+ def get_command_region(self, line):
3383
3494
  """Line numbers of prompt head and tail containing the line."""
3384
3495
  lc = line
3385
3496
  le = lc + 1
@@ -3388,7 +3499,7 @@ class Nautilus(EditorInterface, Shell):
3388
3499
  if not text.startswith(sys.ps2):
3389
3500
  break
3390
3501
  lc -= 1
3391
- if not text.startswith(sys.ps1): # bad region
3502
+ if not text.startswith(sys.ps1): # bad region
3392
3503
  return None
3393
3504
  while le < self.LineCount:
3394
3505
  text = self.GetLine(le)
@@ -3396,11 +3507,11 @@ class Nautilus(EditorInterface, Shell):
3396
3507
  break
3397
3508
  le += 1
3398
3509
  return lc, le
3399
-
3510
+
3400
3511
  ## --------------------------------
3401
- ## Execution methods of the shell
3512
+ ## Execution methods of the shell.
3402
3513
  ## --------------------------------
3403
-
3514
+
3404
3515
  def push(self, command, **kwargs):
3405
3516
  """Send command to the interpreter for execution.
3406
3517
 
@@ -3408,7 +3519,7 @@ class Nautilus(EditorInterface, Shell):
3408
3519
  """
3409
3520
  self.on_text_input(command)
3410
3521
  Shell.push(self, command, **kwargs)
3411
-
3522
+
3412
3523
  def addHistory(self, command):
3413
3524
  """Add command to the command history.
3414
3525
 
@@ -3418,7 +3529,7 @@ class Nautilus(EditorInterface, Shell):
3418
3529
  if not command:
3419
3530
  return
3420
3531
 
3421
- ## この段階では push された直後で,次のようになっている
3532
+ ## この段階では push された直後で,次のようになっている.
3422
3533
  ## bolc : beginning of command-line
3423
3534
  ## eolc : end of the output-buffer
3424
3535
  try:
@@ -3426,7 +3537,7 @@ class Nautilus(EditorInterface, Shell):
3426
3537
  output = self.GetTextRange(self.__eolc_mark, self.eolc)
3427
3538
 
3428
3539
  input = self.regulate_cmd(input)
3429
- Shell.addHistory(self, input) # => self.history
3540
+ Shell.addHistory(self, input) # => self.history
3430
3541
 
3431
3542
  noerr = self.on_text_output(output)
3432
3543
  if noerr:
@@ -3435,18 +3546,30 @@ class Nautilus(EditorInterface, Shell):
3435
3546
  command = self.fixLineEndings(command)
3436
3547
  self.parent.handler('add_log', command + os.linesep, noerr)
3437
3548
  except AttributeError:
3438
- ## execStartupScript 実行時は出力先 (owner) が存在しない
3439
- ## shell.__init__ よりも先に実行される
3549
+ ## execStartupScript 実行時は出力先 (owner) が存在しない.
3550
+ ## shell.__init__ よりも先に実行される.
3440
3551
  pass
3441
-
3552
+
3553
+ def setBuiltinKeywords(self):
3554
+ """Create pseudo keywords as part of builtins.
3555
+
3556
+ (override) Don't add `close`, `exit` and `quit` to builtins.
3557
+ """
3558
+ from wx.py.path import ls, cd, pwd, sx
3559
+ builtins.cd = cd
3560
+ builtins.ls = ls
3561
+ builtins.pwd = pwd
3562
+ builtins.sx = sx
3563
+
3442
3564
  def execStartupScript(self, su):
3443
3565
  """Execute the user's PYTHONSTARTUP script if they have one.
3444
3566
 
3445
3567
  (override) Add globals when executing su:startupScript.
3446
- Fix history point.
3568
+ Don't add '_f' to globals when executing su:startupScript.
3569
+ Don't add intro text in the history.
3447
3570
  """
3448
- keys = set(self.locals.keys()) # check for locals map changes
3449
- self.promptPosEnd = self.TextLength # fix history point
3571
+ keys = set(self.locals.keys()) # to check for locals map changes.
3572
+ self.promptPosEnd = self.TextLength # Fix history point
3450
3573
  if su and os.path.isfile(su):
3451
3574
  self.push("print('Startup script executed:', {0!r})\n".format(su))
3452
3575
  self.push("with open({0!r}) as _f: exec(_f.read())\n".format(su))
@@ -3456,31 +3579,31 @@ class Nautilus(EditorInterface, Shell):
3456
3579
  self.push("")
3457
3580
  self.interp.startupScript = None
3458
3581
  self.__globals = {k: self.locals[k] for k in (self.locals.keys() - keys)}
3459
-
3582
+
3460
3583
  def Paste(self, rectangle=False):
3461
3584
  """Replace selection with clipboard contents.
3462
3585
 
3463
3586
  (override) Remove ps1 and ps2 from the multi-line command to paste.
3464
3587
  Add offset in paste-rectangle mode.
3465
- Don't relplace the last crlf to ps.
3588
+ Don't relplace the last cr/lf to ps.
3466
3589
  """
3467
3590
  if self.CanPaste() and wx.TheClipboard.Open():
3468
3591
  data = wx.TextDataObject()
3469
3592
  if wx.TheClipboard.GetData(data):
3470
3593
  command = data.GetText()
3471
- ## command = command.rstrip()
3594
+ # command = command.rstrip()
3472
3595
  command = self.fixLineEndings(command)
3473
3596
  command = self.regulate_cmd(command)
3474
3597
  ps = sys.ps2
3475
3598
  _text, lp = self.CurLine
3476
3599
  if rectangle:
3477
- ps += ' ' * (lp - len(ps)) # add offset
3600
+ ps += ' ' * (lp - len(ps)) # add offset
3478
3601
  if lp == 0:
3479
- command = ps + command # paste-line
3602
+ command = ps + command # paste-line
3480
3603
  command = command.replace('\n', os.linesep + ps)
3481
3604
  self.ReplaceSelection(command)
3482
3605
  wx.TheClipboard.Close()
3483
-
3606
+
3484
3607
  def regulate_cmd(self, text):
3485
3608
  """Regulate text to executable command.
3486
3609
 
@@ -3489,53 +3612,53 @@ class Nautilus(EditorInterface, Shell):
3489
3612
  The eol-code (cr/lf) is not fixed.
3490
3613
  Call self.fixLineEndings in advance as necessary.
3491
3614
  """
3492
- text = self.lstripPrompt(text) # strip a leading prompt
3615
+ text = self.lstripPrompt(text) # strip a leading prompt
3493
3616
  lf = '\n'
3494
3617
  return (text.replace(os.linesep + sys.ps1, lf)
3495
3618
  .replace(os.linesep + sys.ps2, lf)
3496
3619
  .replace(os.linesep, lf))
3497
-
3620
+
3498
3621
  def clear(self):
3499
3622
  """Delete all text (override) put new prompt."""
3500
3623
  self.ClearAll()
3501
3624
  self.promptPosStart = 0
3502
3625
  self.promptPosEnd = 0
3503
3626
  self.prompt()
3504
-
3627
+
3505
3628
  def write(self, text, pos=None):
3506
3629
  """Display text in the shell.
3507
3630
 
3508
- (override) Append text if it is writable at the position.
3631
+ (override) Append text if it is writable at the given position.
3509
3632
  """
3510
3633
  if pos is not None:
3511
3634
  if pos < 0:
3512
- pos += self.TextLength + 1 # Counts end-of-buffer (+1:\0)
3635
+ pos += self.TextLength + 1 # Counts end-of-buffer (+1:\0)
3513
3636
  self.goto_char(pos)
3514
3637
  if self.CanEdit():
3515
- Shell.write(self, text) # => AddText
3516
-
3638
+ Shell.write(self, text) # => AddText
3639
+
3517
3640
  ## input = classmethod(Shell.ask)
3518
-
3641
+
3519
3642
  def info(self, obj):
3520
3643
  """Short information."""
3521
3644
  doc = inspect.getdoc(obj)\
3522
3645
  or "No information about {}".format(obj)
3523
- self.parent.handler('add_help', doc) or print(doc)
3524
-
3646
+ self.parent.handler('add_help', doc, typename(obj)) or print(doc)
3647
+
3525
3648
  def help(self, obj):
3526
3649
  """Full description."""
3527
3650
  doc = pydoc.plain(pydoc.render_doc(obj))\
3528
3651
  or "No description about {}".format(obj)
3529
- self.parent.handler('add_help', doc) or print(doc)
3530
-
3652
+ self.parent.handler('add_help', doc, typename(obj)) or print(doc)
3653
+
3531
3654
  def eval(self, text):
3532
3655
  return eval(text, self.globals, self.locals)
3533
-
3656
+
3534
3657
  def exec(self, text):
3535
3658
  exec(text, self.globals, self.locals)
3536
3659
  dispatcher.send(signal='Interpreter.push',
3537
3660
  sender=self, command=None, more=False)
3538
-
3661
+
3539
3662
  def exec_cmdline(self):
3540
3663
  """Execute command-line directly.
3541
3664
 
@@ -3556,14 +3679,14 @@ class Nautilus(EditorInterface, Shell):
3556
3679
  continue
3557
3680
  line = self.lstripPrompt(lines)
3558
3681
  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
3682
+ if (lstr and lstr == line # no indent
3683
+ and not lstr.startswith('#') # no comment
3684
+ and not re.match(py_outdent_re, lstr)): # no outdent pattern
3562
3685
  if cmd:
3563
- commands.append(cmd) # Add stacked commands to the list
3686
+ commands.append(cmd) # Add stacked commands to the list
3564
3687
  cmd = line
3565
3688
  else:
3566
- cmd += lines # multi-line command
3689
+ cmd += lines # multi-line command
3567
3690
  lines = ''
3568
3691
  commands.append(cmd + lines)
3569
3692
 
@@ -3587,56 +3710,37 @@ class Nautilus(EditorInterface, Shell):
3587
3710
  for cmd in commands:
3588
3711
  self.write(cmd)
3589
3712
  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.
3713
+
3714
+ def eval_line(self):
3715
+ """Evaluate the selected word or line and show calltips."""
3716
+ if self.CallTipActive():
3717
+ self.CallTipCancel()
3597
3718
 
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
3719
  def _gen_text():
3610
3720
  text = self.SelectedText
3611
3721
  if text:
3612
3722
  yield text
3613
3723
  else:
3614
- yield self.getCommand() # self.line_at_caret
3724
+ yield self.line_at_caret
3615
3725
  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
3726
 
3623
- status = "No words"
3624
- for text in self.gen_text_at_caret():
3727
+ for text in _gen_text():
3625
3728
  tokens = split_words(text)
3626
3729
  try:
3627
3730
  cmd = self.magic_interpret(tokens)
3628
3731
  cmd = self.regulate_cmd(cmd)
3629
3732
  obj = self.eval(cmd)
3630
3733
  except Exception as e:
3631
- status = "- {} : {!r}".format(e, text)
3734
+ self.message(e)
3632
3735
  else:
3633
3736
  self.CallTipShow(self.cpos, pformat(obj))
3634
3737
  self.message(cmd)
3635
3738
  return
3636
- self.message(status)
3637
-
3638
- def exec_region(self, evt):
3639
- """Execute the the selected region."""
3739
+ if not text:
3740
+ self.message("No words")
3741
+
3742
+ def exec_region(self):
3743
+ """Execute a region of code."""
3640
3744
  if self.CallTipActive():
3641
3745
  self.CallTipCancel()
3642
3746
 
@@ -3651,10 +3755,10 @@ class Nautilus(EditorInterface, Shell):
3651
3755
  self.exec(code)
3652
3756
  except Exception as e:
3653
3757
  msg = traceback.format_exc()
3654
- err = re.findall(py_error_re, msg, re.M)
3758
+ err = re.findall(py_trace_re, msg, re.M)
3655
3759
  lines = [int(ln) for fn, ln in err if fn == filename]
3656
3760
  if lines:
3657
- region = self.get_region(self.cline)
3761
+ region = self.get_command_region(self.cline)
3658
3762
  lx = region[0] + lines[-1] - 1
3659
3763
  self.red_pointer = lx
3660
3764
  self.message(e)