mirror of https://github.com/python/cpython.git
1260 lines
35 KiB
Python
1260 lines
35 KiB
Python
"""A (less & less) simple Python editor"""
|
|
|
|
import W
|
|
import Wtraceback
|
|
from Wkeys import *
|
|
|
|
import macfs
|
|
import MacOS
|
|
import Win
|
|
import Res
|
|
import Evt
|
|
import os
|
|
import imp
|
|
import sys
|
|
import string
|
|
import marshal
|
|
import regex
|
|
|
|
try:
|
|
# experimental microthread support
|
|
import uthread2
|
|
except ImportError:
|
|
uthread2 = None
|
|
|
|
_scriptuntitledcounter = 1
|
|
_wordchars = string.letters + string.digits + "_"
|
|
|
|
|
|
runButtonLabels = ["Run all", "Stop!"]
|
|
runSelButtonLabels = ["Run selection", "Pause!", "Resume"]
|
|
|
|
|
|
class Editor(W.Window):
|
|
|
|
def __init__(self, path = "", title = ""):
|
|
defaultfontsettings, defaulttabsettings, defaultwindowsize = geteditorprefs()
|
|
global _scriptuntitledcounter
|
|
if not path:
|
|
if title:
|
|
self.title = title
|
|
else:
|
|
self.title = "Untitled Script " + `_scriptuntitledcounter`
|
|
_scriptuntitledcounter = _scriptuntitledcounter + 1
|
|
text = ""
|
|
self._creator = W._signature
|
|
elif os.path.exists(path):
|
|
path = resolvealiases(path)
|
|
dir, name = os.path.split(path)
|
|
self.title = name
|
|
f = open(path, "rb")
|
|
text = f.read()
|
|
f.close()
|
|
fss = macfs.FSSpec(path)
|
|
self._creator, filetype = fss.GetCreatorType()
|
|
else:
|
|
raise IOError, "file '%s' does not exist" % path
|
|
self.path = path
|
|
|
|
if '\n' in text:
|
|
import EasyDialogs
|
|
if string.find(text, '\r\n') >= 0:
|
|
sourceOS = 'DOS'
|
|
searchString = '\r\n'
|
|
else:
|
|
sourceOS = 'UNIX'
|
|
searchString = '\n'
|
|
change = EasyDialogs.AskYesNoCancel('³%s² contains %s-style line feeds. '
|
|
'Change them to MacOS carriage returns?' % (self.title, sourceOS), 1)
|
|
# bug: Cancel is treated as No
|
|
if change > 0:
|
|
text = string.replace(text, searchString, '\r')
|
|
else:
|
|
change = 0
|
|
|
|
self.settings = {}
|
|
if self.path:
|
|
self.readwindowsettings()
|
|
if self.settings.has_key("windowbounds"):
|
|
bounds = self.settings["windowbounds"]
|
|
else:
|
|
bounds = defaultwindowsize
|
|
if self.settings.has_key("fontsettings"):
|
|
self.fontsettings = self.settings["fontsettings"]
|
|
else:
|
|
self.fontsettings = defaultfontsettings
|
|
if self.settings.has_key("tabsize"):
|
|
try:
|
|
self.tabsettings = (tabsize, tabmode) = self.settings["tabsize"]
|
|
except:
|
|
self.tabsettings = defaulttabsettings
|
|
else:
|
|
self.tabsettings = defaulttabsettings
|
|
|
|
W.Window.__init__(self, bounds, self.title, minsize = (330, 120), tabbable = 0)
|
|
self.setupwidgets(text)
|
|
if change > 0:
|
|
self.editgroup.editor.changed = 1
|
|
|
|
if self.settings.has_key("selection"):
|
|
selstart, selend = self.settings["selection"]
|
|
self.setselection(selstart, selend)
|
|
self.open()
|
|
self.setinfotext()
|
|
self.globals = {}
|
|
self._buf = "" # for write method
|
|
self.debugging = 0
|
|
self.profiling = 0
|
|
if self.settings.has_key("run_as_main"):
|
|
self.run_as_main = self.settings["run_as_main"]
|
|
else:
|
|
self.run_as_main = 0
|
|
self._threadstate = (0, 0)
|
|
self._thread = None
|
|
|
|
def readwindowsettings(self):
|
|
try:
|
|
resref = Res.FSpOpenResFile(self.path, 1)
|
|
except Res.Error:
|
|
return
|
|
try:
|
|
Res.UseResFile(resref)
|
|
data = Res.Get1Resource('PyWS', 128)
|
|
self.settings = marshal.loads(data.data)
|
|
except:
|
|
pass
|
|
Res.CloseResFile(resref)
|
|
|
|
def writewindowsettings(self):
|
|
try:
|
|
resref = Res.FSpOpenResFile(self.path, 3)
|
|
except Res.Error:
|
|
Res.CreateResFile(self.path)
|
|
resref = Res.FSpOpenResFile(self.path, 3)
|
|
try:
|
|
data = Res.Resource(marshal.dumps(self.settings))
|
|
Res.UseResFile(resref)
|
|
try:
|
|
temp = Res.Get1Resource('PyWS', 128)
|
|
temp.RemoveResource()
|
|
except Res.Error:
|
|
pass
|
|
data.AddResource('PyWS', 128, "window settings")
|
|
finally:
|
|
Res.UpdateResFile(resref)
|
|
Res.CloseResFile(resref)
|
|
|
|
def getsettings(self):
|
|
self.settings = {}
|
|
self.settings["windowbounds"] = self.getbounds()
|
|
self.settings["selection"] = self.getselection()
|
|
self.settings["fontsettings"] = self.editgroup.editor.getfontsettings()
|
|
self.settings["tabsize"] = self.editgroup.editor.gettabsettings()
|
|
self.settings["run_as_main"] = self.run_as_main
|
|
|
|
def get(self):
|
|
return self.editgroup.editor.get()
|
|
|
|
def getselection(self):
|
|
return self.editgroup.editor.ted.WEGetSelection()
|
|
|
|
def setselection(self, selstart, selend):
|
|
self.editgroup.editor.setselection(selstart, selend)
|
|
|
|
def getfilename(self):
|
|
if self.path:
|
|
return self.path
|
|
return '<%s>' % self.title
|
|
|
|
def setupwidgets(self, text):
|
|
topbarheight = 24
|
|
popfieldwidth = 80
|
|
self.lastlineno = None
|
|
|
|
# make an editor
|
|
self.editgroup = W.Group((0, topbarheight + 1, 0, 0))
|
|
editor = W.PyEditor((0, 0, -15,-15), text,
|
|
fontsettings = self.fontsettings,
|
|
tabsettings = self.tabsettings,
|
|
file = self.getfilename())
|
|
|
|
# make the widgets
|
|
self.popfield = ClassFinder((popfieldwidth - 17, -15, 16, 16), [], self.popselectline)
|
|
self.linefield = W.EditText((-1, -15, popfieldwidth - 15, 16), inset = (6, 1))
|
|
self.editgroup._barx = W.Scrollbar((popfieldwidth - 2, -15, -14, 16), editor.hscroll, max = 32767)
|
|
self.editgroup._bary = W.Scrollbar((-15, 14, 16, -14), editor.vscroll, max = 32767)
|
|
self.editgroup.editor = editor # add editor *after* scrollbars
|
|
|
|
self.editgroup.optionsmenu = W.PopupMenu((-15, -1, 16, 16), [])
|
|
self.editgroup.optionsmenu.bind('<click>', self.makeoptionsmenu)
|
|
|
|
self.bevelbox = W.BevelBox((0, 0, 0, topbarheight))
|
|
self.hline = W.HorizontalLine((0, topbarheight, 0, 0))
|
|
self.infotext = W.TextBox((175, 6, -4, 14), backgroundcolor = (0xe000, 0xe000, 0xe000))
|
|
self.runbutton = W.Button((5, 4, 80, 16), runButtonLabels[0], self.run)
|
|
self.runselbutton = W.Button((90, 4, 80, 16), runSelButtonLabels[0], self.runselection)
|
|
|
|
# bind some keys
|
|
editor.bind("cmdr", self.runbutton.push)
|
|
editor.bind("enter", self.runselbutton.push)
|
|
editor.bind("cmdj", self.domenu_gotoline)
|
|
editor.bind("cmdd", self.domenu_toggledebugger)
|
|
editor.bind("<idle>", self.updateselection)
|
|
|
|
editor.bind("cmde", searchengine.setfindstring)
|
|
editor.bind("cmdf", searchengine.show)
|
|
editor.bind("cmdg", searchengine.findnext)
|
|
editor.bind("cmdshiftr", searchengine.replace)
|
|
editor.bind("cmdt", searchengine.replacefind)
|
|
|
|
self.linefield.bind("return", self.dolinefield)
|
|
self.linefield.bind("enter", self.dolinefield)
|
|
self.linefield.bind("tab", self.dolinefield)
|
|
|
|
# intercept clicks
|
|
editor.bind("<click>", self.clickeditor)
|
|
self.linefield.bind("<click>", self.clicklinefield)
|
|
|
|
def makeoptionsmenu(self):
|
|
menuitems = [('Font settingsŠ', self.domenu_fontsettings),
|
|
("Save optionsŠ", self.domenu_options),
|
|
'-',
|
|
('\0' + chr(self.run_as_main) + 'Run as __main__', self.domenu_toggle_run_as_main),
|
|
('Modularize', self.domenu_modularize),
|
|
('Browse namespaceŠ', self.domenu_browsenamespace),
|
|
'-']
|
|
if self.profiling:
|
|
menuitems = menuitems + [('Disable profiler', self.domenu_toggleprofiler)]
|
|
else:
|
|
menuitems = menuitems + [('Enable profiler', self.domenu_toggleprofiler)]
|
|
if self.editgroup.editor._debugger:
|
|
menuitems = menuitems + [('Disable debugger', self.domenu_toggledebugger),
|
|
('Clear breakpoints', self.domenu_clearbreakpoints),
|
|
('Edit breakpointsŠ', self.domenu_editbreakpoints)]
|
|
else:
|
|
menuitems = menuitems + [('Enable debugger', self.domenu_toggledebugger)]
|
|
self.editgroup.optionsmenu.set(menuitems)
|
|
|
|
def domenu_toggle_run_as_main(self):
|
|
self.run_as_main = not self.run_as_main
|
|
self.editgroup.editor.selchanged = 1
|
|
|
|
def showbreakpoints(self, onoff):
|
|
self.editgroup.editor.showbreakpoints(onoff)
|
|
self.debugging = onoff
|
|
|
|
def domenu_clearbreakpoints(self, *args):
|
|
self.editgroup.editor.clearbreakpoints()
|
|
|
|
def domenu_editbreakpoints(self, *args):
|
|
self.editgroup.editor.editbreakpoints()
|
|
|
|
def domenu_toggledebugger(self, *args):
|
|
if not self.debugging:
|
|
W.SetCursor('watch')
|
|
self.debugging = not self.debugging
|
|
self.editgroup.editor.togglebreakpoints()
|
|
|
|
def domenu_toggleprofiler(self, *args):
|
|
self.profiling = not self.profiling
|
|
|
|
def domenu_browsenamespace(self, *args):
|
|
import PyBrowser, W
|
|
W.SetCursor('watch')
|
|
globals, file, modname = self.getenvironment()
|
|
if not modname:
|
|
modname = self.title
|
|
PyBrowser.Browser(globals, "Object browser: " + modname)
|
|
|
|
def domenu_modularize(self, *args):
|
|
modname = _filename_as_modname(self.title)
|
|
if not modname:
|
|
raise W.AlertError, 'Can¹t modularize ³%s²' % self.title
|
|
run_as_main = self.run_as_main
|
|
self.run_as_main = 0
|
|
self.run()
|
|
self.run_as_main = run_as_main
|
|
if self.path:
|
|
file = self.path
|
|
else:
|
|
file = self.title
|
|
|
|
if self.globals and not sys.modules.has_key(modname):
|
|
module = imp.new_module(modname)
|
|
for attr in self.globals.keys():
|
|
setattr(module,attr,self.globals[attr])
|
|
sys.modules[modname] = module
|
|
self.globals = {}
|
|
|
|
def domenu_fontsettings(self, *args):
|
|
import FontSettings
|
|
fontsettings = self.editgroup.editor.getfontsettings()
|
|
tabsettings = self.editgroup.editor.gettabsettings()
|
|
settings = FontSettings.FontDialog(fontsettings, tabsettings)
|
|
if settings:
|
|
fontsettings, tabsettings = settings
|
|
self.editgroup.editor.setfontsettings(fontsettings)
|
|
self.editgroup.editor.settabsettings(tabsettings)
|
|
|
|
def domenu_options(self, *args):
|
|
rv = SaveOptions(self._creator)
|
|
if rv:
|
|
self.editgroup.editor.selchanged = 1 # ouch...
|
|
self._creator = rv
|
|
|
|
def clicklinefield(self):
|
|
if self._currentwidget <> self.linefield:
|
|
self.linefield.select(1)
|
|
self.linefield.selectall()
|
|
return 1
|
|
|
|
def clickeditor(self):
|
|
if self._currentwidget <> self.editgroup.editor:
|
|
self.dolinefield()
|
|
return 1
|
|
|
|
def updateselection(self, force = 0):
|
|
sel = min(self.editgroup.editor.getselection())
|
|
lineno = self.editgroup.editor.offsettoline(sel)
|
|
if lineno <> self.lastlineno or force:
|
|
self.lastlineno = lineno
|
|
self.linefield.set(str(lineno + 1))
|
|
self.linefield.selview()
|
|
|
|
def dolinefield(self):
|
|
try:
|
|
lineno = string.atoi(self.linefield.get()) - 1
|
|
if lineno <> self.lastlineno:
|
|
self.editgroup.editor.selectline(lineno)
|
|
self.updateselection(1)
|
|
except:
|
|
self.updateselection(1)
|
|
self.editgroup.editor.select(1)
|
|
|
|
def setinfotext(self):
|
|
if not hasattr(self, 'infotext'):
|
|
return
|
|
if self.path:
|
|
self.infotext.set(self.path)
|
|
else:
|
|
self.infotext.set("")
|
|
|
|
def close(self):
|
|
if self.editgroup.editor.changed:
|
|
import EasyDialogs
|
|
import Qd
|
|
Qd.InitCursor() # XXX should be done by dialog
|
|
save = EasyDialogs.AskYesNoCancel('Save window ³%s² before closing?' % self.title, 1)
|
|
if save > 0:
|
|
if self.domenu_save():
|
|
return 1
|
|
elif save < 0:
|
|
return 1
|
|
self.globals = None # XXX doesn't help... all globals leak :-(
|
|
W.Window.close(self)
|
|
|
|
def domenu_close(self, *args):
|
|
return self.close()
|
|
|
|
def domenu_save(self, *args):
|
|
if not self.path:
|
|
# Will call us recursively
|
|
return self.domenu_save_as()
|
|
data = self.editgroup.editor.get()
|
|
fp = open(self.path, 'wb') # open file in binary mode, data has '\r' line-endings
|
|
fp.write(data)
|
|
fp.close()
|
|
fss = macfs.FSSpec(self.path)
|
|
fss.SetCreatorType(self._creator, 'TEXT')
|
|
self.getsettings()
|
|
self.writewindowsettings()
|
|
self.editgroup.editor.changed = 0
|
|
self.editgroup.editor.selchanged = 0
|
|
import linecache
|
|
if linecache.cache.has_key(self.path):
|
|
del linecache.cache[self.path]
|
|
import macostools
|
|
macostools.touched(self.path)
|
|
|
|
def can_save(self, menuitem):
|
|
return self.editgroup.editor.changed or self.editgroup.editor.selchanged
|
|
|
|
def domenu_save_as(self, *args):
|
|
fss, ok = macfs.StandardPutFile('Save as:', self.title)
|
|
if not ok:
|
|
return 1
|
|
self.showbreakpoints(0)
|
|
self.path = fss.as_pathname()
|
|
self.setinfotext()
|
|
self.title = os.path.split(self.path)[-1]
|
|
self.wid.SetWTitle(self.title)
|
|
self.domenu_save()
|
|
self.editgroup.editor.setfile(self.getfilename())
|
|
app = W.getapplication()
|
|
app.makeopenwindowsmenu()
|
|
if hasattr(app, 'makescriptsmenu'):
|
|
app = W.getapplication()
|
|
fss, fss_changed = app.scriptsfolder.Resolve()
|
|
path = fss.as_pathname()
|
|
if path == self.path[:len(path)]:
|
|
W.getapplication().makescriptsmenu()
|
|
|
|
def domenu_save_as_applet(self, *args):
|
|
try:
|
|
import buildtools
|
|
except ImportError:
|
|
# only have buildtools in Python >= 1.5.2
|
|
raise W.AlertError, "³Save as Applet² is only supported in\rPython 1.5.2 and up."
|
|
|
|
buildtools.DEBUG = 0 # ouch.
|
|
|
|
if self.title[-3:] == ".py":
|
|
destname = self.title[:-3]
|
|
else:
|
|
destname = self.title + ".applet"
|
|
fss, ok = macfs.StandardPutFile('Save as Applet:', destname)
|
|
if not ok:
|
|
return 1
|
|
W.SetCursor("watch")
|
|
destname = fss.as_pathname()
|
|
if self.path:
|
|
filename = self.path
|
|
if filename[-3:] == ".py":
|
|
rsrcname = filename[:-3] + '.rsrc'
|
|
else:
|
|
rsrcname = filename + '.rsrc'
|
|
else:
|
|
filename = self.title
|
|
rsrcname = ""
|
|
|
|
pytext = self.editgroup.editor.get()
|
|
pytext = string.split(pytext, '\r')
|
|
pytext = string.join(pytext, '\n') + '\n'
|
|
try:
|
|
code = compile(pytext, filename, "exec")
|
|
except (SyntaxError, EOFError):
|
|
raise buildtools.BuildError, "Syntax error in script %s" % `filename`
|
|
|
|
# Try removing the output file
|
|
try:
|
|
os.remove(destname)
|
|
except os.error:
|
|
pass
|
|
template = buildtools.findtemplate()
|
|
buildtools.process_common(template, None, code, rsrcname, destname, 0, 1)
|
|
|
|
def domenu_gotoline(self, *args):
|
|
self.linefield.selectall()
|
|
self.linefield.select(1)
|
|
self.linefield.selectall()
|
|
|
|
def domenu_selectline(self, *args):
|
|
self.editgroup.editor.expandselection()
|
|
|
|
def domenu_find(self, *args):
|
|
searchengine.show()
|
|
|
|
def domenu_entersearchstring(self, *args):
|
|
searchengine.setfindstring()
|
|
|
|
def domenu_replace(self, *args):
|
|
searchengine.replace()
|
|
|
|
def domenu_findnext(self, *args):
|
|
searchengine.findnext()
|
|
|
|
def domenu_replacefind(self, *args):
|
|
searchengine.replacefind()
|
|
|
|
def domenu_run(self, *args):
|
|
self.runbutton.push()
|
|
|
|
def domenu_runselection(self, *args):
|
|
self.runselbutton.push()
|
|
|
|
def run(self):
|
|
if self._threadstate == (0, 0):
|
|
self._run()
|
|
else:
|
|
uthread2.globalLock()
|
|
self._thread.raiseException(KeyboardInterrupt)
|
|
if self._thread.isPaused():
|
|
self._thread.start()
|
|
uthread2.globalUnlock()
|
|
|
|
def _run(self):
|
|
pytext = self.editgroup.editor.get()
|
|
globals, file, modname = self.getenvironment()
|
|
self.execstring(pytext, globals, globals, file, modname)
|
|
|
|
def runselection(self):
|
|
if self._threadstate == (0, 0):
|
|
self._runselection()
|
|
elif self._threadstate == (1, 1):
|
|
self._thread.pause()
|
|
self.setthreadstate((1, 2))
|
|
elif self._threadstate == (1, 2):
|
|
self._thread.start()
|
|
self.setthreadstate((1, 1))
|
|
|
|
def _runselection(self):
|
|
globals, file, modname = self.getenvironment()
|
|
locals = globals
|
|
# select whole lines
|
|
self.editgroup.editor.expandselection()
|
|
|
|
# get lineno of first selected line
|
|
selstart, selend = self.editgroup.editor.getselection()
|
|
selstart, selend = min(selstart, selend), max(selstart, selend)
|
|
selfirstline = self.editgroup.editor.offsettoline(selstart)
|
|
alltext = self.editgroup.editor.get()
|
|
pytext = alltext[selstart:selend]
|
|
lines = string.split(pytext, '\r')
|
|
indent = getminindent(lines)
|
|
if indent == 1:
|
|
classname = ''
|
|
alllines = string.split(alltext, '\r')
|
|
identifieRE_match = _identifieRE.match
|
|
for i in range(selfirstline - 1, -1, -1):
|
|
line = alllines[i]
|
|
if line[:6] == 'class ':
|
|
classname = string.split(string.strip(line[6:]))[0]
|
|
classend = identifieRE_match(classname)
|
|
if classend < 1:
|
|
raise W.AlertError, 'Can¹t find a class.'
|
|
classname = classname[:classend]
|
|
break
|
|
elif line and line[0] not in '\t#':
|
|
raise W.AlertError, 'Can¹t find a class.'
|
|
else:
|
|
raise W.AlertError, 'Can¹t find a class.'
|
|
if globals.has_key(classname):
|
|
locals = globals[classname].__dict__
|
|
else:
|
|
raise W.AlertError, 'Can¹t find class ³%s².' % classname
|
|
# dedent to top level
|
|
for i in range(len(lines)):
|
|
lines[i] = lines[i][1:]
|
|
pytext = string.join(lines, '\r')
|
|
elif indent > 0:
|
|
raise W.AlertError, 'Can¹t run indented code.'
|
|
|
|
# add "newlines" to fool compile/exec:
|
|
# now a traceback will give the right line number
|
|
pytext = selfirstline * '\r' + pytext
|
|
self.execstring(pytext, globals, locals, file, modname)
|
|
|
|
def setthreadstate(self, state):
|
|
oldstate = self._threadstate
|
|
if oldstate[0] <> state[0]:
|
|
self.runbutton.settitle(runButtonLabels[state[0]])
|
|
if oldstate[1] <> state[1]:
|
|
self.runselbutton.settitle(runSelButtonLabels[state[1]])
|
|
self._threadstate = state
|
|
|
|
def _exec_threadwrapper(self, *args, **kwargs):
|
|
apply(execstring, args, kwargs)
|
|
self.setthreadstate((0, 0))
|
|
self._thread = None
|
|
|
|
def execstring(self, pytext, globals, locals, file, modname):
|
|
tracebackwindow.hide()
|
|
# update windows
|
|
W.getapplication().refreshwindows()
|
|
if self.run_as_main:
|
|
modname = "__main__"
|
|
if self.path:
|
|
dir = os.path.dirname(self.path)
|
|
savedir = os.getcwd()
|
|
os.chdir(dir)
|
|
sys.path.insert(0, dir)
|
|
else:
|
|
cwdindex = None
|
|
try:
|
|
if uthread2 and uthread2.currentThread() is not None:
|
|
self._thread = uthread2.Thread(file,
|
|
self._exec_threadwrapper, pytext, globals, locals, file, self.debugging,
|
|
modname, self.profiling)
|
|
self.setthreadstate((1, 1))
|
|
self._thread.start()
|
|
else:
|
|
execstring(pytext, globals, locals, file, self.debugging,
|
|
modname, self.profiling)
|
|
finally:
|
|
if self.path:
|
|
os.chdir(savedir)
|
|
del sys.path[0]
|
|
|
|
def getenvironment(self):
|
|
if self.path:
|
|
file = self.path
|
|
dir = os.path.dirname(file)
|
|
# check if we're part of a package
|
|
modname = ""
|
|
while os.path.exists(os.path.join(dir, "__init__.py")):
|
|
dir, dirname = os.path.split(dir)
|
|
modname = dirname + '.' + modname
|
|
subname = _filename_as_modname(self.title)
|
|
if modname:
|
|
if subname == "__init__":
|
|
# strip trailing period
|
|
modname = modname[:-1]
|
|
else:
|
|
modname = modname + subname
|
|
else:
|
|
modname = subname
|
|
if sys.modules.has_key(modname):
|
|
globals = sys.modules[modname].__dict__
|
|
self.globals = {}
|
|
else:
|
|
globals = self.globals
|
|
modname = subname
|
|
else:
|
|
file = '<%s>' % self.title
|
|
globals = self.globals
|
|
modname = file
|
|
return globals, file, modname
|
|
|
|
def write(self, stuff):
|
|
"""for use as stdout"""
|
|
self._buf = self._buf + stuff
|
|
if '\n' in self._buf:
|
|
self.flush()
|
|
|
|
def flush(self):
|
|
stuff = string.split(self._buf, '\n')
|
|
stuff = string.join(stuff, '\r')
|
|
end = self.editgroup.editor.ted.WEGetTextLength()
|
|
self.editgroup.editor.ted.WESetSelection(end, end)
|
|
self.editgroup.editor.ted.WEInsert(stuff, None, None)
|
|
self.editgroup.editor.updatescrollbars()
|
|
self._buf = ""
|
|
# ? optional:
|
|
#self.wid.SelectWindow()
|
|
|
|
def getclasslist(self):
|
|
from string import find, strip
|
|
import re
|
|
methodRE = re.compile(r"\r[ \t]+def ")
|
|
findMethod = methodRE.search
|
|
editor = self.editgroup.editor
|
|
text = editor.get()
|
|
list = []
|
|
append = list.append
|
|
functag = "func"
|
|
classtag = "class"
|
|
methodtag = "method"
|
|
pos = -1
|
|
if text[:4] == 'def ':
|
|
append((pos + 4, functag))
|
|
pos = 4
|
|
while 1:
|
|
pos = find(text, '\rdef ', pos + 1)
|
|
if pos < 0:
|
|
break
|
|
append((pos + 5, functag))
|
|
pos = -1
|
|
if text[:6] == 'class ':
|
|
append((pos + 6, classtag))
|
|
pos = 6
|
|
while 1:
|
|
pos = find(text, '\rclass ', pos + 1)
|
|
if pos < 0:
|
|
break
|
|
append((pos + 7, classtag))
|
|
pos = 0
|
|
while 1:
|
|
m = findMethod(text, pos + 1)
|
|
if m is None:
|
|
break
|
|
pos = m.regs[0][0]
|
|
#pos = find(text, '\r\tdef ', pos + 1)
|
|
append((m.regs[0][1], methodtag))
|
|
list.sort()
|
|
classlist = []
|
|
methodlistappend = None
|
|
offsetToLine = editor.ted.WEOffsetToLine
|
|
getLineRange = editor.ted.WEGetLineRange
|
|
append = classlist.append
|
|
identifieRE_match = _identifieRE.match
|
|
for pos, tag in list:
|
|
lineno = offsetToLine(pos)
|
|
lineStart, lineEnd = getLineRange(lineno)
|
|
line = strip(text[pos:lineEnd])
|
|
line = line[:identifieRE_match(line)]
|
|
if tag is functag:
|
|
append(("def " + line, lineno + 1))
|
|
methodlistappend = None
|
|
elif tag is classtag:
|
|
append(["class " + line])
|
|
methodlistappend = classlist[-1].append
|
|
elif methodlistappend and tag is methodtag:
|
|
methodlistappend(("def " + line, lineno + 1))
|
|
return classlist
|
|
|
|
def popselectline(self, lineno):
|
|
self.editgroup.editor.selectline(lineno - 1)
|
|
|
|
def selectline(self, lineno, charoffset = 0):
|
|
self.editgroup.editor.selectline(lineno - 1, charoffset)
|
|
|
|
class _saveoptions:
|
|
|
|
def __init__(self, creator):
|
|
self.rv = None
|
|
self.w = w = W.ModalDialog((240, 140), 'Save options')
|
|
radiobuttons = []
|
|
w.label = W.TextBox((8, 8, 80, 18), "File creator:")
|
|
w.ide_radio = W.RadioButton((8, 22, 160, 18), "This application", radiobuttons, self.ide_hit)
|
|
w.interp_radio = W.RadioButton((8, 42, 160, 18), "Python Interpreter", radiobuttons, self.interp_hit)
|
|
w.other_radio = W.RadioButton((8, 62, 50, 18), "Other:", radiobuttons)
|
|
w.other_creator = W.EditText((62, 62, 40, 20), creator, self.otherselect)
|
|
w.cancelbutton = W.Button((-180, -30, 80, 16), "Cancel", self.cancelbuttonhit)
|
|
w.okbutton = W.Button((-90, -30, 80, 16), "Done", self.okbuttonhit)
|
|
w.setdefaultbutton(w.okbutton)
|
|
if creator == 'Pyth':
|
|
w.interp_radio.set(1)
|
|
elif creator == W._signature:
|
|
w.ide_radio.set(1)
|
|
else:
|
|
w.other_radio.set(1)
|
|
w.bind("cmd.", w.cancelbutton.push)
|
|
w.open()
|
|
|
|
def ide_hit(self):
|
|
self.w.other_creator.set(W._signature)
|
|
|
|
def interp_hit(self):
|
|
self.w.other_creator.set("Pyth")
|
|
|
|
def otherselect(self, *args):
|
|
sel_from, sel_to = self.w.other_creator.getselection()
|
|
creator = self.w.other_creator.get()[:4]
|
|
creator = creator + " " * (4 - len(creator))
|
|
self.w.other_creator.set(creator)
|
|
self.w.other_creator.setselection(sel_from, sel_to)
|
|
self.w.other_radio.set(1)
|
|
|
|
def cancelbuttonhit(self):
|
|
self.w.close()
|
|
|
|
def okbuttonhit(self):
|
|
self.rv = self.w.other_creator.get()[:4]
|
|
self.w.close()
|
|
|
|
|
|
def SaveOptions(creator):
|
|
s = _saveoptions(creator)
|
|
return s.rv
|
|
|
|
|
|
def _escape(where, what) :
|
|
return string.join(string.split(where, what), '\\' + what)
|
|
|
|
def _makewholewordpattern(word):
|
|
# first, escape special regex chars
|
|
for esc in "\\[].*^+$?":
|
|
word = _escape(word, esc)
|
|
import regex
|
|
notwordcharspat = '[^' + _wordchars + ']'
|
|
pattern = '\(' + word + '\)'
|
|
if word[0] in _wordchars:
|
|
pattern = notwordcharspat + pattern
|
|
if word[-1] in _wordchars:
|
|
pattern = pattern + notwordcharspat
|
|
return regex.compile(pattern)
|
|
|
|
class SearchEngine:
|
|
|
|
def __init__(self):
|
|
self.visible = 0
|
|
self.w = None
|
|
self.parms = { "find": "",
|
|
"replace": "",
|
|
"wrap": 1,
|
|
"casesens": 1,
|
|
"wholeword": 1
|
|
}
|
|
import MacPrefs
|
|
prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath)
|
|
if prefs.searchengine:
|
|
self.parms["casesens"] = prefs.searchengine.casesens
|
|
self.parms["wrap"] = prefs.searchengine.wrap
|
|
self.parms["wholeword"] = prefs.searchengine.wholeword
|
|
|
|
def show(self):
|
|
self.visible = 1
|
|
if self.w:
|
|
self.w.wid.ShowWindow()
|
|
self.w.wid.SelectWindow()
|
|
self.w.find.edit.select(1)
|
|
self.w.find.edit.selectall()
|
|
return
|
|
self.w = W.Dialog((420, 150), "Find")
|
|
|
|
self.w.find = TitledEditText((10, 4, 300, 36), "Search for:")
|
|
self.w.replace = TitledEditText((10, 100, 300, 36), "Replace with:")
|
|
|
|
self.w.boxes = W.Group((10, 50, 300, 40))
|
|
self.w.boxes.casesens = W.CheckBox((0, 0, 100, 16), "Case sensitive")
|
|
self.w.boxes.wholeword = W.CheckBox((0, 20, 100, 16), "Whole word")
|
|
self.w.boxes.wrap = W.CheckBox((110, 0, 100, 16), "Wrap around")
|
|
|
|
self.buttons = [ ("Find", "cmdf", self.find),
|
|
("Replace", "cmdr", self.replace),
|
|
("Replace all", None, self.replaceall),
|
|
("Don¹t find", "cmdd", self.dont),
|
|
("Cancel", "cmd.", self.cancel)
|
|
]
|
|
for i in range(len(self.buttons)):
|
|
bounds = -90, 22 + i * 24, 80, 16
|
|
title, shortcut, callback = self.buttons[i]
|
|
self.w[title] = W.Button(bounds, title, callback)
|
|
if shortcut:
|
|
self.w.bind(shortcut, self.w[title].push)
|
|
self.w.setdefaultbutton(self.w["Don¹t find"])
|
|
self.w.find.edit.bind("<key>", self.key)
|
|
self.w.bind("<activate>", self.activate)
|
|
self.w.bind("<close>", self.close)
|
|
self.w.open()
|
|
self.setparms()
|
|
self.w.find.edit.select(1)
|
|
self.w.find.edit.selectall()
|
|
self.checkbuttons()
|
|
|
|
def close(self):
|
|
self.hide()
|
|
return -1
|
|
|
|
def key(self, char, modifiers):
|
|
self.w.find.edit.key(char, modifiers)
|
|
self.checkbuttons()
|
|
return 1
|
|
|
|
def activate(self, onoff):
|
|
if onoff:
|
|
self.checkbuttons()
|
|
|
|
def checkbuttons(self):
|
|
editor = findeditor(self)
|
|
if editor:
|
|
if self.w.find.get():
|
|
for title, cmd, call in self.buttons[:-2]:
|
|
self.w[title].enable(1)
|
|
self.w.setdefaultbutton(self.w["Find"])
|
|
else:
|
|
for title, cmd, call in self.buttons[:-2]:
|
|
self.w[title].enable(0)
|
|
self.w.setdefaultbutton(self.w["Don¹t find"])
|
|
else:
|
|
for title, cmd, call in self.buttons[:-2]:
|
|
self.w[title].enable(0)
|
|
self.w.setdefaultbutton(self.w["Don¹t find"])
|
|
|
|
def find(self):
|
|
self.getparmsfromwindow()
|
|
if self.findnext():
|
|
self.hide()
|
|
|
|
def replace(self):
|
|
editor = findeditor(self)
|
|
if not editor:
|
|
return
|
|
if self.visible:
|
|
self.getparmsfromwindow()
|
|
text = editor.getselectedtext()
|
|
find = self.parms["find"]
|
|
if not self.parms["casesens"]:
|
|
find = string.lower(find)
|
|
text = string.lower(text)
|
|
if text == find:
|
|
self.hide()
|
|
editor.insert(self.parms["replace"])
|
|
|
|
def replaceall(self):
|
|
editor = findeditor(self)
|
|
if not editor:
|
|
return
|
|
if self.visible:
|
|
self.getparmsfromwindow()
|
|
W.SetCursor("watch")
|
|
find = self.parms["find"]
|
|
if not find:
|
|
return
|
|
findlen = len(find)
|
|
replace = self.parms["replace"]
|
|
replacelen = len(replace)
|
|
Text = editor.get()
|
|
if not self.parms["casesens"]:
|
|
find = string.lower(find)
|
|
text = string.lower(Text)
|
|
else:
|
|
text = Text
|
|
newtext = ""
|
|
pos = 0
|
|
counter = 0
|
|
while 1:
|
|
if self.parms["wholeword"]:
|
|
wholewordRE = _makewholewordpattern(find)
|
|
wholewordRE.search(text, pos)
|
|
if wholewordRE.regs:
|
|
pos = wholewordRE.regs[1][0]
|
|
else:
|
|
pos = -1
|
|
else:
|
|
pos = string.find(text, find, pos)
|
|
if pos < 0:
|
|
break
|
|
counter = counter + 1
|
|
text = text[:pos] + replace + text[pos + findlen:]
|
|
Text = Text[:pos] + replace + Text[pos + findlen:]
|
|
pos = pos + replacelen
|
|
W.SetCursor("arrow")
|
|
if counter:
|
|
self.hide()
|
|
import EasyDialogs
|
|
import Res
|
|
editor.changed = 1
|
|
editor.selchanged = 1
|
|
editor.ted.WEUseText(Res.Resource(Text))
|
|
editor.ted.WECalText()
|
|
editor.SetPort()
|
|
Win.InvalRect(editor._bounds)
|
|
#editor.ted.WEUpdate(self.w.wid.GetWindowPort().visRgn)
|
|
EasyDialogs.Message("Replaced %d occurrences" % counter)
|
|
|
|
def dont(self):
|
|
self.getparmsfromwindow()
|
|
self.hide()
|
|
|
|
def replacefind(self):
|
|
self.replace()
|
|
self.findnext()
|
|
|
|
def setfindstring(self):
|
|
editor = findeditor(self)
|
|
if not editor:
|
|
return
|
|
find = editor.getselectedtext()
|
|
if not find:
|
|
return
|
|
self.parms["find"] = find
|
|
if self.w:
|
|
self.w.find.edit.set(self.parms["find"])
|
|
self.w.find.edit.selectall()
|
|
|
|
def findnext(self):
|
|
editor = findeditor(self)
|
|
if not editor:
|
|
return
|
|
find = self.parms["find"]
|
|
if not find:
|
|
return
|
|
text = editor.get()
|
|
if not self.parms["casesens"]:
|
|
find = string.lower(find)
|
|
text = string.lower(text)
|
|
selstart, selend = editor.getselection()
|
|
selstart, selend = min(selstart, selend), max(selstart, selend)
|
|
if self.parms["wholeword"]:
|
|
wholewordRE = _makewholewordpattern(find)
|
|
wholewordRE.search(text, selend)
|
|
if wholewordRE.regs:
|
|
pos = wholewordRE.regs[1][0]
|
|
else:
|
|
pos = -1
|
|
else:
|
|
pos = string.find(text, find, selend)
|
|
if pos >= 0:
|
|
editor.setselection(pos, pos + len(find))
|
|
return 1
|
|
elif self.parms["wrap"]:
|
|
if self.parms["wholeword"]:
|
|
wholewordRE.search(text, 0)
|
|
if wholewordRE.regs:
|
|
pos = wholewordRE.regs[1][0]
|
|
else:
|
|
pos = -1
|
|
else:
|
|
pos = string.find(text, find)
|
|
if selstart > pos >= 0:
|
|
editor.setselection(pos, pos + len(find))
|
|
return 1
|
|
|
|
def setparms(self):
|
|
for key, value in self.parms.items():
|
|
try:
|
|
self.w[key].set(value)
|
|
except KeyError:
|
|
self.w.boxes[key].set(value)
|
|
|
|
def getparmsfromwindow(self):
|
|
if not self.w:
|
|
return
|
|
for key, value in self.parms.items():
|
|
try:
|
|
value = self.w[key].get()
|
|
except KeyError:
|
|
value = self.w.boxes[key].get()
|
|
self.parms[key] = value
|
|
|
|
def cancel(self):
|
|
self.hide()
|
|
self.setparms()
|
|
|
|
def hide(self):
|
|
if self.w:
|
|
self.w.wid.HideWindow()
|
|
self.visible = 0
|
|
|
|
def writeprefs(self):
|
|
import MacPrefs
|
|
self.getparmsfromwindow()
|
|
prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath)
|
|
prefs.searchengine.casesens = self.parms["casesens"]
|
|
prefs.searchengine.wrap = self.parms["wrap"]
|
|
prefs.searchengine.wholeword = self.parms["wholeword"]
|
|
prefs.save()
|
|
|
|
|
|
class TitledEditText(W.Group):
|
|
|
|
def __init__(self, possize, title, text = ""):
|
|
W.Group.__init__(self, possize)
|
|
self.title = W.TextBox((0, 0, 0, 16), title)
|
|
self.edit = W.EditText((0, 16, 0, 0), text)
|
|
|
|
def set(self, value):
|
|
self.edit.set(value)
|
|
|
|
def get(self):
|
|
return self.edit.get()
|
|
|
|
|
|
class ClassFinder(W.PopupWidget):
|
|
|
|
def click(self, point, modifiers):
|
|
W.SetCursor("watch")
|
|
self.set(self._parentwindow.getclasslist())
|
|
W.PopupWidget.click(self, point, modifiers)
|
|
|
|
|
|
def getminindent(lines):
|
|
indent = -1
|
|
for line in lines:
|
|
stripped = string.strip(line)
|
|
if not stripped or stripped[0] == '#':
|
|
continue
|
|
if indent < 0 or line[:indent] <> indent * '\t':
|
|
indent = 0
|
|
for c in line:
|
|
if c <> '\t':
|
|
break
|
|
indent = indent + 1
|
|
return indent
|
|
|
|
|
|
def getoptionkey():
|
|
return not not ord(Evt.GetKeys()[7]) & 0x04
|
|
|
|
|
|
def execstring(pytext, globals, locals, filename="<string>", debugging=0,
|
|
modname="__main__", profiling=0):
|
|
if debugging:
|
|
import PyDebugger, bdb
|
|
BdbQuit = bdb.BdbQuit
|
|
else:
|
|
BdbQuit = 'BdbQuitDummyException'
|
|
pytext = string.split(pytext, '\r')
|
|
pytext = string.join(pytext, '\n') + '\n'
|
|
W.SetCursor("watch")
|
|
globals['__name__'] = modname
|
|
globals['__file__'] = filename
|
|
sys.argv = [filename]
|
|
try:
|
|
code = compile(pytext, filename, "exec")
|
|
except:
|
|
# XXXX BAAAADDD.... We let tracebackwindow decide to treat SyntaxError
|
|
# special. That's wrong because THIS case is special (could be literal
|
|
# overflow!) and SyntaxError could mean we need a traceback (syntax error
|
|
# in imported module!!!
|
|
tracebackwindow.traceback(1, filename)
|
|
return
|
|
try:
|
|
if debugging:
|
|
if uthread2:
|
|
uthread2.globalLock()
|
|
PyDebugger.startfromhere()
|
|
uthread2.globalUnlock()
|
|
else:
|
|
PyDebugger.startfromhere()
|
|
elif not uthread2:
|
|
MacOS.EnableAppswitch(0)
|
|
try:
|
|
if profiling:
|
|
import profile, ProfileBrowser
|
|
p = profile.Profile()
|
|
p.set_cmd(filename)
|
|
try:
|
|
p.runctx(code, globals, locals)
|
|
finally:
|
|
import pstats
|
|
|
|
stats = pstats.Stats(p)
|
|
ProfileBrowser.ProfileBrowser(stats)
|
|
else:
|
|
exec code in globals, locals
|
|
finally:
|
|
if not uthread2:
|
|
MacOS.EnableAppswitch(-1)
|
|
except W.AlertError, detail:
|
|
raise W.AlertError, detail
|
|
except (KeyboardInterrupt, BdbQuit):
|
|
pass
|
|
except:
|
|
if uthread2:
|
|
uthread2.globalLock()
|
|
if debugging:
|
|
sys.settrace(None)
|
|
PyDebugger.postmortem(sys.exc_type, sys.exc_value, sys.exc_traceback)
|
|
return
|
|
else:
|
|
tracebackwindow.traceback(1, filename)
|
|
if uthread2:
|
|
uthread2.globalUnlock()
|
|
if debugging:
|
|
sys.settrace(None)
|
|
PyDebugger.stop()
|
|
|
|
|
|
_identifieRE = regex.compile("[A-Za-z_][A-Za-z_0-9]*")
|
|
|
|
def _filename_as_modname(fname):
|
|
if fname[-3:] == '.py':
|
|
modname = fname[:-3]
|
|
if _identifieRE.match(modname) == len(modname):
|
|
return string.join(string.split(modname, '.'), '_')
|
|
|
|
def findeditor(topwindow, fromtop = 0):
|
|
wid = Win.FrontWindow()
|
|
if not fromtop:
|
|
if topwindow.w and wid == topwindow.w.wid:
|
|
wid = topwindow.w.wid.GetNextWindow()
|
|
if not wid:
|
|
return
|
|
app = W.getapplication()
|
|
if app._windows.has_key(wid): # KeyError otherwise can happen in RoboFog :-(
|
|
window = W.getapplication()._windows[wid]
|
|
else:
|
|
return
|
|
if not isinstance(window, Editor):
|
|
return
|
|
return window.editgroup.editor
|
|
|
|
|
|
class _EditorDefaultSettings:
|
|
|
|
def __init__(self):
|
|
self.template = "%s, %d point"
|
|
self.fontsettings, self.tabsettings, self.windowsize = geteditorprefs()
|
|
self.w = W.Dialog((328, 120), "Editor default settings")
|
|
self.w.setfontbutton = W.Button((8, 8, 80, 16), "Set fontŠ", self.dofont)
|
|
self.w.fonttext = W.TextBox((98, 10, -8, 14), self.template % (self.fontsettings[0], self.fontsettings[2]))
|
|
|
|
self.w.picksizebutton = W.Button((8, 50, 80, 16), "Front window", self.picksize)
|
|
self.w.xsizelabel = W.TextBox((98, 32, 40, 14), "Width:")
|
|
self.w.ysizelabel = W.TextBox((148, 32, 40, 14), "Height:")
|
|
self.w.xsize = W.EditText((98, 48, 40, 20), `self.windowsize[0]`)
|
|
self.w.ysize = W.EditText((148, 48, 40, 20), `self.windowsize[1]`)
|
|
|
|
self.w.cancelbutton = W.Button((-180, -26, 80, 16), "Cancel", self.cancel)
|
|
self.w.okbutton = W.Button((-90, -26, 80, 16), "Done", self.ok)
|
|
self.w.setdefaultbutton(self.w.okbutton)
|
|
self.w.bind('cmd.', self.w.cancelbutton.push)
|
|
self.w.open()
|
|
|
|
def picksize(self):
|
|
app = W.getapplication()
|
|
editor = findeditor(self)
|
|
if editor is not None:
|
|
width, height = editor._parentwindow._bounds[2:]
|
|
self.w.xsize.set(`width`)
|
|
self.w.ysize.set(`height`)
|
|
else:
|
|
raise W.AlertError, "No edit window found"
|
|
|
|
def dofont(self):
|
|
import FontSettings
|
|
settings = FontSettings.FontDialog(self.fontsettings, self.tabsettings)
|
|
if settings:
|
|
self.fontsettings, self.tabsettings = settings
|
|
sys.exc_traceback = None
|
|
self.w.fonttext.set(self.template % (self.fontsettings[0], self.fontsettings[2]))
|
|
|
|
def close(self):
|
|
self.w.close()
|
|
del self.w
|
|
|
|
def cancel(self):
|
|
self.close()
|
|
|
|
def ok(self):
|
|
try:
|
|
width = string.atoi(self.w.xsize.get())
|
|
except:
|
|
self.w.xsize.select(1)
|
|
self.w.xsize.selectall()
|
|
raise W.AlertError, "Bad number for window width"
|
|
try:
|
|
height = string.atoi(self.w.ysize.get())
|
|
except:
|
|
self.w.ysize.select(1)
|
|
self.w.ysize.selectall()
|
|
raise W.AlertError, "Bad number for window height"
|
|
self.windowsize = width, height
|
|
seteditorprefs(self.fontsettings, self.tabsettings, self.windowsize)
|
|
self.close()
|
|
|
|
def geteditorprefs():
|
|
import MacPrefs
|
|
prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath)
|
|
try:
|
|
fontsettings = prefs.pyedit.fontsettings
|
|
tabsettings = prefs.pyedit.tabsettings
|
|
windowsize = prefs.pyedit.windowsize
|
|
except:
|
|
fontsettings = prefs.pyedit.fontsettings = ("Python-Sans", 0, 9, (0, 0, 0))
|
|
tabsettings = prefs.pyedit.tabsettings = (8, 1)
|
|
windowsize = prefs.pyedit.windowsize = (500, 250)
|
|
sys.exc_traceback = None
|
|
return fontsettings, tabsettings, windowsize
|
|
|
|
def seteditorprefs(fontsettings, tabsettings, windowsize):
|
|
import MacPrefs
|
|
prefs = MacPrefs.GetPrefs(W.getapplication().preffilepath)
|
|
prefs.pyedit.fontsettings = fontsettings
|
|
prefs.pyedit.tabsettings = tabsettings
|
|
prefs.pyedit.windowsize = windowsize
|
|
prefs.save()
|
|
|
|
_defaultSettingsEditor = None
|
|
|
|
def EditorDefaultSettings():
|
|
global _defaultSettingsEditor
|
|
if _defaultSettingsEditor is None or not hasattr(_defaultSettingsEditor, "w"):
|
|
_defaultSettingsEditor = _EditorDefaultSettings()
|
|
else:
|
|
_defaultSettingsEditor.w.select()
|
|
|
|
def resolvealiases(path):
|
|
try:
|
|
return macfs.ResolveAliasFile(path)[0].as_pathname()
|
|
except (macfs.error, ValueError), (error, str):
|
|
if error <> -120:
|
|
raise
|
|
dir, file = os.path.split(path)
|
|
return os.path.join(resolvealiases(dir), file)
|
|
|
|
searchengine = SearchEngine()
|
|
tracebackwindow = Wtraceback.TraceBack()
|