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