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