| """A (less & less) simple Python editor""" |
| |
| import W |
| import Wtraceback |
| from Wkeys import * |
| |
| import macfs |
| import MACFS |
| import MacOS |
| from Carbon import Win |
| from Carbon import Res |
| from Carbon import Evt |
| from Carbon import Qd |
| import os |
| import imp |
| import sys |
| import string |
| import marshal |
| import re |
| |
| if hasattr(Win, "FrontNonFloatingWindow"): |
| MyFrontWindow = Win.FrontNonFloatingWindow |
| else: |
| MyFrontWindow = Win.FrontWindow |
| |
| |
| try: |
| import Wthreading |
| except ImportError: |
| haveThreading = 0 |
| else: |
| haveThreading = Wthreading.haveThreading |
| |
| _scriptuntitledcounter = 1 |
| _wordchars = string.ascii_letters + string.digits + "_" |
| |
| |
| runButtonLabels = ["Run all", "Stop!"] |
| runSelButtonLabels = ["Run selection", "Pause!", "Resume"] |
| |
| |
| class Editor(W.Window): |
| |
| def __init__(self, path = "", title = ""): |
| defaultfontsettings, defaulttabsettings, defaultwindowsize = geteditorprefs() |
| global _scriptuntitledcounter |
| if not path: |
| if title: |
| self.title = title |
| else: |
| self.title = "Untitled Script " + `_scriptuntitledcounter` |
| _scriptuntitledcounter = _scriptuntitledcounter + 1 |
| text = "" |
| self._creator = W._signature |
| self._eoln = os.linesep |
| elif os.path.exists(path): |
| path = resolvealiases(path) |
| dir, name = os.path.split(path) |
| self.title = name |
| f = open(path, "rb") |
| text = f.read() |
| f.close() |
| fss = macfs.FSSpec(path) |
| self._creator, filetype = fss.GetCreatorType() |
| else: |
| raise IOError, "file '%s' does not exist" % path |
| self.path = path |
| |
| if '\n' in text: |
| import EasyDialogs |
| if string.find(text, '\r\n') >= 0: |
| self._eoln = '\r\n' |
| else: |
| self._eoln = '\n' |
| text = string.replace(text, self._eoln, '\r') |
| change = 0 |
| else: |
| change = 0 |
| self._eoln = '\r' |
| |
| self.settings = {} |
| if self.path: |
| self.readwindowsettings() |
| if self.settings.has_key("windowbounds"): |
| bounds = self.settings["windowbounds"] |
| else: |
| bounds = defaultwindowsize |
| if self.settings.has_key("fontsettings"): |
| self.fontsettings = self.settings["fontsettings"] |
| else: |
| self.fontsettings = defaultfontsettings |
| if self.settings.has_key("tabsize"): |
| try: |
| self.tabsettings = (tabsize, tabmode) = self.settings["tabsize"] |
| except: |
| self.tabsettings = defaulttabsettings |
| else: |
| self.tabsettings = defaulttabsettings |
| |
| W.Window.__init__(self, bounds, self.title, minsize = (330, 120), tabbable = 0) |
| self.setupwidgets(text) |
| if change > 0: |
| self.editgroup.editor.textchanged() |
| |
| if self.settings.has_key("selection"): |
| selstart, selend = self.settings["selection"] |
| self.setselection(selstart, selend) |
| self.open() |
| self.setinfotext() |
| self.globals = {} |
| self._buf = "" # for write method |
| self.debugging = 0 |
| self.profiling = 0 |
| self.run_as_main = self.settings.get("run_as_main", 0) |
| self.run_with_interpreter = self.settings.get("run_with_interpreter", 0) |
| self.run_with_cl_interpreter = self.settings.get("run_with_cl_interpreter", 0) |
| self._threadstate = (0, 0) |
| self._thread = None |
| |
| def readwindowsettings(self): |
| try: |
| resref = Res.FSpOpenResFile(self.path, 1) |
| except Res.Error: |
| return |
| try: |
| Res.UseResFile(resref) |
| data = Res.Get1Resource('PyWS', 128) |
| self.settings = marshal.loads(data.data) |
| except: |
| pass |
| Res.CloseResFile(resref) |
| |
| def writewindowsettings(self): |
| try: |
| resref = Res.FSpOpenResFile(self.path, 3) |
| except Res.Error: |
| Res.FSpCreateResFile(self.path, self._creator, 'TEXT', MACFS.smAllScripts) |
| resref = Res.FSpOpenResFile(self.path, 3) |
| try: |
| data = Res.Resource(marshal.dumps(self.settings)) |
| Res.UseResFile(resref) |
| try: |
| temp = Res.Get1Resource('PyWS', 128) |
| temp.RemoveResource() |
| except Res.Error: |
| pass |
| data.AddResource('PyWS', 128, "window settings") |
| finally: |
| Res.UpdateResFile(resref) |
| Res.CloseResFile(resref) |
| |
| def getsettings(self): |
| self.settings = {} |
| self.settings["windowbounds"] = self.getbounds() |
| self.settings["selection"] = self.getselection() |
| self.settings["fontsettings"] = self.editgroup.editor.getfontsettings() |
| self.settings["tabsize"] = self.editgroup.editor.gettabsettings() |
| self.settings["run_as_main"] = self.run_as_main |
| self.settings["run_with_interpreter"] = self.run_with_interpreter |
| self.settings["run_with_cl_interpreter"] = self.run_with_cl_interpreter |
| |
| def get(self): |
| return self.editgroup.editor.get() |
| |
| def getselection(self): |
| return self.editgroup.editor.ted.WEGetSelection() |
| |
| def setselection(self, selstart, selend): |
| self.editgroup.editor.setselection(selstart, selend) |
| |
| def getfilename(self): |
| if self.path: |
| return self.path |
| return '<%s>' % self.title |
| |
| def setupwidgets(self, text): |
| topbarheight = 24 |
| popfieldwidth = 80 |
| self.lastlineno = None |
| |
| # make an editor |
| self.editgroup = W.Group((0, topbarheight + 1, 0, 0)) |
| editor = W.PyEditor((0, 0, -15,-15), text, |
| fontsettings = self.fontsettings, |
| tabsettings = self.tabsettings, |
| file = self.getfilename()) |
| |
| # make the widgets |
| self.popfield = ClassFinder((popfieldwidth - 17, -15, 16, 16), [], self.popselectline) |
| self.linefield = W.EditText((-1, -15, popfieldwidth - 15, 16), inset = (6, 1)) |
| self.editgroup._barx = W.Scrollbar((popfieldwidth - 2, -15, -14, 16), editor.hscroll, max = 32767) |
| self.editgroup._bary = W.Scrollbar((-15, 14, 16, -14), editor.vscroll, max = 32767) |
| self.editgroup.editor = editor # add editor *after* scrollbars |
| |
| self.editgroup.optionsmenu = W.PopupMenu((-15, -1, 16, 16), []) |
| self.editgroup.optionsmenu.bind('<click>', self.makeoptionsmenu) |
| |
| self.bevelbox = W.BevelBox((0, 0, 0, topbarheight)) |
| self.hline = W.HorizontalLine((0, topbarheight, 0, 0)) |
| self.infotext = W.TextBox((175, 6, -4, 14), backgroundcolor = (0xe000, 0xe000, 0xe000)) |
| self.runbutton = W.BevelButton((6, 4, 80, 16), runButtonLabels[0], self.run) |
| self.runselbutton = W.BevelButton((90, 4, 80, 16), runSelButtonLabels[0], self.runselection) |
| |
| # bind some keys |
| editor.bind("cmdr", self.runbutton.push) |
| editor.bind("enter", self.runselbutton.push) |
| editor.bind("cmdj", self.domenu_gotoline) |
| editor.bind("cmdd", self.domenu_toggledebugger) |
| editor.bind("<idle>", self.updateselection) |
| |
| editor.bind("cmde", searchengine.setfindstring) |
| editor.bind("cmdf", searchengine.show) |
| editor.bind("cmdg", searchengine.findnext) |
| editor.bind("cmdshiftr", searchengine.replace) |
| editor.bind("cmdt", searchengine.replacefind) |
| |
| self.linefield.bind("return", self.dolinefield) |
| self.linefield.bind("enter", self.dolinefield) |
| self.linefield.bind("tab", self.dolinefield) |
| |
| # intercept clicks |
| editor.bind("<click>", self.clickeditor) |
| self.linefield.bind("<click>", self.clicklinefield) |
| |
| def makeoptionsmenu(self): |
| menuitems = [('Font settings\xc9', self.domenu_fontsettings), |
| ("Save options\xc9", self.domenu_options), |
| '-', |
| ('\0' + chr(self.run_as_main) + 'Run as __main__', self.domenu_toggle_run_as_main), |
| #('\0' + chr(self.run_with_interpreter) + 'Run with Interpreter', self.domenu_dtoggle_run_with_interpreter), |
| ('\0' + chr(self.run_with_cl_interpreter) + 'Run with commandline Python', self.domenu_toggle_run_with_cl_interpreter), |
| '-', |
| ('Modularize', self.domenu_modularize), |
| ('Browse namespace\xc9', self.domenu_browsenamespace), |
| '-'] |
| if self.profiling: |
| menuitems = menuitems + [('Disable profiler', self.domenu_toggleprofiler)] |
| else: |
| menuitems = menuitems + [('Enable profiler', self.domenu_toggleprofiler)] |
| if self.editgroup.editor._debugger: |
| menuitems = menuitems + [('Disable debugger', self.domenu_toggledebugger), |
| ('Clear breakpoints', self.domenu_clearbreakpoints), |
| ('Edit breakpoints\xc9', self.domenu_editbreakpoints)] |
| else: |
| menuitems = menuitems + [('Enable debugger', self.domenu_toggledebugger)] |
| self.editgroup.optionsmenu.set(menuitems) |
| |
| def domenu_toggle_run_as_main(self): |
| self.run_as_main = not self.run_as_main |
| self.run_with_interpreter = 0 |
| self.run_with_cl_interpreter = 0 |
| self.editgroup.editor.selectionchanged() |
| |
| def XXdomenu_toggle_run_with_interpreter(self): |
| self.run_with_interpreter = not self.run_with_interpreter |
| self.run_as_main = 0 |
| self.run_with_cl_interpreter = 0 |
| self.editgroup.editor.selectionchanged() |
| |
| def domenu_toggle_run_with_cl_interpreter(self): |
| self.run_with_cl_interpreter = not self.run_with_cl_interpreter |
| self.run_as_main = 0 |
| self.run_with_interpreter = 0 |
| self.editgroup.editor.selectionchanged() |
| |
| def showbreakpoints(self, onoff): |
| self.editgroup.editor.showbreakpoints(onoff) |
| self.debugging = onoff |
| |
| def domenu_clearbreakpoints(self, *args): |
| self.editgroup.editor.clearbreakpoints() |
| |
| def domenu_editbreakpoints(self, *args): |
| self.editgroup.editor.editbreakpoints() |
| |
| def domenu_toggledebugger(self, *args): |
| if not self.debugging: |
| W.SetCursor('watch') |
| self.debugging = not self.debugging |
| self.editgroup.editor.togglebreakpoints() |
| |
| def domenu_toggleprofiler(self, *args): |
| self.profiling = not self.profiling |
| |
| def domenu_browsenamespace(self, *args): |
| import PyBrowser, W |
| W.SetCursor('watch') |
| globals, file, modname = self.getenvironment() |
| if not modname: |
| modname = self.title |
| PyBrowser.Browser(globals, "Object browser: " + modname) |
| |
| def domenu_modularize(self, *args): |
| modname = _filename_as_modname(self.title) |
| if not modname: |
| raise W.AlertError, "Can't modularize \"%s\"" % self.title |
| run_as_main = self.run_as_main |
| self.run_as_main = 0 |
| self.run() |
| self.run_as_main = run_as_main |
| if self.path: |
| file = self.path |
| else: |
| file = self.title |
| |
| if self.globals and not sys.modules.has_key(modname): |
| module = imp.new_module(modname) |
| for attr in self.globals.keys(): |
| setattr(module,attr,self.globals[attr]) |
| sys.modules[modname] = module |
| self.globals = {} |
| |
| def domenu_fontsettings(self, *args): |
| import FontSettings |
| fontsettings = self.editgroup.editor.getfontsettings() |
| tabsettings = self.editgroup.editor.gettabsettings() |
| settings = FontSettings.FontDialog(fontsettings, tabsettings) |
| if settings: |
| fontsettings, tabsettings = settings |
| self.editgroup.editor.setfontsettings(fontsettings) |
| self.editgroup.editor.settabsettings(tabsettings) |
| |
| def domenu_options(self, *args): |
| rv = SaveOptions(self._creator, self._eoln) |
| if rv: |
| self.editgroup.editor.selectionchanged() # ouch... |
| self._creator, self._eoln = rv |
| |
| def clicklinefield(self): |
| if self._currentwidget <> self.linefield: |
| self.linefield.select(1) |
| self.linefield.selectall() |
| return 1 |
| |
| def clickeditor(self): |
| if self._currentwidget <> self.editgroup.editor: |
| self.dolinefield() |
| return 1 |
| |
| def updateselection(self, force = 0): |
| sel = min(self.editgroup.editor.getselection()) |
| lineno = self.editgroup.editor.offsettoline(sel) |
| if lineno <> self.lastlineno or force: |
| self.lastlineno = lineno |
| self.linefield.set(str(lineno + 1)) |
| self.linefield.selview() |
| |
| def dolinefield(self): |
| try: |
| lineno = string.atoi(self.linefield.get()) - 1 |
| if lineno <> self.lastlineno: |
| self.editgroup.editor.selectline(lineno) |
| self.updateselection(1) |
| except: |
| self.updateselection(1) |
| self.editgroup.editor.select(1) |
| |
| def setinfotext(self): |
| if not hasattr(self, 'infotext'): |
| return |
| if self.path: |
| self.infotext.set(self.path) |
| else: |
| self.infotext.set("") |
| |
| def close(self): |
| if self.editgroup.editor.changed: |
| import EasyDialogs |
| Qd.InitCursor() |
| save = EasyDialogs.AskYesNoCancel('Save window "%s" before closing?' % self.title, |
| default=1, no="Don\xd5t save") |
| if save > 0: |
| if self.domenu_save(): |
| return 1 |
| elif save < 0: |
| return 1 |
| self.globals = None |
| W.Window.close(self) |
| |
| def domenu_close(self, *args): |
| return self.close() |
| |
| def domenu_save(self, *args): |
| if not self.path: |
| # Will call us recursively |
| return self.domenu_save_as() |
| data = self.editgroup.editor.get() |
| if self._eoln != '\r': |
| data = string.replace(data, '\r', self._eoln) |
| fp = open(self.path, 'wb') # open file in binary mode, data has '\r' line-endings |
| fp.write(data) |
| fp.close() |
| fss = macfs.FSSpec(self.path) |
| fss.SetCreatorType(self._creator, 'TEXT') |
| self.getsettings() |
| self.writewindowsettings() |
| self.editgroup.editor.changed = 0 |
| self.editgroup.editor.selchanged = 0 |
| import linecache |
| if linecache.cache.has_key(self.path): |
| del linecache.cache[self.path] |
| import macostools |
| macostools.touched(self.path) |
| |
| def can_save(self, menuitem): |
| return self.editgroup.editor.changed or self.editgroup.editor.selchanged |
| |
| def domenu_save_as(self, *args): |
| fss, ok = macfs.StandardPutFile('Save as:', self.title) |
| if not ok: |
| return 1 |
| self.showbreakpoints(0) |
| self.path = fss.as_pathname() |
| self.setinfotext() |
| self.title = os.path.split(self.path)[-1] |
| self.wid.SetWTitle(self.title) |
| self.domenu_save() |
| self.editgroup.editor.setfile(self.getfilename()) |
| app = W.getapplication() |
| app.makeopenwindowsmenu() |
| if hasattr(app, 'makescriptsmenu'): |
| app = W.getapplication() |
| fss, fss_changed = app.scriptsfolder.Resolve() |
| path = fss.as_pathname() |
| if path == self.path[:len(path)]: |
| W.getapplication().makescriptsmenu() |
| |
| def domenu_save_as_applet(self, *args): |
| import buildtools |
| |
| buildtools.DEBUG = 0 # ouch. |
| |
| if self.title[-3:] == ".py": |
| destname = self.title[:-3] |
| else: |
| destname = self.title + ".applet" |
| fss, ok = macfs.StandardPutFile('Save as Applet:', destname) |
| if not ok: |
| return 1 |
| W.SetCursor("watch") |
| destname = fss.as_pathname() |
| if self.path: |
| filename = self.path |
| if filename[-3:] == ".py": |
| rsrcname = filename[:-3] + '.rsrc' |
| else: |
| rsrcname = filename + '.rsrc' |
| else: |
| filename = self.title |
| rsrcname = "" |
| |
| pytext = self.editgroup.editor.get() |
| pytext = string.split(pytext, '\r') |
| pytext = string.join(pytext, '\n') + '\n' |
| try: |
| code = compile(pytext, filename, "exec") |
| except (SyntaxError, EOFError): |
| raise buildtools.BuildError, "Syntax error in script %s" % `filename` |
| |
| # Try removing the output file |
| try: |
| os.remove(destname) |
| except os.error: |
| pass |
| template = buildtools.findtemplate() |
| buildtools.process_common(template, None, code, rsrcname, destname, 0, 1) |
| |
| def domenu_gotoline(self, *args): |
| self.linefield.selectall() |
| self.linefield.select(1) |
| self.linefield.selectall() |
| |
| def domenu_selectline(self, *args): |
| self.editgroup.editor.expandselection() |
| |
| def domenu_find(self, *args): |
| searchengine.show() |
| |
| def domenu_entersearchstring(self, *args): |
| searchengine.setfindstring() |
| |
| def domenu_replace(self, *args): |
| searchengine.replace() |
| |
| def domenu_findnext(self, *args): |
| searchengine.findnext() |
| |
| def domenu_replacefind(self, *args): |
| searchengine.replacefind() |
| |
| def domenu_run(self, *args): |
| self.runbutton.push() |
| |
| def domenu_runselection(self, *args): |
| self.runselbutton.push() |
| |
| def run(self): |
| if self._threadstate == (0, 0): |
| self._run() |
| else: |
| lock = Wthreading.Lock() |
| lock.acquire() |
| self._thread.postException(KeyboardInterrupt) |
| if self._thread.isBlocked(): |
| self._thread.start() |
| lock.release() |
| |
| def _run(self): |
| if self.run_with_interpreter: |
| if self.editgroup.editor.changed: |
| import EasyDialogs |
| Qd.InitCursor() |
| save = EasyDialogs.AskYesNoCancel('Save "%s" before running?' % self.title, 1) |
| if save > 0: |
| if self.domenu_save(): |
| return |
| elif save < 0: |
| return |
| if not self.path: |
| raise W.AlertError, "Can't run unsaved file" |
| self._run_with_interpreter() |
| elif self.run_with_cl_interpreter: |
| if self.editgroup.editor.changed: |
| import EasyDialogs |
| Qd.InitCursor() |
| save = EasyDialogs.AskYesNoCancel('Save "%s" before running?' % self.title, 1) |
| if save > 0: |
| if self.domenu_save(): |
| return |
| elif save < 0: |
| return |
| if not self.path: |
| raise W.AlertError, "Can't run unsaved file" |
| self._run_with_cl_interpreter() |
| else: |
| pytext = self.editgroup.editor.get() |
| globals, file, modname = self.getenvironment() |
| self.execstring(pytext, globals, globals, file, modname) |
| |
| def _run_with_interpreter(self): |
| interp_path = os.path.join(sys.exec_prefix, "PythonInterpreter") |
| if not os.path.exists(interp_path): |
| raise W.AlertError, "Can't find interpreter" |
| import findertools |
| XXX |
| |
| def _run_with_cl_interpreter(self): |
| import Terminal |
| interp_path = os.path.join(sys.exec_prefix, "bin", "python") |
| file_path = self.path |
| if not os.path.exists(interp_path): |
| # This "can happen" if we are running IDE under MacPython. Try |
| # the standard location. |
| interp_path = "/Library/Frameworks/Python.framework/Versions/2.3/bin/python" |
| try: |
| fsr = macfs.FSRef(interp_path) |
| except macfs.Error: |
| raise W.AlertError, "Can't find command-line Python" |
| file_path = macfs.FSRef(macfs.FSSpec(self.path)).as_pathname() |
| cmd = '"%s" "%s" ; exit' % (interp_path, file_path) |
| t = Terminal.Terminal() |
| t.do_script(with_command=cmd) |
| |
| def runselection(self): |
| if self._threadstate == (0, 0): |
| self._runselection() |
| elif self._threadstate == (1, 1): |
| self._thread.block() |
| self.setthreadstate((1, 2)) |
| elif self._threadstate == (1, 2): |
| self._thread.start() |
| self.setthreadstate((1, 1)) |
| |
| def _runselection(self): |
| if self.run_with_interpreter or self.run_with_cl_interpreter: |
| raise W.AlertError, "Can't run selection with Interpreter" |
| globals, file, modname = self.getenvironment() |
| locals = globals |
| # select whole lines |
| self.editgroup.editor.expandselection() |
| |
| # get lineno of first selected line |
| selstart, selend = self.editgroup.editor.getselection() |
| selstart, selend = min(selstart, selend), max(selstart, selend) |
| selfirstline = self.editgroup.editor.offsettoline(selstart) |
| alltext = self.editgroup.editor.get() |
| pytext = alltext[selstart:selend] |
| lines = string.split(pytext, '\r') |
| indent = getminindent(lines) |
| if indent == 1: |
| classname = '' |
| alllines = string.split(alltext, '\r') |
| for i in range(selfirstline - 1, -1, -1): |
| line = alllines[i] |
| if line[:6] == 'class ': |
| classname = string.split(string.strip(line[6:]))[0] |
| classend = identifieRE_match(classname) |
| if classend < 1: |
| raise W.AlertError, "Can't find a class." |
| classname = classname[:classend] |
| break |
| elif line and line[0] not in '\t#': |
| raise W.AlertError, "Can't find a class." |
| else: |
| raise W.AlertError, "Can't find a class." |
| if globals.has_key(classname): |
| klass = globals[classname] |
| else: |
| raise W.AlertError, "Can't find class \"%s\"." % classname |
| # add class def |
| pytext = ("class %s:\n" % classname) + pytext |
| selfirstline = selfirstline - 1 |
| elif indent > 0: |
| raise W.AlertError, "Can't run indented code." |
| |
| # add "newlines" to fool compile/exec: |
| # now a traceback will give the right line number |
| pytext = selfirstline * '\r' + pytext |
| self.execstring(pytext, globals, locals, file, modname) |
| if indent == 1 and globals[classname] is not klass: |
| # update the class in place |
| klass.__dict__.update(globals[classname].__dict__) |
| globals[classname] = klass |
| |
| def setthreadstate(self, state): |
| oldstate = self._threadstate |
| if oldstate[0] <> state[0]: |
| self.runbutton.settitle(runButtonLabels[state[0]]) |
| if oldstate[1] <> state[1]: |
| self.runselbutton.settitle(runSelButtonLabels[state[1]]) |
| self._threadstate = state |
| |
| def _exec_threadwrapper(self, *args, **kwargs): |
| apply(execstring, args, kwargs) |
| self.setthreadstate((0, 0)) |
| self._thread = None |
| |
| def execstring(self, pytext, globals, locals, file, modname): |
| tracebackwindow.hide() |
| # update windows |
| W.getapplication().refreshwindows() |
| if self.run_as_main: |
| modname = "__main__" |
| if self.path: |
| dir = os.path.dirname(self.path) |
| savedir = os.getcwd() |
| os.chdir(dir) |
| sys.path.insert(0, dir) |
| else: |
| cwdindex = None |
| try: |
| if haveThreading: |
| self._thread = Wthreading.Thread(os.path.basename(file), |
| self._exec_threadwrapper, pytext, globals, locals, file, self.debugging, |
| modname, self.profiling) |
| self.setthreadstate((1, 1)) |
| self._thread.start() |
| else: |
| execstring(pytext, globals, locals, file, self.debugging, |
| modname, self.profiling) |
| finally: |
| if self.path: |
| os.chdir(savedir) |
| del sys.path[0] |
| |
| def getenvironment(self): |
| if self.path: |
| file = self.path |
| dir = os.path.dirname(file) |
| # check if we're part of a package |
| modname = "" |
| while os.path.exists(os.path.join(dir, "__init__.py")): |
| dir, dirname = os.path.split(dir) |
| modname = dirname + '.' + modname |
| subname = _filename_as_modname(self.title) |
| if subname is None: |
| return self.globals, file, None |
| if modname: |
| if subname == "__init__": |
| # strip trailing period |
| modname = modname[:-1] |
| else: |
| modname = modname + subname |
| else: |
| modname = subname |
| if sys.modules.has_key(modname): |
| globals = sys.modules[modname].__dict__ |
| self.globals = {} |
| else: |
| globals = self.globals |
| modname = subname |
| else: |
| file = '<%s>' % self.title |
| globals = self.globals |
| modname = file |
| return globals, file, modname |
| |
| def write(self, stuff): |
| """for use as stdout""" |
| self._buf = self._buf + stuff |
| if '\n' in self._buf: |
| self.flush() |
| |
| def flush(self): |
| stuff = string.split(self._buf, '\n') |
| stuff = string.join(stuff, '\r') |
| end = self.editgroup.editor.ted.WEGetTextLength() |
| self.editgroup.editor.ted.WESetSelection(end, end) |
| self.editgroup.editor.ted.WEInsert(stuff, None, None) |
| self.editgroup.editor.updatescrollbars() |
| self._buf = "" |
| # ? optional: |
| #self.wid.SelectWindow() |
| |
| def getclasslist(self): |
| from string import find, strip |
| methodRE = re.compile(r"\r[ \t]+def ") |
| findMethod = methodRE.search |
| editor = self.editgroup.editor |
| text = editor.get() |
| list = [] |
| append = list.append |
| functag = "func" |
| classtag = "class" |
| methodtag = "method" |
| pos = -1 |
| if text[:4] == 'def ': |
| append((pos + 4, functag)) |
| pos = 4 |
| while 1: |
| pos = find(text, '\rdef ', pos + 1) |
| if pos < 0: |
| break |
| append((pos + 5, functag)) |
| pos = -1 |
| if text[:6] == 'class ': |
| append((pos + 6, classtag)) |
| pos = 6 |
| while 1: |
| pos = find(text, '\rclass ', pos + 1) |
| if pos < 0: |
| break |
| append((pos + 7, classtag)) |
| pos = 0 |
| while 1: |
| m = findMethod(text, pos + 1) |
| if m is None: |
| break |
| pos = m.regs[0][0] |
| #pos = find(text, '\r\tdef ', pos + 1) |
| append((m.regs[0][1], methodtag)) |
| list.sort() |
| classlist = [] |
| methodlistappend = None |
| offsetToLine = editor.ted.WEOffsetToLine |
| getLineRange = editor.ted.WEGetLineRange |
| append = classlist.append |
| for pos, tag in list: |
| lineno = offsetToLine(pos) |
| lineStart, lineEnd = getLineRange(lineno) |
| line = strip(text[pos:lineEnd]) |
| line = line[:identifieRE_match(line)] |
| if tag is functag: |
| append(("def " + line, lineno + 1)) |
| methodlistappend = None |
| elif tag is classtag: |
| append(["class " + line]) |
| methodlistappend = classlist[-1].append |
| elif methodlistappend and tag is methodtag: |
| methodlistappend(("def " + line, lineno + 1)) |
| return classlist |
| |
| def popselectline(self, lineno): |
| self.editgroup.editor.selectline(lineno - 1) |
| |
| def selectline(self, lineno, charoffset = 0): |
| self.editgroup.editor.selectline(lineno - 1, charoffset) |
| |
| class _saveoptions: |
| |
| def __init__(self, creator, eoln): |
| self.rv = None |
| self.eoln = eoln |
| self.w = w = W.ModalDialog((260, 160), 'Save options') |
| radiobuttons = [] |
| w.label = W.TextBox((8, 8, 80, 18), "File creator:") |
| w.ide_radio = W.RadioButton((8, 22, 160, 18), "This application", radiobuttons, self.ide_hit) |
| w.interp_radio = W.RadioButton((8, 42, 160, 18), "MacPython Interpreter", radiobuttons, self.interp_hit) |
| w.interpx_radio = W.RadioButton((8, 62, 160, 18), "OSX PythonW Interpreter", radiobuttons, self.interpx_hit) |
| w.other_radio = W.RadioButton((8, 82, 50, 18), "Other:", radiobuttons) |
| w.other_creator = W.EditText((62, 82, 40, 20), creator, self.otherselect) |
| w.none_radio = W.RadioButton((8, 102, 160, 18), "None", radiobuttons, self.none_hit) |
| w.cancelbutton = W.Button((-180, -30, 80, 16), "Cancel", self.cancelbuttonhit) |
| w.okbutton = W.Button((-90, -30, 80, 16), "Done", self.okbuttonhit) |
| w.setdefaultbutton(w.okbutton) |
| if creator == 'Pyth': |
| w.interp_radio.set(1) |
| elif creator == W._signature: |
| w.ide_radio.set(1) |
| elif creator == 'PytX': |
| w.interpx_radio.set(1) |
| elif creator == '\0\0\0\0': |
| w.none_radio.set(1) |
| else: |
| w.other_radio.set(1) |
| |
| w.eolnlabel = W.TextBox((168, 8, 80, 18), "Newline style:") |
| radiobuttons = [] |
| w.unix_radio = W.RadioButton((168, 22, 80, 18), "Unix", radiobuttons, self.unix_hit) |
| w.mac_radio = W.RadioButton((168, 42, 80, 18), "Macintosh", radiobuttons, self.mac_hit) |
| w.win_radio = W.RadioButton((168, 62, 80, 18), "Windows", radiobuttons, self.win_hit) |
| if self.eoln == '\n': |
| w.unix_radio.set(1) |
| elif self.eoln == '\r\n': |
| w.win_radio.set(1) |
| else: |
| w.mac_radio.set(1) |
| |
| w.bind("cmd.", w.cancelbutton.push) |
| w.open() |
| |
| def ide_hit(self): |
| self.w.other_creator.set(W._signature) |
| |
| def interp_hit(self): |
| self.w.other_creator.set("Pyth") |
| |
| def interpx_hit(self): |
| self.w.other_creator.set("PytX") |
| |
| def none_hit(self): |
| self.w.other_creator.set("\0\0\0\0") |
| |
| def otherselect(self, *args): |
| sel_from, sel_to = self.w.other_creator.getselection() |
| creator = self.w.other_creator.get()[:4] |
| creator = creator + " " * (4 - len(creator)) |
| self.w.other_creator.set(creator) |
| self.w.other_creator.setselection(sel_from, sel_to) |
| self.w.other_radio.set(1) |
| |
| def mac_hit(self): |
| self.eoln = '\r' |
| |
| def unix_hit(self): |
| self.eoln = '\n' |
| |
| def win_hit(self): |
| self.eoln = '\r\n' |
| |
| def cancelbuttonhit(self): |
| self.w.close() |
| |
| def okbuttonhit(self): |
| self.rv = (self.w.other_creator.get()[:4], self.eoln) |
| self.w.close() |
| |
| |
| def SaveOptions(creator, eoln): |
| s = _saveoptions(creator, eoln) |
| return s.rv |
| |
| |
| def _escape(where, what) : |
| return string.join(string.split(where, what), '\\' + what) |
| |
| def _makewholewordpattern(word): |
| # first, escape special regex chars |
| for esc in "\\[]()|.*^+$?": |
| word = _escape(word, esc) |
| notwordcharspat = '[^' + _wordchars + ']' |
| pattern = '(' + word + ')' |
| if word[0] in _wordchars: |
| pattern = notwordcharspat + pattern |
| if word[-1] in _wordchars: |
| pattern = pattern + notwordcharspat |
| return re.compile(pattern) |
| |
| |
| class SearchEngine: |
| |
| def __init__(self): |
| self.visible = 0 |
| self.w = None |
| self.parms = { "find": "", |
| "replace": "", |
| "wrap": 1, |
| "casesens": 1, |
| "wholeword": 1 |
| } |
| import MacPrefs |
| prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath) |
| if prefs.searchengine: |
| self.parms["casesens"] = prefs.searchengine.casesens |
| self.parms["wrap"] = prefs.searchengine.wrap |
| self.parms["wholeword"] = prefs.searchengine.wholeword |
| |
| def show(self): |
| self.visible = 1 |
| if self.w: |
| self.w.wid.ShowWindow() |
| self.w.wid.SelectWindow() |
| self.w.find.edit.select(1) |
| self.w.find.edit.selectall() |
| return |
| self.w = W.Dialog((420, 150), "Find") |
| |
| self.w.find = TitledEditText((10, 4, 300, 36), "Search for:") |
| self.w.replace = TitledEditText((10, 100, 300, 36), "Replace with:") |
| |
| self.w.boxes = W.Group((10, 50, 300, 40)) |
| self.w.boxes.casesens = W.CheckBox((0, 0, 100, 16), "Case sensitive") |
| self.w.boxes.wholeword = W.CheckBox((0, 20, 100, 16), "Whole word") |
| self.w.boxes.wrap = W.CheckBox((110, 0, 100, 16), "Wrap around") |
| |
| self.buttons = [ ("Find", "cmdf", self.find), |
| ("Replace", "cmdr", self.replace), |
| ("Replace all", None, self.replaceall), |
| ("Don't find", "cmdd", self.dont), |
| ("Cancel", "cmd.", self.cancel) |
| ] |
| for i in range(len(self.buttons)): |
| bounds = -90, 22 + i * 24, 80, 16 |
| title, shortcut, callback = self.buttons[i] |
| self.w[title] = W.Button(bounds, title, callback) |
| if shortcut: |
| self.w.bind(shortcut, self.w[title].push) |
| self.w.setdefaultbutton(self.w["Don't find"]) |
| self.w.find.edit.bind("<key>", self.key) |
| self.w.bind("<activate>", self.activate) |
| self.w.bind("<close>", self.close) |
| self.w.open() |
| self.setparms() |
| self.w.find.edit.select(1) |
| self.w.find.edit.selectall() |
| self.checkbuttons() |
| |
| def close(self): |
| self.hide() |
| return -1 |
| |
| def key(self, char, modifiers): |
| self.w.find.edit.key(char, modifiers) |
| self.checkbuttons() |
| return 1 |
| |
| def activate(self, onoff): |
| if onoff: |
| self.checkbuttons() |
| |
| def checkbuttons(self): |
| editor = findeditor(self) |
| if editor: |
| if self.w.find.get(): |
| for title, cmd, call in self.buttons[:-2]: |
| self.w[title].enable(1) |
| self.w.setdefaultbutton(self.w["Find"]) |
| else: |
| for title, cmd, call in self.buttons[:-2]: |
| self.w[title].enable(0) |
| self.w.setdefaultbutton(self.w["Don't find"]) |
| else: |
| for title, cmd, call in self.buttons[:-2]: |
| self.w[title].enable(0) |
| self.w.setdefaultbutton(self.w["Don't find"]) |
| |
| def find(self): |
| self.getparmsfromwindow() |
| if self.findnext(): |
| self.hide() |
| |
| def replace(self): |
| editor = findeditor(self) |
| if not editor: |
| return |
| if self.visible: |
| self.getparmsfromwindow() |
| text = editor.getselectedtext() |
| find = self.parms["find"] |
| if not self.parms["casesens"]: |
| find = string.lower(find) |
| text = string.lower(text) |
| if text == find: |
| self.hide() |
| editor.insert(self.parms["replace"]) |
| |
| def replaceall(self): |
| editor = findeditor(self) |
| if not editor: |
| return |
| if self.visible: |
| self.getparmsfromwindow() |
| W.SetCursor("watch") |
| find = self.parms["find"] |
| if not find: |
| return |
| findlen = len(find) |
| replace = self.parms["replace"] |
| replacelen = len(replace) |
| Text = editor.get() |
| if not self.parms["casesens"]: |
| find = string.lower(find) |
| text = string.lower(Text) |
| else: |
| text = Text |
| newtext = "" |
| pos = 0 |
| counter = 0 |
| while 1: |
| if self.parms["wholeword"]: |
| wholewordRE = _makewholewordpattern(find) |
| match = wholewordRE.search(text, pos) |
| if match: |
| pos = match.start(1) |
| else: |
| pos = -1 |
| else: |
| pos = string.find(text, find, pos) |
| if pos < 0: |
| break |
| counter = counter + 1 |
| text = text[:pos] + replace + text[pos + findlen:] |
| Text = Text[:pos] + replace + Text[pos + findlen:] |
| pos = pos + replacelen |
| W.SetCursor("arrow") |
| if counter: |
| self.hide() |
| import EasyDialogs |
| from Carbon import Res |
| editor.textchanged() |
| editor.selectionchanged() |
| editor.set(Text) |
| EasyDialogs.Message("Replaced %d occurrences" % counter) |
| |
| def dont(self): |
| self.getparmsfromwindow() |
| self.hide() |
| |
| def replacefind(self): |
| self.replace() |
| self.findnext() |
| |
| def setfindstring(self): |
| editor = findeditor(self) |
| if not editor: |
| return |
| find = editor.getselectedtext() |
| if not find: |
| return |
| self.parms["find"] = find |
| if self.w: |
| self.w.find.edit.set(self.parms["find"]) |
| self.w.find.edit.selectall() |
| |
| def findnext(self): |
| editor = findeditor(self) |
| if not editor: |
| return |
| find = self.parms["find"] |
| if not find: |
| return |
| text = editor.get() |
| if not self.parms["casesens"]: |
| find = string.lower(find) |
| text = string.lower(text) |
| selstart, selend = editor.getselection() |
| selstart, selend = min(selstart, selend), max(selstart, selend) |
| if self.parms["wholeword"]: |
| wholewordRE = _makewholewordpattern(find) |
| match = wholewordRE.search(text, selend) |
| if match: |
| pos = match.start(1) |
| else: |
| pos = -1 |
| else: |
| pos = string.find(text, find, selend) |
| if pos >= 0: |
| editor.setselection(pos, pos + len(find)) |
| return 1 |
| elif self.parms["wrap"]: |
| if self.parms["wholeword"]: |
| match = wholewordRE.search(text, 0) |
| if match: |
| pos = match.start(1) |
| else: |
| pos = -1 |
| else: |
| pos = string.find(text, find) |
| if selstart > pos >= 0: |
| editor.setselection(pos, pos + len(find)) |
| return 1 |
| |
| def setparms(self): |
| for key, value in self.parms.items(): |
| try: |
| self.w[key].set(value) |
| except KeyError: |
| self.w.boxes[key].set(value) |
| |
| def getparmsfromwindow(self): |
| if not self.w: |
| return |
| for key, value in self.parms.items(): |
| try: |
| value = self.w[key].get() |
| except KeyError: |
| value = self.w.boxes[key].get() |
| self.parms[key] = value |
| |
| def cancel(self): |
| self.hide() |
| self.setparms() |
| |
| def hide(self): |
| if self.w: |
| self.w.wid.HideWindow() |
| self.visible = 0 |
| |
| def writeprefs(self): |
| import MacPrefs |
| self.getparmsfromwindow() |
| prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath) |
| prefs.searchengine.casesens = self.parms["casesens"] |
| prefs.searchengine.wrap = self.parms["wrap"] |
| prefs.searchengine.wholeword = self.parms["wholeword"] |
| prefs.save() |
| |
| |
| class TitledEditText(W.Group): |
| |
| def __init__(self, possize, title, text = ""): |
| W.Group.__init__(self, possize) |
| self.title = W.TextBox((0, 0, 0, 16), title) |
| self.edit = W.EditText((0, 16, 0, 0), text) |
| |
| def set(self, value): |
| self.edit.set(value) |
| |
| def get(self): |
| return self.edit.get() |
| |
| |
| class ClassFinder(W.PopupWidget): |
| |
| def click(self, point, modifiers): |
| W.SetCursor("watch") |
| self.set(self._parentwindow.getclasslist()) |
| W.PopupWidget.click(self, point, modifiers) |
| |
| |
| def getminindent(lines): |
| indent = -1 |
| for line in lines: |
| stripped = string.strip(line) |
| if not stripped or stripped[0] == '#': |
| continue |
| if indent < 0 or line[:indent] <> indent * '\t': |
| indent = 0 |
| for c in line: |
| if c <> '\t': |
| break |
| indent = indent + 1 |
| return indent |
| |
| |
| def getoptionkey(): |
| return not not ord(Evt.GetKeys()[7]) & 0x04 |
| |
| |
| def execstring(pytext, globals, locals, filename="<string>", debugging=0, |
| modname="__main__", profiling=0): |
| if debugging: |
| import PyDebugger, bdb |
| BdbQuit = bdb.BdbQuit |
| else: |
| BdbQuit = 'BdbQuitDummyException' |
| pytext = string.split(pytext, '\r') |
| pytext = string.join(pytext, '\n') + '\n' |
| W.SetCursor("watch") |
| globals['__name__'] = modname |
| globals['__file__'] = filename |
| sys.argv = [filename] |
| try: |
| code = compile(pytext, filename, "exec") |
| except: |
| # XXXX BAAAADDD.... We let tracebackwindow decide to treat SyntaxError |
| # special. That's wrong because THIS case is special (could be literal |
| # overflow!) and SyntaxError could mean we need a traceback (syntax error |
| # in imported module!!! |
| tracebackwindow.traceback(1, filename) |
| return |
| try: |
| if debugging: |
| if haveThreading: |
| lock = Wthreading.Lock() |
| lock.acquire() |
| PyDebugger.startfromhere() |
| lock.release() |
| else: |
| PyDebugger.startfromhere() |
| elif not haveThreading: |
| if hasattr(MacOS, 'EnableAppswitch'): |
| MacOS.EnableAppswitch(0) |
| try: |
| if profiling: |
| import profile, ProfileBrowser |
| p = profile.Profile() |
| p.set_cmd(filename) |
| try: |
| p.runctx(code, globals, locals) |
| finally: |
| import pstats |
| |
| stats = pstats.Stats(p) |
| ProfileBrowser.ProfileBrowser(stats) |
| else: |
| exec code in globals, locals |
| finally: |
| if not haveThreading: |
| if hasattr(MacOS, 'EnableAppswitch'): |
| MacOS.EnableAppswitch(-1) |
| except W.AlertError, detail: |
| raise W.AlertError, detail |
| except (KeyboardInterrupt, BdbQuit): |
| pass |
| except SystemExit, arg: |
| if arg.code: |
| sys.stderr.write("Script exited with status code: %s\n" % repr(arg.code)) |
| except: |
| if haveThreading: |
| import continuation |
| lock = Wthreading.Lock() |
| lock.acquire() |
| if debugging: |
| sys.settrace(None) |
| PyDebugger.postmortem(sys.exc_type, sys.exc_value, sys.exc_traceback) |
| return |
| else: |
| tracebackwindow.traceback(1, filename) |
| if haveThreading: |
| lock.release() |
| if debugging: |
| sys.settrace(None) |
| PyDebugger.stop() |
| |
| |
| _identifieRE = re.compile(r"[A-Za-z_][A-Za-z_0-9]*") |
| |
| def identifieRE_match(str): |
| match = _identifieRE.match(str) |
| if not match: |
| return -1 |
| return match.end() |
| |
| def _filename_as_modname(fname): |
| if fname[-3:] == '.py': |
| modname = fname[:-3] |
| match = _identifieRE.match(modname) |
| if match and match.start() == 0 and match.end() == len(modname): |
| return string.join(string.split(modname, '.'), '_') |
| |
| def findeditor(topwindow, fromtop = 0): |
| wid = MyFrontWindow() |
| if not fromtop: |
| if topwindow.w and wid == topwindow.w.wid: |
| wid = topwindow.w.wid.GetNextWindow() |
| if not wid: |
| return |
| app = W.getapplication() |
| if app._windows.has_key(wid): # KeyError otherwise can happen in RoboFog :-( |
| window = W.getapplication()._windows[wid] |
| else: |
| return |
| if not isinstance(window, Editor): |
| return |
| return window.editgroup.editor |
| |
| |
| class _EditorDefaultSettings: |
| |
| def __init__(self): |
| self.template = "%s, %d point" |
| self.fontsettings, self.tabsettings, self.windowsize = geteditorprefs() |
| self.w = W.Dialog((328, 120), "Editor default settings") |
| self.w.setfontbutton = W.Button((8, 8, 80, 16), "Set font\xc9", self.dofont) |
| self.w.fonttext = W.TextBox((98, 10, -8, 14), self.template % (self.fontsettings[0], self.fontsettings[2])) |
| |
| self.w.picksizebutton = W.Button((8, 50, 80, 16), "Front window", self.picksize) |
| self.w.xsizelabel = W.TextBox((98, 32, 40, 14), "Width:") |
| self.w.ysizelabel = W.TextBox((148, 32, 40, 14), "Height:") |
| self.w.xsize = W.EditText((98, 48, 40, 20), `self.windowsize[0]`) |
| self.w.ysize = W.EditText((148, 48, 40, 20), `self.windowsize[1]`) |
| |
| self.w.cancelbutton = W.Button((-180, -26, 80, 16), "Cancel", self.cancel) |
| self.w.okbutton = W.Button((-90, -26, 80, 16), "Done", self.ok) |
| self.w.setdefaultbutton(self.w.okbutton) |
| self.w.bind('cmd.', self.w.cancelbutton.push) |
| self.w.open() |
| |
| def picksize(self): |
| app = W.getapplication() |
| editor = findeditor(self) |
| if editor is not None: |
| width, height = editor._parentwindow._bounds[2:] |
| self.w.xsize.set(`width`) |
| self.w.ysize.set(`height`) |
| else: |
| raise W.AlertError, "No edit window found" |
| |
| def dofont(self): |
| import FontSettings |
| settings = FontSettings.FontDialog(self.fontsettings, self.tabsettings) |
| if settings: |
| self.fontsettings, self.tabsettings = settings |
| sys.exc_traceback = None |
| self.w.fonttext.set(self.template % (self.fontsettings[0], self.fontsettings[2])) |
| |
| def close(self): |
| self.w.close() |
| del self.w |
| |
| def cancel(self): |
| self.close() |
| |
| def ok(self): |
| try: |
| width = string.atoi(self.w.xsize.get()) |
| except: |
| self.w.xsize.select(1) |
| self.w.xsize.selectall() |
| raise W.AlertError, "Bad number for window width" |
| try: |
| height = string.atoi(self.w.ysize.get()) |
| except: |
| self.w.ysize.select(1) |
| self.w.ysize.selectall() |
| raise W.AlertError, "Bad number for window height" |
| self.windowsize = width, height |
| seteditorprefs(self.fontsettings, self.tabsettings, self.windowsize) |
| self.close() |
| |
| def geteditorprefs(): |
| import MacPrefs |
| prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath) |
| try: |
| fontsettings = prefs.pyedit.fontsettings |
| tabsettings = prefs.pyedit.tabsettings |
| windowsize = prefs.pyedit.windowsize |
| except: |
| fontsettings = prefs.pyedit.fontsettings = ("Geneva", 0, 10, (0, 0, 0)) |
| tabsettings = prefs.pyedit.tabsettings = (8, 1) |
| windowsize = prefs.pyedit.windowsize = (500, 250) |
| sys.exc_traceback = None |
| return fontsettings, tabsettings, windowsize |
| |
| def seteditorprefs(fontsettings, tabsettings, windowsize): |
| import MacPrefs |
| prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath) |
| prefs.pyedit.fontsettings = fontsettings |
| prefs.pyedit.tabsettings = tabsettings |
| prefs.pyedit.windowsize = windowsize |
| prefs.save() |
| |
| _defaultSettingsEditor = None |
| |
| def EditorDefaultSettings(): |
| global _defaultSettingsEditor |
| if _defaultSettingsEditor is None or not hasattr(_defaultSettingsEditor, "w"): |
| _defaultSettingsEditor = _EditorDefaultSettings() |
| else: |
| _defaultSettingsEditor.w.select() |
| |
| def resolvealiases(path): |
| try: |
| return macfs.ResolveAliasFile(path)[0].as_pathname() |
| except (macfs.error, ValueError), (error, str): |
| if error <> -120: |
| raise |
| dir, file = os.path.split(path) |
| return os.path.join(resolvealiases(dir), file) |
| |
| searchengine = SearchEngine() |
| tracebackwindow = Wtraceback.TraceBack() |