Initial revision
This commit is contained in:
parent
33a6da9971
commit
7aced17437
|
@ -0,0 +1,92 @@
|
|||
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:]
|
|
@ -0,0 +1,554 @@
|
|||
import string
|
||||
#from Tkinter import TclError
|
||||
#import tkMessageBox
|
||||
#import tkSimpleDialog
|
||||
|
||||
###$ event <<newline-and-indent>>
|
||||
###$ win <Key-Return>
|
||||
###$ win <KP_Enter>
|
||||
###$ unix <Key-Return>
|
||||
###$ unix <KP_Enter>
|
||||
|
||||
###$ event <<indent-region>>
|
||||
###$ win <Control-bracketright>
|
||||
###$ unix <Alt-bracketright>
|
||||
###$ unix <Control-bracketright>
|
||||
|
||||
###$ event <<dedent-region>>
|
||||
###$ win <Control-bracketleft>
|
||||
###$ unix <Alt-bracketleft>
|
||||
###$ unix <Control-bracketleft>
|
||||
|
||||
###$ event <<comment-region>>
|
||||
###$ win <Alt-Key-3>
|
||||
###$ unix <Alt-Key-3>
|
||||
|
||||
###$ event <<uncomment-region>>
|
||||
###$ win <Alt-Key-4>
|
||||
###$ unix <Alt-Key-4>
|
||||
|
||||
###$ event <<tabify-region>>
|
||||
###$ win <Alt-Key-5>
|
||||
###$ unix <Alt-Key-5>
|
||||
|
||||
###$ event <<untabify-region>>
|
||||
###$ win <Alt-Key-6>
|
||||
###$ unix <Alt-Key-6>
|
||||
|
||||
import PyParse
|
||||
|
||||
class AutoIndent:
|
||||
|
||||
menudefs = [
|
||||
('format', [ # /s/edit/format dscherer@cmu.edu
|
||||
None,
|
||||
('_Indent region', '<<indent-region>>'),
|
||||
('_Dedent region', '<<dedent-region>>'),
|
||||
('Comment _out region', '<<comment-region>>'),
|
||||
('U_ncomment region', '<<uncomment-region>>'),
|
||||
('Tabify region', '<<tabify-region>>'),
|
||||
('Untabify region', '<<untabify-region>>'),
|
||||
('Toggle tabs', '<<toggle-tabs>>'),
|
||||
('New indent width', '<<change-indentwidth>>'),
|
||||
]),
|
||||
]
|
||||
|
||||
keydefs = {
|
||||
'<<smart-backspace>>': ['<Key-BackSpace>'],
|
||||
'<<newline-and-indent>>': ['<Key-Return>', '<KP_Enter>'],
|
||||
'<<smart-indent>>': ['<Key-Tab>']
|
||||
}
|
||||
|
||||
windows_keydefs = {
|
||||
'<<indent-region>>': ['<Control-bracketright>'],
|
||||
'<<dedent-region>>': ['<Shift-Tab>', # dscherer@cmu.edu
|
||||
'<Control-bracketleft>'],
|
||||
'<<comment-region>>': ['<Alt-Key-3>'],
|
||||
'<<uncomment-region>>': ['<Alt-Key-4>'],
|
||||
'<<tabify-region>>': ['<Alt-Key-5>'],
|
||||
'<<untabify-region>>': ['<Alt-Key-6>'],
|
||||
'<<toggle-tabs>>': ['<Alt-Key-t>'],
|
||||
'<<change-indentwidth>>': ['<Alt-Key-u>'],
|
||||
}
|
||||
|
||||
unix_keydefs = {
|
||||
'<<indent-region>>': ['<Alt-bracketright>',
|
||||
'<Meta-bracketright>',
|
||||
'<Control-bracketright>'],
|
||||
'<<dedent-region>>': ['<Alt-bracketleft>',
|
||||
'<Meta-bracketleft>',
|
||||
'<Control-bracketleft>'],
|
||||
'<<comment-region>>': ['<Alt-Key-3>', '<Meta-Key-3>'],
|
||||
'<<uncomment-region>>': ['<Alt-Key-4>', '<Meta-Key-4>'],
|
||||
'<<tabify-region>>': ['<Alt-Key-5>', '<Meta-Key-5>'],
|
||||
'<<untabify-region>>': ['<Alt-Key-6>', '<Meta-Key-6>'],
|
||||
'<<toggle-tabs>>': ['<Alt-Key-t>'],
|
||||
'<<change-indentwidth>>': ['<Alt-Key-u>'],
|
||||
}
|
||||
|
||||
# usetabs true -> literal tab characters are used by indent and
|
||||
# dedent cmds, possibly mixed with spaces if
|
||||
# indentwidth is not a multiple of tabwidth
|
||||
# false -> tab characters are converted to spaces by indent
|
||||
# and dedent cmds, and ditto TAB keystrokes
|
||||
# indentwidth is the number of characters per logical indent level.
|
||||
# tabwidth is the display width of a literal tab character.
|
||||
# CAUTION: telling Tk to use anything other than its default
|
||||
# tab setting causes it to use an entirely different tabbing algorithm,
|
||||
# treating tab stops as fixed distances from the left margin.
|
||||
# Nobody expects this, so for now tabwidth should never be changed.
|
||||
usetabs = 1
|
||||
indentwidth = 4
|
||||
tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed
|
||||
|
||||
# If context_use_ps1 is true, parsing searches back for a ps1 line;
|
||||
# else searches for a popular (if, def, ...) Python stmt.
|
||||
context_use_ps1 = 0
|
||||
|
||||
# When searching backwards for a reliable place to begin parsing,
|
||||
# first start num_context_lines[0] lines back, then
|
||||
# num_context_lines[1] lines back if that didn't work, and so on.
|
||||
# The last value should be huge (larger than the # of lines in a
|
||||
# conceivable file).
|
||||
# Making the initial values larger slows things down more often.
|
||||
num_context_lines = 50, 500, 5000000
|
||||
|
||||
def __init__(self, editwin):
|
||||
self.editwin = editwin
|
||||
self.text = editwin.text
|
||||
|
||||
def config(self, **options):
|
||||
for key, value in options.items():
|
||||
if key == 'usetabs':
|
||||
self.usetabs = value
|
||||
elif key == 'indentwidth':
|
||||
self.indentwidth = value
|
||||
elif key == 'tabwidth':
|
||||
self.tabwidth = value
|
||||
elif key == 'context_use_ps1':
|
||||
self.context_use_ps1 = value
|
||||
else:
|
||||
raise KeyError, "bad option name: %s" % `key`
|
||||
|
||||
# If ispythonsource and guess are true, guess a good value for
|
||||
# indentwidth based on file content (if possible), and if
|
||||
# indentwidth != tabwidth set usetabs false.
|
||||
# In any case, adjust the Text widget's view of what a tab
|
||||
# character means.
|
||||
|
||||
def set_indentation_params(self, ispythonsource, guess=1):
|
||||
if guess and ispythonsource:
|
||||
i = self.guess_indent()
|
||||
if 2 <= i <= 8:
|
||||
self.indentwidth = i
|
||||
if self.indentwidth != self.tabwidth:
|
||||
self.usetabs = 0
|
||||
|
||||
self.editwin.set_tabwidth(self.tabwidth)
|
||||
|
||||
def smart_backspace_event(self, event):
|
||||
text = self.text
|
||||
first, last = self.editwin.get_selection_indices()
|
||||
if first and last:
|
||||
text.delete(first, last)
|
||||
text.mark_set("insert", first)
|
||||
return "break"
|
||||
# Delete whitespace left, until hitting a real char or closest
|
||||
# preceding virtual tab stop.
|
||||
chars = text.get("insert linestart", "insert")
|
||||
if chars == '':
|
||||
if text.compare("insert", ">", "1.0"):
|
||||
# easy: delete preceding newline
|
||||
text.delete("insert-1c")
|
||||
else:
|
||||
text.bell() # at start of buffer
|
||||
return "break"
|
||||
if chars[-1] not in " \t":
|
||||
# easy: delete preceding real char
|
||||
text.delete("insert-1c")
|
||||
return "break"
|
||||
# Ick. It may require *inserting* spaces if we back up over a
|
||||
# tab character! This is written to be clear, not fast.
|
||||
expand, tabwidth = string.expandtabs, self.tabwidth
|
||||
have = len(expand(chars, tabwidth))
|
||||
assert have > 0
|
||||
want = int((have - 1) / self.indentwidth) * self.indentwidth
|
||||
ncharsdeleted = 0
|
||||
while 1:
|
||||
chars = chars[:-1]
|
||||
ncharsdeleted = ncharsdeleted + 1
|
||||
have = len(expand(chars, tabwidth))
|
||||
if have <= want or chars[-1] not in " \t":
|
||||
break
|
||||
text.undo_block_start()
|
||||
text.delete("insert-%dc" % ncharsdeleted, "insert")
|
||||
if have < want:
|
||||
text.insert("insert", ' ' * (want - have))
|
||||
text.undo_block_stop()
|
||||
return "break"
|
||||
|
||||
def smart_indent_event(self, event):
|
||||
# if intraline selection:
|
||||
# delete it
|
||||
# elif multiline selection:
|
||||
# do indent-region & return
|
||||
# indent one level
|
||||
text = self.text
|
||||
first, last = self.editwin.get_selection_indices()
|
||||
text.undo_block_start()
|
||||
try:
|
||||
if first and last:
|
||||
if index2line(first) != index2line(last):
|
||||
return self.indent_region_event(event)
|
||||
text.delete(first, last)
|
||||
text.mark_set("insert", first)
|
||||
prefix = text.get("insert linestart", "insert")
|
||||
raw, effective = classifyws(prefix, self.tabwidth)
|
||||
if raw == len(prefix):
|
||||
# only whitespace to the left
|
||||
self.reindent_to(effective + self.indentwidth)
|
||||
else:
|
||||
if self.usetabs:
|
||||
pad = '\t'
|
||||
else:
|
||||
effective = len(string.expandtabs(prefix,
|
||||
self.tabwidth))
|
||||
n = self.indentwidth
|
||||
pad = ' ' * (n - effective % n)
|
||||
text.insert("insert", pad)
|
||||
text.see("insert")
|
||||
return "break"
|
||||
finally:
|
||||
text.undo_block_stop()
|
||||
|
||||
def newline_and_indent_event(self, event):
|
||||
text = self.text
|
||||
first, last = self.editwin.get_selection_indices()
|
||||
text.undo_block_start()
|
||||
try:
|
||||
if first and last:
|
||||
text.delete(first, last)
|
||||
text.mark_set("insert", first)
|
||||
line = text.get("insert linestart", "insert")
|
||||
i, n = 0, len(line)
|
||||
while i < n and line[i] in " \t":
|
||||
i = i+1
|
||||
if i == n:
|
||||
# the cursor is in or at leading indentation; just inject
|
||||
# an empty line at the start
|
||||
text.insert("insert linestart", '\n')
|
||||
return "break"
|
||||
indent = line[:i]
|
||||
# strip whitespace before insert point
|
||||
i = 0
|
||||
while line and line[-1] in " \t":
|
||||
line = line[:-1]
|
||||
i = i+1
|
||||
if i:
|
||||
text.delete("insert - %d chars" % i, "insert")
|
||||
# strip whitespace after insert point
|
||||
while text.get("insert") in " \t":
|
||||
text.delete("insert")
|
||||
# start new line
|
||||
text.insert("insert", '\n')
|
||||
|
||||
# adjust indentation for continuations and block
|
||||
# open/close first need to find the last stmt
|
||||
lno = index2line(text.index('insert'))
|
||||
y = PyParse.Parser(self.indentwidth, self.tabwidth)
|
||||
for context in self.num_context_lines:
|
||||
startat = max(lno - context, 1)
|
||||
startatindex = `startat` + ".0"
|
||||
rawtext = text.get(startatindex, "insert")
|
||||
y.set_str(rawtext)
|
||||
bod = y.find_good_parse_start(
|
||||
self.context_use_ps1,
|
||||
self._build_char_in_string_func(startatindex))
|
||||
if bod is not None or startat == 1:
|
||||
break
|
||||
y.set_lo(bod or 0)
|
||||
c = y.get_continuation_type()
|
||||
if c != PyParse.C_NONE:
|
||||
# The current stmt hasn't ended yet.
|
||||
if c == PyParse.C_STRING:
|
||||
# inside a string; just mimic the current indent
|
||||
text.insert("insert", indent)
|
||||
elif c == PyParse.C_BRACKET:
|
||||
# line up with the first (if any) element of the
|
||||
# last open bracket structure; else indent one
|
||||
# level beyond the indent of the line with the
|
||||
# last open bracket
|
||||
self.reindent_to(y.compute_bracket_indent())
|
||||
elif c == PyParse.C_BACKSLASH:
|
||||
# if more than one line in this stmt already, just
|
||||
# mimic the current indent; else if initial line
|
||||
# has a start on an assignment stmt, indent to
|
||||
# beyond leftmost =; else to beyond first chunk of
|
||||
# non-whitespace on initial line
|
||||
if y.get_num_lines_in_stmt() > 1:
|
||||
text.insert("insert", indent)
|
||||
else:
|
||||
self.reindent_to(y.compute_backslash_indent())
|
||||
else:
|
||||
assert 0, "bogus continuation type " + `c`
|
||||
return "break"
|
||||
|
||||
# This line starts a brand new stmt; indent relative to
|
||||
# indentation of initial line of closest preceding
|
||||
# interesting stmt.
|
||||
indent = y.get_base_indent_string()
|
||||
text.insert("insert", indent)
|
||||
if y.is_block_opener():
|
||||
self.smart_indent_event(event)
|
||||
elif indent and y.is_block_closer():
|
||||
self.smart_backspace_event(event)
|
||||
return "break"
|
||||
finally:
|
||||
text.see("insert")
|
||||
text.undo_block_stop()
|
||||
|
||||
auto_indent = newline_and_indent_event
|
||||
|
||||
# Our editwin provides a is_char_in_string function that works
|
||||
# with a Tk text index, but PyParse only knows about offsets into
|
||||
# a string. This builds a function for PyParse that accepts an
|
||||
# offset.
|
||||
|
||||
def _build_char_in_string_func(self, startindex):
|
||||
def inner(offset, _startindex=startindex,
|
||||
_icis=self.editwin.is_char_in_string):
|
||||
return _icis(_startindex + "+%dc" % offset)
|
||||
return inner
|
||||
|
||||
def indent_region_event(self, event):
|
||||
head, tail, chars, lines = self.get_region()
|
||||
for pos in range(len(lines)):
|
||||
line = lines[pos]
|
||||
if line:
|
||||
raw, effective = classifyws(line, self.tabwidth)
|
||||
effective = effective + self.indentwidth
|
||||
lines[pos] = self._make_blanks(effective) + line[raw:]
|
||||
self.set_region(head, tail, chars, lines)
|
||||
return "break"
|
||||
|
||||
def dedent_region_event(self, event):
|
||||
head, tail, chars, lines = self.get_region()
|
||||
for pos in range(len(lines)):
|
||||
line = lines[pos]
|
||||
if line:
|
||||
raw, effective = classifyws(line, self.tabwidth)
|
||||
effective = max(effective - self.indentwidth, 0)
|
||||
lines[pos] = self._make_blanks(effective) + line[raw:]
|
||||
self.set_region(head, tail, chars, lines)
|
||||
return "break"
|
||||
|
||||
def comment_region_event(self, event):
|
||||
head, tail, chars, lines = self.get_region()
|
||||
for pos in range(len(lines) - 1):
|
||||
line = lines[pos]
|
||||
lines[pos] = '##' + line
|
||||
self.set_region(head, tail, chars, lines)
|
||||
|
||||
def uncomment_region_event(self, event):
|
||||
head, tail, chars, lines = self.get_region()
|
||||
for pos in range(len(lines)):
|
||||
line = lines[pos]
|
||||
if not line:
|
||||
continue
|
||||
if line[:2] == '##':
|
||||
line = line[2:]
|
||||
elif line[:1] == '#':
|
||||
line = line[1:]
|
||||
lines[pos] = line
|
||||
self.set_region(head, tail, chars, lines)
|
||||
|
||||
def tabify_region_event(self, event):
|
||||
head, tail, chars, lines = self.get_region()
|
||||
tabwidth = self._asktabwidth()
|
||||
for pos in range(len(lines)):
|
||||
line = lines[pos]
|
||||
if line:
|
||||
raw, effective = classifyws(line, tabwidth)
|
||||
ntabs, nspaces = divmod(effective, tabwidth)
|
||||
lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
|
||||
self.set_region(head, tail, chars, lines)
|
||||
|
||||
def untabify_region_event(self, event):
|
||||
head, tail, chars, lines = self.get_region()
|
||||
tabwidth = self._asktabwidth()
|
||||
for pos in range(len(lines)):
|
||||
lines[pos] = string.expandtabs(lines[pos], tabwidth)
|
||||
self.set_region(head, tail, chars, lines)
|
||||
|
||||
def toggle_tabs_event(self, event):
|
||||
if self.editwin.askyesno(
|
||||
"Toggle tabs",
|
||||
"Turn tabs " + ("on", "off")[self.usetabs] + "?",
|
||||
parent=self.text):
|
||||
self.usetabs = not self.usetabs
|
||||
return "break"
|
||||
|
||||
# XXX this isn't bound to anything -- see class tabwidth comments
|
||||
def change_tabwidth_event(self, event):
|
||||
new = self._asktabwidth()
|
||||
if new != self.tabwidth:
|
||||
self.tabwidth = new
|
||||
self.set_indentation_params(0, guess=0)
|
||||
return "break"
|
||||
|
||||
def change_indentwidth_event(self, event):
|
||||
new = self.editwin.askinteger(
|
||||
"Indent width",
|
||||
"New indent width (1-16)",
|
||||
parent=self.text,
|
||||
initialvalue=self.indentwidth,
|
||||
minvalue=1,
|
||||
maxvalue=16)
|
||||
if new and new != self.indentwidth:
|
||||
self.indentwidth = new
|
||||
return "break"
|
||||
|
||||
def get_region(self):
|
||||
text = self.text
|
||||
first, last = self.editwin.get_selection_indices()
|
||||
if first and last:
|
||||
head = text.index(first + " linestart")
|
||||
tail = text.index(last + "-1c lineend +1c")
|
||||
else:
|
||||
head = text.index("insert linestart")
|
||||
tail = text.index("insert lineend +1c")
|
||||
chars = text.get(head, tail)
|
||||
lines = string.split(chars, "\n")
|
||||
return head, tail, chars, lines
|
||||
|
||||
def set_region(self, head, tail, chars, lines):
|
||||
text = self.text
|
||||
newchars = string.join(lines, "\n")
|
||||
if newchars == chars:
|
||||
text.bell()
|
||||
return
|
||||
text.tag_remove("sel", "1.0", "end")
|
||||
text.mark_set("insert", head)
|
||||
text.undo_block_start()
|
||||
text.delete(head, tail)
|
||||
text.insert(head, newchars)
|
||||
text.undo_block_stop()
|
||||
text.tag_add("sel", head, "insert")
|
||||
|
||||
# Make string that displays as n leading blanks.
|
||||
|
||||
def _make_blanks(self, n):
|
||||
if self.usetabs:
|
||||
ntabs, nspaces = divmod(n, self.tabwidth)
|
||||
return '\t' * ntabs + ' ' * nspaces
|
||||
else:
|
||||
return ' ' * n
|
||||
|
||||
# Delete from beginning of line to insert point, then reinsert
|
||||
# column logical (meaning use tabs if appropriate) spaces.
|
||||
|
||||
def reindent_to(self, column):
|
||||
text = self.text
|
||||
text.undo_block_start()
|
||||
if text.compare("insert linestart", "!=", "insert"):
|
||||
text.delete("insert linestart", "insert")
|
||||
if column:
|
||||
text.insert("insert", self._make_blanks(column))
|
||||
text.undo_block_stop()
|
||||
|
||||
def _asktabwidth(self):
|
||||
return self.editwin.askinteger(
|
||||
"Tab width",
|
||||
"Spaces per tab?",
|
||||
parent=self.text,
|
||||
initialvalue=self.tabwidth,
|
||||
minvalue=1,
|
||||
maxvalue=16) or self.tabwidth
|
||||
|
||||
# Guess indentwidth from text content.
|
||||
# Return guessed indentwidth. This should not be believed unless
|
||||
# it's in a reasonable range (e.g., it will be 0 if no indented
|
||||
# blocks are found).
|
||||
|
||||
def guess_indent(self):
|
||||
opener, indented = IndentSearcher(self.text, self.tabwidth).run()
|
||||
if opener and indented:
|
||||
raw, indentsmall = classifyws(opener, self.tabwidth)
|
||||
raw, indentlarge = classifyws(indented, self.tabwidth)
|
||||
else:
|
||||
indentsmall = indentlarge = 0
|
||||
return indentlarge - indentsmall
|
||||
|
||||
# "line.col" -> line, as an int
|
||||
def index2line(index):
|
||||
return int(float(index))
|
||||
|
||||
# Look at the leading whitespace in s.
|
||||
# Return pair (# of leading ws characters,
|
||||
# effective # of leading blanks after expanding
|
||||
# tabs to width tabwidth)
|
||||
|
||||
def classifyws(s, tabwidth):
|
||||
raw = effective = 0
|
||||
for ch in s:
|
||||
if ch == ' ':
|
||||
raw = raw + 1
|
||||
effective = effective + 1
|
||||
elif ch == '\t':
|
||||
raw = raw + 1
|
||||
effective = (effective / tabwidth + 1) * tabwidth
|
||||
else:
|
||||
break
|
||||
return raw, effective
|
||||
|
||||
import tokenize
|
||||
_tokenize = tokenize
|
||||
del tokenize
|
||||
|
||||
class IndentSearcher:
|
||||
|
||||
# .run() chews over the Text widget, looking for a block opener
|
||||
# and the stmt following it. Returns a pair,
|
||||
# (line containing block opener, line containing stmt)
|
||||
# Either or both may be None.
|
||||
|
||||
def __init__(self, text, tabwidth):
|
||||
self.text = text
|
||||
self.tabwidth = tabwidth
|
||||
self.i = self.finished = 0
|
||||
self.blkopenline = self.indentedline = None
|
||||
|
||||
def readline(self):
|
||||
if self.finished:
|
||||
return ""
|
||||
i = self.i = self.i + 1
|
||||
mark = `i` + ".0"
|
||||
if self.text.compare(mark, ">=", "end"):
|
||||
return ""
|
||||
return self.text.get(mark, mark + " lineend+1c")
|
||||
|
||||
def tokeneater(self, type, token, start, end, line,
|
||||
INDENT=_tokenize.INDENT,
|
||||
NAME=_tokenize.NAME,
|
||||
OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
|
||||
if self.finished:
|
||||
pass
|
||||
elif type == NAME and token in OPENERS:
|
||||
self.blkopenline = line
|
||||
elif type == INDENT and self.blkopenline:
|
||||
self.indentedline = line
|
||||
self.finished = 1
|
||||
|
||||
def run(self):
|
||||
save_tabsize = _tokenize.tabsize
|
||||
_tokenize.tabsize = self.tabwidth
|
||||
try:
|
||||
try:
|
||||
_tokenize.tokenize(self.readline, self.tokeneater)
|
||||
except _tokenize.TokenError:
|
||||
# since we cut off the tokenizer early, we can trigger
|
||||
# spurious errors
|
||||
pass
|
||||
finally:
|
||||
_tokenize.tabsize = save_tabsize
|
||||
return self.blkopenline, self.indentedline
|
|
@ -0,0 +1,67 @@
|
|||
# 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
|
|
@ -0,0 +1,71 @@
|
|||
# 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()
|
|
@ -0,0 +1,190 @@
|
|||
# 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)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,224 @@
|
|||
"""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()
|
|
@ -0,0 +1,234 @@
|
|||
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()
|
|
@ -0,0 +1,382 @@
|
|||
"""Configuration file parser.
|
||||
|
||||
A setup file consists of sections, lead by a "[section]" header,
|
||||
and followed by "name: value" entries, with continuations and such in
|
||||
the style of RFC 822.
|
||||
|
||||
The option values can contain format strings which refer to other values in
|
||||
the same section, or values in a special [DEFAULT] section.
|
||||
|
||||
For example:
|
||||
|
||||
something: %(dir)s/whatever
|
||||
|
||||
would resolve the "%(dir)s" to the value of dir. All reference
|
||||
expansions are done late, on demand.
|
||||
|
||||
Intrinsic defaults can be specified by passing them into the
|
||||
ConfigParser constructor as a dictionary.
|
||||
|
||||
class:
|
||||
|
||||
ConfigParser -- responsible for for parsing a list of
|
||||
configuration files, and managing the parsed database.
|
||||
|
||||
methods:
|
||||
|
||||
__init__(defaults=None)
|
||||
create the parser and specify a dictionary of intrinsic defaults. The
|
||||
keys must be strings, the values must be appropriate for %()s string
|
||||
interpolation. Note that `__name__' is always an intrinsic default;
|
||||
it's value is the section's name.
|
||||
|
||||
sections()
|
||||
return all the configuration section names, sans DEFAULT
|
||||
|
||||
has_section(section)
|
||||
return whether the given section exists
|
||||
|
||||
options(section)
|
||||
return list of configuration options for the named section
|
||||
|
||||
has_option(section, option)
|
||||
return whether the given section has the given option
|
||||
|
||||
read(filenames)
|
||||
read and parse the list of named configuration files, given by
|
||||
name. A single filename is also allowed. Non-existing files
|
||||
are ignored.
|
||||
|
||||
readfp(fp, filename=None)
|
||||
read and parse one configuration file, given as a file object.
|
||||
The filename defaults to fp.name; it is only used in error
|
||||
messages (if fp has no `name' attribute, the string `<???>' is used).
|
||||
|
||||
get(section, option, raw=0, vars=None)
|
||||
return a string value for the named option. All % interpolations are
|
||||
expanded in the return values, based on the defaults passed into the
|
||||
constructor and the DEFAULT section. Additional substitutions may be
|
||||
provided using the `vars' argument, which must be a dictionary whose
|
||||
contents override any pre-existing defaults.
|
||||
|
||||
getint(section, options)
|
||||
like get(), but convert value to an integer
|
||||
|
||||
getfloat(section, options)
|
||||
like get(), but convert value to a float
|
||||
|
||||
getboolean(section, options)
|
||||
like get(), but convert value to a boolean (currently defined as 0 or
|
||||
1, only)
|
||||
"""
|
||||
|
||||
import sys
|
||||
import string
|
||||
import re
|
||||
|
||||
DEFAULTSECT = "DEFAULT"
|
||||
|
||||
|
||||
|
||||
# exception classes
|
||||
class Error:
|
||||
def __init__(self, msg=''):
|
||||
self._msg = msg
|
||||
def __repr__(self):
|
||||
return self._msg
|
||||
|
||||
class NoSectionError(Error):
|
||||
def __init__(self, section):
|
||||
Error.__init__(self, 'No section: %s' % section)
|
||||
self.section = section
|
||||
|
||||
class DuplicateSectionError(Error):
|
||||
def __init__(self, section):
|
||||
Error.__init__(self, "Section %s already exists" % section)
|
||||
self.section = section
|
||||
|
||||
class NoOptionError(Error):
|
||||
def __init__(self, option, section):
|
||||
Error.__init__(self, "No option `%s' in section: %s" %
|
||||
(option, section))
|
||||
self.option = option
|
||||
self.section = section
|
||||
|
||||
class InterpolationError(Error):
|
||||
def __init__(self, reference, option, section, rawval):
|
||||
Error.__init__(self,
|
||||
"Bad value substitution:\n"
|
||||
"\tsection: [%s]\n"
|
||||
"\toption : %s\n"
|
||||
"\tkey : %s\n"
|
||||
"\trawval : %s\n"
|
||||
% (section, option, reference, rawval))
|
||||
self.reference = reference
|
||||
self.option = option
|
||||
self.section = section
|
||||
|
||||
class MissingSectionHeaderError(Error):
|
||||
def __init__(self, filename, lineno, line):
|
||||
Error.__init__(
|
||||
self,
|
||||
'File contains no section headers.\nfile: %s, line: %d\n%s' %
|
||||
(filename, lineno, line))
|
||||
self.filename = filename
|
||||
self.lineno = lineno
|
||||
self.line = line
|
||||
|
||||
class ParsingError(Error):
|
||||
def __init__(self, filename):
|
||||
Error.__init__(self, 'File contains parsing errors: %s' % filename)
|
||||
self.filename = filename
|
||||
self.errors = []
|
||||
|
||||
def append(self, lineno, line):
|
||||
self.errors.append((lineno, line))
|
||||
self._msg = self._msg + '\n\t[line %2d]: %s' % (lineno, line)
|
||||
|
||||
|
||||
|
||||
class ConfigParser:
|
||||
def __init__(self, defaults=None):
|
||||
self.__sections = {}
|
||||
if defaults is None:
|
||||
self.__defaults = {}
|
||||
else:
|
||||
self.__defaults = defaults
|
||||
|
||||
def defaults(self):
|
||||
return self.__defaults
|
||||
|
||||
def sections(self):
|
||||
"""Return a list of section names, excluding [DEFAULT]"""
|
||||
# self.__sections will never have [DEFAULT] in it
|
||||
return self.__sections.keys()
|
||||
|
||||
def add_section(self, section):
|
||||
"""Create a new section in the configuration.
|
||||
|
||||
Raise DuplicateSectionError if a section by the specified name
|
||||
already exists.
|
||||
"""
|
||||
if self.__sections.has_key(section):
|
||||
raise DuplicateSectionError(section)
|
||||
self.__sections[section] = {}
|
||||
|
||||
def has_section(self, section):
|
||||
"""Indicate whether the named section is present in the configuration.
|
||||
|
||||
The DEFAULT section is not acknowledged.
|
||||
"""
|
||||
return self.__sections.has_key(section)
|
||||
|
||||
def options(self, section):
|
||||
"""Return a list of option names for the given section name."""
|
||||
try:
|
||||
opts = self.__sections[section].copy()
|
||||
except KeyError:
|
||||
raise NoSectionError(section)
|
||||
opts.update(self.__defaults)
|
||||
return opts.keys()
|
||||
|
||||
def has_option(self, section, option):
|
||||
"""Return whether the given section has the given option."""
|
||||
try:
|
||||
opts = self.__sections[section]
|
||||
except KeyError:
|
||||
raise NoSectionError(section)
|
||||
return opts.has_key(option)
|
||||
|
||||
def read(self, filenames):
|
||||
"""Read and parse a filename or a list of filenames.
|
||||
|
||||
Files that cannot be opened are silently ignored; this is
|
||||
designed so that you can specify a list of potential
|
||||
configuration file locations (e.g. current directory, user's
|
||||
home directory, systemwide directory), and all existing
|
||||
configuration files in the list will be read. A single
|
||||
filename may also be given.
|
||||
"""
|
||||
if type(filenames) is type(''):
|
||||
filenames = [filenames]
|
||||
for filename in filenames:
|
||||
try:
|
||||
fp = open(filename)
|
||||
except IOError:
|
||||
continue
|
||||
self.__read(fp, filename)
|
||||
fp.close()
|
||||
|
||||
def readfp(self, fp, filename=None):
|
||||
"""Like read() but the argument must be a file-like object.
|
||||
|
||||
The `fp' argument must have a `readline' method. Optional
|
||||
second argument is the `filename', which if not given, is
|
||||
taken from fp.name. If fp has no `name' attribute, `<???>' is
|
||||
used.
|
||||
|
||||
"""
|
||||
if filename is None:
|
||||
try:
|
||||
filename = fp.name
|
||||
except AttributeError:
|
||||
filename = '<???>'
|
||||
self.__read(fp, filename)
|
||||
|
||||
def get(self, section, option, raw=0, vars=None):
|
||||
"""Get an option value for a given section.
|
||||
|
||||
All % interpolations are expanded in the return values, based on the
|
||||
defaults passed into the constructor, unless the optional argument
|
||||
`raw' is true. Additional substitutions may be provided using the
|
||||
`vars' argument, which must be a dictionary whose contents overrides
|
||||
any pre-existing defaults.
|
||||
|
||||
The section DEFAULT is special.
|
||||
"""
|
||||
try:
|
||||
sectdict = self.__sections[section].copy()
|
||||
except KeyError:
|
||||
if section == DEFAULTSECT:
|
||||
sectdict = {}
|
||||
else:
|
||||
raise NoSectionError(section)
|
||||
d = self.__defaults.copy()
|
||||
d.update(sectdict)
|
||||
# Update with the entry specific variables
|
||||
if vars:
|
||||
d.update(vars)
|
||||
option = self.optionxform(option)
|
||||
try:
|
||||
rawval = d[option]
|
||||
except KeyError:
|
||||
raise NoOptionError(option, section)
|
||||
# do the string interpolation
|
||||
if raw:
|
||||
return rawval
|
||||
|
||||
value = rawval # Make it a pretty variable name
|
||||
depth = 0
|
||||
while depth < 10: # Loop through this until it's done
|
||||
depth = depth + 1
|
||||
if string.find(value, "%(") >= 0:
|
||||
try:
|
||||
value = value % d
|
||||
except KeyError, key:
|
||||
raise InterpolationError(key, option, section, rawval)
|
||||
else:
|
||||
return value
|
||||
|
||||
def __get(self, section, conv, option):
|
||||
return conv(self.get(section, option))
|
||||
|
||||
def getint(self, section, option):
|
||||
return self.__get(section, string.atoi, option)
|
||||
|
||||
def getfloat(self, section, option):
|
||||
return self.__get(section, string.atof, option)
|
||||
|
||||
def getboolean(self, section, option):
|
||||
v = self.get(section, option)
|
||||
val = string.atoi(v)
|
||||
if val not in (0, 1):
|
||||
raise ValueError, 'Not a boolean: %s' % v
|
||||
return val
|
||||
|
||||
def optionxform(self, optionstr):
|
||||
return string.lower(optionstr)
|
||||
|
||||
#
|
||||
# Regular expressions for parsing section headers and options. Note a
|
||||
# slight semantic change from the previous version, because of the use
|
||||
# of \w, _ is allowed in section header names.
|
||||
SECTCRE = re.compile(
|
||||
r'\[' # [
|
||||
r'(?P<header>[-\w_.*,(){}]+)' # a lot of stuff found by IvL
|
||||
r'\]' # ]
|
||||
)
|
||||
OPTCRE = re.compile(
|
||||
r'(?P<option>[-\w_.*,(){}]+)' # a lot of stuff found by IvL
|
||||
r'[ \t]*(?P<vi>[:=])[ \t]*' # any number of space/tab,
|
||||
# followed by separator
|
||||
# (either : or =), followed
|
||||
# by any # space/tab
|
||||
r'(?P<value>.*)$' # everything up to eol
|
||||
)
|
||||
|
||||
def __read(self, fp, fpname):
|
||||
"""Parse a sectioned setup file.
|
||||
|
||||
The sections in setup file contains a title line at the top,
|
||||
indicated by a name in square brackets (`[]'), plus key/value
|
||||
options lines, indicated by `name: value' format lines.
|
||||
Continuation are represented by an embedded newline then
|
||||
leading whitespace. Blank lines, lines beginning with a '#',
|
||||
and just about everything else is ignored.
|
||||
"""
|
||||
cursect = None # None, or a dictionary
|
||||
optname = None
|
||||
lineno = 0
|
||||
e = None # None, or an exception
|
||||
while 1:
|
||||
line = fp.readline()
|
||||
if not line:
|
||||
break
|
||||
lineno = lineno + 1
|
||||
# comment or blank line?
|
||||
if string.strip(line) == '' or line[0] in '#;':
|
||||
continue
|
||||
if string.lower(string.split(line)[0]) == 'rem' \
|
||||
and line[0] in "rR": # no leading whitespace
|
||||
continue
|
||||
# continuation line?
|
||||
if line[0] in ' \t' and cursect is not None and optname:
|
||||
value = string.strip(line)
|
||||
if value:
|
||||
cursect[optname] = cursect[optname] + '\n ' + value
|
||||
# a section header or option header?
|
||||
else:
|
||||
# is it a section header?
|
||||
mo = self.SECTCRE.match(line)
|
||||
if mo:
|
||||
sectname = mo.group('header')
|
||||
if self.__sections.has_key(sectname):
|
||||
cursect = self.__sections[sectname]
|
||||
elif sectname == DEFAULTSECT:
|
||||
cursect = self.__defaults
|
||||
else:
|
||||
cursect = {'__name__': sectname}
|
||||
self.__sections[sectname] = cursect
|
||||
# So sections can't start with a continuation line
|
||||
optname = None
|
||||
# no section header in the file?
|
||||
elif cursect is None:
|
||||
raise MissingSectionHeaderError(fpname, lineno, `line`)
|
||||
# an option line?
|
||||
else:
|
||||
mo = self.OPTCRE.match(line)
|
||||
if mo:
|
||||
optname, vi, optval = mo.group('option', 'vi', 'value')
|
||||
optname = string.lower(optname)
|
||||
if vi in ('=', ':') and ';' in optval:
|
||||
# ';' is a comment delimiter only if it follows
|
||||
# a spacing character
|
||||
pos = string.find(optval, ';')
|
||||
if pos and optval[pos-1] in string.whitespace:
|
||||
optval = optval[:pos]
|
||||
optval = string.strip(optval)
|
||||
# allow empty values
|
||||
if optval == '""':
|
||||
optval = ''
|
||||
cursect[optname] = optval
|
||||
else:
|
||||
# a non-fatal parsing error occurred. set up the
|
||||
# exception but keep going. the exception will be
|
||||
# raised at the end of the file and will contain a
|
||||
# list of all bogus lines
|
||||
if not e:
|
||||
e = ParsingError(fpname)
|
||||
e.append(lineno, `line`)
|
||||
# if any parsing errors occurred, raise an exception
|
||||
if e:
|
||||
raise e
|
|
@ -0,0 +1,308 @@
|
|||
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)
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
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
|
|
@ -0,0 +1,749 @@
|
|||
# changes by dscherer@cmu.edu
|
||||
# - created format and run menus
|
||||
# - added silly advice dialog (apologies to Douglas Adams)
|
||||
# - made Python Documentation work on Windows (requires win32api to
|
||||
# do a ShellExecute(); other ways of starting a web browser are awkward)
|
||||
|
||||
import sys
|
||||
import os
|
||||
import string
|
||||
import re
|
||||
import imp
|
||||
from Tkinter import *
|
||||
import tkSimpleDialog
|
||||
import tkMessageBox
|
||||
import idlever
|
||||
import WindowList
|
||||
from IdleConf import idleconf
|
||||
|
||||
# The default tab setting for a Text widget, in average-width characters.
|
||||
TK_TABWIDTH_DEFAULT = 8
|
||||
|
||||
# File menu
|
||||
|
||||
#$ event <<open-module>>
|
||||
#$ win <Alt-m>
|
||||
#$ unix <Control-x><Control-m>
|
||||
|
||||
#$ event <<open-class-browser>>
|
||||
#$ win <Alt-c>
|
||||
#$ unix <Control-x><Control-b>
|
||||
|
||||
#$ event <<open-path-browser>>
|
||||
|
||||
#$ event <<close-window>>
|
||||
#$ unix <Control-x><Control-0>
|
||||
#$ unix <Control-x><Key-0>
|
||||
#$ win <Alt-F4>
|
||||
|
||||
# Edit menu
|
||||
|
||||
#$ event <<Copy>>
|
||||
#$ win <Control-c>
|
||||
#$ unix <Alt-w>
|
||||
|
||||
#$ event <<Cut>>
|
||||
#$ win <Control-x>
|
||||
#$ unix <Control-w>
|
||||
|
||||
#$ event <<Paste>>
|
||||
#$ win <Control-v>
|
||||
#$ unix <Control-y>
|
||||
|
||||
#$ event <<select-all>>
|
||||
#$ win <Alt-a>
|
||||
#$ unix <Alt-a>
|
||||
|
||||
# Help menu
|
||||
|
||||
#$ event <<help>>
|
||||
#$ win <F1>
|
||||
#$ unix <F1>
|
||||
|
||||
#$ event <<about-idle>>
|
||||
|
||||
# Events without menu entries
|
||||
|
||||
#$ event <<remove-selection>>
|
||||
#$ win <Escape>
|
||||
|
||||
#$ event <<center-insert>>
|
||||
#$ win <Control-l>
|
||||
#$ unix <Control-l>
|
||||
|
||||
#$ event <<do-nothing>>
|
||||
#$ unix <Control-x>
|
||||
|
||||
|
||||
about_title = "About IDLE"
|
||||
about_text = """\
|
||||
IDLE %s
|
||||
|
||||
An Integrated DeveLopment Environment for Python
|
||||
|
||||
by Guido van Rossum
|
||||
|
||||
This version of IDLE has been modified by David Scherer
|
||||
(dscherer@cmu.edu). See readme.txt for details.
|
||||
""" % idlever.IDLE_VERSION
|
||||
|
||||
class EditorWindow:
|
||||
|
||||
from Percolator import Percolator
|
||||
from ColorDelegator import ColorDelegator
|
||||
from UndoDelegator import UndoDelegator
|
||||
from IOBinding import IOBinding
|
||||
import Bindings
|
||||
from Tkinter import Toplevel
|
||||
from MultiStatusBar import MultiStatusBar
|
||||
|
||||
about_title = about_title
|
||||
about_text = about_text
|
||||
|
||||
vars = {}
|
||||
|
||||
def __init__(self, flist=None, filename=None, key=None, root=None):
|
||||
edconf = idleconf.getsection('EditorWindow')
|
||||
coconf = idleconf.getsection('Colors')
|
||||
self.flist = flist
|
||||
root = root or flist.root
|
||||
self.root = root
|
||||
if flist:
|
||||
self.vars = flist.vars
|
||||
self.menubar = Menu(root)
|
||||
self.top = top = self.Toplevel(root, menu=self.menubar)
|
||||
self.vbar = vbar = Scrollbar(top, name='vbar')
|
||||
self.text_frame = text_frame = Frame(top)
|
||||
self.text = text = Text(text_frame, name='text', padx=5,
|
||||
foreground=coconf.getdef('normal-foreground'),
|
||||
background=coconf.getdef('normal-background'),
|
||||
highlightcolor=coconf.getdef('hilite-foreground'),
|
||||
highlightbackground=coconf.getdef('hilite-background'),
|
||||
insertbackground=coconf.getdef('cursor-background'),
|
||||
width=edconf.getint('width'),
|
||||
height=edconf.getint('height'),
|
||||
wrap="none")
|
||||
|
||||
self.createmenubar()
|
||||
self.apply_bindings()
|
||||
|
||||
self.top.protocol("WM_DELETE_WINDOW", self.close)
|
||||
self.top.bind("<<close-window>>", self.close_event)
|
||||
text.bind("<<center-insert>>", self.center_insert_event)
|
||||
text.bind("<<help>>", self.help_dialog)
|
||||
text.bind("<<good-advice>>", self.good_advice)
|
||||
text.bind("<<python-docs>>", self.python_docs)
|
||||
text.bind("<<about-idle>>", self.about_dialog)
|
||||
text.bind("<<open-module>>", self.open_module)
|
||||
text.bind("<<do-nothing>>", lambda event: "break")
|
||||
text.bind("<<select-all>>", self.select_all)
|
||||
text.bind("<<remove-selection>>", self.remove_selection)
|
||||
text.bind("<3>", self.right_menu_event)
|
||||
if flist:
|
||||
flist.inversedict[self] = key
|
||||
if key:
|
||||
flist.dict[key] = self
|
||||
text.bind("<<open-new-window>>", self.flist.new_callback)
|
||||
text.bind("<<close-all-windows>>", self.flist.close_all_callback)
|
||||
text.bind("<<open-class-browser>>", self.open_class_browser)
|
||||
text.bind("<<open-path-browser>>", self.open_path_browser)
|
||||
|
||||
vbar['command'] = text.yview
|
||||
vbar.pack(side=RIGHT, fill=Y)
|
||||
|
||||
text['yscrollcommand'] = vbar.set
|
||||
text['font'] = edconf.get('font-name'), edconf.get('font-size')
|
||||
text_frame.pack(side=LEFT, fill=BOTH, expand=1)
|
||||
text.pack(side=TOP, fill=BOTH, expand=1)
|
||||
text.focus_set()
|
||||
|
||||
self.per = per = self.Percolator(text)
|
||||
if self.ispythonsource(filename):
|
||||
self.color = color = self.ColorDelegator(); per.insertfilter(color)
|
||||
##print "Initial colorizer"
|
||||
else:
|
||||
##print "No initial colorizer"
|
||||
self.color = None
|
||||
self.undo = undo = self.UndoDelegator(); per.insertfilter(undo)
|
||||
self.io = io = self.IOBinding(self)
|
||||
|
||||
text.undo_block_start = undo.undo_block_start
|
||||
text.undo_block_stop = undo.undo_block_stop
|
||||
undo.set_saved_change_hook(self.saved_change_hook)
|
||||
io.set_filename_change_hook(self.filename_change_hook)
|
||||
|
||||
if filename:
|
||||
if os.path.exists(filename):
|
||||
io.loadfile(filename)
|
||||
else:
|
||||
io.set_filename(filename)
|
||||
|
||||
self.saved_change_hook()
|
||||
|
||||
self.load_extensions()
|
||||
|
||||
menu = self.menudict.get('windows')
|
||||
if menu:
|
||||
end = menu.index("end")
|
||||
if end is None:
|
||||
end = -1
|
||||
if end >= 0:
|
||||
menu.add_separator()
|
||||
end = end + 1
|
||||
self.wmenu_end = end
|
||||
WindowList.register_callback(self.postwindowsmenu)
|
||||
|
||||
# Some abstractions so IDLE extensions are cross-IDE
|
||||
self.askyesno = tkMessageBox.askyesno
|
||||
self.askinteger = tkSimpleDialog.askinteger
|
||||
self.showerror = tkMessageBox.showerror
|
||||
|
||||
if self.extensions.has_key('AutoIndent'):
|
||||
self.extensions['AutoIndent'].set_indentation_params(
|
||||
self.ispythonsource(filename))
|
||||
self.set_status_bar()
|
||||
|
||||
def set_status_bar(self):
|
||||
self.status_bar = self.MultiStatusBar(self.text_frame)
|
||||
self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
|
||||
self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
|
||||
self.status_bar.pack(side=BOTTOM, fill=X)
|
||||
self.text.bind('<KeyRelease>', self.set_line_and_column)
|
||||
self.text.bind('<ButtonRelease>', self.set_line_and_column)
|
||||
self.text.after_idle(self.set_line_and_column)
|
||||
|
||||
def set_line_and_column(self, event=None):
|
||||
line, column = string.split(self.text.index(INSERT), '.')
|
||||
self.status_bar.set_label('column', 'Col: %s' % column)
|
||||
self.status_bar.set_label('line', 'Ln: %s' % line)
|
||||
|
||||
def wakeup(self):
|
||||
if self.top.wm_state() == "iconic":
|
||||
self.top.wm_deiconify()
|
||||
else:
|
||||
self.top.tkraise()
|
||||
self.text.focus_set()
|
||||
|
||||
menu_specs = [
|
||||
("file", "_File"),
|
||||
("edit", "_Edit"),
|
||||
("format", "F_ormat"),
|
||||
("run", "_Run"),
|
||||
("windows", "_Windows"),
|
||||
("help", "_Help"),
|
||||
]
|
||||
|
||||
def createmenubar(self):
|
||||
mbar = self.menubar
|
||||
self.menudict = menudict = {}
|
||||
for name, label in self.menu_specs:
|
||||
underline, label = prepstr(label)
|
||||
menudict[name] = menu = Menu(mbar, name=name)
|
||||
mbar.add_cascade(label=label, menu=menu, underline=underline)
|
||||
self.fill_menus()
|
||||
|
||||
def postwindowsmenu(self):
|
||||
# Only called when Windows menu exists
|
||||
# XXX Actually, this Just-In-Time updating interferes badly
|
||||
# XXX with the tear-off feature. It would be better to update
|
||||
# XXX all Windows menus whenever the list of windows changes.
|
||||
menu = self.menudict['windows']
|
||||
end = menu.index("end")
|
||||
if end is None:
|
||||
end = -1
|
||||
if end > self.wmenu_end:
|
||||
menu.delete(self.wmenu_end+1, end)
|
||||
WindowList.add_windows_to_menu(menu)
|
||||
|
||||
rmenu = None
|
||||
|
||||
def right_menu_event(self, event):
|
||||
self.text.tag_remove("sel", "1.0", "end")
|
||||
self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
|
||||
if not self.rmenu:
|
||||
self.make_rmenu()
|
||||
rmenu = self.rmenu
|
||||
self.event = event
|
||||
iswin = sys.platform[:3] == 'win'
|
||||
if iswin:
|
||||
self.text.config(cursor="arrow")
|
||||
rmenu.tk_popup(event.x_root, event.y_root)
|
||||
if iswin:
|
||||
self.text.config(cursor="ibeam")
|
||||
|
||||
rmenu_specs = [
|
||||
# ("Label", "<<virtual-event>>"), ...
|
||||
("Close", "<<close-window>>"), # Example
|
||||
]
|
||||
|
||||
def make_rmenu(self):
|
||||
rmenu = Menu(self.text, tearoff=0)
|
||||
for label, eventname in self.rmenu_specs:
|
||||
def command(text=self.text, eventname=eventname):
|
||||
text.event_generate(eventname)
|
||||
rmenu.add_command(label=label, command=command)
|
||||
self.rmenu = rmenu
|
||||
|
||||
def about_dialog(self, event=None):
|
||||
tkMessageBox.showinfo(self.about_title, self.about_text,
|
||||
master=self.text)
|
||||
|
||||
helpfile = "help.txt"
|
||||
|
||||
def good_advice(self, event=None):
|
||||
tkMessageBox.showinfo('Advice', "Don't Panic!", master=self.text)
|
||||
|
||||
def help_dialog(self, event=None):
|
||||
try:
|
||||
helpfile = os.path.join(os.path.dirname(__file__), self.helpfile)
|
||||
except NameError:
|
||||
helpfile = self.helpfile
|
||||
if self.flist:
|
||||
self.flist.open(helpfile)
|
||||
else:
|
||||
self.io.loadfile(helpfile)
|
||||
|
||||
help_viewer = "netscape -remote 'openurl(%(url)s)' 2>/dev/null || " \
|
||||
"netscape %(url)s &"
|
||||
help_url = "http://www.python.org/doc/current/"
|
||||
|
||||
def python_docs(self, event=None):
|
||||
if sys.platform=='win32':
|
||||
try:
|
||||
import win32api
|
||||
import ExecBinding
|
||||
doc = os.path.join( os.path.dirname( ExecBinding.pyth_exe ), "doc", "index.html" )
|
||||
win32api.ShellExecute(0, None, doc, None, sys.path[0], 1)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
cmd = self.help_viewer % {"url": self.help_url}
|
||||
os.system(cmd)
|
||||
|
||||
def select_all(self, event=None):
|
||||
self.text.tag_add("sel", "1.0", "end-1c")
|
||||
self.text.mark_set("insert", "1.0")
|
||||
self.text.see("insert")
|
||||
return "break"
|
||||
|
||||
def remove_selection(self, event=None):
|
||||
self.text.tag_remove("sel", "1.0", "end")
|
||||
self.text.see("insert")
|
||||
|
||||
def open_module(self, event=None):
|
||||
# XXX Shouldn't this be in IOBinding or in FileList?
|
||||
try:
|
||||
name = self.text.get("sel.first", "sel.last")
|
||||
except TclError:
|
||||
name = ""
|
||||
else:
|
||||
name = string.strip(name)
|
||||
if not name:
|
||||
name = tkSimpleDialog.askstring("Module",
|
||||
"Enter the name of a Python module\n"
|
||||
"to search on sys.path and open:",
|
||||
parent=self.text)
|
||||
if name:
|
||||
name = string.strip(name)
|
||||
if not name:
|
||||
return
|
||||
# XXX Ought to support package syntax
|
||||
# XXX Ought to insert current file's directory in front of path
|
||||
try:
|
||||
(f, file, (suffix, mode, type)) = imp.find_module(name)
|
||||
except (NameError, ImportError), msg:
|
||||
tkMessageBox.showerror("Import error", str(msg), parent=self.text)
|
||||
return
|
||||
if type != imp.PY_SOURCE:
|
||||
tkMessageBox.showerror("Unsupported type",
|
||||
"%s is not a source module" % name, parent=self.text)
|
||||
return
|
||||
if f:
|
||||
f.close()
|
||||
if self.flist:
|
||||
self.flist.open(file)
|
||||
else:
|
||||
self.io.loadfile(file)
|
||||
|
||||
def open_class_browser(self, event=None):
|
||||
filename = self.io.filename
|
||||
if not filename:
|
||||
tkMessageBox.showerror(
|
||||
"No filename",
|
||||
"This buffer has no associated filename",
|
||||
master=self.text)
|
||||
self.text.focus_set()
|
||||
return None
|
||||
head, tail = os.path.split(filename)
|
||||
base, ext = os.path.splitext(tail)
|
||||
import ClassBrowser
|
||||
ClassBrowser.ClassBrowser(self.flist, base, [head])
|
||||
|
||||
def open_path_browser(self, event=None):
|
||||
import PathBrowser
|
||||
PathBrowser.PathBrowser(self.flist)
|
||||
|
||||
def gotoline(self, lineno):
|
||||
if lineno is not None and lineno > 0:
|
||||
self.text.mark_set("insert", "%d.0" % lineno)
|
||||
self.text.tag_remove("sel", "1.0", "end")
|
||||
self.text.tag_add("sel", "insert", "insert +1l")
|
||||
self.center()
|
||||
|
||||
def ispythonsource(self, filename):
|
||||
if not filename:
|
||||
return 1
|
||||
base, ext = os.path.splitext(os.path.basename(filename))
|
||||
if os.path.normcase(ext) in (".py", ".pyw"):
|
||||
return 1
|
||||
try:
|
||||
f = open(filename)
|
||||
line = f.readline()
|
||||
f.close()
|
||||
except IOError:
|
||||
return 0
|
||||
return line[:2] == '#!' and string.find(line, 'python') >= 0
|
||||
|
||||
def close_hook(self):
|
||||
if self.flist:
|
||||
self.flist.close_edit(self)
|
||||
|
||||
def set_close_hook(self, close_hook):
|
||||
self.close_hook = close_hook
|
||||
|
||||
def filename_change_hook(self):
|
||||
if self.flist:
|
||||
self.flist.filename_changed_edit(self)
|
||||
self.saved_change_hook()
|
||||
if self.ispythonsource(self.io.filename):
|
||||
self.addcolorizer()
|
||||
else:
|
||||
self.rmcolorizer()
|
||||
|
||||
def addcolorizer(self):
|
||||
if self.color:
|
||||
return
|
||||
##print "Add colorizer"
|
||||
self.per.removefilter(self.undo)
|
||||
self.color = self.ColorDelegator()
|
||||
self.per.insertfilter(self.color)
|
||||
self.per.insertfilter(self.undo)
|
||||
|
||||
def rmcolorizer(self):
|
||||
if not self.color:
|
||||
return
|
||||
##print "Remove colorizer"
|
||||
self.per.removefilter(self.undo)
|
||||
self.per.removefilter(self.color)
|
||||
self.color = None
|
||||
self.per.insertfilter(self.undo)
|
||||
|
||||
def saved_change_hook(self):
|
||||
short = self.short_title()
|
||||
long = self.long_title()
|
||||
if short and long:
|
||||
title = short + " - " + long
|
||||
elif short:
|
||||
title = short
|
||||
elif long:
|
||||
title = long
|
||||
else:
|
||||
title = "Untitled"
|
||||
icon = short or long or title
|
||||
if not self.get_saved():
|
||||
title = "*%s*" % title
|
||||
icon = "*%s" % icon
|
||||
self.top.wm_title(title)
|
||||
self.top.wm_iconname(icon)
|
||||
|
||||
def get_saved(self):
|
||||
return self.undo.get_saved()
|
||||
|
||||
def set_saved(self, flag):
|
||||
self.undo.set_saved(flag)
|
||||
|
||||
def reset_undo(self):
|
||||
self.undo.reset_undo()
|
||||
|
||||
def short_title(self):
|
||||
filename = self.io.filename
|
||||
if filename:
|
||||
filename = os.path.basename(filename)
|
||||
return filename
|
||||
|
||||
def long_title(self):
|
||||
return self.io.filename or ""
|
||||
|
||||
def center_insert_event(self, event):
|
||||
self.center()
|
||||
|
||||
def center(self, mark="insert"):
|
||||
text = self.text
|
||||
top, bot = self.getwindowlines()
|
||||
lineno = self.getlineno(mark)
|
||||
height = bot - top
|
||||
newtop = max(1, lineno - height/2)
|
||||
text.yview(float(newtop))
|
||||
|
||||
def getwindowlines(self):
|
||||
text = self.text
|
||||
top = self.getlineno("@0,0")
|
||||
bot = self.getlineno("@0,65535")
|
||||
if top == bot and text.winfo_height() == 1:
|
||||
# Geometry manager hasn't run yet
|
||||
height = int(text['height'])
|
||||
bot = top + height - 1
|
||||
return top, bot
|
||||
|
||||
def getlineno(self, mark="insert"):
|
||||
text = self.text
|
||||
return int(float(text.index(mark)))
|
||||
|
||||
def close_event(self, event):
|
||||
self.close()
|
||||
|
||||
def maybesave(self):
|
||||
if self.io:
|
||||
return self.io.maybesave()
|
||||
|
||||
def close(self):
|
||||
self.top.wm_deiconify()
|
||||
self.top.tkraise()
|
||||
reply = self.maybesave()
|
||||
if reply != "cancel":
|
||||
self._close()
|
||||
return reply
|
||||
|
||||
def _close(self):
|
||||
WindowList.unregister_callback(self.postwindowsmenu)
|
||||
if self.close_hook:
|
||||
self.close_hook()
|
||||
self.flist = None
|
||||
colorizing = 0
|
||||
self.unload_extensions()
|
||||
self.io.close(); self.io = None
|
||||
self.undo = None # XXX
|
||||
if self.color:
|
||||
colorizing = self.color.colorizing
|
||||
doh = colorizing and self.top
|
||||
self.color.close(doh) # Cancel colorization
|
||||
self.text = None
|
||||
self.vars = None
|
||||
self.per.close(); self.per = None
|
||||
if not colorizing:
|
||||
self.top.destroy()
|
||||
|
||||
def load_extensions(self):
|
||||
self.extensions = {}
|
||||
self.load_standard_extensions()
|
||||
|
||||
def unload_extensions(self):
|
||||
for ins in self.extensions.values():
|
||||
if hasattr(ins, "close"):
|
||||
ins.close()
|
||||
self.extensions = {}
|
||||
|
||||
def load_standard_extensions(self):
|
||||
for name in self.get_standard_extension_names():
|
||||
try:
|
||||
self.load_extension(name)
|
||||
except:
|
||||
print "Failed to load extension", `name`
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def get_standard_extension_names(self):
|
||||
return idleconf.getextensions()
|
||||
|
||||
def load_extension(self, name):
|
||||
mod = __import__(name, globals(), locals(), [])
|
||||
cls = getattr(mod, name)
|
||||
ins = cls(self)
|
||||
self.extensions[name] = ins
|
||||
kdnames = ["keydefs"]
|
||||
if sys.platform == 'win32':
|
||||
kdnames.append("windows_keydefs")
|
||||
elif sys.platform == 'mac':
|
||||
kdnames.append("mac_keydefs")
|
||||
else:
|
||||
kdnames.append("unix_keydefs")
|
||||
keydefs = {}
|
||||
for kdname in kdnames:
|
||||
if hasattr(ins, kdname):
|
||||
keydefs.update(getattr(ins, kdname))
|
||||
if keydefs:
|
||||
self.apply_bindings(keydefs)
|
||||
for vevent in keydefs.keys():
|
||||
methodname = string.replace(vevent, "-", "_")
|
||||
while methodname[:1] == '<':
|
||||
methodname = methodname[1:]
|
||||
while methodname[-1:] == '>':
|
||||
methodname = methodname[:-1]
|
||||
methodname = methodname + "_event"
|
||||
if hasattr(ins, methodname):
|
||||
self.text.bind(vevent, getattr(ins, methodname))
|
||||
if hasattr(ins, "menudefs"):
|
||||
self.fill_menus(ins.menudefs, keydefs)
|
||||
return ins
|
||||
|
||||
def apply_bindings(self, keydefs=None):
|
||||
if keydefs is None:
|
||||
keydefs = self.Bindings.default_keydefs
|
||||
text = self.text
|
||||
text.keydefs = keydefs
|
||||
for event, keylist in keydefs.items():
|
||||
if keylist:
|
||||
apply(text.event_add, (event,) + tuple(keylist))
|
||||
|
||||
def fill_menus(self, defs=None, keydefs=None):
|
||||
# Fill the menus. Menus that are absent or None in
|
||||
# self.menudict are ignored.
|
||||
if defs is None:
|
||||
defs = self.Bindings.menudefs
|
||||
if keydefs is None:
|
||||
keydefs = self.Bindings.default_keydefs
|
||||
menudict = self.menudict
|
||||
text = self.text
|
||||
for mname, itemlist in defs:
|
||||
menu = menudict.get(mname)
|
||||
if not menu:
|
||||
continue
|
||||
for item in itemlist:
|
||||
if not item:
|
||||
menu.add_separator()
|
||||
else:
|
||||
label, event = item
|
||||
checkbutton = (label[:1] == '!')
|
||||
if checkbutton:
|
||||
label = label[1:]
|
||||
underline, label = prepstr(label)
|
||||
accelerator = get_accelerator(keydefs, event)
|
||||
def command(text=text, event=event):
|
||||
text.event_generate(event)
|
||||
if checkbutton:
|
||||
var = self.getrawvar(event, BooleanVar)
|
||||
menu.add_checkbutton(label=label, underline=underline,
|
||||
command=command, accelerator=accelerator,
|
||||
variable=var)
|
||||
else:
|
||||
menu.add_command(label=label, underline=underline,
|
||||
command=command, accelerator=accelerator)
|
||||
|
||||
def getvar(self, name):
|
||||
var = self.getrawvar(name)
|
||||
if var:
|
||||
return var.get()
|
||||
|
||||
def setvar(self, name, value, vartype=None):
|
||||
var = self.getrawvar(name, vartype)
|
||||
if var:
|
||||
var.set(value)
|
||||
|
||||
def getrawvar(self, name, vartype=None):
|
||||
var = self.vars.get(name)
|
||||
if not var and vartype:
|
||||
self.vars[name] = var = vartype(self.text)
|
||||
return var
|
||||
|
||||
# Tk implementations of "virtual text methods" -- each platform
|
||||
# reusing IDLE's support code needs to define these for its GUI's
|
||||
# flavor of widget.
|
||||
|
||||
# Is character at text_index in a Python string? Return 0 for
|
||||
# "guaranteed no", true for anything else. This info is expensive
|
||||
# to compute ab initio, but is probably already known by the
|
||||
# platform's colorizer.
|
||||
|
||||
def is_char_in_string(self, text_index):
|
||||
if self.color:
|
||||
# Return true iff colorizer hasn't (re)gotten this far
|
||||
# yet, or the character is tagged as being in a string
|
||||
return self.text.tag_prevrange("TODO", text_index) or \
|
||||
"STRING" in self.text.tag_names(text_index)
|
||||
else:
|
||||
# The colorizer is missing: assume the worst
|
||||
return 1
|
||||
|
||||
# If a selection is defined in the text widget, return (start,
|
||||
# end) as Tkinter text indices, otherwise return (None, None)
|
||||
def get_selection_indices(self):
|
||||
try:
|
||||
first = self.text.index("sel.first")
|
||||
last = self.text.index("sel.last")
|
||||
return first, last
|
||||
except TclError:
|
||||
return None, None
|
||||
|
||||
# Return the text widget's current view of what a tab stop means
|
||||
# (equivalent width in spaces).
|
||||
|
||||
def get_tabwidth(self):
|
||||
current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
|
||||
return int(current)
|
||||
|
||||
# Set the text widget's current view of what a tab stop means.
|
||||
|
||||
def set_tabwidth(self, newtabwidth):
|
||||
text = self.text
|
||||
if self.get_tabwidth() != newtabwidth:
|
||||
pixels = text.tk.call("font", "measure", text["font"],
|
||||
"-displayof", text.master,
|
||||
"n" * newtabwith)
|
||||
text.configure(tabs=pixels)
|
||||
|
||||
def prepstr(s):
|
||||
# Helper to extract the underscore from a string, e.g.
|
||||
# prepstr("Co_py") returns (2, "Copy").
|
||||
i = string.find(s, '_')
|
||||
if i >= 0:
|
||||
s = s[:i] + s[i+1:]
|
||||
return i, s
|
||||
|
||||
|
||||
keynames = {
|
||||
'bracketleft': '[',
|
||||
'bracketright': ']',
|
||||
'slash': '/',
|
||||
}
|
||||
|
||||
def get_accelerator(keydefs, event):
|
||||
keylist = keydefs.get(event)
|
||||
if not keylist:
|
||||
return ""
|
||||
s = keylist[0]
|
||||
s = re.sub(r"-[a-z]\b", lambda m: string.upper(m.group()), s)
|
||||
s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
|
||||
s = re.sub("Key-", "", s)
|
||||
s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
|
||||
s = re.sub("Control-", "Ctrl-", s)
|
||||
s = re.sub("-", "+", s)
|
||||
s = re.sub("><", " ", s)
|
||||
s = re.sub("<", "", s)
|
||||
s = re.sub(">", "", s)
|
||||
return s
|
||||
|
||||
|
||||
def fixwordbreaks(root):
|
||||
# Make sure that Tk's double-click and next/previous word
|
||||
# operations use our definition of a word (i.e. an identifier)
|
||||
tk = root.tk
|
||||
tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
|
||||
tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
|
||||
tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
|
||||
|
||||
|
||||
def test():
|
||||
root = Tk()
|
||||
fixwordbreaks(root)
|
||||
root.withdraw()
|
||||
if sys.argv[1:]:
|
||||
filename = sys.argv[1]
|
||||
else:
|
||||
filename = None
|
||||
edit = EditorWindow(root=root, filename=filename)
|
||||
edit.set_close_hook(root.quit)
|
||||
root.mainloop()
|
||||
root.destroy()
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
|
@ -0,0 +1,198 @@
|
|||
"""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
|
|
@ -0,0 +1,150 @@
|
|||
# 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()
|
|
@ -0,0 +1,155 @@
|
|||
# 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)
|
|
@ -0,0 +1,38 @@
|
|||
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
|
|
@ -0,0 +1,135 @@
|
|||
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()
|
|
@ -0,0 +1,254 @@
|
|||
# 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()
|
Binary file not shown.
After Width: | Height: | Size: 120 B |
Binary file not shown.
After Width: | Height: | Size: 75 B |
Binary file not shown.
After Width: | Height: | Size: 125 B |
Binary file not shown.
After Width: | Height: | Size: 79 B |
Binary file not shown.
After Width: | Height: | Size: 895 B |
Binary file not shown.
After Width: | Height: | Size: 85 B |
|
@ -0,0 +1,113 @@
|
|||
"""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()
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
import string
|
||||
|
||||
class History:
|
||||
|
||||
def __init__(self, text, output_sep = "\n"):
|
||||
self.text = text
|
||||
self.history = []
|
||||
self.history_prefix = None
|
||||
self.history_pointer = None
|
||||
self.output_sep = output_sep
|
||||
text.bind("<<history-previous>>", self.history_prev)
|
||||
text.bind("<<history-next>>", self.history_next)
|
||||
|
||||
def history_next(self, event):
|
||||
self.history_do(0)
|
||||
return "break"
|
||||
|
||||
def history_prev(self, event):
|
||||
self.history_do(1)
|
||||
return "break"
|
||||
|
||||
def _get_source(self, start, end):
|
||||
# Get source code from start index to end index. Lines in the
|
||||
# text control may be separated by sys.ps2 .
|
||||
lines = string.split(self.text.get(start, end), self.output_sep)
|
||||
return string.join(lines, "\n")
|
||||
|
||||
def _put_source(self, where, source):
|
||||
output = string.join(string.split(source, "\n"), self.output_sep)
|
||||
self.text.insert(where, output)
|
||||
|
||||
def history_do(self, reverse):
|
||||
nhist = len(self.history)
|
||||
pointer = self.history_pointer
|
||||
prefix = self.history_prefix
|
||||
if pointer is not None and prefix is not None:
|
||||
if self.text.compare("insert", "!=", "end-1c") or \
|
||||
self._get_source("iomark", "end-1c") != self.history[pointer]:
|
||||
pointer = prefix = None
|
||||
if pointer is None or prefix is None:
|
||||
prefix = self._get_source("iomark", "end-1c")
|
||||
if reverse:
|
||||
pointer = nhist
|
||||
else:
|
||||
pointer = -1
|
||||
nprefix = len(prefix)
|
||||
while 1:
|
||||
if reverse:
|
||||
pointer = pointer - 1
|
||||
else:
|
||||
pointer = pointer + 1
|
||||
if pointer < 0 or pointer >= nhist:
|
||||
self.text.bell()
|
||||
if self._get_source("iomark", "end-1c") != prefix:
|
||||
self.text.delete("iomark", "end-1c")
|
||||
self._put_source("iomark", prefix)
|
||||
pointer = prefix = None
|
||||
break
|
||||
item = self.history[pointer]
|
||||
if item[:nprefix] == prefix and len(item) > nprefix:
|
||||
self.text.delete("iomark", "end-1c")
|
||||
self._put_source("iomark", item)
|
||||
break
|
||||
self.text.mark_set("insert", "end-1c")
|
||||
self.text.see("insert")
|
||||
self.text.tag_remove("sel", "1.0", "end")
|
||||
self.history_pointer = pointer
|
||||
self.history_prefix = prefix
|
||||
|
||||
def history_store(self, source):
|
||||
source = string.strip(source)
|
||||
if len(source) > 2:
|
||||
# avoid duplicates
|
||||
try:
|
||||
self.history.remove(source)
|
||||
except ValueError:
|
||||
pass
|
||||
self.history.append(source)
|
||||
self.history_pointer = None
|
||||
self.history_prefix = None
|
||||
|
||||
def recall(self, s):
|
||||
s = string.strip(s)
|
||||
self.text.tag_remove("sel", "1.0", "end")
|
||||
self.text.delete("iomark", "end-1c")
|
||||
self.text.mark_set("insert", "end-1c")
|
||||
self.text.insert("insert", s)
|
||||
self.text.see("insert")
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
# One or more ScrolledLists with HSeparators between them.
|
||||
# There is a hierarchical relationship between them:
|
||||
# the right list displays the substructure of the selected item
|
||||
# in the left list.
|
||||
|
||||
import string
|
||||
from Tkinter import *
|
||||
from WindowList import ListedToplevel
|
||||
from Separator import HSeparator
|
||||
from ScrolledList import ScrolledList
|
||||
|
||||
class MultiScrolledLists:
|
||||
|
||||
def __init__(self, root, nlists=2):
|
||||
assert nlists >= 1
|
||||
self.root = root
|
||||
self.nlists = nlists
|
||||
self.path = []
|
||||
# create top
|
||||
self.top = top = ListedToplevel(root)
|
||||
top.protocol("WM_DELETE_WINDOW", self.close)
|
||||
top.bind("<Escape>", self.close)
|
||||
self.settitle()
|
||||
# create frames and separators in between
|
||||
self.frames = []
|
||||
self.separators = []
|
||||
last = top
|
||||
for i in range(nlists-1):
|
||||
sepa = HSeparator(last)
|
||||
self.separators.append(sepa)
|
||||
frame, last = sepa.parts()
|
||||
self.frames.append(frame)
|
||||
self.frames.append(last)
|
||||
# create labels and lists
|
||||
self.labels = []
|
||||
self.lists = []
|
||||
for i in range(nlists):
|
||||
frame = self.frames[i]
|
||||
label = Label(frame, text=self.subtitle(i),
|
||||
relief="groove", borderwidth=2)
|
||||
label.pack(fill="x")
|
||||
self.labels.append(label)
|
||||
list = ScrolledList(frame, width=self.width(i),
|
||||
height=self.height(i))
|
||||
self.lists.append(list)
|
||||
list.on_select = \
|
||||
lambda index, i=i, self=self: self.on_select(index, i)
|
||||
list.on_double = \
|
||||
lambda index, i=i, self=self: self.on_double(index, i)
|
||||
# fill leftmost list (rest get filled on demand)
|
||||
self.fill(0)
|
||||
# XXX one after_idle isn't enough; two are...
|
||||
top.after_idle(self.call_pack_propagate_1)
|
||||
|
||||
def call_pack_propagate_1(self):
|
||||
self.top.after_idle(self.call_pack_propagate)
|
||||
|
||||
def call_pack_propagate(self):
|
||||
for frame in self.frames:
|
||||
frame.pack_propagate(0)
|
||||
|
||||
def close(self, event=None):
|
||||
self.top.destroy()
|
||||
|
||||
def settitle(self):
|
||||
short = self.shorttitle()
|
||||
long = self.longtitle()
|
||||
if short and long:
|
||||
title = short + " - " + long
|
||||
elif short:
|
||||
title = short
|
||||
elif long:
|
||||
title = long
|
||||
else:
|
||||
title = "Untitled"
|
||||
icon = short or long or title
|
||||
self.top.wm_title(title)
|
||||
self.top.wm_iconname(icon)
|
||||
|
||||
def longtitle(self):
|
||||
# override this
|
||||
return "Multi Scrolled Lists"
|
||||
|
||||
def shorttitle(self):
|
||||
# override this
|
||||
return None
|
||||
|
||||
def width(self, i):
|
||||
# override this
|
||||
return 20
|
||||
|
||||
def height(self, i):
|
||||
# override this
|
||||
return 10
|
||||
|
||||
def subtitle(self, i):
|
||||
# override this
|
||||
return "Column %d" % i
|
||||
|
||||
def fill(self, i):
|
||||
for k in range(i, self.nlists):
|
||||
self.lists[k].clear()
|
||||
self.labels[k].configure(text=self.subtitle(k))
|
||||
list = self.lists[i]
|
||||
l = self.items(i)
|
||||
for s in l:
|
||||
list.append(s)
|
||||
|
||||
def on_select(self, index, i):
|
||||
item = self.lists[i].get(index)
|
||||
del self.path[i:]
|
||||
self.path.append(item)
|
||||
if i+1 < self.nlists:
|
||||
self.fill(i+1)
|
||||
|
||||
def items(self, i):
|
||||
# override this
|
||||
l = []
|
||||
for k in range(10):
|
||||
s = str(k)
|
||||
if i > 0:
|
||||
s = self.path[i-1] + "." + s
|
||||
l.append(s)
|
||||
return l
|
||||
|
||||
def on_double(self, index, i):
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
root = Tk()
|
||||
quit = Button(root, text="Exit", command=root.destroy)
|
||||
quit.pack()
|
||||
MultiScrolledLists(root, 4)
|
||||
root.mainloop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,32 @@
|
|||
from Tkinter import *
|
||||
|
||||
class MultiStatusBar(Frame):
|
||||
|
||||
def __init__(self, master=None, **kw):
|
||||
if master is None:
|
||||
master = Tk()
|
||||
apply(Frame.__init__, (self, master), kw)
|
||||
self.labels = {}
|
||||
|
||||
def set_label(self, name, text='', side=LEFT):
|
||||
if not self.labels.has_key(name):
|
||||
label = Label(self, bd=1, relief=SUNKEN, anchor=W)
|
||||
label.pack(side=side)
|
||||
self.labels[name] = label
|
||||
else:
|
||||
label = self.labels[name]
|
||||
label.config(text=text)
|
||||
|
||||
def _test():
|
||||
b = Frame()
|
||||
c = Text(b)
|
||||
c.pack(side=TOP)
|
||||
a = MultiStatusBar(b)
|
||||
a.set_label("one", "hello")
|
||||
a.set_label("two", "world")
|
||||
a.pack(side=BOTTOM, fill=X)
|
||||
b.pack()
|
||||
b.mainloop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
_test()
|
|
@ -0,0 +1,130 @@
|
|||
(For a more detailed change log, see the file ChangeLog.)
|
||||
|
||||
----------------------------------------------------------------------
|
||||
|
||||
New in IDLE 0.5 (2/15/2000)
|
||||
-------------------------
|
||||
|
||||
Tons of stuff, much of it contributed by Tim Peters and Mark Hammond:
|
||||
|
||||
- Status bar, displaying current line/column (Moshe Zadka).
|
||||
|
||||
- Better stack viewer, using tree widget. (XXX Only used by Stack
|
||||
Viewer menu, not by the debugger.)
|
||||
|
||||
- Format paragraph now recognizes Python block comments and reformats
|
||||
them correctly (MH)
|
||||
|
||||
- New version of pyclbr.py parses top-level functions and understands
|
||||
much more of Python's syntax; this is reflected in the class and path
|
||||
browsers (TP)
|
||||
|
||||
- Much better auto-indent; knows how to indent the insides of
|
||||
multi-line statements (TP)
|
||||
|
||||
- Call tip window pops up when you type the name of a known function
|
||||
followed by an open parenthesis. Hit ESC or click elsewhere in the
|
||||
window to close the tip window (MH)
|
||||
|
||||
- Comment out region now inserts ## to make it stand out more (TP)
|
||||
|
||||
- New path and class browsers based on a tree widget that looks
|
||||
familiar to Windows users
|
||||
|
||||
- Reworked script running commands to be more intuitive: I/O now
|
||||
always goes to the *Python Shell* window, and raw_input() works
|
||||
correctly. You use F5 to import/reload a module: this adds the module
|
||||
name to the __main__ namespace. You use Control-F5 to run a script:
|
||||
this runs the script *in* the __main__ namespace. The latter also
|
||||
sets sys.argv[] to the script name
|
||||
|
||||
New in IDLE 0.4 (4/7/99)
|
||||
------------------------
|
||||
|
||||
Most important change: a new menu entry "File -> Path browser", shows
|
||||
a 4-column hierarchical browser which lets you browse sys.path,
|
||||
directories, modules, and classes. Yes, it's a superset of the Class
|
||||
browser menu entry. There's also a new internal module,
|
||||
MultiScrolledLists.py, which provides the framework for this dialog.
|
||||
|
||||
New in IDLE 0.3 (2/17/99)
|
||||
-------------------------
|
||||
|
||||
Most important changes:
|
||||
|
||||
- Enabled support for running a module, with or without the debugger.
|
||||
Output goes to a new window. Pressing F5 in a module is effectively a
|
||||
reload of that module; Control-F5 loads it under the debugger.
|
||||
|
||||
- Re-enable tearing off the Windows menu, and make a torn-off Windows
|
||||
menu update itself whenever a window is opened or closed.
|
||||
|
||||
- Menu items can now be have a checkbox (when the menu label starts
|
||||
with "!"); use this for the Debugger and "Auto-open stack viewer"
|
||||
(was: JIT stack viewer) menu items.
|
||||
|
||||
- Added a Quit button to the Debugger API.
|
||||
|
||||
- The current directory is explicitly inserted into sys.path.
|
||||
|
||||
- Fix the debugger (when using Python 1.5.2b2) to use canonical
|
||||
filenames for breakpoints, so these actually work. (There's still a
|
||||
lot of work to be done to the management of breakpoints in the
|
||||
debugger though.)
|
||||
|
||||
- Closing a window that is still colorizing now actually works.
|
||||
|
||||
- Allow dragging of the separator between the two list boxes in the
|
||||
class browser.
|
||||
|
||||
- Bind ESC to "close window" of the debugger, stack viewer and class
|
||||
browser. It removes the selection highlighting in regular text
|
||||
windows. (These are standard Windows conventions.)
|
||||
|
||||
----------------------------------------------------------------------
|
||||
|
||||
New in IDLE 0.2 (1/8/99)
|
||||
------------------------
|
||||
|
||||
Lots of changes; here are the highlights:
|
||||
|
||||
General:
|
||||
|
||||
- You can now write and configure your own IDLE extension modules; see
|
||||
extend.txt.
|
||||
|
||||
|
||||
File menu:
|
||||
|
||||
The command to open the Python shell window is now in the File menu.
|
||||
|
||||
|
||||
Edit menu:
|
||||
|
||||
New Find dialog with more options; replace dialog; find in files dialog.
|
||||
|
||||
Commands to tabify or untabify a region.
|
||||
|
||||
Command to format a paragraph.
|
||||
|
||||
|
||||
Debug menu:
|
||||
|
||||
JIT (Just-In-Time) stack viewer toggle -- if set, the stack viewer
|
||||
automaticall pops up when you get a traceback.
|
||||
|
||||
Windows menu:
|
||||
|
||||
Zoom height -- make the window full height.
|
||||
|
||||
|
||||
Help menu:
|
||||
|
||||
The help text now show up in a regular window so you can search and
|
||||
even edit it if you like.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
|
||||
IDLE 0.1 was distributed with the Python 1.5.2b1 release on 12/22/98.
|
||||
|
||||
======================================================================
|
|
@ -0,0 +1,151 @@
|
|||
# XXX TO DO:
|
||||
# - popup menu
|
||||
# - support partial or total redisplay
|
||||
# - more doc strings
|
||||
# - tooltips
|
||||
|
||||
# object browser
|
||||
|
||||
# XXX TO DO:
|
||||
# - for classes/modules, add "open source" to object browser
|
||||
|
||||
from TreeWidget import TreeItem, TreeNode, ScrolledCanvas
|
||||
|
||||
from repr import Repr
|
||||
|
||||
myrepr = Repr()
|
||||
myrepr.maxstring = 100
|
||||
myrepr.maxother = 100
|
||||
|
||||
class ObjectTreeItem(TreeItem):
|
||||
def __init__(self, labeltext, object, setfunction=None):
|
||||
self.labeltext = labeltext
|
||||
self.object = object
|
||||
self.setfunction = setfunction
|
||||
def GetLabelText(self):
|
||||
return self.labeltext
|
||||
def GetText(self):
|
||||
return myrepr.repr(self.object)
|
||||
def GetIconName(self):
|
||||
if not self.IsExpandable():
|
||||
return "python"
|
||||
def IsEditable(self):
|
||||
return self.setfunction is not None
|
||||
def SetText(self, text):
|
||||
try:
|
||||
value = eval(text)
|
||||
self.setfunction(value)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
self.object = value
|
||||
def IsExpandable(self):
|
||||
return not not dir(self.object)
|
||||
def GetSubList(self):
|
||||
keys = dir(self.object)
|
||||
sublist = []
|
||||
for key in keys:
|
||||
try:
|
||||
value = getattr(self.object, key)
|
||||
except AttributeError:
|
||||
continue
|
||||
item = make_objecttreeitem(
|
||||
str(key) + " =",
|
||||
value,
|
||||
lambda value, key=key, object=self.object:
|
||||
setattr(object, key, value))
|
||||
sublist.append(item)
|
||||
return sublist
|
||||
|
||||
class InstanceTreeItem(ObjectTreeItem):
|
||||
def IsExpandable(self):
|
||||
return 1
|
||||
def GetSubList(self):
|
||||
sublist = ObjectTreeItem.GetSubList(self)
|
||||
sublist.insert(0,
|
||||
make_objecttreeitem("__class__ =", self.object.__class__))
|
||||
return sublist
|
||||
|
||||
class ClassTreeItem(ObjectTreeItem):
|
||||
def IsExpandable(self):
|
||||
return 1
|
||||
def GetSubList(self):
|
||||
sublist = ObjectTreeItem.GetSubList(self)
|
||||
if len(self.object.__bases__) == 1:
|
||||
item = make_objecttreeitem("__bases__[0] =",
|
||||
self.object.__bases__[0])
|
||||
else:
|
||||
item = make_objecttreeitem("__bases__ =", self.object.__bases__)
|
||||
sublist.insert(0, item)
|
||||
return sublist
|
||||
|
||||
class AtomicObjectTreeItem(ObjectTreeItem):
|
||||
def IsExpandable(self):
|
||||
return 0
|
||||
|
||||
class SequenceTreeItem(ObjectTreeItem):
|
||||
def IsExpandable(self):
|
||||
return len(self.object) > 0
|
||||
def keys(self):
|
||||
return range(len(self.object))
|
||||
def GetSubList(self):
|
||||
sublist = []
|
||||
for key in self.keys():
|
||||
try:
|
||||
value = self.object[key]
|
||||
except KeyError:
|
||||
continue
|
||||
def setfunction(value, key=key, object=self.object):
|
||||
object[key] = value
|
||||
item = make_objecttreeitem(`key` + ":", value, setfunction)
|
||||
sublist.append(item)
|
||||
return sublist
|
||||
|
||||
class DictTreeItem(SequenceTreeItem):
|
||||
def keys(self):
|
||||
keys = self.object.keys()
|
||||
try:
|
||||
keys.sort()
|
||||
except:
|
||||
pass
|
||||
return keys
|
||||
|
||||
from types import *
|
||||
|
||||
dispatch = {
|
||||
IntType: AtomicObjectTreeItem,
|
||||
LongType: AtomicObjectTreeItem,
|
||||
FloatType: AtomicObjectTreeItem,
|
||||
StringType: AtomicObjectTreeItem,
|
||||
TupleType: SequenceTreeItem,
|
||||
ListType: SequenceTreeItem,
|
||||
DictType: DictTreeItem,
|
||||
InstanceType: InstanceTreeItem,
|
||||
ClassType: ClassTreeItem,
|
||||
}
|
||||
|
||||
def make_objecttreeitem(labeltext, object, setfunction=None):
|
||||
t = type(object)
|
||||
if dispatch.has_key(t):
|
||||
c = dispatch[t]
|
||||
else:
|
||||
c = ObjectTreeItem
|
||||
return c(labeltext, object, setfunction)
|
||||
|
||||
# Test script
|
||||
|
||||
def test():
|
||||
import sys
|
||||
from Tkinter import Toplevel
|
||||
import PyShell
|
||||
root = Toplevel(PyShell.root)
|
||||
root.configure(bd=0, bg="yellow")
|
||||
root.focus_set()
|
||||
sc = ScrolledCanvas(root, bg="white", highlightthickness=0, takefocus=1)
|
||||
sc.frame.pack(expand=1, fill="both")
|
||||
item = make_objecttreeitem("sys", sys)
|
||||
node = TreeNode(sc.canvas, None, item)
|
||||
node.expand()
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
|
@ -0,0 +1,276 @@
|
|||
import string
|
||||
import sys
|
||||
import os
|
||||
from Tkinter import *
|
||||
import linecache
|
||||
from repr import Repr
|
||||
from WindowList import ListedToplevel
|
||||
|
||||
from ScrolledList import ScrolledList
|
||||
|
||||
|
||||
class StackBrowser:
|
||||
|
||||
def __init__(self, root, flist, stack=None):
|
||||
self.top = top = ListedToplevel(root)
|
||||
top.protocol("WM_DELETE_WINDOW", self.close)
|
||||
top.bind("<Key-Escape>", self.close)
|
||||
top.wm_title("Stack viewer")
|
||||
top.wm_iconname("Stack")
|
||||
# Create help label
|
||||
self.helplabel = Label(top,
|
||||
text="Click once to view variables; twice for source",
|
||||
borderwidth=2, relief="groove")
|
||||
self.helplabel.pack(fill="x")
|
||||
#
|
||||
self.sv = StackViewer(top, flist, self)
|
||||
if stack is None:
|
||||
stack = get_stack()
|
||||
self.sv.load_stack(stack)
|
||||
|
||||
def close(self, event=None):
|
||||
self.top.destroy()
|
||||
|
||||
localsframe = None
|
||||
localsviewer = None
|
||||
localsdict = None
|
||||
globalsframe = None
|
||||
globalsviewer = None
|
||||
globalsdict = None
|
||||
curframe = None
|
||||
|
||||
def show_frame(self, (frame, lineno)):
|
||||
if frame is self.curframe:
|
||||
return
|
||||
self.curframe = None
|
||||
if frame.f_globals is not self.globalsdict:
|
||||
self.show_globals(frame)
|
||||
self.show_locals(frame)
|
||||
self.curframe = frame
|
||||
|
||||
def show_globals(self, frame):
|
||||
title = "Global Variables"
|
||||
if frame.f_globals.has_key("__name__"):
|
||||
try:
|
||||
name = str(frame.f_globals["__name__"]) + ""
|
||||
except:
|
||||
name = ""
|
||||
if name:
|
||||
title = title + " in module " + name
|
||||
self.globalsdict = None
|
||||
if self.globalsviewer:
|
||||
self.globalsviewer.close()
|
||||
self.globalsviewer = None
|
||||
if not self.globalsframe:
|
||||
self.globalsframe = Frame(self.top)
|
||||
self.globalsdict = frame.f_globals
|
||||
self.globalsviewer = NamespaceViewer(
|
||||
self.globalsframe,
|
||||
title,
|
||||
self.globalsdict)
|
||||
self.globalsframe.pack(fill="both", side="bottom")
|
||||
|
||||
def show_locals(self, frame):
|
||||
self.localsdict = None
|
||||
if self.localsviewer:
|
||||
self.localsviewer.close()
|
||||
self.localsviewer = None
|
||||
if frame.f_locals is not frame.f_globals:
|
||||
title = "Local Variables"
|
||||
code = frame.f_code
|
||||
funcname = code.co_name
|
||||
if funcname not in ("?", "", None):
|
||||
title = title + " in " + funcname
|
||||
if not self.localsframe:
|
||||
self.localsframe = Frame(self.top)
|
||||
self.localsdict = frame.f_locals
|
||||
self.localsviewer = NamespaceViewer(
|
||||
self.localsframe,
|
||||
title,
|
||||
self.localsdict)
|
||||
self.localsframe.pack(fill="both", side="top")
|
||||
else:
|
||||
if self.localsframe:
|
||||
self.localsframe.forget()
|
||||
|
||||
|
||||
class StackViewer(ScrolledList):
|
||||
|
||||
def __init__(self, master, flist, browser):
|
||||
ScrolledList.__init__(self, master, width=80)
|
||||
self.flist = flist
|
||||
self.browser = browser
|
||||
self.stack = []
|
||||
|
||||
def load_stack(self, stack, index=None):
|
||||
self.stack = stack
|
||||
self.clear()
|
||||
## if len(stack) > 10:
|
||||
## l["height"] = 10
|
||||
## self.topframe.pack(expand=1)
|
||||
## else:
|
||||
## l["height"] = len(stack)
|
||||
## self.topframe.pack(expand=0)
|
||||
for i in range(len(stack)):
|
||||
frame, lineno = stack[i]
|
||||
try:
|
||||
modname = frame.f_globals["__name__"]
|
||||
except:
|
||||
modname = "?"
|
||||
code = frame.f_code
|
||||
filename = code.co_filename
|
||||
funcname = code.co_name
|
||||
sourceline = linecache.getline(filename, lineno)
|
||||
sourceline = string.strip(sourceline)
|
||||
if funcname in ("?", "", None):
|
||||
item = "%s, line %d: %s" % (modname, lineno, sourceline)
|
||||
else:
|
||||
item = "%s.%s(), line %d: %s" % (modname, funcname,
|
||||
lineno, sourceline)
|
||||
if i == index:
|
||||
item = "> " + item
|
||||
self.append(item)
|
||||
if index is not None:
|
||||
self.select(index)
|
||||
|
||||
def popup_event(self, event):
|
||||
if self.stack:
|
||||
return ScrolledList.popup_event(self, event)
|
||||
|
||||
def fill_menu(self):
|
||||
menu = self.menu
|
||||
menu.add_command(label="Go to source line",
|
||||
command=self.goto_source_line)
|
||||
menu.add_command(label="Show stack frame",
|
||||
command=self.show_stack_frame)
|
||||
|
||||
def on_select(self, index):
|
||||
if 0 <= index < len(self.stack):
|
||||
self.browser.show_frame(self.stack[index])
|
||||
|
||||
def on_double(self, index):
|
||||
self.show_source(index)
|
||||
|
||||
def goto_source_line(self):
|
||||
index = self.listbox.index("active")
|
||||
self.show_source(index)
|
||||
|
||||
def show_stack_frame(self):
|
||||
index = self.listbox.index("active")
|
||||
if 0 <= index < len(self.stack):
|
||||
self.browser.show_frame(self.stack[index])
|
||||
|
||||
def show_source(self, index):
|
||||
if not (0 <= index < len(self.stack)):
|
||||
return
|
||||
frame, lineno = self.stack[index]
|
||||
code = frame.f_code
|
||||
filename = code.co_filename
|
||||
if os.path.isfile(filename):
|
||||
edit = self.flist.open(filename)
|
||||
if edit:
|
||||
edit.gotoline(lineno)
|
||||
|
||||
|
||||
def get_stack(t=None, f=None):
|
||||
if t is None:
|
||||
t = sys.last_traceback
|
||||
stack = []
|
||||
if t and t.tb_frame is f:
|
||||
t = t.tb_next
|
||||
while f is not None:
|
||||
stack.append((f, f.f_lineno))
|
||||
if f is self.botframe:
|
||||
break
|
||||
f = f.f_back
|
||||
stack.reverse()
|
||||
while t is not None:
|
||||
stack.append((t.tb_frame, t.tb_lineno))
|
||||
t = t.tb_next
|
||||
return stack
|
||||
|
||||
|
||||
def getexception(type=None, value=None):
|
||||
if type is None:
|
||||
type = sys.last_type
|
||||
value = sys.last_value
|
||||
if hasattr(type, "__name__"):
|
||||
type = type.__name__
|
||||
s = str(type)
|
||||
if value is not None:
|
||||
s = s + ": " + str(value)
|
||||
return s
|
||||
|
||||
|
||||
class NamespaceViewer:
|
||||
|
||||
def __init__(self, master, title, dict=None):
|
||||
width = 0
|
||||
height = 40
|
||||
if dict:
|
||||
height = 20*len(dict) # XXX 20 == observed height of Entry widget
|
||||
self.master = master
|
||||
self.title = title
|
||||
self.repr = Repr()
|
||||
self.repr.maxstring = 60
|
||||
self.repr.maxother = 60
|
||||
self.frame = frame = Frame(master)
|
||||
self.frame.pack(expand=1, fill="both")
|
||||
self.label = Label(frame, text=title, borderwidth=2, relief="groove")
|
||||
self.label.pack(fill="x")
|
||||
self.vbar = vbar = Scrollbar(frame, name="vbar")
|
||||
vbar.pack(side="right", fill="y")
|
||||
self.canvas = canvas = Canvas(frame,
|
||||
height=min(300, max(40, height)),
|
||||
scrollregion=(0, 0, width, height))
|
||||
canvas.pack(side="left", fill="both", expand=1)
|
||||
vbar["command"] = canvas.yview
|
||||
canvas["yscrollcommand"] = vbar.set
|
||||
self.subframe = subframe = Frame(canvas)
|
||||
self.sfid = canvas.create_window(0, 0, window=subframe, anchor="nw")
|
||||
self.load_dict(dict)
|
||||
|
||||
dict = -1
|
||||
|
||||
def load_dict(self, dict, force=0):
|
||||
if dict is self.dict and not force:
|
||||
return
|
||||
subframe = self.subframe
|
||||
frame = self.frame
|
||||
for c in subframe.children.values():
|
||||
c.destroy()
|
||||
self.dict = None
|
||||
if not dict:
|
||||
l = Label(subframe, text="None")
|
||||
l.grid(row=0, column=0)
|
||||
else:
|
||||
names = dict.keys()
|
||||
names.sort()
|
||||
row = 0
|
||||
for name in names:
|
||||
value = dict[name]
|
||||
svalue = self.repr.repr(value) # repr(value)
|
||||
l = Label(subframe, text=name)
|
||||
l.grid(row=row, column=0, sticky="nw")
|
||||
## l = Label(subframe, text=svalue, justify="l", wraplength=300)
|
||||
l = Entry(subframe, width=0, borderwidth=0)
|
||||
l.insert(0, svalue)
|
||||
## l["state"] = "disabled"
|
||||
l.grid(row=row, column=1, sticky="nw")
|
||||
row = row+1
|
||||
self.dict = dict
|
||||
# XXX Could we use a <Configure> callback for the following?
|
||||
subframe.update_idletasks() # Alas!
|
||||
width = subframe.winfo_reqwidth()
|
||||
height = subframe.winfo_reqheight()
|
||||
canvas = self.canvas
|
||||
self.canvas["scrollregion"] = (0, 0, width, height)
|
||||
if height > 300:
|
||||
canvas["height"] = 300
|
||||
frame.pack(expand=1)
|
||||
else:
|
||||
canvas["height"] = height
|
||||
frame.pack(expand=0)
|
||||
|
||||
def close(self):
|
||||
self.frame.destroy()
|
|
@ -0,0 +1,279 @@
|
|||
# changes by dscherer@cmu.edu
|
||||
# - OutputWindow and OnDemandOutputWindow have been hastily
|
||||
# extended to provide readline() support, an "iomark" separate
|
||||
# from the "insert" cursor, and scrolling to clear the window.
|
||||
# These changes are used by the ExecBinding module to provide
|
||||
# standard input and output for user programs. Many of the new
|
||||
# features are very similar to features of PyShell, which is a
|
||||
# subclass of OutputWindow. Someone should make some sense of
|
||||
# this.
|
||||
|
||||
from Tkinter import *
|
||||
from EditorWindow import EditorWindow
|
||||
import re
|
||||
import tkMessageBox
|
||||
|
||||
from UndoDelegator import UndoDelegator
|
||||
|
||||
class OutputUndoDelegator(UndoDelegator):
|
||||
reading = 0
|
||||
# Forbid insert/delete before the I/O mark, in the blank lines after
|
||||
# the output, or *anywhere* if we are not presently doing user input
|
||||
def insert(self, index, chars, tags=None):
|
||||
try:
|
||||
if (self.delegate.compare(index, "<", "iomark") or
|
||||
self.delegate.compare(index, ">", "endmark") or
|
||||
(index!="iomark" and not self.reading)):
|
||||
self.delegate.bell()
|
||||
return
|
||||
except TclError:
|
||||
pass
|
||||
UndoDelegator.insert(self, index, chars, tags)
|
||||
def delete(self, index1, index2=None):
|
||||
try:
|
||||
if (self.delegate.compare(index1, "<", "iomark") or
|
||||
self.delegate.compare(index1, ">", "endmark") or
|
||||
(index2 and self.delegate.compare(index2, ">=", "endmark")) or
|
||||
not self.reading):
|
||||
self.delegate.bell()
|
||||
return
|
||||
except TclError:
|
||||
pass
|
||||
UndoDelegator.delete(self, index1, index2)
|
||||
|
||||
class OutputWindow(EditorWindow):
|
||||
"""An editor window that can serve as an input and output file.
|
||||
The input support has been rather hastily hacked in, and should
|
||||
not be trusted.
|
||||
"""
|
||||
|
||||
UndoDelegator = OutputUndoDelegator
|
||||
source_window = None
|
||||
|
||||
def __init__(self, *args, **keywords):
|
||||
if keywords.has_key('source_window'):
|
||||
self.source_window = keywords['source_window']
|
||||
apply(EditorWindow.__init__, (self,) + args)
|
||||
self.text.bind("<<goto-file-line>>", self.goto_file_line)
|
||||
self.text.bind("<<newline-and-indent>>", self.enter_callback)
|
||||
self.text.mark_set("iomark","1.0")
|
||||
self.text.mark_gravity("iomark", LEFT)
|
||||
self.text.mark_set("endmark","1.0")
|
||||
|
||||
# Customize EditorWindow
|
||||
|
||||
def ispythonsource(self, filename):
|
||||
# No colorization needed
|
||||
return 0
|
||||
|
||||
def short_title(self):
|
||||
return "Output"
|
||||
|
||||
def long_title(self):
|
||||
return ""
|
||||
|
||||
def maybesave(self):
|
||||
# Override base class method -- don't ask any questions
|
||||
if self.get_saved():
|
||||
return "yes"
|
||||
else:
|
||||
return "no"
|
||||
|
||||
# Act as input file - incomplete
|
||||
|
||||
def set_line_and_column(self, event=None):
|
||||
index = self.text.index(INSERT)
|
||||
if (self.text.compare(index, ">", "endmark")):
|
||||
self.text.mark_set("insert", "endmark")
|
||||
self.text.see("insert")
|
||||
EditorWindow.set_line_and_column(self)
|
||||
|
||||
reading = 0
|
||||
canceled = 0
|
||||
endoffile = 0
|
||||
|
||||
def readline(self):
|
||||
save = self.reading
|
||||
try:
|
||||
self.reading = self.undo.reading = 1
|
||||
self.text.mark_set("insert", "iomark")
|
||||
self.text.see("insert")
|
||||
self.top.mainloop()
|
||||
finally:
|
||||
self.reading = self.undo.reading = save
|
||||
line = self.text.get("input", "iomark")
|
||||
if self.canceled:
|
||||
self.canceled = 0
|
||||
raise KeyboardInterrupt
|
||||
if self.endoffile:
|
||||
self.endoffile = 0
|
||||
return ""
|
||||
return line or '\n'
|
||||
|
||||
def close(self):
|
||||
self.interrupt()
|
||||
return EditorWindow.close(self)
|
||||
|
||||
def interrupt(self):
|
||||
if self.reading:
|
||||
self.endoffile = 1
|
||||
self.top.quit()
|
||||
|
||||
def enter_callback(self, event):
|
||||
if self.reading and self.text.compare("insert", ">=", "iomark"):
|
||||
self.text.mark_set("input", "iomark")
|
||||
self.text.mark_set("iomark", "insert")
|
||||
self.write('\n',"iomark")
|
||||
self.text.tag_add("stdin", "input", "iomark")
|
||||
self.text.update_idletasks()
|
||||
self.top.quit() # Break out of recursive mainloop() in raw_input()
|
||||
|
||||
return "break"
|
||||
|
||||
# Act as output file
|
||||
|
||||
def write(self, s, tags=(), mark="iomark"):
|
||||
self.text.mark_gravity(mark, RIGHT)
|
||||
self.text.insert(mark, str(s), tags)
|
||||
self.text.mark_gravity(mark, LEFT)
|
||||
self.text.see(mark)
|
||||
self.text.update()
|
||||
|
||||
def writelines(self, l):
|
||||
map(self.write, l)
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
# Our own right-button menu
|
||||
|
||||
rmenu_specs = [
|
||||
("Go to file/line", "<<goto-file-line>>"),
|
||||
]
|
||||
|
||||
file_line_pats = [
|
||||
r'file "([^"]*)", line (\d+)',
|
||||
r'([^\s]+)\((\d+)\)',
|
||||
r'([^\s]+):\s*(\d+):',
|
||||
]
|
||||
|
||||
file_line_progs = None
|
||||
|
||||
def goto_file_line(self, event=None):
|
||||
if self.file_line_progs is None:
|
||||
l = []
|
||||
for pat in self.file_line_pats:
|
||||
l.append(re.compile(pat, re.IGNORECASE))
|
||||
self.file_line_progs = l
|
||||
# x, y = self.event.x, self.event.y
|
||||
# self.text.mark_set("insert", "@%d,%d" % (x, y))
|
||||
line = self.text.get("insert linestart", "insert lineend")
|
||||
result = self._file_line_helper(line)
|
||||
if not result:
|
||||
# Try the previous line. This is handy e.g. in tracebacks,
|
||||
# where you tend to right-click on the displayed source line
|
||||
line = self.text.get("insert -1line linestart",
|
||||
"insert -1line lineend")
|
||||
result = self._file_line_helper(line)
|
||||
if not result:
|
||||
tkMessageBox.showerror(
|
||||
"No special line",
|
||||
"The line you point at doesn't look like "
|
||||
"a valid file name followed by a line number.",
|
||||
master=self.text)
|
||||
return
|
||||
filename, lineno = result
|
||||
edit = self.untitled(filename) or self.flist.open(filename)
|
||||
edit.gotoline(lineno)
|
||||
edit.wakeup()
|
||||
|
||||
def untitled(self, filename):
|
||||
if filename!='Untitled' or not self.source_window or self.source_window.io.filename:
|
||||
return None
|
||||
return self.source_window
|
||||
|
||||
def _file_line_helper(self, line):
|
||||
for prog in self.file_line_progs:
|
||||
m = prog.search(line)
|
||||
if m:
|
||||
break
|
||||
else:
|
||||
return None
|
||||
filename, lineno = m.group(1, 2)
|
||||
if not self.untitled(filename):
|
||||
try:
|
||||
f = open(filename, "r")
|
||||
f.close()
|
||||
except IOError:
|
||||
return None
|
||||
try:
|
||||
return filename, int(lineno)
|
||||
except TypeError:
|
||||
return None
|
||||
|
||||
# This classes now used by ExecBinding.py:
|
||||
|
||||
class OnDemandOutputWindow:
|
||||
source_window = None
|
||||
|
||||
tagdefs = {
|
||||
# XXX Should use IdlePrefs.ColorPrefs
|
||||
"stdin": {"foreground": "black"},
|
||||
"stdout": {"foreground": "blue"},
|
||||
"stderr": {"foreground": "red"},
|
||||
}
|
||||
|
||||
def __init__(self, flist):
|
||||
self.flist = flist
|
||||
self.owin = None
|
||||
self.title = "Output"
|
||||
self.close_hook = None
|
||||
self.old_close = None
|
||||
|
||||
def owclose(self):
|
||||
if self.close_hook:
|
||||
self.close_hook()
|
||||
if self.old_close:
|
||||
self.old_close()
|
||||
|
||||
def set_title(self, title):
|
||||
self.title = title
|
||||
if self.owin and self.owin.text:
|
||||
self.owin.saved_change_hook()
|
||||
|
||||
def write(self, s, tags=(), mark="iomark"):
|
||||
if not self.owin or not self.owin.text:
|
||||
self.setup()
|
||||
self.owin.write(s, tags, mark)
|
||||
|
||||
def readline(self):
|
||||
if not self.owin or not self.owin.text:
|
||||
self.setup()
|
||||
return self.owin.readline()
|
||||
|
||||
def scroll_clear(self):
|
||||
if self.owin and self.owin.text:
|
||||
lineno = self.owin.getlineno("endmark")
|
||||
self.owin.text.mark_set("insert","endmark")
|
||||
self.owin.text.yview(float(lineno))
|
||||
self.owin.wakeup()
|
||||
|
||||
def setup(self):
|
||||
self.owin = owin = OutputWindow(self.flist, source_window = self.source_window)
|
||||
owin.short_title = lambda self=self: self.title
|
||||
text = owin.text
|
||||
|
||||
self.old_close = owin.close_hook
|
||||
owin.close_hook = self.owclose
|
||||
|
||||
# xxx Bad hack: 50 blank lines at the bottom so that
|
||||
# we can scroll the top of the window to the output
|
||||
# cursor in scroll_clear(). There must be a better way...
|
||||
owin.text.mark_gravity('endmark', LEFT)
|
||||
owin.text.insert('iomark', '\n'*50)
|
||||
owin.text.mark_gravity('endmark', RIGHT)
|
||||
|
||||
for tag, cnf in self.tagdefs.items():
|
||||
if cnf:
|
||||
apply(text.tag_configure, (tag,), cnf)
|
||||
text.tag_raise('sel')
|
|
@ -0,0 +1,192 @@
|
|||
"""ParenMatch -- An IDLE extension for parenthesis matching.
|
||||
|
||||
When you hit a right paren, the cursor should move briefly to the left
|
||||
paren. Paren here is used generically; the matching applies to
|
||||
parentheses, square brackets, and curly braces.
|
||||
|
||||
WARNING: This extension will fight with the CallTips extension,
|
||||
because they both are interested in the KeyRelease-parenright event.
|
||||
We'll have to fix IDLE to do something reasonable when two or more
|
||||
extensions what to capture the same event.
|
||||
"""
|
||||
|
||||
import string
|
||||
|
||||
import PyParse
|
||||
from AutoIndent import AutoIndent, index2line
|
||||
from IdleConf import idleconf
|
||||
|
||||
class ParenMatch:
|
||||
"""Highlight matching parentheses
|
||||
|
||||
There are three supported style of paren matching, based loosely
|
||||
on the Emacs options. The style is select based on the
|
||||
HILITE_STYLE attribute; it can be changed used the set_style
|
||||
method.
|
||||
|
||||
The supported styles are:
|
||||
|
||||
default -- When a right paren is typed, highlight the matching
|
||||
left paren for 1/2 sec.
|
||||
|
||||
expression -- When a right paren is typed, highlight the entire
|
||||
expression from the left paren to the right paren.
|
||||
|
||||
TODO:
|
||||
- fix interaction with CallTips
|
||||
- extend IDLE with configuration dialog to change options
|
||||
- implement rest of Emacs highlight styles (see below)
|
||||
- print mismatch warning in IDLE status window
|
||||
|
||||
Note: In Emacs, there are several styles of highlight where the
|
||||
matching paren is highlighted whenever the cursor is immediately
|
||||
to the right of a right paren. I don't know how to do that in Tk,
|
||||
so I haven't bothered.
|
||||
"""
|
||||
|
||||
menudefs = []
|
||||
|
||||
keydefs = {
|
||||
'<<flash-open-paren>>' : ('<KeyRelease-parenright>',
|
||||
'<KeyRelease-bracketright>',
|
||||
'<KeyRelease-braceright>'),
|
||||
'<<check-restore>>' : ('<KeyPress>',),
|
||||
}
|
||||
|
||||
windows_keydefs = {}
|
||||
unix_keydefs = {}
|
||||
|
||||
iconf = idleconf.getsection('ParenMatch')
|
||||
STYLE = iconf.getdef('style', 'default')
|
||||
FLASH_DELAY = iconf.getint('flash-delay')
|
||||
HILITE_CONFIG = iconf.getcolor('hilite')
|
||||
BELL = iconf.getboolean('bell')
|
||||
del iconf
|
||||
|
||||
def __init__(self, editwin):
|
||||
self.editwin = editwin
|
||||
self.text = editwin.text
|
||||
self.finder = LastOpenBracketFinder(editwin)
|
||||
self.counter = 0
|
||||
self._restore = None
|
||||
self.set_style(self.STYLE)
|
||||
|
||||
def set_style(self, style):
|
||||
self.STYLE = style
|
||||
if style == "default":
|
||||
self.create_tag = self.create_tag_default
|
||||
self.set_timeout = self.set_timeout_last
|
||||
elif style == "expression":
|
||||
self.create_tag = self.create_tag_expression
|
||||
self.set_timeout = self.set_timeout_none
|
||||
|
||||
def flash_open_paren_event(self, event):
|
||||
index = self.finder.find(keysym_type(event.keysym))
|
||||
if index is None:
|
||||
self.warn_mismatched()
|
||||
return
|
||||
self._restore = 1
|
||||
self.create_tag(index)
|
||||
self.set_timeout()
|
||||
|
||||
def check_restore_event(self, event=None):
|
||||
if self._restore:
|
||||
self.text.tag_delete("paren")
|
||||
self._restore = None
|
||||
|
||||
def handle_restore_timer(self, timer_count):
|
||||
if timer_count + 1 == self.counter:
|
||||
self.check_restore_event()
|
||||
|
||||
def warn_mismatched(self):
|
||||
if self.BELL:
|
||||
self.text.bell()
|
||||
|
||||
# any one of the create_tag_XXX methods can be used depending on
|
||||
# the style
|
||||
|
||||
def create_tag_default(self, index):
|
||||
"""Highlight the single paren that matches"""
|
||||
self.text.tag_add("paren", index)
|
||||
self.text.tag_config("paren", self.HILITE_CONFIG)
|
||||
|
||||
def create_tag_expression(self, index):
|
||||
"""Highlight the entire expression"""
|
||||
self.text.tag_add("paren", index, "insert")
|
||||
self.text.tag_config("paren", self.HILITE_CONFIG)
|
||||
|
||||
# any one of the set_timeout_XXX methods can be used depending on
|
||||
# the style
|
||||
|
||||
def set_timeout_none(self):
|
||||
"""Highlight will remain until user input turns it off"""
|
||||
pass
|
||||
|
||||
def set_timeout_last(self):
|
||||
"""The last highlight created will be removed after .5 sec"""
|
||||
# associate a counter with an event; only disable the "paren"
|
||||
# tag if the event is for the most recent timer.
|
||||
self.editwin.text_frame.after(self.FLASH_DELAY,
|
||||
lambda self=self, c=self.counter: \
|
||||
self.handle_restore_timer(c))
|
||||
self.counter = self.counter + 1
|
||||
|
||||
def keysym_type(ks):
|
||||
# Not all possible chars or keysyms are checked because of the
|
||||
# limited context in which the function is used.
|
||||
if ks == "parenright" or ks == "(":
|
||||
return "paren"
|
||||
if ks == "bracketright" or ks == "[":
|
||||
return "bracket"
|
||||
if ks == "braceright" or ks == "{":
|
||||
return "brace"
|
||||
|
||||
class LastOpenBracketFinder:
|
||||
num_context_lines = AutoIndent.num_context_lines
|
||||
indentwidth = AutoIndent.indentwidth
|
||||
tabwidth = AutoIndent.tabwidth
|
||||
context_use_ps1 = AutoIndent.context_use_ps1
|
||||
|
||||
def __init__(self, editwin):
|
||||
self.editwin = editwin
|
||||
self.text = editwin.text
|
||||
|
||||
def _find_offset_in_buf(self, lno):
|
||||
y = PyParse.Parser(self.indentwidth, self.tabwidth)
|
||||
for context in self.num_context_lines:
|
||||
startat = max(lno - context, 1)
|
||||
startatindex = `startat` + ".0"
|
||||
# rawtext needs to contain everything up to the last
|
||||
# character, which was the close paren. the parser also
|
||||
# requires that the last line ends with "\n"
|
||||
rawtext = self.text.get(startatindex, "insert")[:-1] + "\n"
|
||||
y.set_str(rawtext)
|
||||
bod = y.find_good_parse_start(
|
||||
self.context_use_ps1,
|
||||
self._build_char_in_string_func(startatindex))
|
||||
if bod is not None or startat == 1:
|
||||
break
|
||||
y.set_lo(bod or 0)
|
||||
i = y.get_last_open_bracket_pos()
|
||||
return i, y.str
|
||||
|
||||
def find(self, right_keysym_type):
|
||||
"""Return the location of the last open paren"""
|
||||
lno = index2line(self.text.index("insert"))
|
||||
i, buf = self._find_offset_in_buf(lno)
|
||||
if i is None \
|
||||
or keysym_type(buf[i]) != right_keysym_type:
|
||||
return None
|
||||
lines_back = string.count(buf[i:], "\n") - 1
|
||||
# subtract one for the "\n" added to please the parser
|
||||
upto_open = buf[:i]
|
||||
j = string.rfind(upto_open, "\n") + 1 # offset of column 0 of line
|
||||
offset = i - j
|
||||
return "%d.%d" % (lno - lines_back, offset)
|
||||
|
||||
def _build_char_in_string_func(self, startindex):
|
||||
def inner(offset, startindex=startindex,
|
||||
icis=self.editwin.is_char_in_string):
|
||||
return icis(startindex + "%dc" % offset)
|
||||
return inner
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
import os
|
||||
import sys
|
||||
import imp
|
||||
|
||||
from TreeWidget import TreeItem
|
||||
from ClassBrowser import ClassBrowser, ModuleBrowserTreeItem
|
||||
|
||||
class PathBrowser(ClassBrowser):
|
||||
|
||||
def __init__(self, flist):
|
||||
self.init(flist)
|
||||
|
||||
def settitle(self):
|
||||
self.top.wm_title("Path Browser")
|
||||
self.top.wm_iconname("Path Browser")
|
||||
|
||||
def rootnode(self):
|
||||
return PathBrowserTreeItem()
|
||||
|
||||
class PathBrowserTreeItem(TreeItem):
|
||||
|
||||
def GetText(self):
|
||||
return "sys.path"
|
||||
|
||||
def GetSubList(self):
|
||||
sublist = []
|
||||
for dir in sys.path:
|
||||
item = DirBrowserTreeItem(dir)
|
||||
sublist.append(item)
|
||||
return sublist
|
||||
|
||||
class DirBrowserTreeItem(TreeItem):
|
||||
|
||||
def __init__(self, dir, packages=[]):
|
||||
self.dir = dir
|
||||
self.packages = packages
|
||||
|
||||
def GetText(self):
|
||||
if not self.packages:
|
||||
return self.dir
|
||||
else:
|
||||
return self.packages[-1] + ": package"
|
||||
|
||||
def GetSubList(self):
|
||||
try:
|
||||
names = os.listdir(self.dir or os.curdir)
|
||||
except os.error:
|
||||
return []
|
||||
packages = []
|
||||
for name in names:
|
||||
file = os.path.join(self.dir, name)
|
||||
if self.ispackagedir(file):
|
||||
nn = os.path.normcase(name)
|
||||
packages.append((nn, name, file))
|
||||
packages.sort()
|
||||
sublist = []
|
||||
for nn, name, file in packages:
|
||||
item = DirBrowserTreeItem(file, self.packages + [name])
|
||||
sublist.append(item)
|
||||
for nn, name in self.listmodules(names):
|
||||
item = ModuleBrowserTreeItem(os.path.join(self.dir, name))
|
||||
sublist.append(item)
|
||||
return sublist
|
||||
|
||||
def ispackagedir(self, file):
|
||||
if not os.path.isdir(file):
|
||||
return 0
|
||||
init = os.path.join(file, "__init__.py")
|
||||
return os.path.exists(init)
|
||||
|
||||
def listmodules(self, allnames):
|
||||
modules = {}
|
||||
suffixes = imp.get_suffixes()
|
||||
sorted = []
|
||||
for suff, mode, flag in suffixes:
|
||||
i = -len(suff)
|
||||
for name in allnames[:]:
|
||||
normed_name = os.path.normcase(name)
|
||||
if normed_name[i:] == suff:
|
||||
mod_name = name[:i]
|
||||
if not modules.has_key(mod_name):
|
||||
modules[mod_name] = None
|
||||
sorted.append((normed_name, name))
|
||||
allnames.remove(name)
|
||||
sorted.sort()
|
||||
return sorted
|
||||
|
||||
def main():
|
||||
import PyShell
|
||||
PathBrowser(PyShell.flist)
|
||||
if sys.stdin is sys.__stdin__:
|
||||
mainloop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,85 @@
|
|||
from WidgetRedirector import WidgetRedirector
|
||||
from Delegator import Delegator
|
||||
|
||||
class Percolator:
|
||||
|
||||
def __init__(self, text):
|
||||
# XXX would be nice to inherit from Delegator
|
||||
self.text = text
|
||||
self.redir = WidgetRedirector(text)
|
||||
self.top = self.bottom = Delegator(text)
|
||||
self.bottom.insert = self.redir.register("insert", self.insert)
|
||||
self.bottom.delete = self.redir.register("delete", self.delete)
|
||||
self.filters = []
|
||||
|
||||
def close(self):
|
||||
while self.top is not self.bottom:
|
||||
self.removefilter(self.top)
|
||||
self.top = None
|
||||
self.bottom.setdelegate(None); self.bottom = None
|
||||
self.redir.close(); self.redir = None
|
||||
self.text = None
|
||||
|
||||
def insert(self, index, chars, tags=None):
|
||||
# Could go away if inheriting from Delegator
|
||||
self.top.insert(index, chars, tags)
|
||||
|
||||
def delete(self, index1, index2=None):
|
||||
# Could go away if inheriting from Delegator
|
||||
self.top.delete(index1, index2)
|
||||
|
||||
def insertfilter(self, filter):
|
||||
# Perhaps rename to pushfilter()?
|
||||
assert isinstance(filter, Delegator)
|
||||
assert filter.delegate is None
|
||||
filter.setdelegate(self.top)
|
||||
self.top = filter
|
||||
|
||||
def removefilter(self, filter):
|
||||
# XXX Perhaps should only support popfilter()?
|
||||
assert isinstance(filter, Delegator)
|
||||
assert filter.delegate is not None
|
||||
f = self.top
|
||||
if f is filter:
|
||||
self.top = filter.delegate
|
||||
filter.setdelegate(None)
|
||||
else:
|
||||
while f.delegate is not filter:
|
||||
assert f is not self.bottom
|
||||
f.resetcache()
|
||||
f = f.delegate
|
||||
f.setdelegate(filter.delegate)
|
||||
filter.setdelegate(None)
|
||||
|
||||
|
||||
def main():
|
||||
class Tracer(Delegator):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
Delegator.__init__(self, None)
|
||||
def insert(self, *args):
|
||||
print self.name, ": insert", args
|
||||
apply(self.delegate.insert, args)
|
||||
def delete(self, *args):
|
||||
print self.name, ": delete", args
|
||||
apply(self.delegate.delete, args)
|
||||
from Tkinter import *
|
||||
root = Tk()
|
||||
root.wm_protocol("WM_DELETE_WINDOW", root.quit)
|
||||
text = Text()
|
||||
text.pack()
|
||||
text.focus_set()
|
||||
p = Percolator(text)
|
||||
t1 = Tracer("t1")
|
||||
t2 = Tracer("t2")
|
||||
p.insertfilter(t1)
|
||||
p.insertfilter(t2)
|
||||
root.mainloop()
|
||||
p.removefilter(t2)
|
||||
root.mainloop()
|
||||
p.insertfilter(t2)
|
||||
p.removefilter(t1)
|
||||
root.mainloop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,569 @@
|
|||
import string
|
||||
import re
|
||||
import sys
|
||||
|
||||
# Reason last stmt is continued (or C_NONE if it's not).
|
||||
C_NONE, C_BACKSLASH, C_STRING, C_BRACKET = range(4)
|
||||
|
||||
if 0: # for throwaway debugging output
|
||||
def dump(*stuff):
|
||||
sys.__stdout__.write(string.join(map(str, stuff), " ") + "\n")
|
||||
|
||||
# Find what looks like the start of a popular stmt.
|
||||
|
||||
_synchre = re.compile(r"""
|
||||
^
|
||||
[ \t]*
|
||||
(?: if
|
||||
| for
|
||||
| while
|
||||
| else
|
||||
| def
|
||||
| return
|
||||
| assert
|
||||
| break
|
||||
| class
|
||||
| continue
|
||||
| elif
|
||||
| try
|
||||
| except
|
||||
| raise
|
||||
| import
|
||||
)
|
||||
\b
|
||||
""", re.VERBOSE | re.MULTILINE).search
|
||||
|
||||
# Match blank line or non-indenting comment line.
|
||||
|
||||
_junkre = re.compile(r"""
|
||||
[ \t]*
|
||||
(?: \# \S .* )?
|
||||
\n
|
||||
""", re.VERBOSE).match
|
||||
|
||||
# Match any flavor of string; the terminating quote is optional
|
||||
# so that we're robust in the face of incomplete program text.
|
||||
|
||||
_match_stringre = re.compile(r"""
|
||||
\""" [^"\\]* (?:
|
||||
(?: \\. | "(?!"") )
|
||||
[^"\\]*
|
||||
)*
|
||||
(?: \""" )?
|
||||
|
||||
| " [^"\\\n]* (?: \\. [^"\\\n]* )* "?
|
||||
|
||||
| ''' [^'\\]* (?:
|
||||
(?: \\. | '(?!'') )
|
||||
[^'\\]*
|
||||
)*
|
||||
(?: ''' )?
|
||||
|
||||
| ' [^'\\\n]* (?: \\. [^'\\\n]* )* '?
|
||||
""", re.VERBOSE | re.DOTALL).match
|
||||
|
||||
# Match a line that starts with something interesting;
|
||||
# used to find the first item of a bracket structure.
|
||||
|
||||
_itemre = re.compile(r"""
|
||||
[ \t]*
|
||||
[^\s#\\] # if we match, m.end()-1 is the interesting char
|
||||
""", re.VERBOSE).match
|
||||
|
||||
# Match start of stmts that should be followed by a dedent.
|
||||
|
||||
_closere = re.compile(r"""
|
||||
\s*
|
||||
(?: return
|
||||
| break
|
||||
| continue
|
||||
| raise
|
||||
| pass
|
||||
)
|
||||
\b
|
||||
""", re.VERBOSE).match
|
||||
|
||||
# Chew up non-special chars as quickly as possible. If match is
|
||||
# successful, m.end() less 1 is the index of the last boring char
|
||||
# matched. If match is unsuccessful, the string starts with an
|
||||
# interesting char.
|
||||
|
||||
_chew_ordinaryre = re.compile(r"""
|
||||
[^[\](){}#'"\\]+
|
||||
""", re.VERBOSE).match
|
||||
|
||||
# Build translation table to map uninteresting chars to "x", open
|
||||
# brackets to "(", and close brackets to ")".
|
||||
|
||||
_tran = ['x'] * 256
|
||||
for ch in "({[":
|
||||
_tran[ord(ch)] = '('
|
||||
for ch in ")}]":
|
||||
_tran[ord(ch)] = ')'
|
||||
for ch in "\"'\\\n#":
|
||||
_tran[ord(ch)] = ch
|
||||
_tran = string.join(_tran, '')
|
||||
del ch
|
||||
|
||||
class Parser:
|
||||
|
||||
def __init__(self, indentwidth, tabwidth):
|
||||
self.indentwidth = indentwidth
|
||||
self.tabwidth = tabwidth
|
||||
|
||||
def set_str(self, str):
|
||||
assert len(str) == 0 or str[-1] == '\n'
|
||||
self.str = str
|
||||
self.study_level = 0
|
||||
|
||||
# Return index of a good place to begin parsing, as close to the
|
||||
# end of the string as possible. This will be the start of some
|
||||
# popular stmt like "if" or "def". Return None if none found:
|
||||
# the caller should pass more prior context then, if possible, or
|
||||
# if not (the entire program text up until the point of interest
|
||||
# has already been tried) pass 0 to set_lo.
|
||||
#
|
||||
# This will be reliable iff given a reliable is_char_in_string
|
||||
# function, meaning that when it says "no", it's absolutely
|
||||
# guaranteed that the char is not in a string.
|
||||
#
|
||||
# Ack, hack: in the shell window this kills us, because there's
|
||||
# no way to tell the differences between output, >>> etc and
|
||||
# user input. Indeed, IDLE's first output line makes the rest
|
||||
# look like it's in an unclosed paren!:
|
||||
# Python 1.5.2 (#0, Apr 13 1999, ...
|
||||
|
||||
def find_good_parse_start(self, use_ps1, is_char_in_string=None,
|
||||
_rfind=string.rfind,
|
||||
_synchre=_synchre):
|
||||
str, pos = self.str, None
|
||||
if use_ps1:
|
||||
# shell window
|
||||
ps1 = '\n' + sys.ps1
|
||||
i = _rfind(str, ps1)
|
||||
if i >= 0:
|
||||
pos = i + len(ps1)
|
||||
# make it look like there's a newline instead
|
||||
# of ps1 at the start -- hacking here once avoids
|
||||
# repeated hackery later
|
||||
self.str = str[:pos-1] + '\n' + str[pos:]
|
||||
return pos
|
||||
|
||||
# File window -- real work.
|
||||
if not is_char_in_string:
|
||||
# no clue -- make the caller pass everything
|
||||
return None
|
||||
|
||||
# Peek back from the end for a good place to start,
|
||||
# but don't try too often; pos will be left None, or
|
||||
# bumped to a legitimate synch point.
|
||||
limit = len(str)
|
||||
for tries in range(5):
|
||||
i = _rfind(str, ":\n", 0, limit)
|
||||
if i < 0:
|
||||
break
|
||||
i = _rfind(str, '\n', 0, i) + 1 # start of colon line
|
||||
m = _synchre(str, i, limit)
|
||||
if m and not is_char_in_string(m.start()):
|
||||
pos = m.start()
|
||||
break
|
||||
limit = i
|
||||
if pos is None:
|
||||
# Nothing looks like a block-opener, or stuff does
|
||||
# but is_char_in_string keeps returning true; most likely
|
||||
# we're in or near a giant string, the colorizer hasn't
|
||||
# caught up enough to be helpful, or there simply *aren't*
|
||||
# any interesting stmts. In any of these cases we're
|
||||
# going to have to parse the whole thing to be sure, so
|
||||
# give it one last try from the start, but stop wasting
|
||||
# time here regardless of the outcome.
|
||||
m = _synchre(str)
|
||||
if m and not is_char_in_string(m.start()):
|
||||
pos = m.start()
|
||||
return pos
|
||||
|
||||
# Peeking back worked; look forward until _synchre no longer
|
||||
# matches.
|
||||
i = pos + 1
|
||||
while 1:
|
||||
m = _synchre(str, i)
|
||||
if m:
|
||||
s, i = m.span()
|
||||
if not is_char_in_string(s):
|
||||
pos = s
|
||||
else:
|
||||
break
|
||||
return pos
|
||||
|
||||
# Throw away the start of the string. Intended to be called with
|
||||
# find_good_parse_start's result.
|
||||
|
||||
def set_lo(self, lo):
|
||||
assert lo == 0 or self.str[lo-1] == '\n'
|
||||
if lo > 0:
|
||||
self.str = self.str[lo:]
|
||||
|
||||
# As quickly as humanly possible <wink>, find the line numbers (0-
|
||||
# based) of the non-continuation lines.
|
||||
# Creates self.{goodlines, continuation}.
|
||||
|
||||
def _study1(self, _replace=string.replace, _find=string.find):
|
||||
if self.study_level >= 1:
|
||||
return
|
||||
self.study_level = 1
|
||||
|
||||
# Map all uninteresting characters to "x", all open brackets
|
||||
# to "(", all close brackets to ")", then collapse runs of
|
||||
# uninteresting characters. This can cut the number of chars
|
||||
# by a factor of 10-40, and so greatly speed the following loop.
|
||||
str = self.str
|
||||
str = string.translate(str, _tran)
|
||||
str = _replace(str, 'xxxxxxxx', 'x')
|
||||
str = _replace(str, 'xxxx', 'x')
|
||||
str = _replace(str, 'xx', 'x')
|
||||
str = _replace(str, 'xx', 'x')
|
||||
str = _replace(str, '\nx', '\n')
|
||||
# note that replacing x\n with \n would be incorrect, because
|
||||
# x may be preceded by a backslash
|
||||
|
||||
# March over the squashed version of the program, accumulating
|
||||
# the line numbers of non-continued stmts, and determining
|
||||
# whether & why the last stmt is a continuation.
|
||||
continuation = C_NONE
|
||||
level = lno = 0 # level is nesting level; lno is line number
|
||||
self.goodlines = goodlines = [0]
|
||||
push_good = goodlines.append
|
||||
i, n = 0, len(str)
|
||||
while i < n:
|
||||
ch = str[i]
|
||||
i = i+1
|
||||
|
||||
# cases are checked in decreasing order of frequency
|
||||
if ch == 'x':
|
||||
continue
|
||||
|
||||
if ch == '\n':
|
||||
lno = lno + 1
|
||||
if level == 0:
|
||||
push_good(lno)
|
||||
# else we're in an unclosed bracket structure
|
||||
continue
|
||||
|
||||
if ch == '(':
|
||||
level = level + 1
|
||||
continue
|
||||
|
||||
if ch == ')':
|
||||
if level:
|
||||
level = level - 1
|
||||
# else the program is invalid, but we can't complain
|
||||
continue
|
||||
|
||||
if ch == '"' or ch == "'":
|
||||
# consume the string
|
||||
quote = ch
|
||||
if str[i-1:i+2] == quote * 3:
|
||||
quote = quote * 3
|
||||
w = len(quote) - 1
|
||||
i = i+w
|
||||
while i < n:
|
||||
ch = str[i]
|
||||
i = i+1
|
||||
|
||||
if ch == 'x':
|
||||
continue
|
||||
|
||||
if str[i-1:i+w] == quote:
|
||||
i = i+w
|
||||
break
|
||||
|
||||
if ch == '\n':
|
||||
lno = lno + 1
|
||||
if w == 0:
|
||||
# unterminated single-quoted string
|
||||
if level == 0:
|
||||
push_good(lno)
|
||||
break
|
||||
continue
|
||||
|
||||
if ch == '\\':
|
||||
assert i < n
|
||||
if str[i] == '\n':
|
||||
lno = lno + 1
|
||||
i = i+1
|
||||
continue
|
||||
|
||||
# else comment char or paren inside string
|
||||
|
||||
else:
|
||||
# didn't break out of the loop, so we're still
|
||||
# inside a string
|
||||
continuation = C_STRING
|
||||
continue # with outer loop
|
||||
|
||||
if ch == '#':
|
||||
# consume the comment
|
||||
i = _find(str, '\n', i)
|
||||
assert i >= 0
|
||||
continue
|
||||
|
||||
assert ch == '\\'
|
||||
assert i < n
|
||||
if str[i] == '\n':
|
||||
lno = lno + 1
|
||||
if i+1 == n:
|
||||
continuation = C_BACKSLASH
|
||||
i = i+1
|
||||
|
||||
# The last stmt may be continued for all 3 reasons.
|
||||
# String continuation takes precedence over bracket
|
||||
# continuation, which beats backslash continuation.
|
||||
if continuation != C_STRING and level > 0:
|
||||
continuation = C_BRACKET
|
||||
self.continuation = continuation
|
||||
|
||||
# Push the final line number as a sentinel value, regardless of
|
||||
# whether it's continued.
|
||||
assert (continuation == C_NONE) == (goodlines[-1] == lno)
|
||||
if goodlines[-1] != lno:
|
||||
push_good(lno)
|
||||
|
||||
def get_continuation_type(self):
|
||||
self._study1()
|
||||
return self.continuation
|
||||
|
||||
# study1 was sufficient to determine the continuation status,
|
||||
# but doing more requires looking at every character. study2
|
||||
# does this for the last interesting statement in the block.
|
||||
# Creates:
|
||||
# self.stmt_start, stmt_end
|
||||
# slice indices of last interesting stmt
|
||||
# self.lastch
|
||||
# last non-whitespace character before optional trailing
|
||||
# comment
|
||||
# self.lastopenbracketpos
|
||||
# if continuation is C_BRACKET, index of last open bracket
|
||||
|
||||
def _study2(self, _rfind=string.rfind, _find=string.find,
|
||||
_ws=string.whitespace):
|
||||
if self.study_level >= 2:
|
||||
return
|
||||
self._study1()
|
||||
self.study_level = 2
|
||||
|
||||
# Set p and q to slice indices of last interesting stmt.
|
||||
str, goodlines = self.str, self.goodlines
|
||||
i = len(goodlines) - 1
|
||||
p = len(str) # index of newest line
|
||||
while i:
|
||||
assert p
|
||||
# p is the index of the stmt at line number goodlines[i].
|
||||
# Move p back to the stmt at line number goodlines[i-1].
|
||||
q = p
|
||||
for nothing in range(goodlines[i-1], goodlines[i]):
|
||||
# tricky: sets p to 0 if no preceding newline
|
||||
p = _rfind(str, '\n', 0, p-1) + 1
|
||||
# The stmt str[p:q] isn't a continuation, but may be blank
|
||||
# or a non-indenting comment line.
|
||||
if _junkre(str, p):
|
||||
i = i-1
|
||||
else:
|
||||
break
|
||||
if i == 0:
|
||||
# nothing but junk!
|
||||
assert p == 0
|
||||
q = p
|
||||
self.stmt_start, self.stmt_end = p, q
|
||||
|
||||
# Analyze this stmt, to find the last open bracket (if any)
|
||||
# and last interesting character (if any).
|
||||
lastch = ""
|
||||
stack = [] # stack of open bracket indices
|
||||
push_stack = stack.append
|
||||
while p < q:
|
||||
# suck up all except ()[]{}'"#\\
|
||||
m = _chew_ordinaryre(str, p, q)
|
||||
if m:
|
||||
# we skipped at least one boring char
|
||||
p = m.end()
|
||||
# back up over totally boring whitespace
|
||||
i = p-1 # index of last boring char
|
||||
while i >= 0 and str[i] in " \t\n":
|
||||
i = i-1
|
||||
if i >= 0:
|
||||
lastch = str[i]
|
||||
if p >= q:
|
||||
break
|
||||
|
||||
ch = str[p]
|
||||
|
||||
if ch in "([{":
|
||||
push_stack(p)
|
||||
lastch = ch
|
||||
p = p+1
|
||||
continue
|
||||
|
||||
if ch in ")]}":
|
||||
if stack:
|
||||
del stack[-1]
|
||||
lastch = ch
|
||||
p = p+1
|
||||
continue
|
||||
|
||||
if ch == '"' or ch == "'":
|
||||
# consume string
|
||||
# Note that study1 did this with a Python loop, but
|
||||
# we use a regexp here; the reason is speed in both
|
||||
# cases; the string may be huge, but study1 pre-squashed
|
||||
# strings to a couple of characters per line. study1
|
||||
# also needed to keep track of newlines, and we don't
|
||||
# have to.
|
||||
lastch = ch
|
||||
p = _match_stringre(str, p, q).end()
|
||||
continue
|
||||
|
||||
if ch == '#':
|
||||
# consume comment and trailing newline
|
||||
p = _find(str, '\n', p, q) + 1
|
||||
assert p > 0
|
||||
continue
|
||||
|
||||
assert ch == '\\'
|
||||
p = p+1 # beyond backslash
|
||||
assert p < q
|
||||
if str[p] != '\n':
|
||||
# the program is invalid, but can't complain
|
||||
lastch = ch + str[p]
|
||||
p = p+1 # beyond escaped char
|
||||
|
||||
# end while p < q:
|
||||
|
||||
self.lastch = lastch
|
||||
if stack:
|
||||
self.lastopenbracketpos = stack[-1]
|
||||
|
||||
# Assuming continuation is C_BRACKET, return the number
|
||||
# of spaces the next line should be indented.
|
||||
|
||||
def compute_bracket_indent(self, _find=string.find):
|
||||
self._study2()
|
||||
assert self.continuation == C_BRACKET
|
||||
j = self.lastopenbracketpos
|
||||
str = self.str
|
||||
n = len(str)
|
||||
origi = i = string.rfind(str, '\n', 0, j) + 1
|
||||
j = j+1 # one beyond open bracket
|
||||
# find first list item; set i to start of its line
|
||||
while j < n:
|
||||
m = _itemre(str, j)
|
||||
if m:
|
||||
j = m.end() - 1 # index of first interesting char
|
||||
extra = 0
|
||||
break
|
||||
else:
|
||||
# this line is junk; advance to next line
|
||||
i = j = _find(str, '\n', j) + 1
|
||||
else:
|
||||
# nothing interesting follows the bracket;
|
||||
# reproduce the bracket line's indentation + a level
|
||||
j = i = origi
|
||||
while str[j] in " \t":
|
||||
j = j+1
|
||||
extra = self.indentwidth
|
||||
return len(string.expandtabs(str[i:j],
|
||||
self.tabwidth)) + extra
|
||||
|
||||
# Return number of physical lines in last stmt (whether or not
|
||||
# it's an interesting stmt! this is intended to be called when
|
||||
# continuation is C_BACKSLASH).
|
||||
|
||||
def get_num_lines_in_stmt(self):
|
||||
self._study1()
|
||||
goodlines = self.goodlines
|
||||
return goodlines[-1] - goodlines[-2]
|
||||
|
||||
# Assuming continuation is C_BACKSLASH, return the number of spaces
|
||||
# the next line should be indented. Also assuming the new line is
|
||||
# the first one following the initial line of the stmt.
|
||||
|
||||
def compute_backslash_indent(self):
|
||||
self._study2()
|
||||
assert self.continuation == C_BACKSLASH
|
||||
str = self.str
|
||||
i = self.stmt_start
|
||||
while str[i] in " \t":
|
||||
i = i+1
|
||||
startpos = i
|
||||
|
||||
# See whether the initial line starts an assignment stmt; i.e.,
|
||||
# look for an = operator
|
||||
endpos = string.find(str, '\n', startpos) + 1
|
||||
found = level = 0
|
||||
while i < endpos:
|
||||
ch = str[i]
|
||||
if ch in "([{":
|
||||
level = level + 1
|
||||
i = i+1
|
||||
elif ch in ")]}":
|
||||
if level:
|
||||
level = level - 1
|
||||
i = i+1
|
||||
elif ch == '"' or ch == "'":
|
||||
i = _match_stringre(str, i, endpos).end()
|
||||
elif ch == '#':
|
||||
break
|
||||
elif level == 0 and ch == '=' and \
|
||||
(i == 0 or str[i-1] not in "=<>!") and \
|
||||
str[i+1] != '=':
|
||||
found = 1
|
||||
break
|
||||
else:
|
||||
i = i+1
|
||||
|
||||
if found:
|
||||
# found a legit =, but it may be the last interesting
|
||||
# thing on the line
|
||||
i = i+1 # move beyond the =
|
||||
found = re.match(r"\s*\\", str[i:endpos]) is None
|
||||
|
||||
if not found:
|
||||
# oh well ... settle for moving beyond the first chunk
|
||||
# of non-whitespace chars
|
||||
i = startpos
|
||||
while str[i] not in " \t\n":
|
||||
i = i+1
|
||||
|
||||
return len(string.expandtabs(str[self.stmt_start :
|
||||
i],
|
||||
self.tabwidth)) + 1
|
||||
|
||||
# Return the leading whitespace on the initial line of the last
|
||||
# interesting stmt.
|
||||
|
||||
def get_base_indent_string(self):
|
||||
self._study2()
|
||||
i, n = self.stmt_start, self.stmt_end
|
||||
j = i
|
||||
str = self.str
|
||||
while j < n and str[j] in " \t":
|
||||
j = j + 1
|
||||
return str[i:j]
|
||||
|
||||
# Did the last interesting stmt open a block?
|
||||
|
||||
def is_block_opener(self):
|
||||
self._study2()
|
||||
return self.lastch == ':'
|
||||
|
||||
# Did the last interesting stmt close a block?
|
||||
|
||||
def is_block_closer(self):
|
||||
self._study2()
|
||||
return _closere(self.str, self.stmt_start) is not None
|
||||
|
||||
# index of last open bracket ({[, or None if none
|
||||
lastopenbracketpos = None
|
||||
|
||||
def get_last_open_bracket_pos(self):
|
||||
self._study2()
|
||||
return self.lastopenbracketpos
|
|
@ -0,0 +1,860 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# changes by dscherer@cmu.edu
|
||||
|
||||
# the main() function has been replaced by a whole class, in order to
|
||||
# address the constraint that only one process can sit on the port
|
||||
# hard-coded into the loader.
|
||||
|
||||
# It attempts to load the RPC protocol server and publish itself. If
|
||||
# that fails, it assumes that some other copy of IDLE is already running
|
||||
# on the port and attempts to contact it. It then uses the RPC mechanism
|
||||
# to ask that copy to do whatever it was instructed (via the command
|
||||
# line) to do. (Think netscape -remote). The handling of command line
|
||||
# arguments for remotes is still very incomplete.
|
||||
|
||||
# default behavior (no command line options) is to NOT start the Python
|
||||
# Shell. If files are specified, they are opened, otherwise a single
|
||||
# blank editor window opens.
|
||||
|
||||
# If any command line -options are specified, a shell does appear. This
|
||||
# is necessary to make the current semantics of the options make sense.
|
||||
|
||||
import os
|
||||
import spawn
|
||||
import sys
|
||||
import string
|
||||
import getopt
|
||||
import re
|
||||
import protocol
|
||||
|
||||
import linecache
|
||||
from code import InteractiveInterpreter
|
||||
|
||||
from Tkinter import *
|
||||
import tkMessageBox
|
||||
|
||||
from EditorWindow import EditorWindow, fixwordbreaks
|
||||
from FileList import FileList
|
||||
from ColorDelegator import ColorDelegator
|
||||
from UndoDelegator import UndoDelegator
|
||||
from OutputWindow import OutputWindow, OnDemandOutputWindow
|
||||
from IdleConf import idleconf
|
||||
import idlever
|
||||
|
||||
# We need to patch linecache.checkcache, because we don't want it
|
||||
# to throw away our <pyshell#...> entries.
|
||||
# Rather than repeating its code here, we save those entries,
|
||||
# then call the original function, and then restore the saved entries.
|
||||
def linecache_checkcache(orig_checkcache=linecache.checkcache):
|
||||
cache = linecache.cache
|
||||
save = {}
|
||||
for filename in cache.keys():
|
||||
if filename[:1] + filename[-1:] == '<>':
|
||||
save[filename] = cache[filename]
|
||||
orig_checkcache()
|
||||
cache.update(save)
|
||||
linecache.checkcache = linecache_checkcache
|
||||
|
||||
|
||||
# Note: <<newline-and-indent>> event is defined in AutoIndent.py
|
||||
|
||||
#$ event <<plain-newline-and-indent>>
|
||||
#$ win <Control-j>
|
||||
#$ unix <Control-j>
|
||||
|
||||
#$ event <<beginning-of-line>>
|
||||
#$ win <Control-a>
|
||||
#$ win <Home>
|
||||
#$ unix <Control-a>
|
||||
#$ unix <Home>
|
||||
|
||||
#$ event <<history-next>>
|
||||
#$ win <Alt-n>
|
||||
#$ unix <Alt-n>
|
||||
|
||||
#$ event <<history-previous>>
|
||||
#$ win <Alt-p>
|
||||
#$ unix <Alt-p>
|
||||
|
||||
#$ event <<interrupt-execution>>
|
||||
#$ win <Control-c>
|
||||
#$ unix <Control-c>
|
||||
|
||||
#$ event <<end-of-file>>
|
||||
#$ win <Control-d>
|
||||
#$ unix <Control-d>
|
||||
|
||||
#$ event <<open-stack-viewer>>
|
||||
|
||||
#$ event <<toggle-debugger>>
|
||||
|
||||
|
||||
class PyShellEditorWindow(EditorWindow):
|
||||
|
||||
# Regular text edit window when a shell is present
|
||||
# XXX ought to merge with regular editor window
|
||||
|
||||
def __init__(self, *args):
|
||||
apply(EditorWindow.__init__, (self,) + args)
|
||||
self.text.bind("<<set-breakpoint-here>>", self.set_breakpoint_here)
|
||||
self.text.bind("<<open-python-shell>>", self.flist.open_shell)
|
||||
|
||||
rmenu_specs = [
|
||||
("Set breakpoint here", "<<set-breakpoint-here>>"),
|
||||
]
|
||||
|
||||
def set_breakpoint_here(self, event=None):
|
||||
if not self.flist.pyshell or not self.flist.pyshell.interp.debugger:
|
||||
self.text.bell()
|
||||
return
|
||||
self.flist.pyshell.interp.debugger.set_breakpoint_here(self)
|
||||
|
||||
|
||||
class PyShellFileList(FileList):
|
||||
|
||||
# File list when a shell is present
|
||||
|
||||
EditorWindow = PyShellEditorWindow
|
||||
|
||||
pyshell = None
|
||||
|
||||
def open_shell(self, event=None):
|
||||
if self.pyshell:
|
||||
self.pyshell.wakeup()
|
||||
else:
|
||||
self.pyshell = PyShell(self)
|
||||
self.pyshell.begin()
|
||||
return self.pyshell
|
||||
|
||||
|
||||
class ModifiedColorDelegator(ColorDelegator):
|
||||
|
||||
# Colorizer for the shell window itself
|
||||
|
||||
def recolorize_main(self):
|
||||
self.tag_remove("TODO", "1.0", "iomark")
|
||||
self.tag_add("SYNC", "1.0", "iomark")
|
||||
ColorDelegator.recolorize_main(self)
|
||||
|
||||
tagdefs = ColorDelegator.tagdefs.copy()
|
||||
cconf = idleconf.getsection('Colors')
|
||||
|
||||
tagdefs.update({
|
||||
"stdin": cconf.getcolor("stdin"),
|
||||
"stdout": cconf.getcolor("stdout"),
|
||||
"stderr": cconf.getcolor("stderr"),
|
||||
"console": cconf.getcolor("console"),
|
||||
"ERROR": cconf.getcolor("ERROR"),
|
||||
None: cconf.getcolor("normal"),
|
||||
})
|
||||
|
||||
|
||||
class ModifiedUndoDelegator(UndoDelegator):
|
||||
|
||||
# Forbid insert/delete before the I/O mark
|
||||
|
||||
def insert(self, index, chars, tags=None):
|
||||
try:
|
||||
if self.delegate.compare(index, "<", "iomark"):
|
||||
self.delegate.bell()
|
||||
return
|
||||
except TclError:
|
||||
pass
|
||||
UndoDelegator.insert(self, index, chars, tags)
|
||||
|
||||
def delete(self, index1, index2=None):
|
||||
try:
|
||||
if self.delegate.compare(index1, "<", "iomark"):
|
||||
self.delegate.bell()
|
||||
return
|
||||
except TclError:
|
||||
pass
|
||||
UndoDelegator.delete(self, index1, index2)
|
||||
|
||||
class ModifiedInterpreter(InteractiveInterpreter):
|
||||
|
||||
def __init__(self, tkconsole):
|
||||
self.tkconsole = tkconsole
|
||||
locals = sys.modules['__main__'].__dict__
|
||||
InteractiveInterpreter.__init__(self, locals=locals)
|
||||
|
||||
gid = 0
|
||||
|
||||
def execsource(self, source):
|
||||
# Like runsource() but assumes complete exec source
|
||||
filename = self.stuffsource(source)
|
||||
self.execfile(filename, source)
|
||||
|
||||
def execfile(self, filename, source=None):
|
||||
# Execute an existing file
|
||||
if source is None:
|
||||
source = open(filename, "r").read()
|
||||
try:
|
||||
code = compile(source, filename, "exec")
|
||||
except (OverflowError, SyntaxError):
|
||||
self.tkconsole.resetoutput()
|
||||
InteractiveInterpreter.showsyntaxerror(self, filename)
|
||||
else:
|
||||
self.runcode(code)
|
||||
|
||||
def runsource(self, source):
|
||||
# Extend base class to stuff the source in the line cache first
|
||||
filename = self.stuffsource(source)
|
||||
self.more = 0
|
||||
return InteractiveInterpreter.runsource(self, source, filename)
|
||||
|
||||
def stuffsource(self, source):
|
||||
# Stuff source in the filename cache
|
||||
filename = "<pyshell#%d>" % self.gid
|
||||
self.gid = self.gid + 1
|
||||
lines = string.split(source, "\n")
|
||||
linecache.cache[filename] = len(source)+1, 0, lines, filename
|
||||
return filename
|
||||
|
||||
def showsyntaxerror(self, filename=None):
|
||||
# Extend base class to color the offending position
|
||||
# (instead of printing it and pointing at it with a caret)
|
||||
text = self.tkconsole.text
|
||||
stuff = self.unpackerror()
|
||||
if not stuff:
|
||||
self.tkconsole.resetoutput()
|
||||
InteractiveInterpreter.showsyntaxerror(self, filename)
|
||||
return
|
||||
msg, lineno, offset, line = stuff
|
||||
if lineno == 1:
|
||||
pos = "iomark + %d chars" % (offset-1)
|
||||
else:
|
||||
pos = "iomark linestart + %d lines + %d chars" % (lineno-1,
|
||||
offset-1)
|
||||
text.tag_add("ERROR", pos)
|
||||
text.see(pos)
|
||||
char = text.get(pos)
|
||||
if char and char in string.letters + string.digits + "_":
|
||||
text.tag_add("ERROR", pos + " wordstart", pos)
|
||||
self.tkconsole.resetoutput()
|
||||
self.write("SyntaxError: %s\n" % str(msg))
|
||||
|
||||
def unpackerror(self):
|
||||
type, value, tb = sys.exc_info()
|
||||
ok = type is SyntaxError
|
||||
if ok:
|
||||
try:
|
||||
msg, (dummy_filename, lineno, offset, line) = value
|
||||
except:
|
||||
ok = 0
|
||||
if ok:
|
||||
return msg, lineno, offset, line
|
||||
else:
|
||||
return None
|
||||
|
||||
def showtraceback(self):
|
||||
# Extend base class method to reset output properly
|
||||
text = self.tkconsole.text
|
||||
self.tkconsole.resetoutput()
|
||||
self.checklinecache()
|
||||
InteractiveInterpreter.showtraceback(self)
|
||||
|
||||
def checklinecache(self):
|
||||
c = linecache.cache
|
||||
for key in c.keys():
|
||||
if key[:1] + key[-1:] != "<>":
|
||||
del c[key]
|
||||
|
||||
debugger = None
|
||||
|
||||
def setdebugger(self, debugger):
|
||||
self.debugger = debugger
|
||||
|
||||
def getdebugger(self):
|
||||
return self.debugger
|
||||
|
||||
def runcode(self, code):
|
||||
# Override base class method
|
||||
debugger = self.debugger
|
||||
try:
|
||||
self.tkconsole.beginexecuting()
|
||||
try:
|
||||
if debugger:
|
||||
debugger.run(code, self.locals)
|
||||
else:
|
||||
exec code in self.locals
|
||||
except SystemExit:
|
||||
if tkMessageBox.askyesno(
|
||||
"Exit?",
|
||||
"Do you want to exit altogether?",
|
||||
default="yes",
|
||||
master=self.tkconsole.text):
|
||||
raise
|
||||
else:
|
||||
self.showtraceback()
|
||||
if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
|
||||
self.tkconsole.open_stack_viewer()
|
||||
except:
|
||||
self.showtraceback()
|
||||
if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
|
||||
self.tkconsole.open_stack_viewer()
|
||||
|
||||
finally:
|
||||
self.tkconsole.endexecuting()
|
||||
|
||||
def write(self, s):
|
||||
# Override base class write
|
||||
self.tkconsole.console.write(s)
|
||||
|
||||
|
||||
class PyShell(OutputWindow):
|
||||
|
||||
shell_title = "Python Shell"
|
||||
|
||||
# Override classes
|
||||
ColorDelegator = ModifiedColorDelegator
|
||||
UndoDelegator = ModifiedUndoDelegator
|
||||
|
||||
# Override menu bar specs
|
||||
menu_specs = PyShellEditorWindow.menu_specs[:]
|
||||
menu_specs.insert(len(menu_specs)-2, ("debug", "_Debug"))
|
||||
|
||||
# New classes
|
||||
from IdleHistory import History
|
||||
|
||||
def __init__(self, flist=None):
|
||||
self.interp = ModifiedInterpreter(self)
|
||||
if flist is None:
|
||||
root = Tk()
|
||||
fixwordbreaks(root)
|
||||
root.withdraw()
|
||||
flist = PyShellFileList(root)
|
||||
|
||||
OutputWindow.__init__(self, flist, None, None)
|
||||
|
||||
import __builtin__
|
||||
__builtin__.quit = __builtin__.exit = "To exit, type Ctrl-D."
|
||||
|
||||
self.auto = self.extensions["AutoIndent"] # Required extension
|
||||
self.auto.config(usetabs=1, indentwidth=8, context_use_ps1=1)
|
||||
|
||||
text = self.text
|
||||
text.configure(wrap="char")
|
||||
text.bind("<<newline-and-indent>>", self.enter_callback)
|
||||
text.bind("<<plain-newline-and-indent>>", self.linefeed_callback)
|
||||
text.bind("<<interrupt-execution>>", self.cancel_callback)
|
||||
text.bind("<<beginning-of-line>>", self.home_callback)
|
||||
text.bind("<<end-of-file>>", self.eof_callback)
|
||||
text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
|
||||
text.bind("<<toggle-debugger>>", self.toggle_debugger)
|
||||
text.bind("<<open-python-shell>>", self.flist.open_shell)
|
||||
text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer)
|
||||
|
||||
self.save_stdout = sys.stdout
|
||||
self.save_stderr = sys.stderr
|
||||
self.save_stdin = sys.stdin
|
||||
sys.stdout = PseudoFile(self, "stdout")
|
||||
sys.stderr = PseudoFile(self, "stderr")
|
||||
sys.stdin = self
|
||||
self.console = PseudoFile(self, "console")
|
||||
|
||||
self.history = self.History(self.text)
|
||||
|
||||
reading = 0
|
||||
executing = 0
|
||||
canceled = 0
|
||||
endoffile = 0
|
||||
|
||||
def toggle_debugger(self, event=None):
|
||||
if self.executing:
|
||||
tkMessageBox.showerror("Don't debug now",
|
||||
"You can only toggle the debugger when idle",
|
||||
master=self.text)
|
||||
self.set_debugger_indicator()
|
||||
return "break"
|
||||
else:
|
||||
db = self.interp.getdebugger()
|
||||
if db:
|
||||
self.close_debugger()
|
||||
else:
|
||||
self.open_debugger()
|
||||
|
||||
def set_debugger_indicator(self):
|
||||
db = self.interp.getdebugger()
|
||||
self.setvar("<<toggle-debugger>>", not not db)
|
||||
|
||||
def toggle_jit_stack_viewer( self, event=None):
|
||||
pass # All we need is the variable
|
||||
|
||||
def close_debugger(self):
|
||||
db = self.interp.getdebugger()
|
||||
if db:
|
||||
self.interp.setdebugger(None)
|
||||
db.close()
|
||||
self.resetoutput()
|
||||
self.console.write("[DEBUG OFF]\n")
|
||||
sys.ps1 = ">>> "
|
||||
self.showprompt()
|
||||
self.set_debugger_indicator()
|
||||
|
||||
def open_debugger(self):
|
||||
import Debugger
|
||||
self.interp.setdebugger(Debugger.Debugger(self))
|
||||
sys.ps1 = "[DEBUG ON]\n>>> "
|
||||
self.showprompt()
|
||||
self.set_debugger_indicator()
|
||||
|
||||
def beginexecuting(self):
|
||||
# Helper for ModifiedInterpreter
|
||||
self.resetoutput()
|
||||
self.executing = 1
|
||||
##self._cancel_check = self.cancel_check
|
||||
##sys.settrace(self._cancel_check)
|
||||
|
||||
def endexecuting(self):
|
||||
# Helper for ModifiedInterpreter
|
||||
##sys.settrace(None)
|
||||
##self._cancel_check = None
|
||||
self.executing = 0
|
||||
self.canceled = 0
|
||||
|
||||
def close(self):
|
||||
# Extend base class method
|
||||
if self.executing:
|
||||
# XXX Need to ask a question here
|
||||
if not tkMessageBox.askokcancel(
|
||||
"Kill?",
|
||||
"The program is still running; do you want to kill it?",
|
||||
default="ok",
|
||||
master=self.text):
|
||||
return "cancel"
|
||||
self.canceled = 1
|
||||
if self.reading:
|
||||
self.top.quit()
|
||||
return "cancel"
|
||||
return PyShellEditorWindow.close(self)
|
||||
|
||||
def _close(self):
|
||||
self.close_debugger()
|
||||
# Restore std streams
|
||||
sys.stdout = self.save_stdout
|
||||
sys.stderr = self.save_stderr
|
||||
sys.stdin = self.save_stdin
|
||||
# Break cycles
|
||||
self.interp = None
|
||||
self.console = None
|
||||
self.auto = None
|
||||
self.flist.pyshell = None
|
||||
self.history = None
|
||||
OutputWindow._close(self) # Really EditorWindow._close
|
||||
|
||||
def ispythonsource(self, filename):
|
||||
# Override this so EditorWindow never removes the colorizer
|
||||
return 1
|
||||
|
||||
def short_title(self):
|
||||
return self.shell_title
|
||||
|
||||
def begin(self):
|
||||
self.resetoutput()
|
||||
self.write("Python %s on %s\n%s\nIDLE %s -- press F1 for help\n" %
|
||||
(sys.version, sys.platform, sys.copyright,
|
||||
idlever.IDLE_VERSION))
|
||||
try:
|
||||
sys.ps1
|
||||
except AttributeError:
|
||||
sys.ps1 = ">>> "
|
||||
self.showprompt()
|
||||
import Tkinter
|
||||
Tkinter._default_root = None
|
||||
|
||||
def interact(self):
|
||||
self.begin()
|
||||
self.top.mainloop()
|
||||
|
||||
def readline(self):
|
||||
save = self.reading
|
||||
try:
|
||||
self.reading = 1
|
||||
self.top.mainloop()
|
||||
finally:
|
||||
self.reading = save
|
||||
line = self.text.get("iomark", "end-1c")
|
||||
self.resetoutput()
|
||||
if self.canceled:
|
||||
self.canceled = 0
|
||||
raise KeyboardInterrupt
|
||||
if self.endoffile:
|
||||
self.endoffile = 0
|
||||
return ""
|
||||
return line
|
||||
|
||||
def isatty(self):
|
||||
return 1
|
||||
|
||||
def cancel_callback(self, event):
|
||||
try:
|
||||
if self.text.compare("sel.first", "!=", "sel.last"):
|
||||
return # Active selection -- always use default binding
|
||||
except:
|
||||
pass
|
||||
if not (self.executing or self.reading):
|
||||
self.resetoutput()
|
||||
self.write("KeyboardInterrupt\n")
|
||||
self.showprompt()
|
||||
return "break"
|
||||
self.endoffile = 0
|
||||
self.canceled = 1
|
||||
if self.reading:
|
||||
self.top.quit()
|
||||
return "break"
|
||||
|
||||
def eof_callback(self, event):
|
||||
if self.executing and not self.reading:
|
||||
return # Let the default binding (delete next char) take over
|
||||
if not (self.text.compare("iomark", "==", "insert") and
|
||||
self.text.compare("insert", "==", "end-1c")):
|
||||
return # Let the default binding (delete next char) take over
|
||||
if not self.executing:
|
||||
## if not tkMessageBox.askokcancel(
|
||||
## "Exit?",
|
||||
## "Are you sure you want to exit?",
|
||||
## default="ok", master=self.text):
|
||||
## return "break"
|
||||
self.resetoutput()
|
||||
self.close()
|
||||
else:
|
||||
self.canceled = 0
|
||||
self.endoffile = 1
|
||||
self.top.quit()
|
||||
return "break"
|
||||
|
||||
def home_callback(self, event):
|
||||
if event.state != 0 and event.keysym == "Home":
|
||||
return # <Modifier-Home>; fall back to class binding
|
||||
if self.text.compare("iomark", "<=", "insert") and \
|
||||
self.text.compare("insert linestart", "<=", "iomark"):
|
||||
self.text.mark_set("insert", "iomark")
|
||||
self.text.tag_remove("sel", "1.0", "end")
|
||||
self.text.see("insert")
|
||||
return "break"
|
||||
|
||||
def linefeed_callback(self, event):
|
||||
# Insert a linefeed without entering anything (still autoindented)
|
||||
if self.reading:
|
||||
self.text.insert("insert", "\n")
|
||||
self.text.see("insert")
|
||||
else:
|
||||
self.auto.auto_indent(event)
|
||||
return "break"
|
||||
|
||||
def enter_callback(self, event):
|
||||
if self.executing and not self.reading:
|
||||
return # Let the default binding (insert '\n') take over
|
||||
# If some text is selected, recall the selection
|
||||
# (but only if this before the I/O mark)
|
||||
try:
|
||||
sel = self.text.get("sel.first", "sel.last")
|
||||
if sel:
|
||||
if self.text.compare("sel.last", "<=", "iomark"):
|
||||
self.recall(sel)
|
||||
return "break"
|
||||
except:
|
||||
pass
|
||||
# If we're strictly before the line containing iomark, recall
|
||||
# the current line, less a leading prompt, less leading or
|
||||
# trailing whitespace
|
||||
if self.text.compare("insert", "<", "iomark linestart"):
|
||||
# Check if there's a relevant stdin range -- if so, use it
|
||||
prev = self.text.tag_prevrange("stdin", "insert")
|
||||
if prev and self.text.compare("insert", "<", prev[1]):
|
||||
self.recall(self.text.get(prev[0], prev[1]))
|
||||
return "break"
|
||||
next = self.text.tag_nextrange("stdin", "insert")
|
||||
if next and self.text.compare("insert lineend", ">=", next[0]):
|
||||
self.recall(self.text.get(next[0], next[1]))
|
||||
return "break"
|
||||
# No stdin mark -- just get the current line
|
||||
self.recall(self.text.get("insert linestart", "insert lineend"))
|
||||
return "break"
|
||||
# If we're in the current input and there's only whitespace
|
||||
# beyond the cursor, erase that whitespace first
|
||||
s = self.text.get("insert", "end-1c")
|
||||
if s and not string.strip(s):
|
||||
self.text.delete("insert", "end-1c")
|
||||
# If we're in the current input before its last line,
|
||||
# insert a newline right at the insert point
|
||||
if self.text.compare("insert", "<", "end-1c linestart"):
|
||||
self.auto.auto_indent(event)
|
||||
return "break"
|
||||
# We're in the last line; append a newline and submit it
|
||||
self.text.mark_set("insert", "end-1c")
|
||||
if self.reading:
|
||||
self.text.insert("insert", "\n")
|
||||
self.text.see("insert")
|
||||
else:
|
||||
self.auto.auto_indent(event)
|
||||
self.text.tag_add("stdin", "iomark", "end-1c")
|
||||
self.text.update_idletasks()
|
||||
if self.reading:
|
||||
self.top.quit() # Break out of recursive mainloop() in raw_input()
|
||||
else:
|
||||
self.runit()
|
||||
return "break"
|
||||
|
||||
def recall(self, s):
|
||||
if self.history:
|
||||
self.history.recall(s)
|
||||
|
||||
def runit(self):
|
||||
line = self.text.get("iomark", "end-1c")
|
||||
# Strip off last newline and surrounding whitespace.
|
||||
# (To allow you to hit return twice to end a statement.)
|
||||
i = len(line)
|
||||
while i > 0 and line[i-1] in " \t":
|
||||
i = i-1
|
||||
if i > 0 and line[i-1] == "\n":
|
||||
i = i-1
|
||||
while i > 0 and line[i-1] in " \t":
|
||||
i = i-1
|
||||
line = line[:i]
|
||||
more = self.interp.runsource(line)
|
||||
if not more:
|
||||
self.showprompt()
|
||||
|
||||
def cancel_check(self, frame, what, args,
|
||||
dooneevent=tkinter.dooneevent,
|
||||
dontwait=tkinter.DONT_WAIT):
|
||||
# Hack -- use the debugger hooks to be able to handle events
|
||||
# and interrupt execution at any time.
|
||||
# This slows execution down quite a bit, so you may want to
|
||||
# disable this (by not calling settrace() in runcode() above)
|
||||
# for full-bore (uninterruptable) speed.
|
||||
# XXX This should become a user option.
|
||||
if self.canceled:
|
||||
return
|
||||
dooneevent(dontwait)
|
||||
if self.canceled:
|
||||
self.canceled = 0
|
||||
raise KeyboardInterrupt
|
||||
return self._cancel_check
|
||||
|
||||
def open_stack_viewer(self, event=None):
|
||||
try:
|
||||
sys.last_traceback
|
||||
except:
|
||||
tkMessageBox.showerror("No stack trace",
|
||||
"There is no stack trace yet.\n"
|
||||
"(sys.last_traceback is not defined)",
|
||||
master=self.text)
|
||||
return
|
||||
from StackViewer import StackBrowser
|
||||
sv = StackBrowser(self.root, self.flist)
|
||||
|
||||
def showprompt(self):
|
||||
self.resetoutput()
|
||||
try:
|
||||
s = str(sys.ps1)
|
||||
except:
|
||||
s = ""
|
||||
self.console.write(s)
|
||||
self.text.mark_set("insert", "end-1c")
|
||||
|
||||
def resetoutput(self):
|
||||
source = self.text.get("iomark", "end-1c")
|
||||
if self.history:
|
||||
self.history.history_store(source)
|
||||
if self.text.get("end-2c") != "\n":
|
||||
self.text.insert("end-1c", "\n")
|
||||
self.text.mark_set("iomark", "end-1c")
|
||||
sys.stdout.softspace = 0
|
||||
|
||||
def write(self, s, tags=()):
|
||||
self.text.mark_gravity("iomark", "right")
|
||||
OutputWindow.write(self, s, tags, "iomark")
|
||||
self.text.mark_gravity("iomark", "left")
|
||||
if self.canceled:
|
||||
self.canceled = 0
|
||||
raise KeyboardInterrupt
|
||||
|
||||
class PseudoFile:
|
||||
|
||||
def __init__(self, shell, tags):
|
||||
self.shell = shell
|
||||
self.tags = tags
|
||||
|
||||
def write(self, s):
|
||||
self.shell.write(s, self.tags)
|
||||
|
||||
def writelines(self, l):
|
||||
map(self.write, l)
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def isatty(self):
|
||||
return 1
|
||||
|
||||
usage_msg = """\
|
||||
usage: idle.py [-c command] [-d] [-e] [-s] [-t title] [arg] ...
|
||||
|
||||
-c command run this command
|
||||
-d enable debugger
|
||||
-e edit mode; arguments are files to be edited
|
||||
-s run $IDLESTARTUP or $PYTHONSTARTUP before anything else
|
||||
-t title set title of shell window
|
||||
|
||||
When neither -c nor -e is used, and there are arguments, and the first
|
||||
argument is not '-', the first argument is run as a script. Remaining
|
||||
arguments are arguments to the script or to the command run by -c.
|
||||
"""
|
||||
|
||||
class usageError:
|
||||
def __init__(self, string): self.string = string
|
||||
def __repr__(self): return self.string
|
||||
|
||||
class main:
|
||||
def __init__(self):
|
||||
try:
|
||||
self.server = protocol.Server(connection_hook = self.address_ok)
|
||||
protocol.publish( 'IDLE', self.connect )
|
||||
self.main( sys.argv[1:] )
|
||||
return
|
||||
except protocol.connectionLost:
|
||||
try:
|
||||
client = protocol.Client()
|
||||
IDLE = client.getobject('IDLE')
|
||||
if IDLE:
|
||||
try:
|
||||
IDLE.remote( sys.argv[1:] )
|
||||
except usageError, msg:
|
||||
sys.stderr.write("Error: %s\n" % str(msg))
|
||||
sys.stderr.write(usage_msg)
|
||||
return
|
||||
except protocol.connectionLost:
|
||||
pass
|
||||
|
||||
# xxx Should scream via Tk()
|
||||
print "Something already has our socket, but it won't open a window for me!"
|
||||
print "Unable to proceed."
|
||||
|
||||
def idle(self):
|
||||
spawn.kill_zombies()
|
||||
self.server.rpc_loop()
|
||||
root.after(25, self.idle)
|
||||
|
||||
# We permit connections from localhost only
|
||||
def address_ok(self, addr):
|
||||
return addr[0] == '127.0.0.1'
|
||||
|
||||
def connect(self, client, addr):
|
||||
return self
|
||||
|
||||
def remote( self, argv ):
|
||||
# xxx Should make this behavior match the behavior in main, or redo
|
||||
# command line options entirely.
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv, "c:deist:")
|
||||
except getopt.error, msg:
|
||||
raise usageError(msg)
|
||||
|
||||
for filename in args:
|
||||
flist.open(filename)
|
||||
if not args:
|
||||
flist.new()
|
||||
|
||||
def main( self, argv ):
|
||||
cmd = None
|
||||
edit = 0
|
||||
noshell = 1
|
||||
|
||||
debug = 0
|
||||
startup = 0
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv, "c:deist:")
|
||||
except getopt.error, msg:
|
||||
sys.stderr.write("Error: %s\n" % str(msg))
|
||||
sys.stderr.write(usage_msg)
|
||||
sys.exit(2)
|
||||
|
||||
for o, a in opts:
|
||||
noshell = 0
|
||||
if o == '-c':
|
||||
cmd = a
|
||||
if o == '-d':
|
||||
debug = 1
|
||||
if o == '-e':
|
||||
edit = 1
|
||||
if o == '-s':
|
||||
startup = 1
|
||||
if o == '-t':
|
||||
PyShell.shell_title = a
|
||||
|
||||
if noshell: edit=1
|
||||
|
||||
if not edit:
|
||||
if cmd:
|
||||
sys.argv = ["-c"] + args
|
||||
else:
|
||||
sys.argv = args or [""]
|
||||
|
||||
for i in range(len(sys.path)):
|
||||
sys.path[i] = os.path.abspath(sys.path[i])
|
||||
|
||||
pathx = []
|
||||
if edit:
|
||||
for filename in args:
|
||||
pathx.append(os.path.dirname(filename))
|
||||
elif args and args[0] != "-":
|
||||
pathx.append(os.path.dirname(args[0]))
|
||||
else:
|
||||
pathx.append(os.curdir)
|
||||
for dir in pathx:
|
||||
dir = os.path.abspath(dir)
|
||||
if not dir in sys.path:
|
||||
sys.path.insert(0, dir)
|
||||
|
||||
global flist, root
|
||||
root = Tk()
|
||||
fixwordbreaks(root)
|
||||
root.withdraw()
|
||||
flist = PyShellFileList(root)
|
||||
|
||||
if edit:
|
||||
for filename in args:
|
||||
flist.open(filename)
|
||||
if not args:
|
||||
flist.new()
|
||||
|
||||
#dbg=OnDemandOutputWindow(flist)
|
||||
#dbg.set_title('Internal IDLE Problem')
|
||||
#sys.stdout = PseudoFile(dbg,['stdout'])
|
||||
#sys.stderr = PseudoFile(dbg,['stderr'])
|
||||
|
||||
if noshell:
|
||||
flist.pyshell = None
|
||||
else:
|
||||
shell = PyShell(flist)
|
||||
interp = shell.interp
|
||||
flist.pyshell = shell
|
||||
|
||||
if startup:
|
||||
filename = os.environ.get("IDLESTARTUP") or \
|
||||
os.environ.get("PYTHONSTARTUP")
|
||||
if filename and os.path.isfile(filename):
|
||||
interp.execfile(filename)
|
||||
|
||||
if debug:
|
||||
shell.open_debugger()
|
||||
if cmd:
|
||||
interp.execsource(cmd)
|
||||
elif not edit and args and args[0] != "-":
|
||||
interp.execfile(args[0])
|
||||
|
||||
shell.begin()
|
||||
|
||||
self.idle()
|
||||
root.mainloop()
|
||||
root.destroy()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,121 @@
|
|||
EXPERIMENTAL LOADER IDLE 2000-05-29
|
||||
-----------------------------------
|
||||
|
||||
David Scherer <dscherer@cmu.edu>
|
||||
|
||||
This is a modification of the CVS version of IDLE 0.5, updated as of
|
||||
2000-03-09. It is alpha software and might be unstable. If it breaks,
|
||||
you get to keep both pieces.
|
||||
|
||||
If you have problems or suggestions, you should either contact me or
|
||||
post to the list at http://www.python.org/mailman/listinfo/idle-dev
|
||||
(making it clear that you are using this modified version of IDLE).
|
||||
|
||||
Changes:
|
||||
|
||||
The ExecBinding module, a replacement for ScriptBinding, executes
|
||||
programs in a separate process, piping standard I/O through an RPC
|
||||
mechanism to an OnDemandOutputWindow in IDLE. It supports executing
|
||||
unnamed programs (through a temporary file). It does not yet support
|
||||
debugging.
|
||||
|
||||
When running programs with ExecBinding, tracebacks will be clipped
|
||||
to exclude system modules. If, however, a system module calls back
|
||||
into the user program, that part of the traceback will be shown.
|
||||
|
||||
The OnDemandOutputWindow class has been improved. In particular,
|
||||
it now supports a readline() function used to implement user input,
|
||||
and a scroll_clear() operation which is used to hide the output of
|
||||
a previous run by scrolling it out of the window.
|
||||
|
||||
Startup behavior has been changed. By default IDLE starts up with
|
||||
just a blank editor window, rather than an interactive window. Opening
|
||||
a file in such a blank window replaces the (nonexistent) contents of
|
||||
that window instead of creating another window. Because of the need to
|
||||
have a well-known port for the ExecBinding protocol, only one copy of
|
||||
IDLE can be running. Additional invocations use the RPC mechanism to
|
||||
report their command line arguments to the copy already running.
|
||||
|
||||
The menus have been reorganized. In particular, the excessively large
|
||||
'edit' menu has been split up into 'edit', 'format', and 'run'.
|
||||
|
||||
'Python Documentation' now works on Windows, if the win32api module is
|
||||
present.
|
||||
|
||||
A few key bindings have been changed: F1 now loads Python Documentation
|
||||
instead of the IDLE help; shift-TAB is now a synonym for unindent.
|
||||
|
||||
New modules:
|
||||
ExecBinding.py Executes program through loader
|
||||
loader.py Bootstraps user program
|
||||
protocol.py RPC protocol
|
||||
Remote.py User-process interpreter
|
||||
spawn.py OS-specific code to start programs
|
||||
|
||||
Files modified:
|
||||
autoindent.py ( bindings tweaked )
|
||||
bindings.py ( menus reorganized )
|
||||
config.txt ( execbinding enabled )
|
||||
editorwindow.py ( new menus, fixed 'Python Documentation' )
|
||||
filelist.py ( hook for "open in same window" )
|
||||
formatparagraph.py ( bindings tweaked )
|
||||
idle.bat ( removed absolute pathname )
|
||||
idle.pyw ( weird bug due to import with same name? )
|
||||
iobinding.py ( open in same window, EOL convention )
|
||||
keydefs.py ( bindings tweaked )
|
||||
outputwindow.py ( readline, scroll_clear, etc )
|
||||
pyshell.py ( changed startup behavior )
|
||||
readme.txt ( <Recursion on file with id=1234567> )
|
||||
|
||||
IDLE 0.5 - February 2000
|
||||
------------------------
|
||||
|
||||
This is an early release of IDLE, my own attempt at a Tkinter-based
|
||||
IDE for Python.
|
||||
|
||||
For news about this release, see the file NEWS.txt. (For a more
|
||||
detailed change log, see the file ChangeLog.)
|
||||
|
||||
FEATURES
|
||||
|
||||
IDLE has the following features:
|
||||
|
||||
- coded in 100% pure Python, using the Tkinter GUI toolkit (i.e. Tcl/Tk)
|
||||
|
||||
- cross-platform: works on Windows and Unix (on the Mac, there are
|
||||
currently problems with Tcl/Tk)
|
||||
|
||||
- multi-window text editor with multiple undo, Python colorizing
|
||||
and many other features, e.g. smart indent and call tips
|
||||
|
||||
- Python shell window (a.k.a. interactive interpreter)
|
||||
|
||||
- debugger (not complete, but you can set breakpoints, view and step)
|
||||
|
||||
USAGE
|
||||
|
||||
The main program is in the file "idle.py"; on Unix, you should be able
|
||||
to run it by typing "./idle.py" to your shell. On Windows, you can
|
||||
run it by double-clicking it; you can use idle.pyw to avoid popping up
|
||||
a DOS console. If you want to pass command line arguments on Windows,
|
||||
use the batch file idle.bat.
|
||||
|
||||
Command line arguments: files passed on the command line are executed,
|
||||
not opened for editing, unless you give the -e command line option.
|
||||
Try "./idle.py -h" to see other command line options.
|
||||
|
||||
IDLE requires Python 1.5.2, so it is currently only usable with a
|
||||
Python 1.5.2 distribution. (An older version of IDLE is distributed
|
||||
with Python 1.5.2; you can drop this version on top of it.)
|
||||
|
||||
COPYRIGHT
|
||||
|
||||
IDLE is covered by the standard Python copyright notice
|
||||
(http://www.python.org/doc/Copyright.html).
|
||||
|
||||
FEEDBACK
|
||||
|
||||
(removed, since Guido probably doesn't want complaints about my
|
||||
changes)
|
||||
|
||||
--Guido van Rossum (home page: http://www.python.org/~guido/)
|
|
@ -0,0 +1,101 @@
|
|||
"""Remote
|
||||
This module is imported by the loader and serves to control
|
||||
the execution of the user program. It presently executes files
|
||||
and reports exceptions to IDLE. It could be extended to provide
|
||||
other services, such as interactive mode and debugging. To that
|
||||
end, it could be a subclass of e.g. InteractiveInterpreter.
|
||||
|
||||
Two other classes, pseudoIn and pseudoOut, are file emulators also
|
||||
used by loader.
|
||||
"""
|
||||
import sys, os
|
||||
import traceback
|
||||
|
||||
class Remote:
|
||||
def __init__(self, main, master):
|
||||
self.main = main
|
||||
self.master = master
|
||||
self.this_file = self.canonic( self.__init__.im_func.func_code.co_filename )
|
||||
|
||||
def canonic(self, path):
|
||||
return os.path.normcase(os.path.abspath(path))
|
||||
|
||||
def mainloop(self):
|
||||
while 1:
|
||||
args = self.master.get_command()
|
||||
|
||||
try:
|
||||
f = getattr(self,args[0])
|
||||
apply(f,args[1:])
|
||||
except:
|
||||
if not self.report_exception(): raise
|
||||
|
||||
def finish(self):
|
||||
sys.exit()
|
||||
|
||||
def run(self, *argv):
|
||||
sys.argv = argv
|
||||
|
||||
path = self.canonic( argv[0] )
|
||||
dir = self.dir = os.path.dirname(path)
|
||||
os.chdir(dir)
|
||||
|
||||
sys.path[0] = dir
|
||||
|
||||
usercode = open(path)
|
||||
exec usercode in self.main
|
||||
|
||||
def report_exception(self):
|
||||
try:
|
||||
type, value, tb = sys.exc_info()
|
||||
sys.last_type = type
|
||||
sys.last_value = value
|
||||
sys.last_traceback = tb
|
||||
|
||||
tblist = traceback.extract_tb(tb)
|
||||
|
||||
# Look through the traceback, canonicalizing filenames and
|
||||
# eliminating leading and trailing system modules.
|
||||
first = last = 1
|
||||
for i in range(len(tblist)):
|
||||
filename, lineno, name, line = tblist[i]
|
||||
filename = self.canonic(filename)
|
||||
tblist[i] = filename, lineno, name, line
|
||||
|
||||
dir = os.path.dirname(filename)
|
||||
if filename == self.this_file:
|
||||
first = i+1
|
||||
elif dir==self.dir:
|
||||
last = i+1
|
||||
|
||||
# Canonicalize the filename in a syntax error, too:
|
||||
if type is SyntaxError:
|
||||
try:
|
||||
msg, (filename, lineno, offset, line) = value
|
||||
filename = self.canonic(filename)
|
||||
value = msg, (filename, lineno, offset, line)
|
||||
except:
|
||||
pass
|
||||
|
||||
return self.master.program_exception( type, value, tblist, first, last )
|
||||
finally:
|
||||
# avoid any circular reference through the traceback
|
||||
del tb
|
||||
|
||||
class pseudoIn:
|
||||
def __init__(self, readline):
|
||||
self.readline = readline
|
||||
def isatty():
|
||||
return 1
|
||||
|
||||
class pseudoOut:
|
||||
def __init__(self, func, **kw):
|
||||
self.func = func
|
||||
self.kw = kw
|
||||
def write(self, *args):
|
||||
return apply( self.func, args, self.kw )
|
||||
def writelines(self, l):
|
||||
map(self.write, l)
|
||||
def flush(self):
|
||||
pass
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
import string
|
||||
import os
|
||||
import re
|
||||
import fnmatch
|
||||
from Tkinter import *
|
||||
import tkMessageBox
|
||||
import SearchEngine
|
||||
from SearchDialogBase import SearchDialogBase
|
||||
|
||||
def replace(text):
|
||||
root = text._root()
|
||||
engine = SearchEngine.get(root)
|
||||
if not hasattr(engine, "_replacedialog"):
|
||||
engine._replacedialog = ReplaceDialog(root, engine)
|
||||
dialog = engine._replacedialog
|
||||
dialog.open(text)
|
||||
|
||||
class ReplaceDialog(SearchDialogBase):
|
||||
|
||||
title = "Replace Dialog"
|
||||
icon = "Replace"
|
||||
|
||||
def __init__(self, root, engine):
|
||||
SearchDialogBase.__init__(self, root, engine)
|
||||
self.replvar = StringVar(root)
|
||||
|
||||
def open(self, text):
|
||||
SearchDialogBase.open(self, text)
|
||||
try:
|
||||
first = text.index("sel.first")
|
||||
except TclError:
|
||||
first = None
|
||||
try:
|
||||
last = text.index("sel.last")
|
||||
except TclError:
|
||||
last = None
|
||||
first = first or text.index("insert")
|
||||
last = last or first
|
||||
self.show_hit(first, last)
|
||||
self.ok = 1
|
||||
|
||||
def create_entries(self):
|
||||
SearchDialogBase.create_entries(self)
|
||||
self.replent = self.make_entry("Replace with:", self.replvar)
|
||||
|
||||
def create_command_buttons(self):
|
||||
SearchDialogBase.create_command_buttons(self)
|
||||
self.make_button("Find", self.find_it)
|
||||
self.make_button("Replace", self.replace_it)
|
||||
self.make_button("Replace+Find", self.default_command, 1)
|
||||
self.make_button("Replace All", self.replace_all)
|
||||
|
||||
def find_it(self, event=None):
|
||||
self.do_find(0)
|
||||
|
||||
def replace_it(self, event=None):
|
||||
if self.do_find(self.ok):
|
||||
self.do_replace()
|
||||
|
||||
def default_command(self, event=None):
|
||||
if self.do_find(self.ok):
|
||||
self.do_replace()
|
||||
self.do_find(0)
|
||||
|
||||
def replace_all(self, event=None):
|
||||
prog = self.engine.getprog()
|
||||
if not prog:
|
||||
return
|
||||
repl = self.replvar.get()
|
||||
text = self.text
|
||||
res = self.engine.search_text(text, prog)
|
||||
if not res:
|
||||
text.bell()
|
||||
return
|
||||
text.tag_remove("sel", "1.0", "end")
|
||||
text.tag_remove("hit", "1.0", "end")
|
||||
line = res[0]
|
||||
col = res[1].start()
|
||||
if self.engine.iswrap():
|
||||
line = 1
|
||||
col = 0
|
||||
ok = 1
|
||||
first = last = None
|
||||
# XXX ought to replace circular instead of top-to-bottom when wrapping
|
||||
text.undo_block_start()
|
||||
while 1:
|
||||
res = self.engine.search_forward(text, prog, line, col, 0, ok)
|
||||
if not res:
|
||||
break
|
||||
line, m = res
|
||||
chars = text.get("%d.0" % line, "%d.0" % (line+1))
|
||||
orig = m.group()
|
||||
new = re.pcre_expand(m, repl)
|
||||
i, j = m.span()
|
||||
first = "%d.%d" % (line, i)
|
||||
last = "%d.%d" % (line, j)
|
||||
if new == orig:
|
||||
text.mark_set("insert", last)
|
||||
else:
|
||||
text.mark_set("insert", first)
|
||||
if first != last:
|
||||
text.delete(first, last)
|
||||
if new:
|
||||
text.insert(first, new)
|
||||
col = i + len(new)
|
||||
ok = 0
|
||||
text.undo_block_stop()
|
||||
if first and last:
|
||||
self.show_hit(first, last)
|
||||
self.close()
|
||||
|
||||
def do_find(self, ok=0):
|
||||
if not self.engine.getprog():
|
||||
return 0
|
||||
text = self.text
|
||||
res = self.engine.search_text(text, None, ok)
|
||||
if not res:
|
||||
text.bell()
|
||||
return 0
|
||||
line, m = res
|
||||
i, j = m.span()
|
||||
first = "%d.%d" % (line, i)
|
||||
last = "%d.%d" % (line, j)
|
||||
self.show_hit(first, last)
|
||||
self.ok = 1
|
||||
return 1
|
||||
|
||||
def do_replace(self):
|
||||
prog = self.engine.getprog()
|
||||
if not prog:
|
||||
return 0
|
||||
text = self.text
|
||||
try:
|
||||
first = pos = text.index("sel.first")
|
||||
last = text.index("sel.last")
|
||||
except TclError:
|
||||
pos = None
|
||||
if not pos:
|
||||
first = last = pos = text.index("insert")
|
||||
line, col = SearchEngine.get_line_col(pos)
|
||||
chars = text.get("%d.0" % line, "%d.0" % (line+1))
|
||||
m = prog.match(chars, col)
|
||||
if not prog:
|
||||
return 0
|
||||
new = re.pcre_expand(m, self.replvar.get())
|
||||
text.mark_set("insert", first)
|
||||
text.undo_block_start()
|
||||
if m.group():
|
||||
text.delete(first, last)
|
||||
if new:
|
||||
text.insert(first, new)
|
||||
text.undo_block_stop()
|
||||
self.show_hit(first, text.index("insert"))
|
||||
self.ok = 0
|
||||
return 1
|
||||
|
||||
def show_hit(self, first, last):
|
||||
text = self.text
|
||||
text.mark_set("insert", first)
|
||||
text.tag_remove("sel", "1.0", "end")
|
||||
text.tag_add("sel", first, last)
|
||||
text.tag_remove("hit", "1.0", "end")
|
||||
if first == last:
|
||||
text.tag_add("hit", first)
|
||||
else:
|
||||
text.tag_add("hit", first, last)
|
||||
text.see("insert")
|
||||
text.update_idletasks()
|
||||
|
||||
def close(self, event=None):
|
||||
SearchDialogBase.close(self, event)
|
||||
self.text.tag_remove("hit", "1.0", "end")
|
|
@ -0,0 +1,169 @@
|
|||
"""Extension to execute code outside the Python shell window.
|
||||
|
||||
This adds the following commands (to the Edit menu, until there's a
|
||||
separate Python menu):
|
||||
|
||||
- Check module (Alt-F5) does a full syntax check of the current module.
|
||||
It also runs the tabnanny to catch any inconsistent tabs.
|
||||
|
||||
- Import module (F5) is equivalent to either import or reload of the
|
||||
current module. The window must have been saved previously. The
|
||||
module is added to sys.modules, and is also added to the __main__
|
||||
namespace. Output goes to the shell window.
|
||||
|
||||
- Run module (Control-F5) does the same but executes the module's
|
||||
code in the __main__ namespace.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import imp
|
||||
import tkMessageBox
|
||||
|
||||
indent_message = """Error: Inconsistent indentation detected!
|
||||
|
||||
This means that either:
|
||||
|
||||
(1) your indentation is outright incorrect (easy to fix), or
|
||||
|
||||
(2) your indentation mixes tabs and spaces in a way that depends on \
|
||||
how many spaces a tab is worth.
|
||||
|
||||
To fix case 2, change all tabs to spaces by using Select All followed \
|
||||
by Untabify Region (both in the Edit menu)."""
|
||||
|
||||
class ScriptBinding:
|
||||
|
||||
keydefs = {
|
||||
'<<check-module>>': ['<Alt-F5>', '<Meta-F5>'],
|
||||
'<<import-module>>': ['<F5>'],
|
||||
'<<run-script>>': ['<Control-F5>'],
|
||||
}
|
||||
|
||||
menudefs = [
|
||||
('edit', [None,
|
||||
('Check module', '<<check-module>>'),
|
||||
('Import module', '<<import-module>>'),
|
||||
('Run script', '<<run-script>>'),
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
def __init__(self, editwin):
|
||||
self.editwin = editwin
|
||||
# Provide instance variables referenced by Debugger
|
||||
# XXX This should be done differently
|
||||
self.flist = self.editwin.flist
|
||||
self.root = self.flist.root
|
||||
|
||||
def check_module_event(self, event):
|
||||
filename = self.getfilename()
|
||||
if not filename:
|
||||
return
|
||||
if not self.tabnanny(filename):
|
||||
return
|
||||
if not self.checksyntax(filename):
|
||||
return
|
||||
|
||||
def tabnanny(self, filename):
|
||||
import tabnanny
|
||||
import tokenize
|
||||
tabnanny.reset_globals()
|
||||
f = open(filename, 'r')
|
||||
try:
|
||||
tokenize.tokenize(f.readline, tabnanny.tokeneater)
|
||||
except tokenize.TokenError, msg:
|
||||
self.errorbox("Token error",
|
||||
"Token error:\n%s" % str(msg))
|
||||
return 0
|
||||
except tabnanny.NannyNag, nag:
|
||||
# The error messages from tabnanny are too confusing...
|
||||
self.editwin.gotoline(nag.get_lineno())
|
||||
self.errorbox("Tab/space error", indent_message)
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def checksyntax(self, filename):
|
||||
f = open(filename, 'r')
|
||||
source = f.read()
|
||||
f.close()
|
||||
if '\r' in source:
|
||||
import re
|
||||
source = re.sub(r"\r\n", "\n", source)
|
||||
if source and source[-1] != '\n':
|
||||
source = source + '\n'
|
||||
try:
|
||||
compile(source, filename, "exec")
|
||||
except (SyntaxError, OverflowError), err:
|
||||
try:
|
||||
msg, (errorfilename, lineno, offset, line) = err
|
||||
if not errorfilename:
|
||||
err.args = msg, (filename, lineno, offset, line)
|
||||
err.filename = filename
|
||||
except:
|
||||
lineno = None
|
||||
msg = "*** " + str(err)
|
||||
if lineno:
|
||||
self.editwin.gotoline(lineno)
|
||||
self.errorbox("Syntax error",
|
||||
"There's an error in your program:\n" + msg)
|
||||
return 1
|
||||
|
||||
def import_module_event(self, event):
|
||||
filename = self.getfilename()
|
||||
if not filename:
|
||||
return
|
||||
|
||||
modname, ext = os.path.splitext(os.path.basename(filename))
|
||||
if sys.modules.has_key(modname):
|
||||
mod = sys.modules[modname]
|
||||
else:
|
||||
mod = imp.new_module(modname)
|
||||
sys.modules[modname] = mod
|
||||
mod.__file__ = filename
|
||||
setattr(sys.modules['__main__'], modname, mod)
|
||||
|
||||
dir = os.path.dirname(filename)
|
||||
dir = os.path.normpath(os.path.abspath(dir))
|
||||
if dir not in sys.path:
|
||||
sys.path.insert(0, dir)
|
||||
|
||||
flist = self.editwin.flist
|
||||
shell = flist.open_shell()
|
||||
interp = shell.interp
|
||||
interp.runcode("reload(%s)" % modname)
|
||||
|
||||
def run_script_event(self, event):
|
||||
filename = self.getfilename()
|
||||
if not filename:
|
||||
return
|
||||
|
||||
flist = self.editwin.flist
|
||||
shell = flist.open_shell()
|
||||
interp = shell.interp
|
||||
if (not sys.argv or
|
||||
os.path.basename(sys.argv[0]) != os.path.basename(filename)):
|
||||
# XXX Too often this discards arguments the user just set...
|
||||
sys.argv = [filename]
|
||||
interp.execfile(filename)
|
||||
|
||||
def getfilename(self):
|
||||
# Logic to make sure we have a saved filename
|
||||
# XXX Better logic would offer to save!
|
||||
if not self.editwin.get_saved():
|
||||
self.errorbox("Not saved",
|
||||
"Please save first!")
|
||||
self.editwin.text.focus_set()
|
||||
return
|
||||
filename = self.editwin.io.filename
|
||||
if not filename:
|
||||
self.errorbox("No file name",
|
||||
"This window has no file name")
|
||||
return
|
||||
return filename
|
||||
|
||||
def errorbox(self, title, message):
|
||||
# XXX This should really be a function of EditorWindow...
|
||||
tkMessageBox.showerror(title, message, master=self.editwin.text)
|
||||
self.editwin.text.focus_set()
|
|
@ -0,0 +1,139 @@
|
|||
from Tkinter import *
|
||||
|
||||
class ScrolledList:
|
||||
|
||||
default = "(None)"
|
||||
|
||||
def __init__(self, master, **options):
|
||||
# Create top frame, with scrollbar and listbox
|
||||
self.master = master
|
||||
self.frame = frame = Frame(master)
|
||||
self.frame.pack(fill="both", expand=1)
|
||||
self.vbar = vbar = Scrollbar(frame, name="vbar")
|
||||
self.vbar.pack(side="right", fill="y")
|
||||
self.listbox = listbox = Listbox(frame, exportselection=0,
|
||||
background="white")
|
||||
if options:
|
||||
listbox.configure(options)
|
||||
listbox.pack(expand=1, fill="both")
|
||||
# Tie listbox and scrollbar together
|
||||
vbar["command"] = listbox.yview
|
||||
listbox["yscrollcommand"] = vbar.set
|
||||
# Bind events to the list box
|
||||
listbox.bind("<ButtonRelease-1>", self.click_event)
|
||||
listbox.bind("<Double-ButtonRelease-1>", self.double_click_event)
|
||||
listbox.bind("<ButtonPress-3>", self.popup_event)
|
||||
listbox.bind("<Key-Up>", self.up_event)
|
||||
listbox.bind("<Key-Down>", self.down_event)
|
||||
# Mark as empty
|
||||
self.clear()
|
||||
|
||||
def close(self):
|
||||
self.frame.destroy()
|
||||
|
||||
def clear(self):
|
||||
self.listbox.delete(0, "end")
|
||||
self.empty = 1
|
||||
self.listbox.insert("end", self.default)
|
||||
|
||||
def append(self, item):
|
||||
if self.empty:
|
||||
self.listbox.delete(0, "end")
|
||||
self.empty = 0
|
||||
self.listbox.insert("end", str(item))
|
||||
|
||||
def get(self, index):
|
||||
return self.listbox.get(index)
|
||||
|
||||
def click_event(self, event):
|
||||
self.listbox.activate("@%d,%d" % (event.x, event.y))
|
||||
index = self.listbox.index("active")
|
||||
self.select(index)
|
||||
self.on_select(index)
|
||||
return "break"
|
||||
|
||||
def double_click_event(self, event):
|
||||
index = self.listbox.index("active")
|
||||
self.select(index)
|
||||
self.on_double(index)
|
||||
return "break"
|
||||
|
||||
menu = None
|
||||
|
||||
def popup_event(self, event):
|
||||
if not self.menu:
|
||||
self.make_menu()
|
||||
menu = self.menu
|
||||
self.listbox.activate("@%d,%d" % (event.x, event.y))
|
||||
index = self.listbox.index("active")
|
||||
self.select(index)
|
||||
menu.tk_popup(event.x_root, event.y_root)
|
||||
|
||||
def make_menu(self):
|
||||
menu = Menu(self.listbox, tearoff=0)
|
||||
self.menu = menu
|
||||
self.fill_menu()
|
||||
|
||||
def up_event(self, event):
|
||||
index = self.listbox.index("active")
|
||||
if self.listbox.selection_includes(index):
|
||||
index = index - 1
|
||||
else:
|
||||
index = self.listbox.size() - 1
|
||||
if index < 0:
|
||||
self.listbox.bell()
|
||||
else:
|
||||
self.select(index)
|
||||
self.on_select(index)
|
||||
return "break"
|
||||
|
||||
def down_event(self, event):
|
||||
index = self.listbox.index("active")
|
||||
if self.listbox.selection_includes(index):
|
||||
index = index + 1
|
||||
else:
|
||||
index = 0
|
||||
if index >= self.listbox.size():
|
||||
self.listbox.bell()
|
||||
else:
|
||||
self.select(index)
|
||||
self.on_select(index)
|
||||
return "break"
|
||||
|
||||
def select(self, index):
|
||||
self.listbox.focus_set()
|
||||
self.listbox.activate(index)
|
||||
self.listbox.selection_clear(0, "end")
|
||||
self.listbox.selection_set(index)
|
||||
self.listbox.see(index)
|
||||
|
||||
# Methods to override for specific actions
|
||||
|
||||
def fill_menu(self):
|
||||
pass
|
||||
|
||||
def on_select(self, index):
|
||||
pass
|
||||
|
||||
def on_double(self, index):
|
||||
pass
|
||||
|
||||
|
||||
def test():
|
||||
root = Tk()
|
||||
root.protocol("WM_DELETE_WINDOW", root.destroy)
|
||||
class MyScrolledList(ScrolledList):
|
||||
def fill_menu(self): self.menu.add_command(label="pass")
|
||||
def on_select(self, index): print "select", self.get(index)
|
||||
def on_double(self, index): print "double", self.get(index)
|
||||
s = MyScrolledList(root)
|
||||
for i in range(30):
|
||||
s.append("item %02d" % i)
|
||||
return root
|
||||
|
||||
def main():
|
||||
root = test()
|
||||
root.mainloop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,97 @@
|
|||
import tkSimpleDialog
|
||||
|
||||
###$ event <<find>>
|
||||
###$ win <Control-f>
|
||||
###$ unix <Control-u><Control-u><Control-s>
|
||||
|
||||
###$ event <<find-again>>
|
||||
###$ win <Control-g>
|
||||
###$ win <F3>
|
||||
###$ unix <Control-u><Control-s>
|
||||
|
||||
###$ event <<find-selection>>
|
||||
###$ win <Control-F3>
|
||||
###$ unix <Control-s>
|
||||
|
||||
###$ event <<find-in-files>>
|
||||
###$ win <Alt-F3>
|
||||
|
||||
###$ event <<replace>>
|
||||
###$ win <Control-h>
|
||||
|
||||
###$ event <<goto-line>>
|
||||
###$ win <Alt-g>
|
||||
###$ unix <Alt-g>
|
||||
|
||||
class SearchBinding:
|
||||
|
||||
windows_keydefs = {
|
||||
'<<find-again>>': ['<Control-g>', '<F3>'],
|
||||
'<<find-in-files>>': ['<Alt-F3>'],
|
||||
'<<find-selection>>': ['<Control-F3>'],
|
||||
'<<find>>': ['<Control-f>'],
|
||||
'<<replace>>': ['<Control-h>'],
|
||||
'<<goto-line>>': ['<Alt-g>'],
|
||||
}
|
||||
|
||||
unix_keydefs = {
|
||||
'<<find-again>>': ['<Control-u><Control-s>'],
|
||||
'<<find-in-files>>': ['<Alt-s>', '<Meta-s>'],
|
||||
'<<find-selection>>': ['<Control-s>'],
|
||||
'<<find>>': ['<Control-u><Control-u><Control-s>'],
|
||||
'<<replace>>': ['<Control-r>'],
|
||||
'<<goto-line>>': ['<Alt-g>', '<Meta-g>'],
|
||||
}
|
||||
|
||||
menudefs = [
|
||||
('edit', [
|
||||
None,
|
||||
('_Find...', '<<find>>'),
|
||||
('Find a_gain', '<<find-again>>'),
|
||||
('Find _selection', '<<find-selection>>'),
|
||||
('Find in Files...', '<<find-in-files>>'),
|
||||
('R_eplace...', '<<replace>>'),
|
||||
('Go to _line', '<<goto-line>>'),
|
||||
]),
|
||||
]
|
||||
|
||||
def __init__(self, editwin):
|
||||
self.editwin = editwin
|
||||
|
||||
def find_event(self, event):
|
||||
import SearchDialog
|
||||
SearchDialog.find(self.editwin.text)
|
||||
return "break"
|
||||
|
||||
def find_again_event(self, event):
|
||||
import SearchDialog
|
||||
SearchDialog.find_again(self.editwin.text)
|
||||
return "break"
|
||||
|
||||
def find_selection_event(self, event):
|
||||
import SearchDialog
|
||||
SearchDialog.find_selection(self.editwin.text)
|
||||
return "break"
|
||||
|
||||
def find_in_files_event(self, event):
|
||||
import GrepDialog
|
||||
GrepDialog.grep(self.editwin.text, self.editwin.io, self.editwin.flist)
|
||||
return "break"
|
||||
|
||||
def replace_event(self, event):
|
||||
import ReplaceDialog
|
||||
ReplaceDialog.replace(self.editwin.text)
|
||||
return "break"
|
||||
|
||||
def goto_line_event(self, event):
|
||||
text = self.editwin.text
|
||||
lineno = tkSimpleDialog.askinteger("Goto",
|
||||
"Go to line number:",
|
||||
parent=text)
|
||||
if lineno is None:
|
||||
return "break"
|
||||
if lineno <= 0:
|
||||
text.bell()
|
||||
return "break"
|
||||
text.mark_set("insert", "%d.0" % lineno)
|
||||
text.see("insert")
|
|
@ -0,0 +1,67 @@
|
|||
from Tkinter import *
|
||||
import SearchEngine
|
||||
from SearchDialogBase import SearchDialogBase
|
||||
|
||||
|
||||
def _setup(text):
|
||||
root = text._root()
|
||||
engine = SearchEngine.get(root)
|
||||
if not hasattr(engine, "_searchdialog"):
|
||||
engine._searchdialog = SearchDialog(root, engine)
|
||||
return engine._searchdialog
|
||||
|
||||
def find(text):
|
||||
return _setup(text).open(text)
|
||||
|
||||
def find_again(text):
|
||||
return _setup(text).find_again(text)
|
||||
|
||||
def find_selection(text):
|
||||
return _setup(text).find_selection(text)
|
||||
|
||||
class SearchDialog(SearchDialogBase):
|
||||
|
||||
def create_widgets(self):
|
||||
f = SearchDialogBase.create_widgets(self)
|
||||
self.make_button("Find", self.default_command, 1)
|
||||
|
||||
def default_command(self, event=None):
|
||||
if not self.engine.getprog():
|
||||
return
|
||||
if self.find_again(self.text):
|
||||
self.close()
|
||||
|
||||
def find_again(self, text):
|
||||
if not self.engine.getpat():
|
||||
self.open(text)
|
||||
return 0
|
||||
if not self.engine.getprog():
|
||||
return 0
|
||||
res = self.engine.search_text(text)
|
||||
if res:
|
||||
line, m = res
|
||||
i, j = m.span()
|
||||
first = "%d.%d" % (line, i)
|
||||
last = "%d.%d" % (line, j)
|
||||
try:
|
||||
selfirst = text.index("sel.first")
|
||||
sellast = text.index("sel.last")
|
||||
if selfirst == first and sellast == last:
|
||||
text.bell()
|
||||
return 0
|
||||
except TclError:
|
||||
pass
|
||||
text.tag_remove("sel", "1.0", "end")
|
||||
text.tag_add("sel", first, last)
|
||||
text.mark_set("insert", self.engine.isback() and first or last)
|
||||
text.see("insert")
|
||||
return 1
|
||||
else:
|
||||
text.bell()
|
||||
return 0
|
||||
|
||||
def find_selection(self, text):
|
||||
pat = text.get("sel.first", "sel.last")
|
||||
if pat:
|
||||
self.engine.setcookedpat(pat)
|
||||
return self.find_again(text)
|
|
@ -0,0 +1,129 @@
|
|||
import string
|
||||
from Tkinter import *
|
||||
|
||||
class SearchDialogBase:
|
||||
|
||||
title = "Search Dialog"
|
||||
icon = "Search"
|
||||
needwrapbutton = 1
|
||||
|
||||
def __init__(self, root, engine):
|
||||
self.root = root
|
||||
self.engine = engine
|
||||
self.top = None
|
||||
|
||||
def open(self, text):
|
||||
self.text = text
|
||||
if not self.top:
|
||||
self.create_widgets()
|
||||
else:
|
||||
self.top.deiconify()
|
||||
self.top.tkraise()
|
||||
self.ent.focus_set()
|
||||
self.ent.selection_range(0, "end")
|
||||
self.ent.icursor(0)
|
||||
self.top.grab_set()
|
||||
|
||||
def close(self, event=None):
|
||||
if self.top:
|
||||
self.top.grab_release()
|
||||
self.top.withdraw()
|
||||
|
||||
def create_widgets(self):
|
||||
top = Toplevel(self.root)
|
||||
top.bind("<Return>", self.default_command)
|
||||
top.bind("<Escape>", self.close)
|
||||
top.protocol("WM_DELETE_WINDOW", self.close)
|
||||
top.wm_title(self.title)
|
||||
top.wm_iconname(self.icon)
|
||||
self.top = top
|
||||
|
||||
self.row = 0
|
||||
self.top.grid_columnconfigure(0, weight=0)
|
||||
self.top.grid_columnconfigure(1, weight=100)
|
||||
|
||||
self.create_entries()
|
||||
self.create_option_buttons()
|
||||
self.create_other_buttons()
|
||||
return self.create_command_buttons()
|
||||
|
||||
def make_entry(self, label, var):
|
||||
l = Label(self.top, text=label)
|
||||
l.grid(row=self.row, col=0, sticky="w")
|
||||
e = Entry(self.top, textvariable=var, exportselection=0)
|
||||
e.grid(row=self.row, col=1, sticky="we")
|
||||
self.row = self.row + 1
|
||||
return e
|
||||
|
||||
def make_frame(self):
|
||||
f = Frame(self.top)
|
||||
f.grid(row=self.row, col=0, columnspan=2, sticky="we")
|
||||
self.row = self.row + 1
|
||||
return f
|
||||
|
||||
def make_button(self, label, command, isdef=0, side="left"):
|
||||
b = Button(self.buttonframe,
|
||||
text=label, command=command,
|
||||
default=isdef and "active" or "normal")
|
||||
b.pack(side=side)
|
||||
return b
|
||||
|
||||
def create_entries(self):
|
||||
self.ent = self.make_entry("Find:", self.engine.patvar)
|
||||
|
||||
def create_option_buttons(self):
|
||||
f = self.make_frame()
|
||||
|
||||
btn = Checkbutton(f, anchor="w",
|
||||
variable=self.engine.revar,
|
||||
text="Regular expression")
|
||||
btn.pack(side="left", fill="both")
|
||||
if self.engine.isre():
|
||||
btn.select()
|
||||
|
||||
btn = Checkbutton(f, anchor="w",
|
||||
variable=self.engine.casevar,
|
||||
text="Match case")
|
||||
btn.pack(side="left", fill="both")
|
||||
if self.engine.iscase():
|
||||
btn.select()
|
||||
|
||||
btn = Checkbutton(f, anchor="w",
|
||||
variable=self.engine.wordvar,
|
||||
text="Whole word")
|
||||
btn.pack(side="left", fill="both")
|
||||
if self.engine.isword():
|
||||
btn.select()
|
||||
|
||||
if self.needwrapbutton:
|
||||
btn = Checkbutton(f, anchor="w",
|
||||
variable=self.engine.wrapvar,
|
||||
text="Wrap around")
|
||||
btn.pack(side="left", fill="both")
|
||||
if self.engine.iswrap():
|
||||
btn.select()
|
||||
|
||||
def create_other_buttons(self):
|
||||
f = self.make_frame()
|
||||
|
||||
lbl = Label(f, text="Direction: ")
|
||||
lbl.pack(side="left")
|
||||
|
||||
btn = Radiobutton(f, anchor="w",
|
||||
variable=self.engine.backvar, value=1,
|
||||
text="Up")
|
||||
btn.pack(side="left", fill="both")
|
||||
if self.engine.isback():
|
||||
btn.select()
|
||||
|
||||
btn = Radiobutton(f, anchor="w",
|
||||
variable=self.engine.backvar, value=0,
|
||||
text="Down")
|
||||
btn.pack(side="left", fill="both")
|
||||
if not self.engine.isback():
|
||||
btn.select()
|
||||
|
||||
def create_command_buttons(self):
|
||||
f = self.buttonframe = self.make_frame()
|
||||
b = self.make_button("close", self.close, side="right")
|
||||
b.lower()
|
|
@ -0,0 +1,221 @@
|
|||
import string
|
||||
import re
|
||||
from Tkinter import *
|
||||
import tkMessageBox
|
||||
|
||||
def get(root):
|
||||
if not hasattr(root, "_searchengine"):
|
||||
root._searchengine = SearchEngine(root)
|
||||
# XXX This will never garbage-collect -- who cares
|
||||
return root._searchengine
|
||||
|
||||
class SearchEngine:
|
||||
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
# State shared by search, replace, and grep;
|
||||
# the search dialogs bind these to UI elements.
|
||||
self.patvar = StringVar(root) # search pattern
|
||||
self.revar = BooleanVar(root) # regular expression?
|
||||
self.casevar = BooleanVar(root) # match case?
|
||||
self.wordvar = BooleanVar(root) # match whole word?
|
||||
self.wrapvar = BooleanVar(root) # wrap around buffer?
|
||||
self.wrapvar.set(1) # (on by default)
|
||||
self.backvar = BooleanVar(root) # search backwards?
|
||||
|
||||
# Access methods
|
||||
|
||||
def getpat(self):
|
||||
return self.patvar.get()
|
||||
|
||||
def setpat(self, pat):
|
||||
self.patvar.set(pat)
|
||||
|
||||
def isre(self):
|
||||
return self.revar.get()
|
||||
|
||||
def iscase(self):
|
||||
return self.casevar.get()
|
||||
|
||||
def isword(self):
|
||||
return self.wordvar.get()
|
||||
|
||||
def iswrap(self):
|
||||
return self.wrapvar.get()
|
||||
|
||||
def isback(self):
|
||||
return self.backvar.get()
|
||||
|
||||
# Higher level access methods
|
||||
|
||||
def getcookedpat(self):
|
||||
pat = self.getpat()
|
||||
if not self.isre():
|
||||
pat = re.escape(pat)
|
||||
if self.isword():
|
||||
pat = r"\b%s\b" % pat
|
||||
return pat
|
||||
|
||||
def getprog(self):
|
||||
pat = self.getpat()
|
||||
if not pat:
|
||||
self.report_error(pat, "Empty regular expression")
|
||||
return None
|
||||
pat = self.getcookedpat()
|
||||
flags = 0
|
||||
if not self.iscase():
|
||||
flags = flags | re.IGNORECASE
|
||||
try:
|
||||
prog = re.compile(pat, flags)
|
||||
except re.error, what:
|
||||
try:
|
||||
msg, col = what
|
||||
except:
|
||||
msg = str(what)
|
||||
col = -1
|
||||
self.report_error(pat, msg, col)
|
||||
return None
|
||||
return prog
|
||||
|
||||
def report_error(self, pat, msg, col=-1):
|
||||
# Derived class could overrid this with something fancier
|
||||
msg = "Error: " + str(msg)
|
||||
if pat:
|
||||
msg = msg + "\np\Pattern: " + str(pat)
|
||||
if col >= 0:
|
||||
msg = msg + "\nOffset: " + str(col)
|
||||
tkMessageBox.showerror("Regular expression error",
|
||||
msg, master=self.root)
|
||||
|
||||
def setcookedpat(self, pat):
|
||||
if self.isre():
|
||||
pat = re.escape(pat)
|
||||
self.setpat(pat)
|
||||
|
||||
def search_text(self, text, prog=None, ok=0):
|
||||
"""Search a text widget for the pattern.
|
||||
|
||||
If prog is given, it should be the precompiled pattern.
|
||||
Return a tuple (lineno, matchobj); None if not found.
|
||||
|
||||
This obeys the wrap and direction (back) settings.
|
||||
|
||||
The search starts at the selection (if there is one) or
|
||||
at the insert mark (otherwise). If the search is forward,
|
||||
it starts at the right of the selection; for a backward
|
||||
search, it starts at the left end. An empty match exactly
|
||||
at either end of the selection (or at the insert mark if
|
||||
there is no selection) is ignored unless the ok flag is true
|
||||
-- this is done to guarantee progress.
|
||||
|
||||
If the search is allowed to wrap around, it will return the
|
||||
original selection if (and only if) it is the only match.
|
||||
|
||||
"""
|
||||
if not prog:
|
||||
prog = self.getprog()
|
||||
if not prog:
|
||||
return None # Compilation failed -- stop
|
||||
wrap = self.wrapvar.get()
|
||||
first, last = get_selection(text)
|
||||
if self.isback():
|
||||
if ok:
|
||||
start = last
|
||||
else:
|
||||
start = first
|
||||
line, col = get_line_col(start)
|
||||
res = self.search_backward(text, prog, line, col, wrap, ok)
|
||||
else:
|
||||
if ok:
|
||||
start = first
|
||||
else:
|
||||
start = last
|
||||
line, col = get_line_col(start)
|
||||
res = self.search_forward(text, prog, line, col, wrap, ok)
|
||||
return res
|
||||
|
||||
def search_forward(self, text, prog, line, col, wrap, ok=0):
|
||||
wrapped = 0
|
||||
startline = line
|
||||
chars = text.get("%d.0" % line, "%d.0" % (line+1))
|
||||
while chars:
|
||||
m = prog.search(chars[:-1], col)
|
||||
if m:
|
||||
if ok or m.end() > col:
|
||||
return line, m
|
||||
line = line + 1
|
||||
if wrapped and line > startline:
|
||||
break
|
||||
col = 0
|
||||
ok = 1
|
||||
chars = text.get("%d.0" % line, "%d.0" % (line+1))
|
||||
if not chars and wrap:
|
||||
wrapped = 1
|
||||
wrap = 0
|
||||
line = 1
|
||||
chars = text.get("1.0", "2.0")
|
||||
return None
|
||||
|
||||
def search_backward(self, text, prog, line, col, wrap, ok=0):
|
||||
wrapped = 0
|
||||
startline = line
|
||||
chars = text.get("%d.0" % line, "%d.0" % (line+1))
|
||||
while 1:
|
||||
m = search_reverse(prog, chars[:-1], col)
|
||||
if m:
|
||||
if ok or m.start() < col:
|
||||
return line, m
|
||||
line = line - 1
|
||||
if wrapped and line < startline:
|
||||
break
|
||||
ok = 1
|
||||
if line <= 0:
|
||||
if not wrap:
|
||||
break
|
||||
wrapped = 1
|
||||
wrap = 0
|
||||
pos = text.index("end-1c")
|
||||
line, col = map(int, string.split(pos, "."))
|
||||
chars = text.get("%d.0" % line, "%d.0" % (line+1))
|
||||
col = len(chars) - 1
|
||||
return None
|
||||
|
||||
# Helper to search backwards in a string.
|
||||
# (Optimized for the case where the pattern isn't found.)
|
||||
|
||||
def search_reverse(prog, chars, col):
|
||||
m = prog.search(chars)
|
||||
if not m:
|
||||
return None
|
||||
found = None
|
||||
i, j = m.span()
|
||||
while i < col and j <= col:
|
||||
found = m
|
||||
if i == j:
|
||||
j = j+1
|
||||
m = prog.search(chars, j)
|
||||
if not m:
|
||||
break
|
||||
i, j = m.span()
|
||||
return found
|
||||
|
||||
# Helper to get selection end points, defaulting to insert mark.
|
||||
# Return a tuple of indices ("line.col" strings).
|
||||
|
||||
def get_selection(text):
|
||||
try:
|
||||
first = text.index("sel.first")
|
||||
last = text.index("sel.last")
|
||||
except TclError:
|
||||
first = last = None
|
||||
if not first:
|
||||
first = text.index("insert")
|
||||
if not last:
|
||||
last = first
|
||||
return first, last
|
||||
|
||||
# Helper to parse a text index into a (line, col) tuple.
|
||||
|
||||
def get_line_col(index):
|
||||
line, col = map(int, string.split(index, ".")) # Fails on invalid index
|
||||
return line, col
|
|
@ -0,0 +1,92 @@
|
|||
from Tkinter import *
|
||||
|
||||
class Separator:
|
||||
|
||||
def __init__(self, master, orient, min=10, thickness=5, bg=None):
|
||||
self.min = max(1, min)
|
||||
self.thickness = max(1, thickness)
|
||||
if orient in ("h", "horizontal"):
|
||||
self.side = "left"
|
||||
self.dim = "width"
|
||||
self.dir = "x"
|
||||
self.cursor = "sb_h_double_arrow"
|
||||
elif orient in ("v", "vertical"):
|
||||
self.side = "top"
|
||||
self.dim = "height"
|
||||
self.dir = "y"
|
||||
self.cursor = "sb_v_double_arrow"
|
||||
else:
|
||||
raise ValueError, "Separator: orient should be h or v"
|
||||
self.winfo_dim = "winfo_" + self.dim
|
||||
self.master = master = Frame(master)
|
||||
master.pack(expand=1, fill="both")
|
||||
self.f1 = Frame(master)
|
||||
self.f1.pack(expand=1, fill="both", side=self.side)
|
||||
self.div = Frame(master, cursor=self.cursor)
|
||||
self.div[self.dim] = self.thickness
|
||||
self.div.pack(fill="both", side=self.side)
|
||||
self.f2 = Frame(master)
|
||||
self.f2.pack(expand=1, fill="both", side=self.side)
|
||||
self.div.bind("<ButtonPress-1>", self.divider_press)
|
||||
if bg:
|
||||
##self.f1["bg"] = bg
|
||||
##self.f2["bg"] = bg
|
||||
self.div["bg"] = bg
|
||||
|
||||
def parts(self):
|
||||
return self.f1, self.f2
|
||||
|
||||
def divider_press(self, event):
|
||||
self.press_event = event
|
||||
self.f1.pack_propagate(0)
|
||||
self.f2.pack_propagate(0)
|
||||
for f in self.f1, self.f2:
|
||||
for dim in "width", "height":
|
||||
f[dim] = getattr(f, "winfo_"+dim)()
|
||||
self.div.bind("<Motion>", self.div_motion)
|
||||
self.div.bind("<ButtonRelease-1>", self.div_release)
|
||||
self.div.grab_set()
|
||||
|
||||
def div_motion(self, event):
|
||||
delta = getattr(event, self.dir) - getattr(self.press_event, self.dir)
|
||||
if delta:
|
||||
dim1 = getattr(self.f1, self.winfo_dim)()
|
||||
dim2 = getattr(self.f2, self.winfo_dim)()
|
||||
delta = max(delta, self.min-dim1)
|
||||
delta = min(delta, dim2-self.min)
|
||||
dim1 = dim1 + delta
|
||||
dim2 = dim2 - delta
|
||||
self.f1[self.dim] = dim1
|
||||
self.f2[self.dim] = dim2
|
||||
|
||||
def div_release(self, event):
|
||||
self.div_motion(event)
|
||||
self.div.unbind("<Motion>")
|
||||
self.div.grab_release()
|
||||
|
||||
class VSeparator(Separator):
|
||||
|
||||
def __init__(self, master, min=10, thickness=5, bg=None):
|
||||
Separator.__init__(self, master, "v", min, thickness, bg)
|
||||
|
||||
class HSeparator(Separator):
|
||||
|
||||
def __init__(self, master, min=10, thickness=5, bg=None):
|
||||
Separator.__init__(self, master, "h", min, thickness, bg)
|
||||
|
||||
def main():
|
||||
root = Tk()
|
||||
tlist = []
|
||||
outer = HSeparator(root, bg="red")
|
||||
for part in outer.parts():
|
||||
inner = VSeparator(part, bg="blue")
|
||||
for f in inner.parts():
|
||||
t = Text(f, width=40, height=10, borderwidth=0)
|
||||
t.pack(fill="both", expand=1)
|
||||
tlist.append(t)
|
||||
tlist[0].insert("1.0", "Make your own Mondrian!")
|
||||
tlist[1].insert("1.0", "Move the colored dividers...")
|
||||
root.mainloop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,135 @@
|
|||
import string
|
||||
from Tkinter import *
|
||||
import linecache
|
||||
|
||||
from TreeWidget import TreeNode, TreeItem, ScrolledCanvas
|
||||
from ObjectBrowser import ObjectTreeItem, make_objecttreeitem
|
||||
from OldStackViewer import StackViewer, NamespaceViewer
|
||||
|
||||
def StackBrowser(root, flist=None, stack=None):
|
||||
top = Toplevel(root)
|
||||
sc = ScrolledCanvas(top, bg="white", highlightthickness=0)
|
||||
sc.frame.pack(expand=1, fill="both")
|
||||
item = StackTreeItem(flist)
|
||||
node = TreeNode(sc.canvas, None, item)
|
||||
node.expand()
|
||||
|
||||
class StackTreeItem(TreeItem):
|
||||
|
||||
def __init__(self, flist=None):
|
||||
self.flist = flist
|
||||
self.stack = get_stack()
|
||||
self.text = get_exception()
|
||||
|
||||
def GetText(self):
|
||||
return self.text
|
||||
|
||||
def GetSubList(self):
|
||||
sublist = []
|
||||
for info in self.stack:
|
||||
item = FrameTreeItem(info, self.flist)
|
||||
sublist.append(item)
|
||||
return sublist
|
||||
|
||||
class FrameTreeItem(TreeItem):
|
||||
|
||||
def __init__(self, info, flist):
|
||||
self.info = info
|
||||
self.flist = flist
|
||||
|
||||
def GetText(self):
|
||||
frame, lineno = self.info
|
||||
try:
|
||||
modname = frame.f_globals["__name__"]
|
||||
except:
|
||||
modname = "?"
|
||||
code = frame.f_code
|
||||
filename = code.co_filename
|
||||
funcname = code.co_name
|
||||
sourceline = linecache.getline(filename, lineno)
|
||||
sourceline = string.strip(sourceline)
|
||||
if funcname in ("?", "", None):
|
||||
item = "%s, line %d: %s" % (modname, lineno, sourceline)
|
||||
else:
|
||||
item = "%s.%s(...), line %d: %s" % (modname, funcname,
|
||||
lineno, sourceline)
|
||||
## if i == index:
|
||||
## item = "> " + item
|
||||
return item
|
||||
|
||||
def GetSubList(self):
|
||||
frame, lineno = self.info
|
||||
sublist = []
|
||||
if frame.f_globals is not frame.f_locals:
|
||||
item = VariablesTreeItem("<locals>", frame.f_locals, self.flist)
|
||||
sublist.append(item)
|
||||
item = VariablesTreeItem("<globals>", frame.f_globals, self.flist)
|
||||
sublist.append(item)
|
||||
return sublist
|
||||
|
||||
def OnDoubleClick(self):
|
||||
if self.flist:
|
||||
frame, lineno = self.info
|
||||
filename = frame.f_code.co_filename
|
||||
edit = self.flist.open(filename)
|
||||
edit.gotoline(lineno)
|
||||
|
||||
class VariablesTreeItem(ObjectTreeItem):
|
||||
|
||||
def GetText(self):
|
||||
return self.labeltext
|
||||
|
||||
def GetLabelText(self):
|
||||
return None
|
||||
|
||||
def IsExpandable(self):
|
||||
return len(self.object) > 0
|
||||
|
||||
def keys(self):
|
||||
return self.object.keys()
|
||||
|
||||
def GetSubList(self):
|
||||
sublist = []
|
||||
for key in self.keys():
|
||||
try:
|
||||
value = self.object[key]
|
||||
except KeyError:
|
||||
continue
|
||||
def setfunction(value, key=key, object=self.object):
|
||||
object[key] = value
|
||||
item = make_objecttreeitem(key + " =", value, setfunction)
|
||||
sublist.append(item)
|
||||
return sublist
|
||||
|
||||
def get_stack(t=None, f=None):
|
||||
if t is None:
|
||||
t = sys.last_traceback
|
||||
stack = []
|
||||
if t and t.tb_frame is f:
|
||||
t = t.tb_next
|
||||
while f is not None:
|
||||
stack.append((f, f.f_lineno))
|
||||
if f is self.botframe:
|
||||
break
|
||||
f = f.f_back
|
||||
stack.reverse()
|
||||
while t is not None:
|
||||
stack.append((t.tb_frame, t.tb_lineno))
|
||||
t = t.tb_next
|
||||
return stack
|
||||
|
||||
def get_exception(type=None, value=None):
|
||||
if type is None:
|
||||
type = sys.last_type
|
||||
value = sys.last_value
|
||||
if hasattr(type, "__name__"):
|
||||
type = type.__name__
|
||||
s = str(type)
|
||||
if value is not None:
|
||||
s = s + ": " + str(value)
|
||||
return s
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = Tk()
|
||||
root.withdraw()
|
||||
StackBrowser(root)
|
|
@ -0,0 +1,205 @@
|
|||
|
||||
TO DO:
|
||||
|
||||
- improve debugger:
|
||||
- manage breakpoints globally, allow bp deletion, tbreak, cbreak etc.
|
||||
- real object browser
|
||||
- help on how to use it (a simple help button will do wonders)
|
||||
- performance? (updates of large sets of locals are slow)
|
||||
- better integration of "debug module"
|
||||
- debugger should be global resource (attached to flist, not to shell)
|
||||
- fix the stupid bug where you need to step twice
|
||||
- display class name in stack viewer entries for methods
|
||||
- suppress tracing through IDLE internals (e.g. print)
|
||||
- add a button to suppress through a specific module or class or method
|
||||
- insert the initial current directory into sys.path
|
||||
- default directory attribute for each window instead of only for windows
|
||||
that have an associated filename
|
||||
- command expansion from keywords, module contents, other buffers, etc.
|
||||
- "Recent documents" menu item
|
||||
- Filter region command
|
||||
- Optional horizontal scroll bar
|
||||
- more Emacsisms:
|
||||
- ^K should cut to buffer
|
||||
- M-[, M-] to move by paragraphs
|
||||
- incremental search?
|
||||
- search should indicate wrap-around in some way
|
||||
- restructure state sensitive code to avoid testing flags all the time
|
||||
- persistent user state (e.g. window and cursor positions, bindings)
|
||||
- make backups when saving
|
||||
- check file mtimes at various points
|
||||
- Pluggable interface with RCS/CVS/Perforce/Clearcase
|
||||
- better help?
|
||||
- don't open second class browser on same module (nor second path browser)
|
||||
- unify class and path browsers
|
||||
- Need to define a standard way whereby one can determine one is running
|
||||
inside IDLE (needed for Tk mainloop, also handy for $PYTHONSTARTUP)
|
||||
- Add more utility methods for use by extensions (a la get_selection)
|
||||
- Way to run command in totally separate interpreter (fork+os.system?)
|
||||
- Way to find definition of fully-qualified name:
|
||||
In other words, select "UserDict.UserDict", hit some magic key and
|
||||
it loads up UserDict.py and finds the first def or class for UserDict.
|
||||
- need a way to force colorization on/off
|
||||
- need a way to force auto-indent on/off
|
||||
|
||||
Details:
|
||||
|
||||
- when there's a selection, left/right arrow should go to either
|
||||
end of the selection
|
||||
- ^O (on Unix -- open-line) should honor autoindent
|
||||
- after paste, show end of pasted text
|
||||
- on Windows, should turn short filename to long filename (not only in argv!)
|
||||
(shouldn't this be done -- or undone -- by ntpath.normpath?)
|
||||
- new autoindent after colon even indents when the colon is in a comment!
|
||||
- sometimes forward slashes in pathname remain
|
||||
- sometimes star in window name remains in Windows menu
|
||||
- With unix bindings, ESC by itself is ignored
|
||||
- Sometimes for no apparent reason a selection from the cursor to the
|
||||
end of the command buffer appears, which is hard to get rid of
|
||||
because it stays when you are typing!
|
||||
- The Line/Col in the status bar can be wrong initially in PyShell
|
||||
|
||||
Structural problems:
|
||||
|
||||
- too much knowledge in FileList about EditorWindow (for example)
|
||||
- should add some primitives for accessing the selection etc.
|
||||
to repeat cumbersome code over and over
|
||||
|
||||
======================================================================
|
||||
|
||||
Jeff Bauer suggests:
|
||||
|
||||
- Open Module doesn't appear to handle hierarchical packages.
|
||||
- Class browser should also allow hierarchical packages.
|
||||
- Open and Open Module could benefit from a history,
|
||||
either command line style, or Microsoft recent-file
|
||||
style.
|
||||
- Add a Smalltalk-style inspector (i.e. Tkinspect)
|
||||
|
||||
The last suggestion is already a reality, but not yet
|
||||
integrated into IDLE. I use a module called inspector.py,
|
||||
that used to be available from python.org(?) It no longer
|
||||
appears to be in the contributed section, and the source
|
||||
has no author attribution.
|
||||
|
||||
In any case, the code is useful for visually navigating
|
||||
an object's attributes, including its container hierarchy.
|
||||
|
||||
>>> from inspector import Tkinspect
|
||||
>>> Tkinspect(None, myObject)
|
||||
|
||||
Tkinspect could probably be extended and refined to
|
||||
integrate better into IDLE.
|
||||
|
||||
======================================================================
|
||||
|
||||
Comparison to PTUI
|
||||
------------------
|
||||
|
||||
+ PTUI's help is better (HTML!)
|
||||
|
||||
+ PTUI can attach a shell to any module
|
||||
|
||||
+ PTUI has some more I/O commands:
|
||||
open multiple
|
||||
append
|
||||
examine (what's that?)
|
||||
|
||||
======================================================================
|
||||
|
||||
Notes after trying to run Grail
|
||||
-------------------------------
|
||||
|
||||
- Grail does stuff to sys.path based on sys.argv[0]; you must set
|
||||
sys.argv[0] to something decent first (it is normally set to the path of
|
||||
the idle script).
|
||||
|
||||
- Grail must be exec'ed in __main__ because that's imported by some
|
||||
other parts of Grail.
|
||||
|
||||
- Grail uses a module called History and so does idle :-(
|
||||
|
||||
======================================================================
|
||||
|
||||
Robin Friedrich's items:
|
||||
|
||||
Things I'd like to see:
|
||||
- I'd like support for shift-click extending the selection. There's a
|
||||
bug now that it doesn't work the first time you try it.
|
||||
- Printing is needed. How hard can that be on Windows?
|
||||
- The python-mode trick of autoindenting a line with <tab> is neat and
|
||||
very handy.
|
||||
- (someday) a spellchecker for docstrings and comments.
|
||||
- a pagedown/up command key which moves to next class/def statement (top
|
||||
level)
|
||||
- split window capability
|
||||
- DnD text relocation/copying
|
||||
|
||||
Things I don't want to see.
|
||||
- line numbers... will probably slow things down way too much.
|
||||
- Please use another icon for the tree browser leaf. The small snake
|
||||
isn't cutting it.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
|
||||
- Customizable views (multi-window or multi-pane). (Markus Gritsch)
|
||||
|
||||
- Being able to double click (maybe double right click) on a callable
|
||||
object in the editor which shows the source of the object, if
|
||||
possible. (Gerrit Holl)
|
||||
|
||||
- Hooks into the guts, like in Emacs. (Mike Romberg)
|
||||
|
||||
- Sharing the editor with a remote tutor. (Martijn Faassen)
|
||||
|
||||
- Multiple views on the same file. (Tony J Ibbs)
|
||||
|
||||
- Store breakpoints in a global (per-project) database (GvR); Dirk
|
||||
Heise adds: save some space-trimmed context and search around when
|
||||
reopening a file that might have been edited by someone else.
|
||||
|
||||
- Capture menu events in extensions without changing the IDLE source.
|
||||
(Matthias Barmeier)
|
||||
|
||||
- Use overlapping panels (a "notebook" in MFC terms I think) for info
|
||||
that doesn't need to be accessible simultaneously (e.g. HTML source
|
||||
and output). Use multi-pane windows for info that does need to be
|
||||
shown together (e.g. class browser and source). (Albert Brandl)
|
||||
|
||||
- A project should invisibly track all symbols, for instant search,
|
||||
replace and cross-ref. Projects should be allowed to span multiple
|
||||
directories, hosts, etc. Project management files are placed in a
|
||||
directory you specify. A global mapping between project names and
|
||||
project directories should exist [not so sure --GvR]. (Tim Peters)
|
||||
|
||||
- Merge attr-tips and auto-expand. (Mark Hammond, Tim Peters)
|
||||
|
||||
- Python Shell should behave more like a "shell window" as users know
|
||||
it -- i.e. you can only edit the current command, and the cursor can't
|
||||
escape from the command area. (Albert Brandl)
|
||||
|
||||
- Set X11 class to "idle/Idle", set icon and title to something
|
||||
beginning with "idle" -- for window manangers. (Randall Hopper)
|
||||
|
||||
- Config files editable through a preferences dialog. (me)
|
||||
|
||||
- Config files still editable outside the preferences dialog.
|
||||
(Randall Hopper)
|
||||
|
||||
- When you're editing a command in PyShell, and there are only blank
|
||||
lines below the cursor, hitting Return should ignore or delete those
|
||||
blank lines rather than deciding you're not on the last line. (me)
|
||||
|
||||
- Run command (F5 c.s.) should be more like Pythonwin's Run -- a
|
||||
dialog with options to give command line arguments, run the debugger,
|
||||
etc. (me)
|
||||
|
||||
- Shouldn't be able to delete part of the prompt (or any text before
|
||||
it) in the PyShell. (Martijn Faassen)
|
||||
|
||||
- Emacs style auto-fill (also smart about comments and strings).
|
||||
(Jeremy Hylton)
|
||||
|
||||
- Output of Run Script should go to a separate output window, not to
|
||||
the shell window. Output of separate runs should all go to the same
|
||||
window but clearly delimited. (David Scherer)
|
|
@ -0,0 +1,87 @@
|
|||
# Ideas gleaned from PySol
|
||||
|
||||
import os
|
||||
from Tkinter import *
|
||||
|
||||
class ToolTipBase:
|
||||
|
||||
def __init__(self, button):
|
||||
self.button = button
|
||||
self.tipwindow = None
|
||||
self.id = None
|
||||
self.x = self.y = 0
|
||||
self._id1 = self.button.bind("<Enter>", self.enter)
|
||||
self._id2 = self.button.bind("<Leave>", self.leave)
|
||||
self._id3 = self.button.bind("<ButtonPress>", self.leave)
|
||||
|
||||
def enter(self, event=None):
|
||||
self.schedule()
|
||||
|
||||
def leave(self, event=None):
|
||||
self.unschedule()
|
||||
self.hidetip()
|
||||
|
||||
def schedule(self):
|
||||
self.unschedule()
|
||||
self.id = self.button.after(1500, self.showtip)
|
||||
|
||||
def unschedule(self):
|
||||
id = self.id
|
||||
self.id = None
|
||||
if id:
|
||||
self.button.after_cancel(id)
|
||||
|
||||
def showtip(self):
|
||||
if self.tipwindow:
|
||||
return
|
||||
# The tip window must be completely outside the button;
|
||||
# otherwise when the mouse enters the tip window we get
|
||||
# a leave event and it disappears, and then we get an enter
|
||||
# event and it reappears, and so on forever :-(
|
||||
x = self.button.winfo_rootx() + 20
|
||||
y = self.button.winfo_rooty() + self.button.winfo_height() + 1
|
||||
self.tipwindow = tw = Toplevel(self.button)
|
||||
tw.wm_overrideredirect(1)
|
||||
tw.wm_geometry("+%d+%d" % (x, y))
|
||||
self.showcontents()
|
||||
|
||||
def showcontents(self, text="Your text here"):
|
||||
# Override this in derived class
|
||||
label = Label(self.tipwindow, text=text, justify=LEFT,
|
||||
background="#ffffe0", relief=SOLID, borderwidth=1)
|
||||
label.pack()
|
||||
|
||||
def hidetip(self):
|
||||
tw = self.tipwindow
|
||||
self.tipwindow = None
|
||||
if tw:
|
||||
tw.destroy()
|
||||
|
||||
class ToolTip(ToolTipBase):
|
||||
def __init__(self, button, text):
|
||||
ToolTipBase.__init__(self, button)
|
||||
self.text = text
|
||||
def showcontents(self):
|
||||
ToolTipBase.showcontents(self, self.text)
|
||||
|
||||
class ListboxToolTip(ToolTipBase):
|
||||
def __init__(self, button, items):
|
||||
ToolTipBase.__init__(self, button)
|
||||
self.items = items
|
||||
def showcontents(self):
|
||||
listbox = Listbox(self.tipwindow, background="#ffffe0")
|
||||
listbox.pack()
|
||||
for item in self.items:
|
||||
listbox.insert(END, item)
|
||||
|
||||
def main():
|
||||
# Test code
|
||||
root = Tk()
|
||||
b = Button(root, text="Hello", command=root.destroy)
|
||||
b.pack()
|
||||
root.update()
|
||||
tip = ListboxToolTip(b, ["Hello", "world"])
|
||||
|
||||
# root.mainloop() # not in idle
|
||||
|
||||
main()
|
|
@ -0,0 +1,471 @@
|
|||
# XXX TO DO:
|
||||
# - popup menu
|
||||
# - support partial or total redisplay
|
||||
# - key bindings (instead of quick-n-dirty bindings on Canvas):
|
||||
# - up/down arrow keys to move focus around
|
||||
# - ditto for page up/down, home/end
|
||||
# - left/right arrows to expand/collapse & move out/in
|
||||
# - more doc strings
|
||||
# - add icons for "file", "module", "class", "method"; better "python" icon
|
||||
# - callback for selection???
|
||||
# - multiple-item selection
|
||||
# - tooltips
|
||||
# - redo geometry without magic numbers
|
||||
# - keep track of object ids to allow more careful cleaning
|
||||
# - optimize tree redraw after expand of subnode
|
||||
|
||||
import os
|
||||
import sys
|
||||
import string
|
||||
from Tkinter import *
|
||||
import imp
|
||||
|
||||
import ZoomHeight
|
||||
|
||||
ICONDIR = "Icons"
|
||||
|
||||
# Look for Icons subdirectory in the same directory as this module
|
||||
try:
|
||||
_icondir = os.path.join(os.path.dirname(__file__), ICONDIR)
|
||||
except NameError:
|
||||
_icondir = ICONDIR
|
||||
if os.path.isdir(_icondir):
|
||||
ICONDIR = _icondir
|
||||
elif not os.path.isdir(ICONDIR):
|
||||
raise RuntimeError, "can't find icon directory (%s)" % `ICONDIR`
|
||||
|
||||
def listicons(icondir=ICONDIR):
|
||||
"""Utility to display the available icons."""
|
||||
root = Tk()
|
||||
import glob
|
||||
list = glob.glob(os.path.join(icondir, "*.gif"))
|
||||
list.sort()
|
||||
images = []
|
||||
row = column = 0
|
||||
for file in list:
|
||||
name = os.path.splitext(os.path.basename(file))[0]
|
||||
image = PhotoImage(file=file, master=root)
|
||||
images.append(image)
|
||||
label = Label(root, image=image, bd=1, relief="raised")
|
||||
label.grid(row=row, column=column)
|
||||
label = Label(root, text=name)
|
||||
label.grid(row=row+1, column=column)
|
||||
column = column + 1
|
||||
if column >= 10:
|
||||
row = row+2
|
||||
column = 0
|
||||
root.images = images
|
||||
|
||||
|
||||
class TreeNode:
|
||||
|
||||
def __init__(self, canvas, parent, item):
|
||||
self.canvas = canvas
|
||||
self.parent = parent
|
||||
self.item = item
|
||||
self.state = 'collapsed'
|
||||
self.selected = 0
|
||||
self.children = []
|
||||
self.x = self.y = None
|
||||
self.iconimages = {} # cache of PhotoImage instances for icons
|
||||
|
||||
def destroy(self):
|
||||
for c in self.children[:]:
|
||||
self.children.remove(c)
|
||||
c.destroy()
|
||||
self.parent = None
|
||||
|
||||
def geticonimage(self, name):
|
||||
try:
|
||||
return self.iconimages[name]
|
||||
except KeyError:
|
||||
pass
|
||||
file, ext = os.path.splitext(name)
|
||||
ext = ext or ".gif"
|
||||
fullname = os.path.join(ICONDIR, file + ext)
|
||||
image = PhotoImage(master=self.canvas, file=fullname)
|
||||
self.iconimages[name] = image
|
||||
return image
|
||||
|
||||
def select(self, event=None):
|
||||
if self.selected:
|
||||
return
|
||||
self.deselectall()
|
||||
self.selected = 1
|
||||
self.canvas.delete(self.image_id)
|
||||
self.drawicon()
|
||||
self.drawtext()
|
||||
|
||||
def deselect(self, event=None):
|
||||
if not self.selected:
|
||||
return
|
||||
self.selected = 0
|
||||
self.canvas.delete(self.image_id)
|
||||
self.drawicon()
|
||||
self.drawtext()
|
||||
|
||||
def deselectall(self):
|
||||
if self.parent:
|
||||
self.parent.deselectall()
|
||||
else:
|
||||
self.deselecttree()
|
||||
|
||||
def deselecttree(self):
|
||||
if self.selected:
|
||||
self.deselect()
|
||||
for child in self.children:
|
||||
child.deselecttree()
|
||||
|
||||
def flip(self, event=None):
|
||||
if self.state == 'expanded':
|
||||
self.collapse()
|
||||
else:
|
||||
self.expand()
|
||||
self.item.OnDoubleClick()
|
||||
return "break"
|
||||
|
||||
def expand(self, event=None):
|
||||
if not self.item._IsExpandable():
|
||||
return
|
||||
if self.state != 'expanded':
|
||||
self.state = 'expanded'
|
||||
self.update()
|
||||
self.view()
|
||||
|
||||
def collapse(self, event=None):
|
||||
if self.state != 'collapsed':
|
||||
self.state = 'collapsed'
|
||||
self.update()
|
||||
|
||||
def view(self):
|
||||
top = self.y - 2
|
||||
bottom = self.lastvisiblechild().y + 17
|
||||
height = bottom - top
|
||||
visible_top = self.canvas.canvasy(0)
|
||||
visible_height = self.canvas.winfo_height()
|
||||
visible_bottom = self.canvas.canvasy(visible_height)
|
||||
if visible_top <= top and bottom <= visible_bottom:
|
||||
return
|
||||
x0, y0, x1, y1 = self.canvas._getints(self.canvas['scrollregion'])
|
||||
if top >= visible_top and height <= visible_height:
|
||||
fraction = top + height - visible_height
|
||||
else:
|
||||
fraction = top
|
||||
fraction = float(fraction) / y1
|
||||
self.canvas.yview_moveto(fraction)
|
||||
|
||||
def lastvisiblechild(self):
|
||||
if self.children and self.state == 'expanded':
|
||||
return self.children[-1].lastvisiblechild()
|
||||
else:
|
||||
return self
|
||||
|
||||
def update(self):
|
||||
if self.parent:
|
||||
self.parent.update()
|
||||
else:
|
||||
oldcursor = self.canvas['cursor']
|
||||
self.canvas['cursor'] = "watch"
|
||||
self.canvas.update()
|
||||
self.canvas.delete(ALL) # XXX could be more subtle
|
||||
self.draw(7, 2)
|
||||
x0, y0, x1, y1 = self.canvas.bbox(ALL)
|
||||
self.canvas.configure(scrollregion=(0, 0, x1, y1))
|
||||
self.canvas['cursor'] = oldcursor
|
||||
|
||||
def draw(self, x, y):
|
||||
# XXX This hard-codes too many geometry constants!
|
||||
self.x, self.y = x, y
|
||||
self.drawicon()
|
||||
self.drawtext()
|
||||
if self.state != 'expanded':
|
||||
return y+17
|
||||
# draw children
|
||||
if not self.children:
|
||||
sublist = self.item._GetSubList()
|
||||
if not sublist:
|
||||
# _IsExpandable() was mistaken; that's allowed
|
||||
return y+17
|
||||
for item in sublist:
|
||||
child = TreeNode(self.canvas, self, item)
|
||||
self.children.append(child)
|
||||
cx = x+20
|
||||
cy = y+17
|
||||
cylast = 0
|
||||
for child in self.children:
|
||||
cylast = cy
|
||||
self.canvas.create_line(x+9, cy+7, cx, cy+7, fill="gray50")
|
||||
cy = child.draw(cx, cy)
|
||||
if child.item._IsExpandable():
|
||||
if child.state == 'expanded':
|
||||
iconname = "minusnode"
|
||||
callback = child.collapse
|
||||
else:
|
||||
iconname = "plusnode"
|
||||
callback = child.expand
|
||||
image = self.geticonimage(iconname)
|
||||
id = self.canvas.create_image(x+9, cylast+7, image=image)
|
||||
# XXX This leaks bindings until canvas is deleted:
|
||||
self.canvas.tag_bind(id, "<1>", callback)
|
||||
self.canvas.tag_bind(id, "<Double-1>", lambda x: None)
|
||||
id = self.canvas.create_line(x+9, y+10, x+9, cylast+7,
|
||||
##stipple="gray50", # XXX Seems broken in Tk 8.0.x
|
||||
fill="gray50")
|
||||
self.canvas.tag_lower(id) # XXX .lower(id) before Python 1.5.2
|
||||
return cy
|
||||
|
||||
def drawicon(self):
|
||||
if self.selected:
|
||||
imagename = (self.item.GetSelectedIconName() or
|
||||
self.item.GetIconName() or
|
||||
"openfolder")
|
||||
else:
|
||||
imagename = self.item.GetIconName() or "folder"
|
||||
image = self.geticonimage(imagename)
|
||||
id = self.canvas.create_image(self.x, self.y, anchor="nw", image=image)
|
||||
self.image_id = id
|
||||
self.canvas.tag_bind(id, "<1>", self.select)
|
||||
self.canvas.tag_bind(id, "<Double-1>", self.flip)
|
||||
|
||||
def drawtext(self):
|
||||
textx = self.x+20-1
|
||||
texty = self.y-1
|
||||
labeltext = self.item.GetLabelText()
|
||||
if labeltext:
|
||||
id = self.canvas.create_text(textx, texty, anchor="nw",
|
||||
text=labeltext)
|
||||
self.canvas.tag_bind(id, "<1>", self.select)
|
||||
self.canvas.tag_bind(id, "<Double-1>", self.flip)
|
||||
x0, y0, x1, y1 = self.canvas.bbox(id)
|
||||
textx = max(x1, 200) + 10
|
||||
text = self.item.GetText() or "<no text>"
|
||||
try:
|
||||
self.entry
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
self.edit_finish()
|
||||
try:
|
||||
label = self.label
|
||||
except AttributeError:
|
||||
# padding carefully selected (on Windows) to match Entry widget:
|
||||
self.label = Label(self.canvas, text=text, bd=0, padx=2, pady=2)
|
||||
if self.selected:
|
||||
self.label.configure(fg="white", bg="darkblue")
|
||||
else:
|
||||
self.label.configure(fg="black", bg="white")
|
||||
id = self.canvas.create_window(textx, texty,
|
||||
anchor="nw", window=self.label)
|
||||
self.label.bind("<1>", self.select_or_edit)
|
||||
self.label.bind("<Double-1>", self.flip)
|
||||
self.text_id = id
|
||||
|
||||
def select_or_edit(self, event=None):
|
||||
if self.selected and self.item.IsEditable():
|
||||
self.edit(event)
|
||||
else:
|
||||
self.select(event)
|
||||
|
||||
def edit(self, event=None):
|
||||
self.entry = Entry(self.label, bd=0, highlightthickness=1, width=0)
|
||||
self.entry.insert(0, self.label['text'])
|
||||
self.entry.selection_range(0, END)
|
||||
self.entry.pack(ipadx=5)
|
||||
self.entry.focus_set()
|
||||
self.entry.bind("<Return>", self.edit_finish)
|
||||
self.entry.bind("<Escape>", self.edit_cancel)
|
||||
|
||||
def edit_finish(self, event=None):
|
||||
try:
|
||||
entry = self.entry
|
||||
del self.entry
|
||||
except AttributeError:
|
||||
return
|
||||
text = entry.get()
|
||||
entry.destroy()
|
||||
if text and text != self.item.GetText():
|
||||
self.item.SetText(text)
|
||||
text = self.item.GetText()
|
||||
self.label['text'] = text
|
||||
self.drawtext()
|
||||
self.canvas.focus_set()
|
||||
|
||||
def edit_cancel(self, event=None):
|
||||
self.drawtext()
|
||||
self.canvas.focus_set()
|
||||
|
||||
|
||||
class TreeItem:
|
||||
|
||||
"""Abstract class representing tree items.
|
||||
|
||||
Methods should typically be overridden, otherwise a default action
|
||||
is used.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Constructor. Do whatever you need to do."""
|
||||
|
||||
def GetText(self):
|
||||
"""Return text string to display."""
|
||||
|
||||
def GetLabelText(self):
|
||||
"""Return label text string to display in front of text (if any)."""
|
||||
|
||||
expandable = None
|
||||
|
||||
def _IsExpandable(self):
|
||||
"""Do not override! Called by TreeNode."""
|
||||
if self.expandable is None:
|
||||
self.expandable = self.IsExpandable()
|
||||
return self.expandable
|
||||
|
||||
def IsExpandable(self):
|
||||
"""Return whether there are subitems."""
|
||||
return 1
|
||||
|
||||
def _GetSubList(self):
|
||||
"""Do not override! Called by TreeNode."""
|
||||
if not self.IsExpandable():
|
||||
return []
|
||||
sublist = self.GetSubList()
|
||||
if not sublist:
|
||||
self.expandable = 0
|
||||
return sublist
|
||||
|
||||
def IsEditable(self):
|
||||
"""Return whether the item's text may be edited."""
|
||||
|
||||
def SetText(self, text):
|
||||
"""Change the item's text (if it is editable)."""
|
||||
|
||||
def GetIconName(self):
|
||||
"""Return name of icon to be displayed normally."""
|
||||
|
||||
def GetSelectedIconName(self):
|
||||
"""Return name of icon to be displayed when selected."""
|
||||
|
||||
def GetSubList(self):
|
||||
"""Return list of items forming sublist."""
|
||||
|
||||
def OnDoubleClick(self):
|
||||
"""Called on a double-click on the item."""
|
||||
|
||||
|
||||
# Example application
|
||||
|
||||
class FileTreeItem(TreeItem):
|
||||
|
||||
"""Example TreeItem subclass -- browse the file system."""
|
||||
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
|
||||
def GetText(self):
|
||||
return os.path.basename(self.path) or self.path
|
||||
|
||||
def IsEditable(self):
|
||||
return os.path.basename(self.path) != ""
|
||||
|
||||
def SetText(self, text):
|
||||
newpath = os.path.dirname(self.path)
|
||||
newpath = os.path.join(newpath, text)
|
||||
if os.path.dirname(newpath) != os.path.dirname(self.path):
|
||||
return
|
||||
try:
|
||||
os.rename(self.path, newpath)
|
||||
self.path = newpath
|
||||
except os.error:
|
||||
pass
|
||||
|
||||
def GetIconName(self):
|
||||
if not self.IsExpandable():
|
||||
return "python" # XXX wish there was a "file" icon
|
||||
|
||||
def IsExpandable(self):
|
||||
return os.path.isdir(self.path)
|
||||
|
||||
def GetSubList(self):
|
||||
try:
|
||||
names = os.listdir(self.path)
|
||||
except os.error:
|
||||
return []
|
||||
names.sort(lambda a, b: cmp(os.path.normcase(a), os.path.normcase(b)))
|
||||
sublist = []
|
||||
for name in names:
|
||||
item = FileTreeItem(os.path.join(self.path, name))
|
||||
sublist.append(item)
|
||||
return sublist
|
||||
|
||||
|
||||
# A canvas widget with scroll bars and some useful bindings
|
||||
|
||||
class ScrolledCanvas:
|
||||
def __init__(self, master, **opts):
|
||||
if not opts.has_key('yscrollincrement'):
|
||||
opts['yscrollincrement'] = 17
|
||||
self.master = master
|
||||
self.frame = Frame(master)
|
||||
self.frame.rowconfigure(0, weight=1)
|
||||
self.frame.columnconfigure(0, weight=1)
|
||||
self.canvas = apply(Canvas, (self.frame,), opts)
|
||||
self.canvas.grid(row=0, column=0, sticky="nsew")
|
||||
self.vbar = Scrollbar(self.frame, name="vbar")
|
||||
self.vbar.grid(row=0, column=1, sticky="nse")
|
||||
self.hbar = Scrollbar(self.frame, name="hbar", orient="horizontal")
|
||||
self.hbar.grid(row=1, column=0, sticky="ews")
|
||||
self.canvas['yscrollcommand'] = self.vbar.set
|
||||
self.vbar['command'] = self.canvas.yview
|
||||
self.canvas['xscrollcommand'] = self.hbar.set
|
||||
self.hbar['command'] = self.canvas.xview
|
||||
self.canvas.bind("<Key-Prior>", self.page_up)
|
||||
self.canvas.bind("<Key-Next>", self.page_down)
|
||||
self.canvas.bind("<Key-Up>", self.unit_up)
|
||||
self.canvas.bind("<Key-Down>", self.unit_down)
|
||||
if isinstance(master, Toplevel) or isinstance(master, Tk):
|
||||
self.canvas.bind("<Alt-F2>", self.zoom_height)
|
||||
self.canvas.focus_set()
|
||||
def page_up(self, event):
|
||||
self.canvas.yview_scroll(-1, "page")
|
||||
return "break"
|
||||
def page_down(self, event):
|
||||
self.canvas.yview_scroll(1, "page")
|
||||
return "break"
|
||||
def unit_up(self, event):
|
||||
self.canvas.yview_scroll(-1, "unit")
|
||||
return "break"
|
||||
def unit_down(self, event):
|
||||
self.canvas.yview_scroll(1, "unit")
|
||||
return "break"
|
||||
def zoom_height(self, event):
|
||||
ZoomHeight.zoom_height(self.master)
|
||||
return "break"
|
||||
|
||||
|
||||
# Testing functions
|
||||
|
||||
def test():
|
||||
import PyShell
|
||||
root = Toplevel(PyShell.root)
|
||||
root.configure(bd=0, bg="yellow")
|
||||
root.focus_set()
|
||||
sc = ScrolledCanvas(root, bg="white", highlightthickness=0, takefocus=1)
|
||||
sc.frame.pack(expand=1, fill="both")
|
||||
item = FileTreeItem("C:/windows/desktop")
|
||||
node = TreeNode(sc.canvas, None, item)
|
||||
node.expand()
|
||||
|
||||
def test2():
|
||||
# test w/o scrolling canvas
|
||||
root = Tk()
|
||||
root.configure(bd=0)
|
||||
canvas = Canvas(root, bg="white", highlightthickness=0)
|
||||
canvas.pack(expand=1, fill="both")
|
||||
item = FileTreeItem(os.curdir)
|
||||
node = TreeNode(canvas, None, item)
|
||||
node.update()
|
||||
canvas.focus_set()
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
|
@ -0,0 +1,352 @@
|
|||
import sys
|
||||
import string
|
||||
from Tkinter import *
|
||||
from Delegator import Delegator
|
||||
|
||||
#$ event <<redo>>
|
||||
#$ win <Control-y>
|
||||
#$ unix <Alt-z>
|
||||
|
||||
#$ event <<undo>>
|
||||
#$ win <Control-z>
|
||||
#$ unix <Control-z>
|
||||
|
||||
#$ event <<dump-undo-state>>
|
||||
#$ win <Control-backslash>
|
||||
#$ unix <Control-backslash>
|
||||
|
||||
|
||||
class UndoDelegator(Delegator):
|
||||
|
||||
max_undo = 1000
|
||||
|
||||
def __init__(self):
|
||||
Delegator.__init__(self)
|
||||
self.reset_undo()
|
||||
|
||||
def setdelegate(self, delegate):
|
||||
if self.delegate is not None:
|
||||
self.unbind("<<undo>>")
|
||||
self.unbind("<<redo>>")
|
||||
self.unbind("<<dump-undo-state>>")
|
||||
Delegator.setdelegate(self, delegate)
|
||||
if delegate is not None:
|
||||
self.bind("<<undo>>", self.undo_event)
|
||||
self.bind("<<redo>>", self.redo_event)
|
||||
self.bind("<<dump-undo-state>>", self.dump_event)
|
||||
|
||||
def dump_event(self, event):
|
||||
from pprint import pprint
|
||||
pprint(self.undolist[:self.pointer])
|
||||
print "pointer:", self.pointer,
|
||||
print "saved:", self.saved,
|
||||
print "can_merge:", self.can_merge,
|
||||
print "get_saved():", self.get_saved()
|
||||
pprint(self.undolist[self.pointer:])
|
||||
return "break"
|
||||
|
||||
def reset_undo(self):
|
||||
self.was_saved = -1
|
||||
self.pointer = 0
|
||||
self.undolist = []
|
||||
self.undoblock = 0 # or a CommandSequence instance
|
||||
self.set_saved(1)
|
||||
|
||||
def set_saved(self, flag):
|
||||
if flag:
|
||||
self.saved = self.pointer
|
||||
else:
|
||||
self.saved = -1
|
||||
self.can_merge = 0
|
||||
self.check_saved()
|
||||
|
||||
def get_saved(self):
|
||||
return self.saved == self.pointer
|
||||
|
||||
saved_change_hook = None
|
||||
|
||||
def set_saved_change_hook(self, hook):
|
||||
self.saved_change_hook = hook
|
||||
|
||||
was_saved = -1
|
||||
|
||||
def check_saved(self):
|
||||
is_saved = self.get_saved()
|
||||
if is_saved != self.was_saved:
|
||||
self.was_saved = is_saved
|
||||
if self.saved_change_hook:
|
||||
self.saved_change_hook()
|
||||
|
||||
def insert(self, index, chars, tags=None):
|
||||
self.addcmd(InsertCommand(index, chars, tags))
|
||||
|
||||
def delete(self, index1, index2=None):
|
||||
self.addcmd(DeleteCommand(index1, index2))
|
||||
|
||||
# Clients should call undo_block_start() and undo_block_stop()
|
||||
# around a sequence of editing cmds to be treated as a unit by
|
||||
# undo & redo. Nested matching calls are OK, and the inner calls
|
||||
# then act like nops. OK too if no editing cmds, or only one
|
||||
# editing cmd, is issued in between: if no cmds, the whole
|
||||
# sequence has no effect; and if only one cmd, that cmd is entered
|
||||
# directly into the undo list, as if undo_block_xxx hadn't been
|
||||
# called. The intent of all that is to make this scheme easy
|
||||
# to use: all the client has to worry about is making sure each
|
||||
# _start() call is matched by a _stop() call.
|
||||
|
||||
def undo_block_start(self):
|
||||
if self.undoblock == 0:
|
||||
self.undoblock = CommandSequence()
|
||||
self.undoblock.bump_depth()
|
||||
|
||||
def undo_block_stop(self):
|
||||
if self.undoblock.bump_depth(-1) == 0:
|
||||
cmd = self.undoblock
|
||||
self.undoblock = 0
|
||||
if len(cmd) > 0:
|
||||
if len(cmd) == 1:
|
||||
# no need to wrap a single cmd
|
||||
cmd = cmd.getcmd(0)
|
||||
# this blk of cmds, or single cmd, has already
|
||||
# been done, so don't execute it again
|
||||
self.addcmd(cmd, 0)
|
||||
|
||||
def addcmd(self, cmd, execute=1):
|
||||
if execute:
|
||||
cmd.do(self.delegate)
|
||||
if self.undoblock != 0:
|
||||
self.undoblock.append(cmd)
|
||||
return
|
||||
if self.can_merge and self.pointer > 0:
|
||||
lastcmd = self.undolist[self.pointer-1]
|
||||
if lastcmd.merge(cmd):
|
||||
return
|
||||
self.undolist[self.pointer:] = [cmd]
|
||||
if self.saved > self.pointer:
|
||||
self.saved = -1
|
||||
self.pointer = self.pointer + 1
|
||||
if len(self.undolist) > self.max_undo:
|
||||
##print "truncating undo list"
|
||||
del self.undolist[0]
|
||||
self.pointer = self.pointer - 1
|
||||
if self.saved >= 0:
|
||||
self.saved = self.saved - 1
|
||||
self.can_merge = 1
|
||||
self.check_saved()
|
||||
|
||||
def undo_event(self, event):
|
||||
if self.pointer == 0:
|
||||
self.bell()
|
||||
return "break"
|
||||
cmd = self.undolist[self.pointer - 1]
|
||||
cmd.undo(self.delegate)
|
||||
self.pointer = self.pointer - 1
|
||||
self.can_merge = 0
|
||||
self.check_saved()
|
||||
return "break"
|
||||
|
||||
def redo_event(self, event):
|
||||
if self.pointer >= len(self.undolist):
|
||||
self.bell()
|
||||
return "break"
|
||||
cmd = self.undolist[self.pointer]
|
||||
cmd.redo(self.delegate)
|
||||
self.pointer = self.pointer + 1
|
||||
self.can_merge = 0
|
||||
self.check_saved()
|
||||
return "break"
|
||||
|
||||
|
||||
class Command:
|
||||
|
||||
# Base class for Undoable commands
|
||||
|
||||
tags = None
|
||||
|
||||
def __init__(self, index1, index2, chars, tags=None):
|
||||
self.marks_before = {}
|
||||
self.marks_after = {}
|
||||
self.index1 = index1
|
||||
self.index2 = index2
|
||||
self.chars = chars
|
||||
if tags:
|
||||
self.tags = tags
|
||||
|
||||
def __repr__(self):
|
||||
s = self.__class__.__name__
|
||||
t = (self.index1, self.index2, self.chars, self.tags)
|
||||
if self.tags is None:
|
||||
t = t[:-1]
|
||||
return s + `t`
|
||||
|
||||
def do(self, text):
|
||||
pass
|
||||
|
||||
def redo(self, text):
|
||||
pass
|
||||
|
||||
def undo(self, text):
|
||||
pass
|
||||
|
||||
def merge(self, cmd):
|
||||
return 0
|
||||
|
||||
def save_marks(self, text):
|
||||
marks = {}
|
||||
for name in text.mark_names():
|
||||
if name != "insert" and name != "current":
|
||||
marks[name] = text.index(name)
|
||||
return marks
|
||||
|
||||
def set_marks(self, text, marks):
|
||||
for name, index in marks.items():
|
||||
text.mark_set(name, index)
|
||||
|
||||
|
||||
class InsertCommand(Command):
|
||||
|
||||
# Undoable insert command
|
||||
|
||||
def __init__(self, index1, chars, tags=None):
|
||||
Command.__init__(self, index1, None, chars, tags)
|
||||
|
||||
def do(self, text):
|
||||
self.marks_before = self.save_marks(text)
|
||||
self.index1 = text.index(self.index1)
|
||||
if text.compare(self.index1, ">", "end-1c"):
|
||||
# Insert before the final newline
|
||||
self.index1 = text.index("end-1c")
|
||||
text.insert(self.index1, self.chars, self.tags)
|
||||
self.index2 = text.index("%s+%dc" % (self.index1, len(self.chars)))
|
||||
self.marks_after = self.save_marks(text)
|
||||
##sys.__stderr__.write("do: %s\n" % self)
|
||||
|
||||
def redo(self, text):
|
||||
text.mark_set('insert', self.index1)
|
||||
text.insert(self.index1, self.chars, self.tags)
|
||||
self.set_marks(text, self.marks_after)
|
||||
text.see('insert')
|
||||
##sys.__stderr__.write("redo: %s\n" % self)
|
||||
|
||||
def undo(self, text):
|
||||
text.mark_set('insert', self.index1)
|
||||
text.delete(self.index1, self.index2)
|
||||
self.set_marks(text, self.marks_before)
|
||||
text.see('insert')
|
||||
##sys.__stderr__.write("undo: %s\n" % self)
|
||||
|
||||
def merge(self, cmd):
|
||||
if self.__class__ is not cmd.__class__:
|
||||
return 0
|
||||
if self.index2 != cmd.index1:
|
||||
return 0
|
||||
if self.tags != cmd.tags:
|
||||
return 0
|
||||
if len(cmd.chars) != 1:
|
||||
return 0
|
||||
if self.chars and \
|
||||
self.classify(self.chars[-1]) != self.classify(cmd.chars):
|
||||
return 0
|
||||
self.index2 = cmd.index2
|
||||
self.chars = self.chars + cmd.chars
|
||||
return 1
|
||||
|
||||
alphanumeric = string.letters + string.digits + "_"
|
||||
|
||||
def classify(self, c):
|
||||
if c in self.alphanumeric:
|
||||
return "alphanumeric"
|
||||
if c == "\n":
|
||||
return "newline"
|
||||
return "punctuation"
|
||||
|
||||
|
||||
class DeleteCommand(Command):
|
||||
|
||||
# Undoable delete command
|
||||
|
||||
def __init__(self, index1, index2=None):
|
||||
Command.__init__(self, index1, index2, None, None)
|
||||
|
||||
def do(self, text):
|
||||
self.marks_before = self.save_marks(text)
|
||||
self.index1 = text.index(self.index1)
|
||||
if self.index2:
|
||||
self.index2 = text.index(self.index2)
|
||||
else:
|
||||
self.index2 = text.index(self.index1 + " +1c")
|
||||
if text.compare(self.index2, ">", "end-1c"):
|
||||
# Don't delete the final newline
|
||||
self.index2 = text.index("end-1c")
|
||||
self.chars = text.get(self.index1, self.index2)
|
||||
text.delete(self.index1, self.index2)
|
||||
self.marks_after = self.save_marks(text)
|
||||
##sys.__stderr__.write("do: %s\n" % self)
|
||||
|
||||
def redo(self, text):
|
||||
text.mark_set('insert', self.index1)
|
||||
text.delete(self.index1, self.index2)
|
||||
self.set_marks(text, self.marks_after)
|
||||
text.see('insert')
|
||||
##sys.__stderr__.write("redo: %s\n" % self)
|
||||
|
||||
def undo(self, text):
|
||||
text.mark_set('insert', self.index1)
|
||||
text.insert(self.index1, self.chars)
|
||||
self.set_marks(text, self.marks_before)
|
||||
text.see('insert')
|
||||
##sys.__stderr__.write("undo: %s\n" % self)
|
||||
|
||||
class CommandSequence(Command):
|
||||
|
||||
# Wrapper for a sequence of undoable cmds to be undone/redone
|
||||
# as a unit
|
||||
|
||||
def __init__(self):
|
||||
self.cmds = []
|
||||
self.depth = 0
|
||||
|
||||
def __repr__(self):
|
||||
s = self.__class__.__name__
|
||||
strs = []
|
||||
for cmd in self.cmds:
|
||||
strs.append(" " + `cmd`)
|
||||
return s + "(\n" + string.join(strs, ",\n") + "\n)"
|
||||
|
||||
def __len__(self):
|
||||
return len(self.cmds)
|
||||
|
||||
def append(self, cmd):
|
||||
self.cmds.append(cmd)
|
||||
|
||||
def getcmd(self, i):
|
||||
return self.cmds[i]
|
||||
|
||||
def redo(self, text):
|
||||
for cmd in self.cmds:
|
||||
cmd.redo(text)
|
||||
|
||||
def undo(self, text):
|
||||
cmds = self.cmds[:]
|
||||
cmds.reverse()
|
||||
for cmd in cmds:
|
||||
cmd.undo(text)
|
||||
|
||||
def bump_depth(self, incr=1):
|
||||
self.depth = self.depth + incr
|
||||
return self.depth
|
||||
|
||||
def main():
|
||||
from Percolator import Percolator
|
||||
root = Tk()
|
||||
root.wm_protocol("WM_DELETE_WINDOW", root.quit)
|
||||
text = Text()
|
||||
text.pack()
|
||||
text.focus_set()
|
||||
p = Percolator(text)
|
||||
d = UndoDelegator()
|
||||
p.insertfilter(d)
|
||||
root.mainloop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,92 @@
|
|||
from Tkinter import *
|
||||
|
||||
|
||||
class WidgetRedirector:
|
||||
|
||||
"""Support for redirecting arbitrary widget subcommands."""
|
||||
|
||||
def __init__(self, widget):
|
||||
self.dict = {}
|
||||
self.widget = widget
|
||||
self.tk = tk = widget.tk
|
||||
w = widget._w
|
||||
self.orig = w + "_orig"
|
||||
tk.call("rename", w, self.orig)
|
||||
tk.createcommand(w, self.dispatch)
|
||||
|
||||
def __repr__(self):
|
||||
return "WidgetRedirector(%s<%s>)" % (self.widget.__class__.__name__,
|
||||
self.widget._w)
|
||||
|
||||
def close(self):
|
||||
for name in self.dict.keys():
|
||||
self.unregister(name)
|
||||
widget = self.widget; del self.widget
|
||||
orig = self.orig; del self.orig
|
||||
tk = widget.tk
|
||||
w = widget._w
|
||||
tk.deletecommand(w)
|
||||
tk.call("rename", orig, w)
|
||||
|
||||
def register(self, name, function):
|
||||
if self.dict.has_key(name):
|
||||
previous = dict[name]
|
||||
else:
|
||||
previous = OriginalCommand(self, name)
|
||||
self.dict[name] = function
|
||||
setattr(self.widget, name, function)
|
||||
return previous
|
||||
|
||||
def unregister(self, name):
|
||||
if self.dict.has_key(name):
|
||||
function = self.dict[name]
|
||||
del self.dict[name]
|
||||
if hasattr(self.widget, name):
|
||||
delattr(self.widget, name)
|
||||
return function
|
||||
else:
|
||||
return None
|
||||
|
||||
def dispatch(self, cmd, *args):
|
||||
m = self.dict.get(cmd)
|
||||
try:
|
||||
if m:
|
||||
return apply(m, args)
|
||||
else:
|
||||
return self.tk.call((self.orig, cmd) + args)
|
||||
except TclError:
|
||||
return ""
|
||||
|
||||
|
||||
class OriginalCommand:
|
||||
|
||||
def __init__(self, redir, name):
|
||||
self.redir = redir
|
||||
self.name = name
|
||||
self.tk = redir.tk
|
||||
self.orig = redir.orig
|
||||
self.tk_call = self.tk.call
|
||||
self.orig_and_name = (self.orig, self.name)
|
||||
|
||||
def __repr__(self):
|
||||
return "OriginalCommand(%s, %s)" % (`self.redir`, `self.name`)
|
||||
|
||||
def __call__(self, *args):
|
||||
return self.tk_call(self.orig_and_name + args)
|
||||
|
||||
|
||||
def main():
|
||||
root = Tk()
|
||||
text = Text()
|
||||
text.pack()
|
||||
text.focus_set()
|
||||
redir = WidgetRedirector(text)
|
||||
global orig_insert
|
||||
def my_insert(*args):
|
||||
print "insert", args
|
||||
apply(orig_insert, args)
|
||||
orig_insert = redir.register("insert", my_insert)
|
||||
root.mainloop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,85 @@
|
|||
from Tkinter import *
|
||||
|
||||
class WindowList:
|
||||
|
||||
def __init__(self):
|
||||
self.dict = {}
|
||||
self.callbacks = []
|
||||
|
||||
def add(self, window):
|
||||
window.after_idle(self.call_callbacks)
|
||||
self.dict[str(window)] = window
|
||||
|
||||
def delete(self, window):
|
||||
try:
|
||||
del self.dict[str(window)]
|
||||
except KeyError:
|
||||
# Sometimes, destroy() is called twice
|
||||
pass
|
||||
self.call_callbacks()
|
||||
|
||||
def add_windows_to_menu(self, menu):
|
||||
list = []
|
||||
for key in self.dict.keys():
|
||||
window = self.dict[key]
|
||||
try:
|
||||
title = window.get_title()
|
||||
except TclError:
|
||||
continue
|
||||
list.append((title, window))
|
||||
list.sort()
|
||||
for title, window in list:
|
||||
if title == "Python Shell":
|
||||
# Hack -- until we have a better way to this
|
||||
continue
|
||||
menu.add_command(label=title, command=window.wakeup)
|
||||
|
||||
def register_callback(self, callback):
|
||||
self.callbacks.append(callback)
|
||||
|
||||
def unregister_callback(self, callback):
|
||||
try:
|
||||
self.callbacks.remove(callback)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def call_callbacks(self):
|
||||
for callback in self.callbacks:
|
||||
try:
|
||||
callback()
|
||||
except:
|
||||
print "warning: callback failed in WindowList", \
|
||||
sys.exc_type, ":", sys.exc_value
|
||||
|
||||
registry = WindowList()
|
||||
|
||||
add_windows_to_menu = registry.add_windows_to_menu
|
||||
register_callback = registry.register_callback
|
||||
unregister_callback = registry.unregister_callback
|
||||
|
||||
|
||||
class ListedToplevel(Toplevel):
|
||||
|
||||
def __init__(self, master, **kw):
|
||||
Toplevel.__init__(self, master, kw)
|
||||
registry.add(self)
|
||||
|
||||
def destroy(self):
|
||||
registry.delete(self)
|
||||
Toplevel.destroy(self)
|
||||
|
||||
def get_title(self):
|
||||
# Subclass can override
|
||||
return self.wm_title()
|
||||
|
||||
def wakeup(self):
|
||||
try:
|
||||
if self.wm_state() == "iconic":
|
||||
self.wm_deiconify()
|
||||
else:
|
||||
self.tkraise()
|
||||
self.focus_set()
|
||||
except TclError:
|
||||
# This can happen when the window menu was torn off.
|
||||
# Simply ignore it.
|
||||
pass
|
|
@ -0,0 +1,46 @@
|
|||
# Sample extension: zoom a window to maximum height
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
class ZoomHeight:
|
||||
|
||||
menudefs = [
|
||||
('windows', [
|
||||
('_Zoom Height', '<<zoom-height>>'),
|
||||
])
|
||||
]
|
||||
|
||||
windows_keydefs = {
|
||||
'<<zoom-height>>': ['<Alt-F2>'],
|
||||
}
|
||||
unix_keydefs = {
|
||||
'<<zoom-height>>': ['<Control-x><Control-z>'],
|
||||
}
|
||||
|
||||
def __init__(self, editwin):
|
||||
self.editwin = editwin
|
||||
|
||||
def zoom_height_event(self, event):
|
||||
top = self.editwin.top
|
||||
zoom_height(top)
|
||||
|
||||
def zoom_height(top):
|
||||
geom = top.wm_geometry()
|
||||
m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
|
||||
if not m:
|
||||
top.bell()
|
||||
return
|
||||
width, height, x, y = map(int, m.groups())
|
||||
newheight = top.winfo_screenheight()
|
||||
if sys.platform == 'win32':
|
||||
newy = 0
|
||||
newheight = newheight - 72
|
||||
else:
|
||||
newy = 24
|
||||
newheight = newheight - 96
|
||||
if height >= newheight:
|
||||
newgeom = ""
|
||||
else:
|
||||
newgeom = "%dx%d+%d+%d" % (width, newheight, x, newy)
|
||||
top.wm_geometry(newgeom)
|
|
@ -0,0 +1 @@
|
|||
# Dummy file to make this a potential package.
|
|
@ -0,0 +1,3 @@
|
|||
[EditorWindow]
|
||||
font-name= courier
|
||||
font-size= 10
|
|
@ -0,0 +1,3 @@
|
|||
[EditorWindow]
|
||||
font-name: courier new
|
||||
font-size: 10
|
|
@ -0,0 +1,66 @@
|
|||
# IDLE reads several config files to determine user preferences. This
|
||||
# file is the default config file. When IDLE starts, it will look in
|
||||
# the following four files in order:
|
||||
# config.txt the default config file
|
||||
# config-[win/unix/mac].txt the generic platform config file
|
||||
# config-[sys.platform].txt the specific platform config file
|
||||
# ~/.idle the user config file
|
||||
# XXX what about Windows?
|
||||
#
|
||||
# The last definition of each option is used. For example, you can
|
||||
# override the default window size (80x24) by defining width and
|
||||
# height options in the EditorWindow section of your ~/.idle file
|
||||
#
|
||||
# IDLE extensions can be enabled and disabled by adding them to one of
|
||||
# the config files. To enable an extension, create a section with the
|
||||
# same name as the extension, e.g. the [ParenMatch] section below. To
|
||||
# disable an extension, either remove the section or add the the
|
||||
# enable option with the value 0.
|
||||
|
||||
[EditorWindow]
|
||||
width= 80
|
||||
height= 24
|
||||
# fonts defined in config-[win/unix].txt
|
||||
|
||||
[Colors]
|
||||
normal-foreground= black
|
||||
normal-background= white
|
||||
# These color types are not explicitly defined= sync, todo, stdin
|
||||
keyword-foreground= #ff7700
|
||||
comment-foreground= #dd0000
|
||||
string-foreground= #00aa00
|
||||
definition-foreground= #0000ff
|
||||
hilite-foreground= #000068
|
||||
hilite-background= #006868
|
||||
break-foreground= #ff7777
|
||||
hit-foreground= #ffffff
|
||||
hit-background= #000000
|
||||
stdout-foreground= blue
|
||||
stderr-foreground= red
|
||||
console-foreground= #770000
|
||||
error-background= #ff7777
|
||||
cursor-background= black
|
||||
|
||||
[SearchBinding]
|
||||
|
||||
[AutoIndent]
|
||||
|
||||
[AutoExpand]
|
||||
|
||||
[FormatParagraph]
|
||||
|
||||
[ZoomHeight]
|
||||
|
||||
#[ScriptBinding] # disabled in favor of ExecBinding
|
||||
|
||||
[ExecBinding]
|
||||
|
||||
[CallTips]
|
||||
|
||||
[ParenMatch]
|
||||
enable= 0
|
||||
style= expression
|
||||
flash-delay= 500
|
||||
bell= 1
|
||||
hilite-foreground= black
|
||||
hilite-background= #43cd80
|
|
@ -0,0 +1,93 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
"""Parse event definitions out of comments in source files."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
import string
|
||||
import getopt
|
||||
import glob
|
||||
import fileinput
|
||||
import pprint
|
||||
|
||||
def main():
|
||||
hits = []
|
||||
sublist = []
|
||||
args = sys.argv[1:]
|
||||
if not args:
|
||||
args = filter(lambda s: 'A' <= s[0] <= 'Z', glob.glob("*.py"))
|
||||
if not args:
|
||||
print "No arguments, no [A-Z]*.py files."
|
||||
return 1
|
||||
for line in fileinput.input(args):
|
||||
if line[:2] == '#$':
|
||||
if not sublist:
|
||||
sublist.append('file %s' % fileinput.filename())
|
||||
sublist.append('line %d' % fileinput.lineno())
|
||||
sublist.append(string.strip(line[2:-1]))
|
||||
else:
|
||||
if sublist:
|
||||
hits.append(sublist)
|
||||
sublist = []
|
||||
if sublist:
|
||||
hits.append(sublist)
|
||||
sublist = []
|
||||
dd = {}
|
||||
for sublist in hits:
|
||||
d = {}
|
||||
for line in sublist:
|
||||
words = string.split(line, None, 1)
|
||||
if len(words) != 2:
|
||||
continue
|
||||
tag = words[0]
|
||||
l = d.get(tag, [])
|
||||
l.append(words[1])
|
||||
d[tag] = l
|
||||
if d.has_key('event'):
|
||||
keys = d['event']
|
||||
if len(keys) != 1:
|
||||
print "Multiple event keys in", d
|
||||
print 'File "%s", line %d' % (d['file'], d['line'])
|
||||
key = keys[0]
|
||||
if dd.has_key(key):
|
||||
print "Duplicate event in", d
|
||||
print 'File "%s", line %d' % (d['file'], d['line'])
|
||||
return
|
||||
dd[key] = d
|
||||
else:
|
||||
print "No event key in", d
|
||||
print 'File "%s", line %d' % (d['file'], d['line'])
|
||||
winevents = getevents(dd, "win")
|
||||
unixevents = getevents(dd, "unix")
|
||||
save = sys.stdout
|
||||
f = open("keydefs.py", "w")
|
||||
try:
|
||||
sys.stdout = f
|
||||
print "windows_keydefs = \\"
|
||||
pprint.pprint(winevents)
|
||||
print
|
||||
print "unix_keydefs = \\"
|
||||
pprint.pprint(unixevents)
|
||||
finally:
|
||||
sys.stdout = save
|
||||
f.close()
|
||||
|
||||
def getevents(dd, key):
|
||||
res = {}
|
||||
events = dd.keys()
|
||||
events.sort()
|
||||
for e in events:
|
||||
d = dd[e]
|
||||
if d.has_key(key) or d.has_key("all"):
|
||||
list = []
|
||||
for x in d.get(key, []) + d.get("all", []):
|
||||
list.append(x)
|
||||
if key == "unix" and x[:5] == "<Alt-":
|
||||
x = "<Meta-" + x[5:]
|
||||
list.append(x)
|
||||
res[e] = list
|
||||
return res
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
|
@ -0,0 +1,106 @@
|
|||
Writing an IDLE extension
|
||||
|
||||
An IDLE extension can define new key bindings and menu entries for IDLE
|
||||
edit windows. There is a simple mechanism to load extensions when IDLE
|
||||
starts up and to attach them to each edit window. (It is also possible
|
||||
to make other changes to IDLE, but this must be done by editing the IDLE
|
||||
source code.)
|
||||
|
||||
The list of extensions loaded at startup time is configured by editing
|
||||
the file extend.py; see below for details.
|
||||
|
||||
An IDLE extension is defined by a class. Methods of the class define
|
||||
actions that are invoked by those bindings or menu entries. Class (or
|
||||
instance) variables define the bindings and menu additions; these are
|
||||
automatically applied by IDLE when the extension is linked to an edit
|
||||
window.
|
||||
|
||||
An IDLE extension class is instantiated with a single argument,
|
||||
`editwin', an EditorWindow instance. The extension cannot assume much
|
||||
about this argument, but it is guarateed to have the following instance
|
||||
variables:
|
||||
|
||||
text a Text instance (a widget)
|
||||
io an IOBinding instance (more about this later)
|
||||
flist the FileList instance (shared by all edit windows)
|
||||
|
||||
(There are a few more, but they are rarely useful.)
|
||||
|
||||
The extension class must not bind key events. Rather, it must define
|
||||
one or more virtual events, e.g. <<zoom-height>>, and corresponding
|
||||
methods, e.g. zoom_height_event(), and have one or more class (or instance)
|
||||
variables that define mappings between virtual events and key sequences,
|
||||
e.g. <Alt-F2>. When the extension is loaded, these key sequences will
|
||||
be bound to the corresponding virtual events, and the virtual events
|
||||
will be bound to the corresponding methods. (This indirection is done
|
||||
so that the key bindings can easily be changed, and so that other
|
||||
sources of virtual events can exist, such as menu entries.)
|
||||
|
||||
The following class or instance variables are used to define key
|
||||
bindings for virtual events:
|
||||
|
||||
keydefs for all platforms
|
||||
mac_keydefs for Macintosh
|
||||
windows_keydefs for Windows
|
||||
unix_keydefs for Unix (and other platforms)
|
||||
|
||||
Each of these variables, if it exists, must be a dictionary whose
|
||||
keys are virtual events, and whose values are lists of key sequences.
|
||||
|
||||
An extension can define menu entries in a similar fashion. This is done
|
||||
with a class or instance variable named menudefs; it should be a list of
|
||||
pair, where each pair is a menu name (lowercase) and a list of menu
|
||||
entries. Each menu entry is either None (to insert a separator entry) or
|
||||
a pair of strings (menu_label, virtual_event). Here, menu_label is the
|
||||
label of the menu entry, and virtual_event is the virtual event to be
|
||||
generated when the entry is selected. An underscore in the menu label
|
||||
is removed; the character following the underscore is displayed
|
||||
underlined, to indicate the shortcut character (for Windows).
|
||||
|
||||
At the moment, extensions cannot define whole new menus; they must
|
||||
define entries in existing menus. Some menus are not present on some
|
||||
windows; such entry definitions are then ignored, but the key bindings
|
||||
are still applied. (This should probably be refined in the future.)
|
||||
|
||||
Here is a complete example example:
|
||||
|
||||
class ZoomHeight:
|
||||
|
||||
menudefs = [
|
||||
('edit', [
|
||||
None, # Separator
|
||||
('_Zoom Height', '<<zoom-height>>'),
|
||||
])
|
||||
]
|
||||
|
||||
windows_keydefs = {
|
||||
'<<zoom-height>>': ['<Alt-F2>'],
|
||||
}
|
||||
unix_keydefs = {
|
||||
'<<zoom-height>>': ['<Control-z><Control-z>'],
|
||||
}
|
||||
|
||||
def __init__(self, editwin):
|
||||
self.editwin = editwin
|
||||
|
||||
def zoom_height_event(self, event):
|
||||
"...Do what you want here..."
|
||||
|
||||
The final piece of the puzzle is the file "extend.py", which contains a
|
||||
simple table used to configure the loading of extensions. This file
|
||||
currently contains a single list variable named "standard", which is a
|
||||
list of extension names that are to be loaded. (In the future, other
|
||||
configuration variables may be added to this module.)
|
||||
|
||||
Extensions can define key bindings and menu entries that reference
|
||||
events they don't implement (including standard events); however this is
|
||||
not recommended (and may be forbidden in the future).
|
||||
|
||||
Extensions are not required to define menu entries for all events they
|
||||
implement.
|
||||
|
||||
Note: in order to change key bindings, you must currently edit the file
|
||||
keydefs. It contains two dictionaries named and formatted like the
|
||||
keydefs dictionaries described above, one for the Unix bindings and one
|
||||
for the Windows bindings. In the future, a better mechanism will be
|
||||
provided.
|
|
@ -0,0 +1,155 @@
|
|||
[See end for tips.]
|
||||
|
||||
Click on the dotted line at the top of a menu to "tear it off": a
|
||||
separate window containing the menu is created.
|
||||
|
||||
File menu:
|
||||
|
||||
New window -- create a new editing window
|
||||
Open... -- open an existing file
|
||||
Open module... -- open an existing module (searches sys.path)
|
||||
Class browser -- show classes and methods in current file
|
||||
Path browser -- show sys.path directories, modules, classes
|
||||
and methods
|
||||
---
|
||||
Save -- save current window to the associated file (unsaved
|
||||
windows have a * before and after the window title)
|
||||
|
||||
Save As... -- save current window to new file, which becomes
|
||||
the associated file
|
||||
Save Copy As... -- save current window to different file
|
||||
without changing the associated file
|
||||
---
|
||||
Close -- close current window (asks to save if unsaved)
|
||||
Exit -- close all windows and quit IDLE (asks to save if unsaved)
|
||||
|
||||
Edit menu:
|
||||
|
||||
Undo -- Undo last change to current window (max 1000 changes)
|
||||
Redo -- Redo last undone change to current window
|
||||
---
|
||||
Cut -- Copy selection into system-wide clipboard; then delete selection
|
||||
Copy -- Copy selection into system-wide clipboard
|
||||
Paste -- Insert system-wide clipboard into window
|
||||
Select All -- Select the entire contents of the edit buffer
|
||||
---
|
||||
Find... -- Open a search dialog box with many options
|
||||
Find again -- Repeat last search
|
||||
Find selection -- Search for the string in the selection
|
||||
Find in Files... -- Open a search dialog box for searching files
|
||||
Replace... -- Open a search-and-replace dialog box
|
||||
Go to line -- Ask for a line number and show that line
|
||||
---
|
||||
Indent region -- Shift selected lines right 4 spaces
|
||||
Dedent region -- Shift selected lines left 4 spaces
|
||||
Comment out region -- Insert ## in front of selected lines
|
||||
Uncomment region -- Remove leading # or ## from selected lines
|
||||
Tabify region -- Turns *leading* stretches of spaces into tabs
|
||||
Untabify region -- Turn *all* tabs into the right number of spaces
|
||||
Expand word -- Expand the word you have typed to match another
|
||||
word in the same buffer; repeat to get a different expansion
|
||||
Format Paragraph -- Reformat the current blank-line-separated paragraph
|
||||
---
|
||||
Import module -- Import or reload the current module
|
||||
Run script -- Execute the current file in the __main__ namespace
|
||||
|
||||
Windows menu:
|
||||
|
||||
Zoom Height -- toggles the window between normal size (24x80)
|
||||
and maximum height.
|
||||
---
|
||||
The rest of this menu lists the names of all open windows;
|
||||
select one to bring it to the foreground (deiconifying it if
|
||||
necessary).
|
||||
|
||||
Debug menu (in the Python Shell window only):
|
||||
|
||||
Go to file/line -- look around the insert point for a filename
|
||||
and linenumber, open the file, and show the line
|
||||
Open stack viewer -- show the stack traceback of the last exception
|
||||
Debugger toggle -- Run commands in the shell under the debugger
|
||||
JIT Stack viewer toggle -- Open stack viewer on traceback
|
||||
|
||||
Basic editing and navigation:
|
||||
|
||||
Backspace deletes to the left; DEL deletes to the right
|
||||
Arrow keys and Page Up/Down to move around
|
||||
Home/End go to begin/end of line
|
||||
Control-Home/End go to begin/end of file
|
||||
Some Emacs bindings may also work, e.g. ^B/^P/^A/^E/^D/^L
|
||||
|
||||
Automatic indentation:
|
||||
|
||||
After a block-opening statement, the next line is indented by
|
||||
4 spaces (in the Python Shell window by one tab). After
|
||||
certain keywords (break, return etc.) the next line is
|
||||
dedented. In leading indentation, Backspace deletes up to 4
|
||||
spaces if they are there. Tab inserts 1-4 spaces (in the
|
||||
Python Shell window one tab). See also the indent/dedent
|
||||
region commands in the edit menu.
|
||||
|
||||
Python Shell window:
|
||||
|
||||
^C interrupts executing command
|
||||
^D sends end-of-file; closes window if typed at >>> prompt
|
||||
|
||||
Command history:
|
||||
|
||||
Alt-p retrieves previous command matching what you have typed
|
||||
Alt-n retrieves next
|
||||
Return while on any previous command retrieves that command
|
||||
Alt-/ (Expand word) is also useful here
|
||||
|
||||
Syntax colors:
|
||||
|
||||
The coloring is applied in a background "thread", so you may
|
||||
occasionally see uncolorized text. To change the color
|
||||
scheme, edit the ColorPrefs class in IdlePrefs.py.
|
||||
|
||||
Python syntax colors:
|
||||
|
||||
Keywords orange
|
||||
Strings green
|
||||
Comments red
|
||||
Definitions blue
|
||||
|
||||
Shell colors:
|
||||
|
||||
Console output brown
|
||||
stdout blue
|
||||
stderr dark green
|
||||
stdin black
|
||||
|
||||
Other preferences:
|
||||
|
||||
To change the font on Windows, open EditorWindow.py and change
|
||||
text['font'] = ("lucida console", 8)
|
||||
to, e.g.,
|
||||
text['font'] = ("courier new", 10)
|
||||
|
||||
To change keyboard bindings, edit Bindings.py
|
||||
|
||||
Command line usage:
|
||||
|
||||
idle.py [-c command] [-d] [-e] [-s] [-t title] [arg] ...
|
||||
|
||||
-c command run this command
|
||||
-d enable debugger
|
||||
-e edit mode; arguments are files to be edited
|
||||
-s run $IDLESTARTUP or $PYTHONSTARTUP first
|
||||
-t title set title of shell window
|
||||
|
||||
If there are arguments:
|
||||
|
||||
If -e is used, arguments are files opened for editing and
|
||||
sys.argv reflects the arguments passed to IDLE itself.
|
||||
|
||||
Otherwise, if -c is used, all arguments are placed in
|
||||
sys.argv[1:...], with sys.argv[0] set to '-c'.
|
||||
|
||||
Otherwise, if neither -e nor -c is used, the first
|
||||
argument is a script which is executed with the remaining
|
||||
arguments in sys.argv[1:...] and sys.argv[0] set to the
|
||||
script name. If the script name is '-', no script is
|
||||
executed but an interactive Python session is started; the
|
||||
arguments are still available in sys.argv.
|
|
@ -0,0 +1,3 @@
|
|||
@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
|
|
@ -0,0 +1,12 @@
|
|||
#! /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()
|
|
@ -0,0 +1,12 @@
|
|||
#! /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()
|
|
@ -0,0 +1 @@
|
|||
IDLE_VERSION = "0.5"
|
|
@ -0,0 +1,55 @@
|
|||
windows_keydefs = \
|
||||
{'<<Copy>>': ['<Control-c>'],
|
||||
'<<Cut>>': ['<Control-x>'],
|
||||
'<<Paste>>': ['<Control-v>'],
|
||||
'<<beginning-of-line>>': ['<Control-a>', '<Home>'],
|
||||
'<<center-insert>>': ['<Control-l>'],
|
||||
'<<close-all-windows>>': ['<Control-q>'],
|
||||
'<<close-window>>': ['<Alt-F4>'],
|
||||
'<<dump-undo-state>>': ['<Control-backslash>'],
|
||||
'<<end-of-file>>': ['<Control-d>'],
|
||||
'<<python-docs>>': ['<F1>'],
|
||||
'<<history-next>>': ['<Alt-n>'],
|
||||
'<<history-previous>>': ['<Alt-p>'],
|
||||
'<<interrupt-execution>>': ['<Control-c>'],
|
||||
'<<open-class-browser>>': ['<Alt-c>'],
|
||||
'<<open-module>>': ['<Alt-m>'],
|
||||
'<<open-new-window>>': ['<Control-n>'],
|
||||
'<<open-window-from-file>>': ['<Control-o>'],
|
||||
'<<plain-newline-and-indent>>': ['<Control-j>'],
|
||||
'<<redo>>': ['<Control-y>'],
|
||||
'<<remove-selection>>': ['<Escape>'],
|
||||
'<<save-copy-of-window-as-file>>': ['<Alt-Shift-s>'],
|
||||
'<<save-window-as-file>>': ['<Alt-s>'],
|
||||
'<<save-window>>': ['<Control-s>'],
|
||||
'<<select-all>>': ['<Alt-a>'],
|
||||
'<<toggle-auto-coloring>>': ['<Control-slash>'],
|
||||
'<<undo>>': ['<Control-z>']}
|
||||
|
||||
unix_keydefs = \
|
||||
{'<<Copy>>': ['<Alt-w>', '<Meta-w>'],
|
||||
'<<Cut>>': ['<Control-w>'],
|
||||
'<<Paste>>': ['<Control-y>'],
|
||||
'<<beginning-of-line>>': ['<Control-a>', '<Home>'],
|
||||
'<<center-insert>>': ['<Control-l>'],
|
||||
'<<close-all-windows>>': ['<Control-x><Control-c>'],
|
||||
'<<close-window>>': ['<Control-x><Control-0>', '<Control-x><Key-0>'],
|
||||
'<<do-nothing>>': ['<Control-x>'],
|
||||
'<<dump-undo-state>>': ['<Control-backslash>'],
|
||||
'<<end-of-file>>': ['<Control-d>'],
|
||||
'<<help>>': ['<F1>'],
|
||||
'<<history-next>>': ['<Alt-n>', '<Meta-n>'],
|
||||
'<<history-previous>>': ['<Alt-p>', '<Meta-p>'],
|
||||
'<<interrupt-execution>>': ['<Control-c>'],
|
||||
'<<open-class-browser>>': ['<Control-x><Control-b>'],
|
||||
'<<open-module>>': ['<Control-x><Control-m>'],
|
||||
'<<open-new-window>>': ['<Control-x><Control-n>'],
|
||||
'<<open-window-from-file>>': ['<Control-x><Control-f>'],
|
||||
'<<plain-newline-and-indent>>': ['<Control-j>'],
|
||||
'<<redo>>': ['<Alt-z>', '<Meta-z>'],
|
||||
'<<save-copy-of-window-as-file>>': ['<Control-x><w>'],
|
||||
'<<save-window-as-file>>': ['<Control-x><Control-w>'],
|
||||
'<<save-window>>': ['<Control-x><Control-s>'],
|
||||
'<<select-all>>': ['<Alt-a>', '<Meta-a>'],
|
||||
'<<toggle-auto-coloring>>': ['<Control-slash>'],
|
||||
'<<undo>>': ['<Control-z>']}
|
|
@ -0,0 +1,64 @@
|
|||
# Everything is done inside the loader function so that no other names
|
||||
# are placed in the global namespace. Before user code is executed,
|
||||
# even this name is unbound.
|
||||
def loader():
|
||||
import sys, os, protocol, threading, time
|
||||
import Remote
|
||||
|
||||
## Use to debug the loading process itself:
|
||||
## sys.stdout = open('c:\\windows\\desktop\\stdout.txt','a')
|
||||
## sys.stderr = open('c:\\windows\\desktop\\stderr.txt','a')
|
||||
|
||||
# Ensure that there is absolutely no pollution of the global
|
||||
# namespace by deleting the global name of this function.
|
||||
global loader
|
||||
del loader
|
||||
|
||||
# Connect to IDLE
|
||||
try:
|
||||
client = protocol.Client()
|
||||
except protocol.connectionLost, cL:
|
||||
print 'loader: Unable to connect to IDLE', cL
|
||||
return
|
||||
|
||||
# Connect to an ExecBinding object that needs our help. If
|
||||
# the user is starting multiple programs right now, we might get a
|
||||
# different one than the one that started us. Proving that's okay is
|
||||
# left as an exercise to the reader. (HINT: Twelve, by the pigeonhole
|
||||
# principle)
|
||||
ExecBinding = client.getobject('ExecBinding')
|
||||
if not ExecBinding:
|
||||
print "loader: IDLE does not need me."
|
||||
return
|
||||
|
||||
# All of our input and output goes through ExecBinding.
|
||||
sys.stdin = Remote.pseudoIn( ExecBinding.readline )
|
||||
sys.stdout = Remote.pseudoOut( ExecBinding.write.void, tag="stdout" )
|
||||
sys.stderr = Remote.pseudoOut( ExecBinding.write.void, tag="stderr" )
|
||||
|
||||
# Create a Remote object and start it running.
|
||||
remote = Remote.Remote(globals(), ExecBinding)
|
||||
rthread = threading.Thread(target=remote.mainloop)
|
||||
rthread.setDaemon(1)
|
||||
rthread.start()
|
||||
|
||||
# Block until either the client or the user program stops
|
||||
user = rthread.isAlive
|
||||
while user and client.isAlive():
|
||||
time.sleep(0.025)
|
||||
|
||||
if not user():
|
||||
user = hasattr(sys, "ready_to_exit") and sys.ready_to_exit
|
||||
for t in threading.enumerate():
|
||||
if not t.isDaemon() and t.isAlive() and t!=threading.currentThread():
|
||||
user = t.isAlive
|
||||
break
|
||||
|
||||
# We need to make sure we actually exit, so that the user doesn't get
|
||||
# stuck with an invisible process. We want to finalize C modules, so
|
||||
# we don't use os._exit(), but we don't call sys.exitfunc, which might
|
||||
# block forever.
|
||||
del sys.exitfunc
|
||||
sys.exit()
|
||||
|
||||
loader()
|
|
@ -0,0 +1,369 @@
|
|||
"""protocol (David Scherer <dscherer@cmu.edu>)
|
||||
|
||||
This module implements a simple RPC or "distributed object" protocol.
|
||||
I am probably the 100,000th person to write this in Python, but, hey,
|
||||
it was fun.
|
||||
|
||||
Contents:
|
||||
|
||||
connectionLost is an exception that will be thrown by functions in
|
||||
the protocol module or calls to remote methods that fail because
|
||||
the remote program has closed the socket or because no connection
|
||||
could be established in the first place.
|
||||
|
||||
Server( port=None, connection_hook=None ) creates a server on a
|
||||
well-known port, to which clients can connect. When a client
|
||||
connects, a Connection is created for it. If connection_hook
|
||||
is defined, then connection_hook( socket.getpeername() ) is called
|
||||
before a Connection is created, and if it returns false then the
|
||||
connection is refused. connection_hook must be prepared to be
|
||||
called from any thread.
|
||||
|
||||
Client( ip='127.0.0.1', port=None ) returns a Connection to a Server
|
||||
object at a well-known address and port.
|
||||
|
||||
Connection( socket ) creates an RPC connection on an arbitrary socket,
|
||||
which must already be connected to another program. You do not
|
||||
need to use this directly if you are using Client() or Server().
|
||||
|
||||
publish( name, connect_function ) provides an object with the
|
||||
specified name to some or all Connections. When another program
|
||||
calls Connection.getobject() with the specified name, the
|
||||
specified connect_function is called with the arguments
|
||||
|
||||
connect_function( conn, addr )
|
||||
|
||||
where conn is the Connection object to the requesting client and
|
||||
addr is the address returned by socket.getpeername(). If that
|
||||
function returns an object, that object becomes accessible to
|
||||
the caller. If it returns None, the caller's request fails.
|
||||
|
||||
Connection objects:
|
||||
|
||||
.close() refuses additional RPC messages from the peer, and notifies
|
||||
the peer that the connection has been closed. All pending remote
|
||||
method calls in either program will fail with a connectionLost
|
||||
exception. Further remote method calls on this connection will
|
||||
also result in errors.
|
||||
|
||||
.getobject(name) returns a proxy for the remote object with the
|
||||
specified name, if it exists and the peer permits us access.
|
||||
Otherwise, it returns None. It may throw a connectionLost
|
||||
exception. The returned proxy supports basic attribute access
|
||||
and method calls, and its methods have an extra attribute,
|
||||
.void, which is a function that has the same effect but always
|
||||
returns None. This last capability is provided as a performance
|
||||
hack: object.method.void(params) can return without waiting for
|
||||
the remote process to respond, but object.method(params) needs
|
||||
to wait for a return value or exception.
|
||||
|
||||
.rpc_loop(block=0) processes *incoming* messages for this connection.
|
||||
If block=1, it continues processing until an exception or return
|
||||
value is received, which is normally forever. Otherwise it
|
||||
returns when all currently pending messages have been delivered.
|
||||
It may throw a connectionLost exception.
|
||||
|
||||
.set_close_hook(f) specifies a function to be called when the remote
|
||||
object closes the connection during a call to rpc_loop(). This
|
||||
is a good way for servers to be notified when clients disconnect.
|
||||
|
||||
.set_shutdown_hook(f) specifies a function called *immediately* when
|
||||
the receive loop detects that the connection has been lost. The
|
||||
provided function must be prepared to run in any thread.
|
||||
|
||||
Server objects:
|
||||
|
||||
.rpc_loop() processes incoming messages on all connections, and
|
||||
returns when all pending messages have been processed. It will
|
||||
*not* throw connectionLost exceptions; the
|
||||
Connection.set_close_hook() mechanism is much better for servers.
|
||||
"""
|
||||
|
||||
import sys, os, string, types
|
||||
import socket
|
||||
from threading import Thread
|
||||
from Queue import Queue, Empty
|
||||
from cPickle import Pickler, Unpickler, PicklingError
|
||||
|
||||
class connectionLost:
|
||||
def __init__(self, what=""): self.what = what
|
||||
def __repr__(self): return self.what
|
||||
def __str__(self): return self.what
|
||||
|
||||
def getmethods(cls):
|
||||
"Returns a list of the names of the methods of a class."
|
||||
methods = []
|
||||
for b in cls.__bases__:
|
||||
methods = methods + getmethods(b)
|
||||
d = cls.__dict__
|
||||
for k in d.keys():
|
||||
if type(d[k])==types.FunctionType:
|
||||
methods.append(k)
|
||||
return methods
|
||||
|
||||
class methodproxy:
|
||||
"Proxy for a method of a remote object."
|
||||
def __init__(self, classp, name):
|
||||
self.classp=classp
|
||||
self.name=name
|
||||
self.client = classp.client
|
||||
def __call__(self, *args, **keywords):
|
||||
return self.client.call( 'm', self.classp.name, self.name, args, keywords )
|
||||
|
||||
def void(self, *args, **keywords):
|
||||
self.client.call_void( 'm', self.classp.name,self.name,args,keywords)
|
||||
|
||||
class classproxy:
|
||||
"Proxy for a remote object."
|
||||
def __init__(self, client, name, methods):
|
||||
self.__dict__['client'] = client
|
||||
self.__dict__['name'] = name
|
||||
|
||||
for m in methods:
|
||||
prox = methodproxy( self, m )
|
||||
self.__dict__[m] = prox
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return self.client.call( 'g', self.name, attr )
|
||||
|
||||
def __setattr__(self, attr, value):
|
||||
self.client.call_void( 's', self.name, attr, value )
|
||||
|
||||
local_connect = {}
|
||||
def publish(name, connect_function):
|
||||
local_connect[name]=connect_function
|
||||
|
||||
class socketFile:
|
||||
"File emulator based on a socket. Provides only blocking semantics for now."
|
||||
|
||||
def __init__(self, socket):
|
||||
self.socket = socket
|
||||
self.buffer = ''
|
||||
|
||||
def _recv(self,bytes):
|
||||
try:
|
||||
r=self.socket.recv(bytes)
|
||||
except:
|
||||
raise connectionLost()
|
||||
if not r:
|
||||
raise connectionLost()
|
||||
return r
|
||||
|
||||
def write(self, string):
|
||||
try:
|
||||
self.socket.send( string )
|
||||
except:
|
||||
raise connectionLost()
|
||||
|
||||
def read(self,bytes):
|
||||
x = bytes-len(self.buffer)
|
||||
while x>0:
|
||||
self.buffer=self.buffer+self._recv(x)
|
||||
x = bytes-len(self.buffer)
|
||||
s = self.buffer[:bytes]
|
||||
self.buffer=self.buffer[bytes:]
|
||||
return s
|
||||
|
||||
def readline(self):
|
||||
while 1:
|
||||
f = string.find(self.buffer,'\n')
|
||||
if f>=0:
|
||||
s = self.buffer[:f+1]
|
||||
self.buffer=self.buffer[f+1:]
|
||||
return s
|
||||
self.buffer = self.buffer + self._recv(1024)
|
||||
|
||||
|
||||
class Connection (Thread):
|
||||
debug = 0
|
||||
def __init__(self, socket):
|
||||
self.local_objects = {}
|
||||
self.socket = socket
|
||||
self.name = socket.getpeername()
|
||||
self.socketfile = socketFile(socket)
|
||||
self.queue = Queue(-1)
|
||||
self.refuse_messages = 0
|
||||
self.cmds = { 'm': self.r_meth,
|
||||
'g': self.r_get,
|
||||
's': self.r_set,
|
||||
'o': self.r_geto,
|
||||
'e': self.r_exc,
|
||||
#'r' handled by rpc_loop
|
||||
}
|
||||
|
||||
Thread.__init__(self)
|
||||
self.setDaemon(1)
|
||||
self.start()
|
||||
|
||||
def getobject(self, name):
|
||||
methods = self.call( 'o', name )
|
||||
if methods is None: return None
|
||||
return classproxy(self, name, methods)
|
||||
|
||||
# close_hook is called from rpc_loop(), like a normal remote method
|
||||
# invocation
|
||||
def set_close_hook(self,hook): self.close_hook = hook
|
||||
|
||||
# shutdown_hook is called directly from the run() thread, and needs
|
||||
# to be "thread safe"
|
||||
def set_shutdown_hook(self,hook): self.shutdown_hook = hook
|
||||
|
||||
close_hook = None
|
||||
shutdown_hook = None
|
||||
|
||||
def close(self):
|
||||
self._shutdown()
|
||||
self.refuse_messages = 1
|
||||
|
||||
def call(self, c, *args):
|
||||
self.send( (c, args, 1 ) )
|
||||
return self.rpc_loop( block = 1 )
|
||||
|
||||
def call_void(self, c, *args):
|
||||
try:
|
||||
self.send( (c, args, 0 ) )
|
||||
except:
|
||||
pass
|
||||
|
||||
# the following methods handle individual RPC calls:
|
||||
|
||||
def r_geto(self, obj):
|
||||
c = local_connect.get(obj)
|
||||
if not c: return None
|
||||
o = c(self, self.name)
|
||||
if not o: return None
|
||||
self.local_objects[obj] = o
|
||||
return getmethods(o.__class__)
|
||||
|
||||
def r_meth(self, obj, name, args, keywords):
|
||||
return apply( getattr(self.local_objects[obj],name), args, keywords)
|
||||
|
||||
def r_get(self, obj, name):
|
||||
return getattr(self.local_objects[obj],name)
|
||||
|
||||
def r_set(self, obj, name, value):
|
||||
setattr(self.local_objects[obj],name,value)
|
||||
|
||||
def r_exc(self, e, v):
|
||||
raise e, v
|
||||
|
||||
def rpc_exec(self, cmd, arg, ret):
|
||||
if self.refuse_messages: return
|
||||
if self.debug: print cmd,arg,ret
|
||||
if ret:
|
||||
try:
|
||||
r=apply(self.cmds.get(cmd), arg)
|
||||
self.send( ('r', r, 0) )
|
||||
except:
|
||||
try:
|
||||
self.send( ('e', sys.exc_info()[:2], 0) )
|
||||
except PicklingError:
|
||||
self.send( ('e', (TypeError, 'Unpicklable exception.'), 0 ) )
|
||||
else:
|
||||
# we cannot report exceptions to the caller, so
|
||||
# we report them in this process.
|
||||
r=apply(self.cmds.get(cmd), arg)
|
||||
|
||||
# the following methods implement the RPC and message loops:
|
||||
|
||||
def rpc_loop(self, block=0):
|
||||
if self.refuse_messages: raise connectionLost('(already closed)')
|
||||
try:
|
||||
while 1:
|
||||
try:
|
||||
cmd, arg, ret = self.queue.get( block )
|
||||
except Empty:
|
||||
return None
|
||||
if cmd=='r': return arg
|
||||
self.rpc_exec(cmd,arg,ret)
|
||||
except connectionLost:
|
||||
if self.close_hook:
|
||||
self.close_hook()
|
||||
self.close_hook = None
|
||||
raise
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
while 1:
|
||||
data = self.recv()
|
||||
self.queue.put( data )
|
||||
except:
|
||||
self.queue.put( ('e', sys.exc_info()[:2], 0) )
|
||||
|
||||
# The following send raw pickled data to the peer
|
||||
|
||||
def send(self, data):
|
||||
try:
|
||||
Pickler(self.socketfile,1).dump( data )
|
||||
except connectionLost:
|
||||
self._shutdown()
|
||||
if self.shutdown_hook: self.shutdown_hook()
|
||||
raise
|
||||
|
||||
def recv(self):
|
||||
try:
|
||||
return Unpickler(self.socketfile).load()
|
||||
except connectionLost:
|
||||
self._shutdown()
|
||||
if self.shutdown_hook: self.shutdown_hook()
|
||||
raise
|
||||
except:
|
||||
raise
|
||||
|
||||
def _shutdown(self):
|
||||
try:
|
||||
self.socket.shutdown(1)
|
||||
self.socket.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class Server (Thread):
|
||||
default_port = 0x1D1E # "IDlE"
|
||||
|
||||
def __init__(self, port=None, connection_hook=None):
|
||||
self.connections = []
|
||||
self.port = port or self.default_port
|
||||
self.connection_hook = connection_hook
|
||||
|
||||
try:
|
||||
self.wellknown = s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.bind('', self.port)
|
||||
s.listen(3)
|
||||
except:
|
||||
raise connectionLost
|
||||
|
||||
Thread.__init__(self)
|
||||
self.setDaemon(1)
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
s = self.wellknown
|
||||
while 1:
|
||||
conn, addr = s.accept()
|
||||
if self.connection_hook and not self.connection_hook(addr):
|
||||
try:
|
||||
conn.shutdown(1)
|
||||
except:
|
||||
pass
|
||||
continue
|
||||
self.connections.append( Connection(conn) )
|
||||
|
||||
def rpc_loop(self):
|
||||
cns = self.connections[:]
|
||||
for c in cns:
|
||||
try:
|
||||
c.rpc_loop(block = 0)
|
||||
except connectionLost:
|
||||
if c in self.connections:
|
||||
self.connections.remove(c)
|
||||
|
||||
def Client(ip='127.0.0.1', port=None):
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect(ip,port or Server.default_port)
|
||||
except socket.error, what:
|
||||
raise connectionLost(str(what))
|
||||
except:
|
||||
raise connectionLost()
|
||||
return Connection(s)
|
|
@ -0,0 +1,336 @@
|
|||
"""Parse a Python file and retrieve classes and methods.
|
||||
|
||||
Parse enough of a Python file to recognize class and method
|
||||
definitions and to find out the superclasses of a class.
|
||||
|
||||
The interface consists of a single function:
|
||||
readmodule(module, path)
|
||||
module is the name of a Python module, path is an optional list of
|
||||
directories where the module is to be searched. If present, path is
|
||||
prepended to the system search path sys.path.
|
||||
The return value is a dictionary. The keys of the dictionary are
|
||||
the names of the classes defined in the module (including classes
|
||||
that are defined via the from XXX import YYY construct). The values
|
||||
are class instances of the class Class defined here.
|
||||
|
||||
A class is described by the class Class in this module. Instances
|
||||
of this class have the following instance variables:
|
||||
name -- the name of the class
|
||||
super -- a list of super classes (Class instances)
|
||||
methods -- a dictionary of methods
|
||||
file -- the file in which the class was defined
|
||||
lineno -- the line in the file on which the class statement occurred
|
||||
The dictionary of methods uses the method names as keys and the line
|
||||
numbers on which the method was defined as values.
|
||||
If the name of a super class is not recognized, the corresponding
|
||||
entry in the list of super classes is not a class instance but a
|
||||
string giving the name of the super class. Since import statements
|
||||
are recognized and imported modules are scanned as well, this
|
||||
shouldn't happen often.
|
||||
|
||||
BUGS
|
||||
- Continuation lines are not dealt with at all.
|
||||
- While triple-quoted strings won't confuse it, lines that look like
|
||||
def, class, import or "from ... import" stmts inside backslash-continued
|
||||
single-quoted strings are treated like code. The expense of stopping
|
||||
that isn't worth it.
|
||||
- Code that doesn't pass tabnanny or python -t will confuse it, unless
|
||||
you set the module TABWIDTH vrbl (default 8) to the correct tab width
|
||||
for the file.
|
||||
|
||||
PACKAGE RELATED BUGS
|
||||
- If you have a package and a module inside that or another package
|
||||
with the same name, module caching doesn't work properly since the
|
||||
key is the base name of the module/package.
|
||||
- The only entry that is returned when you readmodule a package is a
|
||||
__path__ whose value is a list which confuses certain class browsers.
|
||||
- When code does:
|
||||
from package import subpackage
|
||||
class MyClass(subpackage.SuperClass):
|
||||
...
|
||||
It can't locate the parent. It probably needs to have the same
|
||||
hairy logic that the import locator already does. (This logic
|
||||
exists coded in Python in the freeze package.)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import imp
|
||||
import re
|
||||
import string
|
||||
|
||||
TABWIDTH = 8
|
||||
|
||||
_getnext = re.compile(r"""
|
||||
(?P<String>
|
||||
\""" [^"\\]* (?:
|
||||
(?: \\. | "(?!"") )
|
||||
[^"\\]*
|
||||
)*
|
||||
\"""
|
||||
|
||||
| ''' [^'\\]* (?:
|
||||
(?: \\. | '(?!'') )
|
||||
[^'\\]*
|
||||
)*
|
||||
'''
|
||||
)
|
||||
|
||||
| (?P<Method>
|
||||
^
|
||||
(?P<MethodIndent> [ \t]* )
|
||||
def [ \t]+
|
||||
(?P<MethodName> [a-zA-Z_] \w* )
|
||||
[ \t]* \(
|
||||
)
|
||||
|
||||
| (?P<Class>
|
||||
^
|
||||
(?P<ClassIndent> [ \t]* )
|
||||
class [ \t]+
|
||||
(?P<ClassName> [a-zA-Z_] \w* )
|
||||
[ \t]*
|
||||
(?P<ClassSupers> \( [^)\n]* \) )?
|
||||
[ \t]* :
|
||||
)
|
||||
|
||||
| (?P<Import>
|
||||
^ import [ \t]+
|
||||
(?P<ImportList> [^#;\n]+ )
|
||||
)
|
||||
|
||||
| (?P<ImportFrom>
|
||||
^ from [ \t]+
|
||||
(?P<ImportFromPath>
|
||||
[a-zA-Z_] \w*
|
||||
(?:
|
||||
[ \t]* \. [ \t]* [a-zA-Z_] \w*
|
||||
)*
|
||||
)
|
||||
[ \t]+
|
||||
import [ \t]+
|
||||
(?P<ImportFromList> [^#;\n]+ )
|
||||
)
|
||||
""", re.VERBOSE | re.DOTALL | re.MULTILINE).search
|
||||
|
||||
_modules = {} # cache of modules we've seen
|
||||
|
||||
# each Python class is represented by an instance of this class
|
||||
class Class:
|
||||
'''Class to represent a Python class.'''
|
||||
def __init__(self, module, name, super, file, lineno):
|
||||
self.module = module
|
||||
self.name = name
|
||||
if super is None:
|
||||
super = []
|
||||
self.super = super
|
||||
self.methods = {}
|
||||
self.file = file
|
||||
self.lineno = lineno
|
||||
|
||||
def _addmethod(self, name, lineno):
|
||||
self.methods[name] = lineno
|
||||
|
||||
class Function(Class):
|
||||
'''Class to represent a top-level Python function'''
|
||||
def __init__(self, module, name, file, lineno):
|
||||
Class.__init__(self, module, name, None, file, lineno)
|
||||
def _addmethod(self, name, lineno):
|
||||
assert 0, "Function._addmethod() shouldn't be called"
|
||||
|
||||
def readmodule(module, path=[], inpackage=0):
|
||||
'''Backwards compatible interface.
|
||||
|
||||
Like readmodule_ex() but strips Function objects from the
|
||||
resulting dictionary.'''
|
||||
|
||||
dict = readmodule_ex(module, path, inpackage)
|
||||
res = {}
|
||||
for key, value in dict.items():
|
||||
if not isinstance(value, Function):
|
||||
res[key] = value
|
||||
return res
|
||||
|
||||
def readmodule_ex(module, path=[], inpackage=0):
|
||||
'''Read a module file and return a dictionary of classes.
|
||||
|
||||
Search for MODULE in PATH and sys.path, read and parse the
|
||||
module and return a dictionary with one entry for each class
|
||||
found in the module.'''
|
||||
|
||||
dict = {}
|
||||
|
||||
i = string.rfind(module, '.')
|
||||
if i >= 0:
|
||||
# Dotted module name
|
||||
package = string.strip(module[:i])
|
||||
submodule = string.strip(module[i+1:])
|
||||
parent = readmodule(package, path, inpackage)
|
||||
child = readmodule(submodule, parent['__path__'], 1)
|
||||
return child
|
||||
|
||||
if _modules.has_key(module):
|
||||
# we've seen this module before...
|
||||
return _modules[module]
|
||||
if module in sys.builtin_module_names:
|
||||
# this is a built-in module
|
||||
_modules[module] = dict
|
||||
return dict
|
||||
|
||||
# search the path for the module
|
||||
f = None
|
||||
if inpackage:
|
||||
try:
|
||||
f, file, (suff, mode, type) = \
|
||||
imp.find_module(module, path)
|
||||
except ImportError:
|
||||
f = None
|
||||
if f is None:
|
||||
fullpath = list(path) + sys.path
|
||||
f, file, (suff, mode, type) = imp.find_module(module, fullpath)
|
||||
if type == imp.PKG_DIRECTORY:
|
||||
dict['__path__'] = [file]
|
||||
_modules[module] = dict
|
||||
path = [file] + path
|
||||
f, file, (suff, mode, type) = \
|
||||
imp.find_module('__init__', [file])
|
||||
if type != imp.PY_SOURCE:
|
||||
# not Python source, can't do anything with this module
|
||||
f.close()
|
||||
_modules[module] = dict
|
||||
return dict
|
||||
|
||||
_modules[module] = dict
|
||||
imports = []
|
||||
classstack = [] # stack of (class, indent) pairs
|
||||
src = f.read()
|
||||
f.close()
|
||||
|
||||
# To avoid having to stop the regexp at each newline, instead
|
||||
# when we need a line number we simply string.count the number of
|
||||
# newlines in the string since the last time we did this; i.e.,
|
||||
# lineno = lineno + \
|
||||
# string.count(src, '\n', last_lineno_pos, here)
|
||||
# last_lineno_pos = here
|
||||
countnl = string.count
|
||||
lineno, last_lineno_pos = 1, 0
|
||||
i = 0
|
||||
while 1:
|
||||
m = _getnext(src, i)
|
||||
if not m:
|
||||
break
|
||||
start, i = m.span()
|
||||
|
||||
if m.start("Method") >= 0:
|
||||
# found a method definition or function
|
||||
thisindent = _indent(m.group("MethodIndent"))
|
||||
meth_name = m.group("MethodName")
|
||||
lineno = lineno + \
|
||||
countnl(src, '\n',
|
||||
last_lineno_pos, start)
|
||||
last_lineno_pos = start
|
||||
# close all classes indented at least as much
|
||||
while classstack and \
|
||||
classstack[-1][1] >= thisindent:
|
||||
del classstack[-1]
|
||||
if classstack:
|
||||
# it's a class method
|
||||
cur_class = classstack[-1][0]
|
||||
cur_class._addmethod(meth_name, lineno)
|
||||
else:
|
||||
# it's a function
|
||||
f = Function(module, meth_name,
|
||||
file, lineno)
|
||||
dict[meth_name] = f
|
||||
|
||||
elif m.start("String") >= 0:
|
||||
pass
|
||||
|
||||
elif m.start("Class") >= 0:
|
||||
# we found a class definition
|
||||
thisindent = _indent(m.group("ClassIndent"))
|
||||
# close all classes indented at least as much
|
||||
while classstack and \
|
||||
classstack[-1][1] >= thisindent:
|
||||
del classstack[-1]
|
||||
lineno = lineno + \
|
||||
countnl(src, '\n', last_lineno_pos, start)
|
||||
last_lineno_pos = start
|
||||
class_name = m.group("ClassName")
|
||||
inherit = m.group("ClassSupers")
|
||||
if inherit:
|
||||
# the class inherits from other classes
|
||||
inherit = string.strip(inherit[1:-1])
|
||||
names = []
|
||||
for n in string.splitfields(inherit, ','):
|
||||
n = string.strip(n)
|
||||
if dict.has_key(n):
|
||||
# we know this super class
|
||||
n = dict[n]
|
||||
else:
|
||||
c = string.splitfields(n, '.')
|
||||
if len(c) > 1:
|
||||
# super class
|
||||
# is of the
|
||||
# form module.class:
|
||||
# look in
|
||||
# module for class
|
||||
m = c[-2]
|
||||
c = c[-1]
|
||||
if _modules.has_key(m):
|
||||
d = _modules[m]
|
||||
if d.has_key(c):
|
||||
n = d[c]
|
||||
names.append(n)
|
||||
inherit = names
|
||||
# remember this class
|
||||
cur_class = Class(module, class_name, inherit,
|
||||
file, lineno)
|
||||
dict[class_name] = cur_class
|
||||
classstack.append((cur_class, thisindent))
|
||||
|
||||
elif m.start("Import") >= 0:
|
||||
# import module
|
||||
for n in string.split(m.group("ImportList"), ','):
|
||||
n = string.strip(n)
|
||||
try:
|
||||
# recursively read the imported module
|
||||
d = readmodule(n, path, inpackage)
|
||||
except:
|
||||
##print 'module', n, 'not found'
|
||||
pass
|
||||
|
||||
elif m.start("ImportFrom") >= 0:
|
||||
# from module import stuff
|
||||
mod = m.group("ImportFromPath")
|
||||
names = string.split(m.group("ImportFromList"), ',')
|
||||
try:
|
||||
# recursively read the imported module
|
||||
d = readmodule(mod, path, inpackage)
|
||||
except:
|
||||
##print 'module', mod, 'not found'
|
||||
continue
|
||||
# add any classes that were defined in the
|
||||
# imported module to our name space if they
|
||||
# were mentioned in the list
|
||||
for n in names:
|
||||
n = string.strip(n)
|
||||
if d.has_key(n):
|
||||
dict[n] = d[n]
|
||||
elif n == '*':
|
||||
# only add a name if not
|
||||
# already there (to mimic what
|
||||
# Python does internally)
|
||||
# also don't add names that
|
||||
# start with _
|
||||
for n in d.keys():
|
||||
if n[0] != '_' and \
|
||||
not dict.has_key(n):
|
||||
dict[n] = d[n]
|
||||
else:
|
||||
assert 0, "regexp _getnext found something unexpected"
|
||||
|
||||
return dict
|
||||
|
||||
def _indent(ws, _expandtabs=string.expandtabs):
|
||||
return len(_expandtabs(ws, TABWIDTH))
|
|
@ -0,0 +1,59 @@
|
|||
# spawn - This is ugly, OS-specific code to spawn a separate process. It
|
||||
# also defines a function for getting the version of a path most
|
||||
# likely to work with cranky API functions.
|
||||
|
||||
import os
|
||||
|
||||
def hardpath(path):
|
||||
path = os.path.normcase(os.path.abspath(path))
|
||||
try:
|
||||
import win32api
|
||||
path = win32api.GetShortPathName( path )
|
||||
except:
|
||||
pass
|
||||
return path
|
||||
|
||||
if hasattr(os, 'spawnv'):
|
||||
|
||||
# Windows-ish OS: we use spawnv(), and stick quotes around arguments
|
||||
# in case they contains spaces, since Windows will jam all the
|
||||
# arguments to spawn() or exec() together into one string. The
|
||||
# kill_zombies function is a noop.
|
||||
|
||||
def spawn(bin, *args):
|
||||
nargs = [bin]
|
||||
for arg in args:
|
||||
nargs.append( '"'+arg+'"' )
|
||||
os.spawnv( os.P_NOWAIT, bin, nargs )
|
||||
|
||||
def kill_zombies(): pass
|
||||
|
||||
elif hasattr(os, 'fork'):
|
||||
|
||||
# UNIX-ish operating system: we fork() and exec(), and we have to track
|
||||
# the pids of our children and call waitpid() on them to avoid leaving
|
||||
# zombies in the process table. kill_zombies() does the dirty work, and
|
||||
# should be called periodically.
|
||||
|
||||
zombies = []
|
||||
|
||||
def spawn(bin, *args):
|
||||
pid = os.fork()
|
||||
if pid:
|
||||
zombies.append(pid)
|
||||
else:
|
||||
os.execv( bin, (bin, ) + args )
|
||||
|
||||
def kill_zombies():
|
||||
for z in zombies[:]:
|
||||
stat = os.waitpid(z, os.WNOHANG)
|
||||
if stat[0]==z:
|
||||
zombies.remove(z)
|
||||
|
||||
else:
|
||||
# If you get here, you may be able to write an alternative implementation
|
||||
# of these functions for your OS.
|
||||
|
||||
def kill_zombies(): pass
|
||||
|
||||
raise OSError, 'This OS does not support fork() or spawnv().'
|
|
@ -0,0 +1,372 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
"""The Tab Nanny despises ambiguous indentation. She knows no mercy."""
|
||||
|
||||
# Released to the public domain, by Tim Peters, 15 April 1998.
|
||||
|
||||
# XXX Note: this is now a standard library module.
|
||||
# XXX The API needs to undergo changes however; the current code is too
|
||||
# XXX script-like. This will be addressed later.
|
||||
|
||||
__version__ = "6"
|
||||
|
||||
import os
|
||||
import sys
|
||||
import string
|
||||
import getopt
|
||||
import tokenize
|
||||
|
||||
verbose = 0
|
||||
filename_only = 0
|
||||
|
||||
def errprint(*args):
|
||||
sep = ""
|
||||
for arg in args:
|
||||
sys.stderr.write(sep + str(arg))
|
||||
sep = " "
|
||||
sys.stderr.write("\n")
|
||||
|
||||
def main():
|
||||
global verbose, filename_only
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "qv")
|
||||
except getopt.error, msg:
|
||||
errprint(msg)
|
||||
return
|
||||
for o, a in opts:
|
||||
if o == '-q':
|
||||
filename_only = filename_only + 1
|
||||
if o == '-v':
|
||||
verbose = verbose + 1
|
||||
if not args:
|
||||
errprint("Usage:", sys.argv[0], "[-v] file_or_directory ...")
|
||||
return
|
||||
for arg in args:
|
||||
check(arg)
|
||||
|
||||
class NannyNag:
|
||||
def __init__(self, lineno, msg, line):
|
||||
self.lineno, self.msg, self.line = lineno, msg, line
|
||||
def get_lineno(self):
|
||||
return self.lineno
|
||||
def get_msg(self):
|
||||
return self.msg
|
||||
def get_line(self):
|
||||
return self.line
|
||||
|
||||
def check(file):
|
||||
if os.path.isdir(file) and not os.path.islink(file):
|
||||
if verbose:
|
||||
print "%s: listing directory" % `file`
|
||||
names = os.listdir(file)
|
||||
for name in names:
|
||||
fullname = os.path.join(file, name)
|
||||
if (os.path.isdir(fullname) and
|
||||
not os.path.islink(fullname) or
|
||||
os.path.normcase(name[-3:]) == ".py"):
|
||||
check(fullname)
|
||||
return
|
||||
|
||||
try:
|
||||
f = open(file)
|
||||
except IOError, msg:
|
||||
errprint("%s: I/O Error: %s" % (`file`, str(msg)))
|
||||
return
|
||||
|
||||
if verbose > 1:
|
||||
print "checking", `file`, "..."
|
||||
|
||||
reset_globals()
|
||||
try:
|
||||
tokenize.tokenize(f.readline, tokeneater)
|
||||
|
||||
except tokenize.TokenError, msg:
|
||||
errprint("%s: Token Error: %s" % (`file`, str(msg)))
|
||||
return
|
||||
|
||||
except NannyNag, nag:
|
||||
badline = nag.get_lineno()
|
||||
line = nag.get_line()
|
||||
if verbose:
|
||||
print "%s: *** Line %d: trouble in tab city! ***" % (
|
||||
`file`, badline)
|
||||
print "offending line:", `line`
|
||||
print nag.get_msg()
|
||||
else:
|
||||
if ' ' in file: file = '"' + file + '"'
|
||||
if filename_only: print file
|
||||
else: print file, badline, `line`
|
||||
return
|
||||
|
||||
if verbose:
|
||||
print "%s: Clean bill of health." % `file`
|
||||
|
||||
class Whitespace:
|
||||
# the characters used for space and tab
|
||||
S, T = ' \t'
|
||||
|
||||
# members:
|
||||
# raw
|
||||
# the original string
|
||||
# n
|
||||
# the number of leading whitespace characters in raw
|
||||
# nt
|
||||
# the number of tabs in raw[:n]
|
||||
# norm
|
||||
# the normal form as a pair (count, trailing), where:
|
||||
# count
|
||||
# a tuple such that raw[:n] contains count[i]
|
||||
# instances of S * i + T
|
||||
# trailing
|
||||
# the number of trailing spaces in raw[:n]
|
||||
# It's A Theorem that m.indent_level(t) ==
|
||||
# n.indent_level(t) for all t >= 1 iff m.norm == n.norm.
|
||||
# is_simple
|
||||
# true iff raw[:n] is of the form (T*)(S*)
|
||||
|
||||
def __init__(self, ws):
|
||||
self.raw = ws
|
||||
S, T = Whitespace.S, Whitespace.T
|
||||
count = []
|
||||
b = n = nt = 0
|
||||
for ch in self.raw:
|
||||
if ch == S:
|
||||
n = n + 1
|
||||
b = b + 1
|
||||
elif ch == T:
|
||||
n = n + 1
|
||||
nt = nt + 1
|
||||
if b >= len(count):
|
||||
count = count + [0] * (b - len(count) + 1)
|
||||
count[b] = count[b] + 1
|
||||
b = 0
|
||||
else:
|
||||
break
|
||||
self.n = n
|
||||
self.nt = nt
|
||||
self.norm = tuple(count), b
|
||||
self.is_simple = len(count) <= 1
|
||||
|
||||
# return length of longest contiguous run of spaces (whether or not
|
||||
# preceding a tab)
|
||||
def longest_run_of_spaces(self):
|
||||
count, trailing = self.norm
|
||||
return max(len(count)-1, trailing)
|
||||
|
||||
def indent_level(self, tabsize):
|
||||
# count, il = self.norm
|
||||
# for i in range(len(count)):
|
||||
# if count[i]:
|
||||
# il = il + (i/tabsize + 1)*tabsize * count[i]
|
||||
# return il
|
||||
|
||||
# quicker:
|
||||
# il = trailing + sum (i/ts + 1)*ts*count[i] =
|
||||
# trailing + ts * sum (i/ts + 1)*count[i] =
|
||||
# trailing + ts * sum i/ts*count[i] + count[i] =
|
||||
# trailing + ts * [(sum i/ts*count[i]) + (sum count[i])] =
|
||||
# trailing + ts * [(sum i/ts*count[i]) + num_tabs]
|
||||
# and note that i/ts*count[i] is 0 when i < ts
|
||||
|
||||
count, trailing = self.norm
|
||||
il = 0
|
||||
for i in range(tabsize, len(count)):
|
||||
il = il + i/tabsize * count[i]
|
||||
return trailing + tabsize * (il + self.nt)
|
||||
|
||||
# return true iff self.indent_level(t) == other.indent_level(t)
|
||||
# for all t >= 1
|
||||
def equal(self, other):
|
||||
return self.norm == other.norm
|
||||
|
||||
# return a list of tuples (ts, i1, i2) such that
|
||||
# i1 == self.indent_level(ts) != other.indent_level(ts) == i2.
|
||||
# Intended to be used after not self.equal(other) is known, in which
|
||||
# case it will return at least one witnessing tab size.
|
||||
def not_equal_witness(self, other):
|
||||
n = max(self.longest_run_of_spaces(),
|
||||
other.longest_run_of_spaces()) + 1
|
||||
a = []
|
||||
for ts in range(1, n+1):
|
||||
if self.indent_level(ts) != other.indent_level(ts):
|
||||
a.append( (ts,
|
||||
self.indent_level(ts),
|
||||
other.indent_level(ts)) )
|
||||
return a
|
||||
|
||||
# Return true iff self.indent_level(t) < other.indent_level(t)
|
||||
# for all t >= 1.
|
||||
# The algorithm is due to Vincent Broman.
|
||||
# Easy to prove it's correct.
|
||||
# XXXpost that.
|
||||
# Trivial to prove n is sharp (consider T vs ST).
|
||||
# Unknown whether there's a faster general way. I suspected so at
|
||||
# first, but no longer.
|
||||
# For the special (but common!) case where M and N are both of the
|
||||
# form (T*)(S*), M.less(N) iff M.len() < N.len() and
|
||||
# M.num_tabs() <= N.num_tabs(). Proof is easy but kinda long-winded.
|
||||
# XXXwrite that up.
|
||||
# Note that M is of the form (T*)(S*) iff len(M.norm[0]) <= 1.
|
||||
def less(self, other):
|
||||
if self.n >= other.n:
|
||||
return 0
|
||||
if self.is_simple and other.is_simple:
|
||||
return self.nt <= other.nt
|
||||
n = max(self.longest_run_of_spaces(),
|
||||
other.longest_run_of_spaces()) + 1
|
||||
# the self.n >= other.n test already did it for ts=1
|
||||
for ts in range(2, n+1):
|
||||
if self.indent_level(ts) >= other.indent_level(ts):
|
||||
return 0
|
||||
return 1
|
||||
|
||||
# return a list of tuples (ts, i1, i2) such that
|
||||
# i1 == self.indent_level(ts) >= other.indent_level(ts) == i2.
|
||||
# Intended to be used after not self.less(other) is known, in which
|
||||
# case it will return at least one witnessing tab size.
|
||||
def not_less_witness(self, other):
|
||||
n = max(self.longest_run_of_spaces(),
|
||||
other.longest_run_of_spaces()) + 1
|
||||
a = []
|
||||
for ts in range(1, n+1):
|
||||
if self.indent_level(ts) >= other.indent_level(ts):
|
||||
a.append( (ts,
|
||||
self.indent_level(ts),
|
||||
other.indent_level(ts)) )
|
||||
return a
|
||||
|
||||
def format_witnesses(w):
|
||||
import string
|
||||
firsts = map(lambda tup: str(tup[0]), w)
|
||||
prefix = "at tab size"
|
||||
if len(w) > 1:
|
||||
prefix = prefix + "s"
|
||||
return prefix + " " + string.join(firsts, ', ')
|
||||
|
||||
# The collection of globals, the reset_globals() function, and the
|
||||
# tokeneater() function, depend on which version of tokenize is
|
||||
# in use.
|
||||
|
||||
if hasattr(tokenize, 'NL'):
|
||||
# take advantage of Guido's patch!
|
||||
|
||||
indents = []
|
||||
check_equal = 0
|
||||
|
||||
def reset_globals():
|
||||
global indents, check_equal
|
||||
check_equal = 0
|
||||
indents = [Whitespace("")]
|
||||
|
||||
def tokeneater(type, token, start, end, line,
|
||||
INDENT=tokenize.INDENT,
|
||||
DEDENT=tokenize.DEDENT,
|
||||
NEWLINE=tokenize.NEWLINE,
|
||||
JUNK=(tokenize.COMMENT, tokenize.NL) ):
|
||||
global indents, check_equal
|
||||
|
||||
if type == NEWLINE:
|
||||
# a program statement, or ENDMARKER, will eventually follow,
|
||||
# after some (possibly empty) run of tokens of the form
|
||||
# (NL | COMMENT)* (INDENT | DEDENT+)?
|
||||
# If an INDENT appears, setting check_equal is wrong, and will
|
||||
# be undone when we see the INDENT.
|
||||
check_equal = 1
|
||||
|
||||
elif type == INDENT:
|
||||
check_equal = 0
|
||||
thisguy = Whitespace(token)
|
||||
if not indents[-1].less(thisguy):
|
||||
witness = indents[-1].not_less_witness(thisguy)
|
||||
msg = "indent not greater e.g. " + format_witnesses(witness)
|
||||
raise NannyNag(start[0], msg, line)
|
||||
indents.append(thisguy)
|
||||
|
||||
elif type == DEDENT:
|
||||
# there's nothing we need to check here! what's important is
|
||||
# that when the run of DEDENTs ends, the indentation of the
|
||||
# program statement (or ENDMARKER) that triggered the run is
|
||||
# equal to what's left at the top of the indents stack
|
||||
|
||||
# Ouch! This assert triggers if the last line of the source
|
||||
# is indented *and* lacks a newline -- then DEDENTs pop out
|
||||
# of thin air.
|
||||
# assert check_equal # else no earlier NEWLINE, or an earlier INDENT
|
||||
check_equal = 1
|
||||
|
||||
del indents[-1]
|
||||
|
||||
elif check_equal and type not in JUNK:
|
||||
# this is the first "real token" following a NEWLINE, so it
|
||||
# must be the first token of the next program statement, or an
|
||||
# ENDMARKER; the "line" argument exposes the leading whitespace
|
||||
# for this statement; in the case of ENDMARKER, line is an empty
|
||||
# string, so will properly match the empty string with which the
|
||||
# "indents" stack was seeded
|
||||
check_equal = 0
|
||||
thisguy = Whitespace(line)
|
||||
if not indents[-1].equal(thisguy):
|
||||
witness = indents[-1].not_equal_witness(thisguy)
|
||||
msg = "indent not equal e.g. " + format_witnesses(witness)
|
||||
raise NannyNag(start[0], msg, line)
|
||||
|
||||
else:
|
||||
# unpatched version of tokenize
|
||||
|
||||
nesting_level = 0
|
||||
indents = []
|
||||
check_equal = 0
|
||||
|
||||
def reset_globals():
|
||||
global nesting_level, indents, check_equal
|
||||
nesting_level = check_equal = 0
|
||||
indents = [Whitespace("")]
|
||||
|
||||
def tokeneater(type, token, start, end, line,
|
||||
INDENT=tokenize.INDENT,
|
||||
DEDENT=tokenize.DEDENT,
|
||||
NEWLINE=tokenize.NEWLINE,
|
||||
COMMENT=tokenize.COMMENT,
|
||||
OP=tokenize.OP):
|
||||
global nesting_level, indents, check_equal
|
||||
|
||||
if type == INDENT:
|
||||
check_equal = 0
|
||||
thisguy = Whitespace(token)
|
||||
if not indents[-1].less(thisguy):
|
||||
witness = indents[-1].not_less_witness(thisguy)
|
||||
msg = "indent not greater e.g. " + format_witnesses(witness)
|
||||
raise NannyNag(start[0], msg, line)
|
||||
indents.append(thisguy)
|
||||
|
||||
elif type == DEDENT:
|
||||
del indents[-1]
|
||||
|
||||
elif type == NEWLINE:
|
||||
if nesting_level == 0:
|
||||
check_equal = 1
|
||||
|
||||
elif type == COMMENT:
|
||||
pass
|
||||
|
||||
elif check_equal:
|
||||
check_equal = 0
|
||||
thisguy = Whitespace(line)
|
||||
if not indents[-1].equal(thisguy):
|
||||
witness = indents[-1].not_equal_witness(thisguy)
|
||||
msg = "indent not equal e.g. " + format_witnesses(witness)
|
||||
raise NannyNag(start[0], msg, line)
|
||||
|
||||
if type == OP and token in ('{', '[', '('):
|
||||
nesting_level = nesting_level + 1
|
||||
|
||||
elif type == OP and token in ('}', ']', ')'):
|
||||
if nesting_level == 0:
|
||||
raise NannyNag(start[0],
|
||||
"unbalanced bracket '" + token + "'",
|
||||
line)
|
||||
nesting_level = nesting_level - 1
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import string
|
||||
|
||||
def f():
|
||||
a = 0
|
||||
b = 1
|
||||
c = 2
|
||||
d = 3
|
||||
e = 4
|
||||
g()
|
||||
|
||||
def g():
|
||||
h()
|
||||
|
||||
def h():
|
||||
i()
|
||||
|
||||
def i():
|
||||
j()
|
||||
|
||||
def j():
|
||||
k()
|
||||
|
||||
def k():
|
||||
l()
|
||||
|
||||
l = lambda: test()
|
||||
|
||||
def test():
|
||||
string.capwords(1)
|
||||
|
||||
f()
|
Loading…
Reference in New Issue