Bump version to 0.9.1.
[python/dscho.git] / Mac / Tools / IDE / PyEdit.py
blob0a0773e4fab25b1ce84fe5429663aef4e8398985
1 """A (less & less) simple Python editor"""
3 import W
4 import Wtraceback
5 from Wkeys import *
7 import macfs
8 import MacOS
9 import Win
10 import Res
11 import Evt
12 import os
13 import imp
14 import sys
15 import string
16 import marshal
17 import regex
19 try:
20 # experimental microthread support
21 import uthread2
22 except ImportError:
23 uthread2 = None
25 _scriptuntitledcounter = 1
26 _wordchars = string.letters + string.digits + "_"
29 runButtonLabels = ["Run all", "Stop!"]
30 runSelButtonLabels = ["Run selection", "Pause!", "Resume"]
33 class Editor(W.Window):
35 def __init__(self, path = "", title = ""):
36 defaultfontsettings, defaulttabsettings, defaultwindowsize = geteditorprefs()
37 global _scriptuntitledcounter
38 if not path:
39 if title:
40 self.title = title
41 else:
42 self.title = "Untitled Script " + `_scriptuntitledcounter`
43 _scriptuntitledcounter = _scriptuntitledcounter + 1
44 text = ""
45 self._creator = W._signature
46 elif os.path.exists(path):
47 path = resolvealiases(path)
48 dir, name = os.path.split(path)
49 self.title = name
50 f = open(path, "rb")
51 text = f.read()
52 f.close()
53 fss = macfs.FSSpec(path)
54 self._creator, filetype = fss.GetCreatorType()
55 else:
56 raise IOError, "file '%s' does not exist" % path
57 self.path = path
59 if '\n' in text:
60 import EasyDialogs
61 if string.find(text, '\r\n') >= 0:
62 sourceOS = 'DOS'
63 searchString = '\r\n'
64 else:
65 sourceOS = 'UNIX'
66 searchString = '\n'
67 change = EasyDialogs.AskYesNoCancel(%s² contains %s-style line feeds. '
68 'Change them to MacOS carriage returns?' % (self.title, sourceOS), 1)
69 # bug: Cancel is treated as No
70 if change > 0:
71 text = string.replace(text, searchString, '\r')
72 else:
73 change = 0
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.changed = 1
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 if self.settings.has_key("run_as_main"):
109 self.run_as_main = self.settings["run_as_main"]
110 else:
111 self.run_as_main = 0
112 self._threadstate = (0, 0)
113 self._thread = None
115 def readwindowsettings(self):
116 try:
117 resref = Res.FSpOpenResFile(self.path, 1)
118 except Res.Error:
119 return
120 try:
121 Res.UseResFile(resref)
122 data = Res.Get1Resource('PyWS', 128)
123 self.settings = marshal.loads(data.data)
124 except:
125 pass
126 Res.CloseResFile(resref)
128 def writewindowsettings(self):
129 try:
130 resref = Res.FSpOpenResFile(self.path, 3)
131 except Res.Error:
132 Res.CreateResFile(self.path)
133 resref = Res.FSpOpenResFile(self.path, 3)
134 try:
135 data = Res.Resource(marshal.dumps(self.settings))
136 Res.UseResFile(resref)
137 try:
138 temp = Res.Get1Resource('PyWS', 128)
139 temp.RemoveResource()
140 except Res.Error:
141 pass
142 data.AddResource('PyWS', 128, "window settings")
143 finally:
144 Res.UpdateResFile(resref)
145 Res.CloseResFile(resref)
147 def getsettings(self):
148 self.settings = {}
149 self.settings["windowbounds"] = self.getbounds()
150 self.settings["selection"] = self.getselection()
151 self.settings["fontsettings"] = self.editgroup.editor.getfontsettings()
152 self.settings["tabsize"] = self.editgroup.editor.gettabsettings()
153 self.settings["run_as_main"] = self.run_as_main
155 def get(self):
156 return self.editgroup.editor.get()
158 def getselection(self):
159 return self.editgroup.editor.ted.WEGetSelection()
161 def setselection(self, selstart, selend):
162 self.editgroup.editor.setselection(selstart, selend)
164 def getfilename(self):
165 if self.path:
166 return self.path
167 return '<%s>' % self.title
169 def setupwidgets(self, text):
170 topbarheight = 24
171 popfieldwidth = 80
172 self.lastlineno = None
174 # make an editor
175 self.editgroup = W.Group((0, topbarheight + 1, 0, 0))
176 editor = W.PyEditor((0, 0, -15,-15), text,
177 fontsettings = self.fontsettings,
178 tabsettings = self.tabsettings,
179 file = self.getfilename())
181 # make the widgets
182 self.popfield = ClassFinder((popfieldwidth - 17, -15, 16, 16), [], self.popselectline)
183 self.linefield = W.EditText((-1, -15, popfieldwidth - 15, 16), inset = (6, 1))
184 self.editgroup._barx = W.Scrollbar((popfieldwidth - 2, -15, -14, 16), editor.hscroll, max = 32767)
185 self.editgroup._bary = W.Scrollbar((-15, 14, 16, -14), editor.vscroll, max = 32767)
186 self.editgroup.editor = editor # add editor *after* scrollbars
188 self.editgroup.optionsmenu = W.PopupMenu((-15, -1, 16, 16), [])
189 self.editgroup.optionsmenu.bind('<click>', self.makeoptionsmenu)
191 self.bevelbox = W.BevelBox((0, 0, 0, topbarheight))
192 self.hline = W.HorizontalLine((0, topbarheight, 0, 0))
193 self.infotext = W.TextBox((175, 6, -4, 14), backgroundcolor = (0xe000, 0xe000, 0xe000))
194 self.runbutton = W.Button((5, 4, 80, 16), runButtonLabels[0], self.run)
195 self.runselbutton = W.Button((90, 4, 80, 16), runSelButtonLabels[0], self.runselection)
197 # bind some keys
198 editor.bind("cmdr", self.runbutton.push)
199 editor.bind("enter", self.runselbutton.push)
200 editor.bind("cmdj", self.domenu_gotoline)
201 editor.bind("cmdd", self.domenu_toggledebugger)
202 editor.bind("<idle>", self.updateselection)
204 editor.bind("cmde", searchengine.setfindstring)
205 editor.bind("cmdf", searchengine.show)
206 editor.bind("cmdg", searchengine.findnext)
207 editor.bind("cmdshiftr", searchengine.replace)
208 editor.bind("cmdt", searchengine.replacefind)
210 self.linefield.bind("return", self.dolinefield)
211 self.linefield.bind("enter", self.dolinefield)
212 self.linefield.bind("tab", self.dolinefield)
214 # intercept clicks
215 editor.bind("<click>", self.clickeditor)
216 self.linefield.bind("<click>", self.clicklinefield)
218 def makeoptionsmenu(self):
219 menuitems = [('Font settingsŠ', self.domenu_fontsettings),
220 ("Save optionsŠ", self.domenu_options),
221 '-',
222 ('\0' + chr(self.run_as_main) + 'Run as __main__', self.domenu_toggle_run_as_main),
223 ('Modularize', self.domenu_modularize),
224 ('Browse namespaceŠ', 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Š', 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.editgroup.editor.selchanged = 1
242 def showbreakpoints(self, onoff):
243 self.editgroup.editor.showbreakpoints(onoff)
244 self.debugging = onoff
246 def domenu_clearbreakpoints(self, *args):
247 self.editgroup.editor.clearbreakpoints()
249 def domenu_editbreakpoints(self, *args):
250 self.editgroup.editor.editbreakpoints()
252 def domenu_toggledebugger(self, *args):
253 if not self.debugging:
254 W.SetCursor('watch')
255 self.debugging = not self.debugging
256 self.editgroup.editor.togglebreakpoints()
258 def domenu_toggleprofiler(self, *args):
259 self.profiling = not self.profiling
261 def domenu_browsenamespace(self, *args):
262 import PyBrowser, W
263 W.SetCursor('watch')
264 globals, file, modname = self.getenvironment()
265 if not modname:
266 modname = self.title
267 PyBrowser.Browser(globals, "Object browser: " + modname)
269 def domenu_modularize(self, *args):
270 modname = _filename_as_modname(self.title)
271 if not modname:
272 raise W.AlertError, 'Can¹t modularize ³%s²' % self.title
273 run_as_main = self.run_as_main
274 self.run_as_main = 0
275 self.run()
276 self.run_as_main = run_as_main
277 if self.path:
278 file = self.path
279 else:
280 file = self.title
282 if self.globals and not sys.modules.has_key(modname):
283 module = imp.new_module(modname)
284 for attr in self.globals.keys():
285 setattr(module,attr,self.globals[attr])
286 sys.modules[modname] = module
287 self.globals = {}
289 def domenu_fontsettings(self, *args):
290 import FontSettings
291 fontsettings = self.editgroup.editor.getfontsettings()
292 tabsettings = self.editgroup.editor.gettabsettings()
293 settings = FontSettings.FontDialog(fontsettings, tabsettings)
294 if settings:
295 fontsettings, tabsettings = settings
296 self.editgroup.editor.setfontsettings(fontsettings)
297 self.editgroup.editor.settabsettings(tabsettings)
299 def domenu_options(self, *args):
300 rv = SaveOptions(self._creator)
301 if rv:
302 self.editgroup.editor.selchanged = 1 # ouch...
303 self._creator = rv
305 def clicklinefield(self):
306 if self._currentwidget <> self.linefield:
307 self.linefield.select(1)
308 self.linefield.selectall()
309 return 1
311 def clickeditor(self):
312 if self._currentwidget <> self.editgroup.editor:
313 self.dolinefield()
314 return 1
316 def updateselection(self, force = 0):
317 sel = min(self.editgroup.editor.getselection())
318 lineno = self.editgroup.editor.offsettoline(sel)
319 if lineno <> self.lastlineno or force:
320 self.lastlineno = lineno
321 self.linefield.set(str(lineno + 1))
322 self.linefield.selview()
324 def dolinefield(self):
325 try:
326 lineno = string.atoi(self.linefield.get()) - 1
327 if lineno <> self.lastlineno:
328 self.editgroup.editor.selectline(lineno)
329 self.updateselection(1)
330 except:
331 self.updateselection(1)
332 self.editgroup.editor.select(1)
334 def setinfotext(self):
335 if not hasattr(self, 'infotext'):
336 return
337 if self.path:
338 self.infotext.set(self.path)
339 else:
340 self.infotext.set("")
342 def close(self):
343 if self.editgroup.editor.changed:
344 import EasyDialogs
345 import Qd
346 Qd.InitCursor() # XXX should be done by dialog
347 save = EasyDialogs.AskYesNoCancel('Save window ³%s² before closing?' % self.title, 1)
348 if save > 0:
349 if self.domenu_save():
350 return 1
351 elif save < 0:
352 return 1
353 self.globals = None # XXX doesn't help... all globals leak :-(
354 W.Window.close(self)
356 def domenu_close(self, *args):
357 return self.close()
359 def domenu_save(self, *args):
360 if not self.path:
361 # Will call us recursively
362 return self.domenu_save_as()
363 data = self.editgroup.editor.get()
364 fp = open(self.path, 'wb') # open file in binary mode, data has '\r' line-endings
365 fp.write(data)
366 fp.close()
367 fss = macfs.FSSpec(self.path)
368 fss.SetCreatorType(self._creator, 'TEXT')
369 self.getsettings()
370 self.writewindowsettings()
371 self.editgroup.editor.changed = 0
372 self.editgroup.editor.selchanged = 0
373 import linecache
374 if linecache.cache.has_key(self.path):
375 del linecache.cache[self.path]
376 import macostools
377 macostools.touched(self.path)
379 def can_save(self, menuitem):
380 return self.editgroup.editor.changed or self.editgroup.editor.selchanged
382 def domenu_save_as(self, *args):
383 fss, ok = macfs.StandardPutFile('Save as:', self.title)
384 if not ok:
385 return 1
386 self.showbreakpoints(0)
387 self.path = fss.as_pathname()
388 self.setinfotext()
389 self.title = os.path.split(self.path)[-1]
390 self.wid.SetWTitle(self.title)
391 self.domenu_save()
392 self.editgroup.editor.setfile(self.getfilename())
393 app = W.getapplication()
394 app.makeopenwindowsmenu()
395 if hasattr(app, 'makescriptsmenu'):
396 app = W.getapplication()
397 fss, fss_changed = app.scriptsfolder.Resolve()
398 path = fss.as_pathname()
399 if path == self.path[:len(path)]:
400 W.getapplication().makescriptsmenu()
402 def domenu_save_as_applet(self, *args):
403 try:
404 import buildtools
405 except ImportError:
406 # only have buildtools in Python >= 1.5.2
407 raise W.AlertError, "³Save as Applet² is only supported in\rPython 1.5.2 and up."
409 buildtools.DEBUG = 0 # ouch.
411 if self.title[-3:] == ".py":
412 destname = self.title[:-3]
413 else:
414 destname = self.title + ".applet"
415 fss, ok = macfs.StandardPutFile('Save as Applet:', destname)
416 if not ok:
417 return 1
418 W.SetCursor("watch")
419 destname = fss.as_pathname()
420 if self.path:
421 filename = self.path
422 if filename[-3:] == ".py":
423 rsrcname = filename[:-3] + '.rsrc'
424 else:
425 rsrcname = filename + '.rsrc'
426 else:
427 filename = self.title
428 rsrcname = ""
430 pytext = self.editgroup.editor.get()
431 pytext = string.split(pytext, '\r')
432 pytext = string.join(pytext, '\n') + '\n'
433 try:
434 code = compile(pytext, filename, "exec")
435 except (SyntaxError, EOFError):
436 raise buildtools.BuildError, "Syntax error in script %s" % `filename`
438 # Try removing the output file
439 try:
440 os.remove(destname)
441 except os.error:
442 pass
443 template = buildtools.findtemplate()
444 buildtools.process_common(template, None, code, rsrcname, destname, 0, 1)
446 def domenu_gotoline(self, *args):
447 self.linefield.selectall()
448 self.linefield.select(1)
449 self.linefield.selectall()
451 def domenu_selectline(self, *args):
452 self.editgroup.editor.expandselection()
454 def domenu_find(self, *args):
455 searchengine.show()
457 def domenu_entersearchstring(self, *args):
458 searchengine.setfindstring()
460 def domenu_replace(self, *args):
461 searchengine.replace()
463 def domenu_findnext(self, *args):
464 searchengine.findnext()
466 def domenu_replacefind(self, *args):
467 searchengine.replacefind()
469 def domenu_run(self, *args):
470 self.runbutton.push()
472 def domenu_runselection(self, *args):
473 self.runselbutton.push()
475 def run(self):
476 if self._threadstate == (0, 0):
477 self._run()
478 else:
479 uthread2.globalLock()
480 self._thread.raiseException(KeyboardInterrupt)
481 if self._thread.isPaused():
482 self._thread.start()
483 uthread2.globalUnlock()
485 def _run(self):
486 pytext = self.editgroup.editor.get()
487 globals, file, modname = self.getenvironment()
488 self.execstring(pytext, globals, globals, file, modname)
490 def runselection(self):
491 if self._threadstate == (0, 0):
492 self._runselection()
493 elif self._threadstate == (1, 1):
494 self._thread.pause()
495 self.setthreadstate((1, 2))
496 elif self._threadstate == (1, 2):
497 self._thread.start()
498 self.setthreadstate((1, 1))
500 def _runselection(self):
501 globals, file, modname = self.getenvironment()
502 locals = globals
503 # select whole lines
504 self.editgroup.editor.expandselection()
506 # get lineno of first selected line
507 selstart, selend = self.editgroup.editor.getselection()
508 selstart, selend = min(selstart, selend), max(selstart, selend)
509 selfirstline = self.editgroup.editor.offsettoline(selstart)
510 alltext = self.editgroup.editor.get()
511 pytext = alltext[selstart:selend]
512 lines = string.split(pytext, '\r')
513 indent = getminindent(lines)
514 if indent == 1:
515 classname = ''
516 alllines = string.split(alltext, '\r')
517 identifieRE_match = _identifieRE.match
518 for i in range(selfirstline - 1, -1, -1):
519 line = alllines[i]
520 if line[:6] == 'class ':
521 classname = string.split(string.strip(line[6:]))[0]
522 classend = identifieRE_match(classname)
523 if classend < 1:
524 raise W.AlertError, 'Can¹t find a class.'
525 classname = classname[:classend]
526 break
527 elif line and line[0] not in '\t#':
528 raise W.AlertError, 'Can¹t find a class.'
529 else:
530 raise W.AlertError, 'Can¹t find a class.'
531 if globals.has_key(classname):
532 locals = globals[classname].__dict__
533 else:
534 raise W.AlertError, 'Can¹t find class ³%s².' % classname
535 # dedent to top level
536 for i in range(len(lines)):
537 lines[i] = lines[i][1:]
538 pytext = string.join(lines, '\r')
539 elif indent > 0:
540 raise W.AlertError, 'Can¹t run indented code.'
542 # add "newlines" to fool compile/exec:
543 # now a traceback will give the right line number
544 pytext = selfirstline * '\r' + pytext
545 self.execstring(pytext, globals, locals, file, modname)
547 def setthreadstate(self, state):
548 oldstate = self._threadstate
549 if oldstate[0] <> state[0]:
550 self.runbutton.settitle(runButtonLabels[state[0]])
551 if oldstate[1] <> state[1]:
552 self.runselbutton.settitle(runSelButtonLabels[state[1]])
553 self._threadstate = state
555 def _exec_threadwrapper(self, *args, **kwargs):
556 apply(execstring, args, kwargs)
557 self.setthreadstate((0, 0))
558 self._thread = None
560 def execstring(self, pytext, globals, locals, file, modname):
561 tracebackwindow.hide()
562 # update windows
563 W.getapplication().refreshwindows()
564 if self.run_as_main:
565 modname = "__main__"
566 if self.path:
567 dir = os.path.dirname(self.path)
568 savedir = os.getcwd()
569 os.chdir(dir)
570 sys.path.insert(0, dir)
571 else:
572 cwdindex = None
573 try:
574 if uthread2 and uthread2.currentThread() is not None:
575 self._thread = uthread2.Thread(file,
576 self._exec_threadwrapper, pytext, globals, locals, file, self.debugging,
577 modname, self.profiling)
578 self.setthreadstate((1, 1))
579 self._thread.start()
580 else:
581 execstring(pytext, globals, locals, file, self.debugging,
582 modname, self.profiling)
583 finally:
584 if self.path:
585 os.chdir(savedir)
586 del sys.path[0]
588 def getenvironment(self):
589 if self.path:
590 file = self.path
591 dir = os.path.dirname(file)
592 # check if we're part of a package
593 modname = ""
594 while os.path.exists(os.path.join(dir, "__init__.py")):
595 dir, dirname = os.path.split(dir)
596 modname = dirname + '.' + modname
597 subname = _filename_as_modname(self.title)
598 if modname:
599 if subname == "__init__":
600 # strip trailing period
601 modname = modname[:-1]
602 else:
603 modname = modname + subname
604 else:
605 modname = subname
606 if sys.modules.has_key(modname):
607 globals = sys.modules[modname].__dict__
608 self.globals = {}
609 else:
610 globals = self.globals
611 modname = subname
612 else:
613 file = '<%s>' % self.title
614 globals = self.globals
615 modname = file
616 return globals, file, modname
618 def write(self, stuff):
619 """for use as stdout"""
620 self._buf = self._buf + stuff
621 if '\n' in self._buf:
622 self.flush()
624 def flush(self):
625 stuff = string.split(self._buf, '\n')
626 stuff = string.join(stuff, '\r')
627 end = self.editgroup.editor.ted.WEGetTextLength()
628 self.editgroup.editor.ted.WESetSelection(end, end)
629 self.editgroup.editor.ted.WEInsert(stuff, None, None)
630 self.editgroup.editor.updatescrollbars()
631 self._buf = ""
632 # ? optional:
633 #self.wid.SelectWindow()
635 def getclasslist(self):
636 from string import find, strip
637 import re
638 methodRE = re.compile(r"\r[ \t]+def ")
639 findMethod = methodRE.search
640 editor = self.editgroup.editor
641 text = editor.get()
642 list = []
643 append = list.append
644 functag = "func"
645 classtag = "class"
646 methodtag = "method"
647 pos = -1
648 if text[:4] == 'def ':
649 append((pos + 4, functag))
650 pos = 4
651 while 1:
652 pos = find(text, '\rdef ', pos + 1)
653 if pos < 0:
654 break
655 append((pos + 5, functag))
656 pos = -1
657 if text[:6] == 'class ':
658 append((pos + 6, classtag))
659 pos = 6
660 while 1:
661 pos = find(text, '\rclass ', pos + 1)
662 if pos < 0:
663 break
664 append((pos + 7, classtag))
665 pos = 0
666 while 1:
667 m = findMethod(text, pos + 1)
668 if m is None:
669 break
670 pos = m.regs[0][0]
671 #pos = find(text, '\r\tdef ', pos + 1)
672 append((m.regs[0][1], methodtag))
673 list.sort()
674 classlist = []
675 methodlistappend = None
676 offsetToLine = editor.ted.WEOffsetToLine
677 getLineRange = editor.ted.WEGetLineRange
678 append = classlist.append
679 identifieRE_match = _identifieRE.match
680 for pos, tag in list:
681 lineno = offsetToLine(pos)
682 lineStart, lineEnd = getLineRange(lineno)
683 line = strip(text[pos:lineEnd])
684 line = line[:identifieRE_match(line)]
685 if tag is functag:
686 append(("def " + line, lineno + 1))
687 methodlistappend = None
688 elif tag is classtag:
689 append(["class " + line])
690 methodlistappend = classlist[-1].append
691 elif methodlistappend and tag is methodtag:
692 methodlistappend(("def " + line, lineno + 1))
693 return classlist
695 def popselectline(self, lineno):
696 self.editgroup.editor.selectline(lineno - 1)
698 def selectline(self, lineno, charoffset = 0):
699 self.editgroup.editor.selectline(lineno - 1, charoffset)
701 class _saveoptions:
703 def __init__(self, creator):
704 self.rv = None
705 self.w = w = W.ModalDialog((240, 140), 'Save options')
706 radiobuttons = []
707 w.label = W.TextBox((8, 8, 80, 18), "File creator:")
708 w.ide_radio = W.RadioButton((8, 22, 160, 18), "This application", radiobuttons, self.ide_hit)
709 w.interp_radio = W.RadioButton((8, 42, 160, 18), "Python Interpreter", radiobuttons, self.interp_hit)
710 w.other_radio = W.RadioButton((8, 62, 50, 18), "Other:", radiobuttons)
711 w.other_creator = W.EditText((62, 62, 40, 20), creator, self.otherselect)
712 w.cancelbutton = W.Button((-180, -30, 80, 16), "Cancel", self.cancelbuttonhit)
713 w.okbutton = W.Button((-90, -30, 80, 16), "Done", self.okbuttonhit)
714 w.setdefaultbutton(w.okbutton)
715 if creator == 'Pyth':
716 w.interp_radio.set(1)
717 elif creator == W._signature:
718 w.ide_radio.set(1)
719 else:
720 w.other_radio.set(1)
721 w.bind("cmd.", w.cancelbutton.push)
722 w.open()
724 def ide_hit(self):
725 self.w.other_creator.set(W._signature)
727 def interp_hit(self):
728 self.w.other_creator.set("Pyth")
730 def otherselect(self, *args):
731 sel_from, sel_to = self.w.other_creator.getselection()
732 creator = self.w.other_creator.get()[:4]
733 creator = creator + " " * (4 - len(creator))
734 self.w.other_creator.set(creator)
735 self.w.other_creator.setselection(sel_from, sel_to)
736 self.w.other_radio.set(1)
738 def cancelbuttonhit(self):
739 self.w.close()
741 def okbuttonhit(self):
742 self.rv = self.w.other_creator.get()[:4]
743 self.w.close()
746 def SaveOptions(creator):
747 s = _saveoptions(creator)
748 return s.rv
751 def _escape(where, what) :
752 return string.join(string.split(where, what), '\\' + what)
754 def _makewholewordpattern(word):
755 # first, escape special regex chars
756 for esc in "\\[].*^+$?":
757 word = _escape(word, esc)
758 import regex
759 notwordcharspat = '[^' + _wordchars + ']'
760 pattern = '\(' + word + '\)'
761 if word[0] in _wordchars:
762 pattern = notwordcharspat + pattern
763 if word[-1] in _wordchars:
764 pattern = pattern + notwordcharspat
765 return regex.compile(pattern)
767 class SearchEngine:
769 def __init__(self):
770 self.visible = 0
771 self.w = None
772 self.parms = { "find": "",
773 "replace": "",
774 "wrap": 1,
775 "casesens": 1,
776 "wholeword": 1
778 import MacPrefs
779 prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath)
780 if prefs.searchengine:
781 self.parms["casesens"] = prefs.searchengine.casesens
782 self.parms["wrap"] = prefs.searchengine.wrap
783 self.parms["wholeword"] = prefs.searchengine.wholeword
785 def show(self):
786 self.visible = 1
787 if self.w:
788 self.w.wid.ShowWindow()
789 self.w.wid.SelectWindow()
790 self.w.find.edit.select(1)
791 self.w.find.edit.selectall()
792 return
793 self.w = W.Dialog((420, 150), "Find")
795 self.w.find = TitledEditText((10, 4, 300, 36), "Search for:")
796 self.w.replace = TitledEditText((10, 100, 300, 36), "Replace with:")
798 self.w.boxes = W.Group((10, 50, 300, 40))
799 self.w.boxes.casesens = W.CheckBox((0, 0, 100, 16), "Case sensitive")
800 self.w.boxes.wholeword = W.CheckBox((0, 20, 100, 16), "Whole word")
801 self.w.boxes.wrap = W.CheckBox((110, 0, 100, 16), "Wrap around")
803 self.buttons = [ ("Find", "cmdf", self.find),
804 ("Replace", "cmdr", self.replace),
805 ("Replace all", None, self.replaceall),
806 ("Don¹t find", "cmdd", self.dont),
807 ("Cancel", "cmd.", self.cancel)
809 for i in range(len(self.buttons)):
810 bounds = -90, 22 + i * 24, 80, 16
811 title, shortcut, callback = self.buttons[i]
812 self.w[title] = W.Button(bounds, title, callback)
813 if shortcut:
814 self.w.bind(shortcut, self.w[title].push)
815 self.w.setdefaultbutton(self.w["Don¹t find"])
816 self.w.find.edit.bind("<key>", self.key)
817 self.w.bind("<activate>", self.activate)
818 self.w.bind("<close>", self.close)
819 self.w.open()
820 self.setparms()
821 self.w.find.edit.select(1)
822 self.w.find.edit.selectall()
823 self.checkbuttons()
825 def close(self):
826 self.hide()
827 return -1
829 def key(self, char, modifiers):
830 self.w.find.edit.key(char, modifiers)
831 self.checkbuttons()
832 return 1
834 def activate(self, onoff):
835 if onoff:
836 self.checkbuttons()
838 def checkbuttons(self):
839 editor = findeditor(self)
840 if editor:
841 if self.w.find.get():
842 for title, cmd, call in self.buttons[:-2]:
843 self.w[title].enable(1)
844 self.w.setdefaultbutton(self.w["Find"])
845 else:
846 for title, cmd, call in self.buttons[:-2]:
847 self.w[title].enable(0)
848 self.w.setdefaultbutton(self.w["Don¹t find"])
849 else:
850 for title, cmd, call in self.buttons[:-2]:
851 self.w[title].enable(0)
852 self.w.setdefaultbutton(self.w["Don¹t find"])
854 def find(self):
855 self.getparmsfromwindow()
856 if self.findnext():
857 self.hide()
859 def replace(self):
860 editor = findeditor(self)
861 if not editor:
862 return
863 if self.visible:
864 self.getparmsfromwindow()
865 text = editor.getselectedtext()
866 find = self.parms["find"]
867 if not self.parms["casesens"]:
868 find = string.lower(find)
869 text = string.lower(text)
870 if text == find:
871 self.hide()
872 editor.insert(self.parms["replace"])
874 def replaceall(self):
875 editor = findeditor(self)
876 if not editor:
877 return
878 if self.visible:
879 self.getparmsfromwindow()
880 W.SetCursor("watch")
881 find = self.parms["find"]
882 if not find:
883 return
884 findlen = len(find)
885 replace = self.parms["replace"]
886 replacelen = len(replace)
887 Text = editor.get()
888 if not self.parms["casesens"]:
889 find = string.lower(find)
890 text = string.lower(Text)
891 else:
892 text = Text
893 newtext = ""
894 pos = 0
895 counter = 0
896 while 1:
897 if self.parms["wholeword"]:
898 wholewordRE = _makewholewordpattern(find)
899 wholewordRE.search(text, pos)
900 if wholewordRE.regs:
901 pos = wholewordRE.regs[1][0]
902 else:
903 pos = -1
904 else:
905 pos = string.find(text, find, pos)
906 if pos < 0:
907 break
908 counter = counter + 1
909 text = text[:pos] + replace + text[pos + findlen:]
910 Text = Text[:pos] + replace + Text[pos + findlen:]
911 pos = pos + replacelen
912 W.SetCursor("arrow")
913 if counter:
914 self.hide()
915 import EasyDialogs
916 import Res
917 editor.changed = 1
918 editor.selchanged = 1
919 editor.ted.WEUseText(Res.Resource(Text))
920 editor.ted.WECalText()
921 editor.SetPort()
922 Win.InvalRect(editor._bounds)
923 #editor.ted.WEUpdate(self.w.wid.GetWindowPort().visRgn)
924 EasyDialogs.Message("Replaced %d occurrences" % counter)
926 def dont(self):
927 self.getparmsfromwindow()
928 self.hide()
930 def replacefind(self):
931 self.replace()
932 self.findnext()
934 def setfindstring(self):
935 editor = findeditor(self)
936 if not editor:
937 return
938 find = editor.getselectedtext()
939 if not find:
940 return
941 self.parms["find"] = find
942 if self.w:
943 self.w.find.edit.set(self.parms["find"])
944 self.w.find.edit.selectall()
946 def findnext(self):
947 editor = findeditor(self)
948 if not editor:
949 return
950 find = self.parms["find"]
951 if not find:
952 return
953 text = editor.get()
954 if not self.parms["casesens"]:
955 find = string.lower(find)
956 text = string.lower(text)
957 selstart, selend = editor.getselection()
958 selstart, selend = min(selstart, selend), max(selstart, selend)
959 if self.parms["wholeword"]:
960 wholewordRE = _makewholewordpattern(find)
961 wholewordRE.search(text, selend)
962 if wholewordRE.regs:
963 pos = wholewordRE.regs[1][0]
964 else:
965 pos = -1
966 else:
967 pos = string.find(text, find, selend)
968 if pos >= 0:
969 editor.setselection(pos, pos + len(find))
970 return 1
971 elif self.parms["wrap"]:
972 if self.parms["wholeword"]:
973 wholewordRE.search(text, 0)
974 if wholewordRE.regs:
975 pos = wholewordRE.regs[1][0]
976 else:
977 pos = -1
978 else:
979 pos = string.find(text, find)
980 if selstart > pos >= 0:
981 editor.setselection(pos, pos + len(find))
982 return 1
984 def setparms(self):
985 for key, value in self.parms.items():
986 try:
987 self.w[key].set(value)
988 except KeyError:
989 self.w.boxes[key].set(value)
991 def getparmsfromwindow(self):
992 if not self.w:
993 return
994 for key, value in self.parms.items():
995 try:
996 value = self.w[key].get()
997 except KeyError:
998 value = self.w.boxes[key].get()
999 self.parms[key] = value
1001 def cancel(self):
1002 self.hide()
1003 self.setparms()
1005 def hide(self):
1006 if self.w:
1007 self.w.wid.HideWindow()
1008 self.visible = 0
1010 def writeprefs(self):
1011 import MacPrefs
1012 self.getparmsfromwindow()
1013 prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath)
1014 prefs.searchengine.casesens = self.parms["casesens"]
1015 prefs.searchengine.wrap = self.parms["wrap"]
1016 prefs.searchengine.wholeword = self.parms["wholeword"]
1017 prefs.save()
1020 class TitledEditText(W.Group):
1022 def __init__(self, possize, title, text = ""):
1023 W.Group.__init__(self, possize)
1024 self.title = W.TextBox((0, 0, 0, 16), title)
1025 self.edit = W.EditText((0, 16, 0, 0), text)
1027 def set(self, value):
1028 self.edit.set(value)
1030 def get(self):
1031 return self.edit.get()
1034 class ClassFinder(W.PopupWidget):
1036 def click(self, point, modifiers):
1037 W.SetCursor("watch")
1038 self.set(self._parentwindow.getclasslist())
1039 W.PopupWidget.click(self, point, modifiers)
1042 def getminindent(lines):
1043 indent = -1
1044 for line in lines:
1045 stripped = string.strip(line)
1046 if not stripped or stripped[0] == '#':
1047 continue
1048 if indent < 0 or line[:indent] <> indent * '\t':
1049 indent = 0
1050 for c in line:
1051 if c <> '\t':
1052 break
1053 indent = indent + 1
1054 return indent
1057 def getoptionkey():
1058 return not not ord(Evt.GetKeys()[7]) & 0x04
1061 def execstring(pytext, globals, locals, filename="<string>", debugging=0,
1062 modname="__main__", profiling=0):
1063 if debugging:
1064 import PyDebugger, bdb
1065 BdbQuit = bdb.BdbQuit
1066 else:
1067 BdbQuit = 'BdbQuitDummyException'
1068 pytext = string.split(pytext, '\r')
1069 pytext = string.join(pytext, '\n') + '\n'
1070 W.SetCursor("watch")
1071 globals['__name__'] = modname
1072 globals['__file__'] = filename
1073 sys.argv = [filename]
1074 try:
1075 code = compile(pytext, filename, "exec")
1076 except:
1077 # XXXX BAAAADDD.... We let tracebackwindow decide to treat SyntaxError
1078 # special. That's wrong because THIS case is special (could be literal
1079 # overflow!) and SyntaxError could mean we need a traceback (syntax error
1080 # in imported module!!!
1081 tracebackwindow.traceback(1, filename)
1082 return
1083 try:
1084 if debugging:
1085 if uthread2:
1086 uthread2.globalLock()
1087 PyDebugger.startfromhere()
1088 uthread2.globalUnlock()
1089 else:
1090 PyDebugger.startfromhere()
1091 elif not uthread2:
1092 MacOS.EnableAppswitch(0)
1093 try:
1094 if profiling:
1095 import profile, ProfileBrowser
1096 p = profile.Profile()
1097 p.set_cmd(filename)
1098 try:
1099 p.runctx(code, globals, locals)
1100 finally:
1101 import pstats
1103 stats = pstats.Stats(p)
1104 ProfileBrowser.ProfileBrowser(stats)
1105 else:
1106 exec code in globals, locals
1107 finally:
1108 if not uthread2:
1109 MacOS.EnableAppswitch(-1)
1110 except W.AlertError, detail:
1111 raise W.AlertError, detail
1112 except (KeyboardInterrupt, BdbQuit):
1113 pass
1114 except:
1115 if uthread2:
1116 uthread2.globalLock()
1117 if debugging:
1118 sys.settrace(None)
1119 PyDebugger.postmortem(sys.exc_type, sys.exc_value, sys.exc_traceback)
1120 return
1121 else:
1122 tracebackwindow.traceback(1, filename)
1123 if uthread2:
1124 uthread2.globalUnlock()
1125 if debugging:
1126 sys.settrace(None)
1127 PyDebugger.stop()
1130 _identifieRE = regex.compile("[A-Za-z_][A-Za-z_0-9]*")
1132 def _filename_as_modname(fname):
1133 if fname[-3:] == '.py':
1134 modname = fname[:-3]
1135 if _identifieRE.match(modname) == len(modname):
1136 return string.join(string.split(modname, '.'), '_')
1138 def findeditor(topwindow, fromtop = 0):
1139 wid = Win.FrontWindow()
1140 if not fromtop:
1141 if topwindow.w and wid == topwindow.w.wid:
1142 wid = topwindow.w.wid.GetNextWindow()
1143 if not wid:
1144 return
1145 app = W.getapplication()
1146 if app._windows.has_key(wid): # KeyError otherwise can happen in RoboFog :-(
1147 window = W.getapplication()._windows[wid]
1148 else:
1149 return
1150 if not isinstance(window, Editor):
1151 return
1152 return window.editgroup.editor
1155 class _EditorDefaultSettings:
1157 def __init__(self):
1158 self.template = "%s, %d point"
1159 self.fontsettings, self.tabsettings, self.windowsize = geteditorprefs()
1160 self.w = W.Dialog((328, 120), "Editor default settings")
1161 self.w.setfontbutton = W.Button((8, 8, 80, 16), "Set fontŠ", self.dofont)
1162 self.w.fonttext = W.TextBox((98, 10, -8, 14), self.template % (self.fontsettings[0], self.fontsettings[2]))
1164 self.w.picksizebutton = W.Button((8, 50, 80, 16), "Front window", self.picksize)
1165 self.w.xsizelabel = W.TextBox((98, 32, 40, 14), "Width:")
1166 self.w.ysizelabel = W.TextBox((148, 32, 40, 14), "Height:")
1167 self.w.xsize = W.EditText((98, 48, 40, 20), `self.windowsize[0]`)
1168 self.w.ysize = W.EditText((148, 48, 40, 20), `self.windowsize[1]`)
1170 self.w.cancelbutton = W.Button((-180, -26, 80, 16), "Cancel", self.cancel)
1171 self.w.okbutton = W.Button((-90, -26, 80, 16), "Done", self.ok)
1172 self.w.setdefaultbutton(self.w.okbutton)
1173 self.w.bind('cmd.', self.w.cancelbutton.push)
1174 self.w.open()
1176 def picksize(self):
1177 app = W.getapplication()
1178 editor = findeditor(self)
1179 if editor is not None:
1180 width, height = editor._parentwindow._bounds[2:]
1181 self.w.xsize.set(`width`)
1182 self.w.ysize.set(`height`)
1183 else:
1184 raise W.AlertError, "No edit window found"
1186 def dofont(self):
1187 import FontSettings
1188 settings = FontSettings.FontDialog(self.fontsettings, self.tabsettings)
1189 if settings:
1190 self.fontsettings, self.tabsettings = settings
1191 sys.exc_traceback = None
1192 self.w.fonttext.set(self.template % (self.fontsettings[0], self.fontsettings[2]))
1194 def close(self):
1195 self.w.close()
1196 del self.w
1198 def cancel(self):
1199 self.close()
1201 def ok(self):
1202 try:
1203 width = string.atoi(self.w.xsize.get())
1204 except:
1205 self.w.xsize.select(1)
1206 self.w.xsize.selectall()
1207 raise W.AlertError, "Bad number for window width"
1208 try:
1209 height = string.atoi(self.w.ysize.get())
1210 except:
1211 self.w.ysize.select(1)
1212 self.w.ysize.selectall()
1213 raise W.AlertError, "Bad number for window height"
1214 self.windowsize = width, height
1215 seteditorprefs(self.fontsettings, self.tabsettings, self.windowsize)
1216 self.close()
1218 def geteditorprefs():
1219 import MacPrefs
1220 prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath)
1221 try:
1222 fontsettings = prefs.pyedit.fontsettings
1223 tabsettings = prefs.pyedit.tabsettings
1224 windowsize = prefs.pyedit.windowsize
1225 except:
1226 fontsettings = prefs.pyedit.fontsettings = ("Python-Sans", 0, 9, (0, 0, 0))
1227 tabsettings = prefs.pyedit.tabsettings = (8, 1)
1228 windowsize = prefs.pyedit.windowsize = (500, 250)
1229 sys.exc_traceback = None
1230 return fontsettings, tabsettings, windowsize
1232 def seteditorprefs(fontsettings, tabsettings, windowsize):
1233 import MacPrefs
1234 prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath)
1235 prefs.pyedit.fontsettings = fontsettings
1236 prefs.pyedit.tabsettings = tabsettings
1237 prefs.pyedit.windowsize = windowsize
1238 prefs.save()
1240 _defaultSettingsEditor = None
1242 def EditorDefaultSettings():
1243 global _defaultSettingsEditor
1244 if _defaultSettingsEditor is None or not hasattr(_defaultSettingsEditor, "w"):
1245 _defaultSettingsEditor = _EditorDefaultSettings()
1246 else:
1247 _defaultSettingsEditor.w.select()
1249 def resolvealiases(path):
1250 try:
1251 return macfs.ResolveAliasFile(path)[0].as_pathname()
1252 except (macfs.error, ValueError), (error, str):
1253 if error <> -120:
1254 raise
1255 dir, file = os.path.split(path)
1256 return os.path.join(resolvealiases(dir), file)
1258 searchengine = SearchEngine()
1259 tracebackwindow = Wtraceback.TraceBack()