mirror of
https://github.com/python/cpython.git
synced 2025-07-24 03:35:53 +00:00
Checking in IDLEFORK exactly as it appears in the idlefork CVS.
On a branch, for now.
This commit is contained in:
parent
19691360c7
commit
57cd21fde2
40 changed files with 4632 additions and 1865 deletions
|
@ -1,551 +0,0 @@
|
|||
#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 = [
|
||||
('edit', [
|
||||
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>>': ['<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.
|
||||
tabwidth = self.tabwidth
|
||||
have = len(chars.expandtabs(tabwidth))
|
||||
assert have > 0
|
||||
want = ((have - 1) // self.indentwidth) * self.indentwidth
|
||||
ncharsdeleted = 0
|
||||
while 1:
|
||||
chars = chars[:-1]
|
||||
ncharsdeleted = ncharsdeleted + 1
|
||||
have = len(chars.expandtabs(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(prefix.expandtabs(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] = lines[pos].expandtabs(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 = chars.split("\n")
|
||||
return head, tail, chars, lines
|
||||
|
||||
def set_region(self, head, tail, chars, lines):
|
||||
text = self.text
|
||||
newchars = "\n".join(lines)
|
||||
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
|
36
Tools/idle/CREDITS.txt
Normal file
36
Tools/idle/CREDITS.txt
Normal file
|
@ -0,0 +1,36 @@
|
|||
IDLEfork Credits
|
||||
==================
|
||||
|
||||
Guido van Rossum, as well as being the creator of the Python language, is
|
||||
the original creator of IDLE. He also developed the RPC code and Remote
|
||||
Debugger extension used in IDLEfork.
|
||||
|
||||
The IDLEfork project was initiated and brought up to version 0.7.1 primarily
|
||||
by David Scherer, with help from Peter Schneider-Kamp and Nicholas Riley.
|
||||
Bruce Sherwood has contributed considerable time testing and suggesting
|
||||
improvements.
|
||||
|
||||
Besides Guido, the main developers who have been active on IDLEfork version
|
||||
0.8.1 and later are Stephen M. Gava, who implemented the Configuration GUI, the
|
||||
new configuration system, and the new About menu, and Kurt B. Kaiser, who
|
||||
completed the integration of the RPC and remote debugger, and made a number of
|
||||
usability enhancements.
|
||||
|
||||
Other contributors include Raymond Hettinger, Tony Lownds (Mac integration),
|
||||
Neal Norwitz (code check and clean-up), and Chui Tey (RPC integration, debugger
|
||||
integration and persistent breakpoints).
|
||||
|
||||
Hernan Foffani, Christos Georgiou, Jason Orendorff, Josh Robb, and Bruce
|
||||
Sherwood have submitted useful patches. Thanks, guys!
|
||||
|
||||
There are others who should be included here, especially those who contributed
|
||||
to IDLE versions prior to 0.8, principally Mark Hammond, Jeremy Hylton,
|
||||
Tim Peters, and Moshe Zadka. For additional details refer to NEWS.txt and
|
||||
Changelog.
|
||||
|
||||
Please contact the IDLEfork maintainer to have yourself included here if you
|
||||
are one of those we missed!
|
||||
|
||||
Contact details at http://idlefork.sourceforge.net
|
||||
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
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
|
180
Tools/idle/HISTORY.txt
Normal file
180
Tools/idle/HISTORY.txt
Normal file
|
@ -0,0 +1,180 @@
|
|||
IDLE History
|
||||
============
|
||||
|
||||
This file contains the release messages for previous IDLE releases.
|
||||
As you read on you go back to the dark ages of IDLE's history.
|
||||
|
||||
|
||||
IDLE 0.5 - February 2000 - Release Notes
|
||||
----------------------------------------
|
||||
|
||||
This is an early release of IDLE, my own attempt at a Tkinter-based
|
||||
IDE for Python.
|
||||
|
||||
(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).
|
||||
|
||||
|
||||
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.
|
||||
|
||||
======================================================================
|
51
Tools/idle/INSTALL.txt
Normal file
51
Tools/idle/INSTALL.txt
Normal file
|
@ -0,0 +1,51 @@
|
|||
IDLEfork Installation Notes
|
||||
===========================
|
||||
|
||||
IDLEfork requires Python Version 2.2 or later.
|
||||
|
||||
There are several distribution files (where xx is the subversion):
|
||||
|
||||
IDLEfork-0.9xx.win32.exe
|
||||
This is a Windows installer which will install IDLEfork in
|
||||
..../site-packages/idleforklib/ and place the idefork startup script
|
||||
at ..../scripts/idlefork. Rename this to idlefork.pyw and
|
||||
point your launcher icons at it. Installation is as idlefork
|
||||
to avoid conflict with the original Python IDLE.
|
||||
|
||||
IDLEfork-0.9xx-1.noarch.rpm
|
||||
This is an rpm which is designed to install as idleforklib in an
|
||||
existing /usr/lib/python2.2 tree. It installs as idlefork to avoid
|
||||
conflict with Python IDLE.
|
||||
|
||||
Python rpms are available at http://www.python.org/2.2.2/rpms.html and
|
||||
http://www.python.org/2.2.1/rpms.html.
|
||||
|
||||
IDLEfork-0.9xx.tar.gz
|
||||
This is a distutils sdist (source) tarfile which can be used to make
|
||||
installations on platforms not supported by the above files.
|
||||
** It remains configured to install as idlelib, not idleforklib. **
|
||||
|
||||
Unpack in ..../Tools/, cd to the IDLEfork directory created, and
|
||||
"python setup.py install" to install in ....site-packages/idlelib.
|
||||
This will overwrite the Python IDLE installation.
|
||||
|
||||
If you don't want to overwrite Python IDLE, it is also possible to
|
||||
simply call "python idle.py" to run from the IDLEfork source directory
|
||||
without making an installation. In this case, IDLE will not be on
|
||||
your PATH unless you are in the source directory. Also, it is then
|
||||
advisable to remove any Python IDLE installation by removing
|
||||
..../site-packages/idlelib so the two identically named packages don't
|
||||
conflict.
|
||||
|
||||
On Redhat Linux systems prior to 8.0, /usr/bin/python may be pointing
|
||||
at python1.5. If so, change the first line in the /usr/bin/idle
|
||||
script to read:
|
||||
!# /usr/bin/python2.2
|
||||
|
||||
See README.txt for more details on this version of IDLEfork.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
"""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()
|
||||
load(os.path.dirname(__file__))
|
50
Tools/idle/LICENSE.txt
Normal file
50
Tools/idle/LICENSE.txt
Normal file
|
@ -0,0 +1,50 @@
|
|||
To apply this license to IDLE or IDLEfork, read 'IDLE' or 'IDLEfork'
|
||||
for every occurence of 'Python 2.1.1' in the text below.
|
||||
|
||||
PSF LICENSE AGREEMENT
|
||||
---------------------
|
||||
|
||||
1. This LICENSE AGREEMENT is between the Python Software Foundation
|
||||
("PSF"), and the Individual or Organization ("Licensee") accessing and
|
||||
otherwise using Python 2.1.1 software in source or binary form and its
|
||||
associated documentation.
|
||||
|
||||
2. Subject to the terms and conditions of this License Agreement, PSF
|
||||
hereby grants Licensee a nonexclusive, royalty-free, world-wide
|
||||
license to reproduce, analyze, test, perform and/or display publicly,
|
||||
prepare derivative works, distribute, and otherwise use Python 2.1.1
|
||||
alone or in any derivative version, provided, however, that PSF's
|
||||
License Agreement and PSF's notice of copyright, i.e., "Copyright (c)
|
||||
2001 Python Software Foundation; All Rights Reserved" are retained in
|
||||
Python 2.1.1 alone or in any derivative version prepared by Licensee.
|
||||
|
||||
3. In the event Licensee prepares a derivative work that is based on
|
||||
or incorporates Python 2.1.1 or any part thereof, and wants to make
|
||||
the derivative work available to others as provided herein, then
|
||||
Licensee hereby agrees to include in any such work a brief summary of
|
||||
the changes made to Python 2.1.1.
|
||||
|
||||
4. PSF is making Python 2.1.1 available to Licensee on an "AS IS"
|
||||
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 2.1.1 WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||
2.1.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 2.1.1,
|
||||
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
6. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
7. Nothing in this License Agreement shall be deemed to create any
|
||||
relationship of agency, partnership, or joint venture between PSF and
|
||||
Licensee. This License Agreement does not grant permission to use PSF
|
||||
trademarks or trade name in a trademark sense to endorse or promote
|
||||
products or services of Licensee, or any third party.
|
||||
|
||||
8. By copying, installing or otherwise using Python 2.1.1, Licensee
|
||||
agrees to be bound by the terms and conditions of this License
|
||||
Agreement.
|
8
Tools/idle/Makefile
Normal file
8
Tools/idle/Makefile
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Makefile to build the interrupt module, which is a C extension
|
||||
|
||||
PYTHON=python2.3
|
||||
|
||||
all: interrupt.so
|
||||
|
||||
interrupt.so: interruptmodule.c
|
||||
$(PYTHON) setup.py build_ext -i
|
|
@ -1,137 +0,0 @@
|
|||
# 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.
|
||||
|
||||
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()
|
|
@ -1,275 +0,0 @@
|
|||
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 = sourceline.strip()
|
||||
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()
|
381
Tools/idle/RemoteDebugger.py
Normal file
381
Tools/idle/RemoteDebugger.py
Normal file
|
@ -0,0 +1,381 @@
|
|||
"""Support for remote Python debugging.
|
||||
|
||||
Some ASCII art to describe the structure:
|
||||
|
||||
IN PYTHON SUBPROCESS # IN IDLE PROCESS
|
||||
#
|
||||
# oid='gui_adapter'
|
||||
+----------+ # +------------+ +-----+
|
||||
| GUIProxy |--remote#call-->| GUIAdapter |--calls-->| GUI |
|
||||
+-----+--calls-->+----------+ # +------------+ +-----+
|
||||
| Idb | # /
|
||||
+-----+<-calls--+------------+ # +----------+<--calls-/
|
||||
| IdbAdapter |<--remote#call--| IdbProxy |
|
||||
+------------+ # +----------+
|
||||
oid='idb_adapter' #
|
||||
|
||||
The purpose of the Proxy and Adapter classes is to translate certain
|
||||
arguments and return values that cannot be transported through the RPC
|
||||
barrier, in particular frame and traceback objects.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import types
|
||||
import rpc
|
||||
import Debugger
|
||||
|
||||
debugging = 0
|
||||
|
||||
idb_adap_oid = "idb_adapter"
|
||||
gui_adap_oid = "gui_adapter"
|
||||
|
||||
#=======================================
|
||||
#
|
||||
# In the PYTHON subprocess:
|
||||
|
||||
frametable = {}
|
||||
dicttable = {}
|
||||
codetable = {}
|
||||
tracebacktable = {}
|
||||
|
||||
def wrap_frame(frame):
|
||||
fid = id(frame)
|
||||
frametable[fid] = frame
|
||||
return fid
|
||||
|
||||
def wrap_info(info):
|
||||
"replace info[2], a traceback instance, by its ID"
|
||||
if info is None:
|
||||
return None
|
||||
else:
|
||||
traceback = info[2]
|
||||
assert isinstance(traceback, types.TracebackType)
|
||||
traceback_id = id(traceback)
|
||||
tracebacktable[traceback_id] = traceback
|
||||
modified_info = (info[0], info[1], traceback_id)
|
||||
return modified_info
|
||||
|
||||
class GUIProxy:
|
||||
|
||||
def __init__(self, conn, gui_adap_oid):
|
||||
self.conn = conn
|
||||
self.oid = gui_adap_oid
|
||||
|
||||
def interaction(self, message, frame, info=None):
|
||||
# calls rpc.SocketIO.remotecall() via run.MyHandler instance
|
||||
# pass frame and traceback object IDs instead of the objects themselves
|
||||
self.conn.remotecall(self.oid, "interaction",
|
||||
(message, wrap_frame(frame), wrap_info(info)),
|
||||
{})
|
||||
|
||||
class IdbAdapter:
|
||||
|
||||
def __init__(self, idb):
|
||||
self.idb = idb
|
||||
|
||||
#----------called by an IdbProxy----------
|
||||
|
||||
def set_step(self):
|
||||
self.idb.set_step()
|
||||
|
||||
def set_quit(self):
|
||||
self.idb.set_quit()
|
||||
|
||||
def set_continue(self):
|
||||
self.idb.set_continue()
|
||||
|
||||
def set_next(self, fid):
|
||||
frame = frametable[fid]
|
||||
self.idb.set_next(frame)
|
||||
|
||||
def set_return(self, fid):
|
||||
frame = frametable[fid]
|
||||
self.idb.set_return(frame)
|
||||
|
||||
def get_stack(self, fid, tbid):
|
||||
##print >>sys.__stderr__, "get_stack(%s, %s)" % (`fid`, `tbid`)
|
||||
frame = frametable[fid]
|
||||
if tbid is None:
|
||||
tb = None
|
||||
else:
|
||||
tb = tracebacktable[tbid]
|
||||
stack, i = self.idb.get_stack(frame, tb)
|
||||
##print >>sys.__stderr__, "get_stack() ->", stack
|
||||
stack = [(wrap_frame(frame), k) for frame, k in stack]
|
||||
##print >>sys.__stderr__, "get_stack() ->", stack
|
||||
return stack, i
|
||||
|
||||
def run(self, cmd):
|
||||
import __main__
|
||||
self.idb.run(cmd, __main__.__dict__)
|
||||
|
||||
def set_break(self, filename, lineno):
|
||||
msg = self.idb.set_break(filename, lineno)
|
||||
return msg
|
||||
|
||||
def clear_break(self, filename, lineno):
|
||||
msg = self.idb.clear_break(filename, lineno)
|
||||
return msg
|
||||
|
||||
def clear_all_file_breaks(self, filename):
|
||||
msg = self.idb.clear_all_file_breaks(filename)
|
||||
return msg
|
||||
|
||||
#----------called by a FrameProxy----------
|
||||
|
||||
def frame_attr(self, fid, name):
|
||||
frame = frametable[fid]
|
||||
return getattr(frame, name)
|
||||
|
||||
def frame_globals(self, fid):
|
||||
frame = frametable[fid]
|
||||
dict = frame.f_globals
|
||||
did = id(dict)
|
||||
dicttable[did] = dict
|
||||
return did
|
||||
|
||||
def frame_locals(self, fid):
|
||||
frame = frametable[fid]
|
||||
dict = frame.f_locals
|
||||
did = id(dict)
|
||||
dicttable[did] = dict
|
||||
return did
|
||||
|
||||
def frame_code(self, fid):
|
||||
frame = frametable[fid]
|
||||
code = frame.f_code
|
||||
cid = id(code)
|
||||
codetable[cid] = code
|
||||
return cid
|
||||
|
||||
#----------called by a CodeProxy----------
|
||||
|
||||
def code_name(self, cid):
|
||||
code = codetable[cid]
|
||||
return code.co_name
|
||||
|
||||
def code_filename(self, cid):
|
||||
code = codetable[cid]
|
||||
return code.co_filename
|
||||
|
||||
#----------called by a DictProxy----------
|
||||
|
||||
def dict_keys(self, did):
|
||||
dict = dicttable[did]
|
||||
return dict.keys()
|
||||
|
||||
def dict_item(self, did, key):
|
||||
dict = dicttable[did]
|
||||
value = dict[key]
|
||||
value = repr(value)
|
||||
return value
|
||||
|
||||
#----------end class IdbAdapter----------
|
||||
|
||||
|
||||
def start_debugger(rpchandler, gui_adap_oid):
|
||||
"""Start the debugger and its RPC link in the Python subprocess
|
||||
|
||||
Start the subprocess side of the split debugger and set up that side of the
|
||||
RPC link by instantiating the GUIProxy, Idb debugger, and IdbAdapter
|
||||
objects and linking them together. Register the IdbAdapter with the
|
||||
RPCServer to handle RPC requests from the split debugger GUI via the
|
||||
IdbProxy.
|
||||
|
||||
"""
|
||||
gui_proxy = GUIProxy(rpchandler, gui_adap_oid)
|
||||
idb = Debugger.Idb(gui_proxy)
|
||||
idb_adap = IdbAdapter(idb)
|
||||
rpchandler.register(idb_adap_oid, idb_adap)
|
||||
return idb_adap_oid
|
||||
|
||||
|
||||
#=======================================
|
||||
#
|
||||
# In the IDLE process:
|
||||
|
||||
|
||||
class FrameProxy:
|
||||
|
||||
def __init__(self, conn, fid):
|
||||
self._conn = conn
|
||||
self._fid = fid
|
||||
self._oid = "idb_adapter"
|
||||
self._dictcache = {}
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name[:1] == "_":
|
||||
raise AttributeError, name
|
||||
if name == "f_code":
|
||||
return self._get_f_code()
|
||||
if name == "f_globals":
|
||||
return self._get_f_globals()
|
||||
if name == "f_locals":
|
||||
return self._get_f_locals()
|
||||
return self._conn.remotecall(self._oid, "frame_attr",
|
||||
(self._fid, name), {})
|
||||
|
||||
def _get_f_code(self):
|
||||
cid = self._conn.remotecall(self._oid, "frame_code", (self._fid,), {})
|
||||
return CodeProxy(self._conn, self._oid, cid)
|
||||
|
||||
def _get_f_globals(self):
|
||||
did = self._conn.remotecall(self._oid, "frame_globals",
|
||||
(self._fid,), {})
|
||||
return self._get_dict_proxy(did)
|
||||
|
||||
def _get_f_locals(self):
|
||||
did = self._conn.remotecall(self._oid, "frame_locals",
|
||||
(self._fid,), {})
|
||||
return self._get_dict_proxy(did)
|
||||
|
||||
def _get_dict_proxy(self, did):
|
||||
if self._dictcache.has_key(did):
|
||||
return self._dictcache[did]
|
||||
dp = DictProxy(self._conn, self._oid, did)
|
||||
self._dictcache[did] = dp
|
||||
return dp
|
||||
|
||||
|
||||
class CodeProxy:
|
||||
|
||||
def __init__(self, conn, oid, cid):
|
||||
self._conn = conn
|
||||
self._oid = oid
|
||||
self._cid = cid
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name == "co_name":
|
||||
return self._conn.remotecall(self._oid, "code_name",
|
||||
(self._cid,), {})
|
||||
if name == "co_filename":
|
||||
return self._conn.remotecall(self._oid, "code_filename",
|
||||
(self._cid,), {})
|
||||
|
||||
|
||||
class DictProxy:
|
||||
|
||||
def __init__(self, conn, oid, did):
|
||||
self._conn = conn
|
||||
self._oid = oid
|
||||
self._did = did
|
||||
|
||||
def keys(self):
|
||||
return self._conn.remotecall(self._oid, "dict_keys", (self._did,), {})
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._conn.remotecall(self._oid, "dict_item",
|
||||
(self._did, key), {})
|
||||
|
||||
def __getattr__(self, name):
|
||||
##print >>sys.__stderr__, "failed DictProxy.__getattr__:", name
|
||||
raise AttributeError, name
|
||||
|
||||
|
||||
class GUIAdapter:
|
||||
|
||||
def __init__(self, conn, gui):
|
||||
self.conn = conn
|
||||
self.gui = gui
|
||||
|
||||
def interaction(self, message, fid, modified_info):
|
||||
##print "interaction: (%s, %s, %s)" % (message, fid, modified_info)
|
||||
frame = FrameProxy(self.conn, fid)
|
||||
self.gui.interaction(message, frame, modified_info)
|
||||
|
||||
|
||||
class IdbProxy:
|
||||
|
||||
def __init__(self, conn, shell, oid):
|
||||
self.oid = oid
|
||||
self.conn = conn
|
||||
self.shell = shell
|
||||
|
||||
def call(self, methodname, *args, **kwargs):
|
||||
##print "**IdbProxy.call %s %s %s" % (methodname, args, kwargs)
|
||||
value = self.conn.remotecall(self.oid, methodname, args, kwargs)
|
||||
##print "**IdbProxy.call %s returns %s" % (methodname, `value`)
|
||||
return value
|
||||
|
||||
def run(self, cmd, locals):
|
||||
# Ignores locals on purpose!
|
||||
seq = self.conn.asynccall(self.oid, "run", (cmd,), {})
|
||||
self.shell.interp.active_seq = seq
|
||||
|
||||
def get_stack(self, frame, tbid):
|
||||
# passing frame and traceback IDs, not the objects themselves
|
||||
stack, i = self.call("get_stack", frame._fid, tbid)
|
||||
stack = [(FrameProxy(self.conn, fid), k) for fid, k in stack]
|
||||
return stack, i
|
||||
|
||||
def set_continue(self):
|
||||
self.call("set_continue")
|
||||
|
||||
def set_step(self):
|
||||
self.call("set_step")
|
||||
|
||||
def set_next(self, frame):
|
||||
self.call("set_next", frame._fid)
|
||||
|
||||
def set_return(self, frame):
|
||||
self.call("set_return", frame._fid)
|
||||
|
||||
def set_quit(self):
|
||||
self.call("set_quit")
|
||||
|
||||
def set_break(self, filename, lineno):
|
||||
msg = self.call("set_break", filename, lineno)
|
||||
return msg
|
||||
|
||||
def clear_break(self, filename, lineno):
|
||||
msg = self.call("clear_break", filename, lineno)
|
||||
return msg
|
||||
|
||||
def clear_all_file_breaks(self, filename):
|
||||
msg = self.call("clear_all_file_breaks", filename)
|
||||
return msg
|
||||
|
||||
def start_remote_debugger(rpcclt, pyshell):
|
||||
"""Start the subprocess debugger, initialize the debugger GUI and RPC link
|
||||
|
||||
Request the RPCServer start the Python subprocess debugger and link. Set
|
||||
up the Idle side of the split debugger by instantiating the IdbProxy,
|
||||
debugger GUI, and debugger GUIAdapter objects and linking them together.
|
||||
|
||||
Register the GUIAdapter with the RPCClient to handle debugger GUI
|
||||
interaction requests coming from the subprocess debugger via the GUIProxy.
|
||||
|
||||
The IdbAdapter will pass execution and environment requests coming from the
|
||||
Idle debugger GUI to the subprocess debugger via the IdbProxy.
|
||||
|
||||
"""
|
||||
global idb_adap_oid
|
||||
|
||||
idb_adap_oid = rpcclt.remotecall("exec", "start_the_debugger",\
|
||||
(gui_adap_oid,), {})
|
||||
idb_proxy = IdbProxy(rpcclt, pyshell, idb_adap_oid)
|
||||
gui = Debugger.Debugger(pyshell, idb_proxy)
|
||||
gui_adap = GUIAdapter(rpcclt, gui)
|
||||
rpcclt.register(gui_adap_oid, gui_adap)
|
||||
return gui
|
||||
|
||||
def close_remote_debugger(rpcclt):
|
||||
"""Shut down subprocess debugger and Idle side of debugger RPC link
|
||||
|
||||
Request that the RPCServer shut down the subprocess debugger and link.
|
||||
Unregister the GUIAdapter, which will cause a GC on the Idle process
|
||||
debugger and RPC link objects. (The second reference to the debugger GUI
|
||||
is deleted in PyShell.close_remote_debugger().)
|
||||
|
||||
"""
|
||||
close_subprocess_debugger(rpcclt)
|
||||
rpcclt.unregister(gui_adap_oid)
|
||||
|
||||
def close_subprocess_debugger(rpcclt):
|
||||
rpcclt.remotecall("exec", "stop_the_debugger", (idb_adap_oid,), {})
|
||||
|
||||
def restart_subprocess_debugger(rpcclt):
|
||||
idb_adap_oid_ret = rpcclt.remotecall("exec", "start_the_debugger",\
|
||||
(gui_adap_oid,), {})
|
||||
assert idb_adap_oid_ret == idb_adap_oid, 'Idb restarted with different oid'
|
|
@ -1,341 +0,0 @@
|
|||
import select
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
import types
|
||||
|
||||
VERBOSE = None
|
||||
|
||||
class SocketProtocol:
|
||||
"""A simple protocol for sending strings across a socket"""
|
||||
BUF_SIZE = 8192
|
||||
|
||||
def __init__(self, sock):
|
||||
self.sock = sock
|
||||
self._buffer = ''
|
||||
self._closed = 0
|
||||
|
||||
def close(self):
|
||||
self._closed = 1
|
||||
self.sock.close()
|
||||
|
||||
def send(self, buf):
|
||||
"""Encode buf and write it on the socket"""
|
||||
if VERBOSE:
|
||||
VERBOSE.write('send %d:%s\n' % (len(buf), `buf`))
|
||||
self.sock.send('%d:%s' % (len(buf), buf))
|
||||
|
||||
def receive(self, timeout=0):
|
||||
"""Get next complete string from socket or return None
|
||||
|
||||
Raise EOFError on EOF
|
||||
"""
|
||||
buf = self._read_from_buffer()
|
||||
if buf is not None:
|
||||
return buf
|
||||
recvbuf = self._read_from_socket(timeout)
|
||||
if recvbuf is None:
|
||||
return None
|
||||
if recvbuf == '' and self._buffer == '':
|
||||
raise EOFError
|
||||
if VERBOSE:
|
||||
VERBOSE.write('recv %s\n' % `recvbuf`)
|
||||
self._buffer = self._buffer + recvbuf
|
||||
r = self._read_from_buffer()
|
||||
return r
|
||||
|
||||
def _read_from_socket(self, timeout):
|
||||
"""Does not block"""
|
||||
if self._closed:
|
||||
return ''
|
||||
if timeout is not None:
|
||||
r, w, x = select.select([self.sock], [], [], timeout)
|
||||
if timeout is None or r:
|
||||
return self.sock.recv(self.BUF_SIZE)
|
||||
else:
|
||||
return None
|
||||
|
||||
def _read_from_buffer(self):
|
||||
buf = self._buffer
|
||||
i = buf.find(':')
|
||||
if i == -1:
|
||||
return None
|
||||
buflen = int(buf[:i])
|
||||
enclen = i + 1 + buflen
|
||||
if len(buf) >= enclen:
|
||||
s = buf[i+1:enclen]
|
||||
self._buffer = buf[enclen:]
|
||||
return s
|
||||
else:
|
||||
self._buffer = buf
|
||||
return None
|
||||
|
||||
# helpers for registerHandler method below
|
||||
|
||||
def get_methods(obj):
|
||||
methods = []
|
||||
for name in dir(obj):
|
||||
attr = getattr(obj, name)
|
||||
if callable(attr):
|
||||
methods.append(name)
|
||||
if type(obj) == types.InstanceType:
|
||||
methods = methods + get_methods(obj.__class__)
|
||||
if type(obj) == types.ClassType:
|
||||
for super in obj.__bases__:
|
||||
methods = methods + get_methods(super)
|
||||
return methods
|
||||
|
||||
class CommandProtocol:
|
||||
def __init__(self, sockp):
|
||||
self.sockp = sockp
|
||||
self.seqno = 0
|
||||
self.handlers = {}
|
||||
|
||||
def close(self):
|
||||
self.sockp.close()
|
||||
self.handlers.clear()
|
||||
|
||||
def registerHandler(self, handler):
|
||||
"""A Handler is an object with handle_XXX methods"""
|
||||
for methname in get_methods(handler):
|
||||
if methname[:7] == "handle_":
|
||||
name = methname[7:]
|
||||
self.handlers[name] = getattr(handler, methname)
|
||||
|
||||
def send(self, cmd, arg='', seqno=None):
|
||||
if arg:
|
||||
msg = "%s %s" % (cmd, arg)
|
||||
else:
|
||||
msg = cmd
|
||||
if seqno is None:
|
||||
seqno = self.get_seqno()
|
||||
msgbuf = self.encode_seqno(seqno) + msg
|
||||
self.sockp.send(msgbuf)
|
||||
if cmd == "reply":
|
||||
return
|
||||
reply = self.sockp.receive(timeout=None)
|
||||
r_cmd, r_arg, r_seqno = self._decode_msg(reply)
|
||||
assert r_seqno == seqno and r_cmd == "reply", "bad reply"
|
||||
return r_arg
|
||||
|
||||
def _decode_msg(self, msg):
|
||||
seqno = self.decode_seqno(msg[:self.SEQNO_ENC_LEN])
|
||||
msg = msg[self.SEQNO_ENC_LEN:]
|
||||
parts = msg.split(" ", 2)
|
||||
if len(parts) == 1:
|
||||
cmd = msg
|
||||
arg = ''
|
||||
else:
|
||||
cmd = parts[0]
|
||||
arg = parts[1]
|
||||
return cmd, arg, seqno
|
||||
|
||||
def dispatch(self):
|
||||
msg = self.sockp.receive()
|
||||
if msg is None:
|
||||
return
|
||||
cmd, arg, seqno = self._decode_msg(msg)
|
||||
self._current_reply = seqno
|
||||
h = self.handlers.get(cmd, self.default_handler)
|
||||
try:
|
||||
r = h(arg)
|
||||
except TypeError, msg:
|
||||
raise TypeError, "handle_%s: %s" % (cmd, msg)
|
||||
if self._current_reply is None:
|
||||
if r is not None:
|
||||
sys.stderr.write("ignoring %s return value type %s\n" % \
|
||||
(cmd, type(r).__name__))
|
||||
return
|
||||
if r is None:
|
||||
r = ''
|
||||
if type(r) != types.StringType:
|
||||
raise ValueError, "invalid return type for %s" % cmd
|
||||
self.send("reply", r, seqno=seqno)
|
||||
|
||||
def reply(self, arg=''):
|
||||
"""Send a reply immediately
|
||||
|
||||
otherwise reply will be sent when handler returns
|
||||
"""
|
||||
self.send("reply", arg, self._current_reply)
|
||||
self._current_reply = None
|
||||
|
||||
def default_handler(self, arg):
|
||||
sys.stderr.write("WARNING: unhandled message %s\n" % arg)
|
||||
return ''
|
||||
|
||||
SEQNO_ENC_LEN = 4
|
||||
|
||||
def get_seqno(self):
|
||||
seqno = self.seqno
|
||||
self.seqno = seqno + 1
|
||||
return seqno
|
||||
|
||||
def encode_seqno(self, seqno):
|
||||
return struct.pack("I", seqno)
|
||||
|
||||
def decode_seqno(self, buf):
|
||||
return struct.unpack("I", buf)[0]
|
||||
|
||||
|
||||
class StdioRedirector:
|
||||
"""Redirect sys.std{in,out,err} to a set of file-like objects"""
|
||||
|
||||
def __init__(self, stdin, stdout, stderr):
|
||||
self.stdin = stdin
|
||||
self.stdout = stdout
|
||||
self.stderr = stderr
|
||||
|
||||
def redirect(self):
|
||||
self.save()
|
||||
sys.stdin = self.stdin
|
||||
sys.stdout = self.stdout
|
||||
sys.stderr = self.stderr
|
||||
|
||||
def save(self):
|
||||
self._stdin = sys.stdin
|
||||
self._stdout = sys.stdout
|
||||
self._stderr = sys.stderr
|
||||
|
||||
def restore(self):
|
||||
sys.stdin = self._stdin
|
||||
sys.stdout = self._stdout
|
||||
sys.stderr = self._stderr
|
||||
|
||||
class IOWrapper:
|
||||
"""Send output from a file-like object across a SocketProtocol
|
||||
|
||||
XXX Should this be more tightly integrated with the CommandProtocol?
|
||||
"""
|
||||
|
||||
def __init__(self, name, cmdp):
|
||||
self.name = name
|
||||
self.cmdp = cmdp
|
||||
self.buffer = []
|
||||
|
||||
class InputWrapper(IOWrapper):
|
||||
def write(self, buf):
|
||||
# XXX what should this do on Windows?
|
||||
raise IOError, (9, '[Errno 9] Bad file descriptor')
|
||||
|
||||
def read(self, arg=None):
|
||||
if arg is not None:
|
||||
if arg <= 0:
|
||||
return ''
|
||||
else:
|
||||
arg = 0
|
||||
return self.cmdp.send(self.name, "read,%s" % arg)
|
||||
|
||||
def readline(self):
|
||||
return self.cmdp.send(self.name, "readline")
|
||||
|
||||
class OutputWrapper(IOWrapper):
|
||||
def write(self, buf):
|
||||
self.cmdp.send(self.name, buf)
|
||||
|
||||
def read(self, arg=None):
|
||||
return ''
|
||||
|
||||
class RemoteInterp:
|
||||
def __init__(self, sock):
|
||||
self._sock = SocketProtocol(sock)
|
||||
self._cmd = CommandProtocol(self._sock)
|
||||
self._cmd.registerHandler(self)
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
while 1:
|
||||
self._cmd.dispatch()
|
||||
except EOFError:
|
||||
pass
|
||||
|
||||
def handle_execfile(self, arg):
|
||||
self._cmd.reply()
|
||||
io = StdioRedirector(InputWrapper("stdin", self._cmd),
|
||||
OutputWrapper("stdout", self._cmd),
|
||||
OutputWrapper("stderr", self._cmd))
|
||||
io.redirect()
|
||||
execfile(arg, {'__name__':'__main__'})
|
||||
io.restore()
|
||||
self._cmd.send("terminated")
|
||||
|
||||
def handle_quit(self, arg):
|
||||
self._cmd.reply()
|
||||
self._cmd.close()
|
||||
|
||||
def startRemoteInterp(id):
|
||||
import os
|
||||
# UNIX domain sockets are simpler for starters
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.bind("/var/tmp/ri.%s" % id)
|
||||
try:
|
||||
sock.listen(1)
|
||||
cli, addr = sock.accept()
|
||||
rinterp = RemoteInterp(cli)
|
||||
rinterp.run()
|
||||
finally:
|
||||
os.unlink("/var/tmp/ri.%s" % id)
|
||||
|
||||
class RIClient:
|
||||
"""Client of the remote interpreter"""
|
||||
def __init__(self, sock):
|
||||
self._sock = SocketProtocol(sock)
|
||||
self._cmd = CommandProtocol(self._sock)
|
||||
self._cmd.registerHandler(self)
|
||||
|
||||
def execfile(self, file):
|
||||
self._cmd.send("execfile", file)
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
while 1:
|
||||
self._cmd.dispatch()
|
||||
except EOFError:
|
||||
pass
|
||||
|
||||
def handle_stdout(self, buf):
|
||||
sys.stdout.write(buf)
|
||||
## sys.stdout.flush()
|
||||
|
||||
def handle_stderr(self, buf):
|
||||
sys.stderr.write(buf)
|
||||
|
||||
def handle_stdin(self, arg):
|
||||
if arg == "readline":
|
||||
return sys.stdin.readline()
|
||||
i = arg.find(",") + 1
|
||||
bytes = int(arg[i:])
|
||||
if bytes == 0:
|
||||
return sys.stdin.read()
|
||||
else:
|
||||
return sys.stdin.read(bytes)
|
||||
|
||||
def handle_terminated(self, arg):
|
||||
self._cmd.reply()
|
||||
self._cmd.send("quit")
|
||||
self._cmd.close()
|
||||
|
||||
def riExec(id, file):
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.connect("/var/tmp/ri.%s" % id)
|
||||
cli = RIClient(sock)
|
||||
cli.execfile(file)
|
||||
cli.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
import getopt
|
||||
|
||||
SERVER = 1
|
||||
opts, args = getopt.getopt(sys.argv[1:], 'cv')
|
||||
for o, v in opts:
|
||||
if o == '-c':
|
||||
SERVER = 0
|
||||
elif o == '-v':
|
||||
VERBOSE = sys.stderr
|
||||
id = args[0]
|
||||
|
||||
if SERVER:
|
||||
startRemoteInterp(id)
|
||||
else:
|
||||
file = args[1]
|
||||
riExec(id, file)
|
36
Tools/idle/RemoteObjectBrowser.py
Normal file
36
Tools/idle/RemoteObjectBrowser.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
import rpc
|
||||
|
||||
def remote_object_tree_item(item):
|
||||
wrapper = WrappedObjectTreeItem(item)
|
||||
oid = id(wrapper)
|
||||
rpc.objecttable[oid] = wrapper
|
||||
return oid
|
||||
|
||||
class WrappedObjectTreeItem:
|
||||
# Lives in PYTHON subprocess
|
||||
|
||||
def __init__(self, item):
|
||||
self.__item = item
|
||||
|
||||
def __getattr__(self, name):
|
||||
value = getattr(self.__item, name)
|
||||
return value
|
||||
|
||||
def _GetSubList(self):
|
||||
list = self.__item._GetSubList()
|
||||
return map(remote_object_tree_item, list)
|
||||
|
||||
class StubObjectTreeItem:
|
||||
# Lives in IDLE process
|
||||
|
||||
def __init__(self, sockio, oid):
|
||||
self.sockio = sockio
|
||||
self.oid = oid
|
||||
|
||||
def __getattr__(self, name):
|
||||
value = rpc.MethodProxy(self.sockio, self.oid, name)
|
||||
return value
|
||||
|
||||
def _GetSubList(self):
|
||||
list = self.sockio.remotecall(self.oid, "_GetSubList", (), {})
|
||||
return [StubObjectTreeItem(self.sockio, oid) for oid in list]
|
|
@ -1,97 +0,0 @@
|
|||
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")
|
|
@ -1,92 +0,0 @@
|
|||
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()
|
126
Tools/idle/aboutDialog.py
Normal file
126
Tools/idle/aboutDialog.py
Normal file
|
@ -0,0 +1,126 @@
|
|||
"""
|
||||
about box for idle
|
||||
"""
|
||||
|
||||
from Tkinter import *
|
||||
import string, os
|
||||
import textView
|
||||
import idlever
|
||||
|
||||
class AboutDialog(Toplevel):
|
||||
"""
|
||||
modal about dialog for idle
|
||||
"""
|
||||
def __init__(self,parent,title):
|
||||
Toplevel.__init__(self, parent)
|
||||
self.configure(borderwidth=5)
|
||||
self.geometry("+%d+%d" % (parent.winfo_rootx()+30,
|
||||
parent.winfo_rooty()+30))
|
||||
self.bg="#707070"
|
||||
self.fg="#ffffff"
|
||||
|
||||
self.CreateWidgets()
|
||||
self.resizable(height=FALSE,width=FALSE)
|
||||
self.title(title)
|
||||
self.transient(parent)
|
||||
self.grab_set()
|
||||
self.protocol("WM_DELETE_WINDOW", self.Ok)
|
||||
self.parent = parent
|
||||
self.buttonOk.focus_set()
|
||||
#key bindings for this dialog
|
||||
self.bind('<Alt-c>',self.CreditsButtonBinding) #credits button
|
||||
self.bind('<Alt-l>',self.LicenseButtonBinding) #license button
|
||||
self.bind('<Return>',self.Ok) #dismiss dialog
|
||||
self.bind('<Escape>',self.Ok) #dismiss dialog
|
||||
self.wait_window()
|
||||
|
||||
def CreateWidgets(self):
|
||||
frameMain = Frame(self,borderwidth=2,relief=SUNKEN)
|
||||
frameButtons = Frame(self)
|
||||
frameButtons.pack(side=BOTTOM,fill=X)
|
||||
frameMain.pack(side=TOP,expand=TRUE,fill=BOTH)
|
||||
self.buttonOk = Button(frameButtons,text='Ok',
|
||||
command=self.Ok)#,default=ACTIVE
|
||||
self.buttonOk.pack(padx=5,pady=5)
|
||||
#self.picture = Image('photo',data=self.pictureData)
|
||||
frameBg = Frame(frameMain,bg=self.bg)
|
||||
frameBg.pack(expand=TRUE,fill=BOTH)
|
||||
labelTitle = Label(frameBg,text='IDLEfork',fg=self.fg,bg=self.bg,
|
||||
font=('courier', 24, 'bold'))
|
||||
labelTitle.grid(row=0,column=0,sticky=W,padx=10,pady=10)
|
||||
#labelPicture = Label(frameBg,text='[picture]')
|
||||
#image=self.picture,bg=self.bg)
|
||||
#labelPicture.grid(row=0,column=1,sticky=W,rowspan=2,padx=0,pady=3)
|
||||
labelVersion = Label(frameBg,text='version '+idlever.IDLE_VERSION,
|
||||
fg=self.fg,bg=self.bg)
|
||||
labelVersion.grid(row=1,column=0,sticky=W,padx=10,pady=5)
|
||||
labelDesc = Label(frameBg,
|
||||
text="A development version of Python's lightweight\n"+
|
||||
'Integrated DeveLopment Environment, IDLE.',
|
||||
justify=LEFT,fg=self.fg,bg=self.bg)
|
||||
labelDesc.grid(row=2,column=0,sticky=W,columnspan=3,padx=10,pady=5)
|
||||
labelCopyright = Label(frameBg,
|
||||
text="Copyright (c) 2001 Python Software Foundation;\nAll Rights Reserved",
|
||||
justify=LEFT,fg=self.fg,bg=self.bg)
|
||||
labelCopyright.grid(row=3,column=0,sticky=W,columnspan=3,padx=10,pady=5)
|
||||
labelLicense = Label(frameBg,
|
||||
text='Released under the Python 2.1.1 PSF Licence',
|
||||
justify=LEFT,fg=self.fg,bg=self.bg)
|
||||
labelLicense.grid(row=4,column=0,sticky=W,columnspan=3,padx=10,pady=5)
|
||||
Frame(frameBg,height=5,bg=self.bg).grid(row=5,column=0)
|
||||
labelEmail = Label(frameBg,text='email: idle-dev@python.org',
|
||||
justify=LEFT,fg=self.fg,bg=self.bg)
|
||||
labelEmail.grid(row=6,column=0,columnspan=2,sticky=W,padx=10,pady=0)
|
||||
labelWWW = Label(frameBg,text='www: http://idlefork.sourceforge.net',
|
||||
justify=LEFT,fg=self.fg,bg=self.bg)
|
||||
labelWWW.grid(row=7,column=0,columnspan=2,sticky=W,padx=10,pady=0)
|
||||
Frame(frameBg,borderwidth=1,relief=SUNKEN,
|
||||
height=2,bg=self.bg).grid(row=8,column=0,sticky=EW,
|
||||
columnspan=3, padx=5, pady=5)
|
||||
labelPythonVer = Label(frameBg,text='Python version: '+
|
||||
sys.version.split()[0],fg=self.fg,bg=self.bg)
|
||||
labelPythonVer.grid(row=9,column=0,sticky=W,padx=10,pady=0)
|
||||
#handle weird tk version num in windoze python >= 1.6 (?!?)
|
||||
tkVer = `TkVersion`.split('.')
|
||||
tkVer[len(tkVer)-1] = str('%.3g' % (float('.'+tkVer[len(tkVer)-1])))[2:]
|
||||
if tkVer[len(tkVer)-1] == '':
|
||||
tkVer[len(tkVer)-1] = '0'
|
||||
tkVer = string.join(tkVer,'.')
|
||||
labelTkVer = Label(frameBg,text='Tk version: '+
|
||||
tkVer,fg=self.fg,bg=self.bg)
|
||||
labelTkVer.grid(row=9,column=1,sticky=W,padx=2,pady=0)
|
||||
|
||||
self.buttonLicense = Button(frameBg,text='View License',underline=5,
|
||||
width=14,highlightbackground=self.bg,command=self.ShowLicense)#takefocus=FALSE
|
||||
self.buttonLicense.grid(row=10,column=0,sticky=W,padx=10,pady=10)
|
||||
self.buttonCredits = Button(frameBg,text='View Credits',underline=5,
|
||||
width=14,highlightbackground=self.bg,command=self.ShowCredits)#takefocus=FALSE
|
||||
self.buttonCredits.grid(row=10,column=1,columnspan=2,sticky=E,padx=10,pady=10)
|
||||
|
||||
def CreditsButtonBinding(self,event):
|
||||
self.buttonCredits.invoke()
|
||||
|
||||
def LicenseButtonBinding(self,event):
|
||||
self.buttonLicense.invoke()
|
||||
|
||||
def ShowLicense(self):
|
||||
self.ViewFile('About - License','LICENSE.txt')
|
||||
|
||||
def ShowCredits(self):
|
||||
self.ViewFile('About - Credits','CREDITS.txt')
|
||||
|
||||
def ViewFile(self,viewTitle,viewFile):
|
||||
fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),viewFile)
|
||||
textView.TextViewer(self,viewTitle,fn)
|
||||
|
||||
def Ok(self, event=None):
|
||||
self.destroy()
|
||||
|
||||
if __name__ == '__main__':
|
||||
#test the dialog
|
||||
root=Tk()
|
||||
def run():
|
||||
import aboutDialog
|
||||
aboutDialog.AboutDialog(root,'About')
|
||||
Button(root,text='Dialog',command=run).pack()
|
||||
root.mainloop()
|
9
Tools/idle/boolcheck.py
Normal file
9
Tools/idle/boolcheck.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
"boolcheck - import this module to ensure True, False, bool() builtins exist."
|
||||
try:
|
||||
True
|
||||
except NameError:
|
||||
import __builtin__
|
||||
__builtin__.True = 1
|
||||
__builtin__.False = 0
|
||||
from operator import truth
|
||||
__builtin__.bool = truth
|
54
Tools/idle/config-extensions.def
Normal file
54
Tools/idle/config-extensions.def
Normal file
|
@ -0,0 +1,54 @@
|
|||
# IDLE reads several config files to determine user preferences. This
|
||||
# file is the default config file for idle extensions settings.
|
||||
#
|
||||
# Each extension must have at least one section, named after the extension
|
||||
# module. This section must contain an 'enable' item (=1 to enable the
|
||||
# extension, =0 to disable it) and also contains any other general
|
||||
# configuration items for the extension. Each extension may also define up to
|
||||
# two optional sections named ExtensionName_bindings and
|
||||
# ExtensionName_cfgBindings. If present, ExtensionName_bindings defines virtual
|
||||
# event bindings for the extension that are not sensibly re-configurable. If
|
||||
# present, ExtensionName_cfgBindings defines virtual event bindings for the
|
||||
# extension that may be sensibly re-configured.
|
||||
|
||||
# See config-keys.def for notes on specifying keys.
|
||||
|
||||
[FormatParagraph]
|
||||
enable=1
|
||||
[FormatParagraph_cfgBindings]
|
||||
format-paragraph=<Alt-Key-q>
|
||||
|
||||
[AutoExpand]
|
||||
enable=1
|
||||
[AutoExpand_cfgBindings]
|
||||
expand-word=<Alt-Key-slash>
|
||||
|
||||
[ZoomHeight]
|
||||
enable=1
|
||||
[ZoomHeight_cfgBindings]
|
||||
zoom-height=<Alt-Key-2>
|
||||
|
||||
[ScriptBinding]
|
||||
enable=1
|
||||
[ScriptBinding_cfgBindings]
|
||||
run-module=<Key-F5>
|
||||
check-module=<Alt-Key-x>
|
||||
|
||||
[CallTips]
|
||||
enable=1
|
||||
[CallTips_bindings]
|
||||
paren-open=<Key-parenleft>
|
||||
paren-close=<Key-parenright>
|
||||
check-calltip-cancel=<KeyRelease>
|
||||
calltip-cancel=<ButtonPress> <Key-Escape>
|
||||
|
||||
[ParenMatch]
|
||||
enable=0
|
||||
style= expression
|
||||
flash-delay= 500
|
||||
bell= 1
|
||||
hilite-foreground= black
|
||||
hilite-background= #43cd80
|
||||
[ParenMatch_bindings]
|
||||
flash-open-paren=<KeyRelease-parenright> <KeyRelease-bracketright> <KeyRelease-braceright>
|
||||
check-restore=<KeyPress>
|
60
Tools/idle/config-highlight.def
Normal file
60
Tools/idle/config-highlight.def
Normal file
|
@ -0,0 +1,60 @@
|
|||
# IDLE reads several config files to determine user preferences. This
|
||||
# file is the default config file for idle highlight theme settings.
|
||||
|
||||
[IDLE Classic]
|
||||
normal-foreground= #000000
|
||||
normal-background= #ffffff
|
||||
keyword-foreground= #ff7700
|
||||
keyword-background= #ffffff
|
||||
comment-foreground= #dd0000
|
||||
comment-background= #ffffff
|
||||
string-foreground= #00aa00
|
||||
string-background= #ffffff
|
||||
definition-foreground= #0000ff
|
||||
definition-background= #ffffff
|
||||
hilite-foreground= #000000
|
||||
hilite-background= gray
|
||||
break-foreground= black
|
||||
break-background= #ffff55
|
||||
hit-foreground= #ffffff
|
||||
hit-background= #000000
|
||||
error-foreground= #000000
|
||||
error-background= #ff7777
|
||||
#cursor (only foreground can be set)
|
||||
cursor-foreground= black
|
||||
#shell window
|
||||
stdout-foreground= blue
|
||||
stdout-background= #ffffff
|
||||
stderr-foreground= red
|
||||
stderr-background= #ffffff
|
||||
console-foreground= #770000
|
||||
console-background= #ffffff
|
||||
|
||||
[IDLE New]
|
||||
normal-foreground= #000000
|
||||
normal-background= #ffffff
|
||||
keyword-foreground= #ff7700
|
||||
keyword-background= #ffffff
|
||||
comment-foreground= #dd0000
|
||||
comment-background= #ffffff
|
||||
string-foreground= #00aa00
|
||||
string-background= #ffffff
|
||||
definition-foreground= #0000ff
|
||||
definition-background= #ffffff
|
||||
hilite-foreground= #000000
|
||||
hilite-background= gray
|
||||
break-foreground= black
|
||||
break-background= #ffff55
|
||||
hit-foreground= #ffffff
|
||||
hit-background= #000000
|
||||
error-foreground= #000000
|
||||
error-background= #ff7777
|
||||
#cursor (only foreground can be set)
|
||||
cursor-foreground= black
|
||||
#shell window
|
||||
stdout-foreground= blue
|
||||
stdout-background= #ffffff
|
||||
stderr-foreground= red
|
||||
stderr-background= #ffffff
|
||||
console-foreground= #770000
|
||||
console-background= #ffffff
|
155
Tools/idle/config-keys.def
Normal file
155
Tools/idle/config-keys.def
Normal file
|
@ -0,0 +1,155 @@
|
|||
# IDLE reads several config files to determine user preferences. This
|
||||
# file is the default config file for idle key binding settings.
|
||||
# Where multiple keys are specified for an action: if they are separated
|
||||
# by a space (eg. action=<key1> <key2>) then the keys are altenatives, if
|
||||
# there is no space (eg. action=<key1><key2>) then the keys comprise a
|
||||
# single 'emacs style' multi-keystoke binding. The tk event specifier 'Key'
|
||||
# is used in all cases, for consistency in auto key conflict checking in the
|
||||
# configuration gui.
|
||||
|
||||
[IDLE Classic Windows]
|
||||
copy=<Control-Key-c>
|
||||
cut=<Control-Key-x>
|
||||
paste=<Control-Key-v>
|
||||
beginning-of-line= <Key-Home>
|
||||
center-insert=<Control-Key-l>
|
||||
close-all-windows=<Control-Key-q>
|
||||
close-window=<Alt-Key-F4> <Meta-Key-F4>
|
||||
do-nothing=<Control-Key-F12>
|
||||
end-of-file=<Control-Key-d>
|
||||
python-docs=<Key-F1>
|
||||
python-context-help=<Shift-Key-F1>
|
||||
history-next=<Alt-Key-n> <Meta-Key-n>
|
||||
history-previous=<Alt-Key-p> <Meta-Key-p>
|
||||
interrupt-execution=<Control-Key-c>
|
||||
view-restart=<Key-F6>
|
||||
restart-shell=<Control-Key-F6>
|
||||
open-class-browser=<Alt-Key-c> <Meta-Key-c>
|
||||
open-module=<Alt-Key-m> <Meta-Key-m>
|
||||
open-new-window=<Control-Key-n>
|
||||
open-window-from-file=<Control-Key-o>
|
||||
plain-newline-and-indent=<Control-Key-j>
|
||||
print-window=<Control-Key-p>
|
||||
redo=<Control-Shift-Key-z>
|
||||
remove-selection=<Key-Escape>
|
||||
save-copy-of-window-as-file=<Alt-Shift-Key-s>
|
||||
save-window-as-file=<Control-Shift-Key-s>
|
||||
save-window=<Control-Key-s>
|
||||
select-all=<Control-Key-a>
|
||||
toggle-auto-coloring=<Control-Key-slash>
|
||||
undo=<Control-Key-z>
|
||||
find=<Control-Key-f>
|
||||
find-again=<Control-Key-g> <Key-F3>
|
||||
find-in-files=<Alt-Key-F3> <Meta-Key-F3>
|
||||
find-selection=<Control-Key-F3>
|
||||
replace=<Control-Key-h>
|
||||
goto-line=<Alt-Key-g> <Meta-Key-g>
|
||||
smart-backspace=<Key-BackSpace>
|
||||
newline-and-indent=<Key-Return> <Key-KP_Enter>
|
||||
smart-indent=<Key-Tab>
|
||||
indent-region=<Control-Key-bracketright>
|
||||
dedent-region=<Control-Key-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> <Meta-Key-t>
|
||||
change-indentwidth=<Alt-Key-u> <Meta-Key-u>
|
||||
|
||||
[IDLE Classic Unix]
|
||||
copy=<Alt-Key-w> <Meta-Key-w>
|
||||
cut=<Control-Key-w>
|
||||
paste=<Control-Key-y>
|
||||
beginning-of-line=<Control-Key-a> <Key-Home>
|
||||
center-insert=<Control-Key-l>
|
||||
close-all-windows=<Control-Key-x><Control-Key-c>
|
||||
close-window=<Control-Key-x><Control-Key-0>
|
||||
do-nothing=<Control-Key-x>
|
||||
end-of-file=<Control-Key-d>
|
||||
history-next=<Alt-Key-n> <Meta-Key-n>
|
||||
history-previous=<Alt-Key-p> <Meta-Key-p>
|
||||
interrupt-execution=<Control-Key-c>
|
||||
view-restart=<Key-F6>
|
||||
restart-shell=<Control-Key-F6>
|
||||
open-class-browser=<Control-Key-x><Control-Key-b>
|
||||
open-module=<Control-Key-x><Control-Key-m>
|
||||
open-new-window=<Control-Key-x><Control-Key-n>
|
||||
open-window-from-file=<Control-Key-x><Control-Key-f>
|
||||
plain-newline-and-indent=<Control-Key-j>
|
||||
print-window=<Control-x><Control-Key-p>
|
||||
python-docs=<Control-Key-h>
|
||||
python-context-help=<Control-Shift-Key-h>
|
||||
redo=<Alt-Key-z> <Meta-Key-z>
|
||||
remove-selection=<Key-Escape>
|
||||
save-copy-of-window-as-file=<Control-Key-x><Control-Key-y>
|
||||
save-window-as-file=<Control-Key-x><Control-Key-w>
|
||||
save-window=<Control-Key-x><Control-Key-s>
|
||||
select-all=<Alt-Key-a> <Meta-Key-a>
|
||||
toggle-auto-coloring=<Control-Key-slash>
|
||||
undo=<Control-Key-z>
|
||||
find=<Control-Key-u><Control-Key-u><Control-Key-s>
|
||||
find-again=<Control-Key-u><Control-Key-s>
|
||||
find-in-files=<Alt-Key-s> <Meta-Key-s>
|
||||
find-selection=<Control-Key-s>
|
||||
replace=<Control-Key-r>
|
||||
goto-line=<Alt-Key-g> <Meta-Key-g>
|
||||
smart-backspace=<Key-BackSpace>
|
||||
newline-and-indent=<Key-Return> <Key-KP_Enter>
|
||||
smart-indent=<Key-Tab>
|
||||
indent-region=<Control-Key-bracketright>
|
||||
dedent-region=<Control-Key-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>
|
||||
|
||||
[IDLE Classic Mac]
|
||||
copy=<Command-Key-c>
|
||||
cut=<Command-Key-x>
|
||||
paste=<Command-Key-v>
|
||||
beginning-of-line= <Key-Home>
|
||||
center-insert=<Control-Key-l>
|
||||
close-all-windows=<Command-Key-q>
|
||||
close-window=<Command-Key-w>
|
||||
do-nothing=<Control-Key-F12>
|
||||
end-of-file=<Control-Key-d>
|
||||
python-docs=<Key-F1>
|
||||
python-context-help=<Shift-Key-F1>
|
||||
history-next=<Control-Key-n>
|
||||
history-previous=<Control-Key-p>
|
||||
interrupt-execution=<Control-Key-c>
|
||||
view-restart=<Key-F6>
|
||||
restart-shell=<Control-Key-F6>
|
||||
open-class-browser=<Command-Key-b>
|
||||
open-module=<Command-Key-m>
|
||||
open-new-window=<Command-Key-n>
|
||||
open-window-from-file=<Command-Key-o>
|
||||
plain-newline-and-indent=<Control-Key-j>
|
||||
print-window=<Command-Key-p>
|
||||
redo=<Shift-Command-Key-z>
|
||||
remove-selection=<Key-Escape>
|
||||
save-window-as-file=<Shift-Command-Key-s>
|
||||
save-window=<Command-Key-s>
|
||||
save-copy-of-window-as-file=<Option-Command-Key-s>
|
||||
select-all=<Command-Key-a>
|
||||
toggle-auto-coloring=<Control-Key-slash>
|
||||
undo=<Command-Key-z>
|
||||
find=<Command-Key-f>
|
||||
find-again=<Command-Key-g> <Key-F3>
|
||||
find-in-files=<Command-Key-F3>
|
||||
find-selection=<Shift-Command-Key-F3>
|
||||
replace=<Command-Key-r>
|
||||
goto-line=<Command-Key-j>
|
||||
smart-backspace=<Key-BackSpace>
|
||||
newline-and-indent=<Key-Return> <Key-KP_Enter>
|
||||
smart-indent=<Key-Tab>
|
||||
indent-region=<Command-Key-bracketright>
|
||||
dedent-region=<Command-Key-bracketleft>
|
||||
comment-region=<Control-Key-3>
|
||||
uncomment-region=<Control-Key-4>
|
||||
tabify-region=<Control-Key-5>
|
||||
untabify-region=<Control-Key-6>
|
||||
toggle-tabs=<Control-Key-t>
|
||||
change-indentwidth=<Control-Key-u>
|
|
@ -1,3 +0,0 @@
|
|||
[EditorWindow]
|
||||
font-name= monaco
|
||||
font-size= 9
|
65
Tools/idle/config-main.def
Normal file
65
Tools/idle/config-main.def
Normal file
|
@ -0,0 +1,65 @@
|
|||
# IDLE reads several config files to determine user preferences. This
|
||||
# file is the default config file for general idle settings.
|
||||
#
|
||||
# When IDLE starts, it will look in
|
||||
# the following two sets of files, in order:
|
||||
#
|
||||
# default configuration
|
||||
# ---------------------
|
||||
# config-main.def the default general config file
|
||||
# config-extensions.def the default extension config file
|
||||
# config-highlight.def the default highlighting config file
|
||||
# config-keys.def the default keybinding config file
|
||||
#
|
||||
# user configuration
|
||||
# -------------------
|
||||
# ~/.idlerc/idle-main.cfg the user general config file
|
||||
# ~/.idlerc/idle-extensions.cfg the user extension config file
|
||||
# ~/.idlerc/idle-highlight.cfg the user highlighting config file
|
||||
# ~/.idlerc/idle-keys.cfg the user keybinding config file
|
||||
#
|
||||
# Any options the user saves through the config dialog will be saved to
|
||||
# the relevant user config file. Reverting any general setting to the
|
||||
# default causes that entry to be wiped from the user file and re-read
|
||||
# from the default file. User highlighting themes or keybinding sets are
|
||||
# retained unless specifically deleted within the config dialog. Choosing
|
||||
# one of the default themes or keysets just applies the relevant settings
|
||||
# from the default file.
|
||||
#
|
||||
# Additional help sources are listed in the [HelpFiles] section and must be
|
||||
# viewable by a web browser (or the Windows Help viewer in the case of .chm
|
||||
# files). These sources will be listed on the Help menu. The pattern is
|
||||
# <sequence_number = menu item;/path/to/help/source>
|
||||
# You can't use a semi-colon in a menu item or path. The path will be platform
|
||||
# specific because of path separators, drive specs etc.
|
||||
#
|
||||
# It is best to use the Configuration GUI to set up additional help sources!
|
||||
# Example:
|
||||
#1 = My Extra Help Source;/usr/share/doc/foo/index.html
|
||||
#2 = Another Help Source;/path/to/another.pdf
|
||||
|
||||
[General]
|
||||
editor-on-startup= 0
|
||||
print-command-posix=lpr %s
|
||||
print-command-win=start /min notepad /p %s
|
||||
|
||||
[EditorWindow]
|
||||
width= 80
|
||||
height= 30
|
||||
font= courier
|
||||
font-size= 12
|
||||
font-bold= 0
|
||||
|
||||
[Indent]
|
||||
use-spaces= 1
|
||||
num-spaces= 4
|
||||
|
||||
[Theme]
|
||||
default= 1
|
||||
name= IDLE Classic
|
||||
|
||||
[Keys]
|
||||
default= 1
|
||||
name= IDLE Classic Windows
|
||||
|
||||
[HelpFiles]
|
|
@ -1,4 +0,0 @@
|
|||
[EditorWindow]
|
||||
font-name= courier
|
||||
font-size= 10
|
||||
print-command=lpr %s
|
|
@ -1,4 +0,0 @@
|
|||
[EditorWindow]
|
||||
font-name: courier new
|
||||
font-size: 10
|
||||
print-command=start /min notepad /p %s
|
|
@ -1,64 +0,0 @@
|
|||
# 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 '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]
|
||||
|
||||
[CallTips]
|
||||
|
||||
[ParenMatch]
|
||||
enable= 0
|
||||
style= expression
|
||||
flash-delay= 500
|
||||
bell= 1
|
||||
hilite-foreground= black
|
||||
hilite-background= #43cd80
|
1134
Tools/idle/configDialog.py
Normal file
1134
Tools/idle/configDialog.py
Normal file
File diff suppressed because it is too large
Load diff
655
Tools/idle/configHandler.py
Normal file
655
Tools/idle/configHandler.py
Normal file
|
@ -0,0 +1,655 @@
|
|||
"""Provides access to stored IDLE configuration information.
|
||||
|
||||
Refer to the comments at the beginning of config-main.def for a description of
|
||||
the available configuration files and the design implemented to update user
|
||||
configuration information. In particular, user configuration choices which
|
||||
duplicate the defaults will be removed from the user's configuration files,
|
||||
and if a file becomes empty, it will be deleted.
|
||||
|
||||
The contents of the user files may be altered using the Options/Configure IDLE
|
||||
menu to access the configuration GUI (configDialog.py), or manually.
|
||||
|
||||
Throughout this module there is an emphasis on returning useable defaults
|
||||
when a problem occurs in returning a requested configuration value back to
|
||||
idle. This is to allow IDLE to continue to function in spite of errors in
|
||||
the retrieval of config information. When a default is returned instead of
|
||||
a requested config value, a message is printed to stderr to aid in
|
||||
configuration problem notification and resolution.
|
||||
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import string
|
||||
from ConfigParser import ConfigParser, NoOptionError, NoSectionError
|
||||
|
||||
class InvalidConfigType(Exception): pass
|
||||
class InvalidConfigSet(Exception): pass
|
||||
class InvalidFgBg(Exception): pass
|
||||
class InvalidTheme(Exception): pass
|
||||
|
||||
class IdleConfParser(ConfigParser):
|
||||
"""
|
||||
A ConfigParser specialised for idle configuration file handling
|
||||
"""
|
||||
def __init__(self, cfgFile, cfgDefaults=None):
|
||||
"""
|
||||
cfgFile - string, fully specified configuration file name
|
||||
"""
|
||||
self.file=cfgFile
|
||||
ConfigParser.__init__(self,defaults=cfgDefaults)
|
||||
|
||||
def Get(self, section, option, type=None, default=None):
|
||||
"""
|
||||
Get an option value for given section/option or return default.
|
||||
If type is specified, return as type.
|
||||
"""
|
||||
if type=='bool':
|
||||
getVal=self.getboolean
|
||||
elif type=='int':
|
||||
getVal=self.getint
|
||||
else:
|
||||
getVal=self.get
|
||||
if self.has_option(section,option):
|
||||
#return getVal(section, option, raw, vars, default)
|
||||
return getVal(section, option)
|
||||
else:
|
||||
return default
|
||||
|
||||
def GetOptionList(self,section):
|
||||
"""
|
||||
Get an option list for given section
|
||||
"""
|
||||
if self.has_section(section):
|
||||
return self.options(section)
|
||||
else: #return a default value
|
||||
return []
|
||||
|
||||
def Load(self):
|
||||
"""
|
||||
Load the configuration file from disk
|
||||
"""
|
||||
self.read(self.file)
|
||||
|
||||
class IdleUserConfParser(IdleConfParser):
|
||||
"""
|
||||
IdleConfigParser specialised for user configuration handling.
|
||||
"""
|
||||
|
||||
def AddSection(self,section):
|
||||
"""
|
||||
if section doesn't exist, add it
|
||||
"""
|
||||
if not self.has_section(section):
|
||||
self.add_section(section)
|
||||
|
||||
def RemoveEmptySections(self):
|
||||
"""
|
||||
remove any sections that have no options
|
||||
"""
|
||||
for section in self.sections():
|
||||
if not self.GetOptionList(section):
|
||||
self.remove_section(section)
|
||||
|
||||
def IsEmpty(self):
|
||||
"""
|
||||
Remove empty sections and then return 1 if parser has no sections
|
||||
left, else return 0.
|
||||
"""
|
||||
self.RemoveEmptySections()
|
||||
if self.sections():
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
|
||||
def RemoveOption(self,section,option):
|
||||
"""
|
||||
If section/option exists, remove it.
|
||||
Returns 1 if option was removed, 0 otherwise.
|
||||
"""
|
||||
if self.has_section(section):
|
||||
return self.remove_option(section,option)
|
||||
|
||||
def SetOption(self,section,option,value):
|
||||
"""
|
||||
Sets option to value, adding section if required.
|
||||
Returns 1 if option was added or changed, otherwise 0.
|
||||
"""
|
||||
if self.has_option(section,option):
|
||||
if self.get(section,option)==value:
|
||||
return 0
|
||||
else:
|
||||
self.set(section,option,value)
|
||||
return 1
|
||||
else:
|
||||
if not self.has_section(section):
|
||||
self.add_section(section)
|
||||
self.set(section,option,value)
|
||||
return 1
|
||||
|
||||
def RemoveFile(self):
|
||||
"""
|
||||
Removes the user config file from disk if it exists.
|
||||
"""
|
||||
if os.path.exists(self.file):
|
||||
os.remove(self.file)
|
||||
|
||||
def Save(self):
|
||||
"""Update user configuration file.
|
||||
|
||||
Remove empty sections. If resulting config isn't empty, write the file
|
||||
to disk. If config is empty, remove the file from disk if it exists.
|
||||
|
||||
"""
|
||||
if not self.IsEmpty():
|
||||
cfgFile=open(self.file,'w')
|
||||
self.write(cfgFile)
|
||||
else:
|
||||
self.RemoveFile()
|
||||
|
||||
class IdleConf:
|
||||
"""
|
||||
holds config parsers for all idle config files:
|
||||
default config files
|
||||
(idle install dir)/config-main.def
|
||||
(idle install dir)/config-extensions.def
|
||||
(idle install dir)/config-highlight.def
|
||||
(idle install dir)/config-keys.def
|
||||
user config files
|
||||
(user home dir)/.idlerc/config-main.cfg
|
||||
(user home dir)/.idlerc/config-extensions.cfg
|
||||
(user home dir)/.idlerc/config-highlight.cfg
|
||||
(user home dir)/.idlerc/config-keys.cfg
|
||||
"""
|
||||
def __init__(self):
|
||||
self.defaultCfg={}
|
||||
self.userCfg={}
|
||||
self.cfg={}
|
||||
self.CreateConfigHandlers()
|
||||
self.LoadCfgFiles()
|
||||
#self.LoadCfg()
|
||||
|
||||
def CreateConfigHandlers(self):
|
||||
"""
|
||||
set up a dictionary of config parsers for default and user
|
||||
configurations respectively
|
||||
"""
|
||||
#build idle install path
|
||||
if __name__ != '__main__': # we were imported
|
||||
idleDir=os.path.dirname(__file__)
|
||||
else: # we were exec'ed (for testing only)
|
||||
idleDir=os.path.abspath(sys.path[0])
|
||||
userDir=self.GetUserCfgDir()
|
||||
configTypes=('main','extensions','highlight','keys')
|
||||
defCfgFiles={}
|
||||
usrCfgFiles={}
|
||||
for cfgType in configTypes: #build config file names
|
||||
defCfgFiles[cfgType]=os.path.join(idleDir,'config-'+cfgType+'.def')
|
||||
usrCfgFiles[cfgType]=os.path.join(userDir,'config-'+cfgType+'.cfg')
|
||||
for cfgType in configTypes: #create config parsers
|
||||
self.defaultCfg[cfgType]=IdleConfParser(defCfgFiles[cfgType])
|
||||
self.userCfg[cfgType]=IdleUserConfParser(usrCfgFiles[cfgType])
|
||||
|
||||
def GetUserCfgDir(self):
|
||||
"""
|
||||
Creates (if required) and returns a filesystem directory for storing
|
||||
user config files.
|
||||
"""
|
||||
cfgDir='.idlerc'
|
||||
userDir=os.path.expanduser('~')
|
||||
if userDir != '~': #'HOME' exists as a key in os.environ
|
||||
if not os.path.exists(userDir):
|
||||
warn=('\n Warning: HOME environment variable points to\n '+
|
||||
userDir+'\n but the path does not exist.\n')
|
||||
sys.stderr.write(warn)
|
||||
userDir='~'
|
||||
if userDir=='~': #we still don't have a home directory
|
||||
#traditionally idle has defaulted to os.getcwd(), is this adeqate?
|
||||
userDir = os.getcwd() #hack for no real homedir
|
||||
userDir=os.path.join(userDir,cfgDir)
|
||||
if not os.path.exists(userDir):
|
||||
try: #make the config dir if it doesn't exist yet
|
||||
os.mkdir(userDir)
|
||||
except IOError:
|
||||
warn=('\n Warning: unable to create user config directory\n '+
|
||||
userDir+'\n')
|
||||
sys.stderr.write(warn)
|
||||
return userDir
|
||||
|
||||
def GetOption(self, configType, section, option, default=None, type=None):
|
||||
"""
|
||||
Get an option value for given config type and given general
|
||||
configuration section/option or return a default. If type is specified,
|
||||
return as type. Firstly the user configuration is checked, with a
|
||||
fallback to the default configuration, and a final 'catch all'
|
||||
fallback to a useable passed-in default if the option isn't present in
|
||||
either the user or the default configuration.
|
||||
configType must be one of ('main','extensions','highlight','keys')
|
||||
If a default is returned a warning is printed to stderr.
|
||||
"""
|
||||
if self.userCfg[configType].has_option(section,option):
|
||||
return self.userCfg[configType].Get(section, option, type=type)
|
||||
elif self.defaultCfg[configType].has_option(section,option):
|
||||
return self.defaultCfg[configType].Get(section, option, type=type)
|
||||
else: #returning default, print warning
|
||||
warning=('\n Warning: configHandler.py - IdleConf.GetOption -\n'+
|
||||
' problem retrieving configration option '+`option`+'\n'+
|
||||
' from section '+`section`+'.\n'+
|
||||
' returning default value: '+`default`+'\n')
|
||||
sys.stderr.write(warning)
|
||||
return default
|
||||
|
||||
def GetSectionList(self, configSet, configType):
|
||||
"""
|
||||
Get a list of sections from either the user or default config for
|
||||
the given config type.
|
||||
configSet must be either 'user' or 'default'
|
||||
configType must be one of ('main','extensions','highlight','keys')
|
||||
"""
|
||||
if not (configType in ('main','extensions','highlight','keys')):
|
||||
raise InvalidConfigType, 'Invalid configType specified'
|
||||
if configSet == 'user':
|
||||
cfgParser=self.userCfg[configType]
|
||||
elif configSet == 'default':
|
||||
cfgParser=self.defaultCfg[configType]
|
||||
else:
|
||||
raise InvalidConfigSet, 'Invalid configSet specified'
|
||||
return cfgParser.sections()
|
||||
|
||||
def GetHighlight(self, theme, element, fgBg=None):
|
||||
"""
|
||||
return individual highlighting theme elements.
|
||||
fgBg - string ('fg'or'bg') or None, if None return a dictionary
|
||||
containing fg and bg colours (appropriate for passing to Tkinter in,
|
||||
e.g., a tag_config call), otherwise fg or bg colour only as specified.
|
||||
"""
|
||||
if self.defaultCfg['highlight'].has_section(theme):
|
||||
themeDict=self.GetThemeDict('default',theme)
|
||||
else:
|
||||
themeDict=self.GetThemeDict('user',theme)
|
||||
fore=themeDict[element+'-foreground']
|
||||
if element=='cursor': #there is no config value for cursor bg
|
||||
back=themeDict['normal-background']
|
||||
else:
|
||||
back=themeDict[element+'-background']
|
||||
highlight={"foreground": fore,"background": back}
|
||||
if not fgBg: #return dict of both colours
|
||||
return highlight
|
||||
else: #return specified colour only
|
||||
if fgBg == 'fg':
|
||||
return highlight["foreground"]
|
||||
if fgBg == 'bg':
|
||||
return highlight["background"]
|
||||
else:
|
||||
raise InvalidFgBg, 'Invalid fgBg specified'
|
||||
|
||||
def GetThemeDict(self,type,themeName):
|
||||
"""
|
||||
type - string, 'default' or 'user' theme type
|
||||
themeName - string, theme name
|
||||
Returns a dictionary which holds {option:value} for each element
|
||||
in the specified theme. Values are loaded over a set of ultimate last
|
||||
fallback defaults to guarantee that all theme elements are present in
|
||||
a newly created theme.
|
||||
"""
|
||||
if type == 'user':
|
||||
cfgParser=self.userCfg['highlight']
|
||||
elif type == 'default':
|
||||
cfgParser=self.defaultCfg['highlight']
|
||||
else:
|
||||
raise InvalidTheme, 'Invalid theme type specified'
|
||||
#foreground and background values are provded for each theme element
|
||||
#(apart from cursor) even though all these values are not yet used
|
||||
#by idle, to allow for their use in the future. Default values are
|
||||
#generally black and white.
|
||||
theme={ 'normal-foreground':'#000000',
|
||||
'normal-background':'#ffffff',
|
||||
'keyword-foreground':'#000000',
|
||||
'keyword-background':'#ffffff',
|
||||
'comment-foreground':'#000000',
|
||||
'comment-background':'#ffffff',
|
||||
'string-foreground':'#000000',
|
||||
'string-background':'#ffffff',
|
||||
'definition-foreground':'#000000',
|
||||
'definition-background':'#ffffff',
|
||||
'hilite-foreground':'#000000',
|
||||
'hilite-background':'gray',
|
||||
'break-foreground':'#ffffff',
|
||||
'break-background':'#000000',
|
||||
'hit-foreground':'#ffffff',
|
||||
'hit-background':'#000000',
|
||||
'error-foreground':'#ffffff',
|
||||
'error-background':'#000000',
|
||||
#cursor (only foreground can be set)
|
||||
'cursor-foreground':'#000000',
|
||||
#shell window
|
||||
'stdout-foreground':'#000000',
|
||||
'stdout-background':'#ffffff',
|
||||
'stderr-foreground':'#000000',
|
||||
'stderr-background':'#ffffff',
|
||||
'console-foreground':'#000000',
|
||||
'console-background':'#ffffff' }
|
||||
for element in theme.keys():
|
||||
if not cfgParser.has_option(themeName,element):
|
||||
#we are going to return a default, print warning
|
||||
warning=('\n Warning: configHandler.py - IdleConf.GetThemeDict'+
|
||||
' -\n problem retrieving theme element '+`element`+
|
||||
'\n from theme '+`themeName`+'.\n'+
|
||||
' returning default value: '+`theme[element]`+'\n')
|
||||
sys.stderr.write(warning)
|
||||
colour=cfgParser.Get(themeName,element,default=theme[element])
|
||||
theme[element]=colour
|
||||
return theme
|
||||
|
||||
def CurrentTheme(self):
|
||||
"""
|
||||
Returns the name of the currently active theme
|
||||
"""
|
||||
return self.GetOption('main','Theme','name',default='')
|
||||
|
||||
def CurrentKeys(self):
|
||||
"""
|
||||
Returns the name of the currently active key set
|
||||
"""
|
||||
return self.GetOption('main','Keys','name',default='')
|
||||
|
||||
def GetExtensions(self, activeOnly=1):
|
||||
"""
|
||||
Gets a list of all idle extensions declared in the config files.
|
||||
activeOnly - boolean, if true only return active (enabled) extensions
|
||||
"""
|
||||
extns=self.RemoveKeyBindNames(
|
||||
self.GetSectionList('default','extensions'))
|
||||
userExtns=self.RemoveKeyBindNames(
|
||||
self.GetSectionList('user','extensions'))
|
||||
for extn in userExtns:
|
||||
if extn not in extns: #user has added own extension
|
||||
extns.append(extn)
|
||||
if activeOnly:
|
||||
activeExtns=[]
|
||||
for extn in extns:
|
||||
if self.GetOption('extensions',extn,'enable',default=1,
|
||||
type='bool'):
|
||||
#the extension is enabled
|
||||
activeExtns.append(extn)
|
||||
return activeExtns
|
||||
else:
|
||||
return extns
|
||||
|
||||
def RemoveKeyBindNames(self,extnNameList):
|
||||
#get rid of keybinding section names
|
||||
names=extnNameList
|
||||
kbNameIndicies=[]
|
||||
for name in names:
|
||||
if name.endswith('_bindings') or name.endswith('_cfgBindings'):
|
||||
kbNameIndicies.append(names.index(name))
|
||||
kbNameIndicies.sort()
|
||||
kbNameIndicies.reverse()
|
||||
for index in kbNameIndicies: #delete each keybinding section name
|
||||
del(names[index])
|
||||
return names
|
||||
|
||||
def GetExtnNameForEvent(self,virtualEvent):
|
||||
"""
|
||||
Returns the name of the extension that virtualEvent is bound in, or
|
||||
None if not bound in any extension.
|
||||
virtualEvent - string, name of the virtual event to test for, without
|
||||
the enclosing '<< >>'
|
||||
"""
|
||||
extName=None
|
||||
vEvent='<<'+virtualEvent+'>>'
|
||||
for extn in self.GetExtensions(activeOnly=0):
|
||||
for event in self.GetExtensionKeys(extn).keys():
|
||||
if event == vEvent:
|
||||
extName=extn
|
||||
return extName
|
||||
|
||||
def GetExtensionKeys(self,extensionName):
|
||||
"""
|
||||
returns a dictionary of the configurable keybindings for a particular
|
||||
extension,as they exist in the dictionary returned by GetCurrentKeySet;
|
||||
that is, where previously used bindings are disabled.
|
||||
"""
|
||||
keysName=extensionName+'_cfgBindings'
|
||||
activeKeys=self.GetCurrentKeySet()
|
||||
extKeys={}
|
||||
if self.defaultCfg['extensions'].has_section(keysName):
|
||||
eventNames=self.defaultCfg['extensions'].GetOptionList(keysName)
|
||||
for eventName in eventNames:
|
||||
event='<<'+eventName+'>>'
|
||||
binding=activeKeys[event]
|
||||
extKeys[event]=binding
|
||||
return extKeys
|
||||
|
||||
def __GetRawExtensionKeys(self,extensionName):
|
||||
"""
|
||||
returns a dictionary of the configurable keybindings for a particular
|
||||
extension, as defined in the configuration files, or an empty dictionary
|
||||
if no bindings are found
|
||||
"""
|
||||
keysName=extensionName+'_cfgBindings'
|
||||
extKeys={}
|
||||
if self.defaultCfg['extensions'].has_section(keysName):
|
||||
eventNames=self.defaultCfg['extensions'].GetOptionList(keysName)
|
||||
for eventName in eventNames:
|
||||
binding=self.GetOption('extensions',keysName,
|
||||
eventName,default='').split()
|
||||
event='<<'+eventName+'>>'
|
||||
extKeys[event]=binding
|
||||
return extKeys
|
||||
|
||||
def GetExtensionBindings(self,extensionName):
|
||||
"""
|
||||
Returns a dictionary of all the event bindings for a particular
|
||||
extension. The configurable keybindings are returned as they exist in
|
||||
the dictionary returned by GetCurrentKeySet; that is, where re-used
|
||||
keybindings are disabled.
|
||||
"""
|
||||
bindsName=extensionName+'_bindings'
|
||||
extBinds=self.GetExtensionKeys(extensionName)
|
||||
#add the non-configurable bindings
|
||||
if self.defaultCfg['extensions'].has_section(bindsName):
|
||||
eventNames=self.defaultCfg['extensions'].GetOptionList(bindsName)
|
||||
for eventName in eventNames:
|
||||
binding=self.GetOption('extensions',bindsName,
|
||||
eventName,default='').split()
|
||||
event='<<'+eventName+'>>'
|
||||
extBinds[event]=binding
|
||||
|
||||
return extBinds
|
||||
|
||||
def GetKeyBinding(self, keySetName, eventStr):
|
||||
"""
|
||||
returns the keybinding for a specific event.
|
||||
keySetName - string, name of key binding set
|
||||
eventStr - string, the virtual event we want the binding for,
|
||||
represented as a string, eg. '<<event>>'
|
||||
"""
|
||||
eventName=eventStr[2:-2] #trim off the angle brackets
|
||||
binding=self.GetOption('keys',keySetName,eventName,default='').split()
|
||||
return binding
|
||||
|
||||
def GetCurrentKeySet(self):
|
||||
return self.GetKeySet(self.CurrentKeys())
|
||||
|
||||
def GetKeySet(self,keySetName):
|
||||
"""
|
||||
Returns a dictionary of: all requested core keybindings, plus the
|
||||
keybindings for all currently active extensions. If a binding defined
|
||||
in an extension is already in use, that binding is disabled.
|
||||
"""
|
||||
keySet=self.GetCoreKeys(keySetName)
|
||||
activeExtns=self.GetExtensions(activeOnly=1)
|
||||
for extn in activeExtns:
|
||||
extKeys=self.__GetRawExtensionKeys(extn)
|
||||
if extKeys: #the extension defines keybindings
|
||||
for event in extKeys.keys():
|
||||
if extKeys[event] in keySet.values():
|
||||
#the binding is already in use
|
||||
extKeys[event]='' #disable this binding
|
||||
keySet[event]=extKeys[event] #add binding
|
||||
return keySet
|
||||
|
||||
def IsCoreBinding(self,virtualEvent):
|
||||
"""
|
||||
returns true if the virtual event is bound in the core idle keybindings.
|
||||
virtualEvent - string, name of the virtual event to test for, without
|
||||
the enclosing '<< >>'
|
||||
"""
|
||||
return ('<<'+virtualEvent+'>>') in self.GetCoreKeys().keys()
|
||||
|
||||
def GetCoreKeys(self, keySetName=None):
|
||||
"""
|
||||
returns the requested set of core keybindings, with fallbacks if
|
||||
required.
|
||||
Keybindings loaded from the config file(s) are loaded _over_ these
|
||||
defaults, so if there is a problem getting any core binding there will
|
||||
be an 'ultimate last resort fallback' to the CUA-ish bindings
|
||||
defined here.
|
||||
"""
|
||||
keyBindings={
|
||||
'<<copy>>': ['<Control-c>', '<Control-C>'],
|
||||
'<<cut>>': ['<Control-x>', '<Control-X>'],
|
||||
'<<paste>>': ['<Control-v>', '<Control-V>'],
|
||||
'<<beginning-of-line>>': ['<Control-a>', '<Home>'],
|
||||
'<<center-insert>>': ['<Control-l>'],
|
||||
'<<close-all-windows>>': ['<Control-q>'],
|
||||
'<<close-window>>': ['<Alt-F4>'],
|
||||
'<<do-nothing>>': ['<Control-x>'],
|
||||
'<<end-of-file>>': ['<Control-d>'],
|
||||
'<<python-docs>>': ['<F1>'],
|
||||
'<<python-context-help>>': ['<Shift-F1>'],
|
||||
'<<history-next>>': ['<Alt-n>'],
|
||||
'<<history-previous>>': ['<Alt-p>'],
|
||||
'<<interrupt-execution>>': ['<Control-c>'],
|
||||
'<<view-restart>>': ['<F6>'],
|
||||
'<<restart-shell>>': ['<Control-F6>'],
|
||||
'<<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>'],
|
||||
'<<print-window>>': ['<Control-p>'],
|
||||
'<<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>'],
|
||||
'<<find-again>>': ['<Control-g>', '<F3>'],
|
||||
'<<find-in-files>>': ['<Alt-F3>'],
|
||||
'<<find-selection>>': ['<Control-F3>'],
|
||||
'<<find>>': ['<Control-f>'],
|
||||
'<<replace>>': ['<Control-h>'],
|
||||
'<<goto-line>>': ['<Alt-g>'],
|
||||
'<<smart-backspace>>': ['<Key-BackSpace>'],
|
||||
'<<newline-and-indent>>': ['<Key-Return> <Key-KP_Enter>'],
|
||||
'<<smart-indent>>': ['<Key-Tab>'],
|
||||
'<<indent-region>>': ['<Control-Key-bracketright>'],
|
||||
'<<dedent-region>>': ['<Control-Key-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>']
|
||||
}
|
||||
if keySetName:
|
||||
for event in keyBindings.keys():
|
||||
binding=self.GetKeyBinding(keySetName,event)
|
||||
if binding:
|
||||
keyBindings[event]=binding
|
||||
else: #we are going to return a default, print warning
|
||||
warning=('\n Warning: configHandler.py - IdleConf.GetCoreKeys'+
|
||||
' -\n problem retrieving key binding for event '+
|
||||
`event`+'\n from key set '+`keySetName`+'.\n'+
|
||||
' returning default value: '+`keyBindings[event]`+'\n')
|
||||
sys.stderr.write(warning)
|
||||
return keyBindings
|
||||
|
||||
def GetExtraHelpSourceList(self,configSet):
|
||||
"""Fetch list of extra help sources from a given configSet.
|
||||
|
||||
Valid configSets are 'user' or 'default'. Return a list of tuples of
|
||||
the form (menu_item , path_to_help_file , option), or return the empty
|
||||
list. 'option' is the sequence number of the help resource. 'option'
|
||||
values determine the position of the menu items on the Help menu,
|
||||
therefore the returned list must be sorted by 'option'.
|
||||
|
||||
"""
|
||||
helpSources=[]
|
||||
if configSet=='user':
|
||||
cfgParser=self.userCfg['main']
|
||||
elif configSet=='default':
|
||||
cfgParser=self.defaultCfg['main']
|
||||
else:
|
||||
raise InvalidConfigSet, 'Invalid configSet specified'
|
||||
options=cfgParser.GetOptionList('HelpFiles')
|
||||
for option in options:
|
||||
value=cfgParser.Get('HelpFiles',option,default=';')
|
||||
if value.find(';')==-1: #malformed config entry with no ';'
|
||||
menuItem='' #make these empty
|
||||
helpPath='' #so value won't be added to list
|
||||
else: #config entry contains ';' as expected
|
||||
value=string.split(value,';')
|
||||
menuItem=value[0].strip()
|
||||
helpPath=value[1].strip()
|
||||
if menuItem and helpPath: #neither are empty strings
|
||||
helpSources.append( (menuItem,helpPath,option) )
|
||||
helpSources.sort(self.__helpsort)
|
||||
return helpSources
|
||||
|
||||
def __helpsort(self, h1, h2):
|
||||
if int(h1[2]) < int(h2[2]):
|
||||
return -1
|
||||
elif int(h1[2]) > int(h2[2]):
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
def GetAllExtraHelpSourcesList(self):
|
||||
"""
|
||||
Returns a list of tuples containing the details of all additional help
|
||||
sources configured, or an empty list if there are none. Tuples are of
|
||||
the format returned by GetExtraHelpSourceList.
|
||||
"""
|
||||
allHelpSources=( self.GetExtraHelpSourceList('default')+
|
||||
self.GetExtraHelpSourceList('user') )
|
||||
return allHelpSources
|
||||
|
||||
def LoadCfgFiles(self):
|
||||
"""
|
||||
load all configuration files.
|
||||
"""
|
||||
for key in self.defaultCfg.keys():
|
||||
self.defaultCfg[key].Load()
|
||||
self.userCfg[key].Load() #same keys
|
||||
|
||||
def SaveUserCfgFiles(self):
|
||||
"""
|
||||
write all loaded user configuration files back to disk
|
||||
"""
|
||||
for key in self.userCfg.keys():
|
||||
self.userCfg[key].Save()
|
||||
|
||||
idleConf=IdleConf()
|
||||
|
||||
### module test
|
||||
if __name__ == '__main__':
|
||||
def dumpCfg(cfg):
|
||||
print '\n',cfg,'\n'
|
||||
for key in cfg.keys():
|
||||
sections=cfg[key].sections()
|
||||
print key
|
||||
print sections
|
||||
for section in sections:
|
||||
options=cfg[key].options(section)
|
||||
print section
|
||||
print options
|
||||
for option in options:
|
||||
print option, '=', cfg[key].Get(section,option)
|
||||
dumpCfg(idleConf.defaultCfg)
|
||||
dumpCfg(idleConf.userCfg)
|
||||
print idleConf.userCfg['main'].Get('Theme','name')
|
||||
#print idleConf.userCfg['highlight'].GetDefHighlight('Foo','normal')
|
157
Tools/idle/configHelpSourceEdit.py
Normal file
157
Tools/idle/configHelpSourceEdit.py
Normal file
|
@ -0,0 +1,157 @@
|
|||
"Dialog to specify or edit the parameters for a user configured help source."
|
||||
|
||||
import os
|
||||
|
||||
from Tkinter import *
|
||||
import tkMessageBox
|
||||
import tkFileDialog
|
||||
|
||||
class GetHelpSourceDialog(Toplevel):
|
||||
def __init__(self, parent, title, menuItem='', filePath=''):
|
||||
"""Get menu entry and url/ local file location for Additional Help
|
||||
|
||||
User selects a name for the Help resource and provides a web url
|
||||
or a local file as its source. The user can enter a url or browse
|
||||
for the file.
|
||||
|
||||
"""
|
||||
Toplevel.__init__(self, parent)
|
||||
self.configure(borderwidth=5)
|
||||
self.resizable(height=FALSE, width=FALSE)
|
||||
self.title(title)
|
||||
self.transient(parent)
|
||||
self.grab_set()
|
||||
self.protocol("WM_DELETE_WINDOW", self.Cancel)
|
||||
self.parent = parent
|
||||
self.result = None
|
||||
self.CreateWidgets()
|
||||
self.menu.set(menuItem)
|
||||
self.path.set(filePath)
|
||||
self.withdraw() #hide while setting geometry
|
||||
#needs to be done here so that the winfo_reqwidth is valid
|
||||
self.update_idletasks()
|
||||
#centre dialog over parent:
|
||||
self.geometry("+%d+%d" %
|
||||
((parent.winfo_rootx() + ((parent.winfo_width()/2)
|
||||
-(self.winfo_reqwidth()/2)),
|
||||
parent.winfo_rooty() + ((parent.winfo_height()/2)
|
||||
-(self.winfo_reqheight()/2)))))
|
||||
self.deiconify() #geometry set, unhide
|
||||
self.bind('<Return>', self.Ok)
|
||||
self.wait_window()
|
||||
|
||||
def CreateWidgets(self):
|
||||
self.menu = StringVar(self)
|
||||
self.path = StringVar(self)
|
||||
self.fontSize = StringVar(self)
|
||||
self.frameMain = Frame(self, borderwidth=2, relief=GROOVE)
|
||||
self.frameMain.pack(side=TOP, expand=TRUE, fill=BOTH)
|
||||
labelMenu = Label(self.frameMain, anchor=W, justify=LEFT,
|
||||
text='Menu Item:')
|
||||
self.entryMenu = Entry(self.frameMain, textvariable=self.menu,
|
||||
width=30)
|
||||
self.entryMenu.focus_set()
|
||||
labelPath = Label(self.frameMain, anchor=W, justify=LEFT,
|
||||
text='Help File Path: Enter URL or browse for file')
|
||||
self.entryPath = Entry(self.frameMain, textvariable=self.path,
|
||||
width=40)
|
||||
self.entryMenu.focus_set()
|
||||
labelMenu.pack(anchor=W, padx=5, pady=3)
|
||||
self.entryMenu.pack(anchor=W, padx=5, pady=3)
|
||||
labelPath.pack(anchor=W, padx=5, pady=3)
|
||||
self.entryPath.pack(anchor=W, padx=5, pady=3)
|
||||
browseButton = Button(self.frameMain, text='Browse', width=8,
|
||||
command=self.browseFile)
|
||||
browseButton.pack(pady=3)
|
||||
frameButtons = Frame(self)
|
||||
frameButtons.pack(side=BOTTOM, fill=X)
|
||||
self.buttonOk = Button(frameButtons, text='OK',
|
||||
width=8, default=ACTIVE, command=self.Ok)
|
||||
self.buttonOk.grid(row=0, column=0, padx=5,pady=5)
|
||||
self.buttonCancel = Button(frameButtons, text='Cancel',
|
||||
width=8, command=self.Cancel)
|
||||
self.buttonCancel.grid(row=0, column=1, padx=5, pady=5)
|
||||
|
||||
def browseFile(self):
|
||||
filetypes = [
|
||||
("HTML Files", "*.htm *.html", "TEXT"),
|
||||
("PDF Files", "*.pdf", "TEXT"),
|
||||
("Windows Help Files", "*.chm"),
|
||||
("Text Files", "*.txt", "TEXT"),
|
||||
("All Files", "*")]
|
||||
path = self.path.get()
|
||||
if path:
|
||||
dir, base = os.path.split(path)
|
||||
else:
|
||||
base = None
|
||||
if sys.platform.count('win') or sys.platform.count('nt'):
|
||||
dir = os.path.join(os.path.dirname(sys.executable), 'Doc')
|
||||
if not os.path.isdir(dir):
|
||||
dir = os.getcwd()
|
||||
else:
|
||||
dir = os.getcwd()
|
||||
opendialog = tkFileDialog.Open(parent=self, filetypes=filetypes)
|
||||
file = opendialog.show(initialdir=dir, initialfile=base)
|
||||
if file:
|
||||
self.path.set(file)
|
||||
|
||||
def MenuOk(self):
|
||||
"Simple validity check for a sensible menu item name"
|
||||
menuOk = True
|
||||
menu = self.menu.get()
|
||||
menu.strip()
|
||||
if not menu:
|
||||
tkMessageBox.showerror(title='Menu Item Error',
|
||||
message='No menu item specified',
|
||||
parent=self)
|
||||
self.entryMenu.focus_set()
|
||||
menuOk = False
|
||||
elif len(menu) > 30:
|
||||
tkMessageBox.showerror(title='Menu Item Error',
|
||||
message='Menu item too long:'
|
||||
'\nLimit 30 characters.',
|
||||
parent=self)
|
||||
self.entryMenu.focus_set()
|
||||
menuOk = False
|
||||
return menuOk
|
||||
|
||||
def PathOk(self):
|
||||
"Simple validity check for menu file path"
|
||||
pathOk = True
|
||||
path = self.path.get()
|
||||
path.strip()
|
||||
if not path: #no path specified
|
||||
tkMessageBox.showerror(title='File Path Error',
|
||||
message='No help file path specified.',
|
||||
parent=self)
|
||||
self.entryPath.focus_set()
|
||||
pathOk = False
|
||||
elif path.startswith('www.') or path.startswith('http'):
|
||||
pathOk = True
|
||||
elif not os.path.exists(path):
|
||||
tkMessageBox.showerror(title='File Path Error',
|
||||
message='Help file path does not exist.',
|
||||
parent=self)
|
||||
self.entryPath.focus_set()
|
||||
pathOk = False
|
||||
return pathOk
|
||||
|
||||
def Ok(self, event=None):
|
||||
if self.MenuOk() and self.PathOk():
|
||||
self.result = (self.menu.get().strip(),
|
||||
self.path.get().strip())
|
||||
self.destroy()
|
||||
|
||||
def Cancel(self, event=None):
|
||||
self.result = None
|
||||
self.destroy()
|
||||
|
||||
if __name__ == '__main__':
|
||||
#test the dialog
|
||||
root = Tk()
|
||||
def run():
|
||||
keySeq = ''
|
||||
dlg = GetHelpSourceDialog(root, 'Get Help Source')
|
||||
print dlg.result
|
||||
Button(root,text='Dialog', command=run).pack()
|
||||
root.mainloop()
|
97
Tools/idle/configSectionNameDialog.py
Normal file
97
Tools/idle/configSectionNameDialog.py
Normal file
|
@ -0,0 +1,97 @@
|
|||
"""
|
||||
Dialog that allows user to specify a new config file section name.
|
||||
Used to get new highlight theme and keybinding set names.
|
||||
"""
|
||||
from Tkinter import *
|
||||
import tkMessageBox
|
||||
|
||||
class GetCfgSectionNameDialog(Toplevel):
|
||||
def __init__(self,parent,title,message,usedNames):
|
||||
"""
|
||||
message - string, informational message to display
|
||||
usedNames - list, list of names already in use for validity check
|
||||
"""
|
||||
Toplevel.__init__(self, parent)
|
||||
self.configure(borderwidth=5)
|
||||
self.resizable(height=FALSE,width=FALSE)
|
||||
self.title(title)
|
||||
self.transient(parent)
|
||||
self.grab_set()
|
||||
self.protocol("WM_DELETE_WINDOW", self.Cancel)
|
||||
self.parent = parent
|
||||
self.message=message
|
||||
self.usedNames=usedNames
|
||||
self.result=''
|
||||
self.CreateWidgets()
|
||||
self.withdraw() #hide while setting geometry
|
||||
self.update_idletasks()
|
||||
#needs to be done here so that the winfo_reqwidth is valid
|
||||
self.messageInfo.config(width=self.frameMain.winfo_reqwidth())
|
||||
self.geometry("+%d+%d" %
|
||||
((parent.winfo_rootx()+((parent.winfo_width()/2)
|
||||
-(self.winfo_reqwidth()/2)),
|
||||
parent.winfo_rooty()+((parent.winfo_height()/2)
|
||||
-(self.winfo_reqheight()/2)) )) ) #centre dialog over parent
|
||||
self.deiconify() #geometry set, unhide
|
||||
self.wait_window()
|
||||
|
||||
def CreateWidgets(self):
|
||||
self.name=StringVar(self)
|
||||
self.fontSize=StringVar(self)
|
||||
self.frameMain = Frame(self,borderwidth=2,relief=SUNKEN)
|
||||
self.frameMain.pack(side=TOP,expand=TRUE,fill=BOTH)
|
||||
self.messageInfo=Message(self.frameMain,anchor=W,justify=LEFT,padx=5,pady=5,
|
||||
text=self.message)#,aspect=200)
|
||||
entryName=Entry(self.frameMain,textvariable=self.name,width=30)
|
||||
entryName.focus_set()
|
||||
self.messageInfo.pack(padx=5,pady=5)#,expand=TRUE,fill=BOTH)
|
||||
entryName.pack(padx=5,pady=5)
|
||||
frameButtons=Frame(self)
|
||||
frameButtons.pack(side=BOTTOM,fill=X)
|
||||
self.buttonOk = Button(frameButtons,text='Ok',
|
||||
width=8,command=self.Ok)
|
||||
self.buttonOk.grid(row=0,column=0,padx=5,pady=5)
|
||||
self.buttonCancel = Button(frameButtons,text='Cancel',
|
||||
width=8,command=self.Cancel)
|
||||
self.buttonCancel.grid(row=0,column=1,padx=5,pady=5)
|
||||
|
||||
def NameOk(self):
|
||||
#simple validity check for a sensible
|
||||
#ConfigParser file section name
|
||||
nameOk=1
|
||||
name=self.name.get()
|
||||
name.strip()
|
||||
if not name: #no name specified
|
||||
tkMessageBox.showerror(title='Name Error',
|
||||
message='No name specified.', parent=self)
|
||||
nameOk=0
|
||||
elif len(name)>30: #name too long
|
||||
tkMessageBox.showerror(title='Name Error',
|
||||
message='Name too long. It should be no more than '+
|
||||
'30 characters.', parent=self)
|
||||
nameOk=0
|
||||
elif name in self.usedNames:
|
||||
tkMessageBox.showerror(title='Name Error',
|
||||
message='This name is already in use.', parent=self)
|
||||
nameOk=0
|
||||
return nameOk
|
||||
|
||||
def Ok(self, event=None):
|
||||
if self.NameOk():
|
||||
self.result=self.name.get().strip()
|
||||
self.destroy()
|
||||
|
||||
def Cancel(self, event=None):
|
||||
self.result=''
|
||||
self.destroy()
|
||||
|
||||
if __name__ == '__main__':
|
||||
#test the dialog
|
||||
root=Tk()
|
||||
def run():
|
||||
keySeq=''
|
||||
dlg=GetCfgSectionNameDialog(root,'Get Name',
|
||||
'The information here should need to be word wrapped. Test.')
|
||||
print dlg.result
|
||||
Button(root,text='Dialog',command=run).pack()
|
||||
root.mainloop()
|
35
Tools/idle/dynOptionMenuWidget.py
Normal file
35
Tools/idle/dynOptionMenuWidget.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
"""
|
||||
OptionMenu widget modified to allow dynamic menu reconfiguration
|
||||
and setting of highlightthickness
|
||||
"""
|
||||
from Tkinter import OptionMenu
|
||||
from Tkinter import _setit
|
||||
import copy
|
||||
|
||||
class DynOptionMenu(OptionMenu):
|
||||
"""
|
||||
unlike OptionMenu, our kwargs can include highlightthickness
|
||||
"""
|
||||
def __init__(self, master, variable, value, *values, **kwargs):
|
||||
#get a copy of kwargs before OptionMenu.__init__ munges them
|
||||
kwargsCopy=copy.copy(kwargs)
|
||||
if 'highlightthickness' in kwargs.keys():
|
||||
del(kwargs['highlightthickness'])
|
||||
OptionMenu.__init__(self, master, variable, value, *values, **kwargs)
|
||||
self.config(highlightthickness=kwargsCopy.get('highlightthickness'))
|
||||
#self.menu=self['menu']
|
||||
self.variable=variable
|
||||
self.command=kwargs.get('command')
|
||||
|
||||
def SetMenu(self,valueList,value=None):
|
||||
"""
|
||||
clear and reload the menu with a new set of options.
|
||||
valueList - list of new options
|
||||
value - initial value to set the optionmenu's menubutton to
|
||||
"""
|
||||
self['menu'].delete(0,'end')
|
||||
for item in valueList:
|
||||
self['menu'].add_command(label=item,
|
||||
command=_setit(self.variable,item,self.command))
|
||||
if value:
|
||||
self.variable.set(value)
|
|
@ -1,89 +0,0 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
"""Parse event definitions out of comments in source files."""
|
||||
|
||||
import sys
|
||||
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(line[2:-1].strip())
|
||||
else:
|
||||
if sublist:
|
||||
hits.append(sublist)
|
||||
sublist = []
|
||||
if sublist:
|
||||
hits.append(sublist)
|
||||
sublist = []
|
||||
dd = {}
|
||||
for sublist in hits:
|
||||
d = {}
|
||||
for line in sublist:
|
||||
words = line.split(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())
|
49
Tools/idle/interruptmodule.c
Normal file
49
Tools/idle/interruptmodule.c
Normal file
|
@ -0,0 +1,49 @@
|
|||
/***********************************************************************
|
||||
* interruptmodule.c
|
||||
*
|
||||
* Python extension implementing the interrupt module.
|
||||
*
|
||||
**********************************************************************/
|
||||
|
||||
#include "Python.h"
|
||||
|
||||
#ifndef PyDoc_STR
|
||||
#define PyDoc_VAR(name) static char name[]
|
||||
#define PyDoc_STR(str) str
|
||||
#define PyDoc_STRVAR(name,str) PyDoc_VAR(name) = PyDoc_STR(str)
|
||||
#endif
|
||||
|
||||
/* module documentation */
|
||||
|
||||
PyDoc_STRVAR(module_doc,
|
||||
"Provide a way to interrupt the main thread from a subthread.\n\n\
|
||||
In threaded Python code the KeyboardInterrupt is always directed to\n\
|
||||
the thread which raised it. This extension provides a method,\n\
|
||||
interrupt_main, which a subthread can use to raise a KeyboardInterrupt\n\
|
||||
in the main thread.");
|
||||
|
||||
/* module functions */
|
||||
|
||||
static PyObject *
|
||||
setinterrupt(PyObject * self, PyObject * args)
|
||||
{
|
||||
PyErr_SetInterrupt();
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
/* registration table */
|
||||
|
||||
static struct PyMethodDef methods[] = {
|
||||
{"interrupt_main", setinterrupt, METH_VARARGS,
|
||||
PyDoc_STR("Interrupt the main thread")},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
/* module initialization */
|
||||
|
||||
void
|
||||
initinterrupt(void)
|
||||
{
|
||||
(void) Py_InitModule3("interrupt", methods, module_doc);
|
||||
}
|
262
Tools/idle/keybindingDialog.py
Normal file
262
Tools/idle/keybindingDialog.py
Normal file
|
@ -0,0 +1,262 @@
|
|||
"""
|
||||
dialog for building tkinter accelerator key bindings
|
||||
"""
|
||||
from Tkinter import *
|
||||
import tkMessageBox
|
||||
import string, os
|
||||
|
||||
class GetKeysDialog(Toplevel):
|
||||
def __init__(self,parent,title,action,currentKeySequences):
|
||||
"""
|
||||
action - string, the name of the virtual event these keys will be
|
||||
mapped to
|
||||
currentKeys - list, a list of all key sequence lists currently mapped
|
||||
to virtual events, for overlap checking
|
||||
"""
|
||||
Toplevel.__init__(self, parent)
|
||||
self.configure(borderwidth=5)
|
||||
self.resizable(height=FALSE,width=FALSE)
|
||||
self.title(title)
|
||||
self.transient(parent)
|
||||
self.grab_set()
|
||||
self.protocol("WM_DELETE_WINDOW", self.Cancel)
|
||||
self.parent = parent
|
||||
self.action=action
|
||||
self.currentKeySequences=currentKeySequences
|
||||
self.result=''
|
||||
self.keyString=StringVar(self)
|
||||
self.keyString.set('')
|
||||
self.SetModifiersForPlatform()
|
||||
self.modifier_vars = []
|
||||
for modifier in self.modifiers:
|
||||
variable = StringVar(self)
|
||||
variable.set('')
|
||||
self.modifier_vars.append(variable)
|
||||
self.CreateWidgets()
|
||||
self.LoadFinalKeyList()
|
||||
self.withdraw() #hide while setting geometry
|
||||
self.update_idletasks()
|
||||
self.geometry("+%d+%d" %
|
||||
((parent.winfo_rootx()+((parent.winfo_width()/2)
|
||||
-(self.winfo_reqwidth()/2)),
|
||||
parent.winfo_rooty()+((parent.winfo_height()/2)
|
||||
-(self.winfo_reqheight()/2)) )) ) #centre dialog over parent
|
||||
self.deiconify() #geometry set, unhide
|
||||
self.wait_window()
|
||||
|
||||
def CreateWidgets(self):
|
||||
frameMain = Frame(self,borderwidth=2,relief=SUNKEN)
|
||||
frameMain.pack(side=TOP,expand=TRUE,fill=BOTH)
|
||||
frameButtons=Frame(self)
|
||||
frameButtons.pack(side=BOTTOM,fill=X)
|
||||
self.buttonOk = Button(frameButtons,text='Ok',
|
||||
width=8,command=self.Ok)
|
||||
self.buttonOk.grid(row=0,column=0,padx=5,pady=5)
|
||||
self.buttonCancel = Button(frameButtons,text='Cancel',
|
||||
width=8,command=self.Cancel)
|
||||
self.buttonCancel.grid(row=0,column=1,padx=5,pady=5)
|
||||
self.frameKeySeqBasic = Frame(frameMain)
|
||||
self.frameKeySeqAdvanced = Frame(frameMain)
|
||||
self.frameControlsBasic = Frame(frameMain)
|
||||
self.frameHelpAdvanced = Frame(frameMain)
|
||||
self.frameKeySeqAdvanced.grid(row=0,column=0,sticky=NSEW,padx=5,pady=5)
|
||||
self.frameKeySeqBasic.grid(row=0,column=0,sticky=NSEW,padx=5,pady=5)
|
||||
self.frameKeySeqBasic.lift()
|
||||
self.frameHelpAdvanced.grid(row=1,column=0,sticky=NSEW,padx=5)
|
||||
self.frameControlsBasic.grid(row=1,column=0,sticky=NSEW,padx=5)
|
||||
self.frameControlsBasic.lift()
|
||||
self.buttonLevel = Button(frameMain,command=self.ToggleLevel,
|
||||
text='Advanced Key Binding Entry >>')
|
||||
self.buttonLevel.grid(row=2,column=0,stick=EW,padx=5,pady=5)
|
||||
labelTitleBasic = Label(self.frameKeySeqBasic,
|
||||
text="New keys for '"+self.action+"' :")
|
||||
labelTitleBasic.pack(anchor=W)
|
||||
labelKeysBasic = Label(self.frameKeySeqBasic,justify=LEFT,
|
||||
textvariable=self.keyString,relief=GROOVE,borderwidth=2)
|
||||
labelKeysBasic.pack(ipadx=5,ipady=5,fill=X)
|
||||
self.modifier_checkbuttons = {}
|
||||
column = 0
|
||||
for modifier, variable in zip(self.modifiers, self.modifier_vars):
|
||||
label = self.modifier_label.get(modifier, modifier)
|
||||
check=Checkbutton(self.frameControlsBasic,
|
||||
command=self.BuildKeyString,
|
||||
text=label,variable=variable,onvalue=modifier,offvalue='')
|
||||
check.grid(row=0,column=column,padx=2,sticky=W)
|
||||
self.modifier_checkbuttons[modifier] = check
|
||||
column += 1
|
||||
labelFnAdvice=Label(self.frameControlsBasic,justify=LEFT,
|
||||
text="Select the desired modifier\n"+
|
||||
"keys above, and final key\n"+
|
||||
"from the list on the right.")
|
||||
labelFnAdvice.grid(row=1,column=0,columnspan=4,padx=2,sticky=W)
|
||||
self.listKeysFinal=Listbox(self.frameControlsBasic,width=15,height=10,
|
||||
selectmode=SINGLE)
|
||||
self.listKeysFinal.bind('<ButtonRelease-1>',self.FinalKeySelected)
|
||||
self.listKeysFinal.grid(row=0,column=4,rowspan=4,sticky=NS)
|
||||
scrollKeysFinal=Scrollbar(self.frameControlsBasic,orient=VERTICAL,
|
||||
command=self.listKeysFinal.yview)
|
||||
self.listKeysFinal.config(yscrollcommand=scrollKeysFinal.set)
|
||||
scrollKeysFinal.grid(row=0,column=5,rowspan=4,sticky=NS)
|
||||
self.buttonClear=Button(self.frameControlsBasic,
|
||||
text='Clear Keys',command=self.ClearKeySeq)
|
||||
self.buttonClear.grid(row=2,column=0,columnspan=4)
|
||||
labelTitleAdvanced = Label(self.frameKeySeqAdvanced,justify=LEFT,
|
||||
text="Enter new binding(s) for '"+self.action+"' :\n"+
|
||||
"(will not be checked for validity)")
|
||||
labelTitleAdvanced.pack(anchor=W)
|
||||
self.entryKeysAdvanced=Entry(self.frameKeySeqAdvanced,
|
||||
textvariable=self.keyString)
|
||||
self.entryKeysAdvanced.pack(fill=X)
|
||||
labelHelpAdvanced=Label(self.frameHelpAdvanced,justify=LEFT,
|
||||
text="Key bindings are specified using tkinter key id's as\n"+
|
||||
"in these samples: <Control-f>, <Shift-F2>, <F12>,\n"
|
||||
"<Control-space>, <Meta-less>, <Control-Alt-Shift-x>.\n\n"+
|
||||
"'Emacs style' multi-keystroke bindings are specified as\n"+
|
||||
"follows: <Control-x><Control-y> or <Meta-f><Meta-g>.\n\n"+
|
||||
"Multiple separate bindings for one action should be\n"+
|
||||
"separated by a space, eg., <Alt-v> <Meta-v>." )
|
||||
labelHelpAdvanced.grid(row=0,column=0,sticky=NSEW)
|
||||
|
||||
def SetModifiersForPlatform(self):
|
||||
"""Determine list of names of key modifiers for this platform.
|
||||
|
||||
The names are used to build Tk bindings -- it doesn't matter if the
|
||||
keyboard has these keys, it matters if Tk understands them. The
|
||||
order is also important: key binding equality depends on it, so
|
||||
config-keys.def must use the same ordering.
|
||||
"""
|
||||
import sys
|
||||
if sys.platform == 'darwin' and sys.executable.count('.app'):
|
||||
self.modifiers = ['Shift', 'Control', 'Option', 'Command']
|
||||
else:
|
||||
self.modifiers = ['Control', 'Alt', 'Shift']
|
||||
self.modifier_label = {'Control': 'Ctrl'}
|
||||
|
||||
def ToggleLevel(self):
|
||||
if self.buttonLevel.cget('text')[:8]=='Advanced':
|
||||
self.ClearKeySeq()
|
||||
self.buttonLevel.config(text='<< Basic Key Binding Entry')
|
||||
self.frameKeySeqAdvanced.lift()
|
||||
self.frameHelpAdvanced.lift()
|
||||
self.entryKeysAdvanced.focus_set()
|
||||
else:
|
||||
self.ClearKeySeq()
|
||||
self.buttonLevel.config(text='Advanced Key Binding Entry >>')
|
||||
self.frameKeySeqBasic.lift()
|
||||
self.frameControlsBasic.lift()
|
||||
|
||||
def FinalKeySelected(self,event):
|
||||
self.BuildKeyString()
|
||||
|
||||
def BuildKeyString(self):
|
||||
keyList=[]
|
||||
modifiers=self.GetModifiers()
|
||||
finalKey=self.listKeysFinal.get(ANCHOR)
|
||||
if modifiers: modifiers[0]='<'+modifiers[0]
|
||||
keyList=keyList+modifiers
|
||||
if finalKey:
|
||||
if (not modifiers) and (finalKey not
|
||||
in self.alphanumKeys+self.punctuationKeys):
|
||||
finalKey='<'+self.TranslateKey(finalKey)
|
||||
else:
|
||||
finalKey=self.TranslateKey(finalKey)
|
||||
keyList.append(finalKey+'>')
|
||||
keyStr=string.join(keyList,'-')
|
||||
self.keyString.set(keyStr)
|
||||
|
||||
def GetModifiers(self):
|
||||
modList = [variable.get() for variable in self.modifier_vars]
|
||||
return filter(None, modList)
|
||||
|
||||
def ClearKeySeq(self):
|
||||
self.listKeysFinal.select_clear(0,END)
|
||||
self.listKeysFinal.yview(MOVETO, '0.0')
|
||||
for variable in self.modifier_vars:
|
||||
variable.set('')
|
||||
self.keyString.set('')
|
||||
|
||||
def LoadFinalKeyList(self):
|
||||
#these tuples are also available for use in validity checks
|
||||
self.functionKeys=('F1','F2','F2','F4','F5','F6','F7','F8','F9',
|
||||
'F10','F11','F12')
|
||||
self.alphanumKeys=tuple(string.ascii_lowercase+string.digits)
|
||||
self.punctuationKeys=tuple('~!@#%^&*()_-+={}[]|;:,.<>/?')
|
||||
self.whitespaceKeys=('Tab','Space','Return')
|
||||
self.editKeys=('BackSpace','Delete','Insert')
|
||||
self.moveKeys=('Home','End','Page Up','Page Down','Left Arrow',
|
||||
'Right Arrow','Up Arrow','Down Arrow')
|
||||
#make a tuple of most of the useful common 'final' keys
|
||||
keys=(self.alphanumKeys+self.punctuationKeys+self.functionKeys+
|
||||
self.whitespaceKeys+self.editKeys+self.moveKeys)
|
||||
apply(self.listKeysFinal.insert,
|
||||
(END,)+keys)
|
||||
|
||||
def TranslateKey(self,key):
|
||||
#translate from key list value to tkinter key-id
|
||||
translateDict={'~':'asciitilde','!':'exclam','@':'at','#':'numbersign',
|
||||
'%':'percent','^':'asciicircum','&':'ampersand','*':'asterisk',
|
||||
'(':'parenleft',')':'parenright','_':'underscore','-':'minus',
|
||||
'+':'plus','=':'equal','{':'braceleft','}':'braceright',
|
||||
'[':'bracketleft',']':'bracketright','|':'bar',';':'semicolon',
|
||||
':':'colon',',':'comma','.':'period','<':'less','>':'greater',
|
||||
'/':'slash','?':'question','Page Up':'Prior','Page Down':'Next',
|
||||
'Left Arrow':'Left','Right Arrow':'Right','Up Arrow':'Up',
|
||||
'Down Arrow': 'Down'}
|
||||
if key in translateDict.keys():
|
||||
key=translateDict[key]
|
||||
key='Key-'+key
|
||||
return key
|
||||
|
||||
def Ok(self, event=None):
|
||||
if self.KeysOk():
|
||||
self.result=self.keyString.get()
|
||||
self.destroy()
|
||||
|
||||
def Cancel(self, event=None):
|
||||
self.result=''
|
||||
self.destroy()
|
||||
|
||||
def KeysOk(self):
|
||||
#simple validity check
|
||||
keysOk=1
|
||||
keys=self.keyString.get()
|
||||
keys.strip()
|
||||
finalKey=self.listKeysFinal.get(ANCHOR)
|
||||
modifiers=self.GetModifiers()
|
||||
keySequence=keys.split()#make into a key sequence list for overlap check
|
||||
if not keys: #no keys specified
|
||||
tkMessageBox.showerror(title='Key Sequence Error',
|
||||
message='No keys specified.')
|
||||
keysOk=0
|
||||
elif not keys.endswith('>'): #no final key specified
|
||||
tkMessageBox.showerror(title='Key Sequence Error',
|
||||
message='No final key specified.')
|
||||
keysOk=0
|
||||
elif (not modifiers) and (finalKey in
|
||||
self.alphanumKeys+self.punctuationKeys):
|
||||
#modifier required
|
||||
tkMessageBox.showerror(title='Key Sequence Error',
|
||||
message='No modifier key(s) specified.')
|
||||
keysOk=0
|
||||
elif (modifiers==['Shift']) and (finalKey not
|
||||
in self.functionKeys+('Tab',)):
|
||||
#shift alone is only a useful modifier with a function key
|
||||
tkMessageBox.showerror(title='Key Sequence Error',
|
||||
message='Shift alone is not a useful modifier '+
|
||||
'when used with this final key key.')
|
||||
keysOk=0
|
||||
elif keySequence in self.currentKeySequences: #keys combo already in use
|
||||
tkMessageBox.showerror(title='Key Sequence Error',
|
||||
message='This key combination is already in use.')
|
||||
keysOk=0
|
||||
return keysOk
|
||||
|
||||
if __name__ == '__main__':
|
||||
#test the dialog
|
||||
root=Tk()
|
||||
def run():
|
||||
keySeq=''
|
||||
dlg=GetKeysDialog(root,'Get Keys','find-again',[])
|
||||
print dlg.result
|
||||
Button(root,text='Dialog',command=run).pack()
|
||||
root.mainloop()
|
|
@ -1,57 +0,0 @@
|
|||
windows_keydefs = \
|
||||
{'<<Copy>>': ['<Control-c>', '<Control-C>'],
|
||||
'<<Cut>>': ['<Control-x>', '<Control-X>'],
|
||||
'<<Paste>>': ['<Control-v>', '<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>'],
|
||||
'<<help>>': ['<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>'],
|
||||
'<<print-window>>': ['<Control-p>'],
|
||||
'<<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>>': ['<Control-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>'],
|
||||
'<<print-window>>': ['<Control-x><Control-p>'],
|
||||
'<<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>']}
|
42
Tools/idle/macosx_main.py
Normal file
42
Tools/idle/macosx_main.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
#!/usr/bin/env pythonw
|
||||
# IDLE.app
|
||||
#
|
||||
# Installation:
|
||||
# see the install_IDLE target in python/dist/src/Mac/OSX/Makefile
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# 1. Double clicking IDLE icon will open IDLE.
|
||||
# 2. Dropping file on IDLE icon will open that file in IDLE.
|
||||
# 3. Launch from command line with files with this command-line:
|
||||
#
|
||||
# /Applications/Python/IDLE.app/Contents/MacOS/python file1 file2 file3
|
||||
#
|
||||
#
|
||||
|
||||
# Add IDLE.app/Contents/Resources/idlelib to path.
|
||||
# __file__ refers to this file when it is used as a module, sys.argv[0]
|
||||
# refers to this file when it is used as a script (pythonw macosx_main.py)
|
||||
import sys
|
||||
|
||||
from os.path import split, join, isdir
|
||||
try:
|
||||
__file__
|
||||
except NameError:
|
||||
__file__ = sys.argv[0]
|
||||
idlelib = join(split(__file__)[0], 'idlelib')
|
||||
if isdir(idlelib):
|
||||
sys.path.append(idlelib)
|
||||
|
||||
# see if we are being asked to execute the subprocess code
|
||||
if '-p' in sys.argv:
|
||||
# run expects only the port number in sys.argv
|
||||
sys.argv.remove('-p')
|
||||
|
||||
# this module will become the namespace used by the interactive
|
||||
# interpreter; remove all variables we have defined.
|
||||
del sys, __file__, split, join, isdir, idlelib
|
||||
__import__('run').main()
|
||||
else:
|
||||
# Load idlelib/idle.py which starts the application.
|
||||
import idle
|
580
Tools/idle/rpc.py
Normal file
580
Tools/idle/rpc.py
Normal file
|
@ -0,0 +1,580 @@
|
|||
"""RPC Implemention, originally written for the Python Idle IDE
|
||||
|
||||
For security reasons, GvR requested that Idle's Python execution server process
|
||||
connect to the Idle process, which listens for the connection. Since Idle has
|
||||
has only one client per server, this was not a limitation.
|
||||
|
||||
+---------------------------------+ +-------------+
|
||||
| SocketServer.BaseRequestHandler | | SocketIO |
|
||||
+---------------------------------+ +-------------+
|
||||
^ | register() |
|
||||
| | unregister()|
|
||||
| +-------------+
|
||||
| ^ ^
|
||||
| | |
|
||||
| + -------------------+ |
|
||||
| | |
|
||||
+-------------------------+ +-----------------+
|
||||
| RPCHandler | | RPCClient |
|
||||
| [attribute of RPCServer]| | |
|
||||
+-------------------------+ +-----------------+
|
||||
|
||||
The RPCServer handler class is expected to provide register/unregister methods.
|
||||
RPCHandler inherits the mix-in class SocketIO, which provides these methods.
|
||||
|
||||
See the Idle run.main() docstring for further information on how this was
|
||||
accomplished in Idle.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import socket
|
||||
import select
|
||||
import SocketServer
|
||||
import struct
|
||||
import cPickle as pickle
|
||||
import threading
|
||||
import traceback
|
||||
import copy_reg
|
||||
import types
|
||||
import marshal
|
||||
|
||||
def unpickle_code(ms):
|
||||
co = marshal.loads(ms)
|
||||
assert isinstance(co, types.CodeType)
|
||||
return co
|
||||
|
||||
def pickle_code(co):
|
||||
assert isinstance(co, types.CodeType)
|
||||
ms = marshal.dumps(co)
|
||||
return unpickle_code, (ms,)
|
||||
|
||||
# XXX KBK 24Aug02 function pickling capability not used in Idle
|
||||
# def unpickle_function(ms):
|
||||
# return ms
|
||||
|
||||
# def pickle_function(fn):
|
||||
# assert isinstance(fn, type.FunctionType)
|
||||
# return `fn`
|
||||
|
||||
copy_reg.pickle(types.CodeType, pickle_code, unpickle_code)
|
||||
# copy_reg.pickle(types.FunctionType, pickle_function, unpickle_function)
|
||||
|
||||
BUFSIZE = 8*1024
|
||||
|
||||
class RPCServer(SocketServer.TCPServer):
|
||||
|
||||
def __init__(self, addr, handlerclass=None):
|
||||
if handlerclass is None:
|
||||
handlerclass = RPCHandler
|
||||
SocketServer.TCPServer.__init__(self, addr, handlerclass)
|
||||
|
||||
def server_bind(self):
|
||||
"Override TCPServer method, no bind() phase for connecting entity"
|
||||
pass
|
||||
|
||||
def server_activate(self):
|
||||
"""Override TCPServer method, connect() instead of listen()
|
||||
|
||||
Due to the reversed connection, self.server_address is actually the
|
||||
address of the Idle Client to which we are connecting.
|
||||
|
||||
"""
|
||||
self.socket.connect(self.server_address)
|
||||
|
||||
def get_request(self):
|
||||
"Override TCPServer method, return already connected socket"
|
||||
return self.socket, self.server_address
|
||||
|
||||
def handle_error(self, request, client_address):
|
||||
"""Override TCPServer method
|
||||
|
||||
Error message goes to __stderr__. No error message if exiting
|
||||
normally or socket raised EOF. Other exceptions not handled in
|
||||
server code will cause os._exit.
|
||||
|
||||
"""
|
||||
try:
|
||||
raise
|
||||
except SystemExit:
|
||||
raise
|
||||
except EOFError:
|
||||
pass
|
||||
except:
|
||||
erf = sys.__stderr__
|
||||
print>>erf, '\n' + '-'*40
|
||||
print>>erf, 'Unhandled server exception!'
|
||||
print>>erf, 'Thread: %s' % threading.currentThread().getName()
|
||||
print>>erf, 'Client Address: ', client_address
|
||||
print>>erf, 'Request: ', repr(request)
|
||||
traceback.print_exc(file=erf)
|
||||
print>>erf, '\n*** Unrecoverable, server exiting!'
|
||||
print>>erf, '-'*40
|
||||
import os
|
||||
os._exit(0)
|
||||
|
||||
|
||||
objecttable = {}
|
||||
|
||||
class SocketIO:
|
||||
|
||||
nextseq = 0
|
||||
|
||||
def __init__(self, sock, objtable=None, debugging=None):
|
||||
self.mainthread = threading.currentThread()
|
||||
if debugging is not None:
|
||||
self.debugging = debugging
|
||||
self.sock = sock
|
||||
if objtable is None:
|
||||
objtable = objecttable
|
||||
self.objtable = objtable
|
||||
self.cvar = threading.Condition()
|
||||
self.responses = {}
|
||||
self.cvars = {}
|
||||
self.interrupted = False
|
||||
|
||||
def close(self):
|
||||
sock = self.sock
|
||||
self.sock = None
|
||||
if sock is not None:
|
||||
sock.close()
|
||||
|
||||
def debug(self, *args):
|
||||
if not self.debugging:
|
||||
return
|
||||
s = self.location + " " + str(threading.currentThread().getName())
|
||||
for a in args:
|
||||
s = s + " " + str(a)
|
||||
print>>sys.__stderr__, s
|
||||
|
||||
def register(self, oid, object):
|
||||
self.objtable[oid] = object
|
||||
|
||||
def unregister(self, oid):
|
||||
try:
|
||||
del self.objtable[oid]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def localcall(self, request):
|
||||
self.debug("localcall:", request)
|
||||
try:
|
||||
how, (oid, methodname, args, kwargs) = request
|
||||
except TypeError:
|
||||
return ("ERROR", "Bad request format")
|
||||
assert how == "call"
|
||||
if not self.objtable.has_key(oid):
|
||||
return ("ERROR", "Unknown object id: %s" % `oid`)
|
||||
obj = self.objtable[oid]
|
||||
if methodname == "__methods__":
|
||||
methods = {}
|
||||
_getmethods(obj, methods)
|
||||
return ("OK", methods)
|
||||
if methodname == "__attributes__":
|
||||
attributes = {}
|
||||
_getattributes(obj, attributes)
|
||||
return ("OK", attributes)
|
||||
if not hasattr(obj, methodname):
|
||||
return ("ERROR", "Unsupported method name: %s" % `methodname`)
|
||||
method = getattr(obj, methodname)
|
||||
try:
|
||||
ret = method(*args, **kwargs)
|
||||
if isinstance(ret, RemoteObject):
|
||||
ret = remoteref(ret)
|
||||
return ("OK", ret)
|
||||
except SystemExit:
|
||||
raise
|
||||
except socket.error:
|
||||
pass
|
||||
except:
|
||||
self.debug("localcall:EXCEPTION")
|
||||
traceback.print_exc(file=sys.__stderr__)
|
||||
return ("EXCEPTION", None)
|
||||
|
||||
def remotecall(self, oid, methodname, args, kwargs):
|
||||
self.debug("remotecall:asynccall: ", oid, methodname)
|
||||
# XXX KBK 06Feb03 self.interrupted logic may not be necessary if
|
||||
# subprocess is threaded.
|
||||
if self.interrupted:
|
||||
self.interrupted = False
|
||||
raise KeyboardInterrupt
|
||||
seq = self.asynccall(oid, methodname, args, kwargs)
|
||||
return self.asyncreturn(seq)
|
||||
|
||||
def asynccall(self, oid, methodname, args, kwargs):
|
||||
request = ("call", (oid, methodname, args, kwargs))
|
||||
seq = self.newseq()
|
||||
self.debug(("asynccall:%d:" % seq), oid, methodname, args, kwargs)
|
||||
self.putmessage((seq, request))
|
||||
return seq
|
||||
|
||||
def asyncreturn(self, seq):
|
||||
self.debug("asyncreturn:%d:call getresponse(): " % seq)
|
||||
response = self.getresponse(seq, wait=None)
|
||||
self.debug(("asyncreturn:%d:response: " % seq), response)
|
||||
return self.decoderesponse(response)
|
||||
|
||||
def decoderesponse(self, response):
|
||||
how, what = response
|
||||
if how == "OK":
|
||||
return what
|
||||
if how == "EXCEPTION":
|
||||
self.debug("decoderesponse: EXCEPTION")
|
||||
return None
|
||||
if how == "ERROR":
|
||||
self.debug("decoderesponse: Internal ERROR:", what)
|
||||
raise RuntimeError, what
|
||||
raise SystemError, (how, what)
|
||||
|
||||
def mainloop(self):
|
||||
"""Listen on socket until I/O not ready or EOF
|
||||
|
||||
Main thread pollresponse() will loop looking for seq number None, which
|
||||
never comes, and exit on EOFError.
|
||||
|
||||
"""
|
||||
try:
|
||||
self.getresponse(myseq=None, wait=None)
|
||||
except EOFError:
|
||||
pass
|
||||
|
||||
def getresponse(self, myseq, wait):
|
||||
response = self._getresponse(myseq, wait)
|
||||
if response is not None:
|
||||
how, what = response
|
||||
if how == "OK":
|
||||
response = how, self._proxify(what)
|
||||
return response
|
||||
|
||||
def _proxify(self, obj):
|
||||
if isinstance(obj, RemoteProxy):
|
||||
return RPCProxy(self, obj.oid)
|
||||
if isinstance(obj, types.ListType):
|
||||
return map(self._proxify, obj)
|
||||
# XXX Check for other types -- not currently needed
|
||||
return obj
|
||||
|
||||
def _getresponse(self, myseq, wait):
|
||||
self.debug("_getresponse:myseq:", myseq)
|
||||
if threading.currentThread() is self.mainthread:
|
||||
# Main thread: does all reading of requests or responses
|
||||
# Loop here, blocking each time until socket is ready.
|
||||
while 1:
|
||||
response = self.pollresponse(myseq, wait)
|
||||
if response is not None:
|
||||
return response
|
||||
else:
|
||||
# Auxiliary thread: wait for notification from main thread
|
||||
self.cvar.acquire()
|
||||
self.cvars[myseq] = self.cvar
|
||||
while not self.responses.has_key(myseq):
|
||||
self.cvar.wait()
|
||||
response = self.responses[myseq]
|
||||
del self.responses[myseq]
|
||||
del self.cvars[myseq]
|
||||
self.cvar.release()
|
||||
return response
|
||||
|
||||
def newseq(self):
|
||||
self.nextseq = seq = self.nextseq + 2
|
||||
return seq
|
||||
|
||||
def putmessage(self, message):
|
||||
self.debug("putmessage:%d:" % message[0])
|
||||
try:
|
||||
s = pickle.dumps(message)
|
||||
except:
|
||||
print >>sys.__stderr__, "Cannot pickle:", `message`
|
||||
raise
|
||||
s = struct.pack("<i", len(s)) + s
|
||||
while len(s) > 0:
|
||||
try:
|
||||
n = self.sock.send(s)
|
||||
except AttributeError:
|
||||
# socket was closed
|
||||
raise IOError
|
||||
else:
|
||||
s = s[n:]
|
||||
|
||||
def ioready(self, wait=0.0):
|
||||
r, w, x = select.select([self.sock.fileno()], [], [], wait)
|
||||
return len(r)
|
||||
|
||||
buffer = ""
|
||||
bufneed = 4
|
||||
bufstate = 0 # meaning: 0 => reading count; 1 => reading data
|
||||
|
||||
def pollpacket(self, wait=0.0):
|
||||
self._stage0()
|
||||
if len(self.buffer) < self.bufneed:
|
||||
if not self.ioready(wait):
|
||||
return None
|
||||
try:
|
||||
s = self.sock.recv(BUFSIZE)
|
||||
except socket.error:
|
||||
raise EOFError
|
||||
if len(s) == 0:
|
||||
raise EOFError
|
||||
self.buffer += s
|
||||
self._stage0()
|
||||
return self._stage1()
|
||||
|
||||
def _stage0(self):
|
||||
if self.bufstate == 0 and len(self.buffer) >= 4:
|
||||
s = self.buffer[:4]
|
||||
self.buffer = self.buffer[4:]
|
||||
self.bufneed = struct.unpack("<i", s)[0]
|
||||
self.bufstate = 1
|
||||
|
||||
def _stage1(self):
|
||||
if self.bufstate == 1 and len(self.buffer) >= self.bufneed:
|
||||
packet = self.buffer[:self.bufneed]
|
||||
self.buffer = self.buffer[self.bufneed:]
|
||||
self.bufneed = 4
|
||||
self.bufstate = 0
|
||||
return packet
|
||||
|
||||
def pollmessage(self, wait=0.0):
|
||||
packet = self.pollpacket(wait)
|
||||
if packet is None:
|
||||
return None
|
||||
try:
|
||||
message = pickle.loads(packet)
|
||||
except:
|
||||
print >>sys.__stderr__, "-----------------------"
|
||||
print >>sys.__stderr__, "cannot unpickle packet:", `packet`
|
||||
traceback.print_stack(file=sys.__stderr__)
|
||||
print >>sys.__stderr__, "-----------------------"
|
||||
raise
|
||||
return message
|
||||
|
||||
def pollresponse(self, myseq, wait=0.0):
|
||||
"""Handle messages received on the socket.
|
||||
|
||||
Some messages received may be asynchronous 'call' commands, and
|
||||
some may be responses intended for other threads.
|
||||
|
||||
Loop until message with myseq sequence number is received. Save others
|
||||
in self.responses and notify the owning thread, except that 'call'
|
||||
commands are handed off to localcall() and the response sent back
|
||||
across the link with the appropriate sequence number.
|
||||
|
||||
"""
|
||||
while 1:
|
||||
message = self.pollmessage(wait)
|
||||
if message is None: # socket not ready
|
||||
return None
|
||||
#wait = 0.0 # poll on subsequent passes instead of blocking
|
||||
seq, resq = message
|
||||
self.debug("pollresponse:%d:myseq:%s" % (seq, myseq))
|
||||
if resq[0] == "call":
|
||||
self.debug("pollresponse:%d:localcall:call:" % seq)
|
||||
response = self.localcall(resq)
|
||||
self.debug("pollresponse:%d:localcall:response:%s"
|
||||
% (seq, response))
|
||||
self.putmessage((seq, response))
|
||||
continue
|
||||
elif seq == myseq:
|
||||
return resq
|
||||
else:
|
||||
self.cvar.acquire()
|
||||
cv = self.cvars.get(seq)
|
||||
# response involving unknown sequence number is discarded,
|
||||
# probably intended for prior incarnation
|
||||
if cv is not None:
|
||||
self.responses[seq] = resq
|
||||
cv.notify()
|
||||
self.cvar.release()
|
||||
continue
|
||||
|
||||
#----------------- end class SocketIO --------------------
|
||||
|
||||
class RemoteObject:
|
||||
# Token mix-in class
|
||||
pass
|
||||
|
||||
def remoteref(obj):
|
||||
oid = id(obj)
|
||||
objecttable[oid] = obj
|
||||
return RemoteProxy(oid)
|
||||
|
||||
class RemoteProxy:
|
||||
|
||||
def __init__(self, oid):
|
||||
self.oid = oid
|
||||
|
||||
class RPCHandler(SocketServer.BaseRequestHandler, SocketIO):
|
||||
|
||||
debugging = False
|
||||
location = "#S" # Server
|
||||
|
||||
def __init__(self, sock, addr, svr):
|
||||
svr.current_handler = self ## cgt xxx
|
||||
SocketIO.__init__(self, sock)
|
||||
SocketServer.BaseRequestHandler.__init__(self, sock, addr, svr)
|
||||
|
||||
def handle(self):
|
||||
"handle() method required by SocketServer"
|
||||
self.mainloop()
|
||||
|
||||
def get_remote_proxy(self, oid):
|
||||
return RPCProxy(self, oid)
|
||||
|
||||
class RPCClient(SocketIO):
|
||||
|
||||
debugging = False
|
||||
location = "#C" # Client
|
||||
|
||||
nextseq = 1 # Requests coming from the client are odd numbered
|
||||
|
||||
def __init__(self, address, family=socket.AF_INET, type=socket.SOCK_STREAM):
|
||||
self.listening_sock = socket.socket(family, type)
|
||||
self.listening_sock.setsockopt(socket.SOL_SOCKET,
|
||||
socket.SO_REUSEADDR, 1)
|
||||
self.listening_sock.bind(address)
|
||||
self.listening_sock.listen(1)
|
||||
|
||||
def accept(self):
|
||||
working_sock, address = self.listening_sock.accept()
|
||||
if self.debugging:
|
||||
print>>sys.__stderr__, "****** Connection request from ", address
|
||||
if address[0] == '127.0.0.1':
|
||||
SocketIO.__init__(self, working_sock)
|
||||
else:
|
||||
print>>sys.__stderr__, "** Invalid host: ", address
|
||||
raise socket.error
|
||||
|
||||
def get_remote_proxy(self, oid):
|
||||
return RPCProxy(self, oid)
|
||||
|
||||
class RPCProxy:
|
||||
|
||||
__methods = None
|
||||
__attributes = None
|
||||
|
||||
def __init__(self, sockio, oid):
|
||||
self.sockio = sockio
|
||||
self.oid = oid
|
||||
|
||||
def __getattr__(self, name):
|
||||
if self.__methods is None:
|
||||
self.__getmethods()
|
||||
if self.__methods.get(name):
|
||||
return MethodProxy(self.sockio, self.oid, name)
|
||||
if self.__attributes is None:
|
||||
self.__getattributes()
|
||||
if not self.__attributes.has_key(name):
|
||||
raise AttributeError, name
|
||||
__getattr__.DebuggerStepThrough=1
|
||||
|
||||
def __getattributes(self):
|
||||
self.__attributes = self.sockio.remotecall(self.oid,
|
||||
"__attributes__", (), {})
|
||||
|
||||
def __getmethods(self):
|
||||
self.__methods = self.sockio.remotecall(self.oid,
|
||||
"__methods__", (), {})
|
||||
|
||||
def _getmethods(obj, methods):
|
||||
# Helper to get a list of methods from an object
|
||||
# Adds names to dictionary argument 'methods'
|
||||
for name in dir(obj):
|
||||
attr = getattr(obj, name)
|
||||
if callable(attr):
|
||||
methods[name] = 1
|
||||
if type(obj) == types.InstanceType:
|
||||
_getmethods(obj.__class__, methods)
|
||||
if type(obj) == types.ClassType:
|
||||
for super in obj.__bases__:
|
||||
_getmethods(super, methods)
|
||||
|
||||
def _getattributes(obj, attributes):
|
||||
for name in dir(obj):
|
||||
attr = getattr(obj, name)
|
||||
if not callable(attr):
|
||||
attributes[name] = 1
|
||||
|
||||
class MethodProxy:
|
||||
|
||||
def __init__(self, sockio, oid, name):
|
||||
self.sockio = sockio
|
||||
self.oid = oid
|
||||
self.name = name
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
value = self.sockio.remotecall(self.oid, self.name, args, kwargs)
|
||||
return value
|
||||
|
||||
#
|
||||
# Self Test
|
||||
#
|
||||
|
||||
def testServer(addr):
|
||||
# XXX 25 Jul 02 KBK needs update to use rpc.py register/unregister methods
|
||||
class RemotePerson:
|
||||
def __init__(self,name):
|
||||
self.name = name
|
||||
def greet(self, name):
|
||||
print "(someone called greet)"
|
||||
print "Hello %s, I am %s." % (name, self.name)
|
||||
print
|
||||
def getName(self):
|
||||
print "(someone called getName)"
|
||||
print
|
||||
return self.name
|
||||
def greet_this_guy(self, name):
|
||||
print "(someone called greet_this_guy)"
|
||||
print "About to greet %s ..." % name
|
||||
remote_guy = self.server.current_handler.get_remote_proxy(name)
|
||||
remote_guy.greet("Thomas Edison")
|
||||
print "Done."
|
||||
print
|
||||
|
||||
person = RemotePerson("Thomas Edison")
|
||||
svr = RPCServer(addr)
|
||||
svr.register('thomas', person)
|
||||
person.server = svr # only required if callbacks are used
|
||||
|
||||
# svr.serve_forever()
|
||||
svr.handle_request() # process once only
|
||||
|
||||
def testClient(addr):
|
||||
"demonstrates RPC Client"
|
||||
# XXX 25 Jul 02 KBK needs update to use rpc.py register/unregister methods
|
||||
import time
|
||||
clt=RPCClient(addr)
|
||||
thomas = clt.get_remote_proxy("thomas")
|
||||
print "The remote person's name is ..."
|
||||
print thomas.getName()
|
||||
# print clt.remotecall("thomas", "getName", (), {})
|
||||
print
|
||||
time.sleep(1)
|
||||
print "Getting remote thomas to say hi..."
|
||||
thomas.greet("Alexander Bell")
|
||||
#clt.remotecall("thomas","greet",("Alexander Bell",), {})
|
||||
print "Done."
|
||||
print
|
||||
time.sleep(2)
|
||||
# demonstrates remote server calling local instance
|
||||
class LocalPerson:
|
||||
def __init__(self,name):
|
||||
self.name = name
|
||||
def greet(self, name):
|
||||
print "You've greeted me!"
|
||||
def getName(self):
|
||||
return self.name
|
||||
person = LocalPerson("Alexander Bell")
|
||||
clt.register("alexander",person)
|
||||
thomas.greet_this_guy("alexander")
|
||||
# clt.remotecall("thomas","greet_this_guy",("alexander",), {})
|
||||
|
||||
def test():
|
||||
addr=("localhost",8833)
|
||||
if len(sys.argv) == 2:
|
||||
if sys.argv[1]=='-server':
|
||||
testServer(addr)
|
||||
return
|
||||
testClient(addr)
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
216
Tools/idle/run.py
Normal file
216
Tools/idle/run.py
Normal file
|
@ -0,0 +1,216 @@
|
|||
import sys
|
||||
import time
|
||||
import socket
|
||||
import traceback
|
||||
import threading
|
||||
import Queue
|
||||
|
||||
import boolcheck
|
||||
|
||||
import CallTips
|
||||
import RemoteDebugger
|
||||
import RemoteObjectBrowser
|
||||
import StackViewer
|
||||
import rpc
|
||||
import interrupt
|
||||
|
||||
import __main__
|
||||
|
||||
# Thread shared globals: Establish a queue between a subthread (which handles
|
||||
# the socket) and the main thread (which runs user code), plus global
|
||||
# completion and exit flags:
|
||||
|
||||
server = None # RPCServer instance
|
||||
queue = Queue.Queue(0)
|
||||
execution_finished = False
|
||||
exit_requested = False
|
||||
|
||||
|
||||
def main():
|
||||
"""Start the Python execution server in a subprocess
|
||||
|
||||
In the Python subprocess, RPCServer is instantiated with handlerclass
|
||||
MyHandler, which inherits register/unregister methods from RPCHandler via
|
||||
the mix-in class SocketIO.
|
||||
|
||||
When the RPCServer 'server' is instantiated, the TCPServer initialization
|
||||
creates an instance of run.MyHandler and calls its handle() method.
|
||||
handle() instantiates a run.Executive object, passing it a reference to the
|
||||
MyHandler object. That reference is saved as attribute rpchandler of the
|
||||
Executive instance. The Executive methods have access to the reference and
|
||||
can pass it on to entities that they command
|
||||
(e.g. RemoteDebugger.Debugger.start_debugger()). The latter, in turn, can
|
||||
call MyHandler(SocketIO) register/unregister methods via the reference to
|
||||
register and unregister themselves.
|
||||
|
||||
"""
|
||||
global queue, execution_finished, exit_requested
|
||||
|
||||
port = 8833
|
||||
if sys.argv[1:]:
|
||||
port = int(sys.argv[1])
|
||||
sys.argv[:] = [""]
|
||||
sockthread = threading.Thread(target=manage_socket,
|
||||
name='SockThread',
|
||||
args=(('localhost', port),))
|
||||
sockthread.setDaemon(True)
|
||||
sockthread.start()
|
||||
while 1:
|
||||
try:
|
||||
if exit_requested:
|
||||
sys.exit()
|
||||
# XXX KBK 22Mar03 eventually check queue here!
|
||||
pass
|
||||
time.sleep(0.05)
|
||||
except KeyboardInterrupt:
|
||||
##execution_finished = True
|
||||
continue
|
||||
|
||||
def manage_socket(address):
|
||||
global server, exit_requested
|
||||
|
||||
for i in range(6):
|
||||
time.sleep(i)
|
||||
try:
|
||||
server = rpc.RPCServer(address, MyHandler)
|
||||
break
|
||||
except socket.error, err:
|
||||
if i < 3:
|
||||
print>>sys.__stderr__, ".. ",
|
||||
else:
|
||||
print>>sys.__stderr__,"\nPython subprocess socket error: "\
|
||||
+ err[1] + ", retrying...."
|
||||
else:
|
||||
print>>sys.__stderr__, "\nConnection to Idle failed, exiting."
|
||||
exit_requested = True
|
||||
server.handle_request() # A single request only
|
||||
|
||||
|
||||
class MyHandler(rpc.RPCHandler):
|
||||
|
||||
def handle(self):
|
||||
"""Override base method"""
|
||||
executive = Executive(self)
|
||||
self.register("exec", executive)
|
||||
sys.stdin = self.get_remote_proxy("stdin")
|
||||
sys.stdout = self.get_remote_proxy("stdout")
|
||||
sys.stderr = self.get_remote_proxy("stderr")
|
||||
rpc.RPCHandler.getresponse(self, myseq=None, wait=0.5)
|
||||
|
||||
|
||||
class Executive:
|
||||
|
||||
def __init__(self, rpchandler):
|
||||
self.rpchandler = rpchandler
|
||||
self.locals = __main__.__dict__
|
||||
self.calltip = CallTips.CallTips()
|
||||
|
||||
def runcode(self, code):
|
||||
global queue, execution_finished
|
||||
|
||||
execution_finished = False
|
||||
queue.put(code)
|
||||
# dequeue and run in subthread
|
||||
self.runcode_from_queue()
|
||||
while not execution_finished:
|
||||
time.sleep(0.05)
|
||||
|
||||
def runcode_from_queue(self):
|
||||
global queue, execution_finished
|
||||
|
||||
# poll until queue has code object, using threads, just block?
|
||||
while True:
|
||||
try:
|
||||
code = queue.get(0)
|
||||
break
|
||||
except Queue.Empty:
|
||||
time.sleep(0.05)
|
||||
try:
|
||||
exec code in self.locals
|
||||
except:
|
||||
self.flush_stdout()
|
||||
efile = sys.stderr
|
||||
typ, val, tb = info = sys.exc_info()
|
||||
sys.last_type, sys.last_value, sys.last_traceback = info
|
||||
tbe = traceback.extract_tb(tb)
|
||||
print >>efile, 'Traceback (most recent call last):'
|
||||
exclude = ("run.py", "rpc.py", "RemoteDebugger.py", "bdb.py")
|
||||
self.cleanup_traceback(tbe, exclude)
|
||||
traceback.print_list(tbe, file=efile)
|
||||
lines = traceback.format_exception_only(typ, val)
|
||||
for line in lines:
|
||||
print>>efile, line,
|
||||
execution_finished = True
|
||||
else:
|
||||
self.flush_stdout()
|
||||
execution_finished = True
|
||||
|
||||
def flush_stdout(self):
|
||||
try:
|
||||
if sys.stdout.softspace:
|
||||
sys.stdout.softspace = 0
|
||||
sys.stdout.write("\n")
|
||||
except (AttributeError, EOFError):
|
||||
pass
|
||||
|
||||
def cleanup_traceback(self, tb, exclude):
|
||||
"Remove excluded traces from beginning/end of tb; get cached lines"
|
||||
orig_tb = tb[:]
|
||||
while tb:
|
||||
for rpcfile in exclude:
|
||||
if tb[0][0].count(rpcfile):
|
||||
break # found an exclude, break for: and delete tb[0]
|
||||
else:
|
||||
break # no excludes, have left RPC code, break while:
|
||||
del tb[0]
|
||||
while tb:
|
||||
for rpcfile in exclude:
|
||||
if tb[-1][0].count(rpcfile):
|
||||
break
|
||||
else:
|
||||
break
|
||||
del tb[-1]
|
||||
if len(tb) == 0:
|
||||
# exception was in IDLE internals, don't prune!
|
||||
tb[:] = orig_tb[:]
|
||||
print>>sys.stderr, "** IDLE Internal Exception: "
|
||||
for i in range(len(tb)):
|
||||
fn, ln, nm, line = tb[i]
|
||||
if nm == '?':
|
||||
nm = "-toplevel-"
|
||||
if not line and fn.startswith("<pyshell#"):
|
||||
line = self.rpchandler.remotecall('linecache', 'getline',
|
||||
(fn, ln), {})
|
||||
tb[i] = fn, ln, nm, line
|
||||
|
||||
def interrupt_the_server(self):
|
||||
self.rpchandler.interrupted = True
|
||||
##print>>sys.__stderr__, "** Interrupt main!"
|
||||
interrupt.interrupt_main()
|
||||
|
||||
def shutdown_the_server(self):
|
||||
global exit_requested
|
||||
|
||||
exit_requested = True
|
||||
|
||||
def start_the_debugger(self, gui_adap_oid):
|
||||
return RemoteDebugger.start_debugger(self.rpchandler, gui_adap_oid)
|
||||
|
||||
def stop_the_debugger(self, idb_adap_oid):
|
||||
"Unregister the Idb Adapter. Link objects and Idb then subject to GC"
|
||||
self.rpchandler.unregister(idb_adap_oid)
|
||||
|
||||
def get_the_calltip(self, name):
|
||||
return self.calltip.fetch_tip(name)
|
||||
|
||||
def stackviewer(self, flist_oid=None):
|
||||
if not hasattr(sys, "last_traceback"):
|
||||
return None
|
||||
flist = None
|
||||
if flist_oid is not None:
|
||||
flist = self.rpchandler.get_remote_proxy(flist_oid)
|
||||
tb = sys.last_traceback
|
||||
while tb and tb.tb_frame.f_globals["__name__"] in ["rpc", "run"]:
|
||||
tb = tb.tb_next
|
||||
item = StackViewer.StackTreeItem(flist, tb)
|
||||
return RemoteObjectBrowser.remote_object_tree_item(item)
|
4
Tools/idle/setup.cfg
Normal file
4
Tools/idle/setup.cfg
Normal file
|
@ -0,0 +1,4 @@
|
|||
[bdist_rpm]
|
||||
release = 1
|
||||
packager = Kurt B. Kaiser <kbk@shore.net>
|
||||
|
113
Tools/idle/tabpage.py
Normal file
113
Tools/idle/tabpage.py
Normal file
|
@ -0,0 +1,113 @@
|
|||
"""
|
||||
a couple of classes for implementing partial tabbed-page like behaviour
|
||||
"""
|
||||
|
||||
from Tkinter import *
|
||||
|
||||
class InvalidTabPage(Exception): pass
|
||||
class AlreadyExists(Exception): pass
|
||||
|
||||
class PageTab(Frame):
|
||||
"""
|
||||
a 'page tab' like framed button
|
||||
"""
|
||||
def __init__(self,parent):
|
||||
Frame.__init__(self, parent,borderwidth=2,relief=RIDGE)
|
||||
self.button=Radiobutton(self,padx=5,pady=5,takefocus=FALSE,
|
||||
indicatoron=FALSE,highlightthickness=0,
|
||||
borderwidth=0,selectcolor=self.cget('bg'))
|
||||
self.button.pack()
|
||||
|
||||
class TabPageSet(Frame):
|
||||
"""
|
||||
a set of 'pages' with TabButtons for controlling their display
|
||||
"""
|
||||
def __init__(self,parent,pageNames=[],**kw):
|
||||
"""
|
||||
pageNames - a list of strings, each string will be the dictionary key
|
||||
to a page's data, and the name displayed on the page's tab. Should be
|
||||
specified in desired page order. The first page will be the default
|
||||
and first active page.
|
||||
"""
|
||||
Frame.__init__(self, parent, kw)
|
||||
self.grid_location(0,0)
|
||||
self.columnconfigure(0,weight=1)
|
||||
self.rowconfigure(1,weight=1)
|
||||
self.tabBar=Frame(self)
|
||||
self.tabBar.grid(row=0,column=0,sticky=EW)
|
||||
self.activePage=StringVar(self)
|
||||
self.defaultPage=''
|
||||
self.pages={}
|
||||
for name in pageNames:
|
||||
self.AddPage(name)
|
||||
|
||||
def ChangePage(self,pageName=None):
|
||||
if pageName:
|
||||
if pageName in self.pages.keys():
|
||||
self.activePage.set(pageName)
|
||||
else:
|
||||
raise InvalidTabPage, 'Invalid TabPage Name'
|
||||
## pop up the active 'tab' only
|
||||
for page in self.pages.keys():
|
||||
self.pages[page]['tab'].config(relief=RIDGE)
|
||||
self.pages[self.GetActivePage()]['tab'].config(relief=RAISED)
|
||||
## switch page
|
||||
self.pages[self.GetActivePage()]['page'].lift()
|
||||
|
||||
def GetActivePage(self):
|
||||
return self.activePage.get()
|
||||
|
||||
def AddPage(self,pageName):
|
||||
if pageName in self.pages.keys():
|
||||
raise AlreadyExists, 'TabPage Name Already Exists'
|
||||
self.pages[pageName]={'tab':PageTab(self.tabBar),
|
||||
'page':Frame(self,borderwidth=2,relief=RAISED)}
|
||||
self.pages[pageName]['tab'].button.config(text=pageName,
|
||||
command=self.ChangePage,variable=self.activePage,
|
||||
value=pageName)
|
||||
self.pages[pageName]['tab'].pack(side=LEFT)
|
||||
self.pages[pageName]['page'].grid(row=1,column=0,sticky=NSEW)
|
||||
if len(self.pages)==1: # adding first page
|
||||
self.defaultPage=pageName
|
||||
self.activePage.set(self.defaultPage)
|
||||
self.ChangePage()
|
||||
|
||||
def RemovePage(self,pageName):
|
||||
if not pageName in self.pages.keys():
|
||||
raise InvalidTabPage, 'Invalid TabPage Name'
|
||||
self.pages[pageName]['tab'].pack_forget()
|
||||
self.pages[pageName]['page'].grid_forget()
|
||||
self.pages[pageName]['tab'].destroy()
|
||||
self.pages[pageName]['page'].destroy()
|
||||
del(self.pages[pageName])
|
||||
# handle removing last remaining, or default, or active page
|
||||
if not self.pages: # removed last remaining page
|
||||
self.defaultPage=''
|
||||
return
|
||||
if pageName==self.defaultPage: # set a new default page
|
||||
self.defaultPage=\
|
||||
self.tabBar.winfo_children()[0].button.cget('text')
|
||||
if pageName==self.GetActivePage(): # set a new active page
|
||||
self.activePage.set(self.defaultPage)
|
||||
self.ChangePage()
|
||||
|
||||
if __name__ == '__main__':
|
||||
#test dialog
|
||||
root=Tk()
|
||||
tabPage=TabPageSet(root,pageNames=['Foobar','Baz'])
|
||||
tabPage.pack(expand=TRUE,fill=BOTH)
|
||||
Label(tabPage.pages['Foobar']['page'],text='Foo',pady=20).pack()
|
||||
Label(tabPage.pages['Foobar']['page'],text='Bar',pady=20).pack()
|
||||
Label(tabPage.pages['Baz']['page'],text='Baz').pack()
|
||||
entryPgName=Entry(root)
|
||||
buttonAdd=Button(root,text='Add Page',
|
||||
command=lambda:tabPage.AddPage(entryPgName.get()))
|
||||
buttonRemove=Button(root,text='Remove Page',
|
||||
command=lambda:tabPage.RemovePage(entryPgName.get()))
|
||||
labelPgName=Label(root,text='name of page to add/remove:')
|
||||
buttonAdd.pack(padx=5,pady=5)
|
||||
buttonRemove.pack(padx=5,pady=5)
|
||||
labelPgName.pack(padx=5)
|
||||
entryPgName.pack(padx=5)
|
||||
tabPage.ChangePage()
|
||||
root.mainloop()
|
77
Tools/idle/textView.py
Normal file
77
Tools/idle/textView.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
##---------------------------------------------------------------------------##
|
||||
##
|
||||
## idle - simple text view dialog
|
||||
## elguavas
|
||||
##
|
||||
##---------------------------------------------------------------------------##
|
||||
"""
|
||||
simple text browser for idle
|
||||
"""
|
||||
from Tkinter import *
|
||||
import tkMessageBox
|
||||
|
||||
class TextViewer(Toplevel):
|
||||
"""
|
||||
simple text viewer dialog for idle
|
||||
"""
|
||||
def __init__(self,parent,title,fileName):
|
||||
"""
|
||||
fileName - string,should be an absoulute filename
|
||||
"""
|
||||
Toplevel.__init__(self, parent)
|
||||
self.configure(borderwidth=5)
|
||||
self.geometry("+%d+%d" % (parent.winfo_rootx()+10,
|
||||
parent.winfo_rooty()+10))
|
||||
#elguavas - config placeholders til config stuff completed
|
||||
self.bg=None
|
||||
self.fg=None
|
||||
|
||||
self.CreateWidgets()
|
||||
self.title(title)
|
||||
self.transient(parent)
|
||||
self.grab_set()
|
||||
self.protocol("WM_DELETE_WINDOW", self.Ok)
|
||||
self.parent = parent
|
||||
self.textView.focus_set()
|
||||
#key bindings for this dialog
|
||||
self.bind('<Return>',self.Ok) #dismiss dialog
|
||||
self.bind('<Escape>',self.Ok) #dismiss dialog
|
||||
self.LoadTextFile(fileName)
|
||||
self.textView.config(state=DISABLED)
|
||||
self.wait_window()
|
||||
|
||||
def LoadTextFile(self, fileName):
|
||||
textFile = None
|
||||
try:
|
||||
textFile = open(fileName, 'r')
|
||||
except IOError:
|
||||
tkMessageBox.showerror(title='File Load Error',
|
||||
message='Unable to load file '+`fileName`+' .')
|
||||
else:
|
||||
self.textView.insert(0.0,textFile.read())
|
||||
|
||||
def CreateWidgets(self):
|
||||
frameText = Frame(self)
|
||||
frameButtons = Frame(self)
|
||||
self.buttonOk = Button(frameButtons,text='Ok',
|
||||
command=self.Ok,takefocus=FALSE,default=ACTIVE)
|
||||
self.scrollbarView = Scrollbar(frameText,orient=VERTICAL,
|
||||
takefocus=FALSE,highlightthickness=0)
|
||||
self.textView = Text(frameText,wrap=WORD,highlightthickness=0)
|
||||
self.scrollbarView.config(command=self.textView.yview)
|
||||
self.textView.config(yscrollcommand=self.scrollbarView.set)
|
||||
self.buttonOk.pack(padx=5,pady=5)
|
||||
self.scrollbarView.pack(side=RIGHT,fill=Y)
|
||||
self.textView.pack(side=LEFT,expand=TRUE,fill=BOTH)
|
||||
frameButtons.pack(side=BOTTOM,fill=X)
|
||||
frameText.pack(side=TOP,expand=TRUE,fill=BOTH)
|
||||
|
||||
def Ok(self, event=None):
|
||||
self.destroy()
|
||||
|
||||
if __name__ == '__main__':
|
||||
#test the dialog
|
||||
root=Tk()
|
||||
Button(root,text='View',
|
||||
command=lambda:TextViewer(root,'Text','./textView.py')).pack()
|
||||
root.mainloop()
|
Loading…
Add table
Add a link
Reference in a new issue