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