#! python3
# -*- coding: utf-8 -*-
"""deb utilus ver 1.0rc
"""
__version__ = "1.0rc"
__author__ = "Kazuya O'moto <komoto@jeol.co.jp>"
import builtins
import getopt
import sys
import os
import re
import wx
from wx import aui
from wx import stc
import mwx
from mwx.utilus import FSM
from mwx.controls import Icon
from mwx.nutshell import Nautilus, EditorBook
from mwx.py.filling import FillingTree
try:
from etc import bookshelf
except ImportError:
from .etc import bookshelf
[docs]def subclasses(cls):
try:
return cls.__subclasses__()
except AttributeError:
pass
builtins.subclasses = subclasses
## This monkey patch forces the filling-tree to display only atoms.
try:
_FillingTree_filter_org = FillingTree.filter
[docs] def atomic(self, obj, key):
if not _FillingTree_filter_org(self, obj, key):
return False
try:
v = getattr(obj, key)
return not hasattr(v, '__name__')
except Exception:
pass
FillingTree.filter = atomic
except AttributeError:
pass
## --------------------------------
## Configuration of Shell / Editor
## --------------------------------
[docs]def init_stc_interface(self):
"""Customize the common keymaps.
"""
@self.define_key('f9')
def toggle_wrap_mode():
mode = ['no-wrap',
'word-wrap',
'char-wrap',
'whitespace-wrap'
]
self.WrapMode = (self.WrapMode + 1) % 4
self.post_message("\b {!r}".format(mode[self.WrapMode]))
@self.define_key('S-f9')
def toggle_eol_view():
self.ViewEOL = not self.ViewEOL
self.ViewWhiteSpace = not self.ViewWhiteSpace
self.define_key('C-x [', self.beginning_of_buffer)
self.define_key('C-x ]', self.end_of_buffer)
self.define_key('C-c C-c', self.goto_matched_paren)
self.define_key('C-x C-x', self.exchange_point_and_mark)
[docs]def init_buffer(self):
"""Customize the keymaps of the Buffer.
"""
## Buffer text control
init_stc_interface(self)
@self.define_key('enter')
def newline_and_indent():
n = self.py_electric_indent()
self.AddText(os.linesep + ' ' * n)
@self.define_key('C-enter')
def newline_and_indent_eol():
n = self.py_electric_indent()
self.goto_char(self.eol)
self.AddText(os.linesep + ' ' * n)
@self.define_key('C-S-enter')
@self.define_key('S-enter')
def open_line_and_indent():
n = self.py_current_indent()
self.goto_char(self.bol)
self.InsertText(self.bol, ' ' * n + os.linesep)
self.goto_char(self.cpos + n) # relative indentation position
@self.define_key('M-w')
def copy_region():
self.anchor = self.mark
self.Copy()
@self.define_key('C-w')
def kill_region():
self.anchor = self.mark
self.Cut()
[docs]def init_editor(self):
"""Customize the keymaps of the Editor.
"""
self.define_key('C-x k', self.kill_all_buffers)
self.define_key('C-x C-k', self.kill_buffer)
self.define_key('C-x C-n', self.new_buffer)
self.define_key('C-x C-l', self.load_buffer)
self.define_key('C-x s', self.save_all_buffers)
self.define_key('C-x C-s', self.save_buffer)
self.define_key('C-x S-s', self.save_buffer_as)
self.define_key('C-x C-f', self.open_buffer) # cf. find-file
@self.define_key('f5')
def reload_buffer():
self.load_buffer()
if self.buffer.code:
shell = self.parent.current_shell
self.buffer.py_exec_region(shell.globals, shell.locals)
self.post_message(f"Reloaded {self.buffer.name!r} successfully.")
@self.define_key('C-x up', dir=wx.UP)
@self.define_key('C-x down', dir=wx.DOWN)
@self.define_key('C-x left', dir=wx.LEFT)
@self.define_key('C-x right', dir=wx.RIGHT)
def split(dir):
j = self.all_buffers.index(self.CurrentPage)
self.Split(j, dir)
@self.define_key('C-x 0')
def unsplit(v):
self.move_tab(self.CurrentPage, self.all_tabs[0])
[docs]def init_shell(self):
"""Customize the keymaps of the Shell.
"""
init_stc_interface(self)
@self.define_key('S-enter') # cf. [C-RET] Shell.insertLineBreak
def open_line():
self.back_to_indentation()
p = self.cpos
self.insertLineBreak()
self.cpos = self.anchor = p
@self.define_key('f1')
def help(v):
text = self.SelectedText or self.expr_at_caret
try:
obj = self.eval(text)
self.help(obj)
except Exception:
v.Skip()
@self.define_key('f2')
def load_target():
text = self.SelectedText or self.expr_at_caret
if not text:
self.post_message("No target")
return
try:
obj = self.eval(text)
except Exception as e:
self.post_message(f"\b failed: {e!r}")
else:
if self.parent.load(obj):
self.post_message(f"\b {obj!r}")
else:
self.post_message(f"\b {text!r} was nowhere to be found.")
error = r'(?i) +File "(.+?)", line ([0-9]+)'
frame = r"(?i) +file '(.+?)', line ([0-9]+)"
where = r'> +([^*?"|\r\n]+?):([0-9]+)'
bp = r'at +([^*?"|\r\n]+?):([0-9]+)'
grep = '|'.join((frame, where, bp))
@self.define_key('f4', pattern=error)
@self.define_key('f10', pattern=grep)
def grep_forward(pattern):
for err in self.grep_forward(pattern):
target = ':'.join(filter(None, err.groups()))
if self.parent.load(target, focus=False):
self.post_message(f"\b {target}")
break
@self.define_key('S-f4', pattern=error)
@self.define_key('S-f10', pattern=grep)
def grep_barckward(pattern):
for err in self.grep_barckward(pattern):
target = ':'.join(filter(None, err.groups()))
if self.parent.load(target, focus=False):
self.post_message(f"\b {target}")
break
@self.define_key('S-f12')
def clear_shell():
self.clear()
@self.define_key('C-f12')
def clone_shell():
self.parent.clone_shell(self.target)
@self.define_key('M-f12')
def close_shell():
self.parent.delete_shell(self)
@self.define_key('C-f4')
def HL():
obj = self.cmdline
try:
highlight(self.eval(obj))
except Exception:
pass
## --------------------------------
## Setup the console of Nautilus
## --------------------------------
[docs]class py_text_mode:
STYLE = {
stc.STC_STYLE_DEFAULT : "fore:#7f7f7f,back:#fffff8,size:9,face:MS Gothic",
stc.STC_STYLE_LINENUMBER : "fore:#000000,back:#fffff8,size:9",
stc.STC_STYLE_BRACELIGHT : "fore:#000000,back:#cccccc,bold",
stc.STC_STYLE_BRACEBAD : "fore:#000000,back:#ff0000,bold",
stc.STC_STYLE_CONTROLCHAR : "size:6",
stc.STC_STYLE_CARETLINE : "fore:#000000,back:#f0f0ff,size:2", # optional
stc.STC_STYLE_ANNOTATION : "fore:#7f0000,back:#ff7f7f", # optional
stc.STC_P_DEFAULT : "fore:#000000",
stc.STC_P_OPERATOR : "fore:#000000",
stc.STC_P_IDENTIFIER : "fore:#000000",
stc.STC_P_COMMENTLINE : "fore:#007f00,back:#f0fff0",
stc.STC_P_COMMENTBLOCK : "fore:#007f00,back:#f0fff0,eol",
stc.STC_P_NUMBER : "fore:#e02000",
stc.STC_P_STRINGEOL : "fore:#7f7f7f,back:#ffc0c0,eol",
stc.STC_P_CHARACTER : "fore:#7f7f7f",
stc.STC_P_STRING : "fore:#7f7f7f",
stc.STC_P_TRIPLE : "fore:#7f7f7f",
stc.STC_P_TRIPLEDOUBLE : "fore:#7f7f7f",
stc.STC_P_CLASSNAME : "fore:#7f00ff,bold",
stc.STC_P_DEFNAME : "fore:#0000ff,bold",
stc.STC_P_WORD : "fore:#0000ff",
stc.STC_P_WORD2 : "fore:#7f007f",
stc.STC_P_WORD3 : "fore:#ff0000,back:#ffff00", # optional for search word
stc.STC_P_DECORATOR : "fore:#c04040,bold",
}
[docs]class py_interactive_mode:
STYLE = {
stc.STC_STYLE_DEFAULT : "fore:#7f7f7f,back:#202020,size:9,face:MS Gothic",
stc.STC_STYLE_LINENUMBER : "fore:#000000,back:#f0f0f0,size:9",
stc.STC_STYLE_BRACELIGHT : "fore:#ffffff,back:#202020,bold",
stc.STC_STYLE_BRACEBAD : "fore:#ffffff,back:#ff0000,bold",
stc.STC_STYLE_CONTROLCHAR : "size:6",
stc.STC_STYLE_CARETLINE : "fore:#ffffff,back:#123460,size:2", # optional
stc.STC_STYLE_ANNOTATION : "fore:#7f0000,back:#ff7f7f", # optional
stc.STC_P_DEFAULT : "fore:#cccccc",
stc.STC_P_OPERATOR : "fore:#cccccc",
stc.STC_P_IDENTIFIER : "fore:#cccccc",
stc.STC_P_COMMENTLINE : "fore:#42c18c,back:#004040",
stc.STC_P_COMMENTBLOCK : "fore:#42c18c,back:#004040,eol",
stc.STC_P_NUMBER : "fore:#ffc080",
stc.STC_P_STRINGEOL : "fore:#cccccc,back:#004040,eol",
stc.STC_P_CHARACTER : "fore:#a0a0a0",
stc.STC_P_STRING : "fore:#a0a0a0",
stc.STC_P_TRIPLE : "fore:#a0a0a0,back:#004040",
stc.STC_P_TRIPLEDOUBLE : "fore:#a0a0a0,back:#004040",
stc.STC_P_CLASSNAME : "fore:#61d6d6,bold",
stc.STC_P_DEFNAME : "fore:#3a96ff,bold",
stc.STC_P_WORD : "fore:#80c0ff",
stc.STC_P_WORD2 : "fore:#ff80ff",
stc.STC_P_WORD3 : "fore:#ff0000,back:#ffff00", # optional for search word
stc.STC_P_DECORATOR : "fore:#ff8040",
}
[docs]def stylus(self):
"""Stylize Nautilus window.
Note:
This function is executed every time you reload.
"""
## Customize the keymaps of the ShellFrame.
self.define_key('C-x C-S-o', self.load_session)
self.define_key('C-x C-S-s', self.save_session_as)
@self.define_key('Xbutton1', p=-1)
@self.define_key('Xbutton2', p=+1)
@self.define_key('C-x p', p=-1)
@self.define_key('C-x n', p=+1)
def other_editor(p=1):
"""Move focus to other topmost notebook page."""
nb = wx.Window.FindFocus()
while isinstance(nb.Parent, aui.AuiNotebook):
nb = nb.Parent
try:
if nb.PageCount > 1:
nb.Selection = (nb.Selection + p) % nb.PageCount
except AttributeError:
pass
@self.define_key('C-d', clear=0)
@self.define_key('C-S-d', clear=1)
def duplicate_line(clear=True):
"""Duplicate an expression at the caret-line."""
buf = wx.Window.FindFocus()
if not isinstance(buf, stc.StyledTextCtrl):
return
text = buf.SelectedText or buf.expr_at_caret or buf.topic_at_caret
if text:
shell = self.current_shell
buf.mark = buf.cpos
if clear:
shell.clearCommand() # move to the prompt end
shell.write(text, -1) # write at the end of command-line
shell.SetFocus()
## Customize keymaps.
for page in self.get_all_pages(EditorBook):
init_editor(page)
for buf in page.all_buffers:
init_buffer(buf)
for page in self.get_all_pages(Nautilus):
init_shell(page)
## Stylize all child windows.
self.Config.set_attributes(Style=py_text_mode.STYLE)
self.Scratch.set_attributes(Style=py_interactive_mode.STYLE)
## Don't kill config buffers.
self.Config.undefine_key('C-x k')
self.Config.undefine_key('C-x C-k')
## --------------------------------
## Main program
## --------------------------------
[docs]class MyDataLoader(wx.DropTarget):
"""DnD target loader.
Supports URL text and file data formats.
"""
def __init__(self, target: EditorBook):
wx.DropTarget.__init__(self)
self.target = target
self.textdo = wx.TextDataObject()
self.filedo = wx.FileDataObject()
self.DataObject = wx.DataObjectComposite()
self.DataObject.Add(self.textdo)
self.DataObject.Add(self.filedo, True)
[docs] def OnData(self, x, y, result):
self.GetData()
if self.textdo.TextLength > 1:
text = self.textdo.Text
if re.match(r"https?://[\w/:%#\$&\?()~.=+-]+", text): # url_re
_load = self.target.load_url
else:
_load = self.target.load_file
res = _load(text.strip())
if res:
self.target.buffer.SetFocus()
result = wx.DragCopy
elif res is None:
self.target.post_message("Load canceled.")
result = wx.DragCancel
else:
self.target.post_message("Load failed.")
result = wx.DragNone
self.textdo.Text = ''
else:
for f in self.filedo.Filenames:
if self.target.load_file(f):
self.target.buffer.SetFocus()
self.target.post_message(f"Loaded {f!r} successfully.")
self.filedo.SetData(wx.DF_FILENAME, None)
return result
[docs]def main(self):
"""Initialize Nautilus configuration.
Note:
This function is executed once at startup.
"""
## Config loader extension
if not hasattr(self, "Config"):
self.Config = EditorBook(self, name="Config")
self.Config.load_file(__file__)
try:
self.Config.default_buffer._load_file(self.SESSION_FILE)
except FileNotFoundError:
self.Config.default_buffer._save_file(self.SESSION_FILE)
self.ghost.AddPage(self.Config, 'Config', bitmap=Icon('proc'))
self.set_hookable(self.Config) # Bind pointer to enable trace.
@self.Config.define_key('M-j')
def eval_buffer():
"""Evaluate this <conf> code and call new stylus"""
locals = {'self': self}
self.Config.buffer.py_exec_region(
locals, locals, self.Config.buffer.filename
)
if "main" in locals:
locals["main"](self)
## Stylize ShellFrame window
stylus(self)
## Define *new* event handlers.
for editor in self.get_all_pages(EditorBook):
editor.SetDropTarget(MyDataLoader(editor))
editor.handler.define('buffer_new', init_buffer)
self.handler.define('shell_new', init_shell)
## Bookshelf treeview extension
if not hasattr(self, "Bookshelf"):
self.Bookshelf = bookshelf.EditorTreeCtrl(self,
style=wx.TR_DEFAULT_STYLE|wx.TR_HIDE_ROOT,
name="Bookshelf")
self.ghost.AddPage(self.Bookshelf, "Bookshelf", bitmap=Icon('book'))
## Note: Bookshelf context must be coded in the editor/shell
## after the *new* event handlers is defined above,
## and also after this module is reloaded.
self.Bookshelf.watch(self.ghost)
self.Bookshelf.SetDropTarget(MyDataLoader(self.Scratch))
## self.post_message("Startup process has completed successfully.")
quote_unqoute = """
Anything one man can imagine, other man can make real.
--- Jules Verne (1828--1905)
"""
if __name__ == "__main__":
session = None
opts, args = getopt.gnu_getopt(sys.argv[1:], "s:")
for k, v in opts:
if k == "-s":
if not v.endswith(".debrc"):
v += ".debrc"
session = v
if session:
print(f"Starting session {session!r}")
app = wx.App()
frame = mwx.deb(loop=0, debrc=session,
introText=__doc__ + quote_unqoute)
main(frame)
frame.undefine_key('f12') # Don't close.
if 1:
## If you want debugger skip a specific module,
## add the module:str to debugger.skip:set.
frame.debugger.skip -= {
mwx.FSM.__module__, # for debugging FSM
}
## Dive into objects to inspect.
dive(frame)
app.MainLoop()