home *** CD-ROM | disk | FTP | other *** search
/ MacHack 2000 / MacHack 2000.toast / pc / The Hacks / MacHacksBug / Python 1.5.2c1 / Mac / Tools / IDE / PyEdit.py < prev    next >
Encoding:
Python Source  |  2000-06-23  |  33.0 KB  |  1,197 lines

  1. """A (less & less) simple Python editor"""
  2.  
  3. import W
  4. import Wtraceback
  5. from Wkeys import *
  6.  
  7. import macfs
  8. import MacOS
  9. import Win
  10. import Res
  11. import Evt
  12. import os
  13. import imp
  14. import sys
  15. import string
  16. import marshal
  17. import regex
  18.  
  19. _scriptuntitledcounter = 1
  20. _wordchars = string.letters + string.digits + "_"
  21.  
  22.  
  23. class Editor(W.Window):
  24.     
  25.     def __init__(self, path = "", title = ""):
  26.         defaultfontsettings, defaulttabsettings, defaultwindowsize = geteditorprefs()
  27.         global _scriptuntitledcounter
  28.         if not path:
  29.             if title:
  30.                 self.title = title
  31.             else:
  32.                 self.title = "Untitled Script " + `_scriptuntitledcounter`
  33.                 _scriptuntitledcounter = _scriptuntitledcounter + 1
  34.             text = ""
  35.             self._creator = W._signature
  36.         elif os.path.exists(path):
  37.             path = resolvealiases(path)
  38.             dir, name = os.path.split(path)
  39.             self.title = name
  40.             f = open(path, "rb")
  41.             text = f.read()
  42.             f.close()
  43.             fss = macfs.FSSpec(path)
  44.             self._creator, filetype = fss.GetCreatorType()
  45.         else:
  46.             raise IOError, "file '%s' does not exist" % path
  47.         self.path = path
  48.         
  49.         if '\n' in text:
  50.             import EasyDialogs
  51.             if string.find(text, '\r\n') >= 0:
  52.                 sourceOS = 'DOS'
  53.                 searchString = '\r\n'
  54.             else:
  55.                 sourceOS = 'UNIX'
  56.                 searchString = '\n'
  57.             change = EasyDialogs.AskYesNoCancel('“%s” contains %s-style line feeds. Change them to MacOS carriage returns?' % (self.title, sourceOS), 1)
  58.             # bug: Cancel is treated as No
  59.             if change > 0:
  60.                 text = string.replace(text, searchString, '\r')
  61.         else:
  62.             change = 0
  63.         
  64.         self.settings = {}
  65.         if self.path:
  66.             self.readwindowsettings()
  67.         if self.settings.has_key("windowbounds"):
  68.             bounds = self.settings["windowbounds"]
  69.         else:
  70.             bounds = defaultwindowsize
  71.         if self.settings.has_key("fontsettings"):
  72.             self.fontsettings = self.settings["fontsettings"]
  73.         else:
  74.             self.fontsettings = defaultfontsettings
  75.         if self.settings.has_key("tabsize"):
  76.             try:
  77.                 self.tabsettings = (tabsize, tabmode) = self.settings["tabsize"]
  78.             except:
  79.                 self.tabsettings = defaulttabsettings
  80.         else:
  81.             self.tabsettings = defaulttabsettings
  82.         
  83.         W.Window.__init__(self, bounds, self.title, minsize = (330, 120), tabbable = 0)
  84.         self.setupwidgets(text)
  85.         if change > 0:
  86.                 self.editgroup.editor.changed = 1
  87.         
  88.         if self.settings.has_key("selection"):
  89.             selstart, selend = self.settings["selection"]
  90.             self.setselection(selstart, selend)
  91.         self.open()
  92.         self.setinfotext()
  93.         self.globals = {}
  94.         self._buf = ""  # for write method
  95.         self.debugging = 0
  96.         self.profiling = 0
  97.         if self.settings.has_key("run_as_main"):
  98.             self.run_as_main = self.settings["run_as_main"]
  99.         else:
  100.             self.run_as_main = 0
  101.     
  102.     def readwindowsettings(self):
  103.         try:
  104.             resref = Res.OpenResFile(self.path)
  105.         except Res.Error:
  106.             return
  107.         try:
  108.             Res.UseResFile(resref)
  109.             data = Res.Get1Resource('PyWS', 128)
  110.             self.settings = marshal.loads(data.data)
  111.         except:
  112.             pass
  113.         Res.CloseResFile(resref)
  114.         
  115.     def writewindowsettings(self):
  116.         try:
  117.             resref = Res.OpenResFile(self.path)
  118.         except Res.Error:
  119.             Res.CreateResFile(self.path)
  120.             resref = Res.OpenResFile(self.path)
  121.         try:
  122.             data = Res.Resource(marshal.dumps(self.settings))
  123.             Res.UseResFile(resref)
  124.             try:
  125.                 temp = Res.Get1Resource('PyWS', 128)
  126.                 temp.RemoveResource()
  127.             except Res.Error:
  128.                 pass
  129.             data.AddResource('PyWS', 128, "window settings")
  130.         finally:
  131.             Res.UpdateResFile(resref)
  132.             Res.CloseResFile(resref)
  133.     
  134.     def getsettings(self):
  135.         self.settings = {}
  136.         self.settings["windowbounds"] = self.getbounds()
  137.         self.settings["selection"] = self.getselection()
  138.         self.settings["fontsettings"] = self.editgroup.editor.getfontsettings()
  139.         self.settings["tabsize"] = self.editgroup.editor.gettabsettings()
  140.         self.settings["run_as_main"] = self.run_as_main
  141.     
  142.     def get(self):
  143.         return self.editgroup.editor.get()
  144.     
  145.     def getselection(self):
  146.         return self.editgroup.editor.ted.WEGetSelection()
  147.     
  148.     def setselection(self, selstart, selend):
  149.         self.editgroup.editor.setselection(selstart, selend)
  150.     
  151.     def getfilename(self):
  152.         if self.path:
  153.             return self.path
  154.         return '<%s>' % self.title
  155.     
  156.     def setupwidgets(self, text):
  157.         topbarheight = 24
  158.         popfieldwidth = 80
  159.         self.lastlineno = None
  160.         
  161.         # make an editor
  162.         self.editgroup = W.Group((0, topbarheight + 1, 0, 0))
  163.         editor = W.PyEditor((0, 0, -15,-15), text, 
  164.                 fontsettings = self.fontsettings, 
  165.                 tabsettings = self.tabsettings,
  166.                 file = self.getfilename())
  167.         
  168.         # make the widgets
  169.         self.popfield = ClassFinder((popfieldwidth - 17, -15, 16, 16), [], self.popselectline)
  170.         self.linefield = W.EditText((-1, -15, popfieldwidth - 15, 16), inset = (6, 1))
  171.         self.editgroup._barx = W.Scrollbar((popfieldwidth - 2, -15, -14, 16), editor.hscroll, max = 32767)
  172.         self.editgroup._bary = W.Scrollbar((-15, 14, 16, -14), editor.vscroll, max = 32767)
  173.         self.editgroup.editor = editor    # add editor *after* scrollbars
  174.         
  175.         self.editgroup.optionsmenu = W.PopupMenu((-15, -1, 16, 16), [])
  176.         self.editgroup.optionsmenu.bind('<click>', self.makeoptionsmenu)
  177.         
  178.         self.bevelbox = W.BevelBox((0, 0, 0, topbarheight))
  179.         self.hline = W.HorizontalLine((0, topbarheight, 0, 0))
  180.         self.infotext = W.TextBox((175, 6, -4, 14), backgroundcolor = (0xe000, 0xe000, 0xe000))
  181.         self.runbutton = W.Button((5, 4, 80, 16), "Run all", self.run)
  182.         self.runselbutton = W.Button((90, 4, 80, 16), "Run selection", self.runselection)
  183.         
  184.         # bind some keys
  185.         editor.bind("cmdr", self.runbutton.push)
  186.         editor.bind("enter", self.runselbutton.push)
  187.         editor.bind("cmdj", self.domenu_gotoline)
  188.         editor.bind("cmdd", self.domenu_toggledebugger)
  189.         editor.bind("<idle>", self.updateselection)
  190.         
  191.         editor.bind("cmde", searchengine.setfindstring)
  192.         editor.bind("cmdf", searchengine.show)
  193.         editor.bind("cmdg", searchengine.findnext)
  194.         editor.bind("cmdshiftr", searchengine.replace)
  195.         editor.bind("cmdt", searchengine.replacefind)
  196.         
  197.         self.linefield.bind("return", self.dolinefield)
  198.         self.linefield.bind("enter", self.dolinefield)
  199.         self.linefield.bind("tab", self.dolinefield)
  200.         
  201.         # intercept clicks
  202.         editor.bind("<click>", self.clickeditor)
  203.         self.linefield.bind("<click>", self.clicklinefield)
  204.     
  205.     def makeoptionsmenu(self):
  206.         menuitems = [('Font settings…', self.domenu_fontsettings), 
  207.                 ("Save options…", self.domenu_options),
  208.                 '-',
  209.                 ('\0' + chr(self.run_as_main) + 'Run as __main__', self.domenu_toggle_run_as_main), 
  210.                 ('Modularize', self.domenu_modularize),
  211.                 ('Browse namespace…', self.domenu_browsenamespace), 
  212.                 '-']
  213.         if self.profiling:
  214.             menuitems = menuitems + [('Disable profiler', self.domenu_toggleprofiler)]
  215.         else:
  216.             menuitems = menuitems + [('Enable profiler', self.domenu_toggleprofiler)]
  217.         if self.editgroup.editor._debugger:
  218.             menuitems = menuitems + [('Disable debugger', self.domenu_toggledebugger),
  219.                 ('Clear breakpoints', self.domenu_clearbreakpoints),
  220.                 ('Edit breakpoints…', self.domenu_editbreakpoints)]
  221.         else:
  222.             menuitems = menuitems + [('Enable debugger', self.domenu_toggledebugger)]
  223.         self.editgroup.optionsmenu.set(menuitems)
  224.     
  225.     def domenu_toggle_run_as_main(self):
  226.         self.run_as_main = not self.run_as_main
  227.         self.editgroup.editor.selchanged = 1
  228.     
  229.     def showbreakpoints(self, onoff):
  230.         self.editgroup.editor.showbreakpoints(onoff)
  231.         self.debugging = onoff
  232.     
  233.     def domenu_clearbreakpoints(self, *args):
  234.         self.editgroup.editor.clearbreakpoints()
  235.     
  236.     def domenu_editbreakpoints(self, *args):
  237.         self.editgroup.editor.editbreakpoints()
  238.     
  239.     def domenu_toggledebugger(self, *args):
  240.         if not self.debugging:
  241.             W.SetCursor('watch')
  242.         self.debugging = not self.debugging
  243.         self.editgroup.editor.togglebreakpoints()
  244.         
  245.     def domenu_toggleprofiler(self, *args):
  246.         self.profiling = not self.profiling
  247.     
  248.     def domenu_browsenamespace(self, *args):
  249.         import PyBrowser, W
  250.         W.SetCursor('watch')
  251.         globals, file, modname = self.getenvironment()
  252.         if not modname:
  253.             modname = self.title
  254.         PyBrowser.Browser(globals, "Object browser: " + modname)
  255.     
  256.     def domenu_modularize(self, *args):
  257.         modname = _filename_as_modname(self.title)
  258.         if not modname:
  259.             raise W.AlertError, 'Can’t modularize “%s”' % self.title
  260.         run_as_main = self.run_as_main
  261.         self.run_as_main = 0
  262.         self.run()
  263.         self.run_as_main = run_as_main
  264.         if self.path:
  265.             file = self.path
  266.         else:
  267.             file = self.title
  268.         
  269.         if self.globals and not sys.modules.has_key(modname):
  270.             module = imp.new_module(modname)
  271.             for attr in self.globals.keys():
  272.                 setattr(module,attr,self.globals[attr])
  273.             sys.modules[modname] = module
  274.             self.globals = {}
  275.     
  276.     def domenu_fontsettings(self, *args):
  277.         import FontSettings
  278.         fontsettings = self.editgroup.editor.getfontsettings()
  279.         tabsettings = self.editgroup.editor.gettabsettings()
  280.         settings = FontSettings.FontDialog(fontsettings, tabsettings)
  281.         if settings:
  282.             fontsettings, tabsettings = settings
  283.             self.editgroup.editor.setfontsettings(fontsettings)
  284.             self.editgroup.editor.settabsettings(tabsettings)
  285.     
  286.     def domenu_options(self, *args):
  287.         rv = SaveOptions(self._creator)
  288.         if rv:
  289.             self.editgroup.editor.selchanged = 1 # ouch...
  290.             self._creator = rv
  291.     
  292.     def clicklinefield(self):
  293.         if self._currentwidget <> self.linefield:
  294.             self.linefield.select(1)
  295.             self.linefield.selectall()
  296.             return 1
  297.     
  298.     def clickeditor(self):
  299.         if self._currentwidget <> self.editgroup.editor:
  300.             self.dolinefield()
  301.             return 1
  302.     
  303.     def updateselection(self, force = 0):
  304.         sel = min(self.editgroup.editor.getselection())
  305.         lineno = self.editgroup.editor.offsettoline(sel)
  306.         if lineno <> self.lastlineno or force:
  307.             self.lastlineno = lineno
  308.             self.linefield.set(str(lineno + 1))
  309.             self.linefield.selview()
  310.     
  311.     def dolinefield(self):
  312.         try:
  313.             lineno = string.atoi(self.linefield.get()) - 1
  314.             if lineno <> self.lastlineno:
  315.                 self.editgroup.editor.selectline(lineno)
  316.                 self.updateselection(1)
  317.         except:
  318.             self.updateselection(1)
  319.         self.editgroup.editor.select(1)
  320.     
  321.     def setinfotext(self):
  322.         if not hasattr(self, 'infotext'):
  323.             return
  324.         if self.path:
  325.             self.infotext.set(self.path)
  326.         else:
  327.             self.infotext.set("")
  328.     
  329.     def close(self):
  330.         if self.editgroup.editor.changed:
  331.             import EasyDialogs
  332.             import Qd
  333.             Qd.InitCursor() # XXX should be done by dialog
  334.             save = EasyDialogs.AskYesNoCancel('Save window “%s” before closing?' % self.title, 1)
  335.             if save > 0:
  336.                 if self.domenu_save():
  337.                     return 1
  338.             elif save < 0:
  339.                 return 1
  340.         self.globals = None        # XXX doesn't help... all globals leak :-(
  341.         W.Window.close(self)
  342.     
  343.     def domenu_close(self, *args):
  344.         return self.close()
  345.     
  346.     def domenu_save(self, *args):
  347.         if not self.path:
  348.             # Will call us recursively
  349.             return self.domenu_save_as()
  350.         data = self.editgroup.editor.get()
  351.         fp = open(self.path, 'wb')  # open file in binary mode, data has '\r' line-endings
  352.         fp.write(data)
  353.         fp.close()
  354.         fss = macfs.FSSpec(self.path)
  355.         fss.SetCreatorType(self._creator, 'TEXT')
  356.         self.getsettings()
  357.         self.writewindowsettings()
  358.         self.editgroup.editor.changed = 0
  359.         self.editgroup.editor.selchanged = 0
  360.         import linecache
  361.         if linecache.cache.has_key(self.path):
  362.             del linecache.cache[self.path]
  363.         import macostools
  364.         macostools.touched(self.path)
  365.     
  366.     def can_save(self, menuitem):
  367.         return self.editgroup.editor.changed or self.editgroup.editor.selchanged
  368.     
  369.     def domenu_save_as(self, *args):
  370.         fss, ok = macfs.StandardPutFile('Save as:', self.title)
  371.         if not ok: 
  372.             return 1
  373.         self.showbreakpoints(0)
  374.         self.path = fss.as_pathname()
  375.         self.setinfotext()
  376.         self.title = os.path.split(self.path)[-1]
  377.         self.wid.SetWTitle(self.title)
  378.         self.domenu_save()
  379.         self.editgroup.editor.setfile(self.getfilename())
  380.         app = W.getapplication()
  381.         app.makeopenwindowsmenu()
  382.         if hasattr(app, 'makescriptsmenu'):
  383.             app = W.getapplication()
  384.             fss, fss_changed = app.scriptsfolder.Resolve()
  385.             path = fss.as_pathname()
  386.             if path == self.path[:len(path)]:
  387.                 W.getapplication().makescriptsmenu()
  388.     
  389.     def domenu_save_as_applet(self, *args):
  390.         try:
  391.             import buildtools
  392.         except ImportError:
  393.             # only have buildtools in Python >= 1.5.2
  394.             raise W.AlertError, "“Save as Applet” is only supported in\rPython 1.5.2 and up."
  395.         
  396.         buildtools.DEBUG = 0    # ouch.
  397.         
  398.         if self.title[-3:] == ".py":
  399.             destname = self.title[:-3]
  400.         else:
  401.             destname = self.title + ".applet"
  402.         fss, ok = macfs.StandardPutFile('Save as Applet:', destname)
  403.         if not ok: 
  404.             return 1
  405.         W.SetCursor("watch")
  406.         destname = fss.as_pathname()
  407.         if self.path:
  408.             filename = self.path
  409.             if filename[-3:] == ".py":
  410.                 rsrcname = filename[:-3] + '.rsrc'
  411.             else:
  412.                 rsrcname = filename + '.rsrc'
  413.         else:
  414.             filename = self.title
  415.             rsrcname = ""
  416.         
  417.         pytext = self.editgroup.editor.get()
  418.         pytext = string.split(pytext, '\r')
  419.         pytext = string.join(pytext, '\n') + '\n'
  420.         try:
  421.             code = compile(pytext, filename, "exec")
  422.         except (SyntaxError, EOFError):
  423.             raise buildtools.BuildError, "Syntax error in script %s" % `filename`
  424.         
  425.         # Try removing the output file
  426.         try:
  427.             os.remove(destname)
  428.         except os.error:
  429.             pass
  430.         template = buildtools.findtemplate()
  431.         buildtools.process_common(template, None, code, rsrcname, destname, 0, 1)
  432.     
  433.     def domenu_gotoline(self, *args):
  434.         self.linefield.selectall()
  435.         self.linefield.select(1)
  436.         self.linefield.selectall()
  437.     
  438.     def domenu_selectline(self, *args):
  439.         self.editgroup.editor.expandselection()
  440.     
  441.     def domenu_find(self, *args):
  442.         searchengine.show()
  443.     
  444.     def domenu_entersearchstring(self, *args):
  445.         searchengine.setfindstring()
  446.     
  447.     def domenu_replace(self, *args):
  448.         searchengine.replace()
  449.     
  450.     def domenu_findnext(self, *args):
  451.         searchengine.findnext()
  452.     
  453.     def domenu_replacefind(self, *args):
  454.         searchengine.replacefind()
  455.     
  456.     def domenu_run(self, *args):
  457.         self.runbutton.push()
  458.     
  459.     def domenu_runselection(self, *args):
  460.         self.runselbutton.push()
  461.     
  462.     def run(self):
  463.         self._run()
  464.     
  465.     def _run(self):
  466.         pytext = self.editgroup.editor.get()
  467.         globals, file, modname = self.getenvironment()
  468.         self.execstring(pytext, globals, globals, file, modname)
  469.     
  470.     def runselection(self):
  471.         self._runselection()
  472.     
  473.     def _runselection(self):
  474.         globals, file, modname = self.getenvironment()
  475.         locals = globals
  476.         # select whole lines
  477.         self.editgroup.editor.expandselection()
  478.         
  479.         # get lineno of first selected line
  480.         selstart, selend = self.editgroup.editor.getselection()
  481.         selstart, selend = min(selstart, selend), max(selstart, selend)
  482.         selfirstline = self.editgroup.editor.offsettoline(selstart)
  483.         alltext = self.editgroup.editor.get()
  484.         pytext = alltext[selstart:selend]
  485.         lines = string.split(pytext, '\r')
  486.         indent = getminindent(lines)
  487.         if indent == 1:
  488.             classname = ''
  489.             alllines = string.split(alltext, '\r')
  490.             identifieRE_match = _identifieRE.match
  491.             for i in range(selfirstline - 1, -1, -1):
  492.                 line = alllines[i]
  493.                 if line[:6] == 'class ':
  494.                     classname = string.split(string.strip(line[6:]))[0]
  495.                     classend = identifieRE_match(classname)
  496.                     if classend < 1:
  497.                         raise W.AlertError, 'Can’t find a class.'
  498.                     classname = classname[:classend]
  499.                     break
  500.                 elif line and line[0] not in '\t#':
  501.                     raise W.AlertError, 'Can’t find a class.'
  502.             else:
  503.                 raise W.AlertError, 'Can’t find a class.'
  504.             if globals.has_key(classname):
  505.                 locals = globals[classname].__dict__
  506.             else:
  507.                 raise W.AlertError, 'Can’t find class “%s”.' % classname
  508.             # dedent to top level
  509.             for i in range(len(lines)):
  510.                 lines[i] = lines[i][1:]
  511.             pytext = string.join(lines, '\r')
  512.         elif indent > 0:
  513.             raise W.AlertError, 'Can’t run indented code.'
  514.         
  515.         # add "newlines" to fool compile/exec: 
  516.         # now a traceback will give the right line number
  517.         pytext = selfirstline * '\r' + pytext
  518.         self.execstring(pytext, globals, locals, file, modname)
  519.     
  520.     def execstring(self, pytext, globals, locals, file, modname):
  521.         tracebackwindow.hide()
  522.         # update windows
  523.         W.getapplication().refreshwindows()
  524.         if self.run_as_main:
  525.             modname = "__main__"
  526.         if self.path:
  527.             dir = os.path.dirname(self.path)
  528.             savedir = os.getcwd()
  529.             os.chdir(dir)
  530.             sys.path.insert(0, dir)
  531.         else:
  532.             cwdindex = None
  533.         try:
  534.             execstring(pytext, globals, locals, file, self.debugging, 
  535.                     modname, self.profiling)
  536.         finally:
  537.             if self.path:
  538.                 os.chdir(savedir)
  539.                 del sys.path[0]
  540.     
  541.     def getenvironment(self):
  542.         if self.path:
  543.             file = self.path
  544.             dir = os.path.dirname(file)
  545.             # check if we're part of a package
  546.             modname = ""
  547.             while os.path.exists(os.path.join(dir, "__init__.py")):
  548.                 dir, dirname = os.path.split(dir)
  549.                 modname = dirname + '.' + modname
  550.             subname = _filename_as_modname(self.title)
  551.             if modname:
  552.                 if subname == "__init__":
  553.                     # strip trailing period
  554.                     modname = modname[:-1]
  555.                 else:
  556.                     modname = modname + subname
  557.             else:
  558.                 modname = subname
  559.             if sys.modules.has_key(modname):
  560.                 globals = sys.modules[modname].__dict__
  561.                 self.globals = {}
  562.             else:
  563.                 globals = self.globals
  564.         else:
  565.             file = '<%s>' % self.title
  566.             globals = self.globals
  567.             modname = file
  568.         return globals, file, modname
  569.     
  570.     def write(self, stuff):
  571.         """for use as stdout"""
  572.         self._buf = self._buf + stuff
  573.         if '\n' in self._buf:
  574.             self.flush()
  575.     
  576.     def flush(self):
  577.         stuff = string.split(self._buf, '\n')
  578.         stuff = string.join(stuff, '\r')
  579.         end = self.editgroup.editor.ted.WEGetTextLength()
  580.         self.editgroup.editor.ted.WESetSelection(end, end)
  581.         self.editgroup.editor.ted.WEInsert(stuff, None, None)
  582.         self.editgroup.editor.updatescrollbars()
  583.         self._buf = ""
  584.         # ? optional:
  585.         #self.wid.SelectWindow()
  586.     
  587.     def getclasslist(self):
  588.         from string import find, strip
  589.         editor = self.editgroup.editor
  590.         text = editor.get()
  591.         list = []
  592.         append = list.append
  593.         functag = "func"
  594.         classtag = "class"
  595.         methodtag = "method"
  596.         pos = -1
  597.         if text[:4] == 'def ':
  598.             append((pos + 4, functag))
  599.             pos = 4
  600.         while 1:
  601.             pos = find(text, '\rdef ', pos + 1)
  602.             if pos < 0:
  603.                 break
  604.             append((pos + 5, functag))
  605.         pos = -1
  606.         if text[:6] == 'class ':
  607.             append((pos + 6, classtag))
  608.             pos = 6
  609.         while 1:
  610.             pos = find(text, '\rclass ', pos + 1)
  611.             if pos < 0:
  612.                 break
  613.             append((pos + 7, classtag))
  614.         pos = 0
  615.         while 1:
  616.             pos = find(text, '\r\tdef ', pos + 1)
  617.             if pos < 0:
  618.                 break
  619.             append((pos + 6, methodtag))
  620.         list.sort()
  621.         classlist = []
  622.         methodlistappend = None
  623.         offsetToLine = editor.ted.WEOffsetToLine
  624.         getLineRange = editor.ted.WEGetLineRange
  625.         append = classlist.append
  626.         identifieRE_match = _identifieRE.match
  627.         for pos, tag in list:
  628.             lineno = offsetToLine(pos)
  629.             lineStart, lineEnd = getLineRange(lineno)
  630.             line = strip(text[pos:lineEnd])
  631.             line = line[:identifieRE_match(line)]
  632.             if tag is functag:
  633.                 append(("def " + line, lineno + 1))
  634.                 methodlistappend = None
  635.             elif tag is classtag:
  636.                 append(["class " + line])
  637.                 methodlistappend = classlist[-1].append
  638.             elif methodlistappend and tag is methodtag:
  639.                 methodlistappend(("def " + line, lineno + 1))
  640.         return classlist
  641.     
  642.     def popselectline(self, lineno):
  643.         self.editgroup.editor.selectline(lineno - 1)
  644.     
  645.     def selectline(self, lineno, charoffset = 0):
  646.         self.editgroup.editor.selectline(lineno - 1, charoffset)
  647.  
  648. class _saveoptions:
  649.     
  650.     def __init__(self, creator):
  651.         self.rv = None
  652.         self.w = w = W.ModalDialog((240, 140), 'Save options')
  653.         radiobuttons = []
  654.         w.label = W.TextBox((8, 8, 80, 18), "File creator:")
  655.         w.ide_radio = W.RadioButton((8, 22, 160, 18), "This application", radiobuttons, self.ide_hit)
  656.         w.interp_radio = W.RadioButton((8, 42, 160, 18), "Python Interpreter", radiobuttons, self.interp_hit)
  657.         w.other_radio = W.RadioButton((8, 62, 50, 18), "Other:", radiobuttons)
  658.         w.other_creator = W.EditText((62, 62, 40, 20), creator, self.otherselect)
  659.         w.cancelbutton = W.Button((-180, -30, 80, 16), "Cancel", self.cancelbuttonhit)
  660.         w.okbutton = W.Button((-90, -30, 80, 16), "Done", self.okbuttonhit)
  661.         w.setdefaultbutton(w.okbutton)
  662.         if creator == 'Pyth':
  663.             w.interp_radio.set(1)
  664.         elif creator == W._signature:
  665.             w.ide_radio.set(1)
  666.         else:
  667.             w.other_radio.set(1)
  668.         w.bind("cmd.", w.cancelbutton.push)
  669.         w.open()
  670.     
  671.     def ide_hit(self):
  672.         self.w.other_creator.set(W._signature)
  673.     
  674.     def interp_hit(self):
  675.         self.w.other_creator.set("Pyth")
  676.     
  677.     def otherselect(self, *args):
  678.         sel_from, sel_to = self.w.other_creator.getselection()
  679.         creator = self.w.other_creator.get()[:4]
  680.         creator = creator + " " * (4 - len(creator))
  681.         self.w.other_creator.set(creator)
  682.         self.w.other_creator.setselection(sel_from, sel_to)
  683.         self.w.other_radio.set(1)
  684.     
  685.     def cancelbuttonhit(self):
  686.         self.w.close()
  687.     
  688.     def okbuttonhit(self):
  689.         self.rv = self.w.other_creator.get()[:4]
  690.         self.w.close()
  691.  
  692.  
  693. def SaveOptions(creator):
  694.     s = _saveoptions(creator)
  695.     return s.rv
  696.  
  697.  
  698. def _escape(where, what) : 
  699.     return string.join(string.split(where, what), '\\' + what)
  700.  
  701. def _makewholewordpattern(word):
  702.     # first, escape special regex chars
  703.     for esc in "\\[].*^+$?":
  704.         word = _escape(word, esc)
  705.     import regex
  706.     notwordcharspat = '[^' + _wordchars + ']'
  707.     pattern = '\(' + word + '\)'
  708.     if word[0] in _wordchars:
  709.         pattern = notwordcharspat + pattern
  710.     if word[-1] in _wordchars:
  711.         pattern = pattern + notwordcharspat
  712.     return regex.compile(pattern)
  713.  
  714. class SearchEngine:
  715.     
  716.     def __init__(self):
  717.         self.visible = 0
  718.         self.w = None
  719.         self.parms = {  "find": "",
  720.                     "replace": "",
  721.                     "wrap": 1,
  722.                     "casesens": 1,
  723.                     "wholeword": 1
  724.                 }
  725.         import MacPrefs
  726.         prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath)
  727.         if prefs.searchengine:
  728.             self.parms["casesens"] = prefs.searchengine.casesens
  729.             self.parms["wrap"] = prefs.searchengine.wrap
  730.             self.parms["wholeword"] = prefs.searchengine.wholeword
  731.     
  732.     def show(self):
  733.         self.visible = 1
  734.         if self.w:
  735.             self.w.wid.ShowWindow()
  736.             self.w.wid.SelectWindow()
  737.             self.w.find.edit.select(1)
  738.             self.w.find.edit.selectall()
  739.             return
  740.         self.w = W.Dialog((420, 150), "Find")
  741.         
  742.         self.w.find = TitledEditText((10, 4, 300, 36), "Search for:")
  743.         self.w.replace = TitledEditText((10, 100, 300, 36), "Replace with:")
  744.         
  745.         self.w.boxes = W.Group((10, 50, 300, 40))
  746.         self.w.boxes.casesens = W.CheckBox((0, 0, 100, 16), "Case sensitive")
  747.         self.w.boxes.wholeword = W.CheckBox((0, 20, 100, 16), "Whole word")
  748.         self.w.boxes.wrap = W.CheckBox((110, 0, 100, 16), "Wrap around")
  749.         
  750.         self.buttons = [    ("Find",        "cmdf",     self.find), 
  751.                     ("Replace",         "cmdr",     self.replace), 
  752.                     ("Replace all",     None,   self.replaceall), 
  753.                     ("Don’t find",  "cmdd",     self.dont), 
  754.                     ("Cancel",          "cmd.",     self.cancel)
  755.                 ]
  756.         for i in range(len(self.buttons)):
  757.             bounds = -90, 22 + i * 24, 80, 16
  758.             title, shortcut, callback = self.buttons[i]
  759.             self.w[title] = W.Button(bounds, title, callback)
  760.             if shortcut:
  761.                 self.w.bind(shortcut, self.w[title].push)
  762.         self.w.setdefaultbutton(self.w["Don’t find"])
  763.         self.w.find.edit.bind("<key>", self.key)
  764.         self.w.bind("<activate>", self.activate)
  765.         self.w.bind("<close>", self.close)
  766.         self.w.open()
  767.         self.setparms()
  768.         self.w.find.edit.select(1)
  769.         self.w.find.edit.selectall()
  770.         self.checkbuttons()
  771.     
  772.     def close(self):
  773.         self.hide()
  774.         return -1
  775.     
  776.     def key(self, char, modifiers):
  777.         self.w.find.edit.key(char, modifiers)
  778.         self.checkbuttons()
  779.         return 1
  780.     
  781.     def activate(self, onoff):
  782.         if onoff:
  783.             self.checkbuttons()
  784.     
  785.     def checkbuttons(self):
  786.         editor = findeditor(self)
  787.         if editor:
  788.             if self.w.find.get():
  789.                 for title, cmd, call in self.buttons[:-2]:
  790.                     self.w[title].enable(1)
  791.                 self.w.setdefaultbutton(self.w["Find"])
  792.             else:
  793.                 for title, cmd, call in self.buttons[:-2]:
  794.                     self.w[title].enable(0)
  795.                 self.w.setdefaultbutton(self.w["Don’t find"])
  796.         else:
  797.             for title, cmd, call in self.buttons[:-2]:
  798.                 self.w[title].enable(0)
  799.             self.w.setdefaultbutton(self.w["Don’t find"])
  800.     
  801.     def find(self):
  802.         self.getparmsfromwindow()
  803.         if self.findnext():
  804.             self.hide()
  805.     
  806.     def replace(self):
  807.         editor = findeditor(self)
  808.         if not editor:
  809.             return
  810.         if self.visible:
  811.             self.getparmsfromwindow()
  812.         text = editor.getselectedtext()
  813.         find = self.parms["find"]
  814.         if not self.parms["casesens"]:
  815.             find = string.lower(find)
  816.             text = string.lower(text)
  817.         if text == find:
  818.             self.hide()
  819.             editor.insert(self.parms["replace"])
  820.     
  821.     def replaceall(self):
  822.         editor = findeditor(self)
  823.         if not editor:
  824.             return
  825.         if self.visible:
  826.             self.getparmsfromwindow()
  827.         W.SetCursor("watch")
  828.         find = self.parms["find"]
  829.         if not find:
  830.             return
  831.         findlen = len(find)
  832.         replace = self.parms["replace"]
  833.         replacelen = len(replace)
  834.         Text = editor.get()
  835.         if not self.parms["casesens"]:
  836.             find = string.lower(find)
  837.             text = string.lower(Text)
  838.         else:
  839.             text = Text
  840.         newtext = ""
  841.         pos = 0
  842.         counter = 0
  843.         while 1:
  844.             if self.parms["wholeword"]:
  845.                 wholewordRE = _makewholewordpattern(find)
  846.                 wholewordRE.search(text, pos)
  847.                 if wholewordRE.regs:
  848.                     pos = wholewordRE.regs[1][0]
  849.                 else:
  850.                     pos = -1
  851.             else:
  852.                 pos = string.find(text, find, pos)
  853.             if pos < 0:
  854.                 break
  855.             counter = counter + 1
  856.             text = text[:pos] + replace + text[pos + findlen:]
  857.             Text = Text[:pos] + replace + Text[pos + findlen:]
  858.             pos = pos + replacelen
  859.         W.SetCursor("arrow")
  860.         if counter:
  861.             self.hide()
  862.             import EasyDialogs
  863.             import Res
  864.             editor.changed = 1
  865.             editor.selchanged = 1
  866.             editor.ted.WEUseText(Res.Resource(Text))
  867.             editor.ted.WECalText()
  868.             editor.SetPort()
  869.             Win.InvalRect(editor._bounds)
  870.             #editor.ted.WEUpdate(self.w.wid.GetWindowPort().visRgn)
  871.             EasyDialogs.Message("Replaced %d occurrences" % counter)
  872.     
  873.     def dont(self):
  874.         self.getparmsfromwindow()
  875.         self.hide()
  876.     
  877.     def replacefind(self):
  878.         self.replace()
  879.         self.findnext()
  880.     
  881.     def setfindstring(self):
  882.         editor = findeditor(self)
  883.         if not editor:
  884.             return
  885.         find = editor.getselectedtext()
  886.         if not find:
  887.             return
  888.         self.parms["find"] = find
  889.         if self.w:
  890.             self.w.find.edit.set(self.parms["find"])
  891.             self.w.find.edit.selectall()
  892.     
  893.     def findnext(self):
  894.         editor = findeditor(self)
  895.         if not editor:
  896.             return
  897.         find = self.parms["find"]
  898.         if not find:
  899.             return
  900.         text = editor.get()
  901.         if not self.parms["casesens"]:
  902.             find = string.lower(find)
  903.             text = string.lower(text)
  904.         selstart, selend = editor.getselection()
  905.         selstart, selend = min(selstart, selend), max(selstart, selend)
  906.         if self.parms["wholeword"]:
  907.             wholewordRE = _makewholewordpattern(find)
  908.             wholewordRE.search(text, selend)
  909.             if wholewordRE.regs:
  910.                 pos = wholewordRE.regs[1][0]
  911.             else:
  912.                 pos = -1
  913.         else:
  914.             pos = string.find(text, find, selend)
  915.         if pos >= 0:
  916.             editor.setselection(pos, pos + len(find))
  917.             return 1
  918.         elif self.parms["wrap"]:
  919.             if self.parms["wholeword"]:
  920.                 wholewordRE.search(text, 0)
  921.                 if wholewordRE.regs:
  922.                     pos = wholewordRE.regs[1][0]
  923.                 else:
  924.                     pos = -1
  925.             else:
  926.                 pos = string.find(text, find)
  927.             if selstart > pos >= 0:
  928.                 editor.setselection(pos, pos + len(find))
  929.                 return 1
  930.     
  931.     def setparms(self):
  932.         for key, value in self.parms.items():
  933.             try:
  934.                 self.w[key].set(value)
  935.             except KeyError:
  936.                 self.w.boxes[key].set(value)
  937.     
  938.     def getparmsfromwindow(self):
  939.         if not self.w:
  940.             return
  941.         for key, value in self.parms.items():
  942.             try:
  943.                 value = self.w[key].get()
  944.             except KeyError:
  945.                 value = self.w.boxes[key].get()
  946.             self.parms[key] = value
  947.     
  948.     def cancel(self):
  949.         self.hide()
  950.         self.setparms()
  951.     
  952.     def hide(self):
  953.         if self.w:
  954.             self.w.wid.HideWindow()
  955.             self.visible = 0
  956.     
  957.     def writeprefs(self):
  958.         import MacPrefs
  959.         self.getparmsfromwindow()
  960.         prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath)
  961.         prefs.searchengine.casesens = self.parms["casesens"]
  962.         prefs.searchengine.wrap = self.parms["wrap"]
  963.         prefs.searchengine.wholeword = self.parms["wholeword"]
  964.         prefs.save()
  965.     
  966.  
  967. class TitledEditText(W.Group):
  968.     
  969.     def __init__(self, possize, title, text = ""):
  970.         W.Group.__init__(self, possize)
  971.         self.title = W.TextBox((0, 0, 0, 16), title)
  972.         self.edit = W.EditText((0, 16, 0, 0), text)
  973.     
  974.     def set(self, value):
  975.         self.edit.set(value)
  976.     
  977.     def get(self):
  978.         return self.edit.get()
  979.  
  980.  
  981. class ClassFinder(W.PopupWidget):
  982.     
  983.     def click(self, point, modifiers):
  984.         W.SetCursor("watch")
  985.         self.set(self._parentwindow.getclasslist())
  986.         W.PopupWidget.click(self, point, modifiers)
  987.  
  988.  
  989. def getminindent(lines):
  990.     indent = -1
  991.     for line in lines:
  992.         stripped = string.strip(line)
  993.         if not stripped or stripped[0] == '#':
  994.             continue
  995.         if indent < 0 or line[:indent] <> indent * '\t':
  996.             indent = 0
  997.             for c in line:
  998.                 if c <> '\t':
  999.                     break
  1000.                 indent = indent + 1
  1001.     return indent
  1002.  
  1003.  
  1004. def getoptionkey():
  1005.     return not not ord(Evt.GetKeys()[7]) & 0x04
  1006.  
  1007.  
  1008. def execstring(pytext, globals, locals, filename="<string>", debugging=0, 
  1009.             modname="__main__", profiling=0):
  1010.     if debugging:
  1011.         import PyDebugger, bdb
  1012.         BdbQuit = bdb.BdbQuit
  1013.     else:
  1014.         BdbQuit = 'BdbQuitDummyException'
  1015.     pytext = string.split(pytext, '\r')
  1016.     pytext = string.join(pytext, '\n') + '\n'
  1017.     W.SetCursor("watch")
  1018.     globals['__name__'] = modname
  1019.     globals['__file__'] = filename
  1020.     sys.argv = [filename]
  1021.     try:
  1022.         code = compile(pytext, filename, "exec")
  1023.     except:
  1024.         # XXXX BAAAADDD.... We let tracebackwindow decide to treat SyntaxError 
  1025.         # special. That's wrong because THIS case is special (could be literal 
  1026.         # overflow!) and SyntaxError could mean we need a traceback (syntax error 
  1027.         # in imported module!!!
  1028.         tracebackwindow.traceback(1, filename)
  1029.         return
  1030.     try:
  1031.         if debugging:
  1032.             PyDebugger.startfromhere()
  1033.         else:
  1034.             MacOS.EnableAppswitch(0)
  1035.         try:
  1036.             if profiling:
  1037.                 import profile, ProfileBrowser
  1038.                 p = profile.Profile()
  1039.                 p.set_cmd(filename)
  1040.                 try:
  1041.                     p.runctx(code, globals, locals)
  1042.                 finally:
  1043.                     import pstats
  1044.                     
  1045.                     stats = pstats.Stats(p)
  1046.                     ProfileBrowser.ProfileBrowser(stats)
  1047.             else:
  1048.                 exec code in globals, locals
  1049.         finally:
  1050.             MacOS.EnableAppswitch(-1)
  1051.     except W.AlertError, detail:
  1052.         raise W.AlertError, detail
  1053.     except (KeyboardInterrupt, BdbQuit):
  1054.         pass
  1055.     except:
  1056.         if debugging:
  1057.             sys.settrace(None)
  1058.             PyDebugger.postmortem(sys.exc_type, sys.exc_value, sys.exc_traceback)
  1059.             return
  1060.         else:
  1061.             tracebackwindow.traceback(1, filename)
  1062.     if debugging:
  1063.         sys.settrace(None)
  1064.         PyDebugger.stop()
  1065.  
  1066.  
  1067. _identifieRE = regex.compile("[A-Za-z_][A-Za-z_0-9]*")
  1068.  
  1069. def _filename_as_modname(fname):
  1070.     if fname[-3:] == '.py':
  1071.         modname = fname[:-3]
  1072.         if _identifieRE.match(modname) == len(modname):
  1073.             return string.join(string.split(modname, '.'), '_')
  1074.  
  1075. def findeditor(topwindow, fromtop = 0):
  1076.     wid = Win.FrontWindow()
  1077.     if not fromtop:
  1078.         if topwindow.w and wid == topwindow.w.wid:
  1079.             wid = topwindow.w.wid.GetNextWindow()
  1080.     if not wid:
  1081.         return
  1082.     app = W.getapplication()
  1083.     if app._windows.has_key(wid): # KeyError otherwise can happen in RoboFog :-(
  1084.         window = W.getapplication()._windows[wid]
  1085.     else:
  1086.         return
  1087.     if not isinstance(window, Editor):
  1088.         return
  1089.     return window.editgroup.editor
  1090.  
  1091.  
  1092. class _EditorDefaultSettings:
  1093.     
  1094.     def __init__(self):
  1095.         self.template = "%s, %d point"
  1096.         self.fontsettings, self.tabsettings, self.windowsize = geteditorprefs()
  1097.         self.w = W.Dialog((328, 120), "Editor default settings")
  1098.         self.w.setfontbutton = W.Button((8, 8, 80, 16), "Set font…", self.dofont)
  1099.         self.w.fonttext = W.TextBox((98, 10, -8, 14), self.template % (self.fontsettings[0], self.fontsettings[2]))
  1100.         
  1101.         self.w.picksizebutton = W.Button((8, 50, 80, 16), "Front window", self.picksize)
  1102.         self.w.xsizelabel = W.TextBox((98, 32, 40, 14), "Width:")
  1103.         self.w.ysizelabel = W.TextBox((148, 32, 40, 14), "Height:")
  1104.         self.w.xsize = W.EditText((98, 48, 40, 20), `self.windowsize[0]`)
  1105.         self.w.ysize = W.EditText((148, 48, 40, 20), `self.windowsize[1]`)
  1106.         
  1107.         self.w.cancelbutton = W.Button((-180, -26, 80, 16), "Cancel", self.cancel)
  1108.         self.w.okbutton = W.Button((-90, -26, 80, 16), "Done", self.ok)
  1109.         self.w.setdefaultbutton(self.w.okbutton)
  1110.         self.w.bind('cmd.', self.w.cancelbutton.push)
  1111.         self.w.open()
  1112.     
  1113.     def picksize(self):
  1114.         app = W.getapplication()
  1115.         editor = findeditor(self)
  1116.         if editor is not None:
  1117.             width, height = editor._parentwindow._bounds[2:]
  1118.             self.w.xsize.set(`width`)
  1119.             self.w.ysize.set(`height`)
  1120.         else:
  1121.             raise W.AlertError, "No edit window found"
  1122.     
  1123.     def dofont(self):
  1124.         import FontSettings
  1125.         settings = FontSettings.FontDialog(self.fontsettings, self.tabsettings)
  1126.         if settings:
  1127.             self.fontsettings, self.tabsettings = settings
  1128.             sys.exc_traceback = None
  1129.             self.w.fonttext.set(self.template % (self.fontsettings[0], self.fontsettings[2]))
  1130.     
  1131.     def close(self):
  1132.         self.w.close()
  1133.         del self.w
  1134.     
  1135.     def cancel(self):
  1136.         self.close()
  1137.     
  1138.     def ok(self):
  1139.         try:
  1140.             width = string.atoi(self.w.xsize.get())
  1141.         except:
  1142.             self.w.xsize.select(1)
  1143.             self.w.xsize.selectall()
  1144.             raise W.AlertError, "Bad number for window width"
  1145.         try:
  1146.             height = string.atoi(self.w.ysize.get())
  1147.         except:
  1148.             self.w.ysize.select(1)
  1149.             self.w.ysize.selectall()
  1150.             raise W.AlertError, "Bad number for window height"
  1151.         self.windowsize = width, height
  1152.         seteditorprefs(self.fontsettings, self.tabsettings, self.windowsize)
  1153.         self.close()
  1154.  
  1155. def geteditorprefs():
  1156.     import MacPrefs
  1157.     prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath)
  1158.     try:
  1159.         fontsettings = prefs.pyedit.fontsettings
  1160.         tabsettings = prefs.pyedit.tabsettings
  1161.         windowsize = prefs.pyedit.windowsize
  1162.     except:
  1163.         fontsettings = prefs.pyedit.fontsettings = ("Python-Sans", 0, 9, (0, 0, 0))
  1164.         tabsettings = prefs.pyedit.tabsettings = (8, 1)
  1165.         windowsize = prefs.pyedit.windowsize = (500, 250)
  1166.         sys.exc_traceback = None
  1167.     return fontsettings, tabsettings, windowsize
  1168.  
  1169. def seteditorprefs(fontsettings, tabsettings, windowsize):
  1170.     import MacPrefs
  1171.     prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath)
  1172.     prefs.pyedit.fontsettings = fontsettings
  1173.     prefs.pyedit.tabsettings = tabsettings
  1174.     prefs.pyedit.windowsize = windowsize
  1175.     prefs.save()
  1176.  
  1177. _defaultSettingsEditor = None
  1178.  
  1179. def EditorDefaultSettings():
  1180.     global _defaultSettingsEditor
  1181.     if _defaultSettingsEditor is None or not hasattr(_defaultSettingsEditor, "w"):
  1182.         _defaultSettingsEditor = _EditorDefaultSettings()
  1183.     else:
  1184.         _defaultSettingsEditor.w.select()
  1185.  
  1186. def resolvealiases(path):
  1187.     try:
  1188.         return macfs.ResolveAliasFile(path)[0].as_pathname()
  1189.     except (macfs.error, ValueError), (error, str):
  1190.         if error <> -120:
  1191.             raise
  1192.         dir, file = os.path.split(path)
  1193.         return os.path.join(resolvealiases(dir), file)
  1194.  
  1195. searchengine = SearchEngine()
  1196. tracebackwindow = Wtraceback.TraceBack()
  1197.