This commit was manufactured by cvs2svn to create tag 'r234'.
[python/dscho.git] / Mac / Tools / IDE / PyEdit.py
blob0ad7a8a58c7bd06ec00a87a9d40cd5fff98fe51a
1 """A (less & less) simple Python editor"""
3 import W
4 import Wtraceback
5 from Wkeys import *
7 import MacOS
8 import EasyDialogs
9 from Carbon import Win
10 from Carbon import Res
11 from Carbon import Evt
12 from Carbon import Qd
13 from Carbon import File
14 import os
15 import imp
16 import sys
17 import string
18 import marshal
19 import re
21 smAllScripts = -3
23 if hasattr(Win, "FrontNonFloatingWindow"):
24 MyFrontWindow = Win.FrontNonFloatingWindow
25 else:
26 MyFrontWindow = Win.FrontWindow
29 _scriptuntitledcounter = 1
30 _wordchars = string.ascii_letters + string.digits + "_"
33 runButtonLabels = ["Run all", "Stop!"]
34 runSelButtonLabels = ["Run selection", "Pause!", "Resume"]
37 class Editor(W.Window):
39 def __init__(self, path = "", title = ""):
40 defaultfontsettings, defaulttabsettings, defaultwindowsize = geteditorprefs()
41 global _scriptuntitledcounter
42 if not path:
43 if title:
44 self.title = title
45 else:
46 self.title = "Untitled Script " + `_scriptuntitledcounter`
47 _scriptuntitledcounter = _scriptuntitledcounter + 1
48 text = ""
49 self._creator = W._signature
50 self._eoln = os.linesep
51 elif os.path.exists(path):
52 path = resolvealiases(path)
53 dir, name = os.path.split(path)
54 self.title = name
55 f = open(path, "rb")
56 text = f.read()
57 f.close()
58 self._creator, filetype = MacOS.GetCreatorAndType(path)
59 self.addrecentfile(path)
60 if '\n' in text:
61 if string.find(text, '\r\n') >= 0:
62 self._eoln = '\r\n'
63 else:
64 self._eoln = '\n'
65 text = string.replace(text, self._eoln, '\r')
66 else:
67 self._eoln = '\r'
68 else:
69 raise IOError, "file '%s' does not exist" % path
70 self.path = path
72 self.settings = {}
73 if self.path:
74 self.readwindowsettings()
75 if self.settings.has_key("windowbounds"):
76 bounds = self.settings["windowbounds"]
77 else:
78 bounds = defaultwindowsize
79 if self.settings.has_key("fontsettings"):
80 self.fontsettings = self.settings["fontsettings"]
81 else:
82 self.fontsettings = defaultfontsettings
83 if self.settings.has_key("tabsize"):
84 try:
85 self.tabsettings = (tabsize, tabmode) = self.settings["tabsize"]
86 except:
87 self.tabsettings = defaulttabsettings
88 else:
89 self.tabsettings = defaulttabsettings
91 W.Window.__init__(self, bounds, self.title, minsize = (330, 120), tabbable = 0)
92 self.setupwidgets(text)
94 if self.settings.has_key("selection"):
95 selstart, selend = self.settings["selection"]
96 self.setselection(selstart, selend)
97 self.open()
98 self.setinfotext()
99 self.globals = {}
100 self._buf = "" # for write method
101 self.debugging = 0
102 self.profiling = 0
103 self.run_as_main = self.settings.get("run_as_main", 0)
104 self.run_with_interpreter = self.settings.get("run_with_interpreter", 0)
105 self.run_with_cl_interpreter = self.settings.get("run_with_cl_interpreter", 0)
107 def readwindowsettings(self):
108 try:
109 resref = Res.FSpOpenResFile(self.path, 1)
110 except Res.Error:
111 return
112 try:
113 Res.UseResFile(resref)
114 data = Res.Get1Resource('PyWS', 128)
115 self.settings = marshal.loads(data.data)
116 except:
117 pass
118 Res.CloseResFile(resref)
120 def writewindowsettings(self):
121 try:
122 resref = Res.FSpOpenResFile(self.path, 3)
123 except Res.Error:
124 Res.FSpCreateResFile(self.path, self._creator, 'TEXT', smAllScripts)
125 resref = Res.FSpOpenResFile(self.path, 3)
126 try:
127 data = Res.Resource(marshal.dumps(self.settings))
128 Res.UseResFile(resref)
129 try:
130 temp = Res.Get1Resource('PyWS', 128)
131 temp.RemoveResource()
132 except Res.Error:
133 pass
134 data.AddResource('PyWS', 128, "window settings")
135 finally:
136 Res.UpdateResFile(resref)
137 Res.CloseResFile(resref)
139 def getsettings(self):
140 self.settings = {}
141 self.settings["windowbounds"] = self.getbounds()
142 self.settings["selection"] = self.getselection()
143 self.settings["fontsettings"] = self.editgroup.editor.getfontsettings()
144 self.settings["tabsize"] = self.editgroup.editor.gettabsettings()
145 self.settings["run_as_main"] = self.run_as_main
146 self.settings["run_with_interpreter"] = self.run_with_interpreter
147 self.settings["run_with_cl_interpreter"] = self.run_with_cl_interpreter
149 def get(self):
150 return self.editgroup.editor.get()
152 def getselection(self):
153 return self.editgroup.editor.ted.WEGetSelection()
155 def setselection(self, selstart, selend):
156 self.editgroup.editor.setselection(selstart, selend)
158 def getselectedtext(self):
159 return self.editgroup.editor.getselectedtext()
161 def getfilename(self):
162 if self.path:
163 return self.path
164 return '<%s>' % self.title
166 def setupwidgets(self, text):
167 topbarheight = 24
168 popfieldwidth = 80
169 self.lastlineno = None
171 # make an editor
172 self.editgroup = W.Group((0, topbarheight + 1, 0, 0))
173 editor = W.PyEditor((0, 0, -15,-15), text,
174 fontsettings = self.fontsettings,
175 tabsettings = self.tabsettings,
176 file = self.getfilename())
178 # make the widgets
179 self.popfield = ClassFinder((popfieldwidth - 17, -15, 16, 16), [], self.popselectline)
180 self.linefield = W.EditText((-1, -15, popfieldwidth - 15, 16), inset = (6, 1))
181 self.editgroup._barx = W.Scrollbar((popfieldwidth - 2, -15, -14, 16), editor.hscroll, max = 32767)
182 self.editgroup._bary = W.Scrollbar((-15, 14, 16, -14), editor.vscroll, max = 32767)
183 self.editgroup.editor = editor # add editor *after* scrollbars
185 self.editgroup.optionsmenu = W.PopupMenu((-15, -1, 16, 16), [])
186 self.editgroup.optionsmenu.bind('<click>', self.makeoptionsmenu)
188 self.bevelbox = W.BevelBox((0, 0, 0, topbarheight))
189 self.hline = W.HorizontalLine((0, topbarheight, 0, 0))
190 self.infotext = W.TextBox((175, 6, -4, 14), backgroundcolor = (0xe000, 0xe000, 0xe000))
191 self.runbutton = W.BevelButton((6, 4, 80, 16), runButtonLabels[0], self.run)
192 self.runselbutton = W.BevelButton((90, 4, 80, 16), runSelButtonLabels[0], self.runselection)
194 # bind some keys
195 editor.bind("cmdr", self.runbutton.push)
196 editor.bind("enter", self.runselbutton.push)
197 editor.bind("cmdj", self.domenu_gotoline)
198 editor.bind("cmdd", self.domenu_toggledebugger)
199 editor.bind("<idle>", self.updateselection)
201 editor.bind("cmde", searchengine.setfindstring)
202 editor.bind("cmdf", searchengine.show)
203 editor.bind("cmdg", searchengine.findnext)
204 editor.bind("cmdshiftr", searchengine.replace)
205 editor.bind("cmdt", searchengine.replacefind)
207 self.linefield.bind("return", self.dolinefield)
208 self.linefield.bind("enter", self.dolinefield)
209 self.linefield.bind("tab", self.dolinefield)
211 # intercept clicks
212 editor.bind("<click>", self.clickeditor)
213 self.linefield.bind("<click>", self.clicklinefield)
215 def makeoptionsmenu(self):
216 menuitems = [('Font settings\xc9', self.domenu_fontsettings),
217 ("Save options\xc9", self.domenu_options),
218 '-',
219 ('\0' + chr(self.run_as_main) + 'Run as __main__', self.domenu_toggle_run_as_main),
220 #('\0' + chr(self.run_with_interpreter) + 'Run with Interpreter', self.domenu_dtoggle_run_with_interpreter),
221 ('\0' + chr(self.run_with_cl_interpreter) + 'Run with commandline Python', self.domenu_toggle_run_with_cl_interpreter),
222 '-',
223 ('Modularize', self.domenu_modularize),
224 ('Browse namespace\xc9', self.domenu_browsenamespace),
225 '-']
226 if self.profiling:
227 menuitems = menuitems + [('Disable profiler', self.domenu_toggleprofiler)]
228 else:
229 menuitems = menuitems + [('Enable profiler', self.domenu_toggleprofiler)]
230 if self.editgroup.editor._debugger:
231 menuitems = menuitems + [('Disable debugger', self.domenu_toggledebugger),
232 ('Clear breakpoints', self.domenu_clearbreakpoints),
233 ('Edit breakpoints\xc9', self.domenu_editbreakpoints)]
234 else:
235 menuitems = menuitems + [('Enable debugger', self.domenu_toggledebugger)]
236 self.editgroup.optionsmenu.set(menuitems)
238 def domenu_toggle_run_as_main(self):
239 self.run_as_main = not self.run_as_main
240 self.run_with_interpreter = 0
241 self.run_with_cl_interpreter = 0
242 self.editgroup.editor.selectionchanged()
244 def XXdomenu_toggle_run_with_interpreter(self):
245 self.run_with_interpreter = not self.run_with_interpreter
246 self.run_as_main = 0
247 self.run_with_cl_interpreter = 0
248 self.editgroup.editor.selectionchanged()
250 def domenu_toggle_run_with_cl_interpreter(self):
251 self.run_with_cl_interpreter = not self.run_with_cl_interpreter
252 self.run_as_main = 0
253 self.run_with_interpreter = 0
254 self.editgroup.editor.selectionchanged()
256 def showbreakpoints(self, onoff):
257 self.editgroup.editor.showbreakpoints(onoff)
258 self.debugging = onoff
260 def domenu_clearbreakpoints(self, *args):
261 self.editgroup.editor.clearbreakpoints()
263 def domenu_editbreakpoints(self, *args):
264 self.editgroup.editor.editbreakpoints()
266 def domenu_toggledebugger(self, *args):
267 if not self.debugging:
268 W.SetCursor('watch')
269 self.debugging = not self.debugging
270 self.editgroup.editor.togglebreakpoints()
272 def domenu_toggleprofiler(self, *args):
273 self.profiling = not self.profiling
275 def domenu_browsenamespace(self, *args):
276 import PyBrowser, W
277 W.SetCursor('watch')
278 globals, file, modname = self.getenvironment()
279 if not modname:
280 modname = self.title
281 PyBrowser.Browser(globals, "Object browser: " + modname)
283 def domenu_modularize(self, *args):
284 modname = _filename_as_modname(self.title)
285 if not modname:
286 raise W.AlertError, "Can't modularize \"%s\"" % self.title
287 run_as_main = self.run_as_main
288 self.run_as_main = 0
289 self.run()
290 self.run_as_main = run_as_main
291 if self.path:
292 file = self.path
293 else:
294 file = self.title
296 if self.globals and not sys.modules.has_key(modname):
297 module = imp.new_module(modname)
298 for attr in self.globals.keys():
299 setattr(module,attr,self.globals[attr])
300 sys.modules[modname] = module
301 self.globals = {}
303 def domenu_fontsettings(self, *args):
304 import FontSettings
305 fontsettings = self.editgroup.editor.getfontsettings()
306 tabsettings = self.editgroup.editor.gettabsettings()
307 settings = FontSettings.FontDialog(fontsettings, tabsettings)
308 if settings:
309 fontsettings, tabsettings = settings
310 self.editgroup.editor.setfontsettings(fontsettings)
311 self.editgroup.editor.settabsettings(tabsettings)
313 def domenu_options(self, *args):
314 rv = SaveOptions(self._creator, self._eoln)
315 if rv:
316 self.editgroup.editor.selectionchanged() # ouch...
317 self._creator, self._eoln = rv
319 def clicklinefield(self):
320 if self._currentwidget <> self.linefield:
321 self.linefield.select(1)
322 self.linefield.selectall()
323 return 1
325 def clickeditor(self):
326 if self._currentwidget <> self.editgroup.editor:
327 self.dolinefield()
328 return 1
330 def updateselection(self, force = 0):
331 sel = min(self.editgroup.editor.getselection())
332 lineno = self.editgroup.editor.offsettoline(sel)
333 if lineno <> self.lastlineno or force:
334 self.lastlineno = lineno
335 self.linefield.set(str(lineno + 1))
336 self.linefield.selview()
338 def dolinefield(self):
339 try:
340 lineno = string.atoi(self.linefield.get()) - 1
341 if lineno <> self.lastlineno:
342 self.editgroup.editor.selectline(lineno)
343 self.updateselection(1)
344 except:
345 self.updateselection(1)
346 self.editgroup.editor.select(1)
348 def setinfotext(self):
349 if not hasattr(self, 'infotext'):
350 return
351 if self.path:
352 self.infotext.set(self.path)
353 else:
354 self.infotext.set("")
356 def close(self):
357 if self.editgroup.editor.changed:
358 Qd.InitCursor()
359 save = EasyDialogs.AskYesNoCancel('Save window "%s" before closing?' % self.title,
360 default=1, no="Don\xd5t save")
361 if save > 0:
362 if self.domenu_save():
363 return 1
364 elif save < 0:
365 return 1
366 self.globals = None
367 W.Window.close(self)
369 def domenu_close(self, *args):
370 return self.close()
372 def domenu_save(self, *args):
373 if not self.path:
374 # Will call us recursively
375 return self.domenu_save_as()
376 data = self.editgroup.editor.get()
377 if self._eoln != '\r':
378 data = string.replace(data, '\r', self._eoln)
379 fp = open(self.path, 'wb') # open file in binary mode, data has '\r' line-endings
380 fp.write(data)
381 fp.close()
382 MacOS.SetCreatorAndType(self.path, self._creator, 'TEXT')
383 self.getsettings()
384 self.writewindowsettings()
385 self.editgroup.editor.changed = 0
386 self.editgroup.editor.selchanged = 0
387 import linecache
388 if linecache.cache.has_key(self.path):
389 del linecache.cache[self.path]
390 import macostools
391 macostools.touched(self.path)
392 self.addrecentfile(self.path)
394 def can_save(self, menuitem):
395 return self.editgroup.editor.changed or self.editgroup.editor.selchanged
397 def domenu_save_as(self, *args):
398 path = EasyDialogs.AskFileForSave(message='Save as:', savedFileName=self.title)
399 if not path:
400 return 1
401 self.showbreakpoints(0)
402 self.path = path
403 self.setinfotext()
404 self.title = os.path.split(self.path)[-1]
405 self.wid.SetWTitle(self.title)
406 self.domenu_save()
407 self.editgroup.editor.setfile(self.getfilename())
408 app = W.getapplication()
409 app.makeopenwindowsmenu()
410 if hasattr(app, 'makescriptsmenu'):
411 app = W.getapplication()
412 fsr, changed = app.scriptsfolder.FSResolveAlias(None)
413 path = fsr.as_pathname()
414 if path == self.path[:len(path)]:
415 W.getapplication().makescriptsmenu()
417 def domenu_save_as_applet(self, *args):
418 import buildtools
420 buildtools.DEBUG = 0 # ouch.
422 if self.title[-3:] == ".py":
423 destname = self.title[:-3]
424 else:
425 destname = self.title + ".applet"
426 destname = EasyDialogs.AskFileForSave(message='Save as Applet:',
427 savedFileName=destname)
428 if not destname:
429 return 1
430 W.SetCursor("watch")
431 if self.path:
432 filename = self.path
433 if filename[-3:] == ".py":
434 rsrcname = filename[:-3] + '.rsrc'
435 else:
436 rsrcname = filename + '.rsrc'
437 else:
438 filename = self.title
439 rsrcname = ""
441 pytext = self.editgroup.editor.get()
442 pytext = string.split(pytext, '\r')
443 pytext = string.join(pytext, '\n') + '\n'
444 try:
445 code = compile(pytext, filename, "exec")
446 except (SyntaxError, EOFError):
447 raise buildtools.BuildError, "Syntax error in script %s" % `filename`
449 import tempfile
450 tmpdir = tempfile.mkdtemp()
452 if filename[-3:] != ".py":
453 filename = filename + ".py"
454 filename = os.path.join(tmpdir, os.path.split(filename)[1])
455 fp = open(filename, "w")
456 fp.write(pytext)
457 fp.close()
459 # Try removing the output file
460 try:
461 os.remove(destname)
462 except os.error:
463 pass
464 template = buildtools.findtemplate()
465 buildtools.process(template, filename, destname, 1, rsrcname=rsrcname, progress=None)
466 try:
467 os.remove(filename)
468 os.rmdir(tmpdir)
469 except os.error:
470 pass
472 def domenu_gotoline(self, *args):
473 self.linefield.selectall()
474 self.linefield.select(1)
475 self.linefield.selectall()
477 def domenu_selectline(self, *args):
478 self.editgroup.editor.expandselection()
480 def domenu_find(self, *args):
481 searchengine.show()
483 def domenu_entersearchstring(self, *args):
484 searchengine.setfindstring()
486 def domenu_replace(self, *args):
487 searchengine.replace()
489 def domenu_findnext(self, *args):
490 searchengine.findnext()
492 def domenu_replacefind(self, *args):
493 searchengine.replacefind()
495 def domenu_run(self, *args):
496 self.runbutton.push()
498 def domenu_runselection(self, *args):
499 self.runselbutton.push()
501 def run(self):
502 self._run()
504 def _run(self):
505 if self.run_with_interpreter:
506 if self.editgroup.editor.changed:
507 Qd.InitCursor()
508 save = EasyDialogs.AskYesNoCancel('Save "%s" before running?' % self.title, 1)
509 if save > 0:
510 if self.domenu_save():
511 return
512 elif save < 0:
513 return
514 if not self.path:
515 raise W.AlertError, "Can't run unsaved file"
516 self._run_with_interpreter()
517 elif self.run_with_cl_interpreter:
518 if self.editgroup.editor.changed:
519 Qd.InitCursor()
520 save = EasyDialogs.AskYesNoCancel('Save "%s" before running?' % self.title, 1)
521 if save > 0:
522 if self.domenu_save():
523 return
524 elif save < 0:
525 return
526 if not self.path:
527 raise W.AlertError, "Can't run unsaved file"
528 self._run_with_cl_interpreter()
529 else:
530 pytext = self.editgroup.editor.get()
531 globals, file, modname = self.getenvironment()
532 self.execstring(pytext, globals, globals, file, modname)
534 def _run_with_interpreter(self):
535 interp_path = os.path.join(sys.exec_prefix, "PythonInterpreter")
536 if not os.path.exists(interp_path):
537 raise W.AlertError, "Can't find interpreter"
538 import findertools
541 def _run_with_cl_interpreter(self):
542 import Terminal
543 interp_path = os.path.join(sys.exec_prefix, "bin", "python")
544 file_path = self.path
545 if not os.path.exists(interp_path):
546 # This "can happen" if we are running IDE under MacPython-OS9.
547 raise W.AlertError, "Can't find command-line Python"
548 cmd = '"%s" "%s" ; exit' % (interp_path, file_path)
549 t = Terminal.Terminal()
550 t.do_script(with_command=cmd)
552 def runselection(self):
553 self._runselection()
555 def _runselection(self):
556 if self.run_with_interpreter or self.run_with_cl_interpreter:
557 raise W.AlertError, "Can't run selection with Interpreter"
558 globals, file, modname = self.getenvironment()
559 locals = globals
560 # select whole lines
561 self.editgroup.editor.expandselection()
563 # get lineno of first selected line
564 selstart, selend = self.editgroup.editor.getselection()
565 selstart, selend = min(selstart, selend), max(selstart, selend)
566 selfirstline = self.editgroup.editor.offsettoline(selstart)
567 alltext = self.editgroup.editor.get()
568 pytext = alltext[selstart:selend]
569 lines = string.split(pytext, '\r')
570 indent = getminindent(lines)
571 if indent == 1:
572 classname = ''
573 alllines = string.split(alltext, '\r')
574 for i in range(selfirstline - 1, -1, -1):
575 line = alllines[i]
576 if line[:6] == 'class ':
577 classname = string.split(string.strip(line[6:]))[0]
578 classend = identifieRE_match(classname)
579 if classend < 1:
580 raise W.AlertError, "Can't find a class."
581 classname = classname[:classend]
582 break
583 elif line and line[0] not in '\t#':
584 raise W.AlertError, "Can't find a class."
585 else:
586 raise W.AlertError, "Can't find a class."
587 if globals.has_key(classname):
588 klass = globals[classname]
589 else:
590 raise W.AlertError, "Can't find class \"%s\"." % classname
591 # add class def
592 pytext = ("class %s:\n" % classname) + pytext
593 selfirstline = selfirstline - 1
594 elif indent > 0:
595 raise W.AlertError, "Can't run indented code."
597 # add "newlines" to fool compile/exec:
598 # now a traceback will give the right line number
599 pytext = selfirstline * '\r' + pytext
600 self.execstring(pytext, globals, locals, file, modname)
601 if indent == 1 and globals[classname] is not klass:
602 # update the class in place
603 klass.__dict__.update(globals[classname].__dict__)
604 globals[classname] = klass
606 def execstring(self, pytext, globals, locals, file, modname):
607 tracebackwindow.hide()
608 # update windows
609 W.getapplication().refreshwindows()
610 if self.run_as_main:
611 modname = "__main__"
612 if self.path:
613 dir = os.path.dirname(self.path)
614 savedir = os.getcwd()
615 os.chdir(dir)
616 sys.path.insert(0, dir)
617 self._scriptDone = False
618 if sys.platform == "darwin":
619 # On MacOSX, MacPython doesn't poll for command-period
620 # (cancel), so to enable the user to cancel a running
621 # script, we have to spawn a thread which does the
622 # polling. It will send a SIGINT to the main thread
623 # (in which the script is running) when the user types
624 # command-period.
625 from threading import Thread
626 t = Thread(target=self._userCancelledMonitor,
627 name="UserCancelledMonitor")
628 t.start()
629 try:
630 execstring(pytext, globals, locals, file, self.debugging,
631 modname, self.profiling)
632 finally:
633 self._scriptDone = True
634 if self.path:
635 os.chdir(savedir)
636 del sys.path[0]
638 def _userCancelledMonitor(self):
639 import time
640 from signal import SIGINT
641 while not self._scriptDone:
642 if Evt.CheckEventQueueForUserCancel():
643 # Send a SIGINT signal to ourselves.
644 # This gets delivered to the main thread,
645 # cancelling the running script.
646 os.kill(os.getpid(), SIGINT)
647 break
648 time.sleep(0.25)
650 def getenvironment(self):
651 if self.path:
652 file = self.path
653 dir = os.path.dirname(file)
654 # check if we're part of a package
655 modname = ""
656 while os.path.exists(os.path.join(dir, "__init__.py")):
657 dir, dirname = os.path.split(dir)
658 modname = dirname + '.' + modname
659 subname = _filename_as_modname(self.title)
660 if subname is None:
661 return self.globals, file, None
662 if modname:
663 if subname == "__init__":
664 # strip trailing period
665 modname = modname[:-1]
666 else:
667 modname = modname + subname
668 else:
669 modname = subname
670 if sys.modules.has_key(modname):
671 globals = sys.modules[modname].__dict__
672 self.globals = {}
673 else:
674 globals = self.globals
675 modname = subname
676 else:
677 file = '<%s>' % self.title
678 globals = self.globals
679 modname = file
680 return globals, file, modname
682 def write(self, stuff):
683 """for use as stdout"""
684 self._buf = self._buf + stuff
685 if '\n' in self._buf:
686 self.flush()
688 def flush(self):
689 stuff = string.split(self._buf, '\n')
690 stuff = string.join(stuff, '\r')
691 end = self.editgroup.editor.ted.WEGetTextLength()
692 self.editgroup.editor.ted.WESetSelection(end, end)
693 self.editgroup.editor.ted.WEInsert(stuff, None, None)
694 self.editgroup.editor.updatescrollbars()
695 self._buf = ""
696 # ? optional:
697 #self.wid.SelectWindow()
699 def getclasslist(self):
700 from string import find, strip
701 methodRE = re.compile(r"\r[ \t]+def ")
702 findMethod = methodRE.search
703 editor = self.editgroup.editor
704 text = editor.get()
705 list = []
706 append = list.append
707 functag = "func"
708 classtag = "class"
709 methodtag = "method"
710 pos = -1
711 if text[:4] == 'def ':
712 append((pos + 4, functag))
713 pos = 4
714 while 1:
715 pos = find(text, '\rdef ', pos + 1)
716 if pos < 0:
717 break
718 append((pos + 5, functag))
719 pos = -1
720 if text[:6] == 'class ':
721 append((pos + 6, classtag))
722 pos = 6
723 while 1:
724 pos = find(text, '\rclass ', pos + 1)
725 if pos < 0:
726 break
727 append((pos + 7, classtag))
728 pos = 0
729 while 1:
730 m = findMethod(text, pos + 1)
731 if m is None:
732 break
733 pos = m.regs[0][0]
734 #pos = find(text, '\r\tdef ', pos + 1)
735 append((m.regs[0][1], methodtag))
736 list.sort()
737 classlist = []
738 methodlistappend = None
739 offsetToLine = editor.ted.WEOffsetToLine
740 getLineRange = editor.ted.WEGetLineRange
741 append = classlist.append
742 for pos, tag in list:
743 lineno = offsetToLine(pos)
744 lineStart, lineEnd = getLineRange(lineno)
745 line = strip(text[pos:lineEnd])
746 line = line[:identifieRE_match(line)]
747 if tag is functag:
748 append(("def " + line, lineno + 1))
749 methodlistappend = None
750 elif tag is classtag:
751 append(["class " + line])
752 methodlistappend = classlist[-1].append
753 elif methodlistappend and tag is methodtag:
754 methodlistappend(("def " + line, lineno + 1))
755 return classlist
757 def popselectline(self, lineno):
758 self.editgroup.editor.selectline(lineno - 1)
760 def selectline(self, lineno, charoffset = 0):
761 self.editgroup.editor.selectline(lineno - 1, charoffset)
763 def addrecentfile(self, filename):
764 app = W.getapplication()
765 app.addrecentfile(filename)
767 class _saveoptions:
769 def __init__(self, creator, eoln):
770 self.rv = None
771 self.eoln = eoln
772 self.w = w = W.ModalDialog((260, 160), 'Save options')
773 radiobuttons = []
774 w.label = W.TextBox((8, 8, 80, 18), "File creator:")
775 w.ide_radio = W.RadioButton((8, 22, 160, 18), "This application", radiobuttons, self.ide_hit)
776 w.interp_radio = W.RadioButton((8, 42, 160, 18), "MacPython Interpreter", radiobuttons, self.interp_hit)
777 w.interpx_radio = W.RadioButton((8, 62, 160, 18), "OSX PythonW Interpreter", radiobuttons, self.interpx_hit)
778 w.other_radio = W.RadioButton((8, 82, 50, 18), "Other:", radiobuttons)
779 w.other_creator = W.EditText((62, 82, 40, 20), creator, self.otherselect)
780 w.none_radio = W.RadioButton((8, 102, 160, 18), "None", radiobuttons, self.none_hit)
781 w.cancelbutton = W.Button((-180, -30, 80, 16), "Cancel", self.cancelbuttonhit)
782 w.okbutton = W.Button((-90, -30, 80, 16), "Done", self.okbuttonhit)
783 w.setdefaultbutton(w.okbutton)
784 if creator == 'Pyth':
785 w.interp_radio.set(1)
786 elif creator == W._signature:
787 w.ide_radio.set(1)
788 elif creator == 'PytX':
789 w.interpx_radio.set(1)
790 elif creator == '\0\0\0\0':
791 w.none_radio.set(1)
792 else:
793 w.other_radio.set(1)
795 w.eolnlabel = W.TextBox((168, 8, 80, 18), "Newline style:")
796 radiobuttons = []
797 w.unix_radio = W.RadioButton((168, 22, 80, 18), "Unix", radiobuttons, self.unix_hit)
798 w.mac_radio = W.RadioButton((168, 42, 80, 18), "Macintosh", radiobuttons, self.mac_hit)
799 w.win_radio = W.RadioButton((168, 62, 80, 18), "Windows", radiobuttons, self.win_hit)
800 if self.eoln == '\n':
801 w.unix_radio.set(1)
802 elif self.eoln == '\r\n':
803 w.win_radio.set(1)
804 else:
805 w.mac_radio.set(1)
807 w.bind("cmd.", w.cancelbutton.push)
808 w.open()
810 def ide_hit(self):
811 self.w.other_creator.set(W._signature)
813 def interp_hit(self):
814 self.w.other_creator.set("Pyth")
816 def interpx_hit(self):
817 self.w.other_creator.set("PytX")
819 def none_hit(self):
820 self.w.other_creator.set("\0\0\0\0")
822 def otherselect(self, *args):
823 sel_from, sel_to = self.w.other_creator.getselection()
824 creator = self.w.other_creator.get()[:4]
825 creator = creator + " " * (4 - len(creator))
826 self.w.other_creator.set(creator)
827 self.w.other_creator.setselection(sel_from, sel_to)
828 self.w.other_radio.set(1)
830 def mac_hit(self):
831 self.eoln = '\r'
833 def unix_hit(self):
834 self.eoln = '\n'
836 def win_hit(self):
837 self.eoln = '\r\n'
839 def cancelbuttonhit(self):
840 self.w.close()
842 def okbuttonhit(self):
843 self.rv = (self.w.other_creator.get()[:4], self.eoln)
844 self.w.close()
847 def SaveOptions(creator, eoln):
848 s = _saveoptions(creator, eoln)
849 return s.rv
852 def _escape(where, what) :
853 return string.join(string.split(where, what), '\\' + what)
855 def _makewholewordpattern(word):
856 # first, escape special regex chars
857 for esc in "\\[]()|.*^+$?":
858 word = _escape(word, esc)
859 notwordcharspat = '[^' + _wordchars + ']'
860 pattern = '(' + word + ')'
861 if word[0] in _wordchars:
862 pattern = notwordcharspat + pattern
863 if word[-1] in _wordchars:
864 pattern = pattern + notwordcharspat
865 return re.compile(pattern)
868 class SearchEngine:
870 def __init__(self):
871 self.visible = 0
872 self.w = None
873 self.parms = { "find": "",
874 "replace": "",
875 "wrap": 1,
876 "casesens": 1,
877 "wholeword": 1
879 import MacPrefs
880 prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath)
881 if prefs.searchengine:
882 self.parms["casesens"] = prefs.searchengine.casesens
883 self.parms["wrap"] = prefs.searchengine.wrap
884 self.parms["wholeword"] = prefs.searchengine.wholeword
886 def show(self):
887 self.visible = 1
888 if self.w:
889 self.w.wid.ShowWindow()
890 self.w.wid.SelectWindow()
891 self.w.find.edit.select(1)
892 self.w.find.edit.selectall()
893 return
894 self.w = W.Dialog((420, 150), "Find")
896 self.w.find = TitledEditText((10, 4, 300, 36), "Search for:")
897 self.w.replace = TitledEditText((10, 100, 300, 36), "Replace with:")
899 self.w.boxes = W.Group((10, 50, 300, 40))
900 self.w.boxes.casesens = W.CheckBox((0, 0, 100, 16), "Case sensitive")
901 self.w.boxes.wholeword = W.CheckBox((0, 20, 100, 16), "Whole word")
902 self.w.boxes.wrap = W.CheckBox((110, 0, 100, 16), "Wrap around")
904 self.buttons = [ ("Find", "cmdf", self.find),
905 ("Replace", "cmdr", self.replace),
906 ("Replace all", None, self.replaceall),
907 ("Don't find", "cmdd", self.dont),
908 ("Cancel", "cmd.", self.cancel)
910 for i in range(len(self.buttons)):
911 bounds = -90, 22 + i * 24, 80, 16
912 title, shortcut, callback = self.buttons[i]
913 self.w[title] = W.Button(bounds, title, callback)
914 if shortcut:
915 self.w.bind(shortcut, self.w[title].push)
916 self.w.setdefaultbutton(self.w["Don't find"])
917 self.w.find.edit.bind("<key>", self.key)
918 self.w.bind("<activate>", self.activate)
919 self.w.bind("<close>", self.close)
920 self.w.open()
921 self.setparms()
922 self.w.find.edit.select(1)
923 self.w.find.edit.selectall()
924 self.checkbuttons()
926 def close(self):
927 self.hide()
928 return -1
930 def key(self, char, modifiers):
931 self.w.find.edit.key(char, modifiers)
932 self.checkbuttons()
933 return 1
935 def activate(self, onoff):
936 if onoff:
937 self.checkbuttons()
939 def checkbuttons(self):
940 editor = findeditor(self)
941 if editor:
942 if self.w.find.get():
943 for title, cmd, call in self.buttons[:-2]:
944 self.w[title].enable(1)
945 self.w.setdefaultbutton(self.w["Find"])
946 else:
947 for title, cmd, call in self.buttons[:-2]:
948 self.w[title].enable(0)
949 self.w.setdefaultbutton(self.w["Don't find"])
950 else:
951 for title, cmd, call in self.buttons[:-2]:
952 self.w[title].enable(0)
953 self.w.setdefaultbutton(self.w["Don't find"])
955 def find(self):
956 self.getparmsfromwindow()
957 if self.findnext():
958 self.hide()
960 def replace(self):
961 editor = findeditor(self)
962 if not editor:
963 return
964 if self.visible:
965 self.getparmsfromwindow()
966 text = editor.getselectedtext()
967 find = self.parms["find"]
968 if not self.parms["casesens"]:
969 find = string.lower(find)
970 text = string.lower(text)
971 if text == find:
972 self.hide()
973 editor.insert(self.parms["replace"])
975 def replaceall(self):
976 editor = findeditor(self)
977 if not editor:
978 return
979 if self.visible:
980 self.getparmsfromwindow()
981 W.SetCursor("watch")
982 find = self.parms["find"]
983 if not find:
984 return
985 findlen = len(find)
986 replace = self.parms["replace"]
987 replacelen = len(replace)
988 Text = editor.get()
989 if not self.parms["casesens"]:
990 find = string.lower(find)
991 text = string.lower(Text)
992 else:
993 text = Text
994 newtext = ""
995 pos = 0
996 counter = 0
997 while 1:
998 if self.parms["wholeword"]:
999 wholewordRE = _makewholewordpattern(find)
1000 match = wholewordRE.search(text, pos)
1001 if match:
1002 pos = match.start(1)
1003 else:
1004 pos = -1
1005 else:
1006 pos = string.find(text, find, pos)
1007 if pos < 0:
1008 break
1009 counter = counter + 1
1010 text = text[:pos] + replace + text[pos + findlen:]
1011 Text = Text[:pos] + replace + Text[pos + findlen:]
1012 pos = pos + replacelen
1013 W.SetCursor("arrow")
1014 if counter:
1015 self.hide()
1016 from Carbon import Res
1017 editor.textchanged()
1018 editor.selectionchanged()
1019 editor.set(Text)
1020 EasyDialogs.Message("Replaced %d occurrences" % counter)
1022 def dont(self):
1023 self.getparmsfromwindow()
1024 self.hide()
1026 def replacefind(self):
1027 self.replace()
1028 self.findnext()
1030 def setfindstring(self):
1031 editor = findeditor(self)
1032 if not editor:
1033 return
1034 find = editor.getselectedtext()
1035 if not find:
1036 return
1037 self.parms["find"] = find
1038 if self.w:
1039 self.w.find.edit.set(self.parms["find"])
1040 self.w.find.edit.selectall()
1042 def findnext(self):
1043 editor = findeditor(self)
1044 if not editor:
1045 return
1046 find = self.parms["find"]
1047 if not find:
1048 return
1049 text = editor.get()
1050 if not self.parms["casesens"]:
1051 find = string.lower(find)
1052 text = string.lower(text)
1053 selstart, selend = editor.getselection()
1054 selstart, selend = min(selstart, selend), max(selstart, selend)
1055 if self.parms["wholeword"]:
1056 wholewordRE = _makewholewordpattern(find)
1057 match = wholewordRE.search(text, selend)
1058 if match:
1059 pos = match.start(1)
1060 else:
1061 pos = -1
1062 else:
1063 pos = string.find(text, find, selend)
1064 if pos >= 0:
1065 editor.setselection(pos, pos + len(find))
1066 return 1
1067 elif self.parms["wrap"]:
1068 if self.parms["wholeword"]:
1069 match = wholewordRE.search(text, 0)
1070 if match:
1071 pos = match.start(1)
1072 else:
1073 pos = -1
1074 else:
1075 pos = string.find(text, find)
1076 if selstart > pos >= 0:
1077 editor.setselection(pos, pos + len(find))
1078 return 1
1080 def setparms(self):
1081 for key, value in self.parms.items():
1082 try:
1083 self.w[key].set(value)
1084 except KeyError:
1085 self.w.boxes[key].set(value)
1087 def getparmsfromwindow(self):
1088 if not self.w:
1089 return
1090 for key, value in self.parms.items():
1091 try:
1092 value = self.w[key].get()
1093 except KeyError:
1094 value = self.w.boxes[key].get()
1095 self.parms[key] = value
1097 def cancel(self):
1098 self.hide()
1099 self.setparms()
1101 def hide(self):
1102 if self.w:
1103 self.w.wid.HideWindow()
1104 self.visible = 0
1106 def writeprefs(self):
1107 import MacPrefs
1108 self.getparmsfromwindow()
1109 prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath)
1110 prefs.searchengine.casesens = self.parms["casesens"]
1111 prefs.searchengine.wrap = self.parms["wrap"]
1112 prefs.searchengine.wholeword = self.parms["wholeword"]
1113 prefs.save()
1116 class TitledEditText(W.Group):
1118 def __init__(self, possize, title, text = ""):
1119 W.Group.__init__(self, possize)
1120 self.title = W.TextBox((0, 0, 0, 16), title)
1121 self.edit = W.EditText((0, 16, 0, 0), text)
1123 def set(self, value):
1124 self.edit.set(value)
1126 def get(self):
1127 return self.edit.get()
1130 class ClassFinder(W.PopupWidget):
1132 def click(self, point, modifiers):
1133 W.SetCursor("watch")
1134 self.set(self._parentwindow.getclasslist())
1135 W.PopupWidget.click(self, point, modifiers)
1138 def getminindent(lines):
1139 indent = -1
1140 for line in lines:
1141 stripped = string.strip(line)
1142 if not stripped or stripped[0] == '#':
1143 continue
1144 if indent < 0 or line[:indent] <> indent * '\t':
1145 indent = 0
1146 for c in line:
1147 if c <> '\t':
1148 break
1149 indent = indent + 1
1150 return indent
1153 def getoptionkey():
1154 return not not ord(Evt.GetKeys()[7]) & 0x04
1157 def execstring(pytext, globals, locals, filename="<string>", debugging=0,
1158 modname="__main__", profiling=0):
1159 if debugging:
1160 import PyDebugger, bdb
1161 BdbQuit = bdb.BdbQuit
1162 else:
1163 BdbQuit = 'BdbQuitDummyException'
1164 pytext = string.split(pytext, '\r')
1165 pytext = string.join(pytext, '\n') + '\n'
1166 W.SetCursor("watch")
1167 globals['__name__'] = modname
1168 globals['__file__'] = filename
1169 sys.argv = [filename]
1170 try:
1171 code = compile(pytext, filename, "exec")
1172 except:
1173 # XXXX BAAAADDD.... We let tracebackwindow decide to treat SyntaxError
1174 # special. That's wrong because THIS case is special (could be literal
1175 # overflow!) and SyntaxError could mean we need a traceback (syntax error
1176 # in imported module!!!
1177 tracebackwindow.traceback(1, filename)
1178 return
1179 try:
1180 if debugging:
1181 PyDebugger.startfromhere()
1182 else:
1183 if hasattr(MacOS, 'EnableAppswitch'):
1184 MacOS.EnableAppswitch(0)
1185 try:
1186 if profiling:
1187 import profile, ProfileBrowser
1188 p = profile.Profile()
1189 p.set_cmd(filename)
1190 try:
1191 p.runctx(code, globals, locals)
1192 finally:
1193 import pstats
1195 stats = pstats.Stats(p)
1196 ProfileBrowser.ProfileBrowser(stats)
1197 else:
1198 exec code in globals, locals
1199 finally:
1200 if hasattr(MacOS, 'EnableAppswitch'):
1201 MacOS.EnableAppswitch(-1)
1202 except W.AlertError, detail:
1203 raise W.AlertError, detail
1204 except (KeyboardInterrupt, BdbQuit):
1205 pass
1206 except SystemExit, arg:
1207 if arg.code:
1208 sys.stderr.write("Script exited with status code: %s\n" % repr(arg.code))
1209 except:
1210 if debugging:
1211 sys.settrace(None)
1212 PyDebugger.postmortem(sys.exc_type, sys.exc_value, sys.exc_traceback)
1213 return
1214 else:
1215 tracebackwindow.traceback(1, filename)
1216 if debugging:
1217 sys.settrace(None)
1218 PyDebugger.stop()
1221 _identifieRE = re.compile(r"[A-Za-z_][A-Za-z_0-9]*")
1223 def identifieRE_match(str):
1224 match = _identifieRE.match(str)
1225 if not match:
1226 return -1
1227 return match.end()
1229 def _filename_as_modname(fname):
1230 if fname[-3:] == '.py':
1231 modname = fname[:-3]
1232 match = _identifieRE.match(modname)
1233 if match and match.start() == 0 and match.end() == len(modname):
1234 return string.join(string.split(modname, '.'), '_')
1236 def findeditor(topwindow, fromtop = 0):
1237 wid = MyFrontWindow()
1238 if not fromtop:
1239 if topwindow.w and wid == topwindow.w.wid:
1240 wid = topwindow.w.wid.GetNextWindow()
1241 if not wid:
1242 return
1243 app = W.getapplication()
1244 if app._windows.has_key(wid): # KeyError otherwise can happen in RoboFog :-(
1245 window = W.getapplication()._windows[wid]
1246 else:
1247 return
1248 if not isinstance(window, Editor):
1249 return
1250 return window.editgroup.editor
1253 class _EditorDefaultSettings:
1255 def __init__(self):
1256 self.template = "%s, %d point"
1257 self.fontsettings, self.tabsettings, self.windowsize = geteditorprefs()
1258 self.w = W.Dialog((328, 120), "Editor default settings")
1259 self.w.setfontbutton = W.Button((8, 8, 80, 16), "Set font\xc9", self.dofont)
1260 self.w.fonttext = W.TextBox((98, 10, -8, 14), self.template % (self.fontsettings[0], self.fontsettings[2]))
1262 self.w.picksizebutton = W.Button((8, 50, 80, 16), "Front window", self.picksize)
1263 self.w.xsizelabel = W.TextBox((98, 32, 40, 14), "Width:")
1264 self.w.ysizelabel = W.TextBox((148, 32, 40, 14), "Height:")
1265 self.w.xsize = W.EditText((98, 48, 40, 20), `self.windowsize[0]`)
1266 self.w.ysize = W.EditText((148, 48, 40, 20), `self.windowsize[1]`)
1268 self.w.cancelbutton = W.Button((-180, -26, 80, 16), "Cancel", self.cancel)
1269 self.w.okbutton = W.Button((-90, -26, 80, 16), "Done", self.ok)
1270 self.w.setdefaultbutton(self.w.okbutton)
1271 self.w.bind('cmd.', self.w.cancelbutton.push)
1272 self.w.open()
1274 def picksize(self):
1275 app = W.getapplication()
1276 editor = findeditor(self)
1277 if editor is not None:
1278 width, height = editor._parentwindow._bounds[2:]
1279 self.w.xsize.set(`width`)
1280 self.w.ysize.set(`height`)
1281 else:
1282 raise W.AlertError, "No edit window found"
1284 def dofont(self):
1285 import FontSettings
1286 settings = FontSettings.FontDialog(self.fontsettings, self.tabsettings)
1287 if settings:
1288 self.fontsettings, self.tabsettings = settings
1289 sys.exc_traceback = None
1290 self.w.fonttext.set(self.template % (self.fontsettings[0], self.fontsettings[2]))
1292 def close(self):
1293 self.w.close()
1294 del self.w
1296 def cancel(self):
1297 self.close()
1299 def ok(self):
1300 try:
1301 width = string.atoi(self.w.xsize.get())
1302 except:
1303 self.w.xsize.select(1)
1304 self.w.xsize.selectall()
1305 raise W.AlertError, "Bad number for window width"
1306 try:
1307 height = string.atoi(self.w.ysize.get())
1308 except:
1309 self.w.ysize.select(1)
1310 self.w.ysize.selectall()
1311 raise W.AlertError, "Bad number for window height"
1312 self.windowsize = width, height
1313 seteditorprefs(self.fontsettings, self.tabsettings, self.windowsize)
1314 self.close()
1316 def geteditorprefs():
1317 import MacPrefs
1318 prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath)
1319 try:
1320 fontsettings = prefs.pyedit.fontsettings
1321 tabsettings = prefs.pyedit.tabsettings
1322 windowsize = prefs.pyedit.windowsize
1323 except:
1324 fontsettings = prefs.pyedit.fontsettings = ("Geneva", 0, 10, (0, 0, 0))
1325 tabsettings = prefs.pyedit.tabsettings = (8, 1)
1326 windowsize = prefs.pyedit.windowsize = (500, 250)
1327 sys.exc_traceback = None
1328 return fontsettings, tabsettings, windowsize
1330 def seteditorprefs(fontsettings, tabsettings, windowsize):
1331 import MacPrefs
1332 prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath)
1333 prefs.pyedit.fontsettings = fontsettings
1334 prefs.pyedit.tabsettings = tabsettings
1335 prefs.pyedit.windowsize = windowsize
1336 prefs.save()
1338 _defaultSettingsEditor = None
1340 def EditorDefaultSettings():
1341 global _defaultSettingsEditor
1342 if _defaultSettingsEditor is None or not hasattr(_defaultSettingsEditor, "w"):
1343 _defaultSettingsEditor = _EditorDefaultSettings()
1344 else:
1345 _defaultSettingsEditor.w.select()
1347 def resolvealiases(path):
1348 try:
1349 fsr, d1, d2 = File.FSResolveAliasFile(path, 1)
1350 path = fsr.as_pathname()
1351 return path
1352 except (File.Error, ValueError), (error, str):
1353 if error <> -120:
1354 raise
1355 dir, file = os.path.split(path)
1356 return os.path.join(resolvealiases(dir), file)
1358 searchengine = SearchEngine()
1359 tracebackwindow = Wtraceback.TraceBack()