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