mwxlib 1.3.11__py3-none-any.whl → 1.4.5__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.

Potentially problematic release.


This version of mwxlib might be problematic. Click here for more details.

mwx/framework.py CHANGED
@@ -1,7 +1,7 @@
1
1
  #! python3
2
2
  """mwxlib framework.
3
3
  """
4
- __version__ = "1.3.11"
4
+ __version__ = "1.4.5"
5
5
  __author__ = "Kazuya O'moto <komoto@jeol.co.jp>"
6
6
 
7
7
  from contextlib import contextmanager
@@ -1420,7 +1420,7 @@ class ShellFrame(MiniFrame):
1420
1420
 
1421
1421
  def OnClose(self, evt):
1422
1422
  if self.debugger.busy:
1423
- if wx.MessageBox( # Confirm debugger close.
1423
+ if wx.MessageBox( # Confirm closing the debugger.
1424
1424
  "The debugger is running.\n\n"
1425
1425
  "Enter [q]uit to exit before closing.\n"
1426
1426
  "Continue closing?",
@@ -1441,7 +1441,7 @@ class ShellFrame(MiniFrame):
1441
1441
  if buf.need_buffer_save:
1442
1442
  self.popup_window(book)
1443
1443
  buf.SetFocus()
1444
- if wx.MessageBox( # Confirm close.
1444
+ if wx.MessageBox( # Confirm closing the buffer.
1445
1445
  "You are closing unsaved content.\n\n"
1446
1446
  "Changes to the content will be discarded.\n"
1447
1447
  "Continue closing?",
@@ -1460,7 +1460,8 @@ class ShellFrame(MiniFrame):
1460
1460
  if not evt.Active:
1461
1461
  ## Reset autoload when active focus going outside.
1462
1462
  self.__autoload = True
1463
- elif evt.GetActivationReason() == evt.Reason_Mouse and self.__autoload:
1463
+ ## elif evt.GetActivationReason() == evt.Reason_Mouse and self.__autoload:
1464
+ elif self.__autoload:
1464
1465
  ## Check all buffers that need to be loaded.
1465
1466
  verbose = 1
1466
1467
  for book in self.get_all_editors():
mwx/graphman.py CHANGED
@@ -152,32 +152,38 @@ class Thread:
152
152
  raise KeyboardInterrupt("terminated by user")
153
153
  return True
154
154
 
155
- def pause(self, msg="Pausing..."):
155
+ def pause(self, msg=None, caption=wx.MessageBoxCaptionStr):
156
156
  """Pause the thread.
157
+ Confirm whether to terminate the thread.
157
158
 
158
- Use ``check`` method where you want to pause.
159
+ Returns:
160
+ True : [OK] if terminating.
161
+ False : [CANCEL] otherwise.
159
162
 
160
163
  Note:
164
+ Use ``check`` method where you want to pause.
161
165
  Even after the message dialog is displayed, the thread
162
166
  does not suspend until check (or event.wait) is called.
163
167
  """
164
168
  if not self.running:
165
169
  return None
166
- if '\n\n' not in msg:
167
- msg += '\n\n'
170
+ if not msg:
171
+ msg = ("The thread is running.\n\n"
172
+ "Do you want to terminate the thread?")
168
173
  try:
169
174
  self.event.clear() # suspend
170
- if wx.MessageBox( # Confirm terminatation.
171
- msg + "Do you want to terminate the process?",
175
+ if wx.MessageBox( # Confirm closing the thread.
176
+ msg, caption,
172
177
  style=wx.OK|wx.CANCEL|wx.ICON_WARNING) == wx.OK:
173
178
  self.Stop()
174
- return False
175
- return True
179
+ return True
180
+ return False
176
181
  finally:
177
182
  self.event.set() # resume
178
183
 
179
184
  def Start(self, f, *args, **kwargs):
180
- """Start the thread to run the specified function."""
185
+ """Start the thread to run the specified function.
186
+ """
181
187
  @wraps(f)
182
188
  def _f(*v, **kw):
183
189
  try:
@@ -186,12 +192,12 @@ class Thread:
186
192
  except BdbQuit:
187
193
  pass
188
194
  except KeyboardInterrupt as e:
189
- print("- Thread:execution stopped:", e)
195
+ print("- Thread execution stopped:", e)
190
196
  except AssertionError as e:
191
- print("- Thread:execution failed:", e)
197
+ print("- Thread execution failed:", e)
192
198
  except Exception as e:
193
199
  traceback.print_exc()
194
- print("- Thread:exception:", e)
200
+ print("- Thread exception:", e)
195
201
  self.handler('thread_error', self)
196
202
  finally:
197
203
  self.active = 0
@@ -213,7 +219,10 @@ class Thread:
213
219
  def Stop(self):
214
220
  """Stop the thread.
215
221
 
216
- Use ``check`` method where you want to quit.
222
+ Note:
223
+ Use ``check`` method where you want to stop.
224
+ Even after the message dialog is displayed, the thread
225
+ does not suspend until check (or event.wait) is called.
217
226
  """
218
227
  def _stop():
219
228
  with wx.BusyInfo("One moment please, "
@@ -428,7 +437,7 @@ class LayerInterface(CtrlInterface):
428
437
  def OnDestroy(self, evt):
429
438
  if evt.EventObject is self:
430
439
  if self.thread and self.thread.active:
431
- self.thread.active = 0
440
+ ## self.thread.active = 0
432
441
  self.thread.Stop()
433
442
  del self.Arts
434
443
  evt.Skip()
@@ -873,31 +882,28 @@ class Frame(mwx.Frame):
873
882
  elif ret == wx.ID_CANCEL:
874
883
  evt.Veto()
875
884
  return
876
- for name in self.plugins:
877
- plug = self.get_plug(name)
878
- if plug.thread and plug.thread.active:
879
- if wx.MessageBox( # Confirm thread close.
880
- "The thread is running.\n\n"
881
- "Enter [q]uit to exit before closing.\n"
882
- "Continue closing?",
883
- "Close {!r}".format(plug.Name),
884
- style=wx.YES_NO|wx.ICON_INFORMATION) != wx.YES:
885
- self.message("The close has been canceled.")
886
- evt.Veto()
887
- return
888
- self.Quit()
889
- break
890
- for frame in self.graph.get_all_frames():
891
- if frame.pathname is None:
892
- if wx.MessageBox( # Confirm close.
893
- "You are closing unsaved frame.\n\n"
894
- "Continue closing?",
895
- "Close {!r}".format(frame.name),
896
- style=wx.YES_NO|wx.ICON_INFORMATION) != wx.YES:
897
- self.message("The close has been canceled.")
898
- evt.Veto()
899
- return
900
- break
885
+ n = sum(bool(plug.thread and plug.thread.active)
886
+ for plug in (self.get_plug(name) for name in self.plugins))
887
+ if n:
888
+ s = 's' if n > 1 else ''
889
+ if wx.MessageBox( # Confirm closing the thread.
890
+ f"Currently running {n} thread{s}.\n\n"
891
+ "Continue closing?",
892
+ style=wx.YES_NO|wx.ICON_INFORMATION) != wx.YES:
893
+ self.message("The close has been canceled.")
894
+ evt.Veto()
895
+ return
896
+ self.Quit()
897
+ n = sum(frame.pathname is None for frame in self.graph.all_frames)
898
+ if n:
899
+ s = 's' if n > 1 else ''
900
+ if wx.MessageBox( # Confirm closing the frame.
901
+ f"You are closing {n} unsaved frame{s}.\n\n"
902
+ "Continue closing?",
903
+ style=wx.YES_NO|wx.ICON_INFORMATION) != wx.YES:
904
+ self.message("The close has been canceled.")
905
+ evt.Veto()
906
+ return
901
907
  evt.Skip()
902
908
 
903
909
  def Destroy(self):
@@ -1377,7 +1383,7 @@ class Frame(mwx.Frame):
1377
1383
  plug = self.get_plug(name)
1378
1384
  thread = plug.thread # Note: thread can be None or shared.
1379
1385
  if thread and thread.active:
1380
- thread.active = 0
1386
+ ## thread.active = 0
1381
1387
  thread.Stop()
1382
1388
 
1383
1389
  ## --------------------------------
@@ -1426,7 +1432,7 @@ class Frame(mwx.Frame):
1426
1432
  """
1427
1433
  view = self.selected_view
1428
1434
  if not frames:
1429
- frames = view.get_all_frames()
1435
+ frames = view.all_frames
1430
1436
  if not frames:
1431
1437
  return None
1432
1438
 
@@ -1692,7 +1698,7 @@ class Frame(mwx.Frame):
1692
1698
  def save_buffers_as_tiffs(self, path=None, frames=None):
1693
1699
  """Save buffers to a file as a multi-page tiff."""
1694
1700
  if not frames:
1695
- frames = self.selected_view.get_all_frames()
1701
+ frames = self.selected_view.all_frames
1696
1702
  if not frames:
1697
1703
  return None
1698
1704
 
@@ -1811,7 +1817,7 @@ class Frame(mwx.Frame):
1811
1817
 
1812
1818
  def _save(view):
1813
1819
  name = view.Name
1814
- paths = [x.pathname for x in view.get_all_frames() if x.pathname]
1820
+ paths = [x.pathname for x in view.all_frames if x.pathname]
1815
1821
  o.write(f"self.{name}.unit = {view.unit:g}\n")
1816
1822
  o.write(f"self.load_frame({paths!r}, self.{name})\n")
1817
1823
  try:
mwx/matplot2g.py CHANGED
@@ -783,9 +783,14 @@ class GraphPlot(MatplotPanel):
783
783
  else:
784
784
  return self.__Arts[j] # j:int -> frame
785
785
 
786
- def get_all_frames(self):
786
+ def get_all_frames(self, j=None):
787
787
  """List of arts <matplotlib.image.AxesImage>."""
788
- return self.__Arts
788
+ if j is None:
789
+ yield from self.__Arts
790
+ elif isinstance(j, str):
791
+ yield from (art for art in self.__Arts if j in art.name)
792
+ elif isinstance(j, np.ndarray):
793
+ yield from (art for art in self.__Arts if j is art.buffer)
789
794
 
790
795
  ## --------------------------------
791
796
  ## Property of frame / drawer
@@ -798,7 +803,7 @@ class GraphPlot(MatplotPanel):
798
803
  score_percentile = 0.005
799
804
 
800
805
  @property
801
- def all_frames(self): # (deprecated) for backward compatibility
806
+ def all_frames(self):
802
807
  """List of arts <matplotlib.image.AxesImage>."""
803
808
  return self.__Arts
804
809
 
mwx/nutshell.py CHANGED
@@ -28,10 +28,23 @@ from wx.py.editwindow import EditWindow
28
28
 
29
29
  from .utilus import funcall as _F
30
30
  from .utilus import ignore, typename
31
- from .utilus import split_words, split_paren, split_tokens, find_modules
31
+ from .utilus import split_words, split_parts, split_tokens, find_modules
32
32
  from .framework import CtrlInterface, AuiNotebook, Menu
33
33
 
34
34
 
35
+ ## Monkey-patch for wx.py.introspect.getRoot with terminator '('.
36
+ getRoot = introspect.getRoot
37
+ def _getRoot(command, terminator=None):
38
+ """Return the rightmost root portion of an arbitrary Python command."""
39
+ if terminator == '(':
40
+ words = split_words(command, reverse=1)
41
+ for word in words:
42
+ if word == terminator:
43
+ return next(words, '')
44
+ return getRoot(command, terminator) # original
45
+ introspect.getRoot = _getRoot
46
+
47
+
35
48
  ## URL pattern (flag = re.M | re.A)
36
49
  ## url_re = r"https?://[\w/:%#$&?()~.=+-]+"
37
50
  url_re = r"https?://[\w/:%#$&?!@~.,;=+-]+" # excluding ()
@@ -210,8 +223,8 @@ class AutoCompInterfaceMixin:
210
223
  dispatcher.send(signal='Shell.calltip', sender=self, calltip=tip)
211
224
  p = self.cpos
212
225
  if argspec and insertcalltip:
213
- self.AddText(argspec + ')') # 挿入後のカーソル位置は変化しない
214
- self.SetSelection(self.cpos, p) # selection back
226
+ self.AddText(argspec + ')')
227
+ self.cpos = p # selection backward to the point
215
228
  if tip:
216
229
  ## In case there isn't enough room, only go back to bol fallback.
217
230
  tippos = max(self.bol, p - len(name) - 1)
@@ -269,7 +282,7 @@ class AutoCompInterfaceMixin:
269
282
  self.anchor = q
270
283
  with self.off_undocollection():
271
284
  self.ReplaceSelection(word[n:])
272
- self.cpos = p # backward selection to the point
285
+ self.cpos = p # selection backward to the point
273
286
  self.__comp_ind = j
274
287
  except IndexError:
275
288
  self.message("No completion words")
@@ -641,8 +654,8 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
641
654
  self.IndicatorSetStyle(11, stc.STC_INDIC_STRAIGHTBOX)
642
655
  self.IndicatorSetForeground(11, "yellow")
643
656
  self.IndicatorSetUnder(11, True)
644
- self.IndicatorSetAlpha(11, 0xe8)
645
- self.IndicatorSetOutlineAlpha(11, 0)
657
+ ## self.IndicatorSetAlpha(11, 0xe8)
658
+ ## self.IndicatorSetOutlineAlpha(11, 0)
646
659
 
647
660
  self.IndicatorSetStyle(2, stc.STC_INDIC_DOTS)
648
661
  self.IndicatorSetForeground(2, "light gray")
@@ -863,7 +876,7 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
863
876
  return sty
864
877
 
865
878
  def get_char(self, pos):
866
- """Returns the character at the position."""
879
+ """Returns the character at the given position."""
867
880
  return chr(self.GetCharAt(pos))
868
881
 
869
882
  def get_text(self, start, end):
@@ -944,10 +957,12 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
944
957
  return topic
945
958
  with self.save_excursion():
946
959
  p = q = self.cpos
947
- if self.get_char(p-1).isalnum():
960
+ ## if self.get_char(p-1).isidentifier():
961
+ if self.GetTextRange(self.PositionBefore(p), p).isidentifier():
948
962
  self.WordLeft()
949
963
  p = self.cpos
950
- if self.get_char(q).isalnum():
964
+ ## if self.get_char(q).isidentifier():
965
+ if self.GetTextRange(q, self.PositionAfter(q)).isidentifier():
951
966
  self.WordRightEnd()
952
967
  q = self.cpos
953
968
  return self.GetTextRange(p, q)
@@ -1376,32 +1391,34 @@ class EditorInterface(AutoCompInterfaceMixin, CtrlInterface):
1376
1391
  def grep(self, pattern, flags=re.M):
1377
1392
  yield from re.finditer(pattern.encode(), self.TextRaw, flags)
1378
1393
 
1379
- def search_text(self, text):
1380
- """Yields raw-positions where `text` is found."""
1381
- word = text.encode()
1382
- raw = self.TextRaw
1394
+ def search_text(self, text, mode=True):
1395
+ """Yields positions where `text` is found.
1396
+ If mode is True, search by word; otherwise, search by string.
1397
+ """
1398
+ text = text.encode()
1383
1399
  pos = -1
1400
+ p = re.compile(r"[a-zA-Z0-9_]")
1384
1401
  while 1:
1385
- pos = raw.find(word, pos+1)
1402
+ pos = self.TextRaw.find(text, pos+1)
1386
1403
  if pos < 0:
1387
1404
  break
1405
+ if mode and p.search(self.get_char(pos-1) + self.get_char(pos+len(text))):
1406
+ continue
1388
1407
  yield pos
1389
1408
 
1390
- def filter_text(self, text=None):
1409
+ def filter_text(self):
1391
1410
  """Show indicators for the selected text."""
1392
1411
  self.__itextlines = []
1393
1412
  for i in (10, 11,):
1394
1413
  self.SetIndicatorCurrent(i)
1395
1414
  self.IndicatorClearRange(0, self.TextLength)
1396
- if text is None:
1397
- text = self.topic_at_caret
1415
+ text = self.topic_at_caret
1398
1416
  if not text:
1399
1417
  self.message("No words")
1400
1418
  return
1401
-
1402
1419
  lw = len(text.encode()) # for multi-byte string
1403
1420
  lines = []
1404
- for p in self.search_text(text):
1421
+ for p in self.search_text(text, mode=(not self.SelectedText)):
1405
1422
  lines.append(self.LineFromPosition(p))
1406
1423
  for i in (10, 11,):
1407
1424
  self.SetIndicatorCurrent(i)
@@ -1833,12 +1850,8 @@ class Buffer(EditorInterface, EditWindow):
1833
1850
  self.message("")
1834
1851
 
1835
1852
  def clear_autocomp(evt):
1836
- ## """Clear autocomp, selection, and message."""
1837
1853
  if self.AutoCompActive():
1838
1854
  self.AutoCompCancel()
1839
- if self.CanEdit():
1840
- with self.off_undocollection():
1841
- self.ReplaceSelection("")
1842
1855
  self.message("")
1843
1856
 
1844
1857
  def fork(evt):
@@ -1882,7 +1895,7 @@ class Buffer(EditorInterface, EditWindow):
1882
1895
  '* pressed' : (0, clear_autocomp, fork),
1883
1896
  'tab pressed' : (0, clear, skip),
1884
1897
  'enter pressed' : (0, clear, skip),
1885
- 'escape pressed' : (0, clear_autocomp),
1898
+ 'escape pressed' : (0, clear, skip),
1886
1899
  'up pressed' : (2, skip, self.on_completion_backward),
1887
1900
  'down pressed' : (2, skip, self.on_completion_forward),
1888
1901
  '*left pressed' : (2, skip),
@@ -1909,7 +1922,7 @@ class Buffer(EditorInterface, EditWindow):
1909
1922
  '* pressed' : (0, clear_autocomp, fork),
1910
1923
  'tab pressed' : (0, clear, skip),
1911
1924
  'enter pressed' : (0, clear, skip),
1912
- 'escape pressed' : (0, clear_autocomp),
1925
+ 'escape pressed' : (0, clear, skip),
1913
1926
  'up pressed' : (3, skip, self.on_completion_backward),
1914
1927
  'down pressed' : (3, skip, self.on_completion_forward),
1915
1928
  '*left pressed' : (3, skip),
@@ -2099,8 +2112,15 @@ class Buffer(EditorInterface, EditWindow):
2099
2112
  if self.CallTipActive():
2100
2113
  self.CallTipCancel()
2101
2114
 
2102
- text = self.SelectedText or self.expr_at_caret
2103
- if text:
2115
+ def _gen_text():
2116
+ text = self.SelectedText
2117
+ if text:
2118
+ yield text
2119
+ else:
2120
+ yield self.line_at_caret
2121
+ yield self.expr_at_caret
2122
+
2123
+ for text in _gen_text():
2104
2124
  try:
2105
2125
  obj = eval(text, self.globals, self.locals)
2106
2126
  except Exception as e:
@@ -2108,7 +2128,8 @@ class Buffer(EditorInterface, EditWindow):
2108
2128
  else:
2109
2129
  self.CallTipShow(self.cpos, pformat(obj))
2110
2130
  self.message(text)
2111
- else:
2131
+ return
2132
+ if not text:
2112
2133
  self.message("No words")
2113
2134
 
2114
2135
  def exec_region(self):
@@ -2216,7 +2237,7 @@ class EditorBook(AuiNotebook, CtrlInterface):
2216
2237
  def OnPageClose(self, evt): #<wx._aui.AuiNotebookEvent>
2217
2238
  buf = self.GetPage(evt.Selection)
2218
2239
  if buf.need_buffer_save:
2219
- if wx.MessageBox( # Confirm close.
2240
+ if wx.MessageBox( # Confirm closing the buffer.
2220
2241
  "You are closing unsaved content.\n\n"
2221
2242
  "The changes will be discarded.\n"
2222
2243
  "Continue closing?",
@@ -2414,7 +2435,7 @@ class EditorBook(AuiNotebook, CtrlInterface):
2414
2435
  if not buf:
2415
2436
  buf = self.create_buffer("*temp file*")
2416
2437
  elif buf.need_buffer_save and verbose:
2417
- if wx.MessageBox( # Confirm load.
2438
+ if wx.MessageBox( # Confirm loading the buffer.
2418
2439
  "You are leaving unsaved content.\n\n"
2419
2440
  "The changes will be discarded.\n"
2420
2441
  "Continue loading?",
@@ -2468,7 +2489,7 @@ class EditorBook(AuiNotebook, CtrlInterface):
2468
2489
  buf = buf or self.buffer
2469
2490
  if buf.need_buffer_load and verbose:
2470
2491
  self.swap_buffer(buf)
2471
- if wx.MessageBox( # Confirm save.
2492
+ if wx.MessageBox( # Confirm saving the buffer.
2472
2493
  "The file has been modified externally.\n\n"
2473
2494
  "The contents of the file will be overwritten.\n"
2474
2495
  "Continue saving?",
@@ -2532,7 +2553,7 @@ class EditorBook(AuiNotebook, CtrlInterface):
2532
2553
  """Confirm the close with the dialog."""
2533
2554
  buf = buf or self.buffer
2534
2555
  if buf.need_buffer_save:
2535
- if wx.MessageBox( # Confirm close.
2556
+ if wx.MessageBox( # Confirm closing the buffer.
2536
2557
  "You are closing unsaved content.\n\n"
2537
2558
  "The changes will be discarded.\n"
2538
2559
  "Continue closing?",
@@ -2545,7 +2566,7 @@ class EditorBook(AuiNotebook, CtrlInterface):
2545
2566
  def kill_all_buffers(self):
2546
2567
  for buf in self.get_all_buffers():
2547
2568
  if buf.need_buffer_save:
2548
- if wx.MessageBox( # Confirm close.
2569
+ if wx.MessageBox( # Confirm closing the buffer.
2549
2570
  "You are closing unsaved content.\n\n"
2550
2571
  "The changes will be discarded.\n"
2551
2572
  "Continue closing?",
@@ -2610,22 +2631,6 @@ class Interpreter(interpreter.Interpreter):
2610
2631
  self.parent.handler('interp_error', v)
2611
2632
  except AttributeError:
2612
2633
  pass
2613
-
2614
- @ignore(DeprecationWarning)
2615
- def getCallTip(self, command='', *args, **kwargs):
2616
- """Return call tip text for a command.
2617
-
2618
- (override) Ignore DeprecationWarning: for function,
2619
- `formatargspec` is deprecated since Python 3.5.
2620
- (override) Ignore ValueError: no signature found for builtin
2621
- if the unwrapped function is a builtin function.
2622
- """
2623
- ## In 4.2.1, DeprecationWarning was fixed.
2624
- ## In 4.2.2, ValueError was fixed.
2625
- try:
2626
- return interpreter.Interpreter.getCallTip(self, command, *args, **kwargs)
2627
- except ValueError:
2628
- return interpreter.Interpreter.getCallTip(self) # dummy
2629
2634
 
2630
2635
 
2631
2636
  class Nautilus(EditorInterface, Shell):
@@ -2796,12 +2801,8 @@ class Nautilus(EditorInterface, Shell):
2796
2801
  self.message("")
2797
2802
 
2798
2803
  def clear_autocomp(evt):
2799
- ## """Clear autocomp, selection, and message."""
2800
2804
  if self.AutoCompActive():
2801
2805
  self.AutoCompCancel()
2802
- if self.CanEdit():
2803
- with self.off_undocollection():
2804
- self.ReplaceSelection("")
2805
2806
  self.message("")
2806
2807
 
2807
2808
  def fork(evt):
@@ -2900,7 +2901,7 @@ class Nautilus(EditorInterface, Shell):
2900
2901
  '* pressed' : (0, clear_autocomp, fork),
2901
2902
  'tab pressed' : (0, clear, skip),
2902
2903
  'enter pressed' : (0, clear, skip),
2903
- 'escape pressed' : (0, clear_autocomp),
2904
+ 'escape pressed' : (0, clear, skip),
2904
2905
  'up pressed' : (2, skip, self.on_completion_backward),
2905
2906
  'down pressed' : (2, skip, self.on_completion_forward),
2906
2907
  '*left pressed' : (2, skip),
@@ -2928,7 +2929,7 @@ class Nautilus(EditorInterface, Shell):
2928
2929
  '* pressed' : (0, clear_autocomp, fork),
2929
2930
  'tab pressed' : (0, clear, skip),
2930
2931
  'enter pressed' : (0, clear, skip),
2931
- 'escape pressed' : (0, clear_autocomp),
2932
+ 'escape pressed' : (0, clear, skip),
2932
2933
  'up pressed' : (3, skip, self.on_completion_backward),
2933
2934
  'down pressed' : (3, skip, self.on_completion_forward),
2934
2935
  '*left pressed' : (3, skip),
@@ -2956,7 +2957,7 @@ class Nautilus(EditorInterface, Shell):
2956
2957
  '* pressed' : (0, clear_autocomp, fork),
2957
2958
  'tab pressed' : (0, clear, skip),
2958
2959
  'enter pressed' : (0, clear, skip),
2959
- 'escape pressed' : (0, clear_autocomp),
2960
+ 'escape pressed' : (0, clear, skip),
2960
2961
  'up pressed' : (4, skip, self.on_completion_backward),
2961
2962
  'down pressed' : (4, skip, self.on_completion_forward),
2962
2963
  '*left pressed' : (4, skip),
@@ -2984,7 +2985,7 @@ class Nautilus(EditorInterface, Shell):
2984
2985
  '* pressed' : (0, clear_autocomp, fork),
2985
2986
  'tab pressed' : (0, clear, skip),
2986
2987
  'enter pressed' : (0, clear, skip),
2987
- 'escape pressed' : (0, clear_autocomp),
2988
+ 'escape pressed' : (0, clear, skip),
2988
2989
  'up pressed' : (5, skip, self.on_completion_backward),
2989
2990
  'down pressed' : (5, skip, self.on_completion_forward),
2990
2991
  '*left pressed' : (5, skip),
@@ -3135,7 +3136,7 @@ class Nautilus(EditorInterface, Shell):
3135
3136
  rst = self.get_style(p)
3136
3137
  if p == self.bolc:
3137
3138
  self.ReplaceSelection('self') # replace [.] --> [self.]
3138
- elif st in ('nil', 'space', 'op', 'sep', 'lparen'):
3139
+ elif st in ('space', 'sep', 'lparen'):
3139
3140
  self.ReplaceSelection('self')
3140
3141
  elif st not in ('moji', 'word', 'rparen') or rst == 'word':
3141
3142
  self.handler('quit', evt) # don't enter autocomp
@@ -3238,16 +3239,21 @@ class Nautilus(EditorInterface, Shell):
3238
3239
  ## func(a,b,c) @debug --> func,a,b,c @debug
3239
3240
  if rhs in ("debug", "profile", "timeit"):
3240
3241
  if lhs[-1] in ')':
3241
- L, R = split_paren(lhs, reverse=1)
3242
+ R = next(split_parts(lhs, reverse=1))
3243
+ L = lhs[:-len(R)]
3242
3244
  if not L:
3243
3245
  lhs = "{!r}".format(R[1:-1])
3244
- elif R:
3246
+ else:
3245
3247
  lhs = "{}, {}".format(L, R[1:-1])
3246
3248
 
3247
3249
  ## @(y1,,,yn) --> @partial(y1,,,yn)
3248
3250
  elif rhs.startswith('('):
3249
3251
  rhs = re.sub(r"^\((.*)\)", r"partial(\1)", rhs, flags=re.S)
3250
3252
 
3253
+ ## obj @.method --> (obj).method
3254
+ elif rhs.startswith('.'):
3255
+ return self.magic_interpret([f"({lhs}){rhs}"] + rest)
3256
+
3251
3257
  return self.magic_interpret([f"{rhs}({lhs})"] + rest)
3252
3258
 
3253
3259
  if c == '`':
@@ -3516,7 +3522,7 @@ class Nautilus(EditorInterface, Shell):
3516
3522
  def write(self, text, pos=None):
3517
3523
  """Display text in the shell.
3518
3524
 
3519
- (override) Append text if it is writable at the position.
3525
+ (override) Append text if it is writable at the given position.
3520
3526
  """
3521
3527
  if pos is not None:
3522
3528
  if pos < 0:
@@ -3603,22 +3609,20 @@ class Nautilus(EditorInterface, Shell):
3603
3609
  ## Autocomp actions of the shell
3604
3610
  ## --------------------------------
3605
3611
 
3606
- def autoCallTipShow(self, command, insertcalltip=True, forceCallTip=False):
3607
- """Display argument spec and docstring in a popup window.
3608
-
3609
- (override) Swap anchors to not scroll to the end of the line.
3610
- """
3611
- Shell.autoCallTipShow(self, command, insertcalltip, forceCallTip)
3612
- self.cpos, self.anchor = self.anchor, self.cpos
3613
- self.EnsureCaretVisible()
3614
-
3615
3612
  def eval_line(self):
3616
3613
  """Evaluate the selected word or line."""
3617
3614
  if self.CallTipActive():
3618
3615
  self.CallTipCancel()
3619
3616
 
3620
- text = self.SelectedText or self.expr_at_caret
3621
- if text:
3617
+ def _gen_text():
3618
+ text = self.SelectedText
3619
+ if text:
3620
+ yield text
3621
+ else:
3622
+ yield self.line_at_caret
3623
+ yield self.expr_at_caret
3624
+
3625
+ for text in _gen_text():
3622
3626
  tokens = split_words(text)
3623
3627
  try:
3624
3628
  cmd = self.magic_interpret(tokens)
@@ -3629,7 +3633,8 @@ class Nautilus(EditorInterface, Shell):
3629
3633
  else:
3630
3634
  self.CallTipShow(self.cpos, pformat(obj))
3631
3635
  self.message(cmd)
3632
- else:
3636
+ return
3637
+ if not text:
3633
3638
  self.message("No words")
3634
3639
 
3635
3640
  def exec_region(self):
mwx/utilus.py CHANGED
@@ -283,24 +283,28 @@ if pp:
283
283
  pp.sort_dicts = True
284
284
 
285
285
 
286
- def split_paren(text, reverse=False):
287
- """Split text into a head parenthesis and the rest, including the tail.
288
- If reverse is True, search from tail to head.
286
+ def split_words(text, reverse=False):
287
+ """Generates words (python phrase) extracted from text.
288
+ If reverse is True, process from tail to head.
289
289
  """
290
290
  tokens = list(split_tokens(text))
291
291
  if reverse:
292
292
  tokens = tokens[::-1]
293
- words = _extract_paren_from_tokens(tokens, reverse)
294
- paren = ''.join(reversed(words) if reverse else words)
295
- rest = ''.join(reversed(tokens) if reverse else tokens)
296
- if reverse:
297
- return rest, paren
298
- else:
299
- return paren, rest
293
+ while tokens:
294
+ words = []
295
+ while 1:
296
+ word = _extract_words_from_tokens(tokens, reverse)
297
+ if not word:
298
+ break
299
+ words += word
300
+ if words:
301
+ yield ''.join(reversed(words) if reverse else words)
302
+ if tokens:
303
+ yield tokens.pop(0) # sep-token
300
304
 
301
305
 
302
- def split_words(text, reverse=False):
303
- """Generates words extracted from text.
306
+ def split_parts(text, reverse=False):
307
+ """Generates portions (words and parens) extracted from text.
304
308
  If reverse is True, process from tail to head.
305
309
  """
306
310
  tokens = list(split_tokens(text))
@@ -345,10 +349,6 @@ def split_tokens(text, comment=True):
345
349
  def _extract_words_from_tokens(tokens, reverse=False):
346
350
  """Extracts pythonic expressions from tokens.
347
351
 
348
- Extraction continues until the parenthesis is closed
349
- and the following token starts with a char in sep, where
350
- the sep includes `@, ops, delims, and whitespaces, etc.
351
-
352
352
  Returns:
353
353
  A token list extracted including the parenthesis.
354
354
  If reverse is True, the order of the tokens will be reversed.
@@ -372,45 +372,16 @@ def _extract_words_from_tokens(tokens, reverse=False):
372
372
  elif not stack and c[0] in sep: # ok; starts with a char in sep
373
373
  break
374
374
  words.append(c)
375
- else: # if stack: error("unclosed-paren")
375
+ if not stack: # ok
376
+ j += 1 # to remove current token
377
+ break
378
+ else:
379
+ ## if stack: error("unclosed-paren")
376
380
  j = None
377
381
  del tokens[:j] # remove extracted tokens (except the last one)
378
382
  return words
379
383
 
380
384
 
381
- def _extract_paren_from_tokens(tokens, reverse=False):
382
- """Extracts parenthesis from tokens.
383
-
384
- The first token must be a parenthesis.
385
- Returns:
386
- A token list extracted including the parenthesis,
387
- or an empty list if the parenthesis is not closed.
388
- If reverse is True, the order of the tokens will be reversed.
389
- """
390
- p, q = "({[", ")}]"
391
- if reverse:
392
- p, q = q, p
393
- stack = []
394
- words = []
395
- for j, c in enumerate(tokens):
396
- if not c:
397
- continue
398
- if c in p:
399
- stack.append(c)
400
- elif c in q:
401
- if not stack: # error("open-paren")
402
- break
403
- if c != q[p.index(stack.pop())]: # error("mismatch-paren")
404
- break
405
- elif j == 0:
406
- break # first char is not paren
407
- words.append(c)
408
- if not stack: # ok
409
- del tokens[:j+1] # remove extracted tokens
410
- return words
411
- return [] # error("unclosed-paren")
412
-
413
-
414
385
  def walk_packages_no_import(path=None, prefix=''):
415
386
  """Yields module info recursively for all submodules on path.
416
387
  If path is None, yields all top-level modules on sys.path.
@@ -979,26 +950,44 @@ class TreeList:
979
950
 
980
951
 
981
952
  def get_fullargspec(f):
982
- argv = []
983
- defaults = {}
984
- varargs = None
985
- varkwargs = None
986
- kwonlyargs = []
987
- kwonlydefaults = {}
953
+ """Get the names and default values of a callable object's parameters.
954
+ If the object is a built-in function, it tries to get argument
955
+ information from the docstring. If it fails, it returns None.
956
+
957
+ Returns:
958
+ args : a list of the parameter names.
959
+ varargs : the name of the * parameter or None.
960
+ varkwargs : the name of the ** parameter or None.
961
+ defaults : a dict mapping names from args to defaults.
962
+ kwonlyargs : a list of keyword-only parameter names.
963
+ kwonlydefaults : a dict mapping names from kwonlyargs to defaults.
964
+
965
+ Note:
966
+ `self` parameter is not reported for bound methods.
967
+
968
+ cf. inspect.getfullargspec
969
+ """
970
+ argv = [] # <before /> 0:POSITIONAL_ONLY
971
+ # <before *> 1:POSITIONAL_OR_KEYWORD
972
+ varargs = None # <*args> 2:VAR_POSITIONAL
973
+ varkwargs = None # <**kwargs> 4:VAR_KEYWORD
974
+ defaults = {} #
975
+ kwonlyargs = [] # <after *> 3:KEYWORD_ONLY
976
+ kwonlydefaults = {} #
988
977
  try:
989
978
  sig = inspect.signature(f)
990
979
  for k, v in sig.parameters.items():
991
- if v.kind <= 1: # POSITIONAL_ONLY / POSITIONAL_OR_KEYWORD
980
+ if v.kind in (v.POSITIONAL_ONLY, v.POSITIONAL_OR_KEYWORD):
992
981
  argv.append(k)
993
982
  if v.default != v.empty:
994
983
  defaults[k] = v.default
995
- elif v.kind == 2: # VAR_POSITIONAL (*args)
984
+ elif v.kind == v.VAR_POSITIONAL:
996
985
  varargs = k
997
- elif v.kind == 3: # KEYWORD_ONLY
986
+ elif v.kind == v.KEYWORD_ONLY:
998
987
  kwonlyargs.append(k)
999
988
  if v.default != v.empty:
1000
989
  kwonlydefaults[k] = v.default
1001
- elif v.kind == 4: # VAR_KEYWORD (**kwargs)
990
+ elif v.kind == v.VAR_KEYWORD:
1002
991
  varkwargs = k
1003
992
  except ValueError:
1004
993
  ## Builtin functions don't have an argspec that we can get.
@@ -1009,19 +998,33 @@ def get_fullargspec(f):
1009
998
  ##
1010
999
  ## ...(details)...
1011
1000
  ## ```
1012
- docs = [ln for ln in inspect.getdoc(f).splitlines() if ln]
1013
- m = re.search(r"(\w+)\((.*)\)", docs[0])
1014
- if m:
1015
- ## Note: Cannot process args containing commas.
1016
- ## e.g., v='Hello, world"
1001
+ doc = inspect.getdoc(f)
1002
+ if doc is None:
1003
+ return None
1004
+ m = re.match(r"(\w+)\s*\((.*?)\)", doc.strip(), re.S)
1005
+ if not m:
1006
+ return None
1007
+ else:
1017
1008
  name, argspec = m.groups()
1018
- for v in argspec.strip().split(','):
1019
- m = re.search(r"(\w+)", v)
1009
+ argparts = ['']
1010
+ for part in split_parts(argspec): # Separate argument parts with commas.
1011
+ if not part.strip():
1012
+ continue
1013
+ if part != ',':
1014
+ argparts[-1] += part
1015
+ else:
1016
+ argparts.append('')
1017
+ for v in argparts:
1018
+ m = re.match(r"(\w+):?", v) # argv + kwonlyargs
1020
1019
  if m:
1021
1020
  argv.append(m.group(1))
1022
- defaults = dict(re.findall(r"(\w+)\s*=\s*([\w' ]+)", argspec))
1023
- else:
1024
- return None
1021
+ m = re.match(r"(\w+)(?::\w+)?=(.+)", v) # defaults + kwonlydefaults
1022
+ if m:
1023
+ defaults.update([m.groups()])
1024
+ elif v.startswith('**'): # <**kwargs>
1025
+ varkwargs = v[2:]
1026
+ elif v.startswith('*'): # <*args>
1027
+ varargs = v[1:]
1025
1028
  return (argv, varargs, varkwargs,
1026
1029
  defaults, kwonlyargs, kwonlydefaults)
1027
1030
 
@@ -1038,10 +1041,10 @@ def funcall(f, *args, doc=None, alias=None, **kwargs):
1038
1041
  >>> Act1 = lambda *v,**kw: f(*(v+args), **(kwargs|kw))
1039
1042
  >>> Act2 = lambda *v,**kw: f(*args, **(kwargs|kw))
1040
1043
 
1041
- Returns Act1 (accepts event arguments) if event arguments
1044
+ `Act1` is returned (accepts event arguments) if event arguments
1042
1045
  cannot be omitted or if there are any remaining arguments
1043
1046
  that must be explicitly specified.
1044
- Otherwise, returns Act2 (ignores event arguments).
1047
+ Otherwise, `Act2` is returned (ignores event arguments).
1045
1048
  """
1046
1049
  assert callable(f)
1047
1050
  assert isinstance(doc, (str, type(None)))
@@ -1062,6 +1065,7 @@ def funcall(f, *args, doc=None, alias=None, **kwargs):
1062
1065
  (argv, varargs, varkwargs, defaults,
1063
1066
  kwonlyargs, kwonlydefaults) = get_fullargspec(f)
1064
1067
  except Exception:
1068
+ warn(f"Failed to get the signature of {f}.")
1065
1069
  return f
1066
1070
  if not varargs:
1067
1071
  N = len(argv)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mwxlib
3
- Version: 1.3.11
3
+ Version: 1.4.5
4
4
  Summary: A wrapper of matplotlib and wxPython (phoenix)
5
5
  Home-page: https://github.com/komoto48g/mwxlib
6
6
  Author: Kazuya O'moto
@@ -1,16 +1,16 @@
1
1
  mwx/__init__.py,sha256=pS7ZG8QKRypiFFiaWAq_opBB6I_1viZ0zUMk2TbjzE0,667
2
2
  mwx/bookshelf.py,sha256=MXjrm_ryRGHvOFKu8wb10qz_oka70xW2yFbYjBMYfns,8140
3
3
  mwx/controls.py,sha256=RssTROprNfgnRMiWVOoSSd8Fy0qwZ8DCrYM2f5Wgr4E,48610
4
- mwx/framework.py,sha256=9H26L5rF3rxpBm0wInvt3oNhB9si6xNR-K5PbIoVx5c,76778
5
- mwx/graphman.py,sha256=qA_tNq_9A5eNuujzmxha8ponAIQxZmMtJQkRkojuVYo,70398
4
+ mwx/framework.py,sha256=DO2VrF_SOS6Gdr22PZclHChWr2oRQfW__7-1xxr97Cc,76830
5
+ mwx/graphman.py,sha256=_8_DaG3qhhZByoh5wboyYQlAsm7GlPoR8wT-RNMx4Cw,70627
6
6
  mwx/images.py,sha256=oxCn0P-emiWujSS2gUgU5TUnr5cPjix2jBcjOBDr24I,48701
7
7
  mwx/matplot2.py,sha256=Htwegq6N5G7oKSRCuajik5Dixd93i8PKVbkL7Azy99M,33150
8
- mwx/matplot2g.py,sha256=H1PeLJk5P0KKMT6tmocpli5RSPNjnteA5GhbJTKEqIg,64869
8
+ mwx/matplot2g.py,sha256=4BBHlvfhg1796hu1nTCsTA82YRkZhwUnfcHCBf5ZGO8,65083
9
9
  mwx/matplot2lg.py,sha256=cb0EZXivccDQu4oFj5ddSUF9pEE4f5UuFJJK2ELItww,27404
10
10
  mwx/mgplt.py,sha256=8mXbHpCmm7lz3XbAxOg7IVC7DaSGBEby1UfTlMl9kjk,5604
11
- mwx/nutshell.py,sha256=XrPBcOCeUbnKi5-JjlpmZinkIJINimNsS82SXRxfv_s,142391
11
+ mwx/nutshell.py,sha256=4GDfGCHvHptpLqJIUVAfCMyI6V2KFcgji8S0PWMviL0,142385
12
12
  mwx/testsuite.py,sha256=Zk75onPSEn2tf0swS3l-vIn6yTXGB7allIyvJsPHj20,1229
13
- mwx/utilus.py,sha256=bDeooo2bOcZwvkIdi0ElkT-qoblqzHNFsIveb72NFOo,37528
13
+ mwx/utilus.py,sha256=iAqtDbkVuFsSuS71leUd-mHH0ETRQ3GLnII7O2wcw3I,38065
14
14
  mwx/wxmon.py,sha256=yzWqrbY6LzpfRwQeytYUeqFhFuLVm_XEvrVAL_k0HBQ,12756
15
15
  mwx/wxpdb.py,sha256=ih2iLcOgYnUX849YXO4niIYue6amuoG7nWdpr-X1jFw,18839
16
16
  mwx/wxwil.py,sha256=hhyB1lPrF9ixeObxCOKQv0Theu-B-kpJg_yVU3EGSNg,5406
@@ -22,7 +22,7 @@ mwx/plugins/frame_listview.py,sha256=gowjQ-ARNonMkDSXkQgPKq4U9YBJ-vQ0jK2krBVOdCs
22
22
  mwx/plugins/line_profile.py,sha256=zzm6_7lnAnNepLbh07ordp3nRWDFQJtu719ZVjrVf8s,819
23
23
  mwx/py/__init__.py,sha256=xykgfOytOwNuvXsfkLoumFZSTN-iBsHOjczYXngjmUE,12
24
24
  mwx/py/filling.py,sha256=fumUG1F5M9TL-Dfqni4G85uk7TmvnUunTbdcPDV0vfo,16857
25
- mwxlib-1.3.11.dist-info/METADATA,sha256=q5aFmjTSmVGQcb9lCf1xvhagruldOzi0LgMAJi3yClI,7434
26
- mwxlib-1.3.11.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
27
- mwxlib-1.3.11.dist-info/top_level.txt,sha256=SI1Mh118AstnUFGPNq5aMNKiAnVNmZk1S9Ij-OwAEpY,4
28
- mwxlib-1.3.11.dist-info/RECORD,,
25
+ mwxlib-1.4.5.dist-info/METADATA,sha256=004S_ou1NJHL8BynaX391SwQ5vamthk4oifMNz8yUrE,7433
26
+ mwxlib-1.4.5.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
27
+ mwxlib-1.4.5.dist-info/top_level.txt,sha256=SI1Mh118AstnUFGPNq5aMNKiAnVNmZk1S9Ij-OwAEpY,4
28
+ mwxlib-1.4.5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (76.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5