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