Quick update to the README file. For intros and books we now point to
[python/dscho.git] / Mac / Tools / IDE / PyEdit.py
blob798370590303f3338e7789c832d01a910405ae9c
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 import re
590 methodRE = re.compile(r"\r[ \t]+def ")
591 findMethod = methodRE.search
592 editor = self.editgroup.editor
593 text = editor.get()
594 list = []
595 append = list.append
596 functag = "func"
597 classtag = "class"
598 methodtag = "method"
599 pos = -1
600 if text[:4] == 'def ':
601 append((pos + 4, functag))
602 pos = 4
603 while 1:
604 pos = find(text, '\rdef ', pos + 1)
605 if pos < 0:
606 break
607 append((pos + 5, functag))
608 pos = -1
609 if text[:6] == 'class ':
610 append((pos + 6, classtag))
611 pos = 6
612 while 1:
613 pos = find(text, '\rclass ', pos + 1)
614 if pos < 0:
615 break
616 append((pos + 7, classtag))
617 pos = 0
618 while 1:
619 m = findMethod(text, pos + 1)
620 if m is None:
621 break
622 pos = m.regs[0][0]
623 #pos = find(text, '\r\tdef ', pos + 1)
624 append((m.regs[0][1], methodtag))
625 list.sort()
626 classlist = []
627 methodlistappend = None
628 offsetToLine = editor.ted.WEOffsetToLine
629 getLineRange = editor.ted.WEGetLineRange
630 append = classlist.append
631 identifieRE_match = _identifieRE.match
632 for pos, tag in list:
633 lineno = offsetToLine(pos)
634 lineStart, lineEnd = getLineRange(lineno)
635 line = strip(text[pos:lineEnd])
636 line = line[:identifieRE_match(line)]
637 if tag is functag:
638 append(("def " + line, lineno + 1))
639 methodlistappend = None
640 elif tag is classtag:
641 append(["class " + line])
642 methodlistappend = classlist[-1].append
643 elif methodlistappend and tag is methodtag:
644 methodlistappend(("def " + line, lineno + 1))
645 return classlist
647 def popselectline(self, lineno):
648 self.editgroup.editor.selectline(lineno - 1)
650 def selectline(self, lineno, charoffset = 0):
651 self.editgroup.editor.selectline(lineno - 1, charoffset)
653 class _saveoptions:
655 def __init__(self, creator):
656 self.rv = None
657 self.w = w = W.ModalDialog((240, 140), 'Save options')
658 radiobuttons = []
659 w.label = W.TextBox((8, 8, 80, 18), "File creator:")
660 w.ide_radio = W.RadioButton((8, 22, 160, 18), "This application", radiobuttons, self.ide_hit)
661 w.interp_radio = W.RadioButton((8, 42, 160, 18), "Python Interpreter", radiobuttons, self.interp_hit)
662 w.other_radio = W.RadioButton((8, 62, 50, 18), "Other:", radiobuttons)
663 w.other_creator = W.EditText((62, 62, 40, 20), creator, self.otherselect)
664 w.cancelbutton = W.Button((-180, -30, 80, 16), "Cancel", self.cancelbuttonhit)
665 w.okbutton = W.Button((-90, -30, 80, 16), "Done", self.okbuttonhit)
666 w.setdefaultbutton(w.okbutton)
667 if creator == 'Pyth':
668 w.interp_radio.set(1)
669 elif creator == W._signature:
670 w.ide_radio.set(1)
671 else:
672 w.other_radio.set(1)
673 w.bind("cmd.", w.cancelbutton.push)
674 w.open()
676 def ide_hit(self):
677 self.w.other_creator.set(W._signature)
679 def interp_hit(self):
680 self.w.other_creator.set("Pyth")
682 def otherselect(self, *args):
683 sel_from, sel_to = self.w.other_creator.getselection()
684 creator = self.w.other_creator.get()[:4]
685 creator = creator + " " * (4 - len(creator))
686 self.w.other_creator.set(creator)
687 self.w.other_creator.setselection(sel_from, sel_to)
688 self.w.other_radio.set(1)
690 def cancelbuttonhit(self):
691 self.w.close()
693 def okbuttonhit(self):
694 self.rv = self.w.other_creator.get()[:4]
695 self.w.close()
698 def SaveOptions(creator):
699 s = _saveoptions(creator)
700 return s.rv
703 def _escape(where, what) :
704 return string.join(string.split(where, what), '\\' + what)
706 def _makewholewordpattern(word):
707 # first, escape special regex chars
708 for esc in "\\[].*^+$?":
709 word = _escape(word, esc)
710 import regex
711 notwordcharspat = '[^' + _wordchars + ']'
712 pattern = '\(' + word + '\)'
713 if word[0] in _wordchars:
714 pattern = notwordcharspat + pattern
715 if word[-1] in _wordchars:
716 pattern = pattern + notwordcharspat
717 return regex.compile(pattern)
719 class SearchEngine:
721 def __init__(self):
722 self.visible = 0
723 self.w = None
724 self.parms = { "find": "",
725 "replace": "",
726 "wrap": 1,
727 "casesens": 1,
728 "wholeword": 1
730 import MacPrefs
731 prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath)
732 if prefs.searchengine:
733 self.parms["casesens"] = prefs.searchengine.casesens
734 self.parms["wrap"] = prefs.searchengine.wrap
735 self.parms["wholeword"] = prefs.searchengine.wholeword
737 def show(self):
738 self.visible = 1
739 if self.w:
740 self.w.wid.ShowWindow()
741 self.w.wid.SelectWindow()
742 self.w.find.edit.select(1)
743 self.w.find.edit.selectall()
744 return
745 self.w = W.Dialog((420, 150), "Find")
747 self.w.find = TitledEditText((10, 4, 300, 36), "Search for:")
748 self.w.replace = TitledEditText((10, 100, 300, 36), "Replace with:")
750 self.w.boxes = W.Group((10, 50, 300, 40))
751 self.w.boxes.casesens = W.CheckBox((0, 0, 100, 16), "Case sensitive")
752 self.w.boxes.wholeword = W.CheckBox((0, 20, 100, 16), "Whole word")
753 self.w.boxes.wrap = W.CheckBox((110, 0, 100, 16), "Wrap around")
755 self.buttons = [ ("Find", "cmdf", self.find),
756 ("Replace", "cmdr", self.replace),
757 ("Replace all", None, self.replaceall),
758 ("Don¹t find", "cmdd", self.dont),
759 ("Cancel", "cmd.", self.cancel)
761 for i in range(len(self.buttons)):
762 bounds = -90, 22 + i * 24, 80, 16
763 title, shortcut, callback = self.buttons[i]
764 self.w[title] = W.Button(bounds, title, callback)
765 if shortcut:
766 self.w.bind(shortcut, self.w[title].push)
767 self.w.setdefaultbutton(self.w["Don¹t find"])
768 self.w.find.edit.bind("<key>", self.key)
769 self.w.bind("<activate>", self.activate)
770 self.w.bind("<close>", self.close)
771 self.w.open()
772 self.setparms()
773 self.w.find.edit.select(1)
774 self.w.find.edit.selectall()
775 self.checkbuttons()
777 def close(self):
778 self.hide()
779 return -1
781 def key(self, char, modifiers):
782 self.w.find.edit.key(char, modifiers)
783 self.checkbuttons()
784 return 1
786 def activate(self, onoff):
787 if onoff:
788 self.checkbuttons()
790 def checkbuttons(self):
791 editor = findeditor(self)
792 if editor:
793 if self.w.find.get():
794 for title, cmd, call in self.buttons[:-2]:
795 self.w[title].enable(1)
796 self.w.setdefaultbutton(self.w["Find"])
797 else:
798 for title, cmd, call in self.buttons[:-2]:
799 self.w[title].enable(0)
800 self.w.setdefaultbutton(self.w["Don¹t find"])
801 else:
802 for title, cmd, call in self.buttons[:-2]:
803 self.w[title].enable(0)
804 self.w.setdefaultbutton(self.w["Don¹t find"])
806 def find(self):
807 self.getparmsfromwindow()
808 if self.findnext():
809 self.hide()
811 def replace(self):
812 editor = findeditor(self)
813 if not editor:
814 return
815 if self.visible:
816 self.getparmsfromwindow()
817 text = editor.getselectedtext()
818 find = self.parms["find"]
819 if not self.parms["casesens"]:
820 find = string.lower(find)
821 text = string.lower(text)
822 if text == find:
823 self.hide()
824 editor.insert(self.parms["replace"])
826 def replaceall(self):
827 editor = findeditor(self)
828 if not editor:
829 return
830 if self.visible:
831 self.getparmsfromwindow()
832 W.SetCursor("watch")
833 find = self.parms["find"]
834 if not find:
835 return
836 findlen = len(find)
837 replace = self.parms["replace"]
838 replacelen = len(replace)
839 Text = editor.get()
840 if not self.parms["casesens"]:
841 find = string.lower(find)
842 text = string.lower(Text)
843 else:
844 text = Text
845 newtext = ""
846 pos = 0
847 counter = 0
848 while 1:
849 if self.parms["wholeword"]:
850 wholewordRE = _makewholewordpattern(find)
851 wholewordRE.search(text, pos)
852 if wholewordRE.regs:
853 pos = wholewordRE.regs[1][0]
854 else:
855 pos = -1
856 else:
857 pos = string.find(text, find, pos)
858 if pos < 0:
859 break
860 counter = counter + 1
861 text = text[:pos] + replace + text[pos + findlen:]
862 Text = Text[:pos] + replace + Text[pos + findlen:]
863 pos = pos + replacelen
864 W.SetCursor("arrow")
865 if counter:
866 self.hide()
867 import EasyDialogs
868 import Res
869 editor.changed = 1
870 editor.selchanged = 1
871 editor.ted.WEUseText(Res.Resource(Text))
872 editor.ted.WECalText()
873 editor.SetPort()
874 Win.InvalRect(editor._bounds)
875 #editor.ted.WEUpdate(self.w.wid.GetWindowPort().visRgn)
876 EasyDialogs.Message("Replaced %d occurrences" % counter)
878 def dont(self):
879 self.getparmsfromwindow()
880 self.hide()
882 def replacefind(self):
883 self.replace()
884 self.findnext()
886 def setfindstring(self):
887 editor = findeditor(self)
888 if not editor:
889 return
890 find = editor.getselectedtext()
891 if not find:
892 return
893 self.parms["find"] = find
894 if self.w:
895 self.w.find.edit.set(self.parms["find"])
896 self.w.find.edit.selectall()
898 def findnext(self):
899 editor = findeditor(self)
900 if not editor:
901 return
902 find = self.parms["find"]
903 if not find:
904 return
905 text = editor.get()
906 if not self.parms["casesens"]:
907 find = string.lower(find)
908 text = string.lower(text)
909 selstart, selend = editor.getselection()
910 selstart, selend = min(selstart, selend), max(selstart, selend)
911 if self.parms["wholeword"]:
912 wholewordRE = _makewholewordpattern(find)
913 wholewordRE.search(text, selend)
914 if wholewordRE.regs:
915 pos = wholewordRE.regs[1][0]
916 else:
917 pos = -1
918 else:
919 pos = string.find(text, find, selend)
920 if pos >= 0:
921 editor.setselection(pos, pos + len(find))
922 return 1
923 elif self.parms["wrap"]:
924 if self.parms["wholeword"]:
925 wholewordRE.search(text, 0)
926 if wholewordRE.regs:
927 pos = wholewordRE.regs[1][0]
928 else:
929 pos = -1
930 else:
931 pos = string.find(text, find)
932 if selstart > pos >= 0:
933 editor.setselection(pos, pos + len(find))
934 return 1
936 def setparms(self):
937 for key, value in self.parms.items():
938 try:
939 self.w[key].set(value)
940 except KeyError:
941 self.w.boxes[key].set(value)
943 def getparmsfromwindow(self):
944 if not self.w:
945 return
946 for key, value in self.parms.items():
947 try:
948 value = self.w[key].get()
949 except KeyError:
950 value = self.w.boxes[key].get()
951 self.parms[key] = value
953 def cancel(self):
954 self.hide()
955 self.setparms()
957 def hide(self):
958 if self.w:
959 self.w.wid.HideWindow()
960 self.visible = 0
962 def writeprefs(self):
963 import MacPrefs
964 self.getparmsfromwindow()
965 prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath)
966 prefs.searchengine.casesens = self.parms["casesens"]
967 prefs.searchengine.wrap = self.parms["wrap"]
968 prefs.searchengine.wholeword = self.parms["wholeword"]
969 prefs.save()
972 class TitledEditText(W.Group):
974 def __init__(self, possize, title, text = ""):
975 W.Group.__init__(self, possize)
976 self.title = W.TextBox((0, 0, 0, 16), title)
977 self.edit = W.EditText((0, 16, 0, 0), text)
979 def set(self, value):
980 self.edit.set(value)
982 def get(self):
983 return self.edit.get()
986 class ClassFinder(W.PopupWidget):
988 def click(self, point, modifiers):
989 W.SetCursor("watch")
990 self.set(self._parentwindow.getclasslist())
991 W.PopupWidget.click(self, point, modifiers)
994 def getminindent(lines):
995 indent = -1
996 for line in lines:
997 stripped = string.strip(line)
998 if not stripped or stripped[0] == '#':
999 continue
1000 if indent < 0 or line[:indent] <> indent * '\t':
1001 indent = 0
1002 for c in line:
1003 if c <> '\t':
1004 break
1005 indent = indent + 1
1006 return indent
1009 def getoptionkey():
1010 return not not ord(Evt.GetKeys()[7]) & 0x04
1013 def execstring(pytext, globals, locals, filename="<string>", debugging=0,
1014 modname="__main__", profiling=0):
1015 if debugging:
1016 import PyDebugger, bdb
1017 BdbQuit = bdb.BdbQuit
1018 else:
1019 BdbQuit = 'BdbQuitDummyException'
1020 pytext = string.split(pytext, '\r')
1021 pytext = string.join(pytext, '\n') + '\n'
1022 W.SetCursor("watch")
1023 globals['__name__'] = modname
1024 globals['__file__'] = filename
1025 sys.argv = [filename]
1026 try:
1027 code = compile(pytext, filename, "exec")
1028 except:
1029 # XXXX BAAAADDD.... We let tracebackwindow decide to treat SyntaxError
1030 # special. That's wrong because THIS case is special (could be literal
1031 # overflow!) and SyntaxError could mean we need a traceback (syntax error
1032 # in imported module!!!
1033 tracebackwindow.traceback(1, filename)
1034 return
1035 try:
1036 if debugging:
1037 PyDebugger.startfromhere()
1038 else:
1039 MacOS.EnableAppswitch(0)
1040 try:
1041 if profiling:
1042 import profile, ProfileBrowser
1043 p = profile.Profile()
1044 p.set_cmd(filename)
1045 try:
1046 p.runctx(code, globals, locals)
1047 finally:
1048 import pstats
1050 stats = pstats.Stats(p)
1051 ProfileBrowser.ProfileBrowser(stats)
1052 else:
1053 exec code in globals, locals
1054 finally:
1055 MacOS.EnableAppswitch(-1)
1056 except W.AlertError, detail:
1057 raise W.AlertError, detail
1058 except (KeyboardInterrupt, BdbQuit):
1059 pass
1060 except:
1061 if debugging:
1062 sys.settrace(None)
1063 PyDebugger.postmortem(sys.exc_type, sys.exc_value, sys.exc_traceback)
1064 return
1065 else:
1066 tracebackwindow.traceback(1, filename)
1067 if debugging:
1068 sys.settrace(None)
1069 PyDebugger.stop()
1072 _identifieRE = regex.compile("[A-Za-z_][A-Za-z_0-9]*")
1074 def _filename_as_modname(fname):
1075 if fname[-3:] == '.py':
1076 modname = fname[:-3]
1077 if _identifieRE.match(modname) == len(modname):
1078 return string.join(string.split(modname, '.'), '_')
1080 def findeditor(topwindow, fromtop = 0):
1081 wid = Win.FrontWindow()
1082 if not fromtop:
1083 if topwindow.w and wid == topwindow.w.wid:
1084 wid = topwindow.w.wid.GetNextWindow()
1085 if not wid:
1086 return
1087 app = W.getapplication()
1088 if app._windows.has_key(wid): # KeyError otherwise can happen in RoboFog :-(
1089 window = W.getapplication()._windows[wid]
1090 else:
1091 return
1092 if not isinstance(window, Editor):
1093 return
1094 return window.editgroup.editor
1097 class _EditorDefaultSettings:
1099 def __init__(self):
1100 self.template = "%s, %d point"
1101 self.fontsettings, self.tabsettings, self.windowsize = geteditorprefs()
1102 self.w = W.Dialog((328, 120), "Editor default settings")
1103 self.w.setfontbutton = W.Button((8, 8, 80, 16), "Set fontŠ", self.dofont)
1104 self.w.fonttext = W.TextBox((98, 10, -8, 14), self.template % (self.fontsettings[0], self.fontsettings[2]))
1106 self.w.picksizebutton = W.Button((8, 50, 80, 16), "Front window", self.picksize)
1107 self.w.xsizelabel = W.TextBox((98, 32, 40, 14), "Width:")
1108 self.w.ysizelabel = W.TextBox((148, 32, 40, 14), "Height:")
1109 self.w.xsize = W.EditText((98, 48, 40, 20), `self.windowsize[0]`)
1110 self.w.ysize = W.EditText((148, 48, 40, 20), `self.windowsize[1]`)
1112 self.w.cancelbutton = W.Button((-180, -26, 80, 16), "Cancel", self.cancel)
1113 self.w.okbutton = W.Button((-90, -26, 80, 16), "Done", self.ok)
1114 self.w.setdefaultbutton(self.w.okbutton)
1115 self.w.bind('cmd.', self.w.cancelbutton.push)
1116 self.w.open()
1118 def picksize(self):
1119 app = W.getapplication()
1120 editor = findeditor(self)
1121 if editor is not None:
1122 width, height = editor._parentwindow._bounds[2:]
1123 self.w.xsize.set(`width`)
1124 self.w.ysize.set(`height`)
1125 else:
1126 raise W.AlertError, "No edit window found"
1128 def dofont(self):
1129 import FontSettings
1130 settings = FontSettings.FontDialog(self.fontsettings, self.tabsettings)
1131 if settings:
1132 self.fontsettings, self.tabsettings = settings
1133 sys.exc_traceback = None
1134 self.w.fonttext.set(self.template % (self.fontsettings[0], self.fontsettings[2]))
1136 def close(self):
1137 self.w.close()
1138 del self.w
1140 def cancel(self):
1141 self.close()
1143 def ok(self):
1144 try:
1145 width = string.atoi(self.w.xsize.get())
1146 except:
1147 self.w.xsize.select(1)
1148 self.w.xsize.selectall()
1149 raise W.AlertError, "Bad number for window width"
1150 try:
1151 height = string.atoi(self.w.ysize.get())
1152 except:
1153 self.w.ysize.select(1)
1154 self.w.ysize.selectall()
1155 raise W.AlertError, "Bad number for window height"
1156 self.windowsize = width, height
1157 seteditorprefs(self.fontsettings, self.tabsettings, self.windowsize)
1158 self.close()
1160 def geteditorprefs():
1161 import MacPrefs
1162 prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath)
1163 try:
1164 fontsettings = prefs.pyedit.fontsettings
1165 tabsettings = prefs.pyedit.tabsettings
1166 windowsize = prefs.pyedit.windowsize
1167 except:
1168 fontsettings = prefs.pyedit.fontsettings = ("Python-Sans", 0, 9, (0, 0, 0))
1169 tabsettings = prefs.pyedit.tabsettings = (8, 1)
1170 windowsize = prefs.pyedit.windowsize = (500, 250)
1171 sys.exc_traceback = None
1172 return fontsettings, tabsettings, windowsize
1174 def seteditorprefs(fontsettings, tabsettings, windowsize):
1175 import MacPrefs
1176 prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath)
1177 prefs.pyedit.fontsettings = fontsettings
1178 prefs.pyedit.tabsettings = tabsettings
1179 prefs.pyedit.windowsize = windowsize
1180 prefs.save()
1182 _defaultSettingsEditor = None
1184 def EditorDefaultSettings():
1185 global _defaultSettingsEditor
1186 if _defaultSettingsEditor is None or not hasattr(_defaultSettingsEditor, "w"):
1187 _defaultSettingsEditor = _EditorDefaultSettings()
1188 else:
1189 _defaultSettingsEditor.w.select()
1191 def resolvealiases(path):
1192 try:
1193 return macfs.ResolveAliasFile(path)[0].as_pathname()
1194 except (macfs.error, ValueError), (error, str):
1195 if error <> -120:
1196 raise
1197 dir, file = os.path.split(path)
1198 return os.path.join(resolvealiases(dir), file)
1200 searchengine = SearchEngine()
1201 tracebackwindow = Wtraceback.TraceBack()