Commit c63e174f authored by David Scherer's avatar David Scherer

Initial revision

parent 076e2c09
import string
import re
###$ event <<expand-word>>
###$ win <Alt-slash>
###$ unix <Alt-slash>
class AutoExpand:
keydefs = {
'<<expand-word>>': ['<Alt-slash>'],
}
unix_keydefs = {
'<<expand-word>>': ['<Meta-slash>'],
}
menudefs = [
('edit', [
('E_xpand word', '<<expand-word>>'),
]),
]
wordchars = string.letters + string.digits + "_"
def __init__(self, editwin):
self.text = editwin.text
self.text.wordlist = None # XXX what is this?
self.state = None
def expand_word_event(self, event):
curinsert = self.text.index("insert")
curline = self.text.get("insert linestart", "insert lineend")
if not self.state:
words = self.getwords()
index = 0
else:
words, index, insert, line = self.state
if insert != curinsert or line != curline:
words = self.getwords()
index = 0
if not words:
self.text.bell()
return "break"
word = self.getprevword()
self.text.delete("insert - %d chars" % len(word), "insert")
newword = words[index]
index = (index + 1) % len(words)
if index == 0:
self.text.bell() # Warn we cycled around
self.text.insert("insert", newword)
curinsert = self.text.index("insert")
curline = self.text.get("insert linestart", "insert lineend")
self.state = words, index, curinsert, curline
return "break"
def getwords(self):
word = self.getprevword()
if not word:
return []
before = self.text.get("1.0", "insert wordstart")
wbefore = re.findall(r"\b" + word + r"\w+\b", before)
del before
after = self.text.get("insert wordend", "end")
wafter = re.findall(r"\b" + word + r"\w+\b", after)
del after
if not wbefore and not wafter:
return []
words = []
dict = {}
# search backwards through words before
wbefore.reverse()
for w in wbefore:
if dict.get(w):
continue
words.append(w)
dict[w] = w
# search onwards through words after
for w in wafter:
if dict.get(w):
continue
words.append(w)
dict[w] = w
words.append(word)
return words
def getprevword(self):
line = self.text.get("insert linestart", "insert")
i = len(line)
while i > 0 and line[i-1] in self.wordchars:
i = i-1
return line[i:]
This diff is collapsed.
# This file defines the menu contents and key bindings. Note that
# there is additional configuration information in the EditorWindow
# class (and subclasses): the menus are created there based on the
# menu_specs (class) variable, and menus not created are silently
# skipped by the code here. This makes it possible to define the
# Debug menu here, which is only present in the PythonShell window.
# changes by dscherer@cmu.edu:
# - Python shell moved to 'Run' menu
# - "Help" renamed to "IDLE Help" to distinguish from Python help.
# The distinction between the environment and the language is dim
# or nonexistent in a novice's mind.
# - Silly advice added
import sys
import string
from keydefs import *
menudefs = [
# underscore prefixes character to underscore
('file', [
('_New window', '<<open-new-window>>'),
('_Open...', '<<open-window-from-file>>'),
('Open _module...', '<<open-module>>'),
('Class _browser', '<<open-class-browser>>'),
('_Path browser', '<<open-path-browser>>'),
None,
('_Save', '<<save-window>>'),
('Save _As...', '<<save-window-as-file>>'),
('Save Co_py As...', '<<save-copy-of-window-as-file>>'),
None,
('_Close', '<<close-window>>'),
('E_xit', '<<close-all-windows>>'),
]),
('edit', [
('_Undo', '<<undo>>'),
('_Redo', '<<redo>>'),
None,
('Cu_t', '<<Cut>>'),
('_Copy', '<<Copy>>'),
('_Paste', '<<Paste>>'),
('Select _All', '<<select-all>>'),
]),
('run',[
('Python shell', '<<open-python-shell>>'),
]),
('debug', [
('_Go to file/line', '<<goto-file-line>>'),
('_Stack viewer', '<<open-stack-viewer>>'),
('!_Debugger', '<<toggle-debugger>>'),
('!_Auto-open stack viewer', '<<toggle-jit-stack-viewer>>' ),
]),
('help', [
('_IDLE Help...', '<<help>>'),
('Python _Documentation...', '<<python-docs>>'),
('_Advice...', '<<good-advice>>'),
None,
('_About IDLE...', '<<about-idle>>'),
]),
]
if sys.platform == 'win32':
default_keydefs = windows_keydefs
else:
default_keydefs = unix_keydefs
del sys
# A CallTip window class for Tkinter/IDLE.
# After ToolTip.py, which uses ideas gleaned from PySol
# Used by the CallTips IDLE extension.
import os
from Tkinter import *
class CallTip:
def __init__(self, widget):
self.widget = widget
self.tipwindow = None
self.id = None
self.x = self.y = 0
def showtip(self, text):
self.text = text
if self.tipwindow or not self.text:
return
self.widget.see("insert")
x, y, cx, cy = self.widget.bbox("insert")
x = x + self.widget.winfo_rootx() + 2
y = y + cy + self.widget.winfo_rooty()
self.tipwindow = tw = Toplevel(self.widget)
tw.wm_overrideredirect(1)
tw.wm_geometry("+%d+%d" % (x, y))
label = Label(tw, text=self.text, justify=LEFT,
background="#ffffe0", relief=SOLID, borderwidth=1,
font = self.widget['font'])
label.pack()
def hidetip(self):
tw = self.tipwindow
self.tipwindow = None
if tw:
tw.destroy()
###############################
#
# Test Code
#
class container: # Conceptually an editor_window
def __init__(self):
root = Tk()
text = self.text = Text(root)
text.pack(side=LEFT, fill=BOTH, expand=1)
text.insert("insert", "string.split")
root.update()
self.calltip = CallTip(text)
text.event_add("<<calltip-show>>", "(")
text.event_add("<<calltip-hide>>", ")")
text.bind("<<calltip-show>>", self.calltip_show)
text.bind("<<calltip-hide>>", self.calltip_hide)
text.focus_set()
# root.mainloop() # not in idle
def calltip_show(self, event):
self.calltip.showtip("Hello world")
def calltip_hide(self, event):
self.calltip.hidetip()
def main():
# Test code
c=container()
if __name__=='__main__':
main()
# CallTips.py - An IDLE extension that provides "Call Tips" - ie, a floating window that
# displays parameter information as you open parens.
import string
import sys
import types
class CallTips:
menudefs = [
]
keydefs = {
'<<paren-open>>': ['<Key-parenleft>'],
'<<paren-close>>': ['<Key-parenright>'],
'<<check-calltip-cancel>>': ['<KeyRelease>'],
'<<calltip-cancel>>': ['<ButtonPress>', '<Key-Escape>'],
}
windows_keydefs = {
}
unix_keydefs = {
}
def __init__(self, editwin):
self.editwin = editwin
self.text = editwin.text
self.calltip = None
if hasattr(self.text, "make_calltip_window"):
self._make_calltip_window = self.text.make_calltip_window
else:
self._make_calltip_window = self._make_tk_calltip_window
def close(self):
self._make_calltip_window = None
# Makes a Tk based calltip window. Used by IDLE, but not Pythonwin.
# See __init__ above for how this is used.
def _make_tk_calltip_window(self):
import CallTipWindow
return CallTipWindow.CallTip(self.text)
def _remove_calltip_window(self):
if self.calltip:
self.calltip.hidetip()
self.calltip = None
def paren_open_event(self, event):
self._remove_calltip_window()
arg_text = get_arg_text(self.get_object_at_cursor())
if arg_text:
self.calltip_start = self.text.index("insert")
self.calltip = self._make_calltip_window()
self.calltip.showtip(arg_text)
return "" #so the event is handled normally.
def paren_close_event(self, event):
# Now just hides, but later we should check if other
# paren'd expressions remain open.
self._remove_calltip_window()
return "" #so the event is handled normally.
def check_calltip_cancel_event(self, event):
if self.calltip:
# If we have moved before the start of the calltip,
# or off the calltip line, then cancel the tip.
# (Later need to be smarter about multi-line, etc)
if self.text.compare("insert", "<=", self.calltip_start) or \
self.text.compare("insert", ">", self.calltip_start + " lineend"):
self._remove_calltip_window()
return "" #so the event is handled normally.
def calltip_cancel_event(self, event):
self._remove_calltip_window()
return "" #so the event is handled normally.
def get_object_at_cursor(self,
wordchars="._" + string.uppercase + string.lowercase + string.digits):
# XXX - This needs to be moved to a better place
# so the "." attribute lookup code can also use it.
text = self.text
chars = text.get("insert linestart", "insert")
i = len(chars)
while i and chars[i-1] in wordchars:
i = i-1
word = chars[i:]
if word:
# How is this for a hack!
import sys, __main__
namespace = sys.modules.copy()
namespace.update(__main__.__dict__)
try:
return eval(word, namespace)
except:
pass
return None # Can't find an object.
def _find_constructor(class_ob):
# Given a class object, return a function object used for the
# constructor (ie, __init__() ) or None if we can't find one.
try:
return class_ob.__init__.im_func
except AttributeError:
for base in class_ob.__bases__:
rc = _find_constructor(base)
if rc is not None: return rc
return None
def get_arg_text(ob):
# Get a string describing the arguments for the given object.
argText = ""
if ob is not None:
argOffset = 0
if type(ob)==types.ClassType:
# Look for the highest __init__ in the class chain.
fob = _find_constructor(ob)
if fob is None:
fob = lambda: None
else:
argOffset = 1
elif type(ob)==types.MethodType:
# bit of a hack for methods - turn it into a function
# but we drop the "self" param.
fob = ob.im_func
argOffset = 1
else:
fob = ob
# Try and build one for Python defined functions
if type(fob) in [types.FunctionType, types.LambdaType]:
try:
realArgs = fob.func_code.co_varnames[argOffset:fob.func_code.co_argcount]
defaults = fob.func_defaults or []
defaults = list(map(lambda name: "=%s" % name, defaults))
defaults = [""] * (len(realArgs)-len(defaults)) + defaults
items = map(lambda arg, dflt: arg+dflt, realArgs, defaults)
if fob.func_code.co_flags & 0x4:
items.append("...")
if fob.func_code.co_flags & 0x8:
items.append("***")
argText = string.join(items , ", ")
argText = "(%s)" % argText
except:
pass
# See if we can use the docstring
if hasattr(ob, "__doc__") and ob.__doc__:
pos = string.find(ob.__doc__, "\n")
if pos<0 or pos>70: pos=70
if argText: argText = argText + "\n"
argText = argText + ob.__doc__[:pos]
return argText
#################################################
#
# Test code
#
if __name__=='__main__':
def t1(): "()"
def t2(a, b=None): "(a, b=None)"
def t3(a, *args): "(a, ...)"
def t4(*args): "(...)"
def t5(a, *args): "(a, ...)"
def t6(a, b=None, *args, **kw): "(a, b=None, ..., ***)"
class TC:
"(a=None, ...)"
def __init__(self, a=None, *b): "(a=None, ...)"
def t1(self): "()"
def t2(self, a, b=None): "(a, b=None)"
def t3(self, a, *args): "(a, ...)"
def t4(self, *args): "(...)"
def t5(self, a, *args): "(a, ...)"
def t6(self, a, b=None, *args, **kw): "(a, b=None, ..., ***)"
def test( tests ):
failed=[]
for t in tests:
expected = t.__doc__ + "\n" + t.__doc__
if get_arg_text(t) != expected:
failed.append(t)
print "%s - expected %s, but got %s" % (t, `expected`, `get_arg_text(t)`)
print "%d of %d tests failed" % (len(failed), len(tests))
tc = TC()
tests = t1, t2, t3, t4, t5, t6, \
TC, tc.t1, tc.t2, tc.t3, tc.t4, tc.t5, tc.t6
test(tests)
This diff is collapsed.
"""Class browser.
XXX TO DO:
- reparse when source changed (maybe just a button would be OK?)
(or recheck on window popup)
- add popup menu with more options (e.g. doc strings, base classes, imports)
- show function argument list? (have to do pattern matching on source)
- should the classes and methods lists also be in the module's menu bar?
- add base classes to class browser tree
"""
import os
import sys
import string
import pyclbr
# XXX Patch pyclbr with dummies if it's vintage Python 1.5.2:
if not hasattr(pyclbr, "readmodule_ex"):
pyclbr.readmodule_ex = pyclbr.readmodule
if not hasattr(pyclbr, "Function"):
class Function(pyclbr.Class):
pass
pyclbr.Function = Function
import PyShell
from WindowList import ListedToplevel
from TreeWidget import TreeNode, TreeItem, ScrolledCanvas
class ClassBrowser:
def __init__(self, flist, name, path):
# XXX This API should change, if the file doesn't end in ".py"
# XXX the code here is bogus!
self.name = name
self.file = os.path.join(path[0], self.name + ".py")
self.init(flist)
def close(self, event=None):
self.top.destroy()
self.node.destroy()
def init(self, flist):
self.flist = flist
# reset pyclbr
pyclbr._modules.clear()
# create top
self.top = top = ListedToplevel(flist.root)
top.protocol("WM_DELETE_WINDOW", self.close)
top.bind("<Escape>", self.close)
self.settitle()
top.focus_set()
# create scrolled canvas
sc = ScrolledCanvas(top, bg="white", highlightthickness=0, takefocus=1)
sc.frame.pack(expand=1, fill="both")
item = self.rootnode()
self.node = node = TreeNode(sc.canvas, None, item)
node.update()
node.expand()
def settitle(self):
self.top.wm_title("Class Browser - " + self.name)
self.top.wm_iconname("Class Browser")
def rootnode(self):
return ModuleBrowserTreeItem(self.file)
class ModuleBrowserTreeItem(TreeItem):
def __init__(self, file):
self.file = file
def GetText(self):
return os.path.basename(self.file)
def GetIconName(self):
return "python"
def GetSubList(self):
sublist = []
for name in self.listclasses():
item = ClassBrowserTreeItem(name, self.classes, self.file)
sublist.append(item)
return sublist
def OnDoubleClick(self):
if os.path.normcase(self.file[-3:]) != ".py":
return
if not os.path.exists(self.file):
return
PyShell.flist.open(self.file)
def IsExpandable(self):
return os.path.normcase(self.file[-3:]) == ".py"
def listclasses(self):
dir, file = os.path.split(self.file)
name, ext = os.path.splitext(file)
if os.path.normcase(ext) != ".py":
return []
try:
dict = pyclbr.readmodule_ex(name, [dir] + sys.path)
except ImportError, msg:
return []
items = []
self.classes = {}
for key, cl in dict.items():
if cl.module == name:
s = key
if cl.super:
supers = []
for sup in cl.super:
if type(sup) is type(''):
sname = sup
else:
sname = sup.name
if sup.module != cl.module:
sname = "%s.%s" % (sup.module, sname)
supers.append(sname)
s = s + "(%s)" % string.join(supers, ", ")
items.append((cl.lineno, s))
self.classes[s] = cl
items.sort()
list = []
for item, s in items:
list.append(s)
return list
class ClassBrowserTreeItem(TreeItem):
def __init__(self, name, classes, file):
self.name = name
self.classes = classes
self.file = file
try:
self.cl = self.classes[self.name]
except (IndexError, KeyError):
self.cl = None
self.isfunction = isinstance(self.cl, pyclbr.Function)
def GetText(self):
if self.isfunction:
return "def " + self.name + "(...)"
else:
return "class " + self.name
def GetIconName(self):
if self.isfunction:
return "python"
else:
return "folder"
def IsExpandable(self):
if self.cl:
return not not self.cl.methods
def GetSubList(self):
if not self.cl:
return []
sublist = []
for name in self.listmethods():
item = MethodBrowserTreeItem(name, self.cl, self.file)
sublist.append(item)
return sublist
def OnDoubleClick(self):
if not os.path.exists(self.file):
return
edit = PyShell.flist.open(self.file)
if hasattr(self.cl, 'lineno'):
lineno = self.cl.lineno
edit.gotoline(lineno)
def listmethods(self):
if not self.cl:
return []
items = []
for name, lineno in self.cl.methods.items():
items.append((lineno, name))
items.sort()
list = []
for item, name in items:
list.append(name)
return list
class MethodBrowserTreeItem(TreeItem):
def __init__(self, name, cl, file):
self.name = name
self.cl = cl
self.file = file
def GetText(self):
return "def " + self.name + "(...)"
def GetIconName(self):
return "python" # XXX
def IsExpandable(self):
return 0
def OnDoubleClick(self):
if not os.path.exists(self.file):
return
edit = PyShell.flist.open(self.file)
edit.gotoline(self.cl.methods[self.name])
def main():
try:
file = __file__
except NameError:
file = sys.argv[0]
if sys.argv[1:]:
file = sys.argv[1]
else:
file = sys.argv[0]
dir, file = os.path.split(file)
name = os.path.splitext(file)[0]
ClassBrowser(PyShell.flist, name, [dir])
if sys.stdin is sys.__stdin__:
mainloop()
if __name__ == "__main__":
main()
import time
import string
import re
import keyword
from Tkinter import *
from Delegator import Delegator
from IdleConf import idleconf
#$ event <<toggle-auto-coloring>>
#$ win <Control-slash>
#$ unix <Control-slash>
__debug__ = 0
def any(name, list):
return "(?P<%s>" % name + string.join(list, "|") + ")"
def make_pat():
kw = r"\b" + any("KEYWORD", keyword.kwlist) + r"\b"
comment = any("COMMENT", [r"#[^\n]*"])
sqstring = r"(\b[rR])?'[^'\\\n]*(\\.[^'\\\n]*)*'?"
dqstring = r'(\b[rR])?"[^"\\\n]*(\\.[^"\\\n]*)*"?'
sq3string = r"(\b[rR])?'''[^'\\]*((\\.|'(?!''))[^'\\]*)*(''')?"
dq3string = r'(\b[rR])?"""[^"\\]*((\\.|"(?!""))[^"\\]*)*(""")?'
string = any("STRING", [sq3string, dq3string, sqstring, dqstring])
return kw + "|" + comment + "|" + string + "|" + any("SYNC", [r"\n"])
prog = re.compile(make_pat(), re.S)
idprog = re.compile(r"\s+(\w+)", re.S)
class ColorDelegator(Delegator):
def __init__(self):
Delegator.__init__(self)
self.prog = prog
self.idprog = idprog
def setdelegate(self, delegate):
if self.delegate is not None:
self.unbind("<<toggle-auto-coloring>>")
Delegator.setdelegate(self, delegate)
if delegate is not None:
self.config_colors()
self.bind("<<toggle-auto-coloring>>", self.toggle_colorize_event)
self.notify_range("1.0", "end")
def config_colors(self):
for tag, cnf in self.tagdefs.items():
if cnf:
apply(self.tag_configure, (tag,), cnf)
self.tag_raise('sel')
cconf = idleconf.getsection('Colors')
tagdefs = {
"COMMENT": cconf.getcolor("comment"),
"KEYWORD": cconf.getcolor("keyword"),
"STRING": cconf.getcolor("string"),
"DEFINITION": cconf.getcolor("definition"),
"SYNC": cconf.getcolor("sync"),
"TODO": cconf.getcolor("todo"),
"BREAK": cconf.getcolor("break"),
# The following is used by ReplaceDialog:
"hit": cconf.getcolor("hit"),
}
def insert(self, index, chars, tags=None):
index = self.index(index)
self.delegate.insert(index, chars, tags)
self.notify_range(index, index + "+%dc" % len(chars))
def delete(self, index1, index2=None):
index1 = self.index(index1)
self.delegate.delete(index1, index2)
self.notify_range(index1)
after_id = None
allow_colorizing = 1
colorizing = 0
def notify_range(self, index1, index2=None):
self.tag_add("TODO", index1, index2)
if self.after_id:
if __debug__: print "colorizing already scheduled"
return
if self.colorizing:
self.stop_colorizing = 1
if __debug__: print "stop colorizing"
if self.allow_colorizing:
if __debug__: print "schedule colorizing"
self.after_id = self.after(1, self.recolorize)
close_when_done = None # Window to be closed when done colorizing
def close(self, close_when_done=None):
if self.after_id:
after_id = self.after_id
self.after_id = None
if __debug__: print "cancel scheduled recolorizer"
self.after_cancel(after_id)
self.allow_colorizing = 0
self.stop_colorizing = 1
if close_when_done:
if not self.colorizing:
close_when_done.destroy()
else:
self.close_when_done = close_when_done
def toggle_colorize_event(self, event):
if self.after_id:
after_id = self.after_id
self.after_id = None
if __debug__: print "cancel scheduled recolorizer"
self.after_cancel(after_id)
if self.allow_colorizing and self.colorizing:
if __debug__: print "stop colorizing"
self.stop_colorizing = 1
self.allow_colorizing = not self.allow_colorizing
if self.allow_colorizing and not self.colorizing:
self.after_id = self.after(1, self.recolorize)
if __debug__:
print "auto colorizing turned", self.allow_colorizing and "on" or "off"
return "break"
def recolorize(self):
self.after_id = None
if not self.delegate:
if __debug__: print "no delegate"
return
if not self.allow_colorizing:
if __debug__: print "auto colorizing is off"
return
if self.colorizing:
if __debug__: print "already colorizing"
return
try:
self.stop_colorizing = 0
self.colorizing = 1
if __debug__: print "colorizing..."
t0 = time.clock()
self.recolorize_main()
t1 = time.clock()
if __debug__: print "%.3f seconds" % (t1-t0)
finally:
self.colorizing = 0
if self.allow_colorizing and self.tag_nextrange("TODO", "1.0"):
if __debug__: print "reschedule colorizing"
self.after_id = self.after(1, self.recolorize)
if self.close_when_done:
top = self.close_when_done
self.close_when_done = None
top.destroy()
def recolorize_main(self):
next = "1.0"
while 1:
item = self.tag_nextrange("TODO", next)
if not item:
break
head, tail = item
self.tag_remove("SYNC", head, tail)
item = self.tag_prevrange("SYNC", head)
if item:
head = item[1]
else:
head = "1.0"
chars = ""
next = head
lines_to_get = 1
ok = 0
while not ok:
mark = next
next = self.index(mark + "+%d lines linestart" %
lines_to_get)
lines_to_get = min(lines_to_get * 2, 100)
ok = "SYNC" in self.tag_names(next + "-1c")
line = self.get(mark, next)
##print head, "get", mark, next, "->", `line`
if not line:
return
for tag in self.tagdefs.keys():
self.tag_remove(tag, mark, next)
chars = chars + line
m = self.prog.search(chars)
while m:
for key, value in m.groupdict().items():
if value:
a, b = m.span(key)
self.tag_add(key,
head + "+%dc" % a,
head + "+%dc" % b)
if value in ("def", "class"):
m1 = self.idprog.match(chars, b)
if m1:
a, b = m1.span(1)
self.tag_add("DEFINITION",
head + "+%dc" % a,
head + "+%dc" % b)
m = self.prog.search(chars, m.end())
if "SYNC" in self.tag_names(next + "-1c"):
head = next
chars = ""
else:
ok = 0
if not ok:
# We're in an inconsistent state, and the call to
# update may tell us to stop. It may also change
# the correct value for "next" (since this is a
# line.col string, not a true mark). So leave a
# crumb telling the next invocation to resume here
# in case update tells us to leave.
self.tag_add("TODO", next)
self.update()
if self.stop_colorizing:
if __debug__: print "colorizing stopped"
return
def main():
from Percolator import Percolator
root = Tk()
root.wm_protocol("WM_DELETE_WINDOW", root.quit)
text = Text(background="white")
text.pack(expand=1, fill="both")
text.focus_set()
p = Percolator(text)
d = ColorDelegator()
p.insertfilter(d)
root.mainloop()
if __name__ == "__main__":
main()
This diff is collapsed.
import os
import bdb
import traceback
from Tkinter import *
from WindowList import ListedToplevel
import StackViewer
class Debugger(bdb.Bdb):
interacting = 0
vstack = vsource = vlocals = vglobals = None
def __init__(self, pyshell):
bdb.Bdb.__init__(self)
self.pyshell = pyshell
self.make_gui()
def canonic(self, filename):
# Canonicalize filename -- called by Bdb
return os.path.normcase(os.path.abspath(filename))
def close(self, event=None):
if self.interacting:
self.top.bell()
return
if self.stackviewer:
self.stackviewer.close(); self.stackviewer = None
self.pyshell.close_debugger()
self.top.destroy()
def run(self, *args):
try:
self.interacting = 1
return apply(bdb.Bdb.run, (self,) + args)
finally:
self.interacting = 0
def user_line(self, frame):
self.interaction(frame)
def user_return(self, frame, rv):
# XXX show rv?
##self.interaction(frame)
pass
def user_exception(self, frame, info):
self.interaction(frame, info)
def make_gui(self):
pyshell = self.pyshell
self.flist = pyshell.flist
self.root = root = pyshell.root
self.top = top =ListedToplevel(root)
self.top.wm_title("Debug Control")
self.top.wm_iconname("Debug")
top.wm_protocol("WM_DELETE_WINDOW", self.close)
self.top.bind("<Escape>", self.close)
#
self.bframe = bframe = Frame(top)
self.bframe.pack(anchor="w")
self.buttons = bl = []
#
self.bcont = b = Button(bframe, text="Go", command=self.cont)
bl.append(b)
self.bstep = b = Button(bframe, text="Step", command=self.step)
bl.append(b)
self.bnext = b = Button(bframe, text="Over", command=self.next)
bl.append(b)
self.bret = b = Button(bframe, text="Out", command=self.ret)
bl.append(b)
self.bret = b = Button(bframe, text="Quit", command=self.quit)
bl.append(b)
#
for b in bl:
b.configure(state="disabled")
b.pack(side="left")
#
self.cframe = cframe = Frame(bframe)
self.cframe.pack(side="left")
#
if not self.vstack:
self.__class__.vstack = BooleanVar(top)
self.vstack.set(1)
self.bstack = Checkbutton(cframe,
text="Stack", command=self.show_stack, variable=self.vstack)
self.bstack.grid(row=0, column=0)
if not self.vsource:
self.__class__.vsource = BooleanVar(top)
##self.vsource.set(1)
self.bsource = Checkbutton(cframe,
text="Source", command=self.show_source, variable=self.vsource)
self.bsource.grid(row=0, column=1)
if not self.vlocals:
self.__class__.vlocals = BooleanVar(top)
self.vlocals.set(1)
self.blocals = Checkbutton(cframe,
text="Locals", command=self.show_locals, variable=self.vlocals)
self.blocals.grid(row=1, column=0)
if not self.vglobals:
self.__class__.vglobals = BooleanVar(top)
##self.vglobals.set(1)
self.bglobals = Checkbutton(cframe,
text="Globals", command=self.show_globals, variable=self.vglobals)
self.bglobals.grid(row=1, column=1)
#
self.status = Label(top, anchor="w")
self.status.pack(anchor="w")
self.error = Label(top, anchor="w")
self.error.pack(anchor="w", fill="x")
self.errorbg = self.error.cget("background")
#
self.fstack = Frame(top, height=1)
self.fstack.pack(expand=1, fill="both")
self.flocals = Frame(top)
self.flocals.pack(expand=1, fill="both")
self.fglobals = Frame(top, height=1)
self.fglobals.pack(expand=1, fill="both")
#
if self.vstack.get():
self.show_stack()
if self.vlocals.get():
self.show_locals()
if self.vglobals.get():
self.show_globals()
frame = None
def interaction(self, frame, info=None):
self.frame = frame
code = frame.f_code
file = code.co_filename
base = os.path.basename(file)
lineno = frame.f_lineno
#
message = "%s:%s" % (base, lineno)
if code.co_name != "?":
message = "%s: %s()" % (message, code.co_name)
self.status.configure(text=message)
#
if info:
type, value, tb = info
try:
m1 = type.__name__
except AttributeError:
m1 = "%s" % str(type)
if value is not None:
try:
m1 = "%s: %s" % (m1, str(value))
except:
pass
bg = "yellow"
else:
m1 = ""
tb = None
bg = self.errorbg
self.error.configure(text=m1, background=bg)
#
sv = self.stackviewer
if sv:
stack, i = self.get_stack(self.frame, tb)
sv.load_stack(stack, i)
#
self.show_variables(1)
#
if self.vsource.get():
self.sync_source_line()
#
for b in self.buttons:
b.configure(state="normal")
#
self.top.tkraise()
self.root.mainloop()
#
for b in self.buttons:
b.configure(state="disabled")
self.status.configure(text="")
self.error.configure(text="", background=self.errorbg)
self.frame = None
def sync_source_line(self):
frame = self.frame
if not frame:
return
code = frame.f_code
file = code.co_filename
lineno = frame.f_lineno
if file[:1] + file[-1:] != "<>" and os.path.exists(file):
edit = self.flist.open(file)
if edit:
edit.gotoline(lineno)
def cont(self):
self.set_continue()
self.root.quit()
def step(self):
self.set_step()
self.root.quit()
def next(self):
self.set_next(self.frame)
self.root.quit()
def ret(self):
self.set_return(self.frame)
self.root.quit()
def quit(self):
self.set_quit()
self.root.quit()
stackviewer = None
def show_stack(self):
if not self.stackviewer and self.vstack.get():
self.stackviewer = sv = StackViewer.StackViewer(
self.fstack, self.flist, self)
if self.frame:
stack, i = self.get_stack(self.frame, None)
sv.load_stack(stack, i)
else:
sv = self.stackviewer
if sv and not self.vstack.get():
self.stackviewer = None
sv.close()
self.fstack['height'] = 1
def show_source(self):
if self.vsource.get():
self.sync_source_line()
def show_frame(self, (frame, lineno)):
self.frame = frame
self.show_variables()
localsviewer = None
globalsviewer = None
def show_locals(self):
lv = self.localsviewer
if self.vlocals.get():
if not lv:
self.localsviewer = StackViewer.NamespaceViewer(
self.flocals, "Locals")
else:
if lv:
self.localsviewer = None
lv.close()
self.flocals['height'] = 1
self.show_variables()
def show_globals(self):
gv = self.globalsviewer
if self.vglobals.get():
if not gv:
self.globalsviewer = StackViewer.NamespaceViewer(
self.fglobals, "Globals")
else:
if gv:
self.globalsviewer = None
gv.close()
self.fglobals['height'] = 1
self.show_variables()
def show_variables(self, force=0):
lv = self.localsviewer
gv = self.globalsviewer
frame = self.frame
if not frame:
ldict = gdict = None
else:
ldict = frame.f_locals
gdict = frame.f_globals
if lv and gv and ldict is gdict:
ldict = None
if lv:
lv.load_dict(ldict, force)
if gv:
gv.load_dict(gdict, force)
def set_breakpoint_here(self, edit):
text = edit.text
filename = edit.io.filename
if not filename:
text.bell()
return
lineno = int(float(text.index("insert")))
msg = self.set_break(filename, lineno)
if msg:
text.bell()
return
text.tag_add("BREAK", "insert linestart", "insert lineend +1char")
# A literal copy of Bdb.set_break() without the print statement at the end
def set_break(self, filename, lineno, temporary=0, cond = None):
import linecache # Import as late as possible
line = linecache.getline(filename, lineno)
if not line:
return 'That line does not exist!'
if not self.breaks.has_key(filename):
self.breaks[filename] = []
list = self.breaks[filename]
if not lineno in list:
list.append(lineno)
bp = bdb.Breakpoint(filename, lineno, temporary, cond)
class Delegator:
# The cache is only used to be able to change delegates!
def __init__(self, delegate=None):
self.delegate = delegate
self.__cache = {}
def __getattr__(self, name):
attr = getattr(self.delegate, name) # May raise AttributeError
setattr(self, name, attr)
self.__cache[name] = attr
return attr
def resetcache(self):
for key in self.__cache.keys():
try:
delattr(self, key)
except AttributeError:
pass
self.__cache.clear()
def cachereport(self):
keys = self.__cache.keys()
keys.sort()
print keys
def setdelegate(self, delegate):
self.resetcache()
self.delegate = delegate
def getdelegate(self):
return self.delegate
This diff is collapsed.
"""Extension to execute a script in a separate process
David Scherer <dscherer@cmu.edu>
The ExecBinding module, a replacement for ScriptBinding, executes
programs in a separate process. Unlike previous versions, this version
communicates with the user process via an RPC protocol (see the 'protocol'
module). The user program is loaded by the 'loader' and 'Remote'
modules. Its standard output and input are directed back to the
ExecBinding class through the RPC mechanism and implemented here.
A "stop program" command is provided and bound to control-break. Closing
the output window also stops the running program.
"""
import sys
import os
import imp
import OutputWindow
import protocol
import spawn
import traceback
import tempfile
# Find Python and the loader. This should be done as early in execution
# as possible, because if the current directory or sys.path is changed
# it may no longer be possible to get correct paths for these things.
pyth_exe = spawn.hardpath( sys.executable )
load_py = spawn.hardpath( imp.find_module("loader")[1] )
# The following mechanism matches loaders up with ExecBindings that are
# trying to load something.
waiting_for_loader = []
def loader_connect(client, addr):
if waiting_for_loader:
a = waiting_for_loader.pop(0)
try:
return a.connect(client, addr)
except:
return loader_connect(client,addr)
protocol.publish('ExecBinding', loader_connect)
class ExecBinding:
keydefs = {
'<<run-complete-script>>': ['<F5>'],
'<<stop-execution>>': ['<Cancel>'], #'<Control-c>'
}
menudefs = [
('run', [None,
('Run program', '<<run-complete-script>>'),
('Stop program', '<<stop-execution>>'),
]
),
]
delegate = 1
def __init__(self, editwin):
self.editwin = editwin
self.client = None
self.temp = []
if not hasattr(editwin, 'source_window'):
self.delegate = 0
self.output = OutputWindow.OnDemandOutputWindow(editwin.flist)
self.output.close_hook = self.stopProgram
self.output.source_window = editwin
else:
if (self.editwin.source_window and
self.editwin.source_window.extensions.has_key('ExecBinding') and
not self.editwin.source_window.extensions['ExecBinding'].delegate):
delegate = self.editwin.source_window.extensions['ExecBinding']
self.run_complete_script_event = delegate.run_complete_script_event
self.stop_execution_event = delegate.stop_execution_event
def __del__(self):
self.stopProgram()
def stop_execution_event(self, event):
if self.client:
self.stopProgram()
self.write('\nProgram stopped.\n','stderr')
def run_complete_script_event(self, event):
filename = self.getfilename()
if not filename: return
filename = os.path.abspath(filename)
self.stopProgram()
self.commands = [ ('run', filename) ]
waiting_for_loader.append(self)
spawn.spawn( pyth_exe, load_py )
def connect(self, client, addr):
# Called by loader_connect() above. It is remotely possible that
# we get connected to two loaders if the user is running the
# program repeatedly in a short span of time. In this case, we
# simply return None, refusing to connect and letting the redundant
# loader die.
if self.client: return None
self.client = client
client.set_close_hook( self.connect_lost )
title = self.editwin.short_title()
if title:
self.output.set_title(title + " Output")
else:
self.output.set_title("Output")
self.output.write('\n',"stderr")
self.output.scroll_clear()
return self
def connect_lost(self):
# Called by the client's close hook when the loader closes its
# socket.
# We print a disconnect message only if the output window is already
# open.
if self.output.owin and self.output.owin.text:
self.output.owin.interrupt()
self.output.write("\nProgram disconnected.\n","stderr")
for t in self.temp:
try:
os.remove(t)
except:
pass
self.temp = []
self.client = None
def get_command(self):
# Called by Remote to find out what it should be executing.
# Later this will be used to implement debugging, interactivity, etc.
if self.commands:
return self.commands.pop(0)
return ('finish',)
def program_exception(self, type, value, tb, first, last):
if type == SystemExit: return 0
for i in range(len(tb)):
filename, lineno, name, line = tb[i]
if filename in self.temp:
filename = 'Untitled'
tb[i] = filename, lineno, name, line
list = traceback.format_list(tb[first:last])
exc = traceback.format_exception_only( type, value )
self.write('Traceback (innermost last)\n', 'stderr')
for i in (list+exc):
self.write(i, 'stderr')
self.commands = []
return 1
def write(self, text, tag):
self.output.write(text,tag)
def readline(self):
return self.output.readline()
def stopProgram(self):
if self.client:
self.client.close()
self.client = None
def getfilename(self):
# Save all files which have been named, because they might be modules
for edit in self.editwin.flist.inversedict.keys():
if edit.io and edit.io.filename and not edit.get_saved():
edit.io.save(None)
# Experimental: execute unnamed buffer
if not self.editwin.io.filename:
filename = os.path.normcase(os.path.abspath(tempfile.mktemp()))
self.temp.append(filename)
if self.editwin.io.writefile(filename):
return filename
# If the file isn't save, we save it. If it doesn't have a filename,
# the user will be prompted.
if self.editwin.io and not self.editwin.get_saved():
self.editwin.io.save(None)
# If the file *still* isn't saved, we give up.
if not self.editwin.get_saved():
return
return self.editwin.io.filename
# changes by dscherer@cmu.edu
# - FileList.open() takes an optional 3rd parameter action, which is
# called instead of creating a new EditorWindow. This enables
# things like 'open in same window'.
import os
from Tkinter import *
import tkMessageBox
import WindowList
#$ event <<open-new-window>>
#$ win <Control-n>
#$ unix <Control-x><Control-n>
# (This is labeled as 'Exit'in the File menu)
#$ event <<close-all-windows>>
#$ win <Control-q>
#$ unix <Control-x><Control-c>
class FileList:
from EditorWindow import EditorWindow
EditorWindow.Toplevel = WindowList.ListedToplevel # XXX Patch it!
def __init__(self, root):
self.root = root
self.dict = {}
self.inversedict = {}
self.vars = {} # For EditorWindow.getrawvar (shared Tcl variables)
def goodname(self, filename):
filename = self.canonize(filename)
key = os.path.normcase(filename)
if self.dict.has_key(key):
edit = self.dict[key]
filename = edit.io.filename or filename
return filename
def open(self, filename, action=None):
assert filename
filename = self.canonize(filename)
if os.path.isdir(filename):
tkMessageBox.showerror(
"Is A Directory",
"The path %s is a directory." % `filename`,
master=self.root)
return None
key = os.path.normcase(filename)
if self.dict.has_key(key):
edit = self.dict[key]
edit.wakeup()
return edit
if not os.path.exists(filename):
tkMessageBox.showinfo(
"New File",
"Opening non-existent file %s" % `filename`,
master=self.root)
if action is None:
return self.EditorWindow(self, filename, key)
else:
return action(filename)
def new(self):
return self.EditorWindow(self)
def new_callback(self, event):
self.new()
return "break"
def close_all_callback(self, event):
for edit in self.inversedict.keys():
reply = edit.close()
if reply == "cancel":
break
return "break"
def close_edit(self, edit):
try:
key = self.inversedict[edit]
except KeyError:
print "Don't know this EditorWindow object. (close)"
return
if key:
del self.dict[key]
del self.inversedict[edit]
if not self.inversedict:
self.root.quit()
def filename_changed_edit(self, edit):
edit.saved_change_hook()
try:
key = self.inversedict[edit]
except KeyError:
print "Don't know this EditorWindow object. (rename)"
return
filename = edit.io.filename
if not filename:
if key:
del self.dict[key]
self.inversedict[edit] = None
return
filename = self.canonize(filename)
newkey = os.path.normcase(filename)
if newkey == key:
return
if self.dict.has_key(newkey):
conflict = self.dict[newkey]
self.inversedict[conflict] = None
tkMessageBox.showerror(
"Name Conflict",
"You now have multiple edit windows open for %s" % `filename`,
master=self.root)
self.dict[newkey] = edit
self.inversedict[edit] = newkey
if key:
try:
del self.dict[key]
except KeyError:
pass
def canonize(self, filename):
if not os.path.isabs(filename):
try:
pwd = os.getcwd()
except os.error:
pass
else:
filename = os.path.join(pwd, filename)
return os.path.normpath(filename)
def test():
from EditorWindow import fixwordbreaks
import sys
root = Tk()
fixwordbreaks(root)
root.withdraw()
flist = FileList(root)
if sys.argv[1:]:
for filename in sys.argv[1:]:
flist.open(filename)
else:
flist.new()
if flist.inversedict:
root.mainloop()
if __name__ == '__main__':
test()
# Extension to format a paragraph
# Does basic, standard text formatting, and also understands Python
# comment blocks. Thus, for editing Python source code, this
# extension is really only suitable for reformatting these comment
# blocks or triple-quoted strings.
# Known problems with comment reformatting:
# * If there is a selection marked, and the first line of the
# selection is not complete, the block will probably not be detected
# as comments, and will have the normal "text formatting" rules
# applied.
# * If a comment block has leading whitespace that mixes tabs and
# spaces, they will not be considered part of the same block.
# * Fancy comments, like this bulleted list, arent handled :-)
import string
import re
class FormatParagraph:
menudefs = [
('format', [ # /s/edit/format dscherer@cmu.edu
('Format Paragraph', '<<format-paragraph>>'),
])
]
keydefs = {
'<<format-paragraph>>': ['<Alt-q>'],
}
unix_keydefs = {
'<<format-paragraph>>': ['<Meta-q>'],
}
def __init__(self, editwin):
self.editwin = editwin
def close(self):
self.editwin = None
def format_paragraph_event(self, event):
text = self.editwin.text
first, last = self.editwin.get_selection_indices()
if first and last:
data = text.get(first, last)
comment_header = ''
else:
first, last, comment_header, data = \
find_paragraph(text, text.index("insert"))
if comment_header:
# Reformat the comment lines - convert to text sans header.
lines = string.split(data, "\n")
lines = map(lambda st, l=len(comment_header): st[l:], lines)
data = string.join(lines, "\n")
# Reformat to 70 chars or a 20 char width, whichever is greater.
format_width = max(70-len(comment_header), 20)
newdata = reformat_paragraph(data, format_width)
# re-split and re-insert the comment header.
newdata = string.split(newdata, "\n")
# If the block ends in a \n, we dont want the comment
# prefix inserted after it. (Im not sure it makes sense to
# reformat a comment block that isnt made of complete
# lines, but whatever!) Can't think of a clean soltution,
# so we hack away
block_suffix = ""
if not newdata[-1]:
block_suffix = "\n"
newdata = newdata[:-1]
builder = lambda item, prefix=comment_header: prefix+item
newdata = string.join(map(builder, newdata), '\n') + block_suffix
else:
# Just a normal text format
newdata = reformat_paragraph(data)
text.tag_remove("sel", "1.0", "end")
if newdata != data:
text.mark_set("insert", first)
text.undo_block_start()
text.delete(first, last)
text.insert(first, newdata)
text.undo_block_stop()
else:
text.mark_set("insert", last)
text.see("insert")
def find_paragraph(text, mark):
lineno, col = map(int, string.split(mark, "."))
line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
while text.compare("%d.0" % lineno, "<", "end") and is_all_white(line):
lineno = lineno + 1
line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
first_lineno = lineno
comment_header = get_comment_header(line)
comment_header_len = len(comment_header)
while get_comment_header(line)==comment_header and \
not is_all_white(line[comment_header_len:]):
lineno = lineno + 1
line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
last = "%d.0" % lineno
# Search back to beginning of paragraph
lineno = first_lineno - 1
line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
while lineno > 0 and \
get_comment_header(line)==comment_header and \
not is_all_white(line[comment_header_len:]):
lineno = lineno - 1
line = text.get("%d.0" % lineno, "%d.0 lineend" % lineno)
first = "%d.0" % (lineno+1)
return first, last, comment_header, text.get(first, last)
def reformat_paragraph(data, limit=70):
lines = string.split(data, "\n")
i = 0
n = len(lines)
while i < n and is_all_white(lines[i]):
i = i+1
if i >= n:
return data
indent1 = get_indent(lines[i])
if i+1 < n and not is_all_white(lines[i+1]):
indent2 = get_indent(lines[i+1])
else:
indent2 = indent1
new = lines[:i]
partial = indent1
while i < n and not is_all_white(lines[i]):
# XXX Should take double space after period (etc.) into account
words = re.split("(\s+)", lines[i])
for j in range(0, len(words), 2):
word = words[j]
if not word:
continue # Can happen when line ends in whitespace
if len(string.expandtabs(partial + word)) > limit and \
partial != indent1:
new.append(string.rstrip(partial))
partial = indent2
partial = partial + word + " "
if j+1 < len(words) and words[j+1] != " ":
partial = partial + " "
i = i+1
new.append(string.rstrip(partial))
# XXX Should reformat remaining paragraphs as well
new.extend(lines[i:])
return string.join(new, "\n")
def is_all_white(line):
return re.match(r"^\s*$", line) is not None
def get_indent(line):
return re.match(r"^(\s*)", line).group()
def get_comment_header(line):
m = re.match(r"^(\s*#*)", line)
if m is None: return ""
return m.group(1)
from repr import Repr
from Tkinter import *
class FrameViewer:
def __init__(self, root, frame):
self.root = root
self.frame = frame
self.top = Toplevel(self.root)
self.repr = Repr()
self.repr.maxstring = 60
self.load_variables()
def load_variables(self):
row = 0
if self.frame.f_locals is not self.frame.f_globals:
l = Label(self.top, text="Local Variables",
borderwidth=2, relief="raised")
l.grid(row=row, column=0, columnspan=2, sticky="ew")
row = self.load_names(self.frame.f_locals, row+1)
l = Label(self.top, text="Global Variables",
borderwidth=2, relief="raised")
l.grid(row=row, column=0, columnspan=2, sticky="ew")
row = self.load_names(self.frame.f_globals, row+1)
def load_names(self, dict, row):
names = dict.keys()
names.sort()
for name in names:
value = dict[name]
svalue = self.repr.repr(value)
l = Label(self.top, text=name)
l.grid(row=row, column=0, sticky="w")
l = Entry(self.top, width=60, borderwidth=0)
l.insert(0, svalue)
l.grid(row=row, column=1, sticky="w")
row = row+1
return row
import string
import os
import re
import fnmatch
import sys
from Tkinter import *
import tkMessageBox
import SearchEngine
from SearchDialogBase import SearchDialogBase
def grep(text, io=None, flist=None):
root = text._root()
engine = SearchEngine.get(root)
if not hasattr(engine, "_grepdialog"):
engine._grepdialog = GrepDialog(root, engine, flist)
dialog = engine._grepdialog
dialog.open(io)
class GrepDialog(SearchDialogBase):
title = "Find in Files Dialog"
icon = "Grep"
needwrapbutton = 0
def __init__(self, root, engine, flist):
SearchDialogBase.__init__(self, root, engine)
self.flist = flist
self.globvar = StringVar(root)
self.recvar = BooleanVar(root)
def open(self, io=None):
SearchDialogBase.open(self, None)
if io:
path = io.filename or ""
else:
path = ""
dir, base = os.path.split(path)
head, tail = os.path.splitext(base)
if not tail:
tail = ".py"
self.globvar.set(os.path.join(dir, "*" + tail))
def create_entries(self):
SearchDialogBase.create_entries(self)
self.globent = self.make_entry("In files:", self.globvar)
def create_other_buttons(self):
f = self.make_frame()
btn = Checkbutton(f, anchor="w",
variable=self.recvar,
text="Recurse down subdirectories")
btn.pack(side="top", fill="both")
btn.select()
def create_command_buttons(self):
SearchDialogBase.create_command_buttons(self)
self.make_button("Search Files", self.default_command, 1)
def default_command(self, event=None):
prog = self.engine.getprog()
if not prog:
return
path = self.globvar.get()
if not path:
self.top.bell()
return
from OutputWindow import OutputWindow
save = sys.stdout
try:
sys.stdout = OutputWindow(self.flist)
self.grep_it(prog, path)
finally:
sys.stdout = save
def grep_it(self, prog, path):
dir, base = os.path.split(path)
list = self.findfiles(dir, base, self.recvar.get())
list.sort()
self.close()
pat = self.engine.getpat()
print "Searching %s in %s ..." % (`pat`, path)
hits = 0
for fn in list:
try:
f = open(fn)
except IOError, msg:
print msg
continue
lineno = 0
while 1:
block = f.readlines(100000)
if not block:
break
for line in block:
lineno = lineno + 1
if line[-1:] == '\n':
line = line[:-1]
if prog.search(line):
sys.stdout.write("%s: %s: %s\n" % (fn, lineno, line))
hits = hits + 1
if hits:
if hits == 1:
s = ""
else:
s = "s"
print "Found", hits, "hit%s." % s
print "(Hint: right-click to open locations.)"
else:
print "No hits."
def findfiles(self, dir, base, rec):
try:
names = os.listdir(dir or os.curdir)
except os.error, msg:
print msg
return []
list = []
subdirs = []
for name in names:
fn = os.path.join(dir, name)
if os.path.isdir(fn):
subdirs.append(fn)
else:
if fnmatch.fnmatch(name, base):
list.append(fn)
if rec:
for subdir in subdirs:
list.extend(self.findfiles(subdir, base, rec))
return list
def close(self, event=None):
if self.top:
self.top.grab_release()
self.top.withdraw()
# changes by dscherer@cmu.edu
# - IOBinding.open() replaces the current window with the opened file,
# if the current window is both unmodified and unnamed
# - IOBinding.loadfile() interprets Windows, UNIX, and Macintosh
# end-of-line conventions, instead of relying on the standard library,
# which will only understand the local convention.
import os
import tkFileDialog
import tkMessageBox
import re
#$ event <<open-window-from-file>>
#$ win <Control-o>
#$ unix <Control-x><Control-f>
#$ event <<save-window>>
#$ win <Control-s>
#$ unix <Control-x><Control-s>
#$ event <<save-window-as-file>>
#$ win <Alt-s>
#$ unix <Control-x><Control-w>
#$ event <<save-copy-of-window-as-file>>
#$ win <Alt-Shift-s>
#$ unix <Control-x><w>
class IOBinding:
def __init__(self, editwin):
self.editwin = editwin
self.text = editwin.text
self.__id_open = self.text.bind("<<open-window-from-file>>", self.open)
self.__id_save = self.text.bind("<<save-window>>", self.save)
self.__id_saveas = self.text.bind("<<save-window-as-file>>",
self.save_as)
self.__id_savecopy = self.text.bind("<<save-copy-of-window-as-file>>",
self.save_a_copy)
def close(self):
# Undo command bindings
self.text.unbind("<<open-window-from-file>>", self.__id_open)
self.text.unbind("<<save-window>>", self.__id_save)
self.text.unbind("<<save-window-as-file>>",self.__id_saveas)
self.text.unbind("<<save-copy-of-window-as-file>>", self.__id_savecopy)
# Break cycles
self.editwin = None
self.text = None
self.filename_change_hook = None
def get_saved(self):
return self.editwin.get_saved()
def set_saved(self, flag):
self.editwin.set_saved(flag)
def reset_undo(self):
self.editwin.reset_undo()
filename_change_hook = None
def set_filename_change_hook(self, hook):
self.filename_change_hook = hook
filename = None
def set_filename(self, filename):
self.filename = filename
self.set_saved(1)
if self.filename_change_hook:
self.filename_change_hook()
def open(self, event):
if self.editwin.flist:
filename = self.askopenfile()
if filename:
# if the current window has no filename and hasn't been
# modified, we replace it's contents (no loss). Otherwise
# we open a new window.
if not self.filename and self.get_saved():
self.editwin.flist.open(filename, self.loadfile)
else:
self.editwin.flist.open(filename)
else:
self.text.focus_set()
return "break"
# Code for use outside IDLE:
if self.get_saved():
reply = self.maybesave()
if reply == "cancel":
self.text.focus_set()
return "break"
filename = self.askopenfile()
if filename:
self.loadfile(filename)
else:
self.text.focus_set()
return "break"
def loadfile(self, filename):
try:
# open the file in binary mode so that we can handle
# end-of-line convention ourselves.
f = open(filename,'rb')
chars = f.read()
f.close()
except IOError, msg:
tkMessageBox.showerror("I/O Error", str(msg), master=self.text)
return 0
# We now convert all end-of-lines to '\n's
eol = r"(\r\n)|\n|\r" # \r\n (Windows), \n (UNIX), or \r (Mac)
chars = re.compile( eol ).sub( r"\n", chars )
self.text.delete("1.0", "end")
self.set_filename(None)
self.text.insert("1.0", chars)
self.reset_undo()
self.set_filename(filename)
self.text.mark_set("insert", "1.0")
self.text.see("insert")
return 1
def maybesave(self):
if self.get_saved():
return "yes"
message = "Do you want to save %s before closing?" % (
self.filename or "this untitled document")
m = tkMessageBox.Message(
title="Save On Close",
message=message,
icon=tkMessageBox.QUESTION,
type=tkMessageBox.YESNOCANCEL,
master=self.text)
reply = m.show()
if reply == "yes":
self.save(None)
if not self.get_saved():
reply = "cancel"
self.text.focus_set()
return reply
def save(self, event):
if not self.filename:
self.save_as(event)
else:
if self.writefile(self.filename):
self.set_saved(1)
self.text.focus_set()
return "break"
def save_as(self, event):
filename = self.asksavefile()
if filename:
if self.writefile(filename):
self.set_filename(filename)
self.set_saved(1)
self.text.focus_set()
return "break"
def save_a_copy(self, event):
filename = self.asksavefile()
if filename:
self.writefile(filename)
self.text.focus_set()
return "break"
def writefile(self, filename):
self.fixlastline()
try:
f = open(filename, "w")
chars = self.text.get("1.0", "end-1c")
f.write(chars)
f.close()
## print "saved to", `filename`
return 1
except IOError, msg:
tkMessageBox.showerror("I/O Error", str(msg),
master=self.text)
return 0
def fixlastline(self):
c = self.text.get("end-2c")
if c != '\n':
self.text.insert("end-1c", "\n")
opendialog = None
savedialog = None
filetypes = [
("Python and text files", "*.py *.pyw *.txt", "TEXT"),
("All text files", "*", "TEXT"),
("All files", "*"),
]
def askopenfile(self):
dir, base = self.defaultfilename("open")
if not self.opendialog:
self.opendialog = tkFileDialog.Open(master=self.text,
filetypes=self.filetypes)
return self.opendialog.show(initialdir=dir, initialfile=base)
def defaultfilename(self, mode="open"):
if self.filename:
return os.path.split(self.filename)
else:
try:
pwd = os.getcwd()
except os.error:
pwd = ""
return pwd, ""
def asksavefile(self):
dir, base = self.defaultfilename("save")
if not self.savedialog:
self.savedialog = tkFileDialog.SaveAs(master=self.text,
filetypes=self.filetypes)
return self.savedialog.show(initialdir=dir, initialfile=base)
def test():
from Tkinter import *
root = Tk()
class MyEditWin:
def __init__(self, text):
self.text = text
self.flist = None
self.text.bind("<Control-o>", self.open)
self.text.bind("<Control-s>", self.save)
self.text.bind("<Alt-s>", self.save_as)
self.text.bind("<Alt-z>", self.save_a_copy)
def get_saved(self): return 0
def set_saved(self, flag): pass
def reset_undo(self): pass
def open(self, event):
self.text.event_generate("<<open-window-from-file>>")
def save(self, event):
self.text.event_generate("<<save-window>>")
def save_as(self, event):
self.text.event_generate("<<save-window-as-file>>")
def save_a_copy(self, event):
self.text.event_generate("<<save-copy-of-window-as-file>>")
text = Text(root)
text.pack()
text.focus_set()
editwin = MyEditWin(text)
io = IOBinding(editwin)
root.mainloop()
if __name__ == "__main__":
test()
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
"""Provides access to configuration information"""
import os
import sys
from ConfigParser import ConfigParser, NoOptionError, NoSectionError
class IdleConfParser(ConfigParser):
# these conf sections do not define extensions!
builtin_sections = {}
for section in ('EditorWindow', 'Colors'):
builtin_sections[section] = section
def getcolor(self, sec, name):
"""Return a dictionary with foreground and background colors
The return value is appropriate for passing to Tkinter in, e.g.,
a tag_config call.
"""
fore = self.getdef(sec, name + "-foreground")
back = self.getdef(sec, name + "-background")
return {"foreground": fore,
"background": back}
def getdef(self, sec, options, raw=0, vars=None, default=None):
"""Get an option value for given section or return default"""
try:
return self.get(sec, options, raw, vars)
except (NoSectionError, NoOptionError):
return default
def getsection(self, section):
"""Return a SectionConfigParser object"""
return SectionConfigParser(section, self)
def getextensions(self):
exts = []
for sec in self.sections():
if self.builtin_sections.has_key(sec):
continue
# enable is a bool, but it may not be defined
if self.getdef(sec, 'enable') != '0':
exts.append(sec)
return exts
def reload(self):
global idleconf
idleconf = IdleConfParser()
load(_dir) # _dir is a global holding the last directory loaded
class SectionConfigParser:
"""A ConfigParser object specialized for one section
This class has all the get methods that a regular ConfigParser does,
but without requiring a section argument.
"""
def __init__(self, section, config):
self.section = section
self.config = config
def options(self):
return self.config.options(self.section)
def get(self, options, raw=0, vars=None):
return self.config.get(self.section, options, raw, vars)
def getdef(self, options, raw=0, vars=None, default=None):
return self.config.getdef(self.section, options, raw, vars, default)
def getint(self, option):
return self.config.getint(self.section, option)
def getfloat(self, option):
return self.config.getint(self.section, option)
def getboolean(self, option):
return self.config.getint(self.section, option)
def getcolor(self, option):
return self.config.getcolor(self.section, option)
def load(dir):
"""Load IDLE configuration files based on IDLE install in dir
Attempts to load two config files:
dir/config.txt
dir/config-[win/mac/unix].txt
dir/config-%(sys.platform)s.txt
~/.idle
"""
global _dir
_dir = dir
if sys.platform[:3] == 'win':
genplatfile = os.path.join(dir, "config-win.txt")
# XXX don't know what the platform string is on a Mac
elif sys.platform[:3] == 'mac':
genplatfile = os.path.join(dir, "config-mac.txt")
else:
genplatfile = os.path.join(dir, "config-unix.txt")
platfile = os.path.join(dir, "config-%s.txt" % sys.platform)
try:
homedir = os.environ['HOME']
except KeyError:
homedir = os.getcwd()
idleconf.read((os.path.join(dir, "config.txt"), genplatfile, platfile,
os.path.join(homedir, ".idle")))
idleconf = IdleConfParser()
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
# Dummy file to make this a potential package.
[EditorWindow]
font-name= courier
font-size= 10
[EditorWindow]
font-name: courier new
font-size: 10
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
@echo off
rem Working IDLE bat for Windows - uses start instead of absolute pathname
start idle.pyw %1 %2 %3 %4 %5 %6 %7 %8 %9
#! /usr/bin/env python
import os
import sys
import IdleConf
idle_dir = os.path.split(sys.argv[0])[0]
IdleConf.load(idle_dir)
# defer importing Pyshell until IdleConf is loaded
import PyShell
PyShell.main()
This diff is collapsed.
IDLE_VERSION = "0.5"
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment