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