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