Import Upstream version 2.7.18

This commit is contained in:
geos_one
2025-08-15 16:28:06 +02:00
commit ba1f69ab39
4521 changed files with 1778434 additions and 0 deletions

229
Lib/idlelib/AutoComplete.py Normal file
View File

@@ -0,0 +1,229 @@
"""AutoComplete.py - An IDLE extension for automatically completing names.
This extension can complete either attribute names or file names. It can pop
a window with all available names, for the user to select from.
"""
import os
import sys
import string
from idlelib.configHandler import idleConf
# This string includes all chars that may be in a file name (without a path
# separator)
FILENAME_CHARS = string.ascii_letters + string.digits + os.curdir + "._~#$:-"
# This string includes all chars that may be in an identifier
ID_CHARS = string.ascii_letters + string.digits + "_"
# These constants represent the two different types of completions
COMPLETE_ATTRIBUTES, COMPLETE_FILES = range(1, 2+1)
from idlelib import AutoCompleteWindow
from idlelib.HyperParser import HyperParser
import __main__
SEPS = os.sep
if os.altsep: # e.g. '/' on Windows...
SEPS += os.altsep
class AutoComplete:
menudefs = [
('edit', [
("Show Completions", "<<force-open-completions>>"),
])
]
popupwait = idleConf.GetOption("extensions", "AutoComplete",
"popupwait", type="int", default=0)
def __init__(self, editwin=None):
self.editwin = editwin
if editwin is None: # subprocess and test
return
self.text = editwin.text
self.autocompletewindow = None
# id of delayed call, and the index of the text insert when the delayed
# call was issued. If _delayed_completion_id is None, there is no
# delayed call.
self._delayed_completion_id = None
self._delayed_completion_index = None
def _make_autocomplete_window(self):
return AutoCompleteWindow.AutoCompleteWindow(self.text)
def _remove_autocomplete_window(self, event=None):
if self.autocompletewindow:
self.autocompletewindow.hide_window()
self.autocompletewindow = None
def force_open_completions_event(self, event):
"""Happens when the user really wants to open a completion list, even
if a function call is needed.
"""
self.open_completions(True, False, True)
def try_open_completions_event(self, event):
"""Happens when it would be nice to open a completion list, but not
really necessary, for example after a dot, so function
calls won't be made.
"""
lastchar = self.text.get("insert-1c")
if lastchar == ".":
self._open_completions_later(False, False, False,
COMPLETE_ATTRIBUTES)
elif lastchar in SEPS:
self._open_completions_later(False, False, False,
COMPLETE_FILES)
def autocomplete_event(self, event):
"""Happens when the user wants to complete his word, and if necessary,
open a completion list after that (if there is more than one
completion)
"""
if hasattr(event, "mc_state") and event.mc_state:
# A modifier was pressed along with the tab, continue as usual.
return
if self.autocompletewindow and self.autocompletewindow.is_active():
self.autocompletewindow.complete()
return "break"
else:
opened = self.open_completions(False, True, True)
if opened:
return "break"
def _open_completions_later(self, *args):
self._delayed_completion_index = self.text.index("insert")
if self._delayed_completion_id is not None:
self.text.after_cancel(self._delayed_completion_id)
self._delayed_completion_id = \
self.text.after(self.popupwait, self._delayed_open_completions,
*args)
def _delayed_open_completions(self, *args):
self._delayed_completion_id = None
if self.text.index("insert") != self._delayed_completion_index:
return
self.open_completions(*args)
def open_completions(self, evalfuncs, complete, userWantsWin, mode=None):
"""Find the completions and create the AutoCompleteWindow.
Return True if successful (no syntax error or so found).
if complete is True, then if there's nothing to complete and no
start of completion, won't open completions and return False.
If mode is given, will open a completion list only in this mode.
"""
# Cancel another delayed call, if it exists.
if self._delayed_completion_id is not None:
self.text.after_cancel(self._delayed_completion_id)
self._delayed_completion_id = None
hp = HyperParser(self.editwin, "insert")
curline = self.text.get("insert linestart", "insert")
i = j = len(curline)
if hp.is_in_string() and (not mode or mode==COMPLETE_FILES):
self._remove_autocomplete_window()
mode = COMPLETE_FILES
while i and curline[i-1] in FILENAME_CHARS:
i -= 1
comp_start = curline[i:j]
j = i
while i and curline[i-1] in FILENAME_CHARS + SEPS:
i -= 1
comp_what = curline[i:j]
elif hp.is_in_code() and (not mode or mode==COMPLETE_ATTRIBUTES):
self._remove_autocomplete_window()
mode = COMPLETE_ATTRIBUTES
while i and curline[i-1] in ID_CHARS:
i -= 1
comp_start = curline[i:j]
if i and curline[i-1] == '.':
hp.set_index("insert-%dc" % (len(curline)-(i-1)))
comp_what = hp.get_expression()
if not comp_what or \
(not evalfuncs and comp_what.find('(') != -1):
return
else:
comp_what = ""
else:
return
if complete and not comp_what and not comp_start:
return
comp_lists = self.fetch_completions(comp_what, mode)
if not comp_lists[0]:
return
self.autocompletewindow = self._make_autocomplete_window()
return not self.autocompletewindow.show_window(
comp_lists, "insert-%dc" % len(comp_start),
complete, mode, userWantsWin)
def fetch_completions(self, what, mode):
"""Return a pair of lists of completions for something. The first list
is a sublist of the second. Both are sorted.
If there is a Python subprocess, get the comp. list there. Otherwise,
either fetch_completions() is running in the subprocess itself or it
was called in an IDLE EditorWindow before any script had been run.
The subprocess environment is that of the most recently run script. If
two unrelated modules are being edited some calltips in the current
module may be inoperative if the module was not the last to run.
"""
try:
rpcclt = self.editwin.flist.pyshell.interp.rpcclt
except:
rpcclt = None
if rpcclt:
return rpcclt.remotecall("exec", "get_the_completion_list",
(what, mode), {})
else:
if mode == COMPLETE_ATTRIBUTES:
if what == "":
namespace = __main__.__dict__.copy()
namespace.update(__main__.__builtins__.__dict__)
bigl = eval("dir()", namespace)
bigl.sort()
if "__all__" in bigl:
smalll = sorted(eval("__all__", namespace))
else:
smalll = [s for s in bigl if s[:1] != '_']
else:
try:
entity = self.get_entity(what)
bigl = dir(entity)
bigl.sort()
if "__all__" in bigl:
smalll = sorted(entity.__all__)
else:
smalll = [s for s in bigl if s[:1] != '_']
except:
return [], []
elif mode == COMPLETE_FILES:
if what == "":
what = "."
try:
expandedpath = os.path.expanduser(what)
bigl = os.listdir(expandedpath)
bigl.sort()
smalll = [s for s in bigl if s[:1] != '.']
except OSError:
return [], []
if not smalll:
smalll = bigl
return smalll, bigl
def get_entity(self, name):
"""Lookup name in a namespace spanning sys.modules and __main.dict__"""
namespace = sys.modules.copy()
namespace.update(__main__.__dict__)
return eval(name, namespace)
if __name__ == '__main__':
from unittest import main
main('idlelib.idle_test.test_autocomplete', verbosity=2)

View File

@@ -0,0 +1,407 @@
"""
An auto-completion window for IDLE, used by the AutoComplete extension
"""
from Tkinter import *
from idlelib.MultiCall import MC_SHIFT
from idlelib.AutoComplete import COMPLETE_FILES, COMPLETE_ATTRIBUTES
HIDE_VIRTUAL_EVENT_NAME = "<<autocompletewindow-hide>>"
HIDE_SEQUENCES = ("<FocusOut>", "<ButtonPress>")
KEYPRESS_VIRTUAL_EVENT_NAME = "<<autocompletewindow-keypress>>"
# We need to bind event beyond <Key> so that the function will be called
# before the default specific IDLE function
KEYPRESS_SEQUENCES = ("<Key>", "<Key-BackSpace>", "<Key-Return>", "<Key-Tab>",
"<Key-Up>", "<Key-Down>", "<Key-Home>", "<Key-End>",
"<Key-Prior>", "<Key-Next>")
KEYRELEASE_VIRTUAL_EVENT_NAME = "<<autocompletewindow-keyrelease>>"
KEYRELEASE_SEQUENCE = "<KeyRelease>"
LISTUPDATE_SEQUENCE = "<B1-ButtonRelease>"
WINCONFIG_SEQUENCE = "<Configure>"
DOUBLECLICK_SEQUENCE = "<B1-Double-ButtonRelease>"
class AutoCompleteWindow:
def __init__(self, widget):
# The widget (Text) on which we place the AutoCompleteWindow
self.widget = widget
# The widgets we create
self.autocompletewindow = self.listbox = self.scrollbar = None
# The default foreground and background of a selection. Saved because
# they are changed to the regular colors of list items when the
# completion start is not a prefix of the selected completion
self.origselforeground = self.origselbackground = None
# The list of completions
self.completions = None
# A list with more completions, or None
self.morecompletions = None
# The completion mode. Either AutoComplete.COMPLETE_ATTRIBUTES or
# AutoComplete.COMPLETE_FILES
self.mode = None
# The current completion start, on the text box (a string)
self.start = None
# The index of the start of the completion
self.startindex = None
# The last typed start, used so that when the selection changes,
# the new start will be as close as possible to the last typed one.
self.lasttypedstart = None
# Do we have an indication that the user wants the completion window
# (for example, he clicked the list)
self.userwantswindow = None
# event ids
self.hideid = self.keypressid = self.listupdateid = self.winconfigid \
= self.keyreleaseid = self.doubleclickid = None
# Flag set if last keypress was a tab
self.lastkey_was_tab = False
def _change_start(self, newstart):
min_len = min(len(self.start), len(newstart))
i = 0
while i < min_len and self.start[i] == newstart[i]:
i += 1
if i < len(self.start):
self.widget.delete("%s+%dc" % (self.startindex, i),
"%s+%dc" % (self.startindex, len(self.start)))
if i < len(newstart):
self.widget.insert("%s+%dc" % (self.startindex, i),
newstart[i:])
self.start = newstart
def _binary_search(self, s):
"""Find the first index in self.completions where completions[i] is
greater or equal to s, or the last index if there is no such
one."""
i = 0; j = len(self.completions)
while j > i:
m = (i + j) // 2
if self.completions[m] >= s:
j = m
else:
i = m + 1
return min(i, len(self.completions)-1)
def _complete_string(self, s):
"""Assuming that s is the prefix of a string in self.completions,
return the longest string which is a prefix of all the strings which
s is a prefix of them. If s is not a prefix of a string, return s."""
first = self._binary_search(s)
if self.completions[first][:len(s)] != s:
# There is not even one completion which s is a prefix of.
return s
# Find the end of the range of completions where s is a prefix of.
i = first + 1
j = len(self.completions)
while j > i:
m = (i + j) // 2
if self.completions[m][:len(s)] != s:
j = m
else:
i = m + 1
last = i-1
if first == last: # only one possible completion
return self.completions[first]
# We should return the maximum prefix of first and last
first_comp = self.completions[first]
last_comp = self.completions[last]
min_len = min(len(first_comp), len(last_comp))
i = len(s)
while i < min_len and first_comp[i] == last_comp[i]:
i += 1
return first_comp[:i]
def _selection_changed(self):
"""Should be called when the selection of the Listbox has changed.
Updates the Listbox display and calls _change_start."""
cursel = int(self.listbox.curselection()[0])
self.listbox.see(cursel)
lts = self.lasttypedstart
selstart = self.completions[cursel]
if self._binary_search(lts) == cursel:
newstart = lts
else:
min_len = min(len(lts), len(selstart))
i = 0
while i < min_len and lts[i] == selstart[i]:
i += 1
newstart = selstart[:i]
self._change_start(newstart)
if self.completions[cursel][:len(self.start)] == self.start:
# start is a prefix of the selected completion
self.listbox.configure(selectbackground=self.origselbackground,
selectforeground=self.origselforeground)
else:
self.listbox.configure(selectbackground=self.listbox.cget("bg"),
selectforeground=self.listbox.cget("fg"))
# If there are more completions, show them, and call me again.
if self.morecompletions:
self.completions = self.morecompletions
self.morecompletions = None
self.listbox.delete(0, END)
for item in self.completions:
self.listbox.insert(END, item)
self.listbox.select_set(self._binary_search(self.start))
self._selection_changed()
def show_window(self, comp_lists, index, complete, mode, userWantsWin):
"""Show the autocomplete list, bind events.
If complete is True, complete the text, and if there is exactly one
matching completion, don't open a list."""
# Handle the start we already have
self.completions, self.morecompletions = comp_lists
self.mode = mode
self.startindex = self.widget.index(index)
self.start = self.widget.get(self.startindex, "insert")
if complete:
completed = self._complete_string(self.start)
start = self.start
self._change_start(completed)
i = self._binary_search(completed)
if self.completions[i] == completed and \
(i == len(self.completions)-1 or
self.completions[i+1][:len(completed)] != completed):
# There is exactly one matching completion
return completed == start
self.userwantswindow = userWantsWin
self.lasttypedstart = self.start
# Put widgets in place
self.autocompletewindow = acw = Toplevel(self.widget)
# Put it in a position so that it is not seen.
acw.wm_geometry("+10000+10000")
# Make it float
acw.wm_overrideredirect(1)
try:
# This command is only needed and available on Tk >= 8.4.0 for OSX
# Without it, call tips intrude on the typing process by grabbing
# the focus.
acw.tk.call("::tk::unsupported::MacWindowStyle", "style", acw._w,
"help", "noActivates")
except TclError:
pass
self.scrollbar = scrollbar = Scrollbar(acw, orient=VERTICAL)
self.listbox = listbox = Listbox(acw, yscrollcommand=scrollbar.set,
exportselection=False, bg="white")
for item in self.completions:
listbox.insert(END, item)
self.origselforeground = listbox.cget("selectforeground")
self.origselbackground = listbox.cget("selectbackground")
scrollbar.config(command=listbox.yview)
scrollbar.pack(side=RIGHT, fill=Y)
listbox.pack(side=LEFT, fill=BOTH, expand=True)
acw.lift() # work around bug in Tk 8.5.18+ (issue #24570)
# Initialize the listbox selection
self.listbox.select_set(self._binary_search(self.start))
self._selection_changed()
# bind events
self.hideid = self.widget.bind(HIDE_VIRTUAL_EVENT_NAME,
self.hide_event)
for seq in HIDE_SEQUENCES:
self.widget.event_add(HIDE_VIRTUAL_EVENT_NAME, seq)
self.keypressid = self.widget.bind(KEYPRESS_VIRTUAL_EVENT_NAME,
self.keypress_event)
for seq in KEYPRESS_SEQUENCES:
self.widget.event_add(KEYPRESS_VIRTUAL_EVENT_NAME, seq)
self.keyreleaseid = self.widget.bind(KEYRELEASE_VIRTUAL_EVENT_NAME,
self.keyrelease_event)
self.widget.event_add(KEYRELEASE_VIRTUAL_EVENT_NAME,KEYRELEASE_SEQUENCE)
self.listupdateid = listbox.bind(LISTUPDATE_SEQUENCE,
self.listselect_event)
self.winconfigid = acw.bind(WINCONFIG_SEQUENCE, self.winconfig_event)
self.doubleclickid = listbox.bind(DOUBLECLICK_SEQUENCE,
self.doubleclick_event)
def winconfig_event(self, event):
if not self.is_active():
return
# Position the completion list window
text = self.widget
text.see(self.startindex)
x, y, cx, cy = text.bbox(self.startindex)
acw = self.autocompletewindow
acw_width, acw_height = acw.winfo_width(), acw.winfo_height()
text_width, text_height = text.winfo_width(), text.winfo_height()
new_x = text.winfo_rootx() + min(x, max(0, text_width - acw_width))
new_y = text.winfo_rooty() + y
if (text_height - (y + cy) >= acw_height # enough height below
or y < acw_height): # not enough height above
# place acw below current line
new_y += cy
else:
# place acw above current line
new_y -= acw_height
acw.wm_geometry("+%d+%d" % (new_x, new_y))
def hide_event(self, event):
if not self.is_active():
return
self.hide_window()
def listselect_event(self, event):
if not self.is_active():
return
self.userwantswindow = True
cursel = int(self.listbox.curselection()[0])
self._change_start(self.completions[cursel])
def doubleclick_event(self, event):
# Put the selected completion in the text, and close the list
cursel = int(self.listbox.curselection()[0])
self._change_start(self.completions[cursel])
self.hide_window()
def keypress_event(self, event):
if not self.is_active():
return
keysym = event.keysym
if hasattr(event, "mc_state"):
state = event.mc_state
else:
state = 0
if keysym != "Tab":
self.lastkey_was_tab = False
if (len(keysym) == 1 or keysym in ("underscore", "BackSpace")
or (self.mode == COMPLETE_FILES and keysym in
("period", "minus"))) \
and not (state & ~MC_SHIFT):
# Normal editing of text
if len(keysym) == 1:
self._change_start(self.start + keysym)
elif keysym == "underscore":
self._change_start(self.start + '_')
elif keysym == "period":
self._change_start(self.start + '.')
elif keysym == "minus":
self._change_start(self.start + '-')
else:
# keysym == "BackSpace"
if len(self.start) == 0:
self.hide_window()
return
self._change_start(self.start[:-1])
self.lasttypedstart = self.start
self.listbox.select_clear(0, int(self.listbox.curselection()[0]))
self.listbox.select_set(self._binary_search(self.start))
self._selection_changed()
return "break"
elif keysym == "Return":
self.hide_window()
return
elif (self.mode == COMPLETE_ATTRIBUTES and keysym in
("period", "space", "parenleft", "parenright", "bracketleft",
"bracketright")) or \
(self.mode == COMPLETE_FILES and keysym in
("slash", "backslash", "quotedbl", "apostrophe")) \
and not (state & ~MC_SHIFT):
# If start is a prefix of the selection, but is not '' when
# completing file names, put the whole
# selected completion. Anyway, close the list.
cursel = int(self.listbox.curselection()[0])
if self.completions[cursel][:len(self.start)] == self.start \
and (self.mode == COMPLETE_ATTRIBUTES or self.start):
self._change_start(self.completions[cursel])
self.hide_window()
return
elif keysym in ("Home", "End", "Prior", "Next", "Up", "Down") and \
not state:
# Move the selection in the listbox
self.userwantswindow = True
cursel = int(self.listbox.curselection()[0])
if keysym == "Home":
newsel = 0
elif keysym == "End":
newsel = len(self.completions)-1
elif keysym in ("Prior", "Next"):
jump = self.listbox.nearest(self.listbox.winfo_height()) - \
self.listbox.nearest(0)
if keysym == "Prior":
newsel = max(0, cursel-jump)
else:
assert keysym == "Next"
newsel = min(len(self.completions)-1, cursel+jump)
elif keysym == "Up":
newsel = max(0, cursel-1)
else:
assert keysym == "Down"
newsel = min(len(self.completions)-1, cursel+1)
self.listbox.select_clear(cursel)
self.listbox.select_set(newsel)
self._selection_changed()
self._change_start(self.completions[newsel])
return "break"
elif (keysym == "Tab" and not state):
if self.lastkey_was_tab:
# two tabs in a row; insert current selection and close acw
cursel = int(self.listbox.curselection()[0])
self._change_start(self.completions[cursel])
self.hide_window()
return "break"
else:
# first tab; let AutoComplete handle the completion
self.userwantswindow = True
self.lastkey_was_tab = True
return
elif any(s in keysym for s in ("Shift", "Control", "Alt",
"Meta", "Command", "Option")):
# A modifier key, so ignore
return
else:
# Unknown event, close the window and let it through.
self.hide_window()
return
def keyrelease_event(self, event):
if not self.is_active():
return
if self.widget.index("insert") != \
self.widget.index("%s+%dc" % (self.startindex, len(self.start))):
# If we didn't catch an event which moved the insert, close window
self.hide_window()
def is_active(self):
return self.autocompletewindow is not None
def complete(self):
self._change_start(self._complete_string(self.start))
# The selection doesn't change.
def hide_window(self):
if not self.is_active():
return
# unbind events
for seq in HIDE_SEQUENCES:
self.widget.event_delete(HIDE_VIRTUAL_EVENT_NAME, seq)
self.widget.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hideid)
self.hideid = None
for seq in KEYPRESS_SEQUENCES:
self.widget.event_delete(KEYPRESS_VIRTUAL_EVENT_NAME, seq)
self.widget.unbind(KEYPRESS_VIRTUAL_EVENT_NAME, self.keypressid)
self.keypressid = None
self.widget.event_delete(KEYRELEASE_VIRTUAL_EVENT_NAME,
KEYRELEASE_SEQUENCE)
self.widget.unbind(KEYRELEASE_VIRTUAL_EVENT_NAME, self.keyreleaseid)
self.keyreleaseid = None
self.listbox.unbind(LISTUPDATE_SEQUENCE, self.listupdateid)
self.listupdateid = None
self.autocompletewindow.unbind(WINCONFIG_SEQUENCE, self.winconfigid)
self.winconfigid = None
# destroy widgets
self.scrollbar.destroy()
self.scrollbar = None
self.listbox.destroy()
self.listbox = None
self.autocompletewindow.destroy()
self.autocompletewindow = None

104
Lib/idlelib/AutoExpand.py Normal file
View File

@@ -0,0 +1,104 @@
'''Complete the current word before the cursor with words in the editor.
Each menu selection or shortcut key selection replaces the word with a
different word with the same prefix. The search for matches begins
before the target and moves toward the top of the editor. It then starts
after the cursor and moves down. It then returns to the original word and
the cycle starts again.
Changing the current text line or leaving the cursor in a different
place before requesting the next selection causes AutoExpand to reset
its state.
This is an extension file and there is only one instance of AutoExpand.
'''
import string
import re
###$ event <<expand-word>>
###$ win <Alt-slash>
###$ unix <Alt-slash>
class AutoExpand:
menudefs = [
('edit', [
('E_xpand Word', '<<expand-word>>'),
]),
]
wordchars = string.ascii_letters + string.digits + "_"
def __init__(self, editwin):
self.text = editwin.text
self.state = None
def expand_word_event(self, event):
"Replace the current word with the next expansion."
curinsert = self.text.index("insert")
curline = self.text.get("insert linestart", "insert lineend")
if not self.state:
words = self.getwords()
index = 0
else:
words, index, insert, line = self.state
if insert != curinsert or line != curline:
words = self.getwords()
index = 0
if not words:
self.text.bell()
return "break"
word = self.getprevword()
self.text.delete("insert - %d chars" % len(word), "insert")
newword = words[index]
index = (index + 1) % len(words)
if index == 0:
self.text.bell() # Warn we cycled around
self.text.insert("insert", newword)
curinsert = self.text.index("insert")
curline = self.text.get("insert linestart", "insert lineend")
self.state = words, index, curinsert, curline
return "break"
def getwords(self):
"Return a list of words that match the prefix before the cursor."
word = self.getprevword()
if not word:
return []
before = self.text.get("1.0", "insert wordstart")
wbefore = re.findall(r"\b" + word + r"\w+\b", before)
del before
after = self.text.get("insert wordend", "end")
wafter = re.findall(r"\b" + word + r"\w+\b", after)
del after
if not wbefore and not wafter:
return []
words = []
dict = {}
# search backwards through words before
wbefore.reverse()
for w in wbefore:
if dict.get(w):
continue
words.append(w)
dict[w] = w
# search onwards through words after
for w in wafter:
if dict.get(w):
continue
words.append(w)
dict[w] = w
words.append(word)
return words
def getprevword(self):
"Return the word prefix before the cursor."
line = self.text.get("insert linestart", "insert")
i = len(line)
while i > 0 and line[i-1] in self.wordchars:
i = i-1
return line[i:]
if __name__ == '__main__':
import unittest
unittest.main('idlelib.idle_test.test_autoexpand', verbosity=2)

91
Lib/idlelib/Bindings.py Normal file
View File

@@ -0,0 +1,91 @@
"""Define the menu contents, hotkeys, and event bindings.
There is additional configuration information in the EditorWindow class (and
subclasses): the menus are created there based on the menu_specs (class)
variable, and menus not created are silently skipped in the code here. This
makes it possible, for example, to define a Debug menu which is only present in
the PythonShell window, and a Format menu which is only present in the Editor
windows.
"""
from idlelib.configHandler import idleConf
# Warning: menudefs is altered in macosxSupport.overrideRootMenu()
# after it is determined that an OS X Aqua Tk is in use,
# which cannot be done until after Tk() is first called.
# Do not alter the 'file', 'options', or 'help' cascades here
# without altering overrideRootMenu() as well.
# TODO: Make this more robust
menudefs = [
# underscore prefixes character to underscore
('file', [
('_New File', '<<open-new-window>>'),
('_Open...', '<<open-window-from-file>>'),
('Open _Module...', '<<open-module>>'),
('Class _Browser', '<<open-class-browser>>'),
('_Path Browser', '<<open-path-browser>>'),
None,
('_Save', '<<save-window>>'),
('Save _As...', '<<save-window-as-file>>'),
('Save Cop_y As...', '<<save-copy-of-window-as-file>>'),
None,
('Prin_t Window', '<<print-window>>'),
None,
('_Close', '<<close-window>>'),
('E_xit', '<<close-all-windows>>'),
]),
('edit', [
('_Undo', '<<undo>>'),
('_Redo', '<<redo>>'),
None,
('Cu_t', '<<cut>>'),
('_Copy', '<<copy>>'),
('_Paste', '<<paste>>'),
('Select _All', '<<select-all>>'),
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>>'),
]),
('format', [
('_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>>'),
]),
('run', [
('Python Shell', '<<open-python-shell>>'),
]),
('shell', [
('_View Last Restart', '<<view-restart>>'),
('_Restart Shell', '<<restart-shell>>'),
None,
('_Interrupt Execution', '<<interrupt-execution>>'),
]),
('debug', [
('_Go to File/Line', '<<goto-file-line>>'),
('!_Debugger', '<<toggle-debugger>>'),
('_Stack Viewer', '<<open-stack-viewer>>'),
('!_Auto-open Stack Viewer', '<<toggle-jit-stack-viewer>>'),
]),
('options', [
('Configure _IDLE', '<<open-config-dialog>>'),
None,
]),
('help', [
('_About IDLE', '<<about-idle>>'),
None,
('_IDLE Help', '<<help>>'),
('Python _Docs', '<<python-docs>>'),
]),
]
default_keydefs = idleConf.GetCurrentKeySet()

37
Lib/idlelib/CREDITS.txt Normal file
View File

@@ -0,0 +1,37 @@
Guido van Rossum, as well as being the creator of the Python language, is the
original creator of IDLE. Other contributors prior to Version 0.8 include
Mark Hammond, Jeremy Hylton, Tim Peters, and Moshe Zadka.
IDLE's recent development was carried out in the SF IDLEfork project. The
objective was to develop a version of IDLE which had an execution environment
which could be initialized prior to each run of user code.
The IDLEfork project was initiated by David Scherer, with some help from Peter
Schneider-Kamp and Nicholas Riley. David wrote the first version of the RPC
code and designed a fast turn-around environment for VPython. Guido developed
the RPC code and Remote Debugger currently integrated in IDLE. Bruce Sherwood
contributed considerable time testing and suggesting improvements.
Besides David and Guido, the main developers who were active on IDLEfork
are Stephen M. Gava, who implemented the configuration GUI, the new
configuration system, and the About dialog, and Kurt B. Kaiser, who completed
the integration of the RPC and remote debugger, implemented the threaded
subprocess, and made a number of usability enhancements.
Other contributors include Raymond Hettinger, Tony Lownds (Mac integration),
Neal Norwitz (code check and clean-up), Ronald Oussoren (Mac integration),
Noam Raphael (Code Context, Call Tips, many other patches), and Chui Tey (RPC
integration, debugger integration and persistent breakpoints).
Scott David Daniels, Tal Einat, Hernan Foffani, Christos Georgiou,
Jim Jewett, Martin v. Löwis, Jason Orendorff, Guilherme Polo, Josh Robb,
Nigel Rowe, Bruce Sherwood, Jeff Shute, and Weeble have submitted useful
patches. Thanks, guys!
For additional details refer to NEWS.txt and Changelog.
Please contact the IDLE maintainer (kbk@shore.net) to have yourself included
here if you are one of those we missed!

View File

@@ -0,0 +1,162 @@
"""A CallTip window class for Tkinter/IDLE.
After ToolTip.py, which uses ideas gleaned from PySol
Used by the CallTips IDLE extension.
"""
from Tkinter import Toplevel, Label, LEFT, SOLID, TclError
HIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-hide>>"
HIDE_SEQUENCES = ("<Key-Escape>", "<FocusOut>")
CHECKHIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-checkhide>>"
CHECKHIDE_SEQUENCES = ("<KeyRelease>", "<ButtonRelease>")
CHECKHIDE_TIME = 100 # milliseconds
MARK_RIGHT = "calltipwindowregion_right"
class CallTip:
def __init__(self, widget):
self.widget = widget
self.tipwindow = self.label = None
self.parenline = self.parencol = None
self.lastline = None
self.hideid = self.checkhideid = None
self.checkhide_after_id = None
def position_window(self):
"""Check if needs to reposition the window, and if so - do it."""
curline = int(self.widget.index("insert").split('.')[0])
if curline == self.lastline:
return
self.lastline = curline
self.widget.see("insert")
if curline == self.parenline:
box = self.widget.bbox("%d.%d" % (self.parenline,
self.parencol))
else:
box = self.widget.bbox("%d.0" % curline)
if not box:
box = list(self.widget.bbox("insert"))
# align to left of window
box[0] = 0
box[2] = 0
x = box[0] + self.widget.winfo_rootx() + 2
y = box[1] + box[3] + self.widget.winfo_rooty()
self.tipwindow.wm_geometry("+%d+%d" % (x, y))
def showtip(self, text, parenleft, parenright):
"""Show the calltip, bind events which will close it and reposition it.
"""
# Only called in CallTips, where lines are truncated
self.text = text
if self.tipwindow or not self.text:
return
self.widget.mark_set(MARK_RIGHT, parenright)
self.parenline, self.parencol = map(
int, self.widget.index(parenleft).split("."))
self.tipwindow = tw = Toplevel(self.widget)
self.position_window()
# remove border on calltip window
tw.wm_overrideredirect(1)
try:
# This command is only needed and available on Tk >= 8.4.0 for OSX
# Without it, call tips intrude on the typing process by grabbing
# the focus.
tw.tk.call("::tk::unsupported::MacWindowStyle", "style", tw._w,
"help", "noActivates")
except TclError:
pass
self.label = Label(tw, text=self.text, justify=LEFT,
background="#ffffe0", relief=SOLID, borderwidth=1,
font = self.widget['font'])
self.label.pack()
tw.update_idletasks()
tw.lift() # work around bug in Tk 8.5.18+ (issue #24570)
self.checkhideid = self.widget.bind(CHECKHIDE_VIRTUAL_EVENT_NAME,
self.checkhide_event)
for seq in CHECKHIDE_SEQUENCES:
self.widget.event_add(CHECKHIDE_VIRTUAL_EVENT_NAME, seq)
self.widget.after(CHECKHIDE_TIME, self.checkhide_event)
self.hideid = self.widget.bind(HIDE_VIRTUAL_EVENT_NAME,
self.hide_event)
for seq in HIDE_SEQUENCES:
self.widget.event_add(HIDE_VIRTUAL_EVENT_NAME, seq)
def checkhide_event(self, event=None):
if not self.tipwindow:
# If the event was triggered by the same event that unbinded
# this function, the function will be called nevertheless,
# so do nothing in this case.
return
curline, curcol = map(int, self.widget.index("insert").split('.'))
if curline < self.parenline or \
(curline == self.parenline and curcol <= self.parencol) or \
self.widget.compare("insert", ">", MARK_RIGHT):
self.hidetip()
else:
self.position_window()
if self.checkhide_after_id is not None:
self.widget.after_cancel(self.checkhide_after_id)
self.checkhide_after_id = \
self.widget.after(CHECKHIDE_TIME, self.checkhide_event)
def hide_event(self, event):
if not self.tipwindow:
# See the explanation in checkhide_event.
return
self.hidetip()
def hidetip(self):
if not self.tipwindow:
return
for seq in CHECKHIDE_SEQUENCES:
self.widget.event_delete(CHECKHIDE_VIRTUAL_EVENT_NAME, seq)
self.widget.unbind(CHECKHIDE_VIRTUAL_EVENT_NAME, self.checkhideid)
self.checkhideid = None
for seq in HIDE_SEQUENCES:
self.widget.event_delete(HIDE_VIRTUAL_EVENT_NAME, seq)
self.widget.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hideid)
self.hideid = None
self.label.destroy()
self.label = None
self.tipwindow.destroy()
self.tipwindow = None
self.widget.mark_unset(MARK_RIGHT)
self.parenline = self.parencol = self.lastline = None
def is_active(self):
return bool(self.tipwindow)
def _calltip_window(parent): # htest #
from Tkinter import Toplevel, Text, LEFT, BOTH
top = Toplevel(parent)
top.title("Test calltips")
top.geometry("200x100+%d+%d" % (parent.winfo_rootx() + 200,
parent.winfo_rooty() + 150))
text = Text(top)
text.pack(side=LEFT, fill=BOTH, expand=1)
text.insert("insert", "string.split")
top.update()
calltip = CallTip(text)
def calltip_show(event):
calltip.showtip("(s=Hello world)", "insert", "end")
def calltip_hide(event):
calltip.hidetip()
text.event_add("<<calltip-show>>", "(")
text.event_add("<<calltip-hide>>", ")")
text.bind("<<calltip-show>>", calltip_show)
text.bind("<<calltip-hide>>", calltip_hide)
text.focus_set()
if __name__=='__main__':
from idlelib.idle_test.htest import run
run(_calltip_window)

219
Lib/idlelib/CallTips.py Normal file
View File

@@ -0,0 +1,219 @@
"""CallTips.py - An IDLE Extension to Jog Your Memory
Call Tips are floating windows which display function, class, and method
parameter and docstring information when you type an opening parenthesis, and
which disappear when you type a closing parenthesis.
"""
import __main__
import re
import sys
import textwrap
import types
from idlelib import CallTipWindow
from idlelib.HyperParser import HyperParser
class CallTips:
menudefs = [
('edit', [
("Show call tip", "<<force-open-calltip>>"),
])
]
def __init__(self, editwin=None):
if editwin is None: # subprocess and test
self.editwin = None
return
self.editwin = editwin
self.text = editwin.text
self.calltip = None
self._make_calltip_window = self._make_tk_calltip_window
def close(self):
self._make_calltip_window = None
def _make_tk_calltip_window(self):
# See __init__ for usage
return CallTipWindow.CallTip(self.text)
def _remove_calltip_window(self, event=None):
if self.calltip:
self.calltip.hidetip()
self.calltip = None
def force_open_calltip_event(self, event):
"""Happens when the user really wants to open a CallTip, even if a
function call is needed.
"""
self.open_calltip(True)
def try_open_calltip_event(self, event):
"""Happens when it would be nice to open a CallTip, but not really
necessary, for example after an opening bracket, so function calls
won't be made.
"""
self.open_calltip(False)
def refresh_calltip_event(self, event):
"""If there is already a calltip window, check if it is still needed,
and if so, reload it.
"""
if self.calltip and self.calltip.is_active():
self.open_calltip(False)
def open_calltip(self, evalfuncs):
self._remove_calltip_window()
hp = HyperParser(self.editwin, "insert")
sur_paren = hp.get_surrounding_brackets('(')
if not sur_paren:
return
hp.set_index(sur_paren[0])
expression = hp.get_expression()
if not expression or (not evalfuncs and expression.find('(') != -1):
return
arg_text = self.fetch_tip(expression)
if not arg_text:
return
self.calltip = self._make_calltip_window()
self.calltip.showtip(arg_text, sur_paren[0], sur_paren[1])
def fetch_tip(self, expression):
"""Return the argument list and docstring of a function or class
If there is a Python subprocess, get the calltip there. Otherwise,
either fetch_tip() is running in the subprocess itself or it was called
in an IDLE EditorWindow before any script had been run.
The subprocess environment is that of the most recently run script. If
two unrelated modules are being edited some calltips in the current
module may be inoperative if the module was not the last to run.
To find methods, fetch_tip must be fed a fully qualified name.
"""
try:
rpcclt = self.editwin.flist.pyshell.interp.rpcclt
except AttributeError:
rpcclt = None
if rpcclt:
return rpcclt.remotecall("exec", "get_the_calltip",
(expression,), {})
else:
entity = self.get_entity(expression)
return get_arg_text(entity)
def get_entity(self, expression):
"""Return the object corresponding to expression evaluated
in a namespace spanning sys.modules and __main.dict__.
"""
if expression:
namespace = sys.modules.copy()
namespace.update(__main__.__dict__)
try:
return eval(expression, namespace)
except BaseException:
# An uncaught exception closes idle, and eval can raise any
# exception, especially if user classes are involved.
return None
def _find_constructor(class_ob):
# Given a class object, return a function object used for the
# constructor (ie, __init__() ) or None if we can't find one.
try:
return class_ob.__init__.im_func
except AttributeError:
for base in class_ob.__bases__:
rc = _find_constructor(base)
if rc is not None: return rc
return None
# The following are used in get_arg_text
_MAX_COLS = 85
_MAX_LINES = 5 # enough for bytes
_INDENT = ' '*4 # for wrapped signatures
def get_arg_text(ob):
'''Return a string describing the signature of a callable object, or ''.
For Python-coded functions and methods, the first line is introspected.
Delete 'self' parameter for classes (.__init__) and bound methods.
The next lines are the first lines of the doc string up to the first
empty line or _MAX_LINES. For builtins, this typically includes
the arguments in addition to the return value.
'''
argspec = ""
try:
ob_call = ob.__call__
except BaseException:
if type(ob) is types.ClassType: # old-style
ob_call = ob
else:
return argspec
arg_offset = 0
if type(ob) in (types.ClassType, types.TypeType):
# Look for the first __init__ in the class chain with .im_func.
# Slot wrappers (builtins, classes defined in funcs) do not.
fob = _find_constructor(ob)
if fob is None:
fob = lambda: None
else:
arg_offset = 1
elif type(ob) == types.MethodType:
# bit of a hack for methods - turn it into a function
# and drop the "self" param for bound methods
fob = ob.im_func
if ob.im_self is not None:
arg_offset = 1
elif type(ob_call) == types.MethodType:
# a callable class instance
fob = ob_call.im_func
arg_offset = 1
else:
fob = ob
# Try to build one for Python defined functions
if type(fob) in [types.FunctionType, types.LambdaType]:
argcount = fob.func_code.co_argcount
real_args = fob.func_code.co_varnames[arg_offset:argcount]
defaults = fob.func_defaults or []
defaults = list(map(lambda name: "=%s" % repr(name), defaults))
defaults = [""] * (len(real_args) - len(defaults)) + defaults
items = map(lambda arg, dflt: arg + dflt, real_args, defaults)
for flag, pre, name in ((0x4, '*', 'args'), (0x8, '**', 'kwargs')):
if fob.func_code.co_flags & flag:
pre_name = pre + name
if name not in real_args:
items.append(pre_name)
else:
i = 1
while ((name+'%s') % i) in real_args:
i += 1
items.append((pre_name+'%s') % i)
argspec = ", ".join(items)
argspec = "(%s)" % re.sub("(?<!\d)\.\d+", "<tuple>", argspec)
lines = (textwrap.wrap(argspec, _MAX_COLS, subsequent_indent=_INDENT)
if len(argspec) > _MAX_COLS else [argspec] if argspec else [])
if isinstance(ob_call, types.MethodType):
doc = ob_call.__doc__
else:
doc = getattr(ob, "__doc__", "")
if doc:
for line in doc.split('\n', _MAX_LINES)[:_MAX_LINES]:
line = line.strip()
if not line:
break
if len(line) > _MAX_COLS:
line = line[: _MAX_COLS - 3] + '...'
lines.append(line)
argspec = '\n'.join(lines)
return argspec
if __name__ == '__main__':
from unittest import main
main('idlelib.idle_test.test_calltips', verbosity=2)

1591
Lib/idlelib/ChangeLog Normal file

File diff suppressed because it is too large Load Diff

236
Lib/idlelib/ClassBrowser.py Normal file
View File

@@ -0,0 +1,236 @@
"""Class browser.
XXX TO DO:
- reparse when source changed (maybe just a button would be OK?)
(or recheck on window popup)
- add popup menu with more options (e.g. doc strings, base classes, imports)
- show function argument list? (have to do pattern matching on source)
- should the classes and methods lists also be in the module's menu bar?
- add base classes to class browser tree
"""
import os
import sys
import pyclbr
from idlelib import PyShell
from idlelib.WindowList import ListedToplevel
from idlelib.TreeWidget import TreeNode, TreeItem, ScrolledCanvas
from idlelib.configHandler import idleConf
file_open = None # Method...Item and Class...Item use this.
# Normally PyShell.flist.open, but there is no PyShell.flist for htest.
class ClassBrowser:
def __init__(self, flist, name, path, _htest=False):
# XXX This API should change, if the file doesn't end in ".py"
# XXX the code here is bogus!
"""
_htest - bool, change box when location running htest.
"""
global file_open
if not _htest:
file_open = PyShell.flist.open
self.name = name
self.file = os.path.join(path[0], self.name + ".py")
self._htest = _htest
self.init(flist)
def close(self, event=None):
self.top.destroy()
self.node.destroy()
def init(self, flist):
self.flist = flist
# reset pyclbr
pyclbr._modules.clear()
# create top
self.top = top = ListedToplevel(flist.root)
top.protocol("WM_DELETE_WINDOW", self.close)
top.bind("<Escape>", self.close)
if self._htest: # place dialog below parent if running htest
top.geometry("+%d+%d" %
(flist.root.winfo_rootx(), flist.root.winfo_rooty() + 200))
self.settitle()
top.focus_set()
# create scrolled canvas
theme = idleConf.CurrentTheme()
background = idleConf.GetHighlight(theme, 'normal')['background']
sc = ScrolledCanvas(top, bg=background, highlightthickness=0, takefocus=1)
sc.frame.pack(expand=1, fill="both")
item = self.rootnode()
self.node = node = TreeNode(sc.canvas, None, item)
node.update()
node.expand()
def settitle(self):
self.top.wm_title("Class Browser - " + self.name)
self.top.wm_iconname("Class Browser")
def rootnode(self):
return ModuleBrowserTreeItem(self.file)
class ModuleBrowserTreeItem(TreeItem):
def __init__(self, file):
self.file = file
def GetText(self):
return os.path.basename(self.file)
def GetIconName(self):
return "python"
def GetSubList(self):
sublist = []
for name in self.listclasses():
item = ClassBrowserTreeItem(name, self.classes, self.file)
sublist.append(item)
return sublist
def OnDoubleClick(self):
if os.path.normcase(self.file[-3:]) != ".py":
return
if not os.path.exists(self.file):
return
PyShell.flist.open(self.file)
def IsExpandable(self):
return os.path.normcase(self.file[-3:]) == ".py"
def listclasses(self):
dir, file = os.path.split(self.file)
name, ext = os.path.splitext(file)
if os.path.normcase(ext) != ".py":
return []
try:
dict = pyclbr.readmodule_ex(name, [dir] + sys.path)
except ImportError:
return []
items = []
self.classes = {}
for key, cl in dict.items():
if cl.module == name:
s = key
if hasattr(cl, 'super') and cl.super:
supers = []
for sup in cl.super:
if type(sup) is type(''):
sname = sup
else:
sname = sup.name
if sup.module != cl.module:
sname = "%s.%s" % (sup.module, sname)
supers.append(sname)
s = s + "(%s)" % ", ".join(supers)
items.append((cl.lineno, s))
self.classes[s] = cl
items.sort()
list = []
for item, s in items:
list.append(s)
return list
class ClassBrowserTreeItem(TreeItem):
def __init__(self, name, classes, file):
self.name = name
self.classes = classes
self.file = file
try:
self.cl = self.classes[self.name]
except (IndexError, KeyError):
self.cl = None
self.isfunction = isinstance(self.cl, pyclbr.Function)
def GetText(self):
if self.isfunction:
return "def " + self.name + "(...)"
else:
return "class " + self.name
def GetIconName(self):
if self.isfunction:
return "python"
else:
return "folder"
def IsExpandable(self):
if self.cl:
try:
return not not self.cl.methods
except AttributeError:
return False
def GetSubList(self):
if not self.cl:
return []
sublist = []
for name in self.listmethods():
item = MethodBrowserTreeItem(name, self.cl, self.file)
sublist.append(item)
return sublist
def OnDoubleClick(self):
if not os.path.exists(self.file):
return
edit = file_open(self.file)
if hasattr(self.cl, 'lineno'):
lineno = self.cl.lineno
edit.gotoline(lineno)
def listmethods(self):
if not self.cl:
return []
items = []
for name, lineno in self.cl.methods.items():
items.append((lineno, name))
items.sort()
list = []
for item, name in items:
list.append(name)
return list
class MethodBrowserTreeItem(TreeItem):
def __init__(self, name, cl, file):
self.name = name
self.cl = cl
self.file = file
def GetText(self):
return "def " + self.name + "(...)"
def GetIconName(self):
return "python" # XXX
def IsExpandable(self):
return 0
def OnDoubleClick(self):
if not os.path.exists(self.file):
return
edit = file_open(self.file)
edit.gotoline(self.cl.methods[self.name])
def _class_browser(parent): #Wrapper for htest
try:
file = __file__
except NameError:
file = sys.argv[0]
if sys.argv[1:]:
file = sys.argv[1]
else:
file = sys.argv[0]
dir, file = os.path.split(file)
name = os.path.splitext(file)[0]
flist = PyShell.PyShellFileList(parent)
global file_open
file_open = flist.open
ClassBrowser(flist, name, [dir], _htest=True)
if __name__ == "__main__":
from idlelib.idle_test.htest import run
run(_class_browser)

176
Lib/idlelib/CodeContext.py Normal file
View File

@@ -0,0 +1,176 @@
"""CodeContext - Extension to display the block context above the edit window
Once code has scrolled off the top of a window, it can be difficult to
determine which block you are in. This extension implements a pane at the top
of each IDLE edit window which provides block structure hints. These hints are
the lines which contain the block opening keywords, e.g. 'if', for the
enclosing block. The number of hint lines is determined by the numlines
variable in the CodeContext section of config-extensions.def. Lines which do
not open blocks are not shown in the context hints pane.
"""
import Tkinter
from Tkconstants import TOP, LEFT, X, W, SUNKEN
import re
from sys import maxint as INFINITY
from idlelib.configHandler import idleConf
BLOCKOPENERS = {"class", "def", "elif", "else", "except", "finally", "for",
"if", "try", "while", "with"}
UPDATEINTERVAL = 100 # millisec
FONTUPDATEINTERVAL = 1000 # millisec
getspacesfirstword =\
lambda s, c=re.compile(r"^(\s*)(\w*)"): c.match(s).groups()
class CodeContext:
menudefs = [('options', [('!Code Conte_xt', '<<toggle-code-context>>')])]
context_depth = idleConf.GetOption("extensions", "CodeContext",
"numlines", type="int", default=3)
bgcolor = idleConf.GetOption("extensions", "CodeContext",
"bgcolor", type="str", default="LightGray")
fgcolor = idleConf.GetOption("extensions", "CodeContext",
"fgcolor", type="str", default="Black")
def __init__(self, editwin):
self.editwin = editwin
self.text = editwin.text
self.textfont = self.text["font"]
self.label = None
# self.info is a list of (line number, indent level, line text, block
# keyword) tuples providing the block structure associated with
# self.topvisible (the linenumber of the line displayed at the top of
# the edit window). self.info[0] is initialized as a 'dummy' line which
# starts the toplevel 'block' of the module.
self.info = [(0, -1, "", False)]
self.topvisible = 1
visible = idleConf.GetOption("extensions", "CodeContext",
"visible", type="bool", default=False)
if visible:
self.toggle_code_context_event()
self.editwin.setvar('<<toggle-code-context>>', True)
# Start two update cycles, one for context lines, one for font changes.
self.text.after(UPDATEINTERVAL, self.timer_event)
self.text.after(FONTUPDATEINTERVAL, self.font_timer_event)
def toggle_code_context_event(self, event=None):
if not self.label:
# Calculate the border width and horizontal padding required to
# align the context with the text in the main Text widget.
#
# All values are passed through int(str(<value>)), since some
# values may be pixel objects, which can't simply be added to ints.
widgets = self.editwin.text, self.editwin.text_frame
# Calculate the required vertical padding
padx = 0
for widget in widgets:
padx += int(str( widget.pack_info()['padx'] ))
padx += int(str( widget.cget('padx') ))
# Calculate the required border width
border = 0
for widget in widgets:
border += int(str( widget.cget('border') ))
self.label = Tkinter.Label(self.editwin.top,
text="\n" * (self.context_depth - 1),
anchor=W, justify=LEFT,
font=self.textfont,
bg=self.bgcolor, fg=self.fgcolor,
width=1, #don't request more than we get
padx=padx, border=border,
relief=SUNKEN)
# Pack the label widget before and above the text_frame widget,
# thus ensuring that it will appear directly above text_frame
self.label.pack(side=TOP, fill=X, expand=False,
before=self.editwin.text_frame)
else:
self.label.destroy()
self.label = None
idleConf.SetOption("extensions", "CodeContext", "visible",
str(self.label is not None))
idleConf.SaveUserCfgFiles()
def get_line_info(self, linenum):
"""Get the line indent value, text, and any block start keyword
If the line does not start a block, the keyword value is False.
The indentation of empty lines (or comment lines) is INFINITY.
"""
text = self.text.get("%d.0" % linenum, "%d.end" % linenum)
spaces, firstword = getspacesfirstword(text)
opener = firstword in BLOCKOPENERS and firstword
if len(text) == len(spaces) or text[len(spaces)] == '#':
indent = INFINITY
else:
indent = len(spaces)
return indent, text, opener
def get_context(self, new_topvisible, stopline=1, stopindent=0):
"""Get context lines, starting at new_topvisible and working backwards.
Stop when stopline or stopindent is reached. Return a tuple of context
data and the indent level at the top of the region inspected.
"""
assert stopline > 0
lines = []
# The indentation level we are currently in:
lastindent = INFINITY
# For a line to be interesting, it must begin with a block opening
# keyword, and have less indentation than lastindent.
for linenum in xrange(new_topvisible, stopline-1, -1):
indent, text, opener = self.get_line_info(linenum)
if indent < lastindent:
lastindent = indent
if opener in ("else", "elif"):
# We also show the if statement
lastindent += 1
if opener and linenum < new_topvisible and indent >= stopindent:
lines.append((linenum, indent, text, opener))
if lastindent <= stopindent:
break
lines.reverse()
return lines, lastindent
def update_code_context(self):
"""Update context information and lines visible in the context pane.
"""
new_topvisible = int(self.text.index("@0,0").split('.')[0])
if self.topvisible == new_topvisible: # haven't scrolled
return
if self.topvisible < new_topvisible: # scroll down
lines, lastindent = self.get_context(new_topvisible,
self.topvisible)
# retain only context info applicable to the region
# between topvisible and new_topvisible:
while self.info[-1][1] >= lastindent:
del self.info[-1]
elif self.topvisible > new_topvisible: # scroll up
stopindent = self.info[-1][1] + 1
# retain only context info associated
# with lines above new_topvisible:
while self.info[-1][0] >= new_topvisible:
stopindent = self.info[-1][1]
del self.info[-1]
lines, lastindent = self.get_context(new_topvisible,
self.info[-1][0]+1,
stopindent)
self.info.extend(lines)
self.topvisible = new_topvisible
# empty lines in context pane:
context_strings = [""] * max(0, self.context_depth - len(self.info))
# followed by the context hint lines:
context_strings += [x[2] for x in self.info[-self.context_depth:]]
self.label["text"] = '\n'.join(context_strings)
def timer_event(self):
if self.label:
self.update_code_context()
self.text.after(UPDATEINTERVAL, self.timer_event)
def font_timer_event(self):
newtextfont = self.text["font"]
if self.label and newtextfont != self.textfont:
self.textfont = newtextfont
self.label["font"] = self.textfont
self.text.after(FONTUPDATEINTERVAL, self.font_timer_event)

View File

@@ -0,0 +1,258 @@
import time
import re
import keyword
import __builtin__
from idlelib.Delegator import Delegator
from idlelib.configHandler import idleConf
DEBUG = False
def any(name, alternates):
"Return a named group pattern matching list of alternates."
return "(?P<%s>" % name + "|".join(alternates) + ")"
def make_pat():
kw = r"\b" + any("KEYWORD", keyword.kwlist) + r"\b"
builtinlist = [str(name) for name in dir(__builtin__)
if not name.startswith('_')]
# We don't know whether "print" is a function or a keyword,
# so we always treat is as a keyword (the most common case).
builtinlist.remove('print')
# self.file = file("file") :
# 1st 'file' colorized normal, 2nd as builtin, 3rd as string
builtin = r"([^.'\"\\#]\b|^)" + any("BUILTIN", builtinlist) + r"\b"
comment = any("COMMENT", [r"#[^\n]*"])
stringprefix = r"(\br|u|ur|R|U|UR|Ur|uR|b|B|br|Br|bR|BR)?"
sqstring = stringprefix + r"'[^'\\\n]*(\\.[^'\\\n]*)*'?"
dqstring = stringprefix + r'"[^"\\\n]*(\\.[^"\\\n]*)*"?'
sq3string = stringprefix + r"'''[^'\\]*((\\.|'(?!''))[^'\\]*)*(''')?"
dq3string = stringprefix + r'"""[^"\\]*((\\.|"(?!""))[^"\\]*)*(""")?'
string = any("STRING", [sq3string, dq3string, sqstring, dqstring])
return kw + "|" + builtin + "|" + comment + "|" + string +\
"|" + any("SYNC", [r"\n"])
prog = re.compile(make_pat(), re.S)
idprog = re.compile(r"\s+(\w+)", re.S)
class ColorDelegator(Delegator):
def __init__(self):
Delegator.__init__(self)
self.prog = prog
self.idprog = idprog
self.LoadTagDefs()
def setdelegate(self, delegate):
if self.delegate is not None:
self.unbind("<<toggle-auto-coloring>>")
Delegator.setdelegate(self, delegate)
if delegate is not None:
self.config_colors()
self.bind("<<toggle-auto-coloring>>", self.toggle_colorize_event)
self.notify_range("1.0", "end")
else:
# No delegate - stop any colorizing
self.stop_colorizing = True
self.allow_colorizing = False
def config_colors(self):
for tag, cnf in self.tagdefs.items():
if cnf:
self.tag_configure(tag, **cnf)
self.tag_raise('sel')
def LoadTagDefs(self):
theme = idleConf.CurrentTheme()
self.tagdefs = {
"COMMENT": idleConf.GetHighlight(theme, "comment"),
"KEYWORD": idleConf.GetHighlight(theme, "keyword"),
"BUILTIN": idleConf.GetHighlight(theme, "builtin"),
"STRING": idleConf.GetHighlight(theme, "string"),
"DEFINITION": idleConf.GetHighlight(theme, "definition"),
"SYNC": {'background':None,'foreground':None},
"TODO": {'background':None,'foreground':None},
"ERROR": idleConf.GetHighlight(theme, "error"),
# The following is used by ReplaceDialog:
"hit": idleConf.GetHighlight(theme, "hit"),
}
if DEBUG: print 'tagdefs',self.tagdefs
def insert(self, index, chars, tags=None):
index = self.index(index)
self.delegate.insert(index, chars, tags)
self.notify_range(index, index + "+%dc" % len(chars))
def delete(self, index1, index2=None):
index1 = self.index(index1)
self.delegate.delete(index1, index2)
self.notify_range(index1)
after_id = None
allow_colorizing = True
colorizing = False
def notify_range(self, index1, index2=None):
self.tag_add("TODO", index1, index2)
if self.after_id:
if DEBUG: print "colorizing already scheduled"
return
if self.colorizing:
self.stop_colorizing = True
if DEBUG: print "stop colorizing"
if self.allow_colorizing:
if DEBUG: print "schedule colorizing"
self.after_id = self.after(1, self.recolorize)
close_when_done = None # Window to be closed when done colorizing
def close(self, close_when_done=None):
if self.after_id:
after_id = self.after_id
self.after_id = None
if DEBUG: print "cancel scheduled recolorizer"
self.after_cancel(after_id)
self.allow_colorizing = False
self.stop_colorizing = True
if close_when_done:
if not self.colorizing:
close_when_done.destroy()
else:
self.close_when_done = close_when_done
def toggle_colorize_event(self, event):
if self.after_id:
after_id = self.after_id
self.after_id = None
if DEBUG: print "cancel scheduled recolorizer"
self.after_cancel(after_id)
if self.allow_colorizing and self.colorizing:
if DEBUG: print "stop colorizing"
self.stop_colorizing = True
self.allow_colorizing = not self.allow_colorizing
if self.allow_colorizing and not self.colorizing:
self.after_id = self.after(1, self.recolorize)
if DEBUG:
print "auto colorizing turned",\
self.allow_colorizing and "on" or "off"
return "break"
def recolorize(self):
self.after_id = None
if not self.delegate:
if DEBUG: print "no delegate"
return
if not self.allow_colorizing:
if DEBUG: print "auto colorizing is off"
return
if self.colorizing:
if DEBUG: print "already colorizing"
return
try:
self.stop_colorizing = False
self.colorizing = True
if DEBUG: print "colorizing..."
t0 = time.clock()
self.recolorize_main()
t1 = time.clock()
if DEBUG: print "%.3f seconds" % (t1-t0)
finally:
self.colorizing = False
if self.allow_colorizing and self.tag_nextrange("TODO", "1.0"):
if DEBUG: print "reschedule colorizing"
self.after_id = self.after(1, self.recolorize)
if self.close_when_done:
top = self.close_when_done
self.close_when_done = None
top.destroy()
def recolorize_main(self):
next = "1.0"
while True:
item = self.tag_nextrange("TODO", next)
if not item:
break
head, tail = item
self.tag_remove("SYNC", head, tail)
item = self.tag_prevrange("SYNC", head)
if item:
head = item[1]
else:
head = "1.0"
chars = ""
next = head
lines_to_get = 1
ok = False
while not ok:
mark = next
next = self.index(mark + "+%d lines linestart" %
lines_to_get)
lines_to_get = min(lines_to_get * 2, 100)
ok = "SYNC" in self.tag_names(next + "-1c")
line = self.get(mark, next)
##print head, "get", mark, next, "->", repr(line)
if not line:
return
for tag in self.tagdefs.keys():
self.tag_remove(tag, mark, next)
chars = chars + line
m = self.prog.search(chars)
while m:
for key, value in m.groupdict().items():
if value:
a, b = m.span(key)
self.tag_add(key,
head + "+%dc" % a,
head + "+%dc" % b)
if value in ("def", "class"):
m1 = self.idprog.match(chars, b)
if m1:
a, b = m1.span(1)
self.tag_add("DEFINITION",
head + "+%dc" % a,
head + "+%dc" % b)
m = self.prog.search(chars, m.end())
if "SYNC" in self.tag_names(next + "-1c"):
head = next
chars = ""
else:
ok = False
if not ok:
# We're in an inconsistent state, and the call to
# update may tell us to stop. It may also change
# the correct value for "next" (since this is a
# line.col string, not a true mark). So leave a
# crumb telling the next invocation to resume here
# in case update tells us to leave.
self.tag_add("TODO", next)
self.update()
if self.stop_colorizing:
if DEBUG: print "colorizing stopped"
return
def removecolors(self):
for tag in self.tagdefs.keys():
self.tag_remove(tag, "1.0", "end")
def _color_delegator(parent): # htest #
from Tkinter import Toplevel, Text
from idlelib.Percolator import Percolator
top = Toplevel(parent)
top.title("Test ColorDelegator")
top.geometry("200x100+%d+%d" % (parent.winfo_rootx() + 200,
parent.winfo_rooty() + 150))
source = "if somename: x = 'abc' # comment\nprint\n"
text = Text(top, background="white")
text.pack(expand=1, fill="both")
text.insert("insert", source)
text.focus_set()
p = Percolator(text)
d = ColorDelegator()
p.insertfilter(d)
if __name__ == "__main__":
from idlelib.idle_test.htest import run
run(_color_delegator)

529
Lib/idlelib/Debugger.py Normal file
View File

@@ -0,0 +1,529 @@
import os
import bdb
from Tkinter import *
from idlelib.WindowList import ListedToplevel
from idlelib.ScrolledList import ScrolledList
from idlelib import macosxSupport
class Idb(bdb.Bdb):
def __init__(self, gui):
self.gui = gui
bdb.Bdb.__init__(self)
def user_line(self, frame):
if self.in_rpc_code(frame):
self.set_step()
return
message = self.__frame2message(frame)
try:
self.gui.interaction(message, frame)
except TclError: # When closing debugger window with [x] in 3.x
pass
def user_exception(self, frame, info):
if self.in_rpc_code(frame):
self.set_step()
return
message = self.__frame2message(frame)
self.gui.interaction(message, frame, info)
def in_rpc_code(self, frame):
if frame.f_code.co_filename.count('rpc.py'):
return True
else:
prev_frame = frame.f_back
if prev_frame.f_code.co_filename.count('Debugger.py'):
# (that test will catch both Debugger.py and RemoteDebugger.py)
return False
return self.in_rpc_code(prev_frame)
def __frame2message(self, frame):
code = frame.f_code
filename = code.co_filename
lineno = frame.f_lineno
basename = os.path.basename(filename)
message = "%s:%s" % (basename, lineno)
if code.co_name != "?":
message = "%s: %s()" % (message, code.co_name)
return message
class Debugger:
vstack = vsource = vlocals = vglobals = None
def __init__(self, pyshell, idb=None):
if idb is None:
idb = Idb(self)
self.pyshell = pyshell
self.idb = idb
self.frame = None
self.make_gui()
self.interacting = 0
self.nesting_level = 0
def run(self, *args):
# Deal with the scenario where we've already got a program running
# in the debugger and we want to start another. If that is the case,
# our second 'run' was invoked from an event dispatched not from
# the main event loop, but from the nested event loop in 'interaction'
# below. So our stack looks something like this:
# outer main event loop
# run()
# <running program with traces>
# callback to debugger's interaction()
# nested event loop
# run() for second command
#
# This kind of nesting of event loops causes all kinds of problems
# (see e.g. issue #24455) especially when dealing with running as a
# subprocess, where there's all kinds of extra stuff happening in
# there - insert a traceback.print_stack() to check it out.
#
# By this point, we've already called restart_subprocess() in
# ScriptBinding. However, we also need to unwind the stack back to
# that outer event loop. To accomplish this, we:
# - return immediately from the nested run()
# - abort_loop ensures the nested event loop will terminate
# - the debugger's interaction routine completes normally
# - the restart_subprocess() will have taken care of stopping
# the running program, which will also let the outer run complete
#
# That leaves us back at the outer main event loop, at which point our
# after event can fire, and we'll come back to this routine with a
# clean stack.
if self.nesting_level > 0:
self.abort_loop()
self.root.after(100, lambda: self.run(*args))
return
try:
self.interacting = 1
return self.idb.run(*args)
finally:
self.interacting = 0
def close(self, event=None):
try:
self.quit()
except Exception:
pass
if self.interacting:
self.top.bell()
return
if self.stackviewer:
self.stackviewer.close(); self.stackviewer = None
# Clean up pyshell if user clicked debugger control close widget.
# (Causes a harmless extra cycle through close_debugger() if user
# toggled debugger from pyshell Debug menu)
self.pyshell.close_debugger()
# Now close the debugger control window....
self.top.destroy()
def make_gui(self):
pyshell = self.pyshell
self.flist = pyshell.flist
self.root = root = pyshell.root
self.top = top = ListedToplevel(root)
self.top.wm_title("Debug Control")
self.top.wm_iconname("Debug")
top.wm_protocol("WM_DELETE_WINDOW", self.close)
self.top.bind("<Escape>", self.close)
#
self.bframe = bframe = Frame(top)
self.bframe.pack(anchor="w")
self.buttons = bl = []
#
self.bcont = b = Button(bframe, text="Go", command=self.cont)
bl.append(b)
self.bstep = b = Button(bframe, text="Step", command=self.step)
bl.append(b)
self.bnext = b = Button(bframe, text="Over", command=self.next)
bl.append(b)
self.bret = b = Button(bframe, text="Out", command=self.ret)
bl.append(b)
self.bret = b = Button(bframe, text="Quit", command=self.quit)
bl.append(b)
#
for b in bl:
b.configure(state="disabled")
b.pack(side="left")
#
self.cframe = cframe = Frame(bframe)
self.cframe.pack(side="left")
#
if not self.vstack:
self.__class__.vstack = BooleanVar(top)
self.vstack.set(1)
self.bstack = Checkbutton(cframe,
text="Stack", command=self.show_stack, variable=self.vstack)
self.bstack.grid(row=0, column=0)
if not self.vsource:
self.__class__.vsource = BooleanVar(top)
self.bsource = Checkbutton(cframe,
text="Source", command=self.show_source, variable=self.vsource)
self.bsource.grid(row=0, column=1)
if not self.vlocals:
self.__class__.vlocals = BooleanVar(top)
self.vlocals.set(1)
self.blocals = Checkbutton(cframe,
text="Locals", command=self.show_locals, variable=self.vlocals)
self.blocals.grid(row=1, column=0)
if not self.vglobals:
self.__class__.vglobals = BooleanVar(top)
self.bglobals = Checkbutton(cframe,
text="Globals", command=self.show_globals, variable=self.vglobals)
self.bglobals.grid(row=1, column=1)
#
self.status = Label(top, anchor="w")
self.status.pack(anchor="w")
self.error = Label(top, anchor="w")
self.error.pack(anchor="w", fill="x")
self.errorbg = self.error.cget("background")
#
self.fstack = Frame(top, height=1)
self.fstack.pack(expand=1, fill="both")
self.flocals = Frame(top)
self.flocals.pack(expand=1, fill="both")
self.fglobals = Frame(top, height=1)
self.fglobals.pack(expand=1, fill="both")
#
if self.vstack.get():
self.show_stack()
if self.vlocals.get():
self.show_locals()
if self.vglobals.get():
self.show_globals()
def interaction(self, message, frame, info=None):
self.frame = frame
self.status.configure(text=message)
#
if info:
type, value, tb = info
try:
m1 = type.__name__
except AttributeError:
m1 = "%s" % str(type)
if value is not None:
try:
m1 = "%s: %s" % (m1, str(value))
except:
pass
bg = "yellow"
else:
m1 = ""
tb = None
bg = self.errorbg
self.error.configure(text=m1, background=bg)
#
sv = self.stackviewer
if sv:
stack, i = self.idb.get_stack(self.frame, tb)
sv.load_stack(stack, i)
#
self.show_variables(1)
#
if self.vsource.get():
self.sync_source_line()
#
for b in self.buttons:
b.configure(state="normal")
#
self.top.wakeup()
# Nested main loop: Tkinter's main loop is not reentrant, so use
# Tcl's vwait facility, which reenters the event loop until an
# event handler sets the variable we're waiting on
self.nesting_level += 1
self.root.tk.call('vwait', '::idledebugwait')
self.nesting_level -= 1
#
for b in self.buttons:
b.configure(state="disabled")
self.status.configure(text="")
self.error.configure(text="", background=self.errorbg)
self.frame = None
def sync_source_line(self):
frame = self.frame
if not frame:
return
filename, lineno = self.__frame2fileline(frame)
if filename[:1] + filename[-1:] != "<>" and os.path.exists(filename):
self.flist.gotofileline(filename, lineno)
def __frame2fileline(self, frame):
code = frame.f_code
filename = code.co_filename
lineno = frame.f_lineno
return filename, lineno
def cont(self):
self.idb.set_continue()
self.abort_loop()
def step(self):
self.idb.set_step()
self.abort_loop()
def next(self):
self.idb.set_next(self.frame)
self.abort_loop()
def ret(self):
self.idb.set_return(self.frame)
self.abort_loop()
def quit(self):
self.idb.set_quit()
self.abort_loop()
def abort_loop(self):
self.root.tk.call('set', '::idledebugwait', '1')
stackviewer = None
def show_stack(self):
if not self.stackviewer and self.vstack.get():
self.stackviewer = sv = StackViewer(self.fstack, self.flist, self)
if self.frame:
stack, i = self.idb.get_stack(self.frame, None)
sv.load_stack(stack, i)
else:
sv = self.stackviewer
if sv and not self.vstack.get():
self.stackviewer = None
sv.close()
self.fstack['height'] = 1
def show_source(self):
if self.vsource.get():
self.sync_source_line()
def show_frame(self, stackitem):
self.frame = stackitem[0] # lineno is stackitem[1]
self.show_variables()
localsviewer = None
globalsviewer = None
def show_locals(self):
lv = self.localsviewer
if self.vlocals.get():
if not lv:
self.localsviewer = NamespaceViewer(self.flocals, "Locals")
else:
if lv:
self.localsviewer = None
lv.close()
self.flocals['height'] = 1
self.show_variables()
def show_globals(self):
gv = self.globalsviewer
if self.vglobals.get():
if not gv:
self.globalsviewer = NamespaceViewer(self.fglobals, "Globals")
else:
if gv:
self.globalsviewer = None
gv.close()
self.fglobals['height'] = 1
self.show_variables()
def show_variables(self, force=0):
lv = self.localsviewer
gv = self.globalsviewer
frame = self.frame
if not frame:
ldict = gdict = None
else:
ldict = frame.f_locals
gdict = frame.f_globals
if lv and gv and ldict is gdict:
ldict = None
if lv:
lv.load_dict(ldict, force, self.pyshell.interp.rpcclt)
if gv:
gv.load_dict(gdict, force, self.pyshell.interp.rpcclt)
def set_breakpoint_here(self, filename, lineno):
self.idb.set_break(filename, lineno)
def clear_breakpoint_here(self, filename, lineno):
self.idb.clear_break(filename, lineno)
def clear_file_breaks(self, filename):
self.idb.clear_all_file_breaks(filename)
def load_breakpoints(self):
"Load PyShellEditorWindow breakpoints into subprocess debugger"
pyshell_edit_windows = self.pyshell.flist.inversedict.keys()
for editwin in pyshell_edit_windows:
filename = editwin.io.filename
try:
for lineno in editwin.breakpoints:
self.set_breakpoint_here(filename, lineno)
except AttributeError:
continue
class StackViewer(ScrolledList):
def __init__(self, master, flist, gui):
if macosxSupport.isAquaTk():
# At least on with the stock AquaTk version on OSX 10.4 you'll
# get a shaking GUI that eventually kills IDLE if the width
# argument is specified.
ScrolledList.__init__(self, master)
else:
ScrolledList.__init__(self, master, width=80)
self.flist = flist
self.gui = gui
self.stack = []
def load_stack(self, stack, index=None):
self.stack = stack
self.clear()
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
import linecache
sourceline = linecache.getline(filename, lineno)
import string
sourceline = string.strip(sourceline)
if funcname in ("?", "", None):
item = "%s, line %d: %s" % (modname, lineno, sourceline)
else:
item = "%s.%s(), line %d: %s" % (modname, funcname,
lineno, sourceline)
if i == index:
item = "> " + item
self.append(item)
if index is not None:
self.select(index)
def popup_event(self, event):
"override base method"
if self.stack:
return ScrolledList.popup_event(self, event)
def fill_menu(self):
"override base method"
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):
"override base method"
if 0 <= index < len(self.stack):
self.gui.show_frame(self.stack[index])
def on_double(self, index):
"override base method"
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.gui.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)
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
import repr
self.repr = 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, rpc_client=None):
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)
# Strip extra quotes caused by calling repr on the (already)
# repr'd value sent across the RPC interface:
if rpc_client:
svalue = svalue[1:-1]
l = Label(subframe, text=name)
l.grid(row=row, column=0, sticky="nw")
l = Entry(subframe, width=0, borderwidth=0)
l.insert(0, svalue)
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()

25
Lib/idlelib/Delegator.py Normal file
View File

@@ -0,0 +1,25 @@
class Delegator:
# The cache is only used to be able to change delegates!
def __init__(self, delegate=None):
self.delegate = delegate
self.__cache = set()
def __getattr__(self, name):
attr = getattr(self.delegate, name) # May raise AttributeError
setattr(self, name, attr)
self.__cache.add(name)
return attr
def resetcache(self):
for key in self.__cache:
try:
delattr(self, key)
except AttributeError:
pass
self.__cache.clear()
def setdelegate(self, delegate):
self.resetcache()
self.delegate = delegate

1704
Lib/idlelib/EditorWindow.py Normal file

File diff suppressed because it is too large Load Diff

126
Lib/idlelib/FileList.py Normal file
View File

@@ -0,0 +1,126 @@
import os
from Tkinter import *
import tkMessageBox
class FileList:
# N.B. this import overridden in PyShellFileList.
from idlelib.EditorWindow import EditorWindow
def __init__(self, root):
self.root = root
self.dict = {}
self.inversedict = {}
self.vars = {} # For EditorWindow.getrawvar (shared Tcl variables)
def open(self, filename, action=None):
assert filename
filename = self.canonize(filename)
if os.path.isdir(filename):
# This can happen when bad filename is passed on command line:
tkMessageBox.showerror(
"File Error",
"%r is a directory." % (filename,),
master=self.root)
return None
key = os.path.normcase(filename)
if key in self.dict:
edit = self.dict[key]
edit.top.wakeup()
return edit
if action:
# Don't create window, perform 'action', e.g. open in same window
return action(filename)
else:
return self.EditorWindow(self, filename, key)
def gotofileline(self, filename, lineno=None):
edit = self.open(filename)
if edit is not None and lineno is not None:
edit.gotoline(lineno)
def new(self, filename=None):
return self.EditorWindow(self, filename)
def close_all_callback(self, *args, **kwds):
for edit in self.inversedict.keys():
reply = edit.close()
if reply == "cancel":
break
return "break"
def unregister_maybe_terminate(self, edit):
try:
key = self.inversedict[edit]
except KeyError:
print "Don't know this EditorWindow object. (close)"
return
if key:
del self.dict[key]
del self.inversedict[edit]
if not self.inversedict:
self.root.quit()
def filename_changed_edit(self, edit):
edit.saved_change_hook()
try:
key = self.inversedict[edit]
except KeyError:
print "Don't know this EditorWindow object. (rename)"
return
filename = edit.io.filename
if not filename:
if key:
del self.dict[key]
self.inversedict[edit] = None
return
filename = self.canonize(filename)
newkey = os.path.normcase(filename)
if newkey == key:
return
if newkey in self.dict:
conflict = self.dict[newkey]
self.inversedict[conflict] = None
tkMessageBox.showerror(
"Name Conflict",
"You now have multiple edit windows open for %r" % (filename,),
master=self.root)
self.dict[newkey] = edit
self.inversedict[edit] = newkey
if key:
try:
del self.dict[key]
except KeyError:
pass
def canonize(self, filename):
if not os.path.isabs(filename):
try:
pwd = os.getcwd()
except os.error:
pass
else:
filename = os.path.join(pwd, filename)
return os.path.normpath(filename)
def _test():
from idlelib.EditorWindow import fixwordbreaks
from idlelib.run import fix_scaling
import sys
root = Tk()
fix_scaling(root)
fixwordbreaks(root)
root.withdraw()
flist = FileList(root)
if sys.argv[1:]:
for filename in sys.argv[1:]:
flist.open(filename)
else:
flist.new()
if flist.inversedict:
root.mainloop()
if __name__ == '__main__':
_test()

View File

@@ -0,0 +1,195 @@
"""Extension to format a paragraph or selection to a max width.
Does basic, standard text formatting, and also understands Python
comment blocks. Thus, for editing Python source code, this
extension is really only suitable for reformatting these comment
blocks or triple-quoted strings.
Known problems with comment reformatting:
* If there is a selection marked, and the first line of the
selection is not complete, the block will probably not be detected
as comments, and will have the normal "text formatting" rules
applied.
* If a comment block has leading whitespace that mixes tabs and
spaces, they will not be considered part of the same block.
* Fancy comments, like this bulleted list, aren't handled :-)
"""
import re
from idlelib.configHandler import idleConf
class FormatParagraph:
menudefs = [
('format', [ # /s/edit/format dscherer@cmu.edu
('Format Paragraph', '<<format-paragraph>>'),
])
]
def __init__(self, editwin):
self.editwin = editwin
def close(self):
self.editwin = None
def format_paragraph_event(self, event, limit=None):
"""Formats paragraph to a max width specified in idleConf.
If text is selected, format_paragraph_event will start breaking lines
at the max width, starting from the beginning selection.
If no text is selected, format_paragraph_event uses the current
cursor location to determine the paragraph (lines of text surrounded
by blank lines) and formats it.
The length limit parameter is for testing with a known value.
"""
if limit is None:
# The default length limit is that defined by pep8
limit = idleConf.GetOption(
'extensions', 'FormatParagraph', 'max-width',
type='int', default=72)
text = self.editwin.text
first, last = self.editwin.get_selection_indices()
if first and last:
data = text.get(first, last)
comment_header = get_comment_header(data)
else:
first, last, comment_header, data = \
find_paragraph(text, text.index("insert"))
if comment_header:
newdata = reformat_comment(data, limit, comment_header)
else:
newdata = reformat_paragraph(data, limit)
text.tag_remove("sel", "1.0", "end")
if newdata != data:
text.mark_set("insert", first)
text.undo_block_start()
text.delete(first, last)
text.insert(first, newdata)
text.undo_block_stop()
else:
text.mark_set("insert", last)
text.see("insert")
return "break"
def find_paragraph(text, mark):
"""Returns the start/stop indices enclosing the paragraph that mark is in.
Also returns the comment format string, if any, and paragraph of text
between the start/stop indices.
"""
lineno, col = map(int, mark.split("."))
line = text.get("%d.0" % lineno, "%d.end" % lineno)
# Look for start of next paragraph if the index passed in is a blank line
while text.compare("%d.0" % lineno, "<", "end") and is_all_white(line):
lineno = lineno + 1
line = text.get("%d.0" % lineno, "%d.end" % lineno)
first_lineno = lineno
comment_header = get_comment_header(line)
comment_header_len = len(comment_header)
# Once start line found, search for end of paragraph (a blank line)
while get_comment_header(line)==comment_header and \
not is_all_white(line[comment_header_len:]):
lineno = lineno + 1
line = text.get("%d.0" % lineno, "%d.end" % lineno)
last = "%d.0" % lineno
# Search back to beginning of paragraph (first blank line before)
lineno = first_lineno - 1
line = text.get("%d.0" % lineno, "%d.end" % lineno)
while lineno > 0 and \
get_comment_header(line)==comment_header and \
not is_all_white(line[comment_header_len:]):
lineno = lineno - 1
line = text.get("%d.0" % lineno, "%d.end" % lineno)
first = "%d.0" % (lineno+1)
return first, last, comment_header, text.get(first, last)
# This should perhaps be replaced with textwrap.wrap
def reformat_paragraph(data, limit):
"""Return data reformatted to specified width (limit)."""
lines = data.split("\n")
i = 0
n = len(lines)
while i < n and is_all_white(lines[i]):
i = i+1
if i >= n:
return data
indent1 = get_indent(lines[i])
if i+1 < n and not is_all_white(lines[i+1]):
indent2 = get_indent(lines[i+1])
else:
indent2 = indent1
new = lines[:i]
partial = indent1
while i < n and not is_all_white(lines[i]):
# XXX Should take double space after period (etc.) into account
words = re.split("(\s+)", lines[i])
for j in range(0, len(words), 2):
word = words[j]
if not word:
continue # Can happen when line ends in whitespace
if len((partial + word).expandtabs()) > limit and \
partial != indent1:
new.append(partial.rstrip())
partial = indent2
partial = partial + word + " "
if j+1 < len(words) and words[j+1] != " ":
partial = partial + " "
i = i+1
new.append(partial.rstrip())
# XXX Should reformat remaining paragraphs as well
new.extend(lines[i:])
return "\n".join(new)
def reformat_comment(data, limit, comment_header):
"""Return data reformatted to specified width with comment header."""
# Remove header from the comment lines
lc = len(comment_header)
data = "\n".join(line[lc:] for line in data.split("\n"))
# Reformat to maxformatwidth chars or a 20 char width,
# whichever is greater.
format_width = max(limit - len(comment_header), 20)
newdata = reformat_paragraph(data, format_width)
# re-split and re-insert the comment header.
newdata = newdata.split("\n")
# If the block ends in a \n, we dont want the comment prefix
# inserted after it. (Im not sure it makes sense to reformat a
# comment block that is not made of complete lines, but whatever!)
# Can't think of a clean solution, so we hack away
block_suffix = ""
if not newdata[-1]:
block_suffix = "\n"
newdata = newdata[:-1]
return '\n'.join(comment_header+line for line in newdata) + block_suffix
def is_all_white(line):
"""Return True if line is empty or all whitespace."""
return re.match(r"^\s*$", line) is not None
def get_indent(line):
"""Return the initial space or tab indent of line."""
return re.match(r"^([ \t]*)", line).group()
def get_comment_header(line):
"""Return string with leading whitespace and '#' from line or ''.
A null return indicates that the line is not a comment line. A non-
null return, such as ' #', will be used to find the other lines of
a comment block with the same indent.
"""
m = re.match(r"^([ \t]*#*)", line)
if m is None: return ""
return m.group(1)
if __name__ == "__main__":
import unittest
unittest.main('idlelib.idle_test.test_formatparagraph',
verbosity=2, exit=False)

159
Lib/idlelib/GrepDialog.py Normal file
View File

@@ -0,0 +1,159 @@
from __future__ import print_function
import os
import fnmatch
import re # for htest
import sys
from Tkinter import StringVar, BooleanVar, Checkbutton # for GrepDialog
from Tkinter import Tk, Text, Button, SEL, END # for htest
from idlelib import SearchEngine
from idlelib.SearchDialogBase import SearchDialogBase
# Importing OutputWindow fails due to import loop
# EditorWindow -> GrepDialop -> OutputWindow -> EditorWindow
def grep(text, io=None, flist=None):
root = text._root()
engine = SearchEngine.get(root)
if not hasattr(engine, "_grepdialog"):
engine._grepdialog = GrepDialog(root, engine, flist)
dialog = engine._grepdialog
searchphrase = text.get("sel.first", "sel.last")
dialog.open(text, searchphrase, io)
class GrepDialog(SearchDialogBase):
title = "Find in Files Dialog"
icon = "Grep"
needwrapbutton = 0
def __init__(self, root, engine, flist):
SearchDialogBase.__init__(self, root, engine)
self.flist = flist
self.globvar = StringVar(root)
self.recvar = BooleanVar(root)
def open(self, text, searchphrase, io=None):
SearchDialogBase.open(self, text, searchphrase)
if io:
path = io.filename or ""
else:
path = ""
dir, base = os.path.split(path)
head, tail = os.path.splitext(base)
if not tail:
tail = ".py"
self.globvar.set(os.path.join(dir, "*" + tail))
def create_entries(self):
SearchDialogBase.create_entries(self)
self.globent = self.make_entry("In files:", self.globvar)[0]
def create_other_buttons(self):
f = self.make_frame()[0]
btn = Checkbutton(f, anchor="w",
variable=self.recvar,
text="Recurse down subdirectories")
btn.pack(side="top", fill="both")
btn.select()
def create_command_buttons(self):
SearchDialogBase.create_command_buttons(self)
self.make_button("Search Files", self.default_command, 1)
def default_command(self, event=None):
prog = self.engine.getprog()
if not prog:
return
path = self.globvar.get()
if not path:
self.top.bell()
return
from idlelib.OutputWindow import OutputWindow # leave here!
save = sys.stdout
try:
sys.stdout = OutputWindow(self.flist)
self.grep_it(prog, path)
finally:
sys.stdout = save
def grep_it(self, prog, path):
dir, base = os.path.split(path)
list = self.findfiles(dir, base, self.recvar.get())
list.sort()
self.close()
pat = self.engine.getpat()
print("Searching %r in %s ..." % (pat, path))
hits = 0
try:
for fn in list:
try:
with open(fn) as f:
for lineno, line in enumerate(f, 1):
if line[-1:] == '\n':
line = line[:-1]
if prog.search(line):
sys.stdout.write("%s: %s: %s\n" %
(fn, lineno, line))
hits += 1
except IOError as msg:
print(msg)
print(("Hits found: %s\n"
"(Hint: right-click to open locations.)"
% hits) if hits else "No hits.")
except AttributeError:
# Tk window has been closed, OutputWindow.text = None,
# so in OW.write, OW.text.insert fails.
pass
def findfiles(self, dir, base, rec):
try:
names = os.listdir(dir or os.curdir)
except os.error as msg:
print(msg)
return []
list = []
subdirs = []
for name in names:
fn = os.path.join(dir, name)
if os.path.isdir(fn):
subdirs.append(fn)
else:
if fnmatch.fnmatch(name, base):
list.append(fn)
if rec:
for subdir in subdirs:
list.extend(self.findfiles(subdir, base, rec))
return list
def close(self, event=None):
if self.top:
self.top.grab_release()
self.top.withdraw()
def _grep_dialog(parent): # htest #
from idlelib.PyShell import PyShellFileList
root = Tk()
root.title("Test GrepDialog")
width, height, x, y = list(map(int, re.split('[x+]', parent.geometry())))
root.geometry("+%d+%d"%(x, y + 150))
flist = PyShellFileList(root)
text = Text(root, height=5)
text.pack()
def show_grep_dialog():
text.tag_add(SEL, "1.0", END)
grep(text, flist=flist)
text.tag_remove(SEL, "1.0", END)
button = Button(root, text="Show GrepDialog", command=show_grep_dialog)
button.pack()
root.mainloop()
if __name__ == "__main__":
import unittest
unittest.main('idlelib.idle_test.test_grep', verbosity=2, exit=False)
from idlelib.idle_test.htest import run
run(_grep_dialog)

296
Lib/idlelib/HISTORY.txt Normal file
View File

@@ -0,0 +1,296 @@
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.
What's New in IDLEfork 0.8.1?
=============================
*Release date: 22-Jul-2001*
- New tarball released as a result of the 'revitalisation' of the IDLEfork
project.
- This release requires python 2.1 or better. Compatibility with earlier
versions of python (especially ancient ones like 1.5x) is no longer a
priority in IDLEfork development.
- This release is based on a merging of the earlier IDLE fork work with current
cvs IDLE (post IDLE version 0.8), with some minor additional coding by Kurt
B. Kaiser and Stephen M. Gava.
- This release is basically functional but also contains some known breakages,
particularly with running things from the shell window. Also the debugger is
not working, but I believe this was the case with the previous IDLE fork
release (0.7.1) as well.
- This release is being made now to mark the point at which IDLEfork is
launching into a new stage of development.
- IDLEfork CVS will now be branched to enable further development and
exploration of the two "execution in a remote process" patches submitted by
David Scherer (David's is currently in IDLEfork) and GvR, while stabilisation
and development of less heavyweight improvements (like user customisation)
can continue on the trunk.
What's New in IDLEfork 0.7.1?
==============================
*Release date: 15-Aug-2000*
- First project tarball released.
- This was the first release of IDLE fork, which at this stage was a
combination of IDLE 0.5 and the VPython idle fork, with additional changes
coded by David Scherer, Peter Schneider-Kamp and Nicholas Riley.
IDLEfork 0.7.1 - 29 May 2000
-----------------------------
David Scherer <dscherer@cmu.edu>
- This is a modification of the CVS version of IDLE 0.5, updated as of
2000-03-09. It is alpha software and might be unstable. If it breaks, you
get to keep both pieces.
- If you have problems or suggestions, you should either contact me or post to
the list at http://www.python.org/mailman/listinfo/idle-dev (making it clear
that you are using this modified version of IDLE).
- Changes:
- The ExecBinding module, a replacement for ScriptBinding, executes programs
in a separate process, piping standard I/O through an RPC mechanism to an
OnDemandOutputWindow in IDLE. It supports executing unnamed programs
(through a temporary file). It does not yet support debugging.
- When running programs with ExecBinding, tracebacks will be clipped to
exclude system modules. If, however, a system module calls back into the
user program, that part of the traceback will be shown.
- The OnDemandOutputWindow class has been improved. In particular, it now
supports a readline() function used to implement user input, and a
scroll_clear() operation which is used to hide the output of a previous run
by scrolling it out of the window.
- Startup behavior has been changed. By default IDLE starts up with just a
blank editor window, rather than an interactive window. Opening a file in
such a blank window replaces the (nonexistent) contents of that window
instead of creating another window. Because of the need to have a
well-known port for the ExecBinding protocol, only one copy of IDLE can be
running. Additional invocations use the RPC mechanism to report their
command line arguments to the copy already running.
- The menus have been reorganized. In particular, the excessively large
'edit' menu has been split up into 'edit', 'format', and 'run'.
- 'Python Documentation' now works on Windows, if the win32api module is
present.
- A few key bindings have been changed: F1 now loads Python Documentation
instead of the IDLE help; shift-TAB is now a synonym for unindent.
- New modules:
ExecBinding.py Executes program through loader
loader.py Bootstraps user program
protocol.py RPC protocol
Remote.py User-process interpreter
spawn.py OS-specific code to start programs
- Files modified:
autoindent.py ( bindings tweaked )
bindings.py ( menus reorganized )
config.txt ( execbinding enabled )
editorwindow.py ( new menus, fixed 'Python Documentation' )
filelist.py ( hook for "open in same window" )
formatparagraph.py ( bindings tweaked )
idle.bat ( removed absolute pathname )
idle.pyw ( weird bug due to import with same name? )
iobinding.py ( open in same window, EOL convention )
keydefs.py ( bindings tweaked )
outputwindow.py ( readline, scroll_clear, etc )
pyshell.py ( changed startup behavior )
readme.txt ( <Recursion on file with id=1234567> )
IDLE 0.5 - February 2000 - 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.
======================================================================

255
Lib/idlelib/HyperParser.py Normal file
View File

@@ -0,0 +1,255 @@
"""Provide advanced parsing abilities for ParenMatch and other extensions.
HyperParser uses PyParser. PyParser mostly gives information on the
proper indentation of code. HyperParser gives additional information on
the structure of code.
"""
import string
import keyword
from idlelib import PyParse
class HyperParser:
def __init__(self, editwin, index):
"To initialize, analyze the surroundings of the given index."
self.editwin = editwin
self.text = text = editwin.text
parser = PyParse.Parser(editwin.indentwidth, editwin.tabwidth)
def index2line(index):
return int(float(index))
lno = index2line(text.index(index))
if not editwin.context_use_ps1:
for context in editwin.num_context_lines:
startat = max(lno - context, 1)
startatindex = repr(startat) + ".0"
stopatindex = "%d.end" % lno
# We add the newline because PyParse requires a newline
# at end. We add a space so that index won't be at end
# of line, so that its status will be the same as the
# char before it, if should.
parser.set_str(text.get(startatindex, stopatindex)+' \n')
bod = parser.find_good_parse_start(
editwin._build_char_in_string_func(startatindex))
if bod is not None or startat == 1:
break
parser.set_lo(bod or 0)
else:
r = text.tag_prevrange("console", index)
if r:
startatindex = r[1]
else:
startatindex = "1.0"
stopatindex = "%d.end" % lno
# We add the newline because PyParse requires it. We add a
# space so that index won't be at end of line, so that its
# status will be the same as the char before it, if should.
parser.set_str(text.get(startatindex, stopatindex)+' \n')
parser.set_lo(0)
# We want what the parser has, minus the last newline and space.
self.rawtext = parser.str[:-2]
# Parser.str apparently preserves the statement we are in, so
# that stopatindex can be used to synchronize the string with
# the text box indices.
self.stopatindex = stopatindex
self.bracketing = parser.get_last_stmt_bracketing()
# find which pairs of bracketing are openers. These always
# correspond to a character of rawtext.
self.isopener = [i>0 and self.bracketing[i][1] >
self.bracketing[i-1][1]
for i in range(len(self.bracketing))]
self.set_index(index)
def set_index(self, index):
"""Set the index to which the functions relate.
The index must be in the same statement.
"""
indexinrawtext = (len(self.rawtext) -
len(self.text.get(index, self.stopatindex)))
if indexinrawtext < 0:
raise ValueError("Index %s precedes the analyzed statement"
% index)
self.indexinrawtext = indexinrawtext
# find the rightmost bracket to which index belongs
self.indexbracket = 0
while (self.indexbracket < len(self.bracketing)-1 and
self.bracketing[self.indexbracket+1][0] < self.indexinrawtext):
self.indexbracket += 1
if (self.indexbracket < len(self.bracketing)-1 and
self.bracketing[self.indexbracket+1][0] == self.indexinrawtext and
not self.isopener[self.indexbracket+1]):
self.indexbracket += 1
def is_in_string(self):
"""Is the index given to the HyperParser in a string?"""
# The bracket to which we belong should be an opener.
# If it's an opener, it has to have a character.
return (self.isopener[self.indexbracket] and
self.rawtext[self.bracketing[self.indexbracket][0]]
in ('"', "'"))
def is_in_code(self):
"""Is the index given to the HyperParser in normal code?"""
return (not self.isopener[self.indexbracket] or
self.rawtext[self.bracketing[self.indexbracket][0]]
not in ('#', '"', "'"))
def get_surrounding_brackets(self, openers='([{', mustclose=False):
"""Return bracket indexes or None.
If the index given to the HyperParser is surrounded by a
bracket defined in openers (or at least has one before it),
return the indices of the opening bracket and the closing
bracket (or the end of line, whichever comes first).
If it is not surrounded by brackets, or the end of line comes
before the closing bracket and mustclose is True, returns None.
"""
bracketinglevel = self.bracketing[self.indexbracket][1]
before = self.indexbracket
while (not self.isopener[before] or
self.rawtext[self.bracketing[before][0]] not in openers or
self.bracketing[before][1] > bracketinglevel):
before -= 1
if before < 0:
return None
bracketinglevel = min(bracketinglevel, self.bracketing[before][1])
after = self.indexbracket + 1
while (after < len(self.bracketing) and
self.bracketing[after][1] >= bracketinglevel):
after += 1
beforeindex = self.text.index("%s-%dc" %
(self.stopatindex, len(self.rawtext)-self.bracketing[before][0]))
if (after >= len(self.bracketing) or
self.bracketing[after][0] > len(self.rawtext)):
if mustclose:
return None
afterindex = self.stopatindex
else:
# We are after a real char, so it is a ')' and we give the
# index before it.
afterindex = self.text.index(
"%s-%dc" % (self.stopatindex,
len(self.rawtext)-(self.bracketing[after][0]-1)))
return beforeindex, afterindex
# Ascii chars that may be in a white space
_whitespace_chars = " \t\n\\"
# Ascii chars that may be in an identifier
_id_chars = string.ascii_letters + string.digits + "_"
# Ascii chars that may be the first char of an identifier
_id_first_chars = string.ascii_letters + "_"
# Given a string and pos, return the number of chars in the
# identifier which ends at pos, or 0 if there is no such one. Saved
# words are not identifiers.
def _eat_identifier(self, str, limit, pos):
i = pos
while i > limit and str[i-1] in self._id_chars:
i -= 1
if (i < pos and (str[i] not in self._id_first_chars or
keyword.iskeyword(str[i:pos]))):
i = pos
return pos - i
def get_expression(self):
"""Return a string with the Python expression which ends at the
given index, which is empty if there is no real one.
"""
if not self.is_in_code():
raise ValueError("get_expression should only be called "
"if index is inside a code.")
rawtext = self.rawtext
bracketing = self.bracketing
brck_index = self.indexbracket
brck_limit = bracketing[brck_index][0]
pos = self.indexinrawtext
last_identifier_pos = pos
postdot_phase = True
while 1:
# Eat whitespaces, comments, and if postdot_phase is False - a dot
while 1:
if pos>brck_limit and rawtext[pos-1] in self._whitespace_chars:
# Eat a whitespace
pos -= 1
elif (not postdot_phase and
pos > brck_limit and rawtext[pos-1] == '.'):
# Eat a dot
pos -= 1
postdot_phase = True
# The next line will fail if we are *inside* a comment,
# but we shouldn't be.
elif (pos == brck_limit and brck_index > 0 and
rawtext[bracketing[brck_index-1][0]] == '#'):
# Eat a comment
brck_index -= 2
brck_limit = bracketing[brck_index][0]
pos = bracketing[brck_index+1][0]
else:
# If we didn't eat anything, quit.
break
if not postdot_phase:
# We didn't find a dot, so the expression end at the
# last identifier pos.
break
ret = self._eat_identifier(rawtext, brck_limit, pos)
if ret:
# There is an identifier to eat
pos = pos - ret
last_identifier_pos = pos
# Now, to continue the search, we must find a dot.
postdot_phase = False
# (the loop continues now)
elif pos == brck_limit:
# We are at a bracketing limit. If it is a closing
# bracket, eat the bracket, otherwise, stop the search.
level = bracketing[brck_index][1]
while brck_index > 0 and bracketing[brck_index-1][1] > level:
brck_index -= 1
if bracketing[brck_index][0] == brck_limit:
# We were not at the end of a closing bracket
break
pos = bracketing[brck_index][0]
brck_index -= 1
brck_limit = bracketing[brck_index][0]
last_identifier_pos = pos
if rawtext[pos] in "([":
# [] and () may be used after an identifier, so we
# continue. postdot_phase is True, so we don't allow a dot.
pass
else:
# We can't continue after other types of brackets
if rawtext[pos] in "'\"":
# Scan a string prefix
while pos > 0 and rawtext[pos - 1] in "rRbBuU":
pos -= 1
last_identifier_pos = pos
break
else:
# We've found an operator or something.
break
return rawtext[last_identifier_pos:self.indexinrawtext]
if __name__ == '__main__':
import unittest
unittest.main('idlelib.idle_test.test_hyperparser', verbosity=2)

612
Lib/idlelib/IOBinding.py Normal file
View File

@@ -0,0 +1,612 @@
# changes by dscherer@cmu.edu
# - IOBinding.open() replaces the current window with the opened file,
# if the current window is both unmodified and unnamed
# - IOBinding.loadfile() interprets Windows, UNIX, and Macintosh
# end-of-line conventions, instead of relying on the standard library,
# which will only understand the local convention.
import codecs
from codecs import BOM_UTF8
import os
import pipes
import re
import sys
import tempfile
from Tkinter import *
import tkFileDialog
import tkMessageBox
from SimpleDialog import SimpleDialog
from idlelib.configHandler import idleConf
# Try setting the locale, so that we can find out
# what encoding to use
try:
import locale
locale.setlocale(locale.LC_CTYPE, "")
except (ImportError, locale.Error):
pass
# Encoding for file names
filesystemencoding = sys.getfilesystemencoding()
encoding = "ascii"
if sys.platform == 'win32':
# On Windows, we could use "mbcs". However, to give the user
# a portable encoding name, we need to find the code page
try:
encoding = locale.getdefaultlocale()[1]
codecs.lookup(encoding)
except LookupError:
pass
else:
try:
# Different things can fail here: the locale module may not be
# loaded, it may not offer nl_langinfo, or CODESET, or the
# resulting codeset may be unknown to Python. We ignore all
# these problems, falling back to ASCII
encoding = locale.nl_langinfo(locale.CODESET)
if encoding is None or encoding is '':
# situation occurs on Mac OS X
encoding = 'ascii'
codecs.lookup(encoding)
except (NameError, AttributeError, LookupError):
# Try getdefaultlocale well: it parses environment variables,
# which may give a clue. Unfortunately, getdefaultlocale has
# bugs that can cause ValueError.
try:
encoding = locale.getdefaultlocale()[1]
if encoding is None or encoding is '':
# situation occurs on Mac OS X
encoding = 'ascii'
codecs.lookup(encoding)
except (ValueError, LookupError):
pass
encoding = encoding.lower()
coding_re = re.compile(r'^[ \t\f]*#.*?coding[:=][ \t]*([-\w.]+)')
blank_re = re.compile(r'^[ \t\f]*(?:[#\r\n]|$)')
class EncodingMessage(SimpleDialog):
"Inform user that an encoding declaration is needed."
def __init__(self, master, enc):
self.should_edit = False
self.root = top = Toplevel(master)
top.bind("<Return>", self.return_event)
top.bind("<Escape>", self.do_ok)
top.protocol("WM_DELETE_WINDOW", self.wm_delete_window)
top.wm_title("I/O Warning")
top.wm_iconname("I/O Warning")
self.top = top
l1 = Label(top,
text="Non-ASCII found, yet no encoding declared. Add a line like")
l1.pack(side=TOP, anchor=W)
l2 = Entry(top, font="courier")
l2.insert(0, "# -*- coding: %s -*-" % enc)
# For some reason, the text is not selectable anymore if the
# widget is disabled.
# l2['state'] = DISABLED
l2.pack(side=TOP, anchor = W, fill=X)
l3 = Label(top, text="to your file\n"
"See Language Reference, 2.1.4 Encoding declarations.\n"
"Choose OK to save this file as %s\n"
"Edit your general options to silence this warning" % enc)
l3.pack(side=TOP, anchor = W)
buttons = Frame(top)
buttons.pack(side=TOP, fill=X)
# Both return and cancel mean the same thing: do nothing
self.default = self.cancel = 0
b1 = Button(buttons, text="Ok", default="active",
command=self.do_ok)
b1.pack(side=LEFT, fill=BOTH, expand=1)
b2 = Button(buttons, text="Edit my file",
command=self.do_edit)
b2.pack(side=LEFT, fill=BOTH, expand=1)
self._set_transient(master)
def do_ok(self):
self.done(0)
def do_edit(self):
self.done(1)
def coding_spec(str):
"""Return the encoding declaration according to PEP 263.
Raise LookupError if the encoding is declared but unknown.
"""
# Only consider the first two lines
lst = str.split("\n", 2)[:2]
for line in lst:
match = coding_re.match(line)
if match is not None:
break
if not blank_re.match(line):
return None
else:
return None
name = match.group(1)
# Check whether the encoding is known
import codecs
try:
codecs.lookup(name)
except LookupError:
# The standard encoding error does not indicate the encoding
raise LookupError, "Unknown encoding "+name
return name
class IOBinding:
def __init__(self, editwin):
self.editwin = editwin
self.text = editwin.text
self.__id_open = self.text.bind("<<open-window-from-file>>", self.open)
self.__id_save = self.text.bind("<<save-window>>", self.save)
self.__id_saveas = self.text.bind("<<save-window-as-file>>",
self.save_as)
self.__id_savecopy = self.text.bind("<<save-copy-of-window-as-file>>",
self.save_a_copy)
self.fileencoding = None
self.__id_print = self.text.bind("<<print-window>>", self.print_window)
def close(self):
# Undo command bindings
self.text.unbind("<<open-window-from-file>>", self.__id_open)
self.text.unbind("<<save-window>>", self.__id_save)
self.text.unbind("<<save-window-as-file>>",self.__id_saveas)
self.text.unbind("<<save-copy-of-window-as-file>>", self.__id_savecopy)
self.text.unbind("<<print-window>>", self.__id_print)
# Break cycles
self.editwin = None
self.text = None
self.filename_change_hook = None
def get_saved(self):
return self.editwin.get_saved()
def set_saved(self, flag):
self.editwin.set_saved(flag)
def reset_undo(self):
self.editwin.reset_undo()
filename_change_hook = None
def set_filename_change_hook(self, hook):
self.filename_change_hook = hook
filename = None
dirname = None
def set_filename(self, filename):
if filename and os.path.isdir(filename):
self.filename = None
self.dirname = filename
else:
self.filename = filename
self.dirname = None
self.set_saved(1)
if self.filename_change_hook:
self.filename_change_hook()
def open(self, event=None, editFile=None):
flist = self.editwin.flist
# Save in case parent window is closed (ie, during askopenfile()).
if flist:
if not editFile:
filename = self.askopenfile()
else:
filename=editFile
if filename:
# If editFile is valid and already open, flist.open will
# shift focus to its existing window.
# If the current window exists and is a fresh unnamed,
# unmodified editor window (not an interpreter shell),
# pass self.loadfile to flist.open so it will load the file
# in the current window (if the file is not already open)
# instead of a new window.
if (self.editwin and
not getattr(self.editwin, 'interp', None) and
not self.filename and
self.get_saved()):
flist.open(filename, self.loadfile)
else:
flist.open(filename)
else:
if self.text:
self.text.focus_set()
return "break"
# Code for use outside IDLE:
if self.get_saved():
reply = self.maybesave()
if reply == "cancel":
self.text.focus_set()
return "break"
if not editFile:
filename = self.askopenfile()
else:
filename=editFile
if filename:
self.loadfile(filename)
else:
self.text.focus_set()
return "break"
eol = r"(\r\n)|\n|\r" # \r\n (Windows), \n (UNIX), or \r (Mac)
eol_re = re.compile(eol)
eol_convention = os.linesep # Default
def loadfile(self, filename):
try:
# open the file in binary mode so that we can handle
# end-of-line convention ourselves.
with open(filename, 'rb') as f:
chars = f.read()
except IOError as msg:
tkMessageBox.showerror("I/O Error", str(msg), parent=self.text)
return False
chars = self.decode(chars)
# We now convert all end-of-lines to '\n's
firsteol = self.eol_re.search(chars)
if firsteol:
self.eol_convention = firsteol.group(0)
if isinstance(self.eol_convention, unicode):
# Make sure it is an ASCII string
self.eol_convention = self.eol_convention.encode("ascii")
chars = self.eol_re.sub(r"\n", chars)
self.text.delete("1.0", "end")
self.set_filename(None)
self.text.insert("1.0", chars)
self.reset_undo()
self.set_filename(filename)
self.text.mark_set("insert", "1.0")
self.text.yview("insert")
self.updaterecentfileslist(filename)
return True
def decode(self, chars):
"""Create a Unicode string
If that fails, let Tcl try its best
"""
# Check presence of a UTF-8 signature first
if chars.startswith(BOM_UTF8):
try:
chars = chars[3:].decode("utf-8")
except UnicodeError:
# has UTF-8 signature, but fails to decode...
return chars
else:
# Indicates that this file originally had a BOM
self.fileencoding = BOM_UTF8
return chars
# Next look for coding specification
try:
enc = coding_spec(chars)
except LookupError as name:
tkMessageBox.showerror(
title="Error loading the file",
message="The encoding '%s' is not known to this Python "\
"installation. The file may not display correctly" % name,
parent = self.text)
enc = None
if enc:
try:
return unicode(chars, enc)
except UnicodeError:
pass
# If it is ASCII, we need not to record anything
try:
return unicode(chars, 'ascii')
except UnicodeError:
pass
# Finally, try the locale's encoding. This is deprecated;
# the user should declare a non-ASCII encoding
try:
chars = unicode(chars, encoding)
self.fileencoding = encoding
except UnicodeError:
pass
return chars
def maybesave(self):
if self.get_saved():
return "yes"
message = "Do you want to save %s before closing?" % (
self.filename or "this untitled document")
confirm = tkMessageBox.askyesnocancel(
title="Save On Close",
message=message,
default=tkMessageBox.YES,
parent=self.text)
if confirm:
reply = "yes"
self.save(None)
if not self.get_saved():
reply = "cancel"
elif confirm is None:
reply = "cancel"
else:
reply = "no"
self.text.focus_set()
return reply
def save(self, event):
if not self.filename:
self.save_as(event)
else:
if self.writefile(self.filename):
self.set_saved(True)
try:
self.editwin.store_file_breaks()
except AttributeError: # may be a PyShell
pass
self.text.focus_set()
return "break"
def save_as(self, event):
filename = self.asksavefile()
if filename:
if self.writefile(filename):
self.set_filename(filename)
self.set_saved(1)
try:
self.editwin.store_file_breaks()
except AttributeError:
pass
self.text.focus_set()
self.updaterecentfileslist(filename)
return "break"
def save_a_copy(self, event):
filename = self.asksavefile()
if filename:
self.writefile(filename)
self.text.focus_set()
self.updaterecentfileslist(filename)
return "break"
def writefile(self, filename):
self.fixlastline()
chars = self.encode(self.text.get("1.0", "end-1c"))
if self.eol_convention != "\n":
chars = chars.replace("\n", self.eol_convention)
try:
with open(filename, "wb") as f:
f.write(chars)
f.flush()
os.fsync(f.fileno())
return True
except IOError as msg:
tkMessageBox.showerror("I/O Error", str(msg),
parent=self.text)
return False
def encode(self, chars):
if isinstance(chars, str):
# This is either plain ASCII, or Tk was returning mixed-encoding
# text to us. Don't try to guess further.
return chars
# See whether there is anything non-ASCII in it.
# If not, no need to figure out the encoding.
try:
return chars.encode('ascii')
except UnicodeError:
pass
# If there is an encoding declared, try this first.
try:
enc = coding_spec(chars)
failed = None
except LookupError as msg:
failed = msg
enc = None
if enc:
try:
return chars.encode(enc)
except UnicodeError:
failed = "Invalid encoding '%s'" % enc
if failed:
tkMessageBox.showerror(
"I/O Error",
"%s. Saving as UTF-8" % failed,
parent = self.text)
# If there was a UTF-8 signature, use that. This should not fail
if self.fileencoding == BOM_UTF8 or failed:
return BOM_UTF8 + chars.encode("utf-8")
# Try the original file encoding next, if any
if self.fileencoding:
try:
return chars.encode(self.fileencoding)
except UnicodeError:
tkMessageBox.showerror(
"I/O Error",
"Cannot save this as '%s' anymore. Saving as UTF-8" \
% self.fileencoding,
parent = self.text)
return BOM_UTF8 + chars.encode("utf-8")
# Nothing was declared, and we had not determined an encoding
# on loading. Recommend an encoding line.
config_encoding = idleConf.GetOption("main","EditorWindow",
"encoding")
if config_encoding == 'utf-8':
# User has requested that we save files as UTF-8
return BOM_UTF8 + chars.encode("utf-8")
ask_user = True
try:
chars = chars.encode(encoding)
enc = encoding
if config_encoding == 'locale':
ask_user = False
except UnicodeError:
chars = BOM_UTF8 + chars.encode("utf-8")
enc = "utf-8"
if not ask_user:
return chars
dialog = EncodingMessage(self.editwin.top, enc)
dialog.go()
if dialog.num == 1:
# User asked us to edit the file
encline = "# -*- coding: %s -*-\n" % enc
firstline = self.text.get("1.0", "2.0")
if firstline.startswith("#!"):
# Insert encoding after #! line
self.text.insert("2.0", encline)
else:
self.text.insert("1.0", encline)
return self.encode(self.text.get("1.0", "end-1c"))
return chars
def fixlastline(self):
c = self.text.get("end-2c")
if c != '\n':
self.text.insert("end-1c", "\n")
def print_window(self, event):
confirm = tkMessageBox.askokcancel(
title="Print",
message="Print to Default Printer",
default=tkMessageBox.OK,
parent=self.text)
if not confirm:
self.text.focus_set()
return "break"
tempfilename = None
saved = self.get_saved()
if saved:
filename = self.filename
# shell undo is reset after every prompt, looks saved, probably isn't
if not saved or filename is None:
(tfd, tempfilename) = tempfile.mkstemp(prefix='IDLE_tmp_')
filename = tempfilename
os.close(tfd)
if not self.writefile(tempfilename):
os.unlink(tempfilename)
return "break"
platform = os.name
printPlatform = True
if platform == 'posix': #posix platform
command = idleConf.GetOption('main','General',
'print-command-posix')
command = command + " 2>&1"
elif platform == 'nt': #win32 platform
command = idleConf.GetOption('main','General','print-command-win')
else: #no printing for this platform
printPlatform = False
if printPlatform: #we can try to print for this platform
command = command % pipes.quote(filename)
pipe = os.popen(command, "r")
# things can get ugly on NT if there is no printer available.
output = pipe.read().strip()
status = pipe.close()
if status:
output = "Printing failed (exit status 0x%x)\n" % \
status + output
if output:
output = "Printing command: %s\n" % repr(command) + output
tkMessageBox.showerror("Print status", output, parent=self.text)
else: #no printing for this platform
message = "Printing is not enabled for this platform: %s" % platform
tkMessageBox.showinfo("Print status", message, parent=self.text)
if tempfilename:
os.unlink(tempfilename)
return "break"
opendialog = None
savedialog = None
filetypes = [
("Python files", "*.py *.pyw", "TEXT"),
("Text files", "*.txt", "TEXT"),
("All files", "*"),
]
defaultextension = '.py' if sys.platform == 'darwin' else ''
def askopenfile(self):
dir, base = self.defaultfilename("open")
if not self.opendialog:
self.opendialog = tkFileDialog.Open(parent=self.text,
filetypes=self.filetypes)
filename = self.opendialog.show(initialdir=dir, initialfile=base)
if isinstance(filename, unicode):
filename = filename.encode(filesystemencoding)
return filename
def defaultfilename(self, mode="open"):
if self.filename:
return os.path.split(self.filename)
elif self.dirname:
return self.dirname, ""
else:
try:
pwd = os.getcwd()
except os.error:
pwd = ""
return pwd, ""
def asksavefile(self):
dir, base = self.defaultfilename("save")
if not self.savedialog:
self.savedialog = tkFileDialog.SaveAs(
parent=self.text,
filetypes=self.filetypes,
defaultextension=self.defaultextension)
filename = self.savedialog.show(initialdir=dir, initialfile=base)
if isinstance(filename, unicode):
filename = filename.encode(filesystemencoding)
return filename
def updaterecentfileslist(self,filename):
"Update recent file list on all editor windows"
self.editwin.update_recent_files_list(filename)
def _io_binding(parent): # htest #
from Tkinter import Toplevel, Text
root = Toplevel(parent)
root.title("Test IOBinding")
width, height, x, y = list(map(int, re.split('[x+]', parent.geometry())))
root.geometry("+%d+%d"%(x, y + 150))
class MyEditWin:
def __init__(self, text):
self.text = text
self.flist = None
self.text.bind("<Control-o>", self.open)
self.text.bind('<Control-p>', self.printer)
self.text.bind("<Control-s>", self.save)
self.text.bind("<Alt-s>", self.saveas)
self.text.bind('<Control-c>', self.savecopy)
def get_saved(self): return 0
def set_saved(self, flag): pass
def reset_undo(self): pass
def update_recent_files_list(self, filename): pass
def open(self, event):
self.text.event_generate("<<open-window-from-file>>")
def printer(self, event):
self.text.event_generate("<<print-window>>")
def save(self, event):
self.text.event_generate("<<save-window>>")
def saveas(self, event):
self.text.event_generate("<<save-window-as-file>>")
def savecopy(self, event):
self.text.event_generate("<<save-copy-of-window-as-file>>")
text = Text(root)
text.pack()
text.focus_set()
editwin = MyEditWin(text)
IOBinding(editwin)
if __name__ == "__main__":
from idlelib.idle_test.htest import run
run(_io_binding)

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 B

BIN
Lib/idlelib/Icons/idle.icns Normal file

Binary file not shown.

BIN
Lib/idlelib/Icons/idle.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

BIN
Lib/idlelib/Icons/tk.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 B

104
Lib/idlelib/IdleHistory.py Normal file
View File

@@ -0,0 +1,104 @@
"Implement Idle Shell history mechanism with History class"
from idlelib.configHandler import idleConf
class History:
''' Implement Idle Shell history mechanism.
store - Store source statement (called from PyShell.resetoutput).
fetch - Fetch stored statement matching prefix already entered.
history_next - Bound to <<history-next>> event (default Alt-N).
history_prev - Bound to <<history-prev>> event (default Alt-P).
'''
def __init__(self, text):
'''Initialize data attributes and bind event methods.
.text - Idle wrapper of tk Text widget, with .bell().
.history - source statements, possibly with multiple lines.
.prefix - source already entered at prompt; filters history list.
.pointer - index into history.
.cyclic - wrap around history list (or not).
'''
self.text = text
self.history = []
self.prefix = None
self.pointer = None
self.cyclic = idleConf.GetOption("main", "History", "cyclic", 1, "bool")
text.bind("<<history-previous>>", self.history_prev)
text.bind("<<history-next>>", self.history_next)
def history_next(self, event):
"Fetch later statement; start with ealiest if cyclic."
self.fetch(reverse=False)
return "break"
def history_prev(self, event):
"Fetch earlier statement; start with most recent."
self.fetch(reverse=True)
return "break"
def fetch(self, reverse):
'''Fetch statememt and replace current line in text widget.
Set prefix and pointer as needed for successive fetches.
Reset them to None, None when returning to the start line.
Sound bell when return to start line or cannot leave a line
because cyclic is False.
'''
nhist = len(self.history)
pointer = self.pointer
prefix = self.prefix
if pointer is not None and prefix is not None:
if self.text.compare("insert", "!=", "end-1c") or \
self.text.get("iomark", "end-1c") != self.history[pointer]:
pointer = prefix = None
self.text.mark_set("insert", "end-1c") # != after cursor move
if pointer is None or prefix is None:
prefix = self.text.get("iomark", "end-1c")
if reverse:
pointer = nhist # will be decremented
else:
if self.cyclic:
pointer = -1 # will be incremented
else: # abort history_next
self.text.bell()
return
nprefix = len(prefix)
while 1:
pointer += -1 if reverse else 1
if pointer < 0 or pointer >= nhist:
self.text.bell()
if not self.cyclic and pointer < 0: # abort history_prev
return
else:
if self.text.get("iomark", "end-1c") != prefix:
self.text.delete("iomark", "end-1c")
self.text.insert("iomark", prefix)
pointer = prefix = None
break
item = self.history[pointer]
if item[:nprefix] == prefix and len(item) > nprefix:
self.text.delete("iomark", "end-1c")
self.text.insert("iomark", item)
break
self.text.see("insert")
self.text.tag_remove("sel", "1.0", "end")
self.pointer = pointer
self.prefix = prefix
def store(self, source):
"Store Shell input statement into history list."
source = source.strip()
if len(source) > 2:
# avoid duplicates
try:
self.history.remove(source)
except ValueError:
pass
self.history.append(source)
self.pointer = None
self.prefix = None
if __name__ == "__main__":
from unittest import main
main('idlelib.idle_test.test_idlehistory', verbosity=2, exit=False)

430
Lib/idlelib/MultiCall.py Normal file
View File

@@ -0,0 +1,430 @@
"""
MultiCall - a class which inherits its methods from a Tkinter widget (Text, for
example), but enables multiple calls of functions per virtual event - all
matching events will be called, not only the most specific one. This is done
by wrapping the event functions - event_add, event_delete and event_info.
MultiCall recognizes only a subset of legal event sequences. Sequences which
are not recognized are treated by the original Tk handling mechanism. A
more-specific event will be called before a less-specific event.
The recognized sequences are complete one-event sequences (no emacs-style
Ctrl-X Ctrl-C, no shortcuts like <3>), for all types of events.
Key/Button Press/Release events can have modifiers.
The recognized modifiers are Shift, Control, Option and Command for Mac, and
Control, Alt, Shift, Meta/M for other platforms.
For all events which were handled by MultiCall, a new member is added to the
event instance passed to the binded functions - mc_type. This is one of the
event type constants defined in this module (such as MC_KEYPRESS).
For Key/Button events (which are handled by MultiCall and may receive
modifiers), another member is added - mc_state. This member gives the state
of the recognized modifiers, as a combination of the modifier constants
also defined in this module (for example, MC_SHIFT).
Using these members is absolutely portable.
The order by which events are called is defined by these rules:
1. A more-specific event will be called before a less-specific event.
2. A recently-binded event will be called before a previously-binded event,
unless this conflicts with the first rule.
Each function will be called at most once for each event.
"""
import sys
import string
import re
import Tkinter
# the event type constants, which define the meaning of mc_type
MC_KEYPRESS=0; MC_KEYRELEASE=1; MC_BUTTONPRESS=2; MC_BUTTONRELEASE=3;
MC_ACTIVATE=4; MC_CIRCULATE=5; MC_COLORMAP=6; MC_CONFIGURE=7;
MC_DEACTIVATE=8; MC_DESTROY=9; MC_ENTER=10; MC_EXPOSE=11; MC_FOCUSIN=12;
MC_FOCUSOUT=13; MC_GRAVITY=14; MC_LEAVE=15; MC_MAP=16; MC_MOTION=17;
MC_MOUSEWHEEL=18; MC_PROPERTY=19; MC_REPARENT=20; MC_UNMAP=21; MC_VISIBILITY=22;
# the modifier state constants, which define the meaning of mc_state
MC_SHIFT = 1<<0; MC_CONTROL = 1<<2; MC_ALT = 1<<3; MC_META = 1<<5
MC_OPTION = 1<<6; MC_COMMAND = 1<<7
# define the list of modifiers, to be used in complex event types.
if sys.platform == "darwin":
_modifiers = (("Shift",), ("Control",), ("Option",), ("Command",))
_modifier_masks = (MC_SHIFT, MC_CONTROL, MC_OPTION, MC_COMMAND)
else:
_modifiers = (("Control",), ("Alt",), ("Shift",), ("Meta", "M"))
_modifier_masks = (MC_CONTROL, MC_ALT, MC_SHIFT, MC_META)
# a dictionary to map a modifier name into its number
_modifier_names = dict([(name, number)
for number in range(len(_modifiers))
for name in _modifiers[number]])
# A binder is a class which binds functions to one type of event. It has two
# methods: bind and unbind, which get a function and a parsed sequence, as
# returned by _parse_sequence(). There are two types of binders:
# _SimpleBinder handles event types with no modifiers and no detail.
# No Python functions are called when no events are binded.
# _ComplexBinder handles event types with modifiers and a detail.
# A Python function is called each time an event is generated.
class _SimpleBinder:
def __init__(self, type, widget, widgetinst):
self.type = type
self.sequence = '<'+_types[type][0]+'>'
self.widget = widget
self.widgetinst = widgetinst
self.bindedfuncs = []
self.handlerid = None
def bind(self, triplet, func):
if not self.handlerid:
def handler(event, l = self.bindedfuncs, mc_type = self.type):
event.mc_type = mc_type
wascalled = {}
for i in range(len(l)-1, -1, -1):
func = l[i]
if func not in wascalled:
wascalled[func] = True
r = func(event)
if r:
return r
self.handlerid = self.widget.bind(self.widgetinst,
self.sequence, handler)
self.bindedfuncs.append(func)
def unbind(self, triplet, func):
self.bindedfuncs.remove(func)
if not self.bindedfuncs:
self.widget.unbind(self.widgetinst, self.sequence, self.handlerid)
self.handlerid = None
def __del__(self):
if self.handlerid:
self.widget.unbind(self.widgetinst, self.sequence, self.handlerid)
# An int in range(1 << len(_modifiers)) represents a combination of modifiers
# (if the least significant bit is on, _modifiers[0] is on, and so on).
# _state_subsets gives for each combination of modifiers, or *state*,
# a list of the states which are a subset of it. This list is ordered by the
# number of modifiers is the state - the most specific state comes first.
_states = range(1 << len(_modifiers))
_state_names = [''.join(m[0]+'-'
for i, m in enumerate(_modifiers)
if (1 << i) & s)
for s in _states]
def expand_substates(states):
'''For each item of states return a list containing all combinations of
that item with individual bits reset, sorted by the number of set bits.
'''
def nbits(n):
"number of bits set in n base 2"
nb = 0
while n:
n, rem = divmod(n, 2)
nb += rem
return nb
statelist = []
for state in states:
substates = list(set(state & x for x in states))
substates.sort(key=nbits, reverse=True)
statelist.append(substates)
return statelist
_state_subsets = expand_substates(_states)
# _state_codes gives for each state, the portable code to be passed as mc_state
_state_codes = []
for s in _states:
r = 0
for i in range(len(_modifiers)):
if (1 << i) & s:
r |= _modifier_masks[i]
_state_codes.append(r)
class _ComplexBinder:
# This class binds many functions, and only unbinds them when it is deleted.
# self.handlerids is the list of seqs and ids of binded handler functions.
# The binded functions sit in a dictionary of lists of lists, which maps
# a detail (or None) and a state into a list of functions.
# When a new detail is discovered, handlers for all the possible states
# are binded.
def __create_handler(self, lists, mc_type, mc_state):
def handler(event, lists = lists,
mc_type = mc_type, mc_state = mc_state,
ishandlerrunning = self.ishandlerrunning,
doafterhandler = self.doafterhandler):
ishandlerrunning[:] = [True]
event.mc_type = mc_type
event.mc_state = mc_state
wascalled = {}
r = None
for l in lists:
for i in range(len(l)-1, -1, -1):
func = l[i]
if func not in wascalled:
wascalled[func] = True
r = l[i](event)
if r:
break
if r:
break
ishandlerrunning[:] = []
# Call all functions in doafterhandler and remove them from list
for f in doafterhandler:
f()
doafterhandler[:] = []
if r:
return r
return handler
def __init__(self, type, widget, widgetinst):
self.type = type
self.typename = _types[type][0]
self.widget = widget
self.widgetinst = widgetinst
self.bindedfuncs = {None: [[] for s in _states]}
self.handlerids = []
# we don't want to change the lists of functions while a handler is
# running - it will mess up the loop and anyway, we usually want the
# change to happen from the next event. So we have a list of functions
# for the handler to run after it finishes calling the binded functions.
# It calls them only once.
# ishandlerrunning is a list. An empty one means no, otherwise - yes.
# this is done so that it would be mutable.
self.ishandlerrunning = []
self.doafterhandler = []
for s in _states:
lists = [self.bindedfuncs[None][i] for i in _state_subsets[s]]
handler = self.__create_handler(lists, type, _state_codes[s])
seq = '<'+_state_names[s]+self.typename+'>'
self.handlerids.append((seq, self.widget.bind(self.widgetinst,
seq, handler)))
def bind(self, triplet, func):
if triplet[2] not in self.bindedfuncs:
self.bindedfuncs[triplet[2]] = [[] for s in _states]
for s in _states:
lists = [ self.bindedfuncs[detail][i]
for detail in (triplet[2], None)
for i in _state_subsets[s] ]
handler = self.__create_handler(lists, self.type,
_state_codes[s])
seq = "<%s%s-%s>"% (_state_names[s], self.typename, triplet[2])
self.handlerids.append((seq, self.widget.bind(self.widgetinst,
seq, handler)))
doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].append(func)
if not self.ishandlerrunning:
doit()
else:
self.doafterhandler.append(doit)
def unbind(self, triplet, func):
doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].remove(func)
if not self.ishandlerrunning:
doit()
else:
self.doafterhandler.append(doit)
def __del__(self):
for seq, id in self.handlerids:
self.widget.unbind(self.widgetinst, seq, id)
# define the list of event types to be handled by MultiEvent. the order is
# compatible with the definition of event type constants.
_types = (
("KeyPress", "Key"), ("KeyRelease",), ("ButtonPress", "Button"),
("ButtonRelease",), ("Activate",), ("Circulate",), ("Colormap",),
("Configure",), ("Deactivate",), ("Destroy",), ("Enter",), ("Expose",),
("FocusIn",), ("FocusOut",), ("Gravity",), ("Leave",), ("Map",),
("Motion",), ("MouseWheel",), ("Property",), ("Reparent",), ("Unmap",),
("Visibility",),
)
# which binder should be used for every event type?
_binder_classes = (_ComplexBinder,) * 4 + (_SimpleBinder,) * (len(_types)-4)
# A dictionary to map a type name into its number
_type_names = dict([(name, number)
for number in range(len(_types))
for name in _types[number]])
_keysym_re = re.compile(r"^\w+$")
_button_re = re.compile(r"^[1-5]$")
def _parse_sequence(sequence):
"""Get a string which should describe an event sequence. If it is
successfully parsed as one, return a tuple containing the state (as an int),
the event type (as an index of _types), and the detail - None if none, or a
string if there is one. If the parsing is unsuccessful, return None.
"""
if not sequence or sequence[0] != '<' or sequence[-1] != '>':
return None
words = string.split(sequence[1:-1], '-')
modifiers = 0
while words and words[0] in _modifier_names:
modifiers |= 1 << _modifier_names[words[0]]
del words[0]
if words and words[0] in _type_names:
type = _type_names[words[0]]
del words[0]
else:
return None
if _binder_classes[type] is _SimpleBinder:
if modifiers or words:
return None
else:
detail = None
else:
# _ComplexBinder
if type in [_type_names[s] for s in ("KeyPress", "KeyRelease")]:
type_re = _keysym_re
else:
type_re = _button_re
if not words:
detail = None
elif len(words) == 1 and type_re.match(words[0]):
detail = words[0]
else:
return None
return modifiers, type, detail
def _triplet_to_sequence(triplet):
if triplet[2]:
return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'-'+ \
triplet[2]+'>'
else:
return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'>'
_multicall_dict = {}
def MultiCallCreator(widget):
"""Return a MultiCall class which inherits its methods from the
given widget class (for example, Tkinter.Text). This is used
instead of a templating mechanism.
"""
if widget in _multicall_dict:
return _multicall_dict[widget]
class MultiCall (widget):
assert issubclass(widget, Tkinter.Misc)
def __init__(self, *args, **kwargs):
widget.__init__(self, *args, **kwargs)
# a dictionary which maps a virtual event to a tuple with:
# 0. the function binded
# 1. a list of triplets - the sequences it is binded to
self.__eventinfo = {}
self.__binders = [_binder_classes[i](i, widget, self)
for i in range(len(_types))]
def bind(self, sequence=None, func=None, add=None):
#print "bind(%s, %s, %s) called." % (sequence, func, add)
if type(sequence) is str and len(sequence) > 2 and \
sequence[:2] == "<<" and sequence[-2:] == ">>":
if sequence in self.__eventinfo:
ei = self.__eventinfo[sequence]
if ei[0] is not None:
for triplet in ei[1]:
self.__binders[triplet[1]].unbind(triplet, ei[0])
ei[0] = func
if ei[0] is not None:
for triplet in ei[1]:
self.__binders[triplet[1]].bind(triplet, func)
else:
self.__eventinfo[sequence] = [func, []]
return widget.bind(self, sequence, func, add)
def unbind(self, sequence, funcid=None):
if type(sequence) is str and len(sequence) > 2 and \
sequence[:2] == "<<" and sequence[-2:] == ">>" and \
sequence in self.__eventinfo:
func, triplets = self.__eventinfo[sequence]
if func is not None:
for triplet in triplets:
self.__binders[triplet[1]].unbind(triplet, func)
self.__eventinfo[sequence][0] = None
return widget.unbind(self, sequence, funcid)
def event_add(self, virtual, *sequences):
#print "event_add(%s,%s) was called"%(repr(virtual),repr(sequences))
if virtual not in self.__eventinfo:
self.__eventinfo[virtual] = [None, []]
func, triplets = self.__eventinfo[virtual]
for seq in sequences:
triplet = _parse_sequence(seq)
if triplet is None:
#print >> sys.stderr, "Seq. %s was added by Tkinter."%seq
widget.event_add(self, virtual, seq)
else:
if func is not None:
self.__binders[triplet[1]].bind(triplet, func)
triplets.append(triplet)
def event_delete(self, virtual, *sequences):
if virtual not in self.__eventinfo:
return
func, triplets = self.__eventinfo[virtual]
for seq in sequences:
triplet = _parse_sequence(seq)
if triplet is None:
#print >> sys.stderr, "Seq. %s was deleted by Tkinter."%seq
widget.event_delete(self, virtual, seq)
else:
if func is not None:
self.__binders[triplet[1]].unbind(triplet, func)
triplets.remove(triplet)
def event_info(self, virtual=None):
if virtual is None or virtual not in self.__eventinfo:
return widget.event_info(self, virtual)
else:
return tuple(map(_triplet_to_sequence,
self.__eventinfo[virtual][1])) + \
widget.event_info(self, virtual)
def __del__(self):
for virtual in self.__eventinfo:
func, triplets = self.__eventinfo[virtual]
if func:
for triplet in triplets:
self.__binders[triplet[1]].unbind(triplet, func)
_multicall_dict[widget] = MultiCall
return MultiCall
def _multi_call(parent):
root = Tkinter.Tk()
root.title("Test MultiCall")
width, height, x, y = list(map(int, re.split('[x+]', parent.geometry())))
root.geometry("+%d+%d"%(x, y + 150))
text = MultiCallCreator(Tkinter.Text)(root)
text.pack()
def bindseq(seq, n=[0]):
def handler(event):
print seq
text.bind("<<handler%d>>"%n[0], handler)
text.event_add("<<handler%d>>"%n[0], seq)
n[0] += 1
bindseq("<Key>")
bindseq("<Control-Key>")
bindseq("<Alt-Key-a>")
bindseq("<Control-Key-a>")
bindseq("<Alt-Control-Key-a>")
bindseq("<Key-b>")
bindseq("<Control-Button-1>")
bindseq("<Button-2>")
bindseq("<Alt-Button-1>")
bindseq("<FocusOut>")
bindseq("<Enter>")
bindseq("<Leave>")
root.mainloop()
if __name__ == "__main__":
from idlelib.idle_test.htest import run
run(_multi_call)

View File

@@ -0,0 +1,47 @@
from Tkinter import *
class MultiStatusBar(Frame):
def __init__(self, master=None, **kw):
if master is None:
master = Tk()
Frame.__init__(self, master, **kw)
self.labels = {}
def set_label(self, name, text='', side=LEFT, width=0):
if name not in self.labels:
label = Label(self, borderwidth=0, anchor=W)
label.pack(side=side, pady=0, padx=4)
self.labels[name] = label
else:
label = self.labels[name]
if width != 0:
label.config(width=width)
label.config(text=text)
def _multistatus_bar(parent):
root = Tk()
width, height, x, y = list(map(int, re.split('[x+]', parent.geometry())))
root.geometry("+%d+%d" %(x, y + 150))
root.title("Test multistatus bar")
frame = Frame(root)
text = Text(frame)
text.pack()
msb = MultiStatusBar(frame)
msb.set_label("one", "hello")
msb.set_label("two", "world")
msb.pack(side=BOTTOM, fill=X)
def change():
msb.set_label("one", "foo")
msb.set_label("two", "bar")
button = Button(root, text="Update status", command=change)
button.pack(side=BOTTOM)
frame.pack()
frame.mainloop()
root.mainloop()
if __name__ == '__main__':
from idlelib.idle_test.htest import run
run(_multistatus_bar)

1188
Lib/idlelib/NEWS.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,156 @@
# XXX TO DO:
# - popup menu
# - support partial or total redisplay
# - more doc strings
# - tooltips
# object browser
# XXX TO DO:
# - for classes/modules, add "open source" to object browser
import re
from idlelib.TreeWidget import TreeItem, TreeNode, ScrolledCanvas
from repr import Repr
myrepr = Repr()
myrepr.maxstring = 100
myrepr.maxother = 100
class ObjectTreeItem(TreeItem):
def __init__(self, labeltext, object, setfunction=None):
self.labeltext = labeltext
self.object = object
self.setfunction = setfunction
def GetLabelText(self):
return self.labeltext
def GetText(self):
return myrepr.repr(self.object)
def GetIconName(self):
if not self.IsExpandable():
return "python"
def IsEditable(self):
return self.setfunction is not None
def SetText(self, text):
try:
value = eval(text)
self.setfunction(value)
except:
pass
else:
self.object = value
def IsExpandable(self):
return not not dir(self.object)
def GetSubList(self):
keys = dir(self.object)
sublist = []
for key in keys:
try:
value = getattr(self.object, key)
except AttributeError:
continue
item = make_objecttreeitem(
str(key) + " =",
value,
lambda value, key=key, object=self.object:
setattr(object, key, value))
sublist.append(item)
return sublist
class InstanceTreeItem(ObjectTreeItem):
def IsExpandable(self):
return True
def GetSubList(self):
sublist = ObjectTreeItem.GetSubList(self)
sublist.insert(0,
make_objecttreeitem("__class__ =", self.object.__class__))
return sublist
class ClassTreeItem(ObjectTreeItem):
def IsExpandable(self):
return True
def GetSubList(self):
sublist = ObjectTreeItem.GetSubList(self)
if len(self.object.__bases__) == 1:
item = make_objecttreeitem("__bases__[0] =",
self.object.__bases__[0])
else:
item = make_objecttreeitem("__bases__ =", self.object.__bases__)
sublist.insert(0, item)
return sublist
class AtomicObjectTreeItem(ObjectTreeItem):
def IsExpandable(self):
return 0
class SequenceTreeItem(ObjectTreeItem):
def IsExpandable(self):
return len(self.object) > 0
def keys(self):
return range(len(self.object))
def GetSubList(self):
sublist = []
for key in self.keys():
try:
value = self.object[key]
except KeyError:
continue
def setfunction(value, key=key, object=self.object):
object[key] = value
item = make_objecttreeitem("%r:" % (key,), value, setfunction)
sublist.append(item)
return sublist
class DictTreeItem(SequenceTreeItem):
def keys(self):
keys = self.object.keys()
try:
keys.sort()
except:
pass
return keys
from types import *
dispatch = {
IntType: AtomicObjectTreeItem,
LongType: AtomicObjectTreeItem,
FloatType: AtomicObjectTreeItem,
StringType: AtomicObjectTreeItem,
TupleType: SequenceTreeItem,
ListType: SequenceTreeItem,
DictType: DictTreeItem,
InstanceType: InstanceTreeItem,
ClassType: ClassTreeItem,
}
def make_objecttreeitem(labeltext, object, setfunction=None):
t = type(object)
if t in dispatch:
c = dispatch[t]
else:
c = ObjectTreeItem
return c(labeltext, object, setfunction)
def _object_browser(parent):
import sys
from Tkinter import Tk
root = Tk()
root.title("Test ObjectBrowser")
width, height, x, y = list(map(int, re.split('[x+]', parent.geometry())))
root.geometry("+%d+%d"%(x, y + 150))
root.configure(bd=0, bg="yellow")
root.focus_set()
sc = ScrolledCanvas(root, bg="white", highlightthickness=0, takefocus=1)
sc.frame.pack(expand=1, fill="both")
item = make_objecttreeitem("sys", sys)
node = TreeNode(sc.canvas, None, item)
node.update()
root.mainloop()
if __name__ == '__main__':
from idlelib.idle_test.htest import run
run(_object_browser)

149
Lib/idlelib/OutputWindow.py Normal file
View File

@@ -0,0 +1,149 @@
from Tkinter import *
from idlelib.EditorWindow import EditorWindow
import re
import tkMessageBox
from idlelib import IOBinding
class OutputWindow(EditorWindow):
"""An editor window that can serve as an output file.
Also the future base class for the Python shell window.
This class has no input facilities.
"""
def __init__(self, *args):
EditorWindow.__init__(self, *args)
self.text.bind("<<goto-file-line>>", self.goto_file_line)
# Customize EditorWindow
def ispythonsource(self, filename):
# No colorization needed
return 0
def short_title(self):
return "Output"
def maybesave(self):
# Override base class method -- don't ask any questions
if self.get_saved():
return "yes"
else:
return "no"
# Act as output file
def write(self, s, tags=(), mark="insert"):
# Tk assumes that byte strings are Latin-1;
# we assume that they are in the locale's encoding
if isinstance(s, str):
try:
s = unicode(s, IOBinding.encoding)
except UnicodeError:
# some other encoding; let Tcl deal with it
pass
self.text.insert(mark, s, tags)
self.text.see(mark)
self.text.update()
def writelines(self, lines):
for line in lines:
self.write(line)
def flush(self):
pass
# Our own right-button menu
rmenu_specs = [
("Cut", "<<cut>>", "rmenu_check_cut"),
("Copy", "<<copy>>", "rmenu_check_copy"),
("Paste", "<<paste>>", "rmenu_check_paste"),
(None, None, None),
("Go to file/line", "<<goto-file-line>>", None),
]
file_line_pats = [
# order of patterns matters
r'file "([^"]*)", line (\d+)',
r'([^\s]+)\((\d+)\)',
r'^(\s*\S.*?):\s*(\d+):', # Win filename, maybe starting with spaces
r'([^\s]+):\s*(\d+):', # filename or path, ltrim
r'^\s*(\S.*?):\s*(\d+):', # Win abs path with embedded spaces, ltrim
]
file_line_progs = None
def goto_file_line(self, event=None):
if self.file_line_progs is None:
l = []
for pat in self.file_line_pats:
l.append(re.compile(pat, re.IGNORECASE))
self.file_line_progs = l
# x, y = self.event.x, self.event.y
# self.text.mark_set("insert", "@%d,%d" % (x, y))
line = self.text.get("insert linestart", "insert lineend")
result = self._file_line_helper(line)
if not result:
# Try the previous line. This is handy e.g. in tracebacks,
# where you tend to right-click on the displayed source line
line = self.text.get("insert -1line linestart",
"insert -1line lineend")
result = self._file_line_helper(line)
if not result:
tkMessageBox.showerror(
"No special line",
"The line you point at doesn't look like "
"a valid file name followed by a line number.",
parent=self.text)
return
filename, lineno = result
edit = self.flist.open(filename)
edit.gotoline(lineno)
def _file_line_helper(self, line):
for prog in self.file_line_progs:
match = prog.search(line)
if match:
filename, lineno = match.group(1, 2)
try:
f = open(filename, "r")
f.close()
break
except IOError:
continue
else:
return None
try:
return filename, int(lineno)
except TypeError:
return None
# These classes are currently not used but might come in handy
class OnDemandOutputWindow:
tagdefs = {
# XXX Should use IdlePrefs.ColorPrefs
"stdout": {"foreground": "blue"},
"stderr": {"foreground": "#007700"},
}
def __init__(self, flist):
self.flist = flist
self.owin = None
def write(self, s, tags, mark):
if not self.owin:
self.setup()
self.owin.write(s, tags, mark)
def setup(self):
self.owin = owin = OutputWindow(self.flist)
text = owin.text
for tag, cnf in self.tagdefs.items():
if cnf:
text.tag_configure(tag, **cnf)
text.tag_raise('sel')
self.write = self.owin.write

178
Lib/idlelib/ParenMatch.py Normal file
View File

@@ -0,0 +1,178 @@
"""ParenMatch -- An IDLE extension for parenthesis matching.
When you hit a right paren, the cursor should move briefly to the left
paren. Paren here is used generically; the matching applies to
parentheses, square brackets, and curly braces.
"""
from idlelib.HyperParser import HyperParser
from idlelib.configHandler import idleConf
_openers = {')':'(',']':'[','}':'{'}
CHECK_DELAY = 100 # milliseconds
class ParenMatch:
"""Highlight matching parentheses
There are three supported style of paren matching, based loosely
on the Emacs options. The style is select based on the
HILITE_STYLE attribute; it can be changed used the set_style
method.
The supported styles are:
default -- When a right paren is typed, highlight the matching
left paren for 1/2 sec.
expression -- When a right paren is typed, highlight the entire
expression from the left paren to the right paren.
TODO:
- extend IDLE with configuration dialog to change options
- implement rest of Emacs highlight styles (see below)
- print mismatch warning in IDLE status window
Note: In Emacs, there are several styles of highlight where the
matching paren is highlighted whenever the cursor is immediately
to the right of a right paren. I don't know how to do that in Tk,
so I haven't bothered.
"""
menudefs = [
('edit', [
("Show surrounding parens", "<<flash-paren>>"),
])
]
STYLE = idleConf.GetOption('extensions','ParenMatch','style',
default='expression')
FLASH_DELAY = idleConf.GetOption('extensions','ParenMatch','flash-delay',
type='int',default=500)
HILITE_CONFIG = idleConf.GetHighlight(idleConf.CurrentTheme(),'hilite')
BELL = idleConf.GetOption('extensions','ParenMatch','bell',
type='bool',default=1)
RESTORE_VIRTUAL_EVENT_NAME = "<<parenmatch-check-restore>>"
# We want the restore event be called before the usual return and
# backspace events.
RESTORE_SEQUENCES = ("<KeyPress>", "<ButtonPress>",
"<Key-Return>", "<Key-BackSpace>")
def __init__(self, editwin):
self.editwin = editwin
self.text = editwin.text
# Bind the check-restore event to the function restore_event,
# so that we can then use activate_restore (which calls event_add)
# and deactivate_restore (which calls event_delete).
editwin.text.bind(self.RESTORE_VIRTUAL_EVENT_NAME,
self.restore_event)
self.counter = 0
self.is_restore_active = 0
self.set_style(self.STYLE)
def activate_restore(self):
if not self.is_restore_active:
for seq in self.RESTORE_SEQUENCES:
self.text.event_add(self.RESTORE_VIRTUAL_EVENT_NAME, seq)
self.is_restore_active = True
def deactivate_restore(self):
if self.is_restore_active:
for seq in self.RESTORE_SEQUENCES:
self.text.event_delete(self.RESTORE_VIRTUAL_EVENT_NAME, seq)
self.is_restore_active = False
def set_style(self, style):
self.STYLE = style
if style == "default":
self.create_tag = self.create_tag_default
self.set_timeout = self.set_timeout_last
elif style == "expression":
self.create_tag = self.create_tag_expression
self.set_timeout = self.set_timeout_none
def flash_paren_event(self, event):
indices = (HyperParser(self.editwin, "insert")
.get_surrounding_brackets())
if indices is None:
self.warn_mismatched()
return
self.activate_restore()
self.create_tag(indices)
self.set_timeout_last()
def paren_closed_event(self, event):
# If it was a shortcut and not really a closing paren, quit.
closer = self.text.get("insert-1c")
if closer not in _openers:
return
hp = HyperParser(self.editwin, "insert-1c")
if not hp.is_in_code():
return
indices = hp.get_surrounding_brackets(_openers[closer], True)
if indices is None:
self.warn_mismatched()
return
self.activate_restore()
self.create_tag(indices)
self.set_timeout()
def restore_event(self, event=None):
self.text.tag_delete("paren")
self.deactivate_restore()
self.counter += 1 # disable the last timer, if there is one.
def handle_restore_timer(self, timer_count):
if timer_count == self.counter:
self.restore_event()
def warn_mismatched(self):
if self.BELL:
self.text.bell()
# any one of the create_tag_XXX methods can be used depending on
# the style
def create_tag_default(self, indices):
"""Highlight the single paren that matches"""
self.text.tag_add("paren", indices[0])
self.text.tag_config("paren", self.HILITE_CONFIG)
def create_tag_expression(self, indices):
"""Highlight the entire expression"""
if self.text.get(indices[1]) in (')', ']', '}'):
rightindex = indices[1]+"+1c"
else:
rightindex = indices[1]
self.text.tag_add("paren", indices[0], rightindex)
self.text.tag_config("paren", self.HILITE_CONFIG)
# any one of the set_timeout_XXX methods can be used depending on
# the style
def set_timeout_none(self):
"""Highlight will remain until user input turns it off
or the insert has moved"""
# After CHECK_DELAY, call a function which disables the "paren" tag
# if the event is for the most recent timer and the insert has changed,
# or schedules another call for itself.
self.counter += 1
def callme(callme, self=self, c=self.counter,
index=self.text.index("insert")):
if index != self.text.index("insert"):
self.handle_restore_timer(c)
else:
self.editwin.text_frame.after(CHECK_DELAY, callme, callme)
self.editwin.text_frame.after(CHECK_DELAY, callme, callme)
def set_timeout_last(self):
"""The last highlight created will be removed after .5 sec"""
# associate a counter with an event; only disable the "paren"
# tag if the event is for the most recent timer.
self.counter += 1
self.editwin.text_frame.after(
self.FLASH_DELAY,
lambda self=self, c=self.counter: self.handle_restore_timer(c))
if __name__ == '__main__':
import unittest
unittest.main('idlelib.idle_test.test_parenmatch', verbosity=2)

105
Lib/idlelib/PathBrowser.py Normal file
View File

@@ -0,0 +1,105 @@
import os
import sys
import imp
from idlelib.TreeWidget import TreeItem
from idlelib.ClassBrowser import ClassBrowser, ModuleBrowserTreeItem
from idlelib.PyShell import PyShellFileList
class PathBrowser(ClassBrowser):
def __init__(self, flist, _htest=False):
"""
_htest - bool, change box location when running htest
"""
self._htest = _htest
self.init(flist)
def settitle(self):
"Set window titles."
self.top.wm_title("Path Browser")
self.top.wm_iconname("Path Browser")
def rootnode(self):
return PathBrowserTreeItem()
class PathBrowserTreeItem(TreeItem):
def GetText(self):
return "sys.path"
def GetSubList(self):
sublist = []
for dir in sys.path:
item = DirBrowserTreeItem(dir)
sublist.append(item)
return sublist
class DirBrowserTreeItem(TreeItem):
def __init__(self, dir, packages=[]):
self.dir = dir
self.packages = packages
def GetText(self):
if not self.packages:
return self.dir
else:
return self.packages[-1] + ": package"
def GetSubList(self):
try:
names = os.listdir(self.dir or os.curdir)
except os.error:
return []
packages = []
for name in names:
file = os.path.join(self.dir, name)
if self.ispackagedir(file):
nn = os.path.normcase(name)
packages.append((nn, name, file))
packages.sort()
sublist = []
for nn, name, file in packages:
item = DirBrowserTreeItem(file, self.packages + [name])
sublist.append(item)
for nn, name in self.listmodules(names):
item = ModuleBrowserTreeItem(os.path.join(self.dir, name))
sublist.append(item)
return sublist
def ispackagedir(self, file):
if not os.path.isdir(file):
return False
init = os.path.join(file, "__init__.py")
return os.path.exists(init)
def listmodules(self, allnames):
modules = {}
suffixes = imp.get_suffixes()
sorted = []
for suff, mode, flag in suffixes:
i = -len(suff)
for name in allnames[:]:
normed_name = os.path.normcase(name)
if normed_name[i:] == suff:
mod_name = name[:i]
if mod_name not in modules:
modules[mod_name] = None
sorted.append((normed_name, name))
allnames.remove(name)
sorted.sort()
return sorted
def _path_browser(parent): # htest #
flist = PyShellFileList(parent)
PathBrowser(flist, _htest=True)
parent.mainloop()
if __name__ == "__main__":
from unittest import main
main('idlelib.idle_test.test_pathbrowser', verbosity=2, exit=False)
from idlelib.idle_test.htest import run
run(_path_browser)

103
Lib/idlelib/Percolator.py Normal file
View File

@@ -0,0 +1,103 @@
from idlelib.WidgetRedirector import WidgetRedirector
from idlelib.Delegator import Delegator
class Percolator:
def __init__(self, text):
# XXX would be nice to inherit from Delegator
self.text = text
self.redir = WidgetRedirector(text)
self.top = self.bottom = Delegator(text)
self.bottom.insert = self.redir.register("insert", self.insert)
self.bottom.delete = self.redir.register("delete", self.delete)
self.filters = []
def close(self):
while self.top is not self.bottom:
self.removefilter(self.top)
self.top = None
self.bottom.setdelegate(None); self.bottom = None
self.redir.close(); self.redir = None
self.text = None
def insert(self, index, chars, tags=None):
# Could go away if inheriting from Delegator
self.top.insert(index, chars, tags)
def delete(self, index1, index2=None):
# Could go away if inheriting from Delegator
self.top.delete(index1, index2)
def insertfilter(self, filter):
# Perhaps rename to pushfilter()?
assert isinstance(filter, Delegator)
assert filter.delegate is None
filter.setdelegate(self.top)
self.top = filter
def removefilter(self, filter):
# XXX Perhaps should only support popfilter()?
assert isinstance(filter, Delegator)
assert filter.delegate is not None
f = self.top
if f is filter:
self.top = filter.delegate
filter.setdelegate(None)
else:
while f.delegate is not filter:
assert f is not self.bottom
f.resetcache()
f = f.delegate
f.setdelegate(filter.delegate)
filter.setdelegate(None)
def _percolator(parent):
import Tkinter as tk
import re
class Tracer(Delegator):
def __init__(self, name):
self.name = name
Delegator.__init__(self, None)
def insert(self, *args):
print self.name, ": insert", args
self.delegate.insert(*args)
def delete(self, *args):
print self.name, ": delete", args
self.delegate.delete(*args)
root = tk.Tk()
root.title("Test Percolator")
width, height, x, y = list(map(int, re.split('[x+]', parent.geometry())))
root.geometry("+%d+%d"%(x, y + 150))
text = tk.Text(root)
p = Percolator(text)
t1 = Tracer("t1")
t2 = Tracer("t2")
def toggle1():
if var1.get() == 0:
var1.set(1)
p.insertfilter(t1)
elif var1.get() == 1:
var1.set(0)
p.removefilter(t1)
def toggle2():
if var2.get() == 0:
var2.set(1)
p.insertfilter(t2)
elif var2.get() == 1:
var2.set(0)
p.removefilter(t2)
text.pack()
var1 = tk.IntVar()
cb1 = tk.Checkbutton(root, text="Tracer1", command=toggle1, variable=var1)
cb1.pack()
var2 = tk.IntVar()
cb2 = tk.Checkbutton(root, text="Tracer2", command=toggle2, variable=var2)
cb2.pack()
if __name__ == "__main__":
from idlelib.idle_test.htest import run
run(_percolator)

594
Lib/idlelib/PyParse.py Normal file
View File

@@ -0,0 +1,594 @@
import re
import sys
# Reason last stmt is continued (or C_NONE if it's not).
(C_NONE, C_BACKSLASH, C_STRING_FIRST_LINE,
C_STRING_NEXT_LINES, C_BRACKET) = range(5)
if 0: # for throwaway debugging output
def dump(*stuff):
sys.__stdout__.write(" ".join(map(str, stuff)) + "\n")
# Find what looks like the start of a popular stmt.
_synchre = re.compile(r"""
^
[ \t]*
(?: while
| else
| def
| return
| assert
| break
| class
| continue
| elif
| try
| except
| raise
| import
| yield
)
\b
""", re.VERBOSE | re.MULTILINE).search
# Match blank line or non-indenting comment line.
_junkre = re.compile(r"""
[ \t]*
(?: \# \S .* )?
\n
""", re.VERBOSE).match
# Match any flavor of string; the terminating quote is optional
# so that we're robust in the face of incomplete program text.
_match_stringre = re.compile(r"""
\""" [^"\\]* (?:
(?: \\. | "(?!"") )
[^"\\]*
)*
(?: \""" )?
| " [^"\\\n]* (?: \\. [^"\\\n]* )* "?
| ''' [^'\\]* (?:
(?: \\. | '(?!'') )
[^'\\]*
)*
(?: ''' )?
| ' [^'\\\n]* (?: \\. [^'\\\n]* )* '?
""", re.VERBOSE | re.DOTALL).match
# Match a line that starts with something interesting;
# used to find the first item of a bracket structure.
_itemre = re.compile(r"""
[ \t]*
[^\s#\\] # if we match, m.end()-1 is the interesting char
""", re.VERBOSE).match
# Match start of stmts that should be followed by a dedent.
_closere = re.compile(r"""
\s*
(?: return
| break
| continue
| raise
| pass
)
\b
""", re.VERBOSE).match
# Chew up non-special chars as quickly as possible. If match is
# successful, m.end() less 1 is the index of the last boring char
# matched. If match is unsuccessful, the string starts with an
# interesting char.
_chew_ordinaryre = re.compile(r"""
[^[\](){}#'"\\]+
""", re.VERBOSE).match
# Build translation table to map uninteresting chars to "x", open
# brackets to "(", and close brackets to ")".
_tran = ['x'] * 256
for ch in "({[":
_tran[ord(ch)] = '('
for ch in ")}]":
_tran[ord(ch)] = ')'
for ch in "\"'\\\n#":
_tran[ord(ch)] = ch
_tran = ''.join(_tran)
del ch
try:
UnicodeType = type(unicode(""))
except NameError:
UnicodeType = None
class Parser:
def __init__(self, indentwidth, tabwidth):
self.indentwidth = indentwidth
self.tabwidth = tabwidth
def set_str(self, str):
assert len(str) == 0 or str[-1] == '\n'
if type(str) is UnicodeType:
# The parse functions have no idea what to do with Unicode, so
# replace all Unicode characters with "x". This is "safe"
# so long as the only characters germane to parsing the structure
# of Python are 7-bit ASCII. It's *necessary* because Unicode
# strings don't have a .translate() method that supports
# deletechars.
uniphooey = str
str = []
push = str.append
for raw in map(ord, uniphooey):
push(raw < 127 and chr(raw) or "x")
str = "".join(str)
self.str = str
self.study_level = 0
# Return index of a good place to begin parsing, as close to the
# end of the string as possible. This will be the start of some
# popular stmt like "if" or "def". Return None if none found:
# the caller should pass more prior context then, if possible, or
# if not (the entire program text up until the point of interest
# has already been tried) pass 0 to set_lo.
#
# This will be reliable iff given a reliable is_char_in_string
# function, meaning that when it says "no", it's absolutely
# guaranteed that the char is not in a string.
def find_good_parse_start(self, is_char_in_string=None,
_synchre=_synchre):
str, pos = self.str, None
if not is_char_in_string:
# no clue -- make the caller pass everything
return None
# Peek back from the end for a good place to start,
# but don't try too often; pos will be left None, or
# bumped to a legitimate synch point.
limit = len(str)
for tries in range(5):
i = str.rfind(":\n", 0, limit)
if i < 0:
break
i = str.rfind('\n', 0, i) + 1 # start of colon line
m = _synchre(str, i, limit)
if m and not is_char_in_string(m.start()):
pos = m.start()
break
limit = i
if pos is None:
# Nothing looks like a block-opener, or stuff does
# but is_char_in_string keeps returning true; most likely
# we're in or near a giant string, the colorizer hasn't
# caught up enough to be helpful, or there simply *aren't*
# any interesting stmts. In any of these cases we're
# going to have to parse the whole thing to be sure, so
# give it one last try from the start, but stop wasting
# time here regardless of the outcome.
m = _synchre(str)
if m and not is_char_in_string(m.start()):
pos = m.start()
return pos
# Peeking back worked; look forward until _synchre no longer
# matches.
i = pos + 1
while 1:
m = _synchre(str, i)
if m:
s, i = m.span()
if not is_char_in_string(s):
pos = s
else:
break
return pos
# Throw away the start of the string. Intended to be called with
# find_good_parse_start's result.
def set_lo(self, lo):
assert lo == 0 or self.str[lo-1] == '\n'
if lo > 0:
self.str = self.str[lo:]
# As quickly as humanly possible <wink>, find the line numbers (0-
# based) of the non-continuation lines.
# Creates self.{goodlines, continuation}.
def _study1(self):
if self.study_level >= 1:
return
self.study_level = 1
# Map all uninteresting characters to "x", all open brackets
# to "(", all close brackets to ")", then collapse runs of
# uninteresting characters. This can cut the number of chars
# by a factor of 10-40, and so greatly speed the following loop.
str = self.str
str = str.translate(_tran)
str = str.replace('xxxxxxxx', 'x')
str = str.replace('xxxx', 'x')
str = str.replace('xx', 'x')
str = str.replace('xx', 'x')
str = str.replace('\nx', '\n')
# note that replacing x\n with \n would be incorrect, because
# x may be preceded by a backslash
# March over the squashed version of the program, accumulating
# the line numbers of non-continued stmts, and determining
# whether & why the last stmt is a continuation.
continuation = C_NONE
level = lno = 0 # level is nesting level; lno is line number
self.goodlines = goodlines = [0]
push_good = goodlines.append
i, n = 0, len(str)
while i < n:
ch = str[i]
i = i+1
# cases are checked in decreasing order of frequency
if ch == 'x':
continue
if ch == '\n':
lno = lno + 1
if level == 0:
push_good(lno)
# else we're in an unclosed bracket structure
continue
if ch == '(':
level = level + 1
continue
if ch == ')':
if level:
level = level - 1
# else the program is invalid, but we can't complain
continue
if ch == '"' or ch == "'":
# consume the string
quote = ch
if str[i-1:i+2] == quote * 3:
quote = quote * 3
firstlno = lno
w = len(quote) - 1
i = i+w
while i < n:
ch = str[i]
i = i+1
if ch == 'x':
continue
if str[i-1:i+w] == quote:
i = i+w
break
if ch == '\n':
lno = lno + 1
if w == 0:
# unterminated single-quoted string
if level == 0:
push_good(lno)
break
continue
if ch == '\\':
assert i < n
if str[i] == '\n':
lno = lno + 1
i = i+1
continue
# else comment char or paren inside string
else:
# didn't break out of the loop, so we're still
# inside a string
if (lno - 1) == firstlno:
# before the previous \n in str, we were in the first
# line of the string
continuation = C_STRING_FIRST_LINE
else:
continuation = C_STRING_NEXT_LINES
continue # with outer loop
if ch == '#':
# consume the comment
i = str.find('\n', i)
assert i >= 0
continue
assert ch == '\\'
assert i < n
if str[i] == '\n':
lno = lno + 1
if i+1 == n:
continuation = C_BACKSLASH
i = i+1
# The last stmt may be continued for all 3 reasons.
# String continuation takes precedence over bracket
# continuation, which beats backslash continuation.
if (continuation != C_STRING_FIRST_LINE
and continuation != C_STRING_NEXT_LINES and level > 0):
continuation = C_BRACKET
self.continuation = continuation
# Push the final line number as a sentinel value, regardless of
# whether it's continued.
assert (continuation == C_NONE) == (goodlines[-1] == lno)
if goodlines[-1] != lno:
push_good(lno)
def get_continuation_type(self):
self._study1()
return self.continuation
# study1 was sufficient to determine the continuation status,
# but doing more requires looking at every character. study2
# does this for the last interesting statement in the block.
# Creates:
# self.stmt_start, stmt_end
# slice indices of last interesting stmt
# self.stmt_bracketing
# the bracketing structure of the last interesting stmt;
# for example, for the statement "say(boo) or die", stmt_bracketing
# will be [(0, 0), (3, 1), (8, 0)]. Strings and comments are
# treated as brackets, for the matter.
# self.lastch
# last non-whitespace character before optional trailing
# comment
# self.lastopenbracketpos
# if continuation is C_BRACKET, index of last open bracket
def _study2(self):
if self.study_level >= 2:
return
self._study1()
self.study_level = 2
# Set p and q to slice indices of last interesting stmt.
str, goodlines = self.str, self.goodlines
i = len(goodlines) - 1
p = len(str) # index of newest line
while i:
assert p
# p is the index of the stmt at line number goodlines[i].
# Move p back to the stmt at line number goodlines[i-1].
q = p
for nothing in range(goodlines[i-1], goodlines[i]):
# tricky: sets p to 0 if no preceding newline
p = str.rfind('\n', 0, p-1) + 1
# The stmt str[p:q] isn't a continuation, but may be blank
# or a non-indenting comment line.
if _junkre(str, p):
i = i-1
else:
break
if i == 0:
# nothing but junk!
assert p == 0
q = p
self.stmt_start, self.stmt_end = p, q
# Analyze this stmt, to find the last open bracket (if any)
# and last interesting character (if any).
lastch = ""
stack = [] # stack of open bracket indices
push_stack = stack.append
bracketing = [(p, 0)]
while p < q:
# suck up all except ()[]{}'"#\\
m = _chew_ordinaryre(str, p, q)
if m:
# we skipped at least one boring char
newp = m.end()
# back up over totally boring whitespace
i = newp - 1 # index of last boring char
while i >= p and str[i] in " \t\n":
i = i-1
if i >= p:
lastch = str[i]
p = newp
if p >= q:
break
ch = str[p]
if ch in "([{":
push_stack(p)
bracketing.append((p, len(stack)))
lastch = ch
p = p+1
continue
if ch in ")]}":
if stack:
del stack[-1]
lastch = ch
p = p+1
bracketing.append((p, len(stack)))
continue
if ch == '"' or ch == "'":
# consume string
# Note that study1 did this with a Python loop, but
# we use a regexp here; the reason is speed in both
# cases; the string may be huge, but study1 pre-squashed
# strings to a couple of characters per line. study1
# also needed to keep track of newlines, and we don't
# have to.
bracketing.append((p, len(stack)+1))
lastch = ch
p = _match_stringre(str, p, q).end()
bracketing.append((p, len(stack)))
continue
if ch == '#':
# consume comment and trailing newline
bracketing.append((p, len(stack)+1))
p = str.find('\n', p, q) + 1
assert p > 0
bracketing.append((p, len(stack)))
continue
assert ch == '\\'
p = p+1 # beyond backslash
assert p < q
if str[p] != '\n':
# the program is invalid, but can't complain
lastch = ch + str[p]
p = p+1 # beyond escaped char
# end while p < q:
self.lastch = lastch
if stack:
self.lastopenbracketpos = stack[-1]
self.stmt_bracketing = tuple(bracketing)
# Assuming continuation is C_BRACKET, return the number
# of spaces the next line should be indented.
def compute_bracket_indent(self):
self._study2()
assert self.continuation == C_BRACKET
j = self.lastopenbracketpos
str = self.str
n = len(str)
origi = i = str.rfind('\n', 0, j) + 1
j = j+1 # one beyond open bracket
# find first list item; set i to start of its line
while j < n:
m = _itemre(str, j)
if m:
j = m.end() - 1 # index of first interesting char
extra = 0
break
else:
# this line is junk; advance to next line
i = j = str.find('\n', j) + 1
else:
# nothing interesting follows the bracket;
# reproduce the bracket line's indentation + a level
j = i = origi
while str[j] in " \t":
j = j+1
extra = self.indentwidth
return len(str[i:j].expandtabs(self.tabwidth)) + extra
# Return number of physical lines in last stmt (whether or not
# it's an interesting stmt! this is intended to be called when
# continuation is C_BACKSLASH).
def get_num_lines_in_stmt(self):
self._study1()
goodlines = self.goodlines
return goodlines[-1] - goodlines[-2]
# Assuming continuation is C_BACKSLASH, return the number of spaces
# the next line should be indented. Also assuming the new line is
# the first one following the initial line of the stmt.
def compute_backslash_indent(self):
self._study2()
assert self.continuation == C_BACKSLASH
str = self.str
i = self.stmt_start
while str[i] in " \t":
i = i+1
startpos = i
# See whether the initial line starts an assignment stmt; i.e.,
# look for an = operator
endpos = str.find('\n', startpos) + 1
found = level = 0
while i < endpos:
ch = str[i]
if ch in "([{":
level = level + 1
i = i+1
elif ch in ")]}":
if level:
level = level - 1
i = i+1
elif ch == '"' or ch == "'":
i = _match_stringre(str, i, endpos).end()
elif ch == '#':
break
elif level == 0 and ch == '=' and \
(i == 0 or str[i-1] not in "=<>!") and \
str[i+1] != '=':
found = 1
break
else:
i = i+1
if found:
# found a legit =, but it may be the last interesting
# thing on the line
i = i+1 # move beyond the =
found = re.match(r"\s*\\", str[i:endpos]) is None
if not found:
# oh well ... settle for moving beyond the first chunk
# of non-whitespace chars
i = startpos
while str[i] not in " \t\n":
i = i+1
return len(str[self.stmt_start:i].expandtabs(\
self.tabwidth)) + 1
# Return the leading whitespace on the initial line of the last
# interesting stmt.
def get_base_indent_string(self):
self._study2()
i, n = self.stmt_start, self.stmt_end
j = i
str = self.str
while j < n and str[j] in " \t":
j = j + 1
return str[i:j]
# Did the last interesting stmt open a block?
def is_block_opener(self):
self._study2()
return self.lastch == ':'
# Did the last interesting stmt close a block?
def is_block_closer(self):
self._study2()
return _closere(self.str, self.stmt_start) is not None
# index of last open bracket ({[, or None if none
lastopenbracketpos = None
def get_last_open_bracket_pos(self):
self._study2()
return self.lastopenbracketpos
# the structure of the bracketing of the last interesting statement,
# in the format defined in _study2, or None if the text didn't contain
# anything
stmt_bracketing = None
def get_last_stmt_bracketing(self):
self._study2()
return self.stmt_bracketing

1643
Lib/idlelib/PyShell.py Executable file

File diff suppressed because it is too large Load Diff

230
Lib/idlelib/README.txt Normal file
View File

@@ -0,0 +1,230 @@
README.txt: an index to idlelib files and the IDLE menu.
IDLE is Python's Integrated Development and Learning
Environment. The user documentation is part of the Library Reference and
is available in IDLE by selecting Help => IDLE Help. This README documents
idlelib for IDLE developers and curious users.
IDLELIB FILES lists files alphabetically by category,
with a short description of each.
IDLE MENU show the menu tree, annotated with the module
or module object that implements the corresponding function.
This file is descriptive, not prescriptive, and may have errors
and omissions and lag behind changes in idlelib.
IDLELIB FILES
Implemetation files not in IDLE MENU are marked (nim).
Deprecated files and objects are listed separately as the end.
Startup
-------
__init__.py # import, does nothing
__main__.py # -m, starts IDLE
idle.bat
idle.py
idle.pyw
Implementation
--------------
AutoComplete.py # Complete attribute names or filenames.
AutoCompleteWindow.py # Display completions.
AutoExpand.py # Expand word with previous word in file.
Bindings.py # Define most of IDLE menu.
CallTipWindow.py # Display calltip.
CallTips.py # Create calltip text.
ClassBrowser.py # Create module browser window.
CodeContext.py # Show compound statement headers otherwise not visible.
ColorDelegator.py # Colorize text (nim).
Debugger.py # Debug code run from editor; show window.
Delegator.py # Define base class for delegators (nim).
EditorWindow.py # Define most of editor and utility functions.
FileList.py # Open files and manage list of open windows (nim).
FormatParagraph.py# Re-wrap multiline strings and comments.
GrepDialog.py # Find all occurrences of pattern in multiple files.
HyperParser.py # Parse code around a given index.
IOBinding.py # Open, read, and write files
IdleHistory.py # Get previous or next user input in shell (nim)
MultiCall.py # Wrap tk widget to allow multiple calls per event (nim).
MultiStatusBar.py # Define status bar for windows (nim).
ObjectBrowser.py # Define class used in StackViewer (nim).
OutputWindow.py # Create window for grep output.
ParenMatch.py # Match fenceposts: (), [], and {}.
PathBrowser.py # Create path browser window.
Percolator.py # Manage delegator stack (nim).
PyParse.py # Give information on code indentation
PyShell.py # Start IDLE, manage shell, complete editor window
RemoteDebugger.py # Debug code run in remote process.
RemoteObjectBrowser.py # Communicate objects between processes with rpc (nim).
ReplaceDialog.py # Search and replace pattern in text.
RstripExtension.py# Strip trailing whitespace
ScriptBinding.py # Check and run user code.
ScrolledList.py # Define ScrolledList widget for IDLE (nim).
SearchDialog.py # Search for pattern in text.
SearchDialogBase.py # Define base for search, replace, and grep dialogs.
SearchEngine.py # Define engine for all 3 search dialogs.
StackViewer.py # View stack after exception.
TreeWidget.py # Define tree widger, used in browsers (nim).
UndoDelegator.py # Manage undo stack.
WidgetRedirector.py # Intercept widget subcommands (for percolator) (nim).
WindowList.py # Manage window list and define listed top level.
ZoomHeight.py # Zoom window to full height of screen.
aboutDialog.py # Display About IDLE dialog.
configDialog.py # Display user configuration dialogs.
configHandler.py # Load, fetch, and save configuration (nim).
configHelpSourceEdit.py # Specify help source.
configSectionNameDialog.py # Spefify user config section name
dynOptionMenuWidget.py # define mutable OptionMenu widget (nim).
help.py # Display IDLE's html doc.
keybindingDialog.py # Change keybindings.
macosxSupport.py # Help IDLE run on Macs (nim).
rpc.py # Commuicate between idle and user processes (nim).
run.py # Manage user code execution subprocess.
tabbedpages.py # Define tabbed pages widget (nim).
textView.py # Define read-only text widget (nim).
Configuration
-------------
config-extensions.def # Defaults for extensions
config-highlight.def # Defaults for colorizing
config-keys.def # Defaults for key bindings
config-main.def # Defai;ts fpr font and geneal
Text
----
CREDITS.txt # not maintained, displayed by About IDLE
HISTORY.txt # NEWS up to July 2001
NEWS.txt # commits, displayed by About IDLE
README.txt # this file, displeyed by About IDLE
TODO.txt # needs review
extend.txt # about writing extensions
help.html # copy of idle.html in docs, displayed by IDLE Help
Subdirectories
--------------
Icons # small image files
idle_test # files for human test and automated unit tests
Unused and Deprecated files and objects (nim)
---------------------------------------------
EditorWindow.py: Helpdialog and helpDialog
ToolTip.py: unused.
help.txt
idlever.py
IDLE MENUS
Top level items and most submenu items are defined in Bindings.
Extenstions add submenu items when active. The names given are
found, quoted, in one of these modules, paired with a '<<pseudoevent>>'.
Each pseudoevent is bound to an event handler. Some event handlers
call another function that does the actual work. The annotations below
are intended to at least give the module where the actual work is done.
File # IOBindig except as noted
New File
Open... # IOBinding.open
Open Module
Recent Files
Class Browser # Class Browser
Path Browser # Path Browser
---
Save # IDBinding.save
Save As... # IOBinding.save_as
Save Copy As... # IOBindling.save_a_copy
---
Print Window # IOBinding.print_window
---
Close
Exit
Edit
Undo # undoDelegator
Redo # undoDelegator
---
Cut
Copy
Paste
Select All
--- # Next 5 items use SearchEngine; dialogs use SearchDialogBase
Find # Search Dialog
Find Again
Find Selection
Find in Files... # GrepDialog
Replace... # ReplaceDialog
Go to Line
Show Completions # AutoComplete extension and AutoCompleteWidow (&HP)
Expand Word # AutoExpand extension
Show call tip # Calltips extension and CalltipWindow (& Hyperparser)
Show surrounding parens # ParenMatch (& Hyperparser)
Shell # PyShell
View Last Restart # PyShell.PyShell.view_restart_mark
Restart Shell # PyShell.PyShell.restart_shell
Interrupt Execution # pyshell.PyShell.cancel_callback
Debug (Shell only)
Go to File/Line
Debugger # Debugger, RemoteDebugger, PyShell.toggle_debuger
Stack Viewer # StackViewer, PyShell.open_stack_viewer
Auto-open Stack Viewer # StackViewer
Format (Editor only)
Indent Region
Dedent Region
Comment Out Region
Uncomment Region
Tabify Region
Untabify Region
Toggle Tabs
New Indent Width
Format Paragraph # FormatParagraph extension
---
Strip tailing whitespace # RstripExtension extension
Run (Editor only)
Python Shell # PyShell
---
Check Module # ScriptBinding
Run Module # ScriptBinding
Options
Configure IDLE # configDialog
(tabs in the dialog)
Font tab # onfig-main.def
Highlight tab # configSectionNameDialog, config-highlight.def
Keys tab # keybindingDialog, configSectionNameDialog, onfig-keus.def
General tab # configHelpSourceEdit, config-main.def
Configure Extensions # configDialog
Xyz tab # xyz.py, config-extensions.def
---
Code Context (editor only) # CodeContext extension
Window
Zoomheight # ZoomHeight extension
---
<open windows> # WindowList
Help
About IDLE # aboutDialog
---
IDLE Help # help
Python Doc
Turtle Demo
---
<other help sources>
<Context Menu> (right click)
Defined in EditorWindow, PyShell, Output
Cut
Copy
Paste
---
Go to file/line (shell and output only)
Set Breakpoint (editor only)
Clear Breakpoint (editor only)
Defined in Debugger
Go to source line
Show stack frame

View File

@@ -0,0 +1,379 @@
"""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 types
from idlelib 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(%r, %r)" % (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(frame2), k) for frame2, 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 did in self._dictcache:
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 %r" % (methodname, value)
return value
def run(self, cmd, locals):
# Ignores locals on purpose!
seq = self.conn.asyncqueue(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'

View File

@@ -0,0 +1,36 @@
from idlelib 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]

View File

@@ -0,0 +1,220 @@
from Tkinter import *
from idlelib import SearchEngine
from idlelib.SearchDialogBase import SearchDialogBase
import re
def replace(text):
root = text._root()
engine = SearchEngine.get(root)
if not hasattr(engine, "_replacedialog"):
engine._replacedialog = ReplaceDialog(root, engine)
dialog = engine._replacedialog
dialog.open(text)
class ReplaceDialog(SearchDialogBase):
title = "Replace Dialog"
icon = "Replace"
def __init__(self, root, engine):
SearchDialogBase.__init__(self, root, engine)
self.replvar = StringVar(root)
def open(self, text):
SearchDialogBase.open(self, text)
try:
first = text.index("sel.first")
except TclError:
first = None
try:
last = text.index("sel.last")
except TclError:
last = None
first = first or text.index("insert")
last = last or first
self.show_hit(first, last)
self.ok = 1
def create_entries(self):
SearchDialogBase.create_entries(self)
self.replent = self.make_entry("Replace with:", self.replvar)[0]
def create_command_buttons(self):
SearchDialogBase.create_command_buttons(self)
self.make_button("Find", self.find_it)
self.make_button("Replace", self.replace_it)
self.make_button("Replace+Find", self.default_command, 1)
self.make_button("Replace All", self.replace_all)
def find_it(self, event=None):
self.do_find(0)
def replace_it(self, event=None):
if self.do_find(self.ok):
self.do_replace()
def default_command(self, event=None):
if self.do_find(self.ok):
if self.do_replace(): # Only find next match if replace succeeded.
# A bad re can cause it to fail.
self.do_find(0)
def _replace_expand(self, m, repl):
""" Helper function for expanding a regular expression
in the replace field, if needed. """
if self.engine.isre():
try:
new = m.expand(repl)
except re.error:
self.engine.report_error(repl, 'Invalid Replace Expression')
new = None
else:
new = repl
return new
def replace_all(self, event=None):
prog = self.engine.getprog()
if not prog:
return
repl = self.replvar.get()
text = self.text
res = self.engine.search_text(text, prog)
if not res:
text.bell()
return
text.tag_remove("sel", "1.0", "end")
text.tag_remove("hit", "1.0", "end")
line = res[0]
col = res[1].start()
if self.engine.iswrap():
line = 1
col = 0
ok = 1
first = last = None
# XXX ought to replace circular instead of top-to-bottom when wrapping
text.undo_block_start()
while 1:
res = self.engine.search_forward(text, prog, line, col, 0, ok)
if not res:
break
line, m = res
chars = text.get("%d.0" % line, "%d.0" % (line+1))
orig = m.group()
new = self._replace_expand(m, repl)
if new is None:
break
i, j = m.span()
first = "%d.%d" % (line, i)
last = "%d.%d" % (line, j)
if new == orig:
text.mark_set("insert", last)
else:
text.mark_set("insert", first)
if first != last:
text.delete(first, last)
if new:
text.insert(first, new)
col = i + len(new)
ok = 0
text.undo_block_stop()
if first and last:
self.show_hit(first, last)
self.close()
def do_find(self, ok=0):
if not self.engine.getprog():
return False
text = self.text
res = self.engine.search_text(text, None, ok)
if not res:
text.bell()
return False
line, m = res
i, j = m.span()
first = "%d.%d" % (line, i)
last = "%d.%d" % (line, j)
self.show_hit(first, last)
self.ok = 1
return True
def do_replace(self):
prog = self.engine.getprog()
if not prog:
return False
text = self.text
try:
first = pos = text.index("sel.first")
last = text.index("sel.last")
except TclError:
pos = None
if not pos:
first = last = pos = text.index("insert")
line, col = SearchEngine.get_line_col(pos)
chars = text.get("%d.0" % line, "%d.0" % (line+1))
m = prog.match(chars, col)
if not prog:
return False
new = self._replace_expand(m, self.replvar.get())
if new is None:
return False
text.mark_set("insert", first)
text.undo_block_start()
if m.group():
text.delete(first, last)
if new:
text.insert(first, new)
text.undo_block_stop()
self.show_hit(first, text.index("insert"))
self.ok = 0
return True
def show_hit(self, first, last):
text = self.text
text.mark_set("insert", first)
text.tag_remove("sel", "1.0", "end")
text.tag_add("sel", first, last)
text.tag_remove("hit", "1.0", "end")
if first == last:
text.tag_add("hit", first)
else:
text.tag_add("hit", first, last)
text.see("insert")
text.update_idletasks()
def close(self, event=None):
SearchDialogBase.close(self, event)
self.text.tag_remove("hit", "1.0", "end")
def _replace_dialog(parent):
root = Tk()
root.title("Test ReplaceDialog")
width, height, x, y = list(map(int, re.split('[x+]', parent.geometry())))
root.geometry("+%d+%d"%(x, y + 150))
# mock undo delegator methods
def undo_block_start():
pass
def undo_block_stop():
pass
text = Text(root)
text.undo_block_start = undo_block_start
text.undo_block_stop = undo_block_stop
text.pack()
text.insert("insert","This is a sample string.\n"*10)
def show_replace():
text.tag_add(SEL, "1.0", END)
replace(text)
text.tag_remove(SEL, "1.0", END)
button = Button(root, text="Replace", command=show_replace)
button.pack()
if __name__ == '__main__':
from idlelib.idle_test.htest import run
run(_replace_dialog)

View File

@@ -0,0 +1,33 @@
'Provides "Strip trailing whitespace" under the "Format" menu.'
class RstripExtension:
menudefs = [
('format', [None, ('Strip trailing whitespace', '<<do-rstrip>>'), ] ), ]
def __init__(self, editwin):
self.editwin = editwin
self.editwin.text.bind("<<do-rstrip>>", self.do_rstrip)
def do_rstrip(self, event=None):
text = self.editwin.text
undo = self.editwin.undo
undo.undo_block_start()
end_line = int(float(text.index('end')))
for cur in range(1, end_line):
txt = text.get('%i.0' % cur, '%i.end' % cur)
raw = len(txt)
cut = len(txt.rstrip())
# Since text.delete() marks file as changed, even if not,
# only call it when needed to actually delete something.
if cut < raw:
text.delete('%i.%i' % (cur, cut), '%i.end' % cur)
undo.undo_block_stop()
if __name__ == "__main__":
import unittest
unittest.main('idlelib.idle_test.test_rstrip', verbosity=2, exit=False)

View File

@@ -0,0 +1,222 @@
"""Extension to execute code outside the Python shell window.
This adds the following commands:
- Check module does a full syntax check of the current module.
It also runs the tabnanny to catch any inconsistent tabs.
- Run module executes the module's code in the __main__ namespace. The window
must have been saved previously. The module is added to sys.modules, and is
also added to the __main__ namespace.
XXX GvR Redesign this interface (yet again) as follows:
- Present a dialog box for ``Run Module''
- Allow specify command line arguments in the dialog box
"""
import os
import re
import string
import tabnanny
import tokenize
import tkMessageBox
from idlelib import PyShell
from idlelib.configHandler import idleConf
from idlelib import macosxSupport
IDENTCHARS = string.ascii_letters + string.digits + "_"
indent_message = """Error: Inconsistent indentation detected!
1) Your indentation is outright incorrect (easy to fix), OR
2) Your indentation mixes tabs and spaces.
To fix case 2, change all tabs to spaces by using Edit->Select All followed \
by Format->Untabify Region and specify the number of columns used by each tab.
"""
class ScriptBinding:
menudefs = [
('run', [None,
('Check Module', '<<check-module>>'),
('Run Module', '<<run-module>>'), ]), ]
def __init__(self, editwin):
self.editwin = editwin
# Provide instance variables referenced by Debugger
# XXX This should be done differently
self.flist = self.editwin.flist
self.root = self.editwin.root
if macosxSupport.isCocoaTk():
self.editwin.text_frame.bind('<<run-module-event-2>>', self._run_module_event)
def check_module_event(self, event):
filename = self.getfilename()
if not filename:
return 'break'
if not self.checksyntax(filename):
return 'break'
if not self.tabnanny(filename):
return 'break'
def tabnanny(self, filename):
f = open(filename, 'r')
try:
tabnanny.process_tokens(tokenize.generate_tokens(f.readline))
except tokenize.TokenError as msg:
msgtxt, (lineno, start) = msg.args
self.editwin.gotoline(lineno)
self.errorbox("Tabnanny Tokenizing Error",
"Token Error: %s" % msgtxt)
return False
except tabnanny.NannyNag as nag:
# The error messages from tabnanny are too confusing...
self.editwin.gotoline(nag.get_lineno())
self.errorbox("Tab/space error", indent_message)
return False
return True
def checksyntax(self, filename):
self.shell = shell = self.flist.open_shell()
saved_stream = shell.get_warning_stream()
shell.set_warning_stream(shell.stderr)
with open(filename, 'r') as f:
source = f.read()
if '\r' in source:
source = re.sub(r"\r\n", "\n", source)
source = re.sub(r"\r", "\n", source)
if source and source[-1] != '\n':
source = source + '\n'
text = self.editwin.text
text.tag_remove("ERROR", "1.0", "end")
try:
try:
# If successful, return the compiled code
return compile(source, filename, "exec")
except (SyntaxError, OverflowError, ValueError) as err:
try:
msg, (errorfilename, lineno, offset, line) = err
if not errorfilename:
err.args = msg, (filename, lineno, offset, line)
err.filename = filename
self.colorize_syntax_error(msg, lineno, offset)
except:
msg = "*** " + str(err)
self.errorbox("Syntax error",
"There's an error in your program:\n" + msg)
return False
finally:
shell.set_warning_stream(saved_stream)
def colorize_syntax_error(self, msg, lineno, offset):
text = self.editwin.text
pos = "0.0 + %d lines + %d chars" % (lineno-1, offset-1)
text.tag_add("ERROR", pos)
char = text.get(pos)
if char and char in IDENTCHARS:
text.tag_add("ERROR", pos + " wordstart", pos)
if '\n' == text.get(pos): # error at line end
text.mark_set("insert", pos)
else:
text.mark_set("insert", pos + "+1c")
text.see(pos)
def run_module_event(self, event):
"""Run the module after setting up the environment.
First check the syntax. If OK, make sure the shell is active and
then transfer the arguments, set the run environment's working
directory to the directory of the module being executed and also
add that directory to its sys.path if not already included.
"""
filename = self.getfilename()
if not filename:
return 'break'
code = self.checksyntax(filename)
if not code:
return 'break'
if not self.tabnanny(filename):
return 'break'
interp = self.shell.interp
if PyShell.use_subprocess:
interp.restart_subprocess(with_cwd=False, filename=code.co_filename)
dirname = os.path.dirname(filename)
# XXX Too often this discards arguments the user just set...
interp.runcommand("""if 1:
__file__ = {filename!r}
import sys as _sys
from os.path import basename as _basename
if (not _sys.argv or
_basename(_sys.argv[0]) != _basename(__file__)):
_sys.argv = [__file__]
import os as _os
_os.chdir({dirname!r})
del _sys, _basename, _os
\n""".format(filename=filename, dirname=dirname))
interp.prepend_syspath(filename)
# XXX KBK 03Jul04 When run w/o subprocess, runtime warnings still
# go to __stderr__. With subprocess, they go to the shell.
# Need to change streams in PyShell.ModifiedInterpreter.
interp.runcode(code)
return 'break'
if macosxSupport.isCocoaTk():
# Tk-Cocoa in MacOSX is broken until at least
# Tk 8.5.9, and without this rather
# crude workaround IDLE would hang when a user
# tries to run a module using the keyboard shortcut
# (the menu item works fine).
_run_module_event = run_module_event
def run_module_event(self, event):
self.editwin.text_frame.after(200,
lambda: self.editwin.text_frame.event_generate('<<run-module-event-2>>'))
return 'break'
def getfilename(self):
"""Get source filename. If not saved, offer to save (or create) file
The debugger requires a source file. Make sure there is one, and that
the current version of the source buffer has been saved. If the user
declines to save or cancels the Save As dialog, return None.
If the user has configured IDLE for Autosave, the file will be
silently saved if it already exists and is dirty.
"""
filename = self.editwin.io.filename
if not self.editwin.get_saved():
autosave = idleConf.GetOption('main', 'General',
'autosave', type='bool')
if autosave and filename:
self.editwin.io.save(None)
else:
confirm = self.ask_save_dialog()
self.editwin.text.focus_set()
if confirm:
self.editwin.io.save(None)
filename = self.editwin.io.filename
else:
filename = None
return filename
def ask_save_dialog(self):
msg = "Source Must Be Saved\n" + 5*' ' + "OK to Save?"
confirm = tkMessageBox.askokcancel(title="Save Before Run or Check",
message=msg,
default=tkMessageBox.OK,
parent=self.editwin.text)
return confirm
def errorbox(self, title, message):
# XXX This should really be a function of EditorWindow...
tkMessageBox.showerror(title, message, parent=self.editwin.text)
self.editwin.text.focus_set()

145
Lib/idlelib/ScrolledList.py Normal file
View File

@@ -0,0 +1,145 @@
from Tkinter import *
from idlelib import macosxSupport
class ScrolledList:
default = "(None)"
def __init__(self, master, **options):
# Create top frame, with scrollbar and listbox
self.master = master
self.frame = frame = Frame(master)
self.frame.pack(fill="both", expand=1)
self.vbar = vbar = Scrollbar(frame, name="vbar")
self.vbar.pack(side="right", fill="y")
self.listbox = listbox = Listbox(frame, exportselection=0,
background="white")
if options:
listbox.configure(options)
listbox.pack(expand=1, fill="both")
# Tie listbox and scrollbar together
vbar["command"] = listbox.yview
listbox["yscrollcommand"] = vbar.set
# Bind events to the list box
listbox.bind("<ButtonRelease-1>", self.click_event)
listbox.bind("<Double-ButtonRelease-1>", self.double_click_event)
if macosxSupport.isAquaTk():
listbox.bind("<ButtonPress-2>", self.popup_event)
listbox.bind("<Control-Button-1>", self.popup_event)
else:
listbox.bind("<ButtonPress-3>", self.popup_event)
listbox.bind("<Key-Up>", self.up_event)
listbox.bind("<Key-Down>", self.down_event)
# Mark as empty
self.clear()
def close(self):
self.frame.destroy()
def clear(self):
self.listbox.delete(0, "end")
self.empty = 1
self.listbox.insert("end", self.default)
def append(self, item):
if self.empty:
self.listbox.delete(0, "end")
self.empty = 0
self.listbox.insert("end", str(item))
def get(self, index):
return self.listbox.get(index)
def click_event(self, event):
self.listbox.activate("@%d,%d" % (event.x, event.y))
index = self.listbox.index("active")
self.select(index)
self.on_select(index)
return "break"
def double_click_event(self, event):
index = self.listbox.index("active")
self.select(index)
self.on_double(index)
return "break"
menu = None
def popup_event(self, event):
if not self.menu:
self.make_menu()
menu = self.menu
self.listbox.activate("@%d,%d" % (event.x, event.y))
index = self.listbox.index("active")
self.select(index)
menu.tk_popup(event.x_root, event.y_root)
def make_menu(self):
menu = Menu(self.listbox, tearoff=0)
self.menu = menu
self.fill_menu()
def up_event(self, event):
index = self.listbox.index("active")
if self.listbox.selection_includes(index):
index = index - 1
else:
index = self.listbox.size() - 1
if index < 0:
self.listbox.bell()
else:
self.select(index)
self.on_select(index)
return "break"
def down_event(self, event):
index = self.listbox.index("active")
if self.listbox.selection_includes(index):
index = index + 1
else:
index = 0
if index >= self.listbox.size():
self.listbox.bell()
else:
self.select(index)
self.on_select(index)
return "break"
def select(self, index):
self.listbox.focus_set()
self.listbox.activate(index)
self.listbox.selection_clear(0, "end")
self.listbox.selection_set(index)
self.listbox.see(index)
# Methods to override for specific actions
def fill_menu(self):
pass
def on_select(self, index):
pass
def on_double(self, index):
pass
def _scrolled_list(parent):
root = Tk()
root.title("Test ScrolledList")
width, height, x, y = list(map(int, re.split('[x+]', parent.geometry())))
root.geometry("+%d+%d"%(x, y + 150))
class MyScrolledList(ScrolledList):
def fill_menu(self): self.menu.add_command(label="right click")
def on_select(self, index): print "select", self.get(index)
def on_double(self, index): print "double", self.get(index)
scrolled_list = MyScrolledList(root)
for i in range(30):
scrolled_list.append("Item %02d" % i)
root.mainloop()
if __name__ == '__main__':
from idlelib.idle_test.htest import run
run(_scrolled_list)

View File

@@ -0,0 +1,89 @@
from Tkinter import *
from idlelib import SearchEngine
from idlelib.SearchDialogBase import SearchDialogBase
def _setup(text):
root = text._root()
engine = SearchEngine.get(root)
if not hasattr(engine, "_searchdialog"):
engine._searchdialog = SearchDialog(root, engine)
return engine._searchdialog
def find(text):
pat = text.get("sel.first", "sel.last")
return _setup(text).open(text,pat)
def find_again(text):
return _setup(text).find_again(text)
def find_selection(text):
return _setup(text).find_selection(text)
class SearchDialog(SearchDialogBase):
def create_widgets(self):
SearchDialogBase.create_widgets(self)
self.make_button("Find Next", self.default_command, 1)
def default_command(self, event=None):
if not self.engine.getprog():
return
self.find_again(self.text)
def find_again(self, text):
if not self.engine.getpat():
self.open(text)
return False
if not self.engine.getprog():
return False
res = self.engine.search_text(text)
if res:
line, m = res
i, j = m.span()
first = "%d.%d" % (line, i)
last = "%d.%d" % (line, j)
try:
selfirst = text.index("sel.first")
sellast = text.index("sel.last")
if selfirst == first and sellast == last:
text.bell()
return False
except TclError:
pass
text.tag_remove("sel", "1.0", "end")
text.tag_add("sel", first, last)
text.mark_set("insert", self.engine.isback() and first or last)
text.see("insert")
return True
else:
text.bell()
return False
def find_selection(self, text):
pat = text.get("sel.first", "sel.last")
if pat:
self.engine.setcookedpat(pat)
return self.find_again(text)
def _search_dialog(parent):
root = Tk()
root.title("Test SearchDialog")
width, height, x, y = list(map(int, re.split('[x+]', parent.geometry())))
root.geometry("+%d+%d"%(x, y + 150))
text = Text(root)
text.pack()
text.insert("insert","This is a sample string.\n"*10)
def show_find():
text.tag_add(SEL, "1.0", END)
s = _setup(text)
s.open(text)
text.tag_remove(SEL, "1.0", END)
button = Button(root, text="Search", command=show_find)
button.pack()
if __name__ == '__main__':
from idlelib.idle_test.htest import run
run(_search_dialog)

View File

@@ -0,0 +1,186 @@
'''Define SearchDialogBase used by Search, Replace, and Grep dialogs.'''
from Tkinter import (Toplevel, Frame, Entry, Label, Button,
Checkbutton, Radiobutton)
class SearchDialogBase:
'''Create most of a 3 or 4 row, 3 column search dialog.
The left and wide middle column contain:
1 or 2 labeled text entry lines (make_entry, create_entries);
a row of standard Checkbuttons (make_frame, create_option_buttons),
each of which corresponds to a search engine Variable;
a row of dialog-specific Check/Radiobuttons (create_other_buttons).
The narrow right column contains command buttons
(make_button, create_command_buttons).
These are bound to functions that execute the command.
Except for command buttons, this base class is not limited to items
common to all three subclasses. Rather, it is the Find dialog minus
the "Find Next" command, its execution function, and the
default_command attribute needed in create_widgets. The other
dialogs override attributes and methods, the latter to replace and
add widgets.
'''
title = "Search Dialog" # replace in subclasses
icon = "Search"
needwrapbutton = 1 # not in Find in Files
def __init__(self, root, engine):
'''Initialize root, engine, and top attributes.
top (level widget): set in create_widgets() called from open().
text (Text searched): set in open(), only used in subclasses().
ent (ry): created in make_entry() called from create_entry().
row (of grid): 0 in create_widgets(), +1 in make_entry/frame().
default_command: set in subclasses, used in create_widgers().
title (of dialog): class attribute, override in subclasses.
icon (of dialog): ditto, use unclear if cannot minimize dialog.
'''
self.root = root
self.engine = engine
self.top = None
def open(self, text, searchphrase=None):
"Make dialog visible on top of others and ready to use."
self.text = text
if not self.top:
self.create_widgets()
else:
self.top.deiconify()
self.top.tkraise()
self.top.transient(text.winfo_toplevel())
if searchphrase:
self.ent.delete(0,"end")
self.ent.insert("end",searchphrase)
self.ent.focus_set()
self.ent.selection_range(0, "end")
self.ent.icursor(0)
self.top.grab_set()
def close(self, event=None):
"Put dialog away for later use."
if self.top:
self.top.grab_release()
self.top.transient('')
self.top.withdraw()
def create_widgets(self):
'''Create basic 3 row x 3 col search (find) dialog.
Other dialogs override subsidiary create_x methods as needed.
Replace and Find-in-Files add another entry row.
'''
top = Toplevel(self.root)
top.bind("<Return>", self.default_command)
top.bind("<Escape>", self.close)
top.protocol("WM_DELETE_WINDOW", self.close)
top.wm_title(self.title)
top.wm_iconname(self.icon)
self.top = top
self.row = 0
self.top.grid_columnconfigure(0, pad=2, weight=0)
self.top.grid_columnconfigure(1, pad=2, minsize=100, weight=100)
self.create_entries() # row 0 (and maybe 1), cols 0, 1
self.create_option_buttons() # next row, cols 0, 1
self.create_other_buttons() # next row, cols 0, 1
self.create_command_buttons() # col 2, all rows
def make_entry(self, label_text, var):
'''Return (entry, label), .
entry - gridded labeled Entry for text entry.
label - Label widget, returned for testing.
'''
label = Label(self.top, text=label_text)
label.grid(row=self.row, column=0, sticky="nw")
entry = Entry(self.top, textvariable=var, exportselection=0)
entry.grid(row=self.row, column=1, sticky="nwe")
self.row = self.row + 1
return entry, label
def create_entries(self):
"Create one or more entry lines with make_entry."
self.ent = self.make_entry("Find:", self.engine.patvar)[0]
def make_frame(self,labeltext=None):
'''Return (frame, label).
frame - gridded labeled Frame for option or other buttons.
label - Label widget, returned for testing.
'''
if labeltext:
label = Label(self.top, text=labeltext)
label.grid(row=self.row, column=0, sticky="nw")
else:
label = ''
frame = Frame(self.top)
frame.grid(row=self.row, column=1, columnspan=1, sticky="nwe")
self.row = self.row + 1
return frame, label
def create_option_buttons(self):
'''Return (filled frame, options) for testing.
Options is a list of SearchEngine booleanvar, label pairs.
A gridded frame from make_frame is filled with a Checkbutton
for each pair, bound to the var, with the corresponding label.
'''
frame = self.make_frame("Options")[0]
engine = self.engine
options = [(engine.revar, "Regular expression"),
(engine.casevar, "Match case"),
(engine.wordvar, "Whole word")]
if self.needwrapbutton:
options.append((engine.wrapvar, "Wrap around"))
for var, label in options:
btn = Checkbutton(frame, anchor="w", variable=var, text=label)
btn.pack(side="left", fill="both")
if var.get():
btn.select()
return frame, options
def create_other_buttons(self):
'''Return (frame, others) for testing.
Others is a list of value, label pairs.
A gridded frame from make_frame is filled with radio buttons.
'''
frame = self.make_frame("Direction")[0]
var = self.engine.backvar
others = [(1, 'Up'), (0, 'Down')]
for val, label in others:
btn = Radiobutton(frame, anchor="w",
variable=var, value=val, text=label)
btn.pack(side="left", fill="both")
if var.get() == val:
btn.select()
return frame, others
def make_button(self, label, command, isdef=0):
"Return command button gridded in command frame."
b = Button(self.buttonframe,
text=label, command=command,
default=isdef and "active" or "normal")
cols,rows=self.buttonframe.grid_size()
b.grid(pady=1,row=rows,column=0,sticky="ew")
self.buttonframe.grid(rowspan=rows+1)
return b
def create_command_buttons(self):
"Place buttons in vertical command frame gridded on right."
f = self.buttonframe = Frame(self.top)
f.grid(row=0,column=2,padx=2,pady=2,ipadx=2,ipady=2)
b = self.make_button("close", self.close)
b.lower()
if __name__ == '__main__':
import unittest
unittest.main(
'idlelib.idle_test.test_searchdialogbase', verbosity=2)

233
Lib/idlelib/SearchEngine.py Normal file
View File

@@ -0,0 +1,233 @@
'''Define SearchEngine for search dialogs.'''
import re
from Tkinter import StringVar, BooleanVar, TclError
import tkMessageBox
def get(root):
'''Return the singleton SearchEngine instance for the process.
The single SearchEngine saves settings between dialog instances.
If there is not a SearchEngine already, make one.
'''
if not hasattr(root, "_searchengine"):
root._searchengine = SearchEngine(root)
# This creates a cycle that persists until root is deleted.
return root._searchengine
class SearchEngine:
"""Handles searching a text widget for Find, Replace, and Grep."""
def __init__(self, root):
'''Initialize Variables that save search state.
The dialogs bind these to the UI elements present in the dialogs.
'''
self.root = root # need for report_error()
self.patvar = StringVar(root, '') # search pattern
self.revar = BooleanVar(root, False) # regular expression?
self.casevar = BooleanVar(root, False) # match case?
self.wordvar = BooleanVar(root, False) # match whole word?
self.wrapvar = BooleanVar(root, True) # wrap around buffer?
self.backvar = BooleanVar(root, False) # search backwards?
# Access methods
def getpat(self):
return self.patvar.get()
def setpat(self, pat):
self.patvar.set(pat)
def isre(self):
return self.revar.get()
def iscase(self):
return self.casevar.get()
def isword(self):
return self.wordvar.get()
def iswrap(self):
return self.wrapvar.get()
def isback(self):
return self.backvar.get()
# Higher level access methods
def setcookedpat(self, pat):
"Set pattern after escaping if re."
# called only in SearchDialog.py: 66
if self.isre():
pat = re.escape(pat)
self.setpat(pat)
def getcookedpat(self):
pat = self.getpat()
if not self.isre(): # if True, see setcookedpat
pat = re.escape(pat)
if self.isword():
pat = r"\b%s\b" % pat
return pat
def getprog(self):
"Return compiled cooked search pattern."
pat = self.getpat()
if not pat:
self.report_error(pat, "Empty regular expression")
return None
pat = self.getcookedpat()
flags = 0
if not self.iscase():
flags = flags | re.IGNORECASE
try:
prog = re.compile(pat, flags)
except re.error as what:
args = what.args
msg = args[0]
col = args[1] if len(args) >= 2 else -1
self.report_error(pat, msg, col)
return None
return prog
def report_error(self, pat, msg, col=-1):
# Derived class could override this with something fancier
msg = "Error: " + str(msg)
if pat:
msg = msg + "\nPattern: " + str(pat)
if col >= 0:
msg = msg + "\nOffset: " + str(col)
tkMessageBox.showerror("Regular expression error",
msg, master=self.root)
def search_text(self, text, prog=None, ok=0):
'''Return (lineno, matchobj) or None for forward/backward search.
This function calls the right function with the right arguments.
It directly return the result of that call.
Text is a text widget. Prog is a precompiled pattern.
The ok parameter is a bit complicated as it has two effects.
If there is a selection, the search begin at either end,
depending on the direction setting and ok, with ok meaning that
the search starts with the selection. Otherwise, search begins
at the insert mark.
To aid progress, the search functions do not return an empty
match at the starting position unless ok is True.
'''
if not prog:
prog = self.getprog()
if not prog:
return None # Compilation failed -- stop
wrap = self.wrapvar.get()
first, last = get_selection(text)
if self.isback():
if ok:
start = last
else:
start = first
line, col = get_line_col(start)
res = self.search_backward(text, prog, line, col, wrap, ok)
else:
if ok:
start = first
else:
start = last
line, col = get_line_col(start)
res = self.search_forward(text, prog, line, col, wrap, ok)
return res
def search_forward(self, text, prog, line, col, wrap, ok=0):
wrapped = 0
startline = line
chars = text.get("%d.0" % line, "%d.0" % (line+1))
while chars:
m = prog.search(chars[:-1], col)
if m:
if ok or m.end() > col:
return line, m
line = line + 1
if wrapped and line > startline:
break
col = 0
ok = 1
chars = text.get("%d.0" % line, "%d.0" % (line+1))
if not chars and wrap:
wrapped = 1
wrap = 0
line = 1
chars = text.get("1.0", "2.0")
return None
def search_backward(self, text, prog, line, col, wrap, ok=0):
wrapped = 0
startline = line
chars = text.get("%d.0" % line, "%d.0" % (line+1))
while 1:
m = search_reverse(prog, chars[:-1], col)
if m:
if ok or m.start() < col:
return line, m
line = line - 1
if wrapped and line < startline:
break
ok = 1
if line <= 0:
if not wrap:
break
wrapped = 1
wrap = 0
pos = text.index("end-1c")
line, col = map(int, pos.split("."))
chars = text.get("%d.0" % line, "%d.0" % (line+1))
col = len(chars) - 1
return None
def search_reverse(prog, chars, col):
'''Search backwards and return an re match object or None.
This is done by searching forwards until there is no match.
Prog: compiled re object with a search method returning a match.
Chars: line of text, without \\n.
Col: stop index for the search; the limit for match.end().
'''
m = prog.search(chars)
if not m:
return None
found = None
i, j = m.span() # m.start(), m.end() == match slice indexes
while i < col and j <= col:
found = m
if i == j:
j = j+1
m = prog.search(chars, j)
if not m:
break
i, j = m.span()
return found
def get_selection(text):
'''Return tuple of 'line.col' indexes from selection or insert mark.
'''
try:
first = text.index("sel.first")
last = text.index("sel.last")
except TclError:
first = last = None
if not first:
first = text.index("insert")
if not last:
last = first
return first, last
def get_line_col(index):
'''Return (line, col) tuple of ints from 'line.col' string.'''
line, col = map(int, index.split(".")) # Fails on invalid index
return line, col
if __name__ == "__main__":
import unittest
unittest.main('idlelib.idle_test.test_searchengine', verbosity=2, exit=False)

151
Lib/idlelib/StackViewer.py Normal file
View File

@@ -0,0 +1,151 @@
import os
import sys
import linecache
import re
import Tkinter as tk
from idlelib.TreeWidget import TreeNode, TreeItem, ScrolledCanvas
from idlelib.ObjectBrowser import ObjectTreeItem, make_objecttreeitem
from idlelib.PyShell import PyShellFileList
def StackBrowser(root, flist=None, tb=None, top=None):
if top is None:
top = tk.Toplevel(root)
sc = ScrolledCanvas(top, bg="white", highlightthickness=0)
sc.frame.pack(expand=1, fill="both")
item = StackTreeItem(flist, tb)
node = TreeNode(sc.canvas, None, item)
node.expand()
class StackTreeItem(TreeItem):
def __init__(self, flist=None, tb=None):
self.flist = flist
self.stack = self.get_stack(tb)
self.text = self.get_exception()
def get_stack(self, tb):
if tb is None:
tb = sys.last_traceback
stack = []
if tb and tb.tb_frame is None:
tb = tb.tb_next
while tb is not None:
stack.append((tb.tb_frame, tb.tb_lineno))
tb = tb.tb_next
return stack
def get_exception(self):
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
def GetText(self):
return self.text
def GetSubList(self):
sublist = []
for info in self.stack:
item = FrameTreeItem(info, self.flist)
sublist.append(item)
return sublist
class FrameTreeItem(TreeItem):
def __init__(self, info, flist):
self.info = info
self.flist = flist
def GetText(self):
frame, lineno = self.info
try:
modname = frame.f_globals["__name__"]
except:
modname = "?"
code = frame.f_code
filename = code.co_filename
funcname = code.co_name
sourceline = linecache.getline(filename, lineno)
sourceline = 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)
return item
def GetSubList(self):
frame, lineno = self.info
sublist = []
if frame.f_globals is not frame.f_locals:
item = VariablesTreeItem("<locals>", frame.f_locals, self.flist)
sublist.append(item)
item = VariablesTreeItem("<globals>", frame.f_globals, self.flist)
sublist.append(item)
return sublist
def OnDoubleClick(self):
if self.flist:
frame, lineno = self.info
filename = frame.f_code.co_filename
if os.path.isfile(filename):
self.flist.gotofileline(filename, lineno)
class VariablesTreeItem(ObjectTreeItem):
def GetText(self):
return self.labeltext
def GetLabelText(self):
return None
def IsExpandable(self):
return len(self.object) > 0
def GetSubList(self):
sublist = []
for key in self.object.keys():
try:
value = self.object[key]
except KeyError:
continue
def setfunction(value, key=key, object=self.object):
object[key] = value
item = make_objecttreeitem(key + " =", value, setfunction)
sublist.append(item)
return sublist
def keys(self): # unused, left for possible 3rd party use
return self.object.keys()
def _stack_viewer(parent): # htest #
root = tk.Tk()
root.title("Test StackViewer")
width, height, x, y = list(map(int, re.split('[x+]', parent.geometry())))
root.geometry("+%d+%d"%(x, y + 150))
flist = PyShellFileList(root)
try: # to obtain a traceback object
intentional_name_error
except NameError:
exc_type, exc_value, exc_tb = sys.exc_info()
# inject stack trace to sys
sys.last_type = exc_type
sys.last_value = exc_value
sys.last_traceback = exc_tb
StackBrowser(root, flist=flist, top=root, tb=exc_tb)
# restore sys to original state
del sys.last_type
del sys.last_value
del sys.last_traceback
if __name__ == '__main__':
from idlelib.idle_test.htest import run
run(_stack_viewer)

210
Lib/idlelib/TODO.txt Normal file
View File

@@ -0,0 +1,210 @@
Original IDLE todo, much of it now outdated:
============================================
TO DO:
- improve debugger:
- manage breakpoints globally, allow bp deletion, tbreak, cbreak etc.
- real object browser
- help on how to use it (a simple help button will do wonders)
- performance? (updates of large sets of locals are slow)
- better integration of "debug module"
- debugger should be global resource (attached to flist, not to shell)
- fix the stupid bug where you need to step twice
- display class name in stack viewer entries for methods
- suppress tracing through IDLE internals (e.g. print) DONE
- add a button to suppress through a specific module or class or method
- more object inspection to stack viewer, e.g. to view all array items
- insert the initial current directory into sys.path DONE
- default directory attribute for each window instead of only for windows
that have an associated filename
- command expansion from keywords, module contents, other buffers, etc.
- "Recent documents" menu item DONE
- Filter region command
- Optional horizontal scroll bar
- more Emacsisms:
- ^K should cut to buffer
- M-[, M-] to move by paragraphs
- incremental search?
- search should indicate wrap-around in some way
- restructure state sensitive code to avoid testing flags all the time
- persistent user state (e.g. window and cursor positions, bindings)
- make backups when saving
- check file mtimes at various points
- Pluggable interface with RCS/CVS/Perforce/Clearcase
- better help?
- don't open second class browser on same module (nor second path browser)
- unify class and path browsers
- Need to define a standard way whereby one can determine one is running
inside IDLE (needed for Tk mainloop, also handy for $PYTHONSTARTUP)
- Add more utility methods for use by extensions (a la get_selection)
- Way to run command in totally separate interpreter (fork+os.system?) DONE
- Way to find definition of fully-qualified name:
In other words, select "UserDict.UserDict", hit some magic key and
it loads up UserDict.py and finds the first def or class for UserDict.
- need a way to force colorization on/off
- need a way to force auto-indent on/off
Details:
- ^O (on Unix -- open-line) should honor autoindent
- after paste, show end of pasted text
- on Windows, should turn short filename to long filename (not only in argv!)
(shouldn't this be done -- or undone -- by ntpath.normpath?)
- new autoindent after colon even indents when the colon is in a comment!
- sometimes forward slashes in pathname remain
- sometimes star in window name remains in Windows menu
- With unix bindings, ESC by itself is ignored
- Sometimes for no apparent reason a selection from the cursor to the
end of the command buffer appears, which is hard to get rid of
because it stays when you are typing!
- The Line/Col in the status bar can be wrong initially in PyShell DONE
Structural problems:
- too much knowledge in FileList about EditorWindow (for example)
- should add some primitives for accessing the selection etc.
to repeat cumbersome code over and over
======================================================================
Jeff Bauer suggests:
- Open Module doesn't appear to handle hierarchical packages.
- Class browser should also allow hierarchical packages.
- Open and Open Module could benefit from a history, DONE
either command line style, or Microsoft recent-file
style.
- Add a Smalltalk-style inspector (i.e. Tkinspect)
The last suggestion is already a reality, but not yet
integrated into IDLE. I use a module called inspector.py,
that used to be available from python.org(?) It no longer
appears to be in the contributed section, and the source
has no author attribution.
In any case, the code is useful for visually navigating
an object's attributes, including its container hierarchy.
>>> from inspector import Tkinspect
>>> Tkinspect(None, myObject)
Tkinspect could probably be extended and refined to
integrate better into IDLE.
======================================================================
Comparison to PTUI
------------------
+ PTUI's help is better (HTML!)
+ PTUI can attach a shell to any module
+ PTUI has some more I/O commands:
open multiple
append
examine (what's that?)
======================================================================
Notes after trying to run Grail
-------------------------------
- Grail does stuff to sys.path based on sys.argv[0]; you must set
sys.argv[0] to something decent first (it is normally set to the path of
the idle script).
- Grail must be exec'ed in __main__ because that's imported by some
other parts of Grail.
- Grail uses a module called History and so does idle :-(
======================================================================
Robin Friedrich's items:
Things I'd like to see:
- I'd like support for shift-click extending the selection. There's a
bug now that it doesn't work the first time you try it.
- Printing is needed. How hard can that be on Windows? FIRST CUT DONE
- The python-mode trick of autoindenting a line with <tab> is neat and
very handy.
- (someday) a spellchecker for docstrings and comments.
- a pagedown/up command key which moves to next class/def statement (top
level)
- split window capability
- DnD text relocation/copying
Things I don't want to see.
- line numbers... will probably slow things down way too much.
- Please use another icon for the tree browser leaf. The small snake
isn't cutting it.
----------------------------------------------------------------------
- Customizable views (multi-window or multi-pane). (Markus Gritsch)
- Being able to double click (maybe double right click) on a callable
object in the editor which shows the source of the object, if
possible. (Gerrit Holl)
- Hooks into the guts, like in Emacs. (Mike Romberg)
- Sharing the editor with a remote tutor. (Martijn Faassen)
- Multiple views on the same file. (Tony J Ibbs)
- Store breakpoints in a global (per-project) database (GvR); Dirk
Heise adds: save some space-trimmed context and search around when
reopening a file that might have been edited by someone else.
- Capture menu events in extensions without changing the IDLE source.
(Matthias Barmeier)
- Use overlapping panels (a "notebook" in MFC terms I think) for info
that doesn't need to be accessible simultaneously (e.g. HTML source
and output). Use multi-pane windows for info that does need to be
shown together (e.g. class browser and source). (Albert Brandl)
- A project should invisibly track all symbols, for instant search,
replace and cross-ref. Projects should be allowed to span multiple
directories, hosts, etc. Project management files are placed in a
directory you specify. A global mapping between project names and
project directories should exist [not so sure --GvR]. (Tim Peters)
- Merge attr-tips and auto-expand. (Mark Hammond, Tim Peters)
- Python Shell should behave more like a "shell window" as users know
it -- i.e. you can only edit the current command, and the cursor can't
escape from the command area. (Albert Brandl)
- Set X11 class to "idle/Idle", set icon and title to something
beginning with "idle" -- for window manangers. (Randall Hopper)
- Config files editable through a preferences dialog. (me) DONE
- Config files still editable outside the preferences dialog.
(Randall Hopper) DONE
- When you're editing a command in PyShell, and there are only blank
lines below the cursor, hitting Return should ignore or delete those
blank lines rather than deciding you're not on the last line. (me)
- Run command (F5 c.s.) should be more like Pythonwin's Run -- a
dialog with options to give command line arguments, run the debugger,
etc. (me)
- Shouldn't be able to delete part of the prompt (or any text before
it) in the PyShell. (Martijn Faassen) DONE
- Emacs style auto-fill (also smart about comments and strings).
(Jeremy Hylton)
- Output of Run Script should go to a separate output window, not to
the shell window. Output of separate runs should all go to the same
window but clearly delimited. (David Scherer) REJECT FIRST, LATTER DONE
- GUI form designer to kick VB's butt. (Robert Geiger) THAT'S NOT IDLE
- Printing! Possibly via generation of PDF files which the user must
then send to the printer separately. (Dinu Gherman) FIRST CUT

97
Lib/idlelib/ToolTip.py Normal file
View File

@@ -0,0 +1,97 @@
# general purpose 'tooltip' routines - currently unused in idlefork
# (although the 'calltips' extension is partly based on this code)
# may be useful for some purposes in (or almost in ;) the current project scope
# Ideas gleaned from PySol
from Tkinter import *
class ToolTipBase:
def __init__(self, button):
self.button = button
self.tipwindow = None
self.id = None
self.x = self.y = 0
self._id1 = self.button.bind("<Enter>", self.enter)
self._id2 = self.button.bind("<Leave>", self.leave)
self._id3 = self.button.bind("<ButtonPress>", self.leave)
def enter(self, event=None):
self.schedule()
def leave(self, event=None):
self.unschedule()
self.hidetip()
def schedule(self):
self.unschedule()
self.id = self.button.after(1500, self.showtip)
def unschedule(self):
id = self.id
self.id = None
if id:
self.button.after_cancel(id)
def showtip(self):
if self.tipwindow:
return
# The tip window must be completely outside the button;
# otherwise when the mouse enters the tip window we get
# a leave event and it disappears, and then we get an enter
# event and it reappears, and so on forever :-(
x = self.button.winfo_rootx() + 20
y = self.button.winfo_rooty() + self.button.winfo_height() + 1
self.tipwindow = tw = Toplevel(self.button)
tw.wm_overrideredirect(1)
tw.wm_geometry("+%d+%d" % (x, y))
self.showcontents()
def showcontents(self, text="Your text here"):
# Override this in derived class
label = Label(self.tipwindow, text=text, justify=LEFT,
background="#ffffe0", relief=SOLID, borderwidth=1)
label.pack()
def hidetip(self):
tw = self.tipwindow
self.tipwindow = None
if tw:
tw.destroy()
class ToolTip(ToolTipBase):
def __init__(self, button, text):
ToolTipBase.__init__(self, button)
self.text = text
def showcontents(self):
ToolTipBase.showcontents(self, self.text)
class ListboxToolTip(ToolTipBase):
def __init__(self, button, items):
ToolTipBase.__init__(self, button)
self.items = items
def showcontents(self):
listbox = Listbox(self.tipwindow, background="#ffffe0")
listbox.pack()
for item in self.items:
listbox.insert(END, item)
def _tooltip(parent):
root = Tk()
root.title("Test tooltip")
width, height, x, y = list(map(int, re.split('[x+]', parent.geometry())))
root.geometry("+%d+%d"%(x, y + 150))
label = Label(root, text="Place your mouse over buttons")
label.pack()
button1 = Button(root, text="Button 1")
button2 = Button(root, text="Button 2")
button1.pack()
button2.pack()
ToolTip(button1, "This is tooltip text for button1.")
ListboxToolTip(button2, ["This is","multiple line",
"tooltip text","for button2"])
root.mainloop()
if __name__ == '__main__':
from idlelib.idle_test.htest import run
run(_tooltip)

467
Lib/idlelib/TreeWidget.py Normal file
View File

@@ -0,0 +1,467 @@
# XXX TO DO:
# - popup menu
# - support partial or total redisplay
# - key bindings (instead of quick-n-dirty bindings on Canvas):
# - up/down arrow keys to move focus around
# - ditto for page up/down, home/end
# - left/right arrows to expand/collapse & move out/in
# - more doc strings
# - add icons for "file", "module", "class", "method"; better "python" icon
# - callback for selection???
# - multiple-item selection
# - tooltips
# - redo geometry without magic numbers
# - keep track of object ids to allow more careful cleaning
# - optimize tree redraw after expand of subnode
import os
from Tkinter import *
import imp
from idlelib import ZoomHeight
from idlelib.configHandler import idleConf
ICONDIR = "Icons"
# Look for Icons subdirectory in the same directory as this module
try:
_icondir = os.path.join(os.path.dirname(__file__), ICONDIR)
except NameError:
_icondir = ICONDIR
if os.path.isdir(_icondir):
ICONDIR = _icondir
elif not os.path.isdir(ICONDIR):
raise RuntimeError, "can't find icon directory (%r)" % (ICONDIR,)
def listicons(icondir=ICONDIR):
"""Utility to display the available icons."""
root = Tk()
import glob
list = glob.glob(os.path.join(icondir, "*.gif"))
list.sort()
images = []
row = column = 0
for file in list:
name = os.path.splitext(os.path.basename(file))[0]
image = PhotoImage(file=file, master=root)
images.append(image)
label = Label(root, image=image, bd=1, relief="raised")
label.grid(row=row, column=column)
label = Label(root, text=name)
label.grid(row=row+1, column=column)
column = column + 1
if column >= 10:
row = row+2
column = 0
root.images = images
class TreeNode:
def __init__(self, canvas, parent, item):
self.canvas = canvas
self.parent = parent
self.item = item
self.state = 'collapsed'
self.selected = False
self.children = []
self.x = self.y = None
self.iconimages = {} # cache of PhotoImage instances for icons
def destroy(self):
for c in self.children[:]:
self.children.remove(c)
c.destroy()
self.parent = None
def geticonimage(self, name):
try:
return self.iconimages[name]
except KeyError:
pass
file, ext = os.path.splitext(name)
ext = ext or ".gif"
fullname = os.path.join(ICONDIR, file + ext)
image = PhotoImage(master=self.canvas, file=fullname)
self.iconimages[name] = image
return image
def select(self, event=None):
if self.selected:
return
self.deselectall()
self.selected = True
self.canvas.delete(self.image_id)
self.drawicon()
self.drawtext()
def deselect(self, event=None):
if not self.selected:
return
self.selected = False
self.canvas.delete(self.image_id)
self.drawicon()
self.drawtext()
def deselectall(self):
if self.parent:
self.parent.deselectall()
else:
self.deselecttree()
def deselecttree(self):
if self.selected:
self.deselect()
for child in self.children:
child.deselecttree()
def flip(self, event=None):
if self.state == 'expanded':
self.collapse()
else:
self.expand()
self.item.OnDoubleClick()
return "break"
def expand(self, event=None):
if not self.item._IsExpandable():
return
if self.state != 'expanded':
self.state = 'expanded'
self.update()
self.view()
def collapse(self, event=None):
if self.state != 'collapsed':
self.state = 'collapsed'
self.update()
def view(self):
top = self.y - 2
bottom = self.lastvisiblechild().y + 17
height = bottom - top
visible_top = self.canvas.canvasy(0)
visible_height = self.canvas.winfo_height()
visible_bottom = self.canvas.canvasy(visible_height)
if visible_top <= top and bottom <= visible_bottom:
return
x0, y0, x1, y1 = self.canvas._getints(self.canvas['scrollregion'])
if top >= visible_top and height <= visible_height:
fraction = top + height - visible_height
else:
fraction = top
fraction = float(fraction) / y1
self.canvas.yview_moveto(fraction)
def lastvisiblechild(self):
if self.children and self.state == 'expanded':
return self.children[-1].lastvisiblechild()
else:
return self
def update(self):
if self.parent:
self.parent.update()
else:
oldcursor = self.canvas['cursor']
self.canvas['cursor'] = "watch"
self.canvas.update()
self.canvas.delete(ALL) # XXX could be more subtle
self.draw(7, 2)
x0, y0, x1, y1 = self.canvas.bbox(ALL)
self.canvas.configure(scrollregion=(0, 0, x1, y1))
self.canvas['cursor'] = oldcursor
def draw(self, x, y):
# XXX This hard-codes too many geometry constants!
dy = 20
self.x, self.y = x, y
self.drawicon()
self.drawtext()
if self.state != 'expanded':
return y + dy
# draw children
if not self.children:
sublist = self.item._GetSubList()
if not sublist:
# _IsExpandable() was mistaken; that's allowed
return y+17
for item in sublist:
child = self.__class__(self.canvas, self, item)
self.children.append(child)
cx = x+20
cy = y + dy
cylast = 0
for child in self.children:
cylast = cy
self.canvas.create_line(x+9, cy+7, cx, cy+7, fill="gray50")
cy = child.draw(cx, cy)
if child.item._IsExpandable():
if child.state == 'expanded':
iconname = "minusnode"
callback = child.collapse
else:
iconname = "plusnode"
callback = child.expand
image = self.geticonimage(iconname)
id = self.canvas.create_image(x+9, cylast+7, image=image)
# XXX This leaks bindings until canvas is deleted:
self.canvas.tag_bind(id, "<1>", callback)
self.canvas.tag_bind(id, "<Double-1>", lambda x: None)
id = self.canvas.create_line(x+9, y+10, x+9, cylast+7,
##stipple="gray50", # XXX Seems broken in Tk 8.0.x
fill="gray50")
self.canvas.tag_lower(id) # XXX .lower(id) before Python 1.5.2
return cy
def drawicon(self):
if self.selected:
imagename = (self.item.GetSelectedIconName() or
self.item.GetIconName() or
"openfolder")
else:
imagename = self.item.GetIconName() or "folder"
image = self.geticonimage(imagename)
id = self.canvas.create_image(self.x, self.y, anchor="nw", image=image)
self.image_id = id
self.canvas.tag_bind(id, "<1>", self.select)
self.canvas.tag_bind(id, "<Double-1>", self.flip)
def drawtext(self):
textx = self.x+20-1
texty = self.y-4
labeltext = self.item.GetLabelText()
if labeltext:
id = self.canvas.create_text(textx, texty, anchor="nw",
text=labeltext)
self.canvas.tag_bind(id, "<1>", self.select)
self.canvas.tag_bind(id, "<Double-1>", self.flip)
x0, y0, x1, y1 = self.canvas.bbox(id)
textx = max(x1, 200) + 10
text = self.item.GetText() or "<no text>"
try:
self.entry
except AttributeError:
pass
else:
self.edit_finish()
try:
self.label
except AttributeError:
# padding carefully selected (on Windows) to match Entry widget:
self.label = Label(self.canvas, text=text, bd=0, padx=2, pady=2)
theme = idleConf.CurrentTheme()
if self.selected:
self.label.configure(idleConf.GetHighlight(theme, 'hilite'))
else:
self.label.configure(idleConf.GetHighlight(theme, 'normal'))
id = self.canvas.create_window(textx, texty,
anchor="nw", window=self.label)
self.label.bind("<1>", self.select_or_edit)
self.label.bind("<Double-1>", self.flip)
self.text_id = id
def select_or_edit(self, event=None):
if self.selected and self.item.IsEditable():
self.edit(event)
else:
self.select(event)
def edit(self, event=None):
self.entry = Entry(self.label, bd=0, highlightthickness=1, width=0)
self.entry.insert(0, self.label['text'])
self.entry.selection_range(0, END)
self.entry.pack(ipadx=5)
self.entry.focus_set()
self.entry.bind("<Return>", self.edit_finish)
self.entry.bind("<Escape>", self.edit_cancel)
def edit_finish(self, event=None):
try:
entry = self.entry
del self.entry
except AttributeError:
return
text = entry.get()
entry.destroy()
if text and text != self.item.GetText():
self.item.SetText(text)
text = self.item.GetText()
self.label['text'] = text
self.drawtext()
self.canvas.focus_set()
def edit_cancel(self, event=None):
try:
entry = self.entry
del self.entry
except AttributeError:
return
entry.destroy()
self.drawtext()
self.canvas.focus_set()
class TreeItem:
"""Abstract class representing tree items.
Methods should typically be overridden, otherwise a default action
is used.
"""
def __init__(self):
"""Constructor. Do whatever you need to do."""
def GetText(self):
"""Return text string to display."""
def GetLabelText(self):
"""Return label text string to display in front of text (if any)."""
expandable = None
def _IsExpandable(self):
"""Do not override! Called by TreeNode."""
if self.expandable is None:
self.expandable = self.IsExpandable()
return self.expandable
def IsExpandable(self):
"""Return whether there are subitems."""
return 1
def _GetSubList(self):
"""Do not override! Called by TreeNode."""
if not self.IsExpandable():
return []
sublist = self.GetSubList()
if not sublist:
self.expandable = 0
return sublist
def IsEditable(self):
"""Return whether the item's text may be edited."""
def SetText(self, text):
"""Change the item's text (if it is editable)."""
def GetIconName(self):
"""Return name of icon to be displayed normally."""
def GetSelectedIconName(self):
"""Return name of icon to be displayed when selected."""
def GetSubList(self):
"""Return list of items forming sublist."""
def OnDoubleClick(self):
"""Called on a double-click on the item."""
# Example application
class FileTreeItem(TreeItem):
"""Example TreeItem subclass -- browse the file system."""
def __init__(self, path):
self.path = path
def GetText(self):
return os.path.basename(self.path) or self.path
def IsEditable(self):
return os.path.basename(self.path) != ""
def SetText(self, text):
newpath = os.path.dirname(self.path)
newpath = os.path.join(newpath, text)
if os.path.dirname(newpath) != os.path.dirname(self.path):
return
try:
os.rename(self.path, newpath)
self.path = newpath
except os.error:
pass
def GetIconName(self):
if not self.IsExpandable():
return "python" # XXX wish there was a "file" icon
def IsExpandable(self):
return os.path.isdir(self.path)
def GetSubList(self):
try:
names = os.listdir(self.path)
except os.error:
return []
names.sort(key = os.path.normcase)
sublist = []
for name in names:
item = FileTreeItem(os.path.join(self.path, name))
sublist.append(item)
return sublist
# A canvas widget with scroll bars and some useful bindings
class ScrolledCanvas:
def __init__(self, master, **opts):
if 'yscrollincrement' not in opts:
opts['yscrollincrement'] = 17
self.master = master
self.frame = Frame(master)
self.frame.rowconfigure(0, weight=1)
self.frame.columnconfigure(0, weight=1)
self.canvas = Canvas(self.frame, **opts)
self.canvas.grid(row=0, column=0, sticky="nsew")
self.vbar = Scrollbar(self.frame, name="vbar")
self.vbar.grid(row=0, column=1, sticky="nse")
self.hbar = Scrollbar(self.frame, name="hbar", orient="horizontal")
self.hbar.grid(row=1, column=0, sticky="ews")
self.canvas['yscrollcommand'] = self.vbar.set
self.vbar['command'] = self.canvas.yview
self.canvas['xscrollcommand'] = self.hbar.set
self.hbar['command'] = self.canvas.xview
self.canvas.bind("<Key-Prior>", self.page_up)
self.canvas.bind("<Key-Next>", self.page_down)
self.canvas.bind("<Key-Up>", self.unit_up)
self.canvas.bind("<Key-Down>", self.unit_down)
#if isinstance(master, Toplevel) or isinstance(master, Tk):
self.canvas.bind("<Alt-Key-2>", self.zoom_height)
self.canvas.focus_set()
def page_up(self, event):
self.canvas.yview_scroll(-1, "page")
return "break"
def page_down(self, event):
self.canvas.yview_scroll(1, "page")
return "break"
def unit_up(self, event):
self.canvas.yview_scroll(-1, "unit")
return "break"
def unit_down(self, event):
self.canvas.yview_scroll(1, "unit")
return "break"
def zoom_height(self, event):
ZoomHeight.zoom_height(self.master)
return "break"
def _tree_widget(parent):
root = Tk()
root.title("Test TreeWidget")
width, height, x, y = list(map(int, re.split('[x+]', parent.geometry())))
root.geometry("+%d+%d"%(x, y + 150))
sc = ScrolledCanvas(root, bg="white", highlightthickness=0, takefocus=1)
sc.frame.pack(expand=1, fill="both", side=LEFT)
item = FileTreeItem(os.getcwd())
node = TreeNode(sc.canvas, None, item)
node.expand()
root.mainloop()
if __name__ == '__main__':
from idlelib.idle_test.htest import run
run(_tree_widget)

View File

@@ -0,0 +1,365 @@
import string
from Tkinter import *
from idlelib.Delegator import Delegator
#$ event <<redo>>
#$ win <Control-y>
#$ unix <Alt-z>
#$ event <<undo>>
#$ win <Control-z>
#$ unix <Control-z>
#$ event <<dump-undo-state>>
#$ win <Control-backslash>
#$ unix <Control-backslash>
class UndoDelegator(Delegator):
max_undo = 1000
def __init__(self):
Delegator.__init__(self)
self.reset_undo()
def setdelegate(self, delegate):
if self.delegate is not None:
self.unbind("<<undo>>")
self.unbind("<<redo>>")
self.unbind("<<dump-undo-state>>")
Delegator.setdelegate(self, delegate)
if delegate is not None:
self.bind("<<undo>>", self.undo_event)
self.bind("<<redo>>", self.redo_event)
self.bind("<<dump-undo-state>>", self.dump_event)
def dump_event(self, event):
from pprint import pprint
pprint(self.undolist[:self.pointer])
print "pointer:", self.pointer,
print "saved:", self.saved,
print "can_merge:", self.can_merge,
print "get_saved():", self.get_saved()
pprint(self.undolist[self.pointer:])
return "break"
def reset_undo(self):
self.was_saved = -1
self.pointer = 0
self.undolist = []
self.undoblock = 0 # or a CommandSequence instance
self.set_saved(1)
def set_saved(self, flag):
if flag:
self.saved = self.pointer
else:
self.saved = -1
self.can_merge = False
self.check_saved()
def get_saved(self):
return self.saved == self.pointer
saved_change_hook = None
def set_saved_change_hook(self, hook):
self.saved_change_hook = hook
was_saved = -1
def check_saved(self):
is_saved = self.get_saved()
if is_saved != self.was_saved:
self.was_saved = is_saved
if self.saved_change_hook:
self.saved_change_hook()
def insert(self, index, chars, tags=None):
self.addcmd(InsertCommand(index, chars, tags))
def delete(self, index1, index2=None):
self.addcmd(DeleteCommand(index1, index2))
# Clients should call undo_block_start() and undo_block_stop()
# around a sequence of editing cmds to be treated as a unit by
# undo & redo. Nested matching calls are OK, and the inner calls
# then act like nops. OK too if no editing cmds, or only one
# editing cmd, is issued in between: if no cmds, the whole
# sequence has no effect; and if only one cmd, that cmd is entered
# directly into the undo list, as if undo_block_xxx hadn't been
# called. The intent of all that is to make this scheme easy
# to use: all the client has to worry about is making sure each
# _start() call is matched by a _stop() call.
def undo_block_start(self):
if self.undoblock == 0:
self.undoblock = CommandSequence()
self.undoblock.bump_depth()
def undo_block_stop(self):
if self.undoblock.bump_depth(-1) == 0:
cmd = self.undoblock
self.undoblock = 0
if len(cmd) > 0:
if len(cmd) == 1:
# no need to wrap a single cmd
cmd = cmd.getcmd(0)
# this blk of cmds, or single cmd, has already
# been done, so don't execute it again
self.addcmd(cmd, 0)
def addcmd(self, cmd, execute=True):
if execute:
cmd.do(self.delegate)
if self.undoblock != 0:
self.undoblock.append(cmd)
return
if self.can_merge and self.pointer > 0:
lastcmd = self.undolist[self.pointer-1]
if lastcmd.merge(cmd):
return
self.undolist[self.pointer:] = [cmd]
if self.saved > self.pointer:
self.saved = -1
self.pointer = self.pointer + 1
if len(self.undolist) > self.max_undo:
##print "truncating undo list"
del self.undolist[0]
self.pointer = self.pointer - 1
if self.saved >= 0:
self.saved = self.saved - 1
self.can_merge = True
self.check_saved()
def undo_event(self, event):
if self.pointer == 0:
self.bell()
return "break"
cmd = self.undolist[self.pointer - 1]
cmd.undo(self.delegate)
self.pointer = self.pointer - 1
self.can_merge = False
self.check_saved()
return "break"
def redo_event(self, event):
if self.pointer >= len(self.undolist):
self.bell()
return "break"
cmd = self.undolist[self.pointer]
cmd.redo(self.delegate)
self.pointer = self.pointer + 1
self.can_merge = False
self.check_saved()
return "break"
class Command:
# Base class for Undoable commands
tags = None
def __init__(self, index1, index2, chars, tags=None):
self.marks_before = {}
self.marks_after = {}
self.index1 = index1
self.index2 = index2
self.chars = chars
if tags:
self.tags = tags
def __repr__(self):
s = self.__class__.__name__
t = (self.index1, self.index2, self.chars, self.tags)
if self.tags is None:
t = t[:-1]
return s + repr(t)
def do(self, text):
pass
def redo(self, text):
pass
def undo(self, text):
pass
def merge(self, cmd):
return 0
def save_marks(self, text):
marks = {}
for name in text.mark_names():
if name != "insert" and name != "current":
marks[name] = text.index(name)
return marks
def set_marks(self, text, marks):
for name, index in marks.items():
text.mark_set(name, index)
class InsertCommand(Command):
# Undoable insert command
def __init__(self, index1, chars, tags=None):
Command.__init__(self, index1, None, chars, tags)
def do(self, text):
self.marks_before = self.save_marks(text)
self.index1 = text.index(self.index1)
if text.compare(self.index1, ">", "end-1c"):
# Insert before the final newline
self.index1 = text.index("end-1c")
text.insert(self.index1, self.chars, self.tags)
self.index2 = text.index("%s+%dc" % (self.index1, len(self.chars)))
self.marks_after = self.save_marks(text)
##sys.__stderr__.write("do: %s\n" % self)
def redo(self, text):
text.mark_set('insert', self.index1)
text.insert(self.index1, self.chars, self.tags)
self.set_marks(text, self.marks_after)
text.see('insert')
##sys.__stderr__.write("redo: %s\n" % self)
def undo(self, text):
text.mark_set('insert', self.index1)
text.delete(self.index1, self.index2)
self.set_marks(text, self.marks_before)
text.see('insert')
##sys.__stderr__.write("undo: %s\n" % self)
def merge(self, cmd):
if self.__class__ is not cmd.__class__:
return False
if self.index2 != cmd.index1:
return False
if self.tags != cmd.tags:
return False
if len(cmd.chars) != 1:
return False
if self.chars and \
self.classify(self.chars[-1]) != self.classify(cmd.chars):
return False
self.index2 = cmd.index2
self.chars = self.chars + cmd.chars
return True
alphanumeric = string.ascii_letters + string.digits + "_"
def classify(self, c):
if c in self.alphanumeric:
return "alphanumeric"
if c == "\n":
return "newline"
return "punctuation"
class DeleteCommand(Command):
# Undoable delete command
def __init__(self, index1, index2=None):
Command.__init__(self, index1, index2, None, None)
def do(self, text):
self.marks_before = self.save_marks(text)
self.index1 = text.index(self.index1)
if self.index2:
self.index2 = text.index(self.index2)
else:
self.index2 = text.index(self.index1 + " +1c")
if text.compare(self.index2, ">", "end-1c"):
# Don't delete the final newline
self.index2 = text.index("end-1c")
self.chars = text.get(self.index1, self.index2)
text.delete(self.index1, self.index2)
self.marks_after = self.save_marks(text)
##sys.__stderr__.write("do: %s\n" % self)
def redo(self, text):
text.mark_set('insert', self.index1)
text.delete(self.index1, self.index2)
self.set_marks(text, self.marks_after)
text.see('insert')
##sys.__stderr__.write("redo: %s\n" % self)
def undo(self, text):
text.mark_set('insert', self.index1)
text.insert(self.index1, self.chars)
self.set_marks(text, self.marks_before)
text.see('insert')
##sys.__stderr__.write("undo: %s\n" % self)
class CommandSequence(Command):
# Wrapper for a sequence of undoable cmds to be undone/redone
# as a unit
def __init__(self):
self.cmds = []
self.depth = 0
def __repr__(self):
s = self.__class__.__name__
strs = []
for cmd in self.cmds:
strs.append(" %r" % (cmd,))
return s + "(\n" + ",\n".join(strs) + "\n)"
def __len__(self):
return len(self.cmds)
def append(self, cmd):
self.cmds.append(cmd)
def getcmd(self, i):
return self.cmds[i]
def redo(self, text):
for cmd in self.cmds:
cmd.redo(text)
def undo(self, text):
cmds = self.cmds[:]
cmds.reverse()
for cmd in cmds:
cmd.undo(text)
def bump_depth(self, incr=1):
self.depth = self.depth + incr
return self.depth
def _undo_delegator(parent):
from idlelib.Percolator import Percolator
root = Tk()
root.title("Test UndoDelegator")
width, height, x, y = list(map(int, re.split('[x+]', parent.geometry())))
root.geometry("+%d+%d"%(x, y + 150))
text = Text(root)
text.config(height=10)
text.pack()
text.focus_set()
p = Percolator(text)
d = UndoDelegator()
p.insertfilter(d)
undo = Button(root, text="Undo", command=lambda:d.undo_event(None))
undo.pack(side='left')
redo = Button(root, text="Redo", command=lambda:d.redo_event(None))
redo.pack(side='left')
dump = Button(root, text="Dump", command=lambda:d.dump_event(None))
dump.pack(side='left')
root.mainloop()
if __name__ == "__main__":
from idlelib.idle_test.htest import run
run(_undo_delegator)

View File

@@ -0,0 +1,175 @@
from __future__ import print_function
from Tkinter import TclError
class WidgetRedirector:
"""Support for redirecting arbitrary widget subcommands.
Some Tk operations don't normally pass through tkinter. For example, if a
character is inserted into a Text widget by pressing a key, a default Tk
binding to the widget's 'insert' operation is activated, and the Tk library
processes the insert without calling back into tkinter.
Although a binding to <Key> could be made via tkinter, what we really want
to do is to hook the Tk 'insert' operation itself. For one thing, we want
a text.insert call in idle code to have the same effect as a key press.
When a widget is instantiated, a Tcl command is created whose name is the
same as the pathname widget._w. This command is used to invoke the various
widget operations, e.g. insert (for a Text widget). We are going to hook
this command and provide a facility ('register') to intercept the widget
operation. We will also intercept method calls on the Tkinter class
instance that represents the tk widget.
In IDLE, WidgetRedirector is used in Percolator to intercept Text
commands. The function being registered provides access to the top
of a Percolator chain. At the bottom of the chain is a call to the
original Tk widget operation.
"""
def __init__(self, widget):
'''Initialize attributes and setup redirection.
_operations: dict mapping operation name to new function.
widget: the widget whose tcl command is to be intercepted.
tk: widget.tk, a convenience attribute, probably not needed.
orig: new name of the original tcl command.
Since renaming to orig fails with TclError when orig already
exists, only one WidgetDirector can exist for a given widget.
'''
self._operations = {}
self.widget = widget # widget instance
self.tk = tk = widget.tk # widget's root
w = widget._w # widget's (full) Tk pathname
self.orig = w + "_orig"
# Rename the Tcl command within Tcl:
tk.call("rename", w, self.orig)
# Create a new Tcl command whose name is the widget's pathname, and
# whose action is to dispatch on the operation passed to the widget:
tk.createcommand(w, self.dispatch)
def __repr__(self):
return "WidgetRedirector(%s<%s>)" % (self.widget.__class__.__name__,
self.widget._w)
def close(self):
"Unregister operations and revert redirection created by .__init__."
for operation in list(self._operations):
self.unregister(operation)
widget = self.widget
tk = widget.tk
w = widget._w
# Restore the original widget Tcl command.
tk.deletecommand(w)
tk.call("rename", self.orig, w)
del self.widget, self.tk # Should not be needed
# if instance is deleted after close, as in Percolator.
def register(self, operation, function):
'''Return OriginalCommand(operation) after registering function.
Registration adds an operation: function pair to ._operations.
It also adds a widget function attribute that masks the Tkinter
class instance method. Method masking operates independently
from command dispatch.
If a second function is registered for the same operation, the
first function is replaced in both places.
'''
self._operations[operation] = function
setattr(self.widget, operation, function)
return OriginalCommand(self, operation)
def unregister(self, operation):
'''Return the function for the operation, or None.
Deleting the instance attribute unmasks the class attribute.
'''
if operation in self._operations:
function = self._operations[operation]
del self._operations[operation]
try:
delattr(self.widget, operation)
except AttributeError:
pass
return function
else:
return None
def dispatch(self, operation, *args):
'''Callback from Tcl which runs when the widget is referenced.
If an operation has been registered in self._operations, apply the
associated function to the args passed into Tcl. Otherwise, pass the
operation through to Tk via the original Tcl function.
Note that if a registered function is called, the operation is not
passed through to Tk. Apply the function returned by self.register()
to *args to accomplish that. For an example, see ColorDelegator.py.
'''
m = self._operations.get(operation)
try:
if m:
return m(*args)
else:
return self.tk.call((self.orig, operation) + args)
except TclError:
return ""
class OriginalCommand:
'''Callable for original tk command that has been redirected.
Returned by .register; can be used in the function registered.
redir = WidgetRedirector(text)
def my_insert(*args):
print("insert", args)
original_insert(*args)
original_insert = redir.register("insert", my_insert)
'''
def __init__(self, redir, operation):
'''Create .tk_call and .orig_and_operation for .__call__ method.
.redir and .operation store the input args for __repr__.
.tk and .orig copy attributes of .redir (probably not needed).
'''
self.redir = redir
self.operation = operation
self.tk = redir.tk # redundant with self.redir
self.orig = redir.orig # redundant with self.redir
# These two could be deleted after checking recipient code.
self.tk_call = redir.tk.call
self.orig_and_operation = (redir.orig, operation)
def __repr__(self):
return "OriginalCommand(%r, %r)" % (self.redir, self.operation)
def __call__(self, *args):
return self.tk_call(self.orig_and_operation + args)
def _widget_redirector(parent): # htest #
from Tkinter import Tk, Text
import re
root = Tk()
root.title("Test WidgetRedirector")
width, height, x, y = list(map(int, re.split('[x+]', parent.geometry())))
root.geometry("+%d+%d"%(x, y + 150))
text = Text(root)
text.pack()
text.focus_set()
redir = WidgetRedirector(text)
def my_insert(*args):
print("insert", args)
original_insert(*args)
original_insert = redir.register("insert", my_insert)
root.mainloop()
if __name__ == "__main__":
import unittest
unittest.main('idlelib.idle_test.test_widgetredir',
verbosity=2, exit=False)
from idlelib.idle_test.htest import run
run(_widget_redirector)

90
Lib/idlelib/WindowList.py Normal file
View File

@@ -0,0 +1,90 @@
from Tkinter import *
class WindowList:
def __init__(self):
self.dict = {}
self.callbacks = []
def add(self, window):
window.after_idle(self.call_callbacks)
self.dict[str(window)] = window
def delete(self, window):
try:
del self.dict[str(window)]
except KeyError:
# Sometimes, destroy() is called twice
pass
self.call_callbacks()
def add_windows_to_menu(self, menu):
list = []
for key in self.dict.keys():
window = self.dict[key]
try:
title = window.get_title()
except TclError:
continue
list.append((title, window))
list.sort()
for title, window in list:
menu.add_command(label=title, command=window.wakeup)
def register_callback(self, callback):
self.callbacks.append(callback)
def unregister_callback(self, callback):
try:
self.callbacks.remove(callback)
except ValueError:
pass
def call_callbacks(self):
for callback in self.callbacks:
try:
callback()
except:
print "warning: callback failed in WindowList", \
sys.exc_type, ":", sys.exc_value
registry = WindowList()
add_windows_to_menu = registry.add_windows_to_menu
register_callback = registry.register_callback
unregister_callback = registry.unregister_callback
class ListedToplevel(Toplevel):
def __init__(self, master, **kw):
Toplevel.__init__(self, master, kw)
registry.add(self)
self.focused_widget = self
def destroy(self):
registry.delete(self)
Toplevel.destroy(self)
# If this is Idle's last window then quit the mainloop
# (Needed for clean exit on Windows 98)
if not registry.dict:
self.quit()
def update_windowlist_registry(self, window):
registry.call_callbacks()
def get_title(self):
# Subclass can override
return self.wm_title()
def wakeup(self):
try:
if self.wm_state() == "iconic":
self.wm_withdraw()
self.wm_deiconify()
self.tkraise()
self.focused_widget.focus_set()
except TclError:
# This can happen when the window menu was torn off.
# Simply ignore it.
pass

51
Lib/idlelib/ZoomHeight.py Normal file
View File

@@ -0,0 +1,51 @@
# Sample extension: zoom a window to maximum height
import re
import sys
from idlelib import macosxSupport
class ZoomHeight:
menudefs = [
('windows', [
('_Zoom Height', '<<zoom-height>>'),
])
]
def __init__(self, editwin):
self.editwin = editwin
def zoom_height_event(self, event):
top = self.editwin.top
zoom_height(top)
def zoom_height(top):
geom = top.wm_geometry()
m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
if not m:
top.bell()
return
width, height, x, y = map(int, m.groups())
newheight = top.winfo_screenheight()
if sys.platform == 'win32':
newy = 0
newheight = newheight - 72
elif macosxSupport.isAquaTk():
# The '88' below is a magic number that avoids placing the bottom
# of the window below the panel on my machine. I don't know how
# to calculate the correct value for this with tkinter.
newy = 22
newheight = newheight - newy - 88
else:
#newy = 24
newy = 0
#newheight = newheight - 96
newheight = newheight - 88
if height >= newheight:
newgeom = ""
else:
newgeom = "%dx%d+%d+%d" % (width, newheight, x, newy)
top.wm_geometry(newgeom)

8
Lib/idlelib/__init__.py Normal file
View File

@@ -0,0 +1,8 @@
"""The idlelib package implements the Idle application.
Idle includes an interactive shell and editor.
Use the files named idle.* to start Idle.
The other files are private implementations. Their details are subject
to change. See PEP 434 for more. Import them at your own risk.
"""

151
Lib/idlelib/aboutDialog.py Normal file
View File

@@ -0,0 +1,151 @@
"""About Dialog for IDLE
"""
import os
from sys import version
from Tkinter import *
from idlelib import textView
class AboutDialog(Toplevel):
"""Modal about dialog for idle
"""
def __init__(self, parent, title, _htest=False):
"""
_htest - bool, change box location when running htest
"""
Toplevel.__init__(self, parent)
self.configure(borderwidth=5)
# place dialog below parent if running htest
self.geometry("+%d+%d" % (
parent.winfo_rootx()+30,
parent.winfo_rooty()+(30 if not _htest else 100)))
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()
self.bind('<Return>',self.Ok) #dismiss dialog
self.bind('<Escape>',self.Ok) #dismiss dialog
self.wait_window()
def CreateWidgets(self):
release = version[:version.index(' ')]
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='Close',
command=self.Ok)
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='IDLE', 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=1, column=1, sticky=W, rowspan=2,
# padx=0, pady=3)
byline = "Python's Integrated DeveLopment Environment" + 5*'\n'
labelDesc = Label(frameBg, text=byline, justify=LEFT,
fg=self.fg, bg=self.bg)
labelDesc.grid(row=2, column=0, sticky=W, columnspan=3, padx=10, pady=5)
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='https://docs.python.org/' +
version[:3] + '/library/idle.html',
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: ' +
release, fg=self.fg, bg=self.bg)
labelPythonVer.grid(row=9, column=0, sticky=W, padx=10, pady=0)
tkVer = self.tk.call('info', 'patchlevel')
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)
py_button_f = Frame(frameBg, bg=self.bg)
py_button_f.grid(row=10, column=0, columnspan=2, sticky=NSEW)
buttonLicense = Button(py_button_f, text='License', width=8,
highlightbackground=self.bg,
command=self.ShowLicense)
buttonLicense.pack(side=LEFT, padx=10, pady=10)
buttonCopyright = Button(py_button_f, text='Copyright', width=8,
highlightbackground=self.bg,
command=self.ShowCopyright)
buttonCopyright.pack(side=LEFT, padx=10, pady=10)
buttonCredits = Button(py_button_f, text='Credits', width=8,
highlightbackground=self.bg,
command=self.ShowPythonCredits)
buttonCredits.pack(side=LEFT, padx=10, pady=10)
Frame(frameBg, borderwidth=1, relief=SUNKEN,
height=2, bg=self.bg).grid(row=11, column=0, sticky=EW,
columnspan=3, padx=5, pady=5)
idle_v = Label(frameBg, text='IDLE version: ' + release,
fg=self.fg, bg=self.bg)
idle_v.grid(row=12, column=0, sticky=W, padx=10, pady=0)
idle_button_f = Frame(frameBg, bg=self.bg)
idle_button_f.grid(row=13, column=0, columnspan=3, sticky=NSEW)
idle_about_b = Button(idle_button_f, text='README', width=8,
highlightbackground=self.bg,
command=self.ShowIDLEAbout)
idle_about_b.pack(side=LEFT, padx=10, pady=10)
idle_news_b = Button(idle_button_f, text='NEWS', width=8,
highlightbackground=self.bg,
command=self.ShowIDLENEWS)
idle_news_b.pack(side=LEFT, padx=10, pady=10)
idle_credits_b = Button(idle_button_f, text='Credits', width=8,
highlightbackground=self.bg,
command=self.ShowIDLECredits)
idle_credits_b.pack(side=LEFT, padx=10, pady=10)
# License, et all, are of type _sitebuiltins._Printer
def ShowLicense(self):
self.display_printer_text('About - License', license)
def ShowCopyright(self):
self.display_printer_text('About - Copyright', copyright)
def ShowPythonCredits(self):
self.display_printer_text('About - Python Credits', credits)
# Encode CREDITS.txt to utf-8 for proper version of Loewis.
# Specify others as ascii until need utf-8, so catch errors.
def ShowIDLECredits(self):
self.display_file_text('About - Credits', 'CREDITS.txt', 'utf-8')
def ShowIDLEAbout(self):
self.display_file_text('About - Readme', 'README.txt', 'ascii')
def ShowIDLENEWS(self):
self.display_file_text('About - NEWS', 'NEWS.txt', 'utf-8')
def display_printer_text(self, title, printer):
printer._Printer__setup()
text = '\n'.join(printer._Printer__lines)
textView.view_text(self, title, text)
def display_file_text(self, title, filename, encoding=None):
fn = os.path.join(os.path.abspath(os.path.dirname(__file__)), filename)
textView.view_file(self, title, fn, encoding)
def Ok(self, event=None):
self.grab_release()
self.destroy()
if __name__ == '__main__':
import unittest
unittest.main('idlelib.idle_test.test_helpabout', verbosity=2, exit=False)
from idlelib.idle_test.htest import run
run(AboutDialog)

View File

@@ -0,0 +1,99 @@
# config-extensions.def
#
# IDLE reads several config files to determine user preferences. This
# file is the default configuration 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 (=True to
# enable the extension, =False to disable it), it may contain
# 'enable_editor' or 'enable_shell' items, to apply it only to editor ir
# shell windows, and may also contain any other general configuration
# items for the extension. Other True/False values will also be
# recognized as boolean by the Extension Configuration dialog.
#
# Each extension must define at least one section named
# ExtensionName_bindings or ExtensionName_cfgBindings. If present,
# ExtensionName_bindings defines virtual event bindings for the
# extension that are not user re-configurable. If present,
# ExtensionName_cfgBindings defines virtual event bindings for the
# extension that may be sensibly re-configured.
#
# If there are no keybindings for a menus' virtual events, include lines
# like <<toggle-code-context>>= (See [CodeContext], below.)
#
# Currently it is necessary to manually modify this file to change
# extension key bindings and default values. To customize, create
# ~/.idlerc/config-extensions.cfg and append the appropriate customized
# section(s). Those sections will override the defaults in this file.
#
# Note: If a keybinding is already in use when the extension is loaded,
# the extension's virtual event's keybinding will be set to ''.
#
# See config-keys.def for notes on specifying keys and extend.txt for
# information on creating IDLE extensions.
[AutoComplete]
enable=True
popupwait=2000
[AutoComplete_cfgBindings]
force-open-completions=<Control-Key-space>
[AutoComplete_bindings]
autocomplete=<Key-Tab>
try-open-completions=<KeyRelease-period> <KeyRelease-slash> <KeyRelease-backslash>
[AutoExpand]
enable=True
[AutoExpand_cfgBindings]
expand-word=<Alt-Key-slash>
[CallTips]
enable=True
[CallTips_cfgBindings]
force-open-calltip=<Control-Key-backslash>
[CallTips_bindings]
try-open-calltip=<KeyRelease-parenleft>
refresh-calltip=<KeyRelease-parenright> <KeyRelease-0>
[CodeContext]
enable=True
enable_shell=False
numlines=3
visible=False
bgcolor=LightGray
fgcolor=Black
[CodeContext_bindings]
toggle-code-context=
[FormatParagraph]
enable=True
max-width=72
[FormatParagraph_cfgBindings]
format-paragraph=<Alt-Key-q>
[ParenMatch]
enable=True
style= expression
flash-delay= 500
bell=True
[ParenMatch_cfgBindings]
flash-paren=<Control-Key-0>
[ParenMatch_bindings]
paren-closed=<KeyRelease-parenright> <KeyRelease-bracketright> <KeyRelease-braceright>
[RstripExtension]
enable=True
enable_shell=False
enable_editor=True
[ScriptBinding]
enable=True
enable_shell=False
enable_editor=True
[ScriptBinding_cfgBindings]
run-module=<Key-F5>
check-module=<Alt-Key-x>
[ZoomHeight]
enable=True
[ZoomHeight_cfgBindings]
zoom-height=<Alt-Key-2>

View File

@@ -0,0 +1,93 @@
# 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
builtin-foreground= #900090
builtin-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, restart IDLE)
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
builtin-foreground= #900090
builtin-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, restart IDLE)
cursor-foreground= black
#shell window
stdout-foreground= blue
stdout-background= #ffffff
stderr-foreground= red
stderr-background= #ffffff
console-foreground= #770000
console-background= #ffffff
[IDLE Dark]
comment-foreground = #dd0000
console-foreground = #ff4d4d
error-foreground = #FFFFFF
hilite-background = #7e7e7e
string-foreground = #02ff02
stderr-background = #002240
stderr-foreground = #ffb3b3
console-background = #002240
hit-background = #fbfbfb
string-background = #002240
normal-background = #002240
hilite-foreground = #FFFFFF
keyword-foreground = #ff8000
error-background = #c86464
keyword-background = #002240
builtin-background = #002240
break-background = #808000
builtin-foreground = #ff00ff
definition-foreground = #5e5eff
stdout-foreground = #c2d1fa
definition-background = #002240
normal-foreground = #FFFFFF
cursor-foreground = #ffffff
stdout-background = #002240
hit-foreground = #002240
comment-background = #002240
break-foreground = #FFFFFF

214
Lib/idlelib/config-keys.def Normal file
View File

@@ -0,0 +1,214 @@
# 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 alternatives, 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> <Control-Key-C>
cut=<Control-Key-x> <Control-Key-X>
paste=<Control-Key-v> <Control-Key-V>
beginning-of-line= <Key-Home>
center-insert=<Control-Key-l> <Control-Key-L>
close-all-windows=<Control-Key-q> <Control-Key-Q>
close-window=<Alt-Key-F4> <Meta-Key-F4>
do-nothing=<Control-Key-F12>
end-of-file=<Control-Key-d> <Control-Key-D>
python-docs=<Key-F1>
python-context-help=<Shift-Key-F1>
history-next=<Alt-Key-n> <Meta-Key-n> <Alt-Key-N> <Meta-Key-N>
history-previous=<Alt-Key-p> <Meta-Key-p> <Alt-Key-P> <Meta-Key-P>
interrupt-execution=<Control-Key-c> <Control-Key-C>
view-restart=<Key-F6>
restart-shell=<Control-Key-F6>
open-class-browser=<Alt-Key-c> <Meta-Key-c> <Alt-Key-C> <Meta-Key-C>
open-module=<Alt-Key-m> <Meta-Key-m> <Alt-Key-M> <Meta-Key-M>
open-new-window=<Control-Key-n> <Control-Key-N>
open-window-from-file=<Control-Key-o> <Control-Key-O>
plain-newline-and-indent=<Control-Key-j> <Control-Key-J>
print-window=<Control-Key-p> <Control-Key-P>
redo=<Control-Shift-Key-Z> <Control-Shift-Key-z>
remove-selection=<Key-Escape>
save-copy-of-window-as-file=<Alt-Shift-Key-S> <Alt-Shift-Key-s>
save-window-as-file=<Control-Shift-Key-S> <Control-Shift-Key-s>
save-window=<Control-Key-s> <Control-Key-S>
select-all=<Control-Key-a> <Control-Key-A>
toggle-auto-coloring=<Control-Key-slash>
undo=<Control-Key-z> <Control-Key-Z>
find=<Control-Key-f> <Control-Key-F>
find-again=<Control-Key-g> <Key-F3> <Control-Key-G>
find-in-files=<Alt-Key-F3> <Meta-Key-F3>
find-selection=<Control-Key-F3>
replace=<Control-Key-h> <Control-Key-H>
goto-line=<Alt-Key-g> <Meta-Key-g> <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> <Alt-Key-T> <Meta-Key-T>
change-indentwidth=<Alt-Key-u> <Meta-Key-u> <Alt-Key-U> <Meta-Key-U>
del-word-left=<Control-Key-BackSpace>
del-word-right=<Control-Key-Delete>
[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>
del-word-left=<Alt-Key-BackSpace>
del-word-right=<Alt-Key-d>
[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>
del-word-left=<Control-Key-BackSpace>
del-word-right=<Control-Key-Delete>
[IDLE Classic OSX]
toggle-tabs = <Control-Key-t>
interrupt-execution = <Control-Key-c>
untabify-region = <Control-Key-6>
remove-selection = <Key-Escape>
print-window = <Command-Key-p>
replace = <Command-Key-r>
goto-line = <Command-Key-j>
plain-newline-and-indent = <Control-Key-j>
history-previous = <Control-Key-p>
beginning-of-line = <Control-Key-Left>
end-of-line = <Control-Key-Right>
comment-region = <Control-Key-3>
redo = <Shift-Command-Key-Z>
close-window = <Command-Key-w>
restart-shell = <Control-Key-F6>
save-window-as-file = <Shift-Command-Key-S>
close-all-windows = <Command-Key-q>
view-restart = <Key-F6>
tabify-region = <Control-Key-5>
find-again = <Command-Key-g> <Key-F3>
find = <Command-Key-f>
toggle-auto-coloring = <Control-Key-slash>
select-all = <Command-Key-a>
smart-backspace = <Key-BackSpace>
change-indentwidth = <Control-Key-u>
do-nothing = <Control-Key-F12>
smart-indent = <Key-Tab>
center-insert = <Control-Key-l>
history-next = <Control-Key-n>
del-word-right = <Option-Key-Delete>
undo = <Command-Key-z>
save-window = <Command-Key-s>
uncomment-region = <Control-Key-4>
cut = <Command-Key-x>
find-in-files = <Command-Key-F3>
dedent-region = <Command-Key-bracketleft>
copy = <Command-Key-c>
paste = <Command-Key-v>
indent-region = <Command-Key-bracketright>
del-word-left = <Option-Key-BackSpace> <Option-Command-Key-BackSpace>
newline-and-indent = <Key-Return> <Key-KP_Enter>
end-of-file = <Control-Key-d>
open-class-browser = <Command-Key-b>
open-new-window = <Command-Key-n>
open-module = <Command-Key-m>
find-selection = <Shift-Command-Key-F3>
python-context-help = <Shift-Key-F1>
save-copy-of-window-as-file = <Option-Command-Key-s>
open-window-from-file = <Command-Key-o>
python-docs = <Key-F1>

View File

@@ -0,0 +1,78 @@
# 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/config-main.cfg the user general config file
# ~/.idlerc/config-extensions.cfg the user extension config file
# ~/.idlerc/config-highlight.cfg the user highlighting config file
# ~/.idlerc/config-keys.cfg the user keybinding config file
#
# On Windows2000 and Windows XP the .idlerc directory is at
# Documents and Settings\<username>\.idlerc
#
# On Windows98 it is at c:\.idlerc
#
# 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
autosave= 0
print-command-posix=lpr %s
print-command-win=start /min notepad /p %s
delete-exitfunc= 1
[EditorWindow]
width= 80
height= 40
font= TkFixedFont
font-size= 10
font-bold= 0
encoding= none
[Indent]
use-spaces= 1
num-spaces= 4
[Theme]
default= 1
name= IDLE Classic
name2=
# name2 set in user config-main.cfg for themes added after 2015 Oct 1
[Keys]
default= 1
name= IDLE Classic Windows
[History]
cyclic=1
[HelpFiles]

1457
Lib/idlelib/configDialog.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,772 @@
"""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.
"""
# TODOs added Oct 2014, tjr
from __future__ import print_function
import os
import sys
from ConfigParser import ConfigParser
from Tkinter import TkVersion
from tkFont import Font, nametofont
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, raw=False):
"""
Get an option value for given section/option or return default.
If type is specified, return as type.
"""
# TODO Use default as fallback, at least if not None
# Should also print Warning(file, section, option).
# Currently may raise ValueError
if not self.has_option(section, option):
return default
if type == 'bool':
return self.getboolean(section, option)
elif type == 'int':
return self.getint(section, option)
else:
return self.get(section, option, raw=raw)
def GetOptionList(self, section):
"Return a list of options for given section, else []."
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):
"Return True if no sections after removing empty sections."
self.RemoveEmptySections()
return not self.sections()
def RemoveOption(self, section, option):
"""Return True if option is removed from section, else False.
False if either section does not exist or did not have option.
"""
if self.has_section(section):
return self.remove_option(section, option)
return False
def SetOption(self, section, option, value):
"""Return True if option is added or changed to value, else False.
Add section if required. False means option already had value.
"""
if self.has_option(section, option):
if self.get(section, option) == value:
return False
else:
self.set(section, option, value)
return True
else:
if not self.has_section(section):
self.add_section(section)
self.set(section, option, value)
return True
def RemoveFile(self):
"Remove user config file self.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():
fname = self.file
try:
cfgFile = open(fname, 'w')
except IOError:
os.unlink(fname)
cfgFile = open(fname, 'w')
with cfgFile:
self.write(cfgFile)
else:
self.RemoveFile()
class IdleConf:
"""Hold config parsers for all idle config files in singleton instance.
Default config files, self.defaultCfg --
for config_type in self.config_types:
(idle install dir)/config-{config-type}.def
User config files, self.userCfg --
for config_type in self.config_types:
(user home dir)/.idlerc/config-{config-type}.cfg
"""
def __init__(self):
self.config_types = ('main', 'extensions', 'highlight', 'keys')
self.defaultCfg = {}
self.userCfg = {}
self.cfg = {} # TODO use to select userCfg vs defaultCfg
self.CreateConfigHandlers()
self.LoadCfgFiles()
def CreateConfigHandlers(self):
"Populate default and user config parser dictionaries."
#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()
defCfgFiles = {}
usrCfgFiles = {}
# TODO eliminate these temporaries by combining loops
for cfgType in self.config_types: #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 self.config_types: #create config parsers
self.defaultCfg[cfgType] = IdleConfParser(defCfgFiles[cfgType])
self.userCfg[cfgType] = IdleUserConfParser(usrCfgFiles[cfgType])
def GetUserCfgDir(self):
"""Return a filesystem directory for storing user config files.
Creates it if required.
"""
cfgDir = '.idlerc'
userDir = os.path.expanduser('~')
if userDir != '~': # expanduser() found user home dir
if not os.path.exists(userDir):
warn = ('\n Warning: os.path.expanduser("~") points to\n ' +
userDir + ',\n but the path does not exist.')
try:
print(warn, file=sys.stderr)
except IOError:
pass
userDir = '~'
if userDir == "~": # still no path to home!
# traditionally IDLE has defaulted to os.getcwd(), is this adequate?
userDir = os.getcwd()
userDir = os.path.join(userDir, cfgDir)
if not os.path.exists(userDir):
try:
os.mkdir(userDir)
except (OSError, IOError):
warn = ('\n Warning: unable to create user config directory\n' +
userDir + '\n Check path and permissions.\n Exiting!\n')
print(warn, file=sys.stderr)
raise SystemExit
# TODO continue without userDIr instead of exit
return userDir
def GetOption(self, configType, section, option, default=None, type=None,
warn_on_default=True, raw=False):
"""Return a value for configType section option, or default.
If type is not None, return a value of that type. Also pass raw
to the config parser. First try to return a valid value
(including type) from a user configuration. If that fails, try
the default configuration. If that fails, return default, with a
default of None.
Warn if either user or default configurations have an invalid value.
Warn if default is returned and warn_on_default is True.
"""
try:
if self.userCfg[configType].has_option(section, option):
return self.userCfg[configType].Get(section, option,
type=type, raw=raw)
except ValueError:
warning = ('\n Warning: configHandler.py - IdleConf.GetOption -\n'
' invalid %r value for configuration option %r\n'
' from section %r: %r' %
(type, option, section,
self.userCfg[configType].Get(section, option, raw=raw)))
try:
print(warning, file=sys.stderr)
except IOError:
pass
try:
if self.defaultCfg[configType].has_option(section,option):
return self.defaultCfg[configType].Get(
section, option, type=type, raw=raw)
except ValueError:
pass
#returning default, print warning
if warn_on_default:
warning = ('\n Warning: configHandler.py - IdleConf.GetOption -\n'
' problem retrieving configuration option %r\n'
' from section %r.\n'
' returning default value: %r' %
(option, section, default))
try:
print(warning, file=sys.stderr)
except IOError:
pass
return default
def SetOption(self, configType, section, option, value):
"""Set section option to value in user config file."""
self.userCfg[configType].SetOption(section, option, value)
def GetSectionList(self, configSet, configType):
"""Return sections for configSet configType configuration.
configSet must be either 'user' or 'default'
configType must be in self.config_types.
"""
if not (configType in self.config_types):
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 theme element highlight color(s).
fgBg - string ('fg' or 'bg') or None.
If None, return a dictionary containing fg and bg colors with
keys 'foreground' and 'background'. Otherwise, only return
fg or bg color, as specified. Colors are intended to be
appropriate for passing to Tkinter in, e.g., a tag_config call).
"""
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 colors
return highlight
else: # Return specified color only
if fgBg == 'fg':
return highlight["foreground"]
if fgBg == 'bg':
return highlight["background"]
else:
raise InvalidFgBg('Invalid fgBg specified')
def GetThemeDict(self, type, themeName):
"""Return {option:value} dict for elements in themeName.
type - string, 'default' or 'user' theme type
themeName - string, theme name
Values are loaded over ultimate 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')
# Provide foreground and background colors for each theme
# element (other than cursor) even though some values are not
# yet used by idle, to allow for their use in the future.
# Default values are generally black and white.
# TODO copy theme from a class attribute.
theme ={'normal-foreground':'#000000',
'normal-background':'#ffffff',
'keyword-foreground':'#000000',
'keyword-background':'#ffffff',
'builtin-foreground':'#000000',
'builtin-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:
if not cfgParser.has_option(themeName, element):
# Print warning that will return a default color
warning = ('\n Warning: configHandler.IdleConf.GetThemeDict'
' -\n problem retrieving theme element %r'
'\n from theme %r.\n'
' returning default color: %r' %
(element, themeName, theme[element]))
try:
print(warning, file=sys.stderr)
except IOError:
pass
theme[element] = cfgParser.Get(
themeName, element, default=theme[element])
return theme
def CurrentTheme(self):
"""Return the name of the currently active text color theme.
idlelib.config-main.def includes this section
[Theme]
default= 1
name= IDLE Classic
name2=
# name2 set in user config-main.cfg for themes added after 2015 Oct 1
Item name2 is needed because setting name to a new builtin
causes older IDLEs to display multiple error messages or quit.
See https://bugs.python.org/issue25313.
When default = True, name2 takes precedence over name,
while older IDLEs will just use name.
"""
default = self.GetOption('main', 'Theme', 'default',
type='bool', default=True)
if default:
theme = self.GetOption('main', 'Theme', 'name2', default='')
if default and not theme or not default:
theme = self.GetOption('main', 'Theme', 'name', default='')
source = self.defaultCfg if default else self.userCfg
if source['highlight'].has_section(theme):
return theme
else:
return "IDLE Classic"
def CurrentKeys(self):
"Return the name of the currently active key set."
return self.GetOption('main', 'Keys', 'name', default='')
def GetExtensions(self, active_only=True, editor_only=False, shell_only=False):
"""Return extensions in default and user config-extensions files.
If active_only True, only return active (enabled) extensions
and optionally only editor or shell extensions.
If active_only False, return all 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 active_only:
activeExtns = []
for extn in extns:
if self.GetOption('extensions', extn, 'enable', default=True,
type='bool'):
#the extension is enabled
if editor_only or shell_only: # TODO if both, contradictory
if editor_only:
option = "enable_editor"
else:
option = "enable_shell"
if self.GetOption('extensions', extn,option,
default=True, type='bool',
warn_on_default=False):
activeExtns.append(extn)
else:
activeExtns.append(extn)
return activeExtns
else:
return extns
def RemoveKeyBindNames(self, extnNameList):
"Return extnNameList with keybinding section names removed."
# TODO Easier to return filtered copy with list comp
names = extnNameList
kbNameIndicies = []
for name in names:
if name.endswith(('_bindings', '_cfgBindings')):
kbNameIndicies.append(names.index(name))
kbNameIndicies.sort(reverse=True)
for index in kbNameIndicies: #delete each keybinding section name
del(names[index])
return names
def GetExtnNameForEvent(self, virtualEvent):
"""Return the name of the extension binding virtualEvent, or None.
virtualEvent - string, name of the virtual event to test for,
without the enclosing '<< >>'
"""
extName = None
vEvent = '<<' + virtualEvent + '>>'
for extn in self.GetExtensions(active_only=0):
for event in self.GetExtensionKeys(extn):
if event == vEvent:
extName = extn # TODO return here?
return extName
def GetExtensionKeys(self, extensionName):
"""Return dict: {configurable extensionName event : active keybinding}.
Events come from default config extension_cfgBindings section.
Keybindings come from GetCurrentKeySet() active key dict,
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):
"""Return dict {configurable extensionName event : keybinding list}.
Events come from default config extension_cfgBindings section.
Keybindings list come from the splitting of GetOption, which
tries user config before default config.
"""
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):
"""Return dict {extensionName event : active or defined keybinding}.
Augment self.GetExtensionKeys(extensionName) with mapping of non-
configurable events (from default config) to GetOption splits,
as in self.__GetRawExtensionKeys.
"""
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):
"""Return the keybinding list for keySetName eventStr.
keySetName - name of key binding set (config-keys section).
eventStr - virtual event, including brackets, as in '<<event>>'.
"""
eventName = eventStr[2:-2] #trim off the angle brackets
binding = self.GetOption('keys', keySetName, eventName, default='').split()
return binding
def GetCurrentKeySet(self):
"Return CurrentKeys with 'darwin' modifications."
result = self.GetKeySet(self.CurrentKeys())
if sys.platform == "darwin":
# OS X Tk variants do not support the "Alt" keyboard modifier.
# So replace all keybingings that use "Alt" with ones that
# use the "Option" keyboard modifier.
# TODO (Ned?): the "Option" modifier does not work properly for
# Cocoa Tk and XQuartz Tk so we should not use it
# in default OS X KeySets.
for k, v in result.items():
v2 = [ x.replace('<Alt-', '<Option-') for x in v ]
if v != v2:
result[k] = v2
return result
def GetKeySet(self, keySetName):
"""Return event-key dict for keySetName core plus active extensions.
If a binding defined in an extension is already in use, the
extension binding is disabled by being set to ''
"""
keySet = self.GetCoreKeys(keySetName)
activeExtns = self.GetExtensions(active_only=1)
for extn in activeExtns:
extKeys = self.__GetRawExtensionKeys(extn)
if extKeys: #the extension defines keybindings
for event in extKeys:
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):
"""Return True if the virtual event is one of the core idle key events.
virtualEvent - string, name of the virtual event to test for,
without the enclosing '<< >>'
"""
return ('<<'+virtualEvent+'>>') in self.GetCoreKeys()
# TODO make keyBindins a file or class attribute used for test above
# and copied in function below
def GetCoreKeys(self, keySetName=None):
"""Return dict of core virtual-key keybindings for keySetName.
The default keySetName None corresponds to the keyBindings base
dict. If keySetName is not None, bindings 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>'],
'<<del-word-left>>': ['<Control-Key-BackSpace>'],
'<<del-word-right>>': ['<Control-Key-Delete>']
}
if keySetName:
for event in keyBindings:
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 %r'
'\n from key set %r.\n'
' returning default value: %r' %
(event, keySetName, keyBindings[event]))
try:
print(warning, file=sys.stderr)
except IOError:
pass
return keyBindings
def GetExtraHelpSourceList(self, configSet):
"""Return 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=value.split(';')
menuItem=value[0].strip()
helpPath=value[1].strip()
if menuItem and helpPath: #neither are empty strings
helpSources.append( (menuItem,helpPath,option) )
helpSources.sort(key=lambda x: int(x[2]))
return helpSources
def GetAllExtraHelpSourcesList(self):
"""Return a list of the details of all additional help sources.
Tuples in the list are those of GetExtraHelpSourceList.
"""
allHelpSources = (self.GetExtraHelpSourceList('default') +
self.GetExtraHelpSourceList('user') )
return allHelpSources
def GetFont(self, root, configType, section):
"""Retrieve a font from configuration (font, font-size, font-bold)
Intercept the special value 'TkFixedFont' and substitute
the actual font, factoring in some tweaks if needed for
appearance sakes.
The 'root' parameter can normally be any valid Tkinter widget.
Return a tuple (family, size, weight) suitable for passing
to tkinter.Font
"""
family = self.GetOption(configType, section, 'font', default='courier')
size = self.GetOption(configType, section, 'font-size', type='int',
default='10')
bold = self.GetOption(configType, section, 'font-bold', default=0,
type='bool')
if (family == 'TkFixedFont'):
if TkVersion < 8.5:
family = 'Courier'
else:
f = Font(name='TkFixedFont', exists=True, root=root)
actualFont = Font.actual(f)
family = actualFont['family']
size = actualFont['size']
if size <= 0:
size = 10 # if font in pixels, ignore actual size
bold = actualFont['weight']=='bold'
return (family, size, 'bold' if bold else 'normal')
def LoadCfgFiles(self):
"Load all configuration files."
for key in self.defaultCfg:
self.defaultCfg[key].Load()
self.userCfg[key].Load() #same keys
def SaveUserCfgFiles(self):
"Write all loaded user configuration files to disk."
for key in self.userCfg:
self.userCfg[key].Save()
idleConf = IdleConf()
# TODO Revise test output, write expanded unittest
#
if __name__ == '__main__':
from zlib import crc32
line, crc = 0, 0
def sprint(obj):
global line, crc
txt = str(obj)
line += 1
crc = crc32(txt.encode(encoding='utf-8'), crc)
print(txt)
#print('***', line, crc, '***') # uncomment for diagnosis
def dumpCfg(cfg):
print('\n', cfg, '\n') # has variable '0xnnnnnnnn' addresses
for key in sorted(cfg.keys()):
sections = cfg[key].sections()
sprint(key)
sprint(sections)
for section in sections:
options = cfg[key].options(section)
sprint(section)
sprint(options)
for option in options:
sprint(option + ' = ' + cfg[key].Get(section, option))
dumpCfg(idleConf.defaultCfg)
dumpCfg(idleConf.userCfg)
print('\nlines = ', line, ', crc = ', crc, sep='')

View File

@@ -0,0 +1,168 @@
"Dialog to specify or edit the parameters for a user configured help source."
import os
import sys
from Tkinter import *
import tkMessageBox
import tkFileDialog
class GetHelpSourceDialog(Toplevel):
def __init__(self, parent, title, menuItem='', filePath='', _htest=False):
"""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.
_htest - bool, change box location when running htest
"""
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. below parent if running htest.
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)
if not _htest else 150)))
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[:3] == 'win':
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.', 'http')):
pass
else:
if path[:5] == 'file:':
path = path[5:]
if 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())
if sys.platform == 'darwin':
path = self.result[1]
if path.startswith(('www', 'file:', 'http:')):
pass
else:
# Mac Safari insists on using the URI form for local files
self.result = list(self.result)
self.result[1] = "file://" + path
self.grab_release()
self.destroy()
def Cancel(self, event=None):
self.result = None
self.grab_release()
self.destroy()
if __name__ == '__main__':
from idlelib.idle_test.htest import run
run(GetHelpSourceDialog)

View File

@@ -0,0 +1,95 @@
"""
Dialog that allows user to specify a new config file section name.
Used to get new highlight theme and keybinding set names.
The 'return value' for the dialog, used two placed in configDialog.py,
is the .result attribute set in the Ok and Cancel methods.
"""
from Tkinter import *
import tkMessageBox
class GetCfgSectionNameDialog(Toplevel):
def __init__(self, parent, title, message, used_names, _htest=False):
"""
message - string, informational message to display
used_names - string collection, names already in use for validity check
_htest - bool, change box location when running htest
"""
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.used_names = used_names
self.create_widgets()
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)
if not _htest else 100)
) ) #centre dialog over parent (or below htest box)
self.deiconify() #geometry set, unhide
self.wait_window()
def create_widgets(self):
self.name = StringVar(self.parent)
self.fontSize = StringVar(self.parent)
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, pady=2)
frameButtons.pack(side=BOTTOM)
self.buttonOk = Button(frameButtons, text='Ok',
width=8, command=self.Ok)
self.buttonOk.pack(side=LEFT, padx=5)
self.buttonCancel = Button(frameButtons, text='Cancel',
width=8, command=self.Cancel)
self.buttonCancel.pack(side=RIGHT, padx=5)
def name_ok(self):
''' After stripping entered name, check that it is a sensible
ConfigParser file section name. Return it if it is, '' if not.
'''
name = self.name.get().strip()
if not name: #no name specified
tkMessageBox.showerror(title='Name Error',
message='No name specified.', parent=self)
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)
name = ''
elif name in self.used_names:
tkMessageBox.showerror(title='Name Error',
message='This name is already in use.', parent=self)
name = ''
return name
def Ok(self, event=None):
name = self.name_ok()
if name:
self.result = name
self.grab_release()
self.destroy()
def Cancel(self, event=None):
self.result = ''
self.grab_release()
self.destroy()
if __name__ == '__main__':
import unittest
unittest.main('idlelib.idle_test.test_config_name', verbosity=2, exit=False)
from idlelib.idle_test.htest import run
run(GetCfgSectionNameDialog)

View File

@@ -0,0 +1,57 @@
"""
OptionMenu widget modified to allow dynamic menu reconfiguration
and setting of highlightthickness
"""
import copy
from Tkinter import OptionMenu, _setit, StringVar, Button
class DynOptionMenu(OptionMenu):
"""
unlike OptionMenu, our kwargs can include highlightthickness
"""
def __init__(self, master, variable, value, *values, **kwargs):
# TODO copy value instead of whole dict
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)
def _dyn_option_menu(parent): # htest #
from Tkinter import Toplevel
top = Toplevel()
top.title("Tets dynamic option menu")
top.geometry("200x100+%d+%d" % (parent.winfo_rootx() + 200,
parent.winfo_rooty() + 150))
top.focus_set()
var = StringVar(top)
var.set("Old option set") #Set the default value
dyn = DynOptionMenu(top,var, "old1","old2","old3","old4")
dyn.pack()
def update():
dyn.SetMenu(["new1","new2","new3","new4"], value="new option set")
button = Button(top, text="Change option set", command=update)
button.pack()
if __name__ == '__main__':
from idlelib.idle_test.htest import run
run(_dyn_option_menu)

83
Lib/idlelib/extend.txt Normal file
View File

@@ -0,0 +1,83 @@
Writing an IDLE extension
=========================
An IDLE extension can define new key bindings and menu entries for IDLE
edit windows. There is a simple mechanism to load extensions when IDLE
starts up and to attach them to each edit window. (It is also possible
to make other changes to IDLE, but this must be done by editing the IDLE
source code.)
The list of extensions loaded at startup time is configured by editing
the file config-extensions.def. See below for details.
An IDLE extension is defined by a class. Methods of the class define
actions that are invoked by event bindings or menu entries. Class (or
instance) variables define the bindings and menu additions; these are
automatically applied by IDLE when the extension is linked to an edit
window.
An IDLE extension class is instantiated with a single argument,
`editwin', an EditorWindow instance. The extension cannot assume much
about this argument, but it is guaranteed to have the following instance
variables:
text a Text instance (a widget)
io an IOBinding instance (more about this later)
flist the FileList instance (shared by all edit windows)
(There are a few more, but they are rarely useful.)
The extension class must not directly bind Window Manager (e.g. X) events.
Rather, it must define one or more virtual events, e.g. <<zoom-height>>, and
corresponding methods, e.g. zoom_height_event(). The virtual events will be
bound to the corresponding methods, and Window Manager events can then be bound
to the virtual events. (This indirection is done so that the key bindings can
easily be changed, and so that other sources of virtual events can exist, such
as menu entries.)
An extension can define menu entries. This is done with a class or instance
variable named menudefs; it should be a list of pairs, where each pair is a
menu name (lowercase) and a list of menu entries. Each menu entry is either
None (to insert a separator entry) or a pair of strings (menu_label,
virtual_event). Here, menu_label is the label of the menu entry, and
virtual_event is the virtual event to be generated when the entry is selected.
An underscore in the menu label is removed; the character following the
underscore is displayed underlined, to indicate the shortcut character (for
Windows).
At the moment, extensions cannot define whole new menus; they must define
entries in existing menus. Some menus are not present on some windows; such
entry definitions are then ignored, but key bindings are still applied. (This
should probably be refined in the future.)
Extensions are not required to define menu entries for all the events they
implement. (They are also not required to create keybindings, but in that
case there must be empty bindings in cofig-extensions.def)
Here is a complete example:
class ZoomHeight:
menudefs = [
('edit', [
None, # Separator
('_Zoom Height', '<<zoom-height>>'),
])
]
def __init__(self, editwin):
self.editwin = editwin
def zoom_height_event(self, event):
"...Do what you want here..."
The final piece of the puzzle is the file "config-extensions.def", which is
used to configure the loading of extensions and to establish key (or, more
generally, event) bindings to the virtual events defined in the extensions.
See the comments at the top of config-extensions.def for information. It's
currently necessary to manually modify that file to change IDLE's extension
loading or extension key bindings.
For further information on binding refer to the Tkinter Resources web page at
python.org and to the Tk Command "bind" man page.

715
Lib/idlelib/help.html Normal file
View File

@@ -0,0 +1,715 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>24.6. IDLE &mdash; Python 2.7.12 documentation</title>
<link rel="stylesheet" href="../_static/classic.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: '../',
VERSION: '2.7.12',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
HAS_SOURCE: true
};
</script>
<script type="text/javascript" src="../_static/jquery.js"></script>
<script type="text/javascript" src="../_static/underscore.js"></script>
<script type="text/javascript" src="../_static/doctools.js"></script>
<script type="text/javascript" src="../_static/sidebar.js"></script>
<link rel="search" type="application/opensearchdescription+xml"
title="Search within Python 2.7.12 documentation"
href="../_static/opensearch.xml"/>
<link rel="author" title="About these documents" href="../about.html" />
<link rel="copyright" title="Copyright" href="../copyright.html" />
<link rel="top" title="Python 2.7.12 documentation" href="../contents.html" />
<link rel="up" title="24. Graphical User Interfaces with Tk" href="tk.html" />
<link rel="next" title="24.7. Other Graphical User Interface Packages" href="othergui.html" />
<link rel="prev" title="24.5. turtle — Turtle graphics for Tk" href="turtle.html" />
<link rel="shortcut icon" type="image/png" href="../_static/py.png" />
<script type="text/javascript" src="../_static/copybutton.js"></script>
</head>
<body role="document">
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="../py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li class="right" >
<a href="othergui.html" title="24.7. Other Graphical User Interface Packages"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="turtle.html" title="24.5. turtle — Turtle graphics for Tk"
accesskey="P">previous</a> |</li>
<li><img src="../_static/py.png" alt=""
style="vertical-align: middle; margin-top: -1px"/></li>
<li><a href="https://www.python.org/">Python</a> &raquo;</li>
<li>
<a href="../index.html">Python 2.7.12 documentation</a> &raquo;
</li>
<li class="nav-item nav-item-1"><a href="index.html" >The Python Standard Library</a> &raquo;</li>
<li class="nav-item nav-item-2"><a href="tk.html" accesskey="U">24. Graphical User Interfaces with Tk</a> &raquo;</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body" role="main">
<div class="section" id="idle">
<span id="id1"></span><h1>24.6. IDLE<a class="headerlink" href="#idle" title="Permalink to this headline"></a></h1>
<p id="index-0">IDLE is Python&#8217;s Integrated Development and Learning Environment.</p>
<p>IDLE has the following features:</p>
<ul class="simple">
<li>coded in 100% pure Python, using the <code class="xref py py-mod docutils literal"><span class="pre">tkinter</span></code> GUI toolkit</li>
<li>cross-platform: works mostly the same on Windows, Unix, and Mac OS X</li>
<li>Python shell window (interactive interpreter) with colorizing
of code input, output, and error messages</li>
<li>multi-window text editor with multiple undo, Python colorizing,
smart indent, call tips, auto completion, and other features</li>
<li>search within any window, replace within editor windows, and search
through multiple files (grep)</li>
<li>debugger with persistent breakpoints, stepping, and viewing
of global and local namespaces</li>
<li>configuration, browsers, and other dialogs</li>
</ul>
<div class="section" id="menus">
<h2>24.6.1. Menus<a class="headerlink" href="#menus" title="Permalink to this headline"></a></h2>
<p>IDLE has two main window types, the Shell window and the Editor window. It is
possible to have multiple editor windows simultaneously. Output windows, such
as used for Edit / Find in Files, are a subtype of edit window. They currently
have the same top menu as Editor windows but a different default title and
context menu.</p>
<p>IDLE&#8217;s menus dynamically change based on which window is currently selected.
Each menu documented below indicates which window type it is associated with.</p>
<div class="section" id="file-menu-shell-and-editor">
<h3>24.6.1.1. File menu (Shell and Editor)<a class="headerlink" href="#file-menu-shell-and-editor" title="Permalink to this headline"></a></h3>
<dl class="docutils">
<dt>New File</dt>
<dd>Create a new file editing window.</dd>
<dt>Open...</dt>
<dd>Open an existing file with an Open dialog.</dd>
<dt>Recent Files</dt>
<dd>Open a list of recent files. Click one to open it.</dd>
<dt>Open Module...</dt>
<dd>Open an existing module (searches sys.path).</dd>
</dl>
<dl class="docutils" id="index-1">
<dt>Class Browser</dt>
<dd>Show functions, classes, and methods in the current Editor file in a
tree structure. In the shell, open a module first.</dd>
<dt>Path Browser</dt>
<dd>Show sys.path directories, modules, functions, classes and methods in a
tree structure.</dd>
<dt>Save</dt>
<dd>Save the current window to the associated file, if there is one. Windows
that have been changed since being opened or last saved have a * before
and after the window title. If there is no associated file,
do Save As instead.</dd>
<dt>Save As...</dt>
<dd>Save the current window with a Save As dialog. The file saved becomes the
new associated file for the window.</dd>
<dt>Save Copy As...</dt>
<dd>Save the current window to different file without changing the associated
file.</dd>
<dt>Print Window</dt>
<dd>Print the current window to the default printer.</dd>
<dt>Close</dt>
<dd>Close the current window (ask to save if unsaved).</dd>
<dt>Exit</dt>
<dd>Close all windows and quit IDLE (ask to save unsaved windows).</dd>
</dl>
</div>
<div class="section" id="edit-menu-shell-and-editor">
<h3>24.6.1.2. Edit menu (Shell and Editor)<a class="headerlink" href="#edit-menu-shell-and-editor" title="Permalink to this headline"></a></h3>
<dl class="docutils">
<dt>Undo</dt>
<dd>Undo the last change to the current window. A maximum of 1000 changes may
be undone.</dd>
<dt>Redo</dt>
<dd>Redo the last undone change to the current window.</dd>
<dt>Cut</dt>
<dd>Copy selection into the system-wide clipboard; then delete the selection.</dd>
<dt>Copy</dt>
<dd>Copy selection into the system-wide clipboard.</dd>
<dt>Paste</dt>
<dd>Insert contents of the system-wide clipboard into the current window.</dd>
</dl>
<p>The clipboard functions are also available in context menus.</p>
<dl class="docutils">
<dt>Select All</dt>
<dd>Select the entire contents of the current window.</dd>
<dt>Find...</dt>
<dd>Open a search dialog with many options</dd>
<dt>Find Again</dt>
<dd>Repeat the last search, if there is one.</dd>
<dt>Find Selection</dt>
<dd>Search for the currently selected string, if there is one.</dd>
<dt>Find in Files...</dt>
<dd>Open a file search dialog. Put results in a new output window.</dd>
<dt>Replace...</dt>
<dd>Open a search-and-replace dialog.</dd>
<dt>Go to Line</dt>
<dd>Move cursor to the line number requested and make that line visible.</dd>
<dt>Show Completions</dt>
<dd>Open a scrollable list allowing selection of keywords and attributes. See
Completions in the Tips sections below.</dd>
<dt>Expand Word</dt>
<dd>Expand a prefix you have typed to match a full word in the same window;
repeat to get a different expansion.</dd>
<dt>Show call tip</dt>
<dd>After an unclosed parenthesis for a function, open a small window with
function parameter hints.</dd>
<dt>Show surrounding parens</dt>
<dd>Highlight the surrounding parenthesis.</dd>
</dl>
</div>
<div class="section" id="format-menu-editor-window-only">
<h3>24.6.1.3. Format menu (Editor window only)<a class="headerlink" href="#format-menu-editor-window-only" title="Permalink to this headline"></a></h3>
<dl class="docutils">
<dt>Indent Region</dt>
<dd>Shift selected lines right by the indent width (default 4 spaces).</dd>
<dt>Dedent Region</dt>
<dd>Shift selected lines left by the indent width (default 4 spaces).</dd>
<dt>Comment Out Region</dt>
<dd>Insert ## in front of selected lines.</dd>
<dt>Uncomment Region</dt>
<dd>Remove leading # or ## from selected lines.</dd>
<dt>Tabify Region</dt>
<dd>Turn <em>leading</em> stretches of spaces into tabs. (Note: We recommend using
4 space blocks to indent Python code.)</dd>
<dt>Untabify Region</dt>
<dd>Turn <em>all</em> tabs into the correct number of spaces.</dd>
<dt>Toggle Tabs</dt>
<dd>Open a dialog to switch between indenting with spaces and tabs.</dd>
<dt>New Indent Width</dt>
<dd>Open a dialog to change indent width. The accepted default by the Python
community is 4 spaces.</dd>
<dt>Format Paragraph</dt>
<dd>Reformat the current blank-line-delimited paragraph in comment block or
multiline string or selected line in a string. All lines in the
paragraph will be formatted to less than N columns, where N defaults to 72.</dd>
<dt>Strip trailing whitespace</dt>
<dd>Remove any space characters after the last non-space character of a line.</dd>
</dl>
</div>
<div class="section" id="run-menu-editor-window-only">
<span id="index-2"></span><h3>24.6.1.4. Run menu (Editor window only)<a class="headerlink" href="#run-menu-editor-window-only" title="Permalink to this headline"></a></h3>
<dl class="docutils">
<dt>Python Shell</dt>
<dd>Open or wake up the Python Shell window.</dd>
<dt>Check Module</dt>
<dd>Check the syntax of the module currently open in the Editor window. If the
module has not been saved IDLE will either prompt the user to save or
autosave, as selected in the General tab of the Idle Settings dialog. If
there is a syntax error, the approximate location is indicated in the
Editor window.</dd>
<dt>Run Module</dt>
<dd>Do Check Module (above). If no error, restart the shell to clean the
environment, then execute the module. Output is displayed in the Shell
window. Note that output requires use of <code class="docutils literal"><span class="pre">print</span></code> or <code class="docutils literal"><span class="pre">write</span></code>.
When execution is complete, the Shell retains focus and displays a prompt.
At this point, one may interactively explore the result of execution.
This is similar to executing a file with <code class="docutils literal"><span class="pre">python</span> <span class="pre">-i</span> <span class="pre">file</span></code> at a command
line.</dd>
</dl>
</div>
<div class="section" id="shell-menu-shell-window-only">
<h3>24.6.1.5. Shell menu (Shell window only)<a class="headerlink" href="#shell-menu-shell-window-only" title="Permalink to this headline"></a></h3>
<dl class="docutils">
<dt>View Last Restart</dt>
<dd>Scroll the shell window to the last Shell restart.</dd>
<dt>Restart Shell</dt>
<dd>Restart the shell to clean the environment.</dd>
<dt>Interrupt Execution</dt>
<dd>Stop a running program.</dd>
</dl>
</div>
<div class="section" id="debug-menu-shell-window-only">
<h3>24.6.1.6. Debug menu (Shell window only)<a class="headerlink" href="#debug-menu-shell-window-only" title="Permalink to this headline"></a></h3>
<dl class="docutils">
<dt>Go to File/Line</dt>
<dd>Look on the current line. with the cursor, and the line above for a filename
and line number. If found, open the file if not already open, and show the
line. Use this to view source lines referenced in an exception traceback
and lines found by Find in Files. Also available in the context menu of
the Shell window and Output windows.</dd>
</dl>
<dl class="docutils" id="index-3">
<dt>Debugger (toggle)</dt>
<dd>When actived, code entered in the Shell or run from an Editor will run
under the debugger. In the Editor, breakpoints can be set with the context
menu. This feature is still incomplete and somewhat experimental.</dd>
<dt>Stack Viewer</dt>
<dd>Show the stack traceback of the last exception in a tree widget, with
access to locals and globals.</dd>
<dt>Auto-open Stack Viewer</dt>
<dd>Toggle automatically opening the stack viewer on an unhandled exception.</dd>
</dl>
</div>
<div class="section" id="options-menu-shell-and-editor">
<h3>24.6.1.7. Options menu (Shell and Editor)<a class="headerlink" href="#options-menu-shell-and-editor" title="Permalink to this headline"></a></h3>
<dl class="docutils">
<dt>Configure IDLE</dt>
<dd><p class="first">Open a configuration dialog and change preferences for the following:
fonts, indentation, keybindings, text color themes, startup windows and
size, additional help sources, and extensions (see below). On OS X,
open the configuration dialog by selecting Preferences in the application
menu. To use a new built-in color theme (IDLE Dark) with older IDLEs,
save it as a new custom theme.</p>
<p class="last">Non-default user settings are saved in a .idlerc directory in the user&#8217;s
home directory. Problems caused by bad user configuration files are solved
by editing or deleting one or more of the files in .idlerc.</p>
</dd>
<dt>Code Context (toggle)(Editor Window only)</dt>
<dd>Open a pane at the top of the edit window which shows the block context
of the code which has scrolled above the top of the window.</dd>
</dl>
</div>
<div class="section" id="window-menu-shell-and-editor">
<h3>24.6.1.8. Window menu (Shell and Editor)<a class="headerlink" href="#window-menu-shell-and-editor" title="Permalink to this headline"></a></h3>
<dl class="docutils">
<dt>Zoom Height</dt>
<dd>Toggles the window between normal size and maximum height. The initial size
defaults to 40 lines by 80 chars unless changed on the General tab of the
Configure IDLE dialog.</dd>
</dl>
<p>The rest of this menu lists the names of all open windows; select one to bring
it to the foreground (deiconifying it if necessary).</p>
</div>
<div class="section" id="help-menu-shell-and-editor">
<h3>24.6.1.9. Help menu (Shell and Editor)<a class="headerlink" href="#help-menu-shell-and-editor" title="Permalink to this headline"></a></h3>
<dl class="docutils">
<dt>About IDLE</dt>
<dd>Display version, copyright, license, credits, and more.</dd>
<dt>IDLE Help</dt>
<dd>Display a help file for IDLE detailing the menu options, basic editing and
navigation, and other tips.</dd>
<dt>Python Docs</dt>
<dd>Access local Python documentation, if installed, or start a web browser
and open docs.python.org showing the latest Python documentation.</dd>
<dt>Turtle Demo</dt>
<dd>Run the turtledemo module with example python code and turtle drawings.</dd>
</dl>
<p>Additional help sources may be added here with the Configure IDLE dialog under
the General tab.</p>
</div>
<div class="section" id="context-menus">
<span id="index-4"></span><h3>24.6.1.10. Context Menus<a class="headerlink" href="#context-menus" title="Permalink to this headline"></a></h3>
<p>Open a context menu by right-clicking in a window (Control-click on OS X).
Context menus have the standard clipboard functions also on the Edit menu.</p>
<dl class="docutils">
<dt>Cut</dt>
<dd>Copy selection into the system-wide clipboard; then delete the selection.</dd>
<dt>Copy</dt>
<dd>Copy selection into the system-wide clipboard.</dd>
<dt>Paste</dt>
<dd>Insert contents of the system-wide clipboard into the current window.</dd>
</dl>
<p>Editor windows also have breakpoint functions. Lines with a breakpoint set are
specially marked. Breakpoints only have an effect when running under the
debugger. Breakpoints for a file are saved in the user&#8217;s .idlerc directory.</p>
<dl class="docutils">
<dt>Set Breakpoint</dt>
<dd>Set a breakpoint on the current line.</dd>
<dt>Clear Breakpoint</dt>
<dd>Clear the breakpoint on that line.</dd>
</dl>
<p>Shell and Output windows have the following.</p>
<dl class="docutils">
<dt>Go to file/line</dt>
<dd>Same as in Debug menu.</dd>
</dl>
</div>
</div>
<div class="section" id="editing-and-navigation">
<h2>24.6.2. Editing and navigation<a class="headerlink" href="#editing-and-navigation" title="Permalink to this headline"></a></h2>
<p>In this section, &#8216;C&#8217; refers to the <code class="kbd docutils literal"><span class="pre">Control</span></code> key on Windows and Unix and
the <code class="kbd docutils literal"><span class="pre">Command</span></code> key on Mac OSX.</p>
<ul>
<li><p class="first"><code class="kbd docutils literal"><span class="pre">Backspace</span></code> deletes to the left; <code class="kbd docutils literal"><span class="pre">Del</span></code> deletes to the right</p>
</li>
<li><p class="first"><code class="kbd docutils literal"><span class="pre">C-Backspace</span></code> delete word left; <code class="kbd docutils literal"><span class="pre">C-Del</span></code> delete word to the right</p>
</li>
<li><p class="first">Arrow keys and <code class="kbd docutils literal"><span class="pre">Page</span> <span class="pre">Up</span></code>/<code class="kbd docutils literal"><span class="pre">Page</span> <span class="pre">Down</span></code> to move around</p>
</li>
<li><p class="first"><code class="kbd docutils literal"><span class="pre">C-LeftArrow</span></code> and <code class="kbd docutils literal"><span class="pre">C-RightArrow</span></code> moves by words</p>
</li>
<li><p class="first"><code class="kbd docutils literal"><span class="pre">Home</span></code>/<code class="kbd docutils literal"><span class="pre">End</span></code> go to begin/end of line</p>
</li>
<li><p class="first"><code class="kbd docutils literal"><span class="pre">C-Home</span></code>/<code class="kbd docutils literal"><span class="pre">C-End</span></code> go to begin/end of file</p>
</li>
<li><p class="first">Some useful Emacs bindings are inherited from Tcl/Tk:</p>
<blockquote>
<div><ul class="simple">
<li><code class="kbd docutils literal"><span class="pre">C-a</span></code> beginning of line</li>
<li><code class="kbd docutils literal"><span class="pre">C-e</span></code> end of line</li>
<li><code class="kbd docutils literal"><span class="pre">C-k</span></code> kill line (but doesn&#8217;t put it in clipboard)</li>
<li><code class="kbd docutils literal"><span class="pre">C-l</span></code> center window around the insertion point</li>
<li><code class="kbd docutils literal"><span class="pre">C-b</span></code> go backwards one character without deleting (usually you can
also use the cursor key for this)</li>
<li><code class="kbd docutils literal"><span class="pre">C-f</span></code> go forward one character without deleting (usually you can
also use the cursor key for this)</li>
<li><code class="kbd docutils literal"><span class="pre">C-p</span></code> go up one line (usually you can also use the cursor key for
this)</li>
<li><code class="kbd docutils literal"><span class="pre">C-d</span></code> delete next character</li>
</ul>
</div></blockquote>
</li>
</ul>
<p>Standard keybindings (like <code class="kbd docutils literal"><span class="pre">C-c</span></code> to copy and <code class="kbd docutils literal"><span class="pre">C-v</span></code> to paste)
may work. Keybindings are selected in the Configure IDLE dialog.</p>
<div class="section" id="automatic-indentation">
<h3>24.6.2.1. Automatic indentation<a class="headerlink" href="#automatic-indentation" title="Permalink to this headline"></a></h3>
<p>After a block-opening statement, the next line is indented by 4 spaces (in the
Python Shell window by one tab). After certain keywords (break, return etc.)
the next line is dedented. In leading indentation, <code class="kbd docutils literal"><span class="pre">Backspace</span></code> deletes up
to 4 spaces if they are there. <code class="kbd docutils literal"><span class="pre">Tab</span></code> inserts spaces (in the Python
Shell window one tab), number depends on Indent width. Currently tabs
are restricted to four spaces due to Tcl/Tk limitations.</p>
<p>See also the indent/dedent region commands in the edit menu.</p>
</div>
<div class="section" id="completions">
<h3>24.6.2.2. Completions<a class="headerlink" href="#completions" title="Permalink to this headline"></a></h3>
<p>Completions are supplied for functions, classes, and attributes of classes,
both built-in and user-defined. Completions are also provided for
filenames.</p>
<p>The AutoCompleteWindow (ACW) will open after a predefined delay (default is
two seconds) after a &#8216;.&#8217; or (in a string) an os.sep is typed. If after one
of those characters (plus zero or more other characters) a tab is typed
the ACW will open immediately if a possible continuation is found.</p>
<p>If there is only one possible completion for the characters entered, a
<code class="kbd docutils literal"><span class="pre">Tab</span></code> will supply that completion without opening the ACW.</p>
<p>&#8216;Show Completions&#8217; will force open a completions window, by default the
<code class="kbd docutils literal"><span class="pre">C-space</span></code> will open a completions window. In an empty
string, this will contain the files in the current directory. On a
blank line, it will contain the built-in and user-defined functions and
classes in the current name spaces, plus any modules imported. If some
characters have been entered, the ACW will attempt to be more specific.</p>
<p>If a string of characters is typed, the ACW selection will jump to the
entry most closely matching those characters. Entering a <code class="kbd docutils literal"><span class="pre">tab</span></code> will
cause the longest non-ambiguous match to be entered in the Editor window or
Shell. Two <code class="kbd docutils literal"><span class="pre">tab</span></code> in a row will supply the current ACW selection, as
will return or a double click. Cursor keys, Page Up/Down, mouse selection,
and the scroll wheel all operate on the ACW.</p>
<p>&#8220;Hidden&#8221; attributes can be accessed by typing the beginning of hidden
name after a &#8216;.&#8217;, e.g. &#8216;_&#8217;. This allows access to modules with
<code class="docutils literal"><span class="pre">__all__</span></code> set, or to class-private attributes.</p>
<p>Completions and the &#8216;Expand Word&#8217; facility can save a lot of typing!</p>
<p>Completions are currently limited to those in the namespaces. Names in
an Editor window which are not via <code class="docutils literal"><span class="pre">__main__</span></code> and <a class="reference internal" href="sys.html#sys.modules" title="sys.modules"><code class="xref py py-data docutils literal"><span class="pre">sys.modules</span></code></a> will
not be found. Run the module once with your imports to correct this situation.
Note that IDLE itself places quite a few modules in sys.modules, so
much can be found by default, e.g. the re module.</p>
<p>If you don&#8217;t like the ACW popping up unbidden, simply make the delay
longer or disable the extension.</p>
</div>
<div class="section" id="calltips">
<h3>24.6.2.3. Calltips<a class="headerlink" href="#calltips" title="Permalink to this headline"></a></h3>
<p>A calltip is shown when one types <code class="kbd docutils literal"><span class="pre">(</span></code> after the name of an <em>acccessible</em>
function. A name expression may include dots and subscripts. A calltip
remains until it is clicked, the cursor is moved out of the argument area,
or <code class="kbd docutils literal"><span class="pre">)</span></code> is typed. When the cursor is in the argument part of a definition,
the menu or shortcut display a calltip.</p>
<p>A calltip consists of the function signature and the first line of the
docstring. For builtins without an accessible signature, the calltip
consists of all lines up the fifth line or the first blank line. These
details may change.</p>
<p>The set of <em>accessible</em> functions depends on what modules have been imported
into the user process, including those imported by Idle itself,
and what definitions have been run, all since the last restart.</p>
<p>For example, restart the Shell and enter <code class="docutils literal"><span class="pre">itertools.count(</span></code>. A calltip
appears because Idle imports itertools into the user process for its own use.
(This could change.) Enter <code class="docutils literal"><span class="pre">turtle.write(</span></code> and nothing appears. Idle does
not import turtle. The menu or shortcut do nothing either. Enter
<code class="docutils literal"><span class="pre">import</span> <span class="pre">turtle</span></code> and then <code class="docutils literal"><span class="pre">turtle.write(</span></code> will work.</p>
<p>In an editor, import statements have no effect until one runs the file. One
might want to run a file after writing the import statements at the top,
or immediately run an existing file before editing.</p>
</div>
<div class="section" id="python-shell-window">
<h3>24.6.2.4. Python Shell window<a class="headerlink" href="#python-shell-window" title="Permalink to this headline"></a></h3>
<ul>
<li><p class="first"><code class="kbd docutils literal"><span class="pre">C-c</span></code> interrupts executing command</p>
</li>
<li><p class="first"><code class="kbd docutils literal"><span class="pre">C-d</span></code> sends end-of-file; closes window if typed at a <code class="docutils literal"><span class="pre">&gt;&gt;&gt;</span></code> prompt</p>
</li>
<li><p class="first"><code class="kbd docutils literal"><span class="pre">Alt-/</span></code> (Expand word) is also useful to reduce typing</p>
<p>Command history</p>
<ul class="simple">
<li><code class="kbd docutils literal"><span class="pre">Alt-p</span></code> retrieves previous command matching what you have typed. On
OS X use <code class="kbd docutils literal"><span class="pre">C-p</span></code>.</li>
<li><code class="kbd docutils literal"><span class="pre">Alt-n</span></code> retrieves next. On OS X use <code class="kbd docutils literal"><span class="pre">C-n</span></code>.</li>
<li><code class="kbd docutils literal"><span class="pre">Return</span></code> while on any previous command retrieves that command</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="text-colors">
<h3>24.6.2.5. Text colors<a class="headerlink" href="#text-colors" title="Permalink to this headline"></a></h3>
<p>Idle defaults to black on white text, but colors text with special meanings.
For the shell, these are shell output, shell error, user output, and
user error. For Python code, at the shell prompt or in an editor, these are
keywords, builtin class and function names, names following <code class="docutils literal"><span class="pre">class</span></code> and
<code class="docutils literal"><span class="pre">def</span></code>, strings, and comments. For any text window, these are the cursor (when
present), found text (when possible), and selected text.</p>
<p>Text coloring is done in the background, so uncolorized text is occasionally
visible. To change the color scheme, use the Configure IDLE dialog
Highlighting tab. The marking of debugger breakpoint lines in the editor and
text in popups and dialogs is not user-configurable.</p>
</div>
</div>
<div class="section" id="startup-and-code-execution">
<h2>24.6.3. Startup and code execution<a class="headerlink" href="#startup-and-code-execution" title="Permalink to this headline"></a></h2>
<p>Upon startup with the <code class="docutils literal"><span class="pre">-s</span></code> option, IDLE will execute the file referenced by
the environment variables <span class="target" id="index-5"></span><code class="xref std std-envvar docutils literal"><span class="pre">IDLESTARTUP</span></code> or <span class="target" id="index-6"></span><a class="reference internal" href="../using/cmdline.html#envvar-PYTHONSTARTUP"><code class="xref std std-envvar docutils literal"><span class="pre">PYTHONSTARTUP</span></code></a>.
IDLE first checks for <code class="docutils literal"><span class="pre">IDLESTARTUP</span></code>; if <code class="docutils literal"><span class="pre">IDLESTARTUP</span></code> is present the file
referenced is run. If <code class="docutils literal"><span class="pre">IDLESTARTUP</span></code> is not present, IDLE checks for
<code class="docutils literal"><span class="pre">PYTHONSTARTUP</span></code>. Files referenced by these environment variables are
convenient places to store functions that are used frequently from the IDLE
shell, or for executing import statements to import common modules.</p>
<p>In addition, <code class="docutils literal"><span class="pre">Tk</span></code> also loads a startup file if it is present. Note that the
Tk file is loaded unconditionally. This additional file is <code class="docutils literal"><span class="pre">.Idle.py</span></code> and is
looked for in the user&#8217;s home directory. Statements in this file will be
executed in the Tk namespace, so this file is not useful for importing
functions to be used from IDLE&#8217;s Python shell.</p>
<div class="section" id="command-line-usage">
<h3>24.6.3.1. Command line usage<a class="headerlink" href="#command-line-usage" title="Permalink to this headline"></a></h3>
<div class="highlight-none"><div class="highlight"><pre><span></span>idle.py [-c command] [-d] [-e] [-h] [-i] [-r file] [-s] [-t title] [-] [arg] ...
-c command run command in the shell window
-d enable debugger and open shell window
-e open editor window
-h print help message with legal combinations and exit
-i open shell window
-r file run file in shell window
-s run $IDLESTARTUP or $PYTHONSTARTUP first, in shell window
-t title set title of shell window
- run stdin in shell (- must be last option before args)
</pre></div>
</div>
<p>If there are arguments:</p>
<ul class="simple">
<li>If <code class="docutils literal"><span class="pre">-</span></code>, <code class="docutils literal"><span class="pre">-c</span></code>, or <code class="docutils literal"><span class="pre">r</span></code> is used, all arguments are placed in
<code class="docutils literal"><span class="pre">sys.argv[1:...]</span></code> and <code class="docutils literal"><span class="pre">sys.argv[0]</span></code> is set to <code class="docutils literal"><span class="pre">''</span></code>, <code class="docutils literal"><span class="pre">'-c'</span></code>,
or <code class="docutils literal"><span class="pre">'-r'</span></code>. No editor window is opened, even if that is the default
set in the Options dialog.</li>
<li>Otherwise, arguments are files opened for editing and
<code class="docutils literal"><span class="pre">sys.argv</span></code> reflects the arguments passed to IDLE itself.</li>
</ul>
</div>
<div class="section" id="idle-console-differences">
<h3>24.6.3.2. IDLE-console differences<a class="headerlink" href="#idle-console-differences" title="Permalink to this headline"></a></h3>
<p>As much as possible, the result of executing Python code with IDLE is the
same as executing the same code in a console window. However, the different
interface and operation occasionally affects visible results. For instance,
<code class="docutils literal"><span class="pre">sys.modules</span></code> starts with more entries.</p>
<p>IDLE also replaces <code class="docutils literal"><span class="pre">sys.stdin</span></code>, <code class="docutils literal"><span class="pre">sys.stdout</span></code>, and <code class="docutils literal"><span class="pre">sys.stderr</span></code> with
objects that get input from and send output to the Shell window.
When this window has the focus, it controls the keyboard and screen.
This is normally transparent, but functions that directly access the keyboard
and screen will not work. If <code class="docutils literal"><span class="pre">sys</span></code> is reset with <code class="docutils literal"><span class="pre">reload(sys)</span></code>,
IDLE&#8217;s changes are lost and things like <code class="docutils literal"><span class="pre">input</span></code>, <code class="docutils literal"><span class="pre">raw_input</span></code>, and
<code class="docutils literal"><span class="pre">print</span></code> will not work correctly.</p>
<p>With IDLE&#8217;s Shell, one enters, edits, and recalls complete statements.
Some consoles only work with a single physical line at a time. IDLE uses
<code class="docutils literal"><span class="pre">exec</span></code> to run each statement. As a result, <code class="docutils literal"><span class="pre">'__builtins__'</span></code> is always
defined for each statement.</p>
</div>
<div class="section" id="running-without-a-subprocess">
<h3>24.6.3.3. Running without a subprocess<a class="headerlink" href="#running-without-a-subprocess" title="Permalink to this headline"></a></h3>
<p>By default, IDLE executes user code in a separate subprocess via a socket,
which uses the internal loopback interface. This connection is not
externally visible and no data is sent to or received from the Internet.
If firewall software complains anyway, you can ignore it.</p>
<p>If the attempt to make the socket connection fails, Idle will notify you.
Such failures are sometimes transient, but if persistent, the problem
may be either a firewall blocking the connecton or misconfiguration of
a particular system. Until the problem is fixed, one can run Idle with
the -n command line switch.</p>
<p>If IDLE is started with the -n command line switch it will run in a
single process and will not create the subprocess which runs the RPC
Python execution server. This can be useful if Python cannot create
the subprocess or the RPC socket interface on your platform. However,
in this mode user code is not isolated from IDLE itself. Also, the
environment is not restarted when Run/Run Module (F5) is selected. If
your code has been modified, you must reload() the affected modules and
re-import any specific items (e.g. from foo import baz) if the changes
are to take effect. For these reasons, it is preferable to run IDLE
with the default subprocess if at all possible.</p>
<div class="deprecated">
<p><span class="versionmodified">Deprecated since version 3.4.</span></p>
</div>
</div>
</div>
<div class="section" id="help-and-preferences">
<h2>24.6.4. Help and preferences<a class="headerlink" href="#help-and-preferences" title="Permalink to this headline"></a></h2>
<div class="section" id="additional-help-sources">
<h3>24.6.4.1. Additional help sources<a class="headerlink" href="#additional-help-sources" title="Permalink to this headline"></a></h3>
<p>IDLE includes a help menu entry called &#8220;Python Docs&#8221; that will open the
extensive sources of help, including tutorials, available at docs.python.org.
Selected URLs can be added or removed from the help menu at any time using the
Configure IDLE dialog. See the IDLE help option in the help menu of IDLE for
more information.</p>
</div>
<div class="section" id="setting-preferences">
<h3>24.6.4.2. Setting preferences<a class="headerlink" href="#setting-preferences" title="Permalink to this headline"></a></h3>
<p>The font preferences, highlighting, keys, and general preferences can be
changed via Configure IDLE on the Option menu. Keys can be user defined;
IDLE ships with four built in key sets. In addition a user can create a
custom key set in the Configure IDLE dialog under the keys tab.</p>
</div>
<div class="section" id="extensions">
<h3>24.6.4.3. Extensions<a class="headerlink" href="#extensions" title="Permalink to this headline"></a></h3>
<p>IDLE contains an extension facility. Peferences for extensions can be
changed with Configure Extensions. See the beginning of config-extensions.def
in the idlelib directory for further information. The default extensions
are currently:</p>
<ul class="simple">
<li>FormatParagraph</li>
<li>AutoExpand</li>
<li>ZoomHeight</li>
<li>ScriptBinding</li>
<li>CallTips</li>
<li>ParenMatch</li>
<li>AutoComplete</li>
<li>CodeContext</li>
<li>RstripExtension</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h3><a href="../contents.html">Table Of Contents</a></h3>
<ul>
<li><a class="reference internal" href="#">24.6. IDLE</a><ul>
<li><a class="reference internal" href="#menus">24.6.1. Menus</a><ul>
<li><a class="reference internal" href="#file-menu-shell-and-editor">24.6.1.1. File menu (Shell and Editor)</a></li>
<li><a class="reference internal" href="#edit-menu-shell-and-editor">24.6.1.2. Edit menu (Shell and Editor)</a></li>
<li><a class="reference internal" href="#format-menu-editor-window-only">24.6.1.3. Format menu (Editor window only)</a></li>
<li><a class="reference internal" href="#run-menu-editor-window-only">24.6.1.4. Run menu (Editor window only)</a></li>
<li><a class="reference internal" href="#shell-menu-shell-window-only">24.6.1.5. Shell menu (Shell window only)</a></li>
<li><a class="reference internal" href="#debug-menu-shell-window-only">24.6.1.6. Debug menu (Shell window only)</a></li>
<li><a class="reference internal" href="#options-menu-shell-and-editor">24.6.1.7. Options menu (Shell and Editor)</a></li>
<li><a class="reference internal" href="#window-menu-shell-and-editor">24.6.1.8. Window menu (Shell and Editor)</a></li>
<li><a class="reference internal" href="#help-menu-shell-and-editor">24.6.1.9. Help menu (Shell and Editor)</a></li>
<li><a class="reference internal" href="#context-menus">24.6.1.10. Context Menus</a></li>
</ul>
</li>
<li><a class="reference internal" href="#editing-and-navigation">24.6.2. Editing and navigation</a><ul>
<li><a class="reference internal" href="#automatic-indentation">24.6.2.1. Automatic indentation</a></li>
<li><a class="reference internal" href="#completions">24.6.2.2. Completions</a></li>
<li><a class="reference internal" href="#calltips">24.6.2.3. Calltips</a></li>
<li><a class="reference internal" href="#python-shell-window">24.6.2.4. Python Shell window</a></li>
<li><a class="reference internal" href="#text-colors">24.6.2.5. Text colors</a></li>
</ul>
</li>
<li><a class="reference internal" href="#startup-and-code-execution">24.6.3. Startup and code execution</a><ul>
<li><a class="reference internal" href="#command-line-usage">24.6.3.1. Command line usage</a></li>
<li><a class="reference internal" href="#idle-console-differences">24.6.3.2. IDLE-console differences</a></li>
<li><a class="reference internal" href="#running-without-a-subprocess">24.6.3.3. Running without a subprocess</a></li>
</ul>
</li>
<li><a class="reference internal" href="#help-and-preferences">24.6.4. Help and preferences</a><ul>
<li><a class="reference internal" href="#additional-help-sources">24.6.4.1. Additional help sources</a></li>
<li><a class="reference internal" href="#setting-preferences">24.6.4.2. Setting preferences</a></li>
<li><a class="reference internal" href="#extensions">24.6.4.3. Extensions</a></li>
</ul>
</li>
</ul>
</li>
</ul>
<h4>Previous topic</h4>
<p class="topless"><a href="turtle.html"
title="previous chapter">24.5. <code class="docutils literal"><span class="pre">turtle</span></code> &#8212; Turtle graphics for Tk</a></p>
<h4>Next topic</h4>
<p class="topless"><a href="othergui.html"
title="next chapter">24.7. Other Graphical User Interface Packages</a></p>
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../bugs.html">Report a Bug</a></li>
<li><a href="../_sources/library/idle.txt"
rel="nofollow">Show Source</a></li>
</ul>
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="../search.html" method="get">
<input type="text" name="q" />
<input type="submit" value="Go" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
<p class="searchtip" style="font-size: 90%">
Enter search terms or a module, class or function name.
</p>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="../py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li class="right" >
<a href="othergui.html" title="24.7. Other Graphical User Interface Packages"
>next</a> |</li>
<li class="right" >
<a href="turtle.html" title="24.5. turtle — Turtle graphics for Tk"
>previous</a> |</li>
<li><img src="../_static/py.png" alt=""
style="vertical-align: middle; margin-top: -1px"/></li>
<li><a href="https://www.python.org/">Python</a> &raquo;</li>
<li>
<a href="../index.html">Python 2.7.12 documentation</a> &raquo;
</li>
<li class="nav-item nav-item-1"><a href="index.html" >The Python Standard Library</a> &raquo;</li>
<li class="nav-item nav-item-2"><a href="tk.html" >24. Graphical User Interfaces with Tk</a> &raquo;</li>
</ul>
</div>
<div class="footer">
&copy; <a href="../copyright.html">Copyright</a> 1990-2017, Python Software Foundation.
<br />
The Python Software Foundation is a non-profit corporation.
<a href="https://www.python.org/psf/donations/">Please donate.</a>
<br />
Last updated on Sep 12, 2016.
<a href="../bugs.html">Found a bug</a>?
<br />
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.3.6.
</div>
</body>
</html>

277
Lib/idlelib/help.py Normal file
View File

@@ -0,0 +1,277 @@
""" help.py: Implement the Idle help menu.
Contents are subject to revision at any time, without notice.
Help => About IDLE: diplay About Idle dialog
<to be moved here from aboutDialog.py>
Help => IDLE Help: Display help.html with proper formatting.
Doc/library/idle.rst (Sphinx)=> Doc/build/html/library/idle.html
(help.copy_strip)=> Lib/idlelib/help.html
HelpParser - Parse help.html and render to tk Text.
HelpText - Display formatted help.html.
HelpFrame - Contain text, scrollbar, and table-of-contents.
(This will be needed for display in a future tabbed window.)
HelpWindow - Display HelpFrame in a standalone window.
copy_strip - Copy idle.html to help.html, rstripping each line.
show_idlehelp - Create HelpWindow. Called in EditorWindow.help_dialog.
"""
from HTMLParser import HTMLParser
from os.path import abspath, dirname, isdir, isfile, join
from platform import python_version
from Tkinter import Tk, Toplevel, Frame, Text, Scrollbar, Menu, Menubutton
import tkFont as tkfont
from idlelib.configHandler import idleConf
use_ttk = False # until available to import
if use_ttk:
from tkinter.ttk import Menubutton
## About IDLE ##
## IDLE Help ##
class HelpParser(HTMLParser):
"""Render help.html into a text widget.
The overridden handle_xyz methods handle a subset of html tags.
The supplied text should have the needed tag configurations.
The behavior for unsupported tags, such as table, is undefined.
If the tags generated by Sphinx change, this class, especially
the handle_starttag and handle_endtags methods, might have to also.
"""
def __init__(self, text):
HTMLParser.__init__(self)
self.text = text # text widget we're rendering into
self.tags = '' # current block level text tags to apply
self.chartags = '' # current character level text tags
self.show = False # used so we exclude page navigation
self.hdrlink = False # used so we don't show header links
self.level = 0 # indentation level
self.pre = False # displaying preformatted text
self.hprefix = '' # prefix such as '25.5' to strip from headings
self.nested_dl = False # if we're in a nested <dl>
self.simplelist = False # simple list (no double spacing)
self.toc = [] # pair headers with text indexes for toc
self.header = '' # text within header tags for toc
def indent(self, amt=1):
self.level += amt
self.tags = '' if self.level == 0 else 'l'+str(self.level)
def handle_starttag(self, tag, attrs):
"Handle starttags in help.html."
class_ = ''
for a, v in attrs:
if a == 'class':
class_ = v
s = ''
if tag == 'div' and class_ == 'section':
self.show = True # start of main content
elif tag == 'div' and class_ == 'sphinxsidebar':
self.show = False # end of main content
elif tag == 'p' and class_ != 'first':
s = '\n\n'
elif tag == 'span' and class_ == 'pre':
self.chartags = 'pre'
elif tag == 'span' and class_ == 'versionmodified':
self.chartags = 'em'
elif tag == 'em':
self.chartags = 'em'
elif tag in ['ul', 'ol']:
if class_.find('simple') != -1:
s = '\n'
self.simplelist = True
else:
self.simplelist = False
self.indent()
elif tag == 'dl':
if self.level > 0:
self.nested_dl = True
elif tag == 'li':
s = '\n* ' if self.simplelist else '\n\n* '
elif tag == 'dt':
s = '\n\n' if not self.nested_dl else '\n' # avoid extra line
self.nested_dl = False
elif tag == 'dd':
self.indent()
s = '\n'
elif tag == 'pre':
self.pre = True
if self.show:
self.text.insert('end', '\n\n')
self.tags = 'preblock'
elif tag == 'a' and class_ == 'headerlink':
self.hdrlink = True
elif tag == 'h1':
self.tags = tag
elif tag in ['h2', 'h3']:
if self.show:
self.header = ''
self.text.insert('end', '\n\n')
self.tags = tag
if self.show:
self.text.insert('end', s, (self.tags, self.chartags))
def handle_endtag(self, tag):
"Handle endtags in help.html."
if tag in ['h1', 'h2', 'h3']:
self.indent(0) # clear tag, reset indent
if self.show:
self.toc.append((self.header, self.text.index('insert')))
elif tag in ['span', 'em']:
self.chartags = ''
elif tag == 'a':
self.hdrlink = False
elif tag == 'pre':
self.pre = False
self.tags = ''
elif tag in ['ul', 'dd', 'ol']:
self.indent(amt=-1)
def handle_data(self, data):
"Handle date segments in help.html."
if self.show and not self.hdrlink:
d = data if self.pre else data.replace('\n', ' ')
if self.tags == 'h1':
self.hprefix = d[0:d.index(' ')]
if self.tags in ['h1', 'h2', 'h3'] and self.hprefix != '':
if d[0:len(self.hprefix)] == self.hprefix:
d = d[len(self.hprefix):].strip()
self.header += d
self.text.insert('end', d, (self.tags, self.chartags))
def handle_charref(self, name):
if self.show:
self.text.insert('end', unichr(int(name)))
class HelpText(Text):
"Display help.html."
def __init__(self, parent, filename):
"Configure tags and feed file to parser."
uwide = idleConf.GetOption('main', 'EditorWindow', 'width', type='int')
uhigh = idleConf.GetOption('main', 'EditorWindow', 'height', type='int')
uhigh = 3 * uhigh // 4 # lines average 4/3 of editor line height
Text.__init__(self, parent, wrap='word', highlightthickness=0,
padx=5, borderwidth=0, width=uwide, height=uhigh)
normalfont = self.findfont(['TkDefaultFont', 'arial', 'helvetica'])
fixedfont = self.findfont(['TkFixedFont', 'monaco', 'courier'])
self['font'] = (normalfont, 12)
self.tag_configure('em', font=(normalfont, 12, 'italic'))
self.tag_configure('h1', font=(normalfont, 20, 'bold'))
self.tag_configure('h2', font=(normalfont, 18, 'bold'))
self.tag_configure('h3', font=(normalfont, 15, 'bold'))
self.tag_configure('pre', font=(fixedfont, 12), background='#f6f6ff')
self.tag_configure('preblock', font=(fixedfont, 10), lmargin1=25,
borderwidth=1, relief='solid', background='#eeffcc')
self.tag_configure('l1', lmargin1=25, lmargin2=25)
self.tag_configure('l2', lmargin1=50, lmargin2=50)
self.tag_configure('l3', lmargin1=75, lmargin2=75)
self.tag_configure('l4', lmargin1=100, lmargin2=100)
self.parser = HelpParser(self)
with open(filename) as f:
contents = f.read().decode(encoding='utf-8')
self.parser.feed(contents)
self['state'] = 'disabled'
def findfont(self, names):
"Return name of first font family derived from names."
for name in names:
if name.lower() in (x.lower() for x in tkfont.names(root=self)):
font = tkfont.Font(name=name, exists=True, root=self)
return font.actual()['family']
elif name.lower() in (x.lower()
for x in tkfont.families(root=self)):
return name
class HelpFrame(Frame):
"Display html text, scrollbar, and toc."
def __init__(self, parent, filename):
Frame.__init__(self, parent)
text = HelpText(self, filename)
self['background'] = text['background']
scroll = Scrollbar(self, command=text.yview)
text['yscrollcommand'] = scroll.set
self.rowconfigure(0, weight=1)
self.columnconfigure(1, weight=1) # text
self.toc_menu(text).grid(column=0, row=0, sticky='nw')
text.grid(column=1, row=0, sticky='nsew')
scroll.grid(column=2, row=0, sticky='ns')
def toc_menu(self, text):
"Create table of contents as drop-down menu."
toc = Menubutton(self, text='TOC')
drop = Menu(toc, tearoff=False)
for lbl, dex in text.parser.toc:
drop.add_command(label=lbl, command=lambda dex=dex:text.yview(dex))
toc['menu'] = drop
return toc
class HelpWindow(Toplevel):
"Display frame with rendered html."
def __init__(self, parent, filename, title):
Toplevel.__init__(self, parent)
self.wm_title(title)
self.protocol("WM_DELETE_WINDOW", self.destroy)
HelpFrame(self, filename).grid(column=0, row=0, sticky='nsew')
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
def copy_strip():
"""Copy idle.html to idlelib/help.html, stripping trailing whitespace.
Files with trailing whitespace cannot be pushed to the hg cpython
repository. For 3.x (on Windows), help.html is generated, after
editing idle.rst in the earliest maintenance version, with
sphinx-build -bhtml . build/html
python_d.exe -c "from idlelib.help import copy_strip; copy_strip()"
After refreshing TortoiseHG workshop to generate a diff,
check both the diff and displayed text. Push the diff along with
the idle.rst change and merge both into default (or an intermediate
maintenance version).
When the 'earlist' version gets its final maintenance release,
do an update as described above, without editing idle.rst, to
rebase help.html on the next version of idle.rst. Do not worry
about version changes as version is not displayed. Examine other
changes and the result of Help -> IDLE Help.
If maintenance and default versions of idle.rst diverge, and
merging does not go smoothly, then consider generating
separate help.html files from separate idle.htmls.
"""
src = join(abspath(dirname(dirname(dirname(__file__)))),
'Doc', 'build', 'html', 'library', 'idle.html')
dst = join(abspath(dirname(__file__)), 'help.html')
with open(src, 'r') as inn,\
open(dst, 'w') as out:
for line in inn:
out.write(line.rstrip() + '\n')
print('idle.html copied to help.html')
def show_idlehelp(parent):
"Create HelpWindow; called from Idle Help event handler."
filename = join(abspath(dirname(__file__)), 'help.html')
if not isfile(filename):
# try copy_strip, present message
return
HelpWindow(parent, filename, 'IDLE Help (%s)' % python_version())
if __name__ == '__main__':
from idlelib.idle_test.htest import run
run(show_idlehelp)

302
Lib/idlelib/help.txt Normal file
View File

@@ -0,0 +1,302 @@
This file, idlelib/help.txt is out-of-date and no longer used by Idle.
It is deprecated and will be removed in the future, possibly in 3.6
----------------------------------------------------------------------
[See the end of this file for ** TIPS ** on using IDLE !!]
File Menu:
New File -- Create a new editing window
Open... -- Open an existing file
Recent Files... -- Open a list of recent files
Open Module... -- Open an existing module (searches sys.path)
Class Browser -- Show classes and methods in current file
Path Browser -- Show sys.path directories, modules, classes
and methods
---
Save -- Save current window to the associated file (unsaved
windows have a * before and after the window title)
Save As... -- Save current window to new file, which becomes
the associated file
Save Copy As... -- Save current window to different file
without changing the associated file
---
Print Window -- Print the current window
---
Close -- Close current window (asks to save if unsaved)
Exit -- Close all windows, quit (asks to save if unsaved)
Edit Menu:
Undo -- Undo last change to current window
(A maximum of 1000 changes may be undone)
Redo -- Redo last undone change to current window
---
Cut -- Copy a selection into system-wide clipboard,
then delete the selection
Copy -- Copy selection into system-wide clipboard
Paste -- Insert system-wide clipboard into window
Select All -- Select the entire contents of the edit buffer
---
Find... -- Open a search dialog box with many options
Find Again -- Repeat last search
Find Selection -- Search for the string in the selection
Find in Files... -- Open a search dialog box for searching files
Replace... -- Open a search-and-replace dialog box
Go to Line -- Ask for a line number and show that line
Show Calltip -- Open a small window with function param hints
Show Completions -- Open a scroll window allowing selection keywords
and attributes. (see '*TIPS*', below)
Show Parens -- Highlight the surrounding parenthesis
Expand Word -- Expand the word you have typed to match another
word in the same buffer; repeat to get a
different expansion
Format Menu (only in Edit window):
Indent Region -- Shift selected lines right 4 spaces
Dedent Region -- Shift selected lines left 4 spaces
Comment Out Region -- Insert ## in front of selected lines
Uncomment Region -- Remove leading # or ## from selected lines
Tabify Region -- Turns *leading* stretches of spaces into tabs
(Note: We recommend using 4 space blocks to indent Python code.)
Untabify Region -- Turn *all* tabs into the right number of spaces
New Indent Width... -- Open dialog to change indent width
Format Paragraph -- Reformat the current blank-line-separated
paragraph
Run Menu (only in Edit window):
Python Shell -- Open or wake up the Python shell window
---
Check Module -- Run a syntax check on the module
Run Module -- Execute the current file in the __main__ namespace
Shell Menu (only in Shell window):
View Last Restart -- Scroll the shell window to the last restart
Restart Shell -- Restart the interpreter with a fresh environment
Debug Menu (only in Shell window):
Go to File/Line -- look around the insert point for a filename
and line number, open the file, and show the line
Debugger (toggle) -- Run commands in the shell under the debugger
Stack Viewer -- Show the stack traceback of the last exception
Auto-open Stack Viewer (toggle) -- Open stack viewer on traceback
Options Menu:
Configure IDLE -- Open a configuration dialog. Fonts, indentation,
keybindings, and color themes may be altered.
Startup Preferences may be set, and Additional Help
Sources can be specified. On OS X, open the
configuration dialog by selecting Preferences
in the application menu.
---
Code Context -- Open a pane at the top of the edit window which
shows the block context of the section of code
which is scrolling off the top or the window.
(Not present in Shell window.)
Window Menu:
Zoom Height -- toggles the window between configured size
and maximum height.
---
The rest of this menu lists the names of all open windows;
select one to bring it to the foreground (deiconifying it if
necessary).
Help Menu:
About IDLE -- Version, copyright, license, credits
IDLE Readme -- Background discussion and change details
---
IDLE Help -- Display this file
Python Docs -- Access local Python documentation, if
installed. Otherwise, access www.python.org.
---
(Additional Help Sources may be added here)
Edit context menu (Right-click / Control-click on OS X in Edit window):
Cut -- Copy a selection into system-wide clipboard,
then delete the selection
Copy -- Copy selection into system-wide clipboard
Paste -- Insert system-wide clipboard into window
Set Breakpoint -- Sets a breakpoint (when debugger open)
Clear Breakpoint -- Clears the breakpoint on that line
Shell context menu (Right-click / Control-click on OS X in Shell window):
Cut -- Copy a selection into system-wide clipboard,
then delete the selection
Copy -- Copy selection into system-wide clipboard
Paste -- Insert system-wide clipboard into window
---
Go to file/line -- Same as in Debug menu
** TIPS **
==========
Additional Help Sources:
Windows users can Google on zopeshelf.chm to access Zope help files in
the Windows help format. The Additional Help Sources feature of the
configuration GUI supports .chm, along with any other filetypes
supported by your browser. Supply a Menu Item title, and enter the
location in the Help File Path slot of the New Help Source dialog. Use
http:// and/or www. to identify external URLs, or download the file and
browse for its path on your machine using the Browse button.
All users can access the extensive sources of help, including
tutorials, available at www.python.org/doc. Selected URLs can be added
or removed from the Help menu at any time using Configure IDLE.
Basic editing and navigation:
Backspace deletes char to the left; DEL deletes char to the right.
Control-backspace deletes word left, Control-DEL deletes word right.
Arrow keys and Page Up/Down move around.
Control-left/right Arrow moves by words in a strange but useful way.
Home/End go to begin/end of line.
Control-Home/End go to begin/end of file.
Some useful Emacs bindings are inherited from Tcl/Tk:
Control-a beginning of line
Control-e end of line
Control-k kill line (but doesn't put it in clipboard)
Control-l center window around the insertion point
Standard Windows bindings may work on that platform.
Keybindings are selected in the Settings Dialog, look there.
Automatic indentation:
After a block-opening statement, the next line is indented by 4 spaces
(in the Python Shell window by one tab). After certain keywords
(break, return etc.) the next line is dedented. In leading
indentation, Backspace deletes up to 4 spaces if they are there. Tab
inserts spaces (in the Python Shell window one tab), number depends on
Indent Width. (N.B. Currently tabs are restricted to four spaces due
to Tcl/Tk issues.)
See also the indent/dedent region commands in the edit menu.
Completions:
Completions are supplied for functions, classes, and attributes of
classes, both built-in and user-defined. Completions are also provided
for filenames.
The AutoCompleteWindow (ACW) will open after a predefined delay
(default is two seconds) after a '.' or (in a string) an os.sep is
typed. If after one of those characters (plus zero or more other
characters) you type a Tab the ACW will open immediately if a possible
continuation is found.
If there is only one possible completion for the characters entered, a
Tab will supply that completion without opening the ACW.
'Show Completions' will force open a completions window. In an empty
string, this will contain the files in the current directory. On a
blank line, it will contain the built-in and user-defined functions and
classes in the current name spaces, plus any modules imported. If some
characters have been entered, the ACW will attempt to be more specific.
If string of characters is typed, the ACW selection will jump to the
entry most closely matching those characters. Entering a Tab will cause
the longest non-ambiguous match to be entered in the Edit window or
Shell. Two Tabs in a row will supply the current ACW selection, as
will Return or a double click. Cursor keys, Page Up/Down, mouse
selection, and the scrollwheel all operate on the ACW.
'Hidden' attributes can be accessed by typing the beginning of hidden
name after a '.'. e.g. '_'. This allows access to modules with
'__all__' set, or to class-private attributes.
Completions and the 'Expand Word' facility can save a lot of typing!
Completions are currently limited to those in the namespaces. Names in
an Edit window which are not via __main__ or sys.modules will not be
found. Run the module once with your imports to correct this
situation. Note that IDLE itself places quite a few modules in
sys.modules, so much can be found by default, e.g. the re module.
If you don't like the ACW popping up unbidden, simply make the delay
longer or disable the extension. OTOH, you could make the delay zero.
You could also switch off the CallTips extension. (We will be adding
a delay to the call tip window.)
Python Shell window:
Control-c interrupts executing command.
Control-d sends end-of-file; closes window if typed at >>> prompt.
Command history:
Alt-p retrieves previous command matching what you have typed.
Alt-n retrieves next.
(These are Control-p, Control-n on OS X)
Return while cursor is on a previous command retrieves that command.
Expand word is also useful to reduce typing.
Syntax colors:
The coloring is applied in a background "thread", so you may
occasionally see uncolorized text. To change the color
scheme, use the Configure IDLE / Highlighting dialog.
Python default syntax colors:
Keywords orange
Builtins royal purple
Strings green
Comments red
Definitions blue
Shell default colors:
Console output brown
stdout blue
stderr red
stdin black
Other preferences:
The font preferences, keybinding, and startup preferences can
be changed using the Settings dialog.
Command line usage:
Enter idle -h at the command prompt to get a usage message.
Running without a subprocess:
If IDLE is started with the -n command line switch it will run in a
single process and will not create the subprocess which runs the RPC
Python execution server. This can be useful if Python cannot create
the subprocess or the RPC socket interface on your platform. However,
in this mode user code is not isolated from IDLE itself. Also, the
environment is not restarted when Run/Run Module (F5) is selected. If
your code has been modified, you must reload() the affected modules and
re-import any specific items (e.g. from foo import baz) if the changes
are to take effect. For these reasons, it is preferable to run IDLE
with the default subprocess if at all possible.
Extensions:
IDLE contains an extension facility. See the beginning of
config-extensions.def in the idlelib directory for further information.
The default extensions are currently:
FormatParagraph
AutoExpand
ZoomHeight
ScriptBinding
CallTips
ParenMatch
AutoComplete
CodeContext

4
Lib/idlelib/idle.bat Executable file
View File

@@ -0,0 +1,4 @@
@echo off
rem Start IDLE using the appropriate Python interpreter
set CURRDIR=%~dp0
start "IDLE" "%CURRDIR%..\..\pythonw.exe" "%CURRDIR%idle.pyw" %1 %2 %3 %4 %5 %6 %7 %8 %9

13
Lib/idlelib/idle.py Normal file
View File

@@ -0,0 +1,13 @@
import os.path
import sys
# Enable running IDLE with idlelib in a non-standard location.
# This was once used to run development versions of IDLE.
# Because PEP 434 declared idle.py a public interface,
# removal should require deprecation.
idlelib_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if idlelib_dir not in sys.path:
sys.path.insert(0, idlelib_dir)
from idlelib.PyShell import main # This is subject to change
main()

17
Lib/idlelib/idle.pyw Normal file
View File

@@ -0,0 +1,17 @@
try:
import idlelib.PyShell
except ImportError:
# IDLE is not installed, but maybe PyShell is on sys.path:
import PyShell
import os
idledir = os.path.dirname(os.path.abspath(PyShell.__file__))
if idledir != os.getcwd():
# We're not in the IDLE directory, help the subprocess find run.py
pypath = os.environ.get('PYTHONPATH', '')
if pypath:
os.environ['PYTHONPATH'] = pypath + ':' + idledir
else:
os.environ['PYTHONPATH'] = idledir
PyShell.main()
else:
idlelib.PyShell.main()

View File

@@ -0,0 +1,150 @@
README FOR IDLE TESTS IN IDLELIB.IDLE_TEST
0. Quick Start
Automated unit tests were added in 2.7 for Python 2.x and 3.3 for Python 3.x.
To run the tests from a command line:
python -m test.test_idle
Human-mediated tests were added later in 2.7 and in 3.4.
python -m idlelib.idle_test.htest
1. Test Files
The idle directory, idlelib, has over 60 xyz.py files. The idle_test
subdirectory should contain a test_xyz.py for each, where 'xyz' is lowercased
even if xyz.py is not. Here is a possible template, with the blanks after
'.' and 'as', and before and after '_' to be filled in.
import unittest
from test.support import requires
import idlelib. as
class _Test(unittest.TestCase):
def test_(self):
if __name__ == '__main__':
unittest.main(verbosity=2)
Add the following at the end of xyy.py, with the appropriate name added after
'test_'. Some files already have something like this for htest. If so, insert
the import and unittest.main lines before the htest lines.
if __name__ == "__main__":
import unittest
unittest.main('idlelib.idle_test.test_', verbosity=2, exit=False)
2. GUI Tests
When run as part of the Python test suite, Idle GUI tests need to run
test.test_support.requires('gui') (test.support in 3.x). A test is a GUI test
if it creates a Tk root or master object either directly or indirectly by
instantiating a tkinter or idle class. For the benefit of test processes that
either have no graphical environment available or are not allowed to use it, GUI
tests must be 'guarded' by "requires('gui')" in a setUp function or method.
This will typically be setUpClass.
To avoid interfering with other GUI tests, all GUI objects must be destroyed and
deleted by the end of the test. The Tk root created in a setUpX function should
be destroyed in the corresponding tearDownX and the module or class attribute
deleted. Others widgets should descend from the single root and the attributes
deleted BEFORE root is destroyed. See https://bugs.python.org/issue20567.
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = tk.Tk()
cls.text = tk.Text(root)
@classmethod
def tearDownClass(cls):
del cls.text
cls.root.destroy()
del cls.root
WARNING: In 2.7, "requires('gui') MUST NOT be called at module scope.
See https://bugs.python.org/issue18910
Requires('gui') causes the test(s) it guards to be skipped if any of
these conditions are met:
- The tests are being run by regrtest.py, and it was started without enabling
the "gui" resource with the "-u" command line option.
- The tests are being run on Windows by a service that is not allowed to
interact with the graphical environment.
- The tests are being run on Linux and X Windows is not available.
- The tests are being run on Mac OSX in a process that cannot make a window
manager connection.
- tkinter.Tk cannot be successfully instantiated for some reason.
- test.support.use_resources has been set by something other than
regrtest.py and does not contain "gui".
Tests of non-GUI operations should avoid creating tk widgets. Incidental uses of
tk variables and messageboxes can be replaced by the mock classes in
idle_test/mock_tk.py. The mock text handles some uses of the tk Text widget.
3. Running Unit Tests
Assume that xyz.py and test_xyz.py both end with a unittest.main() call.
Running either from an Idle editor runs all tests in the test_xyz file with the
version of Python running Idle. Test output appears in the Shell window. The
'verbosity=2' option lists all test methods in the file, which is appropriate
when developing tests. The 'exit=False' option is needed in xyx.py files when an
htest follows.
The following command lines also run all test methods, including
GUI tests, in test_xyz.py. (Both '-m idlelib' and '-m idlelib.idle' start
Idle and so cannot run tests.)
python -m idlelib.xyz
python -m idlelib.idle_test.test_xyz
The following runs all idle_test/test_*.py tests interactively.
>>> import unittest
>>> unittest.main('idlelib.idle_test', verbosity=2)
The following run all Idle tests at a command line. Option '-v' is the same as
'verbosity=2'. (For 2.7, replace 'test' in the second line with
'test.regrtest'.)
python -m unittest -v idlelib.idle_test
python -m test -v -ugui test_idle
python -m test.test_idle
The idle tests are 'discovered' by idlelib.idle_test.__init__.load_tests,
which is also imported into test.test_idle. Normally, neither file should be
changed when working on individual test modules. The third command runs
unittest indirectly through regrtest. The same happens when the entire test
suite is run with 'python -m test'. So that command must work for buildbots
to stay green. Idle tests must not disturb the environment in a way that
makes other tests fail (issue 18081).
To run an individual Testcase or test method, extend the dotted name given to
unittest on the command line.
python -m unittest -v idlelib.idle_test.test_xyz.Test_case.test_meth
4. Human-mediated Tests
Human-mediated tests are widget tests that cannot be automated but need human
verification. They are contained in idlelib/idle_test/htest.py, which has
instructions. (Some modules need an auxiliary function, identified with # htest
# on the header line.) The set is about complete, though some tests need
improvement. To run all htests, run the htest file from an editor or from the
command line with:
python -m idlelib.idle_test.htest

View File

@@ -0,0 +1,15 @@
'''idlelib.idle_test is a private implementation of test.test_idle,
which tests the IDLE application as part of the stdlib test suite.
Run IDLE tests alone with "python -m test.test_idle".
This package and its contained modules are subject to change and
any direct use is at your own risk.
'''
from os.path import dirname
def load_tests(loader, standard_tests, pattern):
this_dir = dirname(__file__)
top_dir = dirname(dirname(this_dir))
package_tests = loader.discover(start_dir=this_dir, pattern='test*.py',
top_level_dir=top_dir)
standard_tests.addTests(package_tests)
return standard_tests

View File

@@ -0,0 +1,403 @@
'''Run human tests of Idle's window, dialog, and popup widgets.
run(*tests)
Create a master Tk window. Within that, run each callable in tests
after finding the matching test spec in this file. If tests is empty,
run an htest for each spec dict in this file after finding the matching
callable in the module named in the spec. Close the window to skip or
end the test.
In a tested module, let X be a global name bound to a callable (class
or function) whose .__name__ attrubute is also X (the usual situation).
The first parameter of X must be 'parent'. When called, the parent
argument will be the root window. X must create a child Toplevel
window (or subclass thereof). The Toplevel may be a test widget or
dialog, in which case the callable is the corresonding class. Or the
Toplevel may contain the widget to be tested or set up a context in
which a test widget is invoked. In this latter case, the callable is a
wrapper function that sets up the Toplevel and other objects. Wrapper
function names, such as _editor_window', should start with '_'.
End the module with
if __name__ == '__main__':
<unittest, if there is one>
from idlelib.idle_test.htest import run
run(X)
To have wrapper functions and test invocation code ignored by coveragepy
reports, put '# htest #' on the def statement header line.
def _wrapper(parent): # htest #
Also make sure that the 'if __name__' line matches the above. Then have
make sure that .coveragerc includes the following.
[report]
exclude_lines =
.*# htest #
if __name__ == .__main__.:
(The "." instead of "'" is intentional and necessary.)
To run any X, this file must contain a matching instance of the
following template, with X.__name__ prepended to '_spec'.
When all tests are run, the prefix is use to get X.
_spec = {
'file': '',
'kwds': {'title': ''},
'msg': ""
}
file (no .py): run() imports file.py.
kwds: augmented with {'parent':root} and passed to X as **kwds.
title: an example kwd; some widgets need this, delete if not.
msg: master window hints about testing the widget.
Modules and classes not being tested at the moment:
PyShell.PyShellEditorWindow
Debugger.Debugger
AutoCompleteWindow.AutoCompleteWindow
OutputWindow.OutputWindow (indirectly being tested with grep test)
'''
from importlib import import_module
from idlelib.macosxSupport import _initializeTkVariantTests
import Tkinter as tk
AboutDialog_spec = {
'file': 'aboutDialog',
'kwds': {'title': 'aboutDialog test',
'_htest': True,
},
'msg': "Test every button. Ensure Python, TK and IDLE versions "
"are correctly displayed.\n [Close] to exit.",
}
_calltip_window_spec = {
'file': 'CallTipWindow',
'kwds': {},
'msg': "Typing '(' should display a calltip.\n"
"Typing ') should hide the calltip.\n"
}
_class_browser_spec = {
'file': 'ClassBrowser',
'kwds': {},
'msg': "Inspect names of module, class(with superclass if "
"applicable), methods and functions.\nToggle nested items.\n"
"Double clicking on items prints a traceback for an exception "
"that is ignored."
}
_color_delegator_spec = {
'file': 'ColorDelegator',
'kwds': {},
'msg': "The text is sample Python code.\n"
"Ensure components like comments, keywords, builtins,\n"
"string, definitions, and break are correctly colored.\n"
"The default color scheme is in idlelib/config-highlight.def"
}
ConfigDialog_spec = {
'file': 'configDialog',
'kwds': {'title': 'ConfigDialogTest',
'_htest': True,},
'msg': "IDLE preferences dialog.\n"
"In the 'Fonts/Tabs' tab, changing font face, should update the "
"font face of the text in the area below it.\nIn the "
"'Highlighting' tab, try different color schemes. Clicking "
"items in the sample program should update the choices above it."
"\nIn the 'Keys', 'General' and 'Extensions' tabs, test settings "
"of interest."
"\n[Ok] to close the dialog.[Apply] to apply the settings and "
"and [Cancel] to revert all changes.\nRe-run the test to ensure "
"changes made have persisted."
}
# TODO Improve message
_dyn_option_menu_spec = {
'file': 'dynOptionMenuWidget',
'kwds': {},
'msg': "Select one of the many options in the 'old option set'.\n"
"Click the button to change the option set.\n"
"Select one of the many options in the 'new option set'."
}
# TODO edit wrapper
_editor_window_spec = {
'file': 'EditorWindow',
'kwds': {},
'msg': "Test editor functions of interest.\n"
"Best to close editor first."
}
GetCfgSectionNameDialog_spec = {
'file': 'configSectionNameDialog',
'kwds': {'title':'Get Name',
'message':'Enter something',
'used_names': {'abc'},
'_htest': True},
'msg': "After the text entered with [Ok] is stripped, <nothing>, "
"'abc', or more that 30 chars are errors.\n"
"Close 'Get Name' with a valid entry (printed to Shell), "
"[Cancel], or [X]",
}
GetHelpSourceDialog_spec = {
'file': 'configHelpSourceEdit',
'kwds': {'title': 'Get helpsource',
'_htest': True},
'msg': "Enter menu item name and help file path\n "
"<nothing> and more than 30 chars are invalid menu item names.\n"
"<nothing>, file does not exist are invalid path items.\n"
"Test for incomplete web address for help file path.\n"
"A valid entry will be printed to shell with [0k].\n"
"[Cancel] will print None to shell",
}
# Update once issue21519 is resolved.
GetKeysDialog_spec = {
'file': 'keybindingDialog',
'kwds': {'title': 'Test keybindings',
'action': 'find-again',
'currentKeySequences': [''] ,
'_htest': True,
},
'msg': "Test for different key modifier sequences.\n"
"<nothing> is invalid.\n"
"No modifier key is invalid.\n"
"Shift key with [a-z],[0-9], function key, move key, tab, space "
"is invalid.\nNo validitity checking if advanced key binding "
"entry is used."
}
_grep_dialog_spec = {
'file': 'GrepDialog',
'kwds': {},
'msg': "Click the 'Show GrepDialog' button.\n"
"Test the various 'Find-in-files' functions.\n"
"The results should be displayed in a new '*Output*' window.\n"
"'Right-click'->'Goto file/line' anywhere in the search results "
"should open that file \nin a new EditorWindow."
}
_io_binding_spec = {
'file': 'IOBinding',
'kwds': {},
'msg': "Test the following bindings.\n"
"<Control-o> to open file from dialog.\n"
"Edit the file.\n"
"<Control-p> to print the file.\n"
"<Control-s> to save the file.\n"
"<Alt-s> to save-as another file.\n"
"<Control-c> to save-copy-as another file.\n"
"Check that changes were saved by opening the file elsewhere."
}
_multi_call_spec = {
'file': 'MultiCall',
'kwds': {},
'msg': "The following actions should trigger a print to console or IDLE"
" Shell.\nEntering and leaving the text area, key entry, "
"<Control-Key>,\n<Alt-Key-a>, <Control-Key-a>, "
"<Alt-Control-Key-a>, \n<Control-Button-1>, <Alt-Button-1> and "
"focusing out of the window\nare sequences to be tested."
}
_multistatus_bar_spec = {
'file': 'MultiStatusBar',
'kwds': {},
'msg': "Ensure presence of multi-status bar below text area.\n"
"Click 'Update Status' to change the multi-status text"
}
_object_browser_spec = {
'file': 'ObjectBrowser',
'kwds': {},
'msg': "Double click on items upto the lowest level.\n"
"Attributes of the objects and related information "
"will be displayed side-by-side at each level."
}
_path_browser_spec = {
'file': 'PathBrowser',
'kwds': {},
'msg': "Test for correct display of all paths in sys.path.\n"
"Toggle nested items upto the lowest level.\n"
"Double clicking on an item prints a traceback\n"
"for an exception that is ignored."
}
_percolator_spec = {
'file': 'Percolator',
'kwds': {},
'msg': "There are two tracers which can be toggled using a checkbox.\n"
"Toggling a tracer 'on' by checking it should print tracer "
"output to the console or to the IDLE shell.\n"
"If both the tracers are 'on', the output from the tracer which "
"was switched 'on' later, should be printed first\n"
"Test for actions like text entry, and removal."
}
_replace_dialog_spec = {
'file': 'ReplaceDialog',
'kwds': {},
'msg': "Click the 'Replace' button.\n"
"Test various replace options in the 'Replace dialog'.\n"
"Click [Close] or [X] to close the 'Replace Dialog'."
}
_search_dialog_spec = {
'file': 'SearchDialog',
'kwds': {},
'msg': "Click the 'Search' button.\n"
"Test various search options in the 'Search dialog'.\n"
"Click [Close] or [X] to close the 'Search Dialog'."
}
_scrolled_list_spec = {
'file': 'ScrolledList',
'kwds': {},
'msg': "You should see a scrollable list of items\n"
"Selecting (clicking) or double clicking an item "
"prints the name to the console or Idle shell.\n"
"Right clicking an item will display a popup."
}
show_idlehelp_spec = {
'file': 'help',
'kwds': {},
'msg': "If the help text displays, this works.\n"
"Text is selectable. Window is scrollable."
}
_stack_viewer_spec = {
'file': 'StackViewer',
'kwds': {},
'msg': "A stacktrace for a NameError exception.\n"
"Expand 'idlelib ...' and '<locals>'.\n"
"Check that exc_value, exc_tb, and exc_type are correct.\n"
}
_tabbed_pages_spec = {
'file': 'tabbedpages',
'kwds': {},
'msg': "Toggle between the two tabs 'foo' and 'bar'\n"
"Add a tab by entering a suitable name for it.\n"
"Remove an existing tab by entering its name.\n"
"Remove all existing tabs.\n"
"<nothing> is an invalid add page and remove page name.\n"
}
TextViewer_spec = {
'file': 'textView',
'kwds': {'title': 'Test textView',
'text':'The quick brown fox jumps over the lazy dog.\n'*35,
'_htest': True},
'msg': "Test for read-only property of text.\n"
"Text is selectable. Window is scrollable.",
}
_tooltip_spec = {
'file': 'ToolTip',
'kwds': {},
'msg': "Place mouse cursor over both the buttons\n"
"A tooltip should appear with some text."
}
_tree_widget_spec = {
'file': 'TreeWidget',
'kwds': {},
'msg': "The canvas is scrollable.\n"
"Click on folders upto to the lowest level."
}
_undo_delegator_spec = {
'file': 'UndoDelegator',
'kwds': {},
'msg': "Click [Undo] to undo any action.\n"
"Click [Redo] to redo any action.\n"
"Click [Dump] to dump the current state "
"by printing to the console or the IDLE shell.\n"
}
_widget_redirector_spec = {
'file': 'WidgetRedirector',
'kwds': {},
'msg': "Every text insert should be printed to the console "
"or the IDLE shell."
}
def run(*tests):
root = tk.Tk()
root.title('IDLE htest')
root.resizable(0, 0)
_initializeTkVariantTests(root)
# a scrollable Label like constant width text widget.
frameLabel = tk.Frame(root, padx=10)
frameLabel.pack()
text = tk.Text(frameLabel, wrap='word')
text.configure(bg=root.cget('bg'), relief='flat', height=4, width=70)
scrollbar = tk.Scrollbar(frameLabel, command=text.yview)
text.config(yscrollcommand=scrollbar.set)
scrollbar.pack(side='right', fill='y', expand=False)
text.pack(side='left', fill='both', expand=True)
test_list = [] # List of tuples of the form (spec, callable widget)
if tests:
for test in tests:
test_spec = globals()[test.__name__ + '_spec']
test_spec['name'] = test.__name__
test_list.append((test_spec, test))
else:
for k, d in globals().items():
if k.endswith('_spec'):
test_name = k[:-5]
test_spec = d
test_spec['name'] = test_name
mod = import_module('idlelib.' + test_spec['file'])
test = getattr(mod, test_name)
test_list.append((test_spec, test))
test_name = [tk.StringVar('')]
callable_object = [None]
test_kwds = [None]
def next():
if len(test_list) == 1:
next_button.pack_forget()
test_spec, callable_object[0] = test_list.pop()
test_kwds[0] = test_spec['kwds']
test_kwds[0]['parent'] = root
test_name[0].set('Test ' + test_spec['name'])
text.configure(state='normal') # enable text editing
text.delete('1.0','end')
text.insert("1.0",test_spec['msg'])
text.configure(state='disabled') # preserve read-only property
def run_test():
widget = callable_object[0](**test_kwds[0])
try:
print(widget.result)
except AttributeError:
pass
button = tk.Button(root, textvariable=test_name[0], command=run_test)
button.pack()
next_button = tk.Button(root, text="Next", command=next)
next_button.pack()
next()
root.mainloop()
if __name__ == '__main__':
run()

View File

@@ -0,0 +1,55 @@
'''Mock classes that imitate idlelib modules or classes.
Attributes and methods will be added as needed for tests.
'''
from idlelib.idle_test.mock_tk import Text
class Func(object):
'''Mock function captures args and returns result set by test.
Attributes:
self.called - records call even if no args, kwds passed.
self.result - set by init, returned by call.
self.args - captures positional arguments.
self.kwds - captures keyword arguments.
Most common use will probably be to mock methods.
Mock_tk.Var and Mbox_func are special variants of this.
'''
def __init__(self, result=None):
self.called = False
self.result = result
self.args = None
self.kwds = None
def __call__(self, *args, **kwds):
self.called = True
self.args = args
self.kwds = kwds
if isinstance(self.result, BaseException):
raise self.result
else:
return self.result
class Editor(object):
'''Minimally imitate EditorWindow.EditorWindow class.
'''
def __init__(self, flist=None, filename=None, key=None, root=None):
self.text = Text()
self.undo = UndoDelegator()
def get_selection_indices(self):
first = self.text.index('1.0')
last = self.text.index('end')
return first, last
class UndoDelegator(object):
'''Minimally imitate UndoDelegator,UndoDelegator class.
'''
# A real undo block is only needed for user interaction.
def undo_block_start(*args):
pass
def undo_block_stop(*args):
pass

View File

@@ -0,0 +1,298 @@
"""Classes that replace tkinter gui objects used by an object being tested.
A gui object is anything with a master or parent parameter, which is
typically required in spite of what the doc strings say.
"""
class Event(object):
'''Minimal mock with attributes for testing event handlers.
This is not a gui object, but is used as an argument for callbacks
that access attributes of the event passed. If a callback ignores
the event, other than the fact that is happened, pass 'event'.
Keyboard, mouse, window, and other sources generate Event instances.
Event instances have the following attributes: serial (number of
event), time (of event), type (of event as number), widget (in which
event occurred), and x,y (position of mouse). There are other
attributes for specific events, such as keycode for key events.
tkinter.Event.__doc__ has more but is still not complete.
'''
def __init__(self, **kwds):
"Create event with attributes needed for test"
self.__dict__.update(kwds)
class Var(object):
"Use for String/Int/BooleanVar: incomplete"
def __init__(self, master=None, value=None, name=None):
self.master = master
self.value = value
self.name = name
def set(self, value):
self.value = value
def get(self):
return self.value
class Mbox_func(object):
"""Generic mock for messagebox functions, which all have the same signature.
Instead of displaying a message box, the mock's call method saves the
arguments as instance attributes, which test functions can then examime.
The test can set the result returned to ask function
"""
def __init__(self, result=None):
self.result = result # Return None for all show funcs
def __call__(self, title, message, *args, **kwds):
# Save all args for possible examination by tester
self.title = title
self.message = message
self.args = args
self.kwds = kwds
return self.result # Set by tester for ask functions
class Mbox(object):
"""Mock for tkinter.messagebox with an Mbox_func for each function.
This module was 'tkMessageBox' in 2.x; hence the 'import as' in 3.x.
Example usage in test_module.py for testing functions in module.py:
---
from idlelib.idle_test.mock_tk import Mbox
import module
orig_mbox = module.tkMessageBox
showerror = Mbox.showerror # example, for attribute access in test methods
class Test(unittest.TestCase):
@classmethod
def setUpClass(cls):
module.tkMessageBox = Mbox
@classmethod
def tearDownClass(cls):
module.tkMessageBox = orig_mbox
---
For 'ask' functions, set func.result return value before calling the method
that uses the message function. When tkMessageBox functions are the
only gui alls in a method, this replacement makes the method gui-free,
"""
askokcancel = Mbox_func() # True or False
askquestion = Mbox_func() # 'yes' or 'no'
askretrycancel = Mbox_func() # True or False
askyesno = Mbox_func() # True or False
askyesnocancel = Mbox_func() # True, False, or None
showerror = Mbox_func() # None
showinfo = Mbox_func() # None
showwarning = Mbox_func() # None
from _tkinter import TclError
class Text(object):
"""A semi-functional non-gui replacement for tkinter.Text text editors.
The mock's data model is that a text is a list of \n-terminated lines.
The mock adds an empty string at the beginning of the list so that the
index of actual lines start at 1, as with Tk. The methods never see this.
Tk initializes files with a terminal \n that cannot be deleted. It is
invisible in the sense that one cannot move the cursor beyond it.
This class is only tested (and valid) with strings of ascii chars.
For testing, we are not concerned with Tk Text's treatment of,
for instance, 0-width characters or character + accent.
"""
def __init__(self, master=None, cnf={}, **kw):
'''Initialize mock, non-gui, text-only Text widget.
At present, all args are ignored. Almost all affect visual behavior.
There are just a few Text-only options that affect text behavior.
'''
self.data = ['', '\n']
def index(self, index):
"Return string version of index decoded according to current text."
return "%s.%s" % self._decode(index, endflag=1)
def _decode(self, index, endflag=0):
"""Return a (line, char) tuple of int indexes into self.data.
This implements .index without converting the result back to a string.
The result is contrained by the number of lines and linelengths of
self.data. For many indexes, the result is initially (1, 0).
The input index may have any of several possible forms:
* line.char float: converted to 'line.char' string;
* 'line.char' string, where line and char are decimal integers;
* 'line.char lineend', where lineend='lineend' (and char is ignored);
* 'line.end', where end='end' (same as above);
* 'insert', the positions before terminal \n;
* 'end', whose meaning depends on the endflag passed to ._endex.
* 'sel.first' or 'sel.last', where sel is a tag -- not implemented.
"""
if isinstance(index, (float, bytes)):
index = str(index)
try:
index=index.lower()
except AttributeError:
raise TclError('bad text index "%s"' % index)
lastline = len(self.data) - 1 # same as number of text lines
if index == 'insert':
return lastline, len(self.data[lastline]) - 1
elif index == 'end':
return self._endex(endflag)
line, char = index.split('.')
line = int(line)
# Out of bounds line becomes first or last ('end') index
if line < 1:
return 1, 0
elif line > lastline:
return self._endex(endflag)
linelength = len(self.data[line]) -1 # position before/at \n
if char.endswith(' lineend') or char == 'end':
return line, linelength
# Tk requires that ignored chars before ' lineend' be valid int
# Out of bounds char becomes first or last index of line
char = int(char)
if char < 0:
char = 0
elif char > linelength:
char = linelength
return line, char
def _endex(self, endflag):
'''Return position for 'end' or line overflow corresponding to endflag.
-1: position before terminal \n; for .insert(), .delete
0: position after terminal \n; for .get, .delete index 1
1: same viewed as beginning of non-existent next line (for .index)
'''
n = len(self.data)
if endflag == 1:
return n, 0
else:
n -= 1
return n, len(self.data[n]) + endflag
def insert(self, index, chars):
"Insert chars before the character at index."
if not chars: # ''.splitlines() is [], not ['']
return
chars = chars.splitlines(True)
if chars[-1][-1] == '\n':
chars.append('')
line, char = self._decode(index, -1)
before = self.data[line][:char]
after = self.data[line][char:]
self.data[line] = before + chars[0]
self.data[line+1:line+1] = chars[1:]
self.data[line+len(chars)-1] += after
def get(self, index1, index2=None):
"Return slice from index1 to index2 (default is 'index1+1')."
startline, startchar = self._decode(index1)
if index2 is None:
endline, endchar = startline, startchar+1
else:
endline, endchar = self._decode(index2)
if startline == endline:
return self.data[startline][startchar:endchar]
else:
lines = [self.data[startline][startchar:]]
for i in range(startline+1, endline):
lines.append(self.data[i])
lines.append(self.data[endline][:endchar])
return ''.join(lines)
def delete(self, index1, index2=None):
'''Delete slice from index1 to index2 (default is 'index1+1').
Adjust default index2 ('index+1) for line ends.
Do not delete the terminal \n at the very end of self.data ([-1][-1]).
'''
startline, startchar = self._decode(index1, -1)
if index2 is None:
if startchar < len(self.data[startline])-1:
# not deleting \n
endline, endchar = startline, startchar+1
elif startline < len(self.data) - 1:
# deleting non-terminal \n, convert 'index1+1 to start of next line
endline, endchar = startline+1, 0
else:
# do not delete terminal \n if index1 == 'insert'
return
else:
endline, endchar = self._decode(index2, -1)
# restricting end position to insert position excludes terminal \n
if startline == endline and startchar < endchar:
self.data[startline] = self.data[startline][:startchar] + \
self.data[startline][endchar:]
elif startline < endline:
self.data[startline] = self.data[startline][:startchar] + \
self.data[endline][endchar:]
startline += 1
for i in range(startline, endline+1):
del self.data[startline]
def compare(self, index1, op, index2):
line1, char1 = self._decode(index1)
line2, char2 = self._decode(index2)
if op == '<':
return line1 < line2 or line1 == line2 and char1 < char2
elif op == '<=':
return line1 < line2 or line1 == line2 and char1 <= char2
elif op == '>':
return line1 > line2 or line1 == line2 and char1 > char2
elif op == '>=':
return line1 > line2 or line1 == line2 and char1 >= char2
elif op == '==':
return line1 == line2 and char1 == char2
elif op == '!=':
return line1 != line2 or char1 != char2
else:
raise TclError('''bad comparison operator "%s": '''
'''must be <, <=, ==, >=, >, or !=''' % op)
# The following Text methods normally do something and return None.
# Whether doing nothing is sufficient for a test will depend on the test.
def mark_set(self, name, index):
"Set mark *name* before the character at index."
pass
def mark_unset(self, *markNames):
"Delete all marks in markNames."
def tag_remove(self, tagName, index1, index2=None):
"Remove tag tagName from all characters between index1 and index2."
pass
# The following Text methods affect the graphics screen and return None.
# Doing nothing should always be sufficient for tests.
def scan_dragto(self, x, y):
"Adjust the view of the text according to scan_mark"
def scan_mark(self, x, y):
"Remember the current X, Y coordinates."
def see(self, index):
"Scroll screen to make the character at INDEX is visible."
pass
# The following is a Misc method inherited by Text.
# It should properly go in a Misc mock, but is included here for now.
def bind(sequence=None, func=None, add=None):
"Bind to this widget at event sequence a call to function func."
pass

View File

@@ -0,0 +1,140 @@
import unittest
from test.test_support import requires
from Tkinter import Tk, Text
import idlelib.AutoComplete as ac
import idlelib.AutoCompleteWindow as acw
from idlelib.idle_test.mock_idle import Func
from idlelib.idle_test.mock_tk import Event
class AutoCompleteWindow:
def complete():
return
class DummyEditwin:
def __init__(self, root, text):
self.root = root
self.text = text
self.indentwidth = 8
self.tabwidth = 8
self.context_use_ps1 = True
class AutoCompleteTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.text = Text(cls.root)
cls.editor = DummyEditwin(cls.root, cls.text)
@classmethod
def tearDownClass(cls):
del cls.editor, cls.text
cls.root.destroy()
del cls.root
def setUp(self):
self.editor.text.delete('1.0', 'end')
self.autocomplete = ac.AutoComplete(self.editor)
def test_init(self):
self.assertEqual(self.autocomplete.editwin, self.editor)
def test_make_autocomplete_window(self):
testwin = self.autocomplete._make_autocomplete_window()
self.assertIsInstance(testwin, acw.AutoCompleteWindow)
def test_remove_autocomplete_window(self):
self.autocomplete.autocompletewindow = (
self.autocomplete._make_autocomplete_window())
self.autocomplete._remove_autocomplete_window()
self.assertIsNone(self.autocomplete.autocompletewindow)
def test_force_open_completions_event(self):
# Test that force_open_completions_event calls _open_completions
o_cs = Func()
self.autocomplete.open_completions = o_cs
self.autocomplete.force_open_completions_event('event')
self.assertEqual(o_cs.args, (True, False, True))
def test_try_open_completions_event(self):
Equal = self.assertEqual
autocomplete = self.autocomplete
trycompletions = self.autocomplete.try_open_completions_event
o_c_l = Func()
autocomplete._open_completions_later = o_c_l
# _open_completions_later should not be called with no text in editor
trycompletions('event')
Equal(o_c_l.args, None)
# _open_completions_later should be called with COMPLETE_ATTRIBUTES (1)
self.text.insert('1.0', 're.')
trycompletions('event')
Equal(o_c_l.args, (False, False, False, 1))
# _open_completions_later should be called with COMPLETE_FILES (2)
self.text.delete('1.0', 'end')
self.text.insert('1.0', '"./Lib/')
trycompletions('event')
Equal(o_c_l.args, (False, False, False, 2))
def test_autocomplete_event(self):
Equal = self.assertEqual
autocomplete = self.autocomplete
# Test that the autocomplete event is ignored if user is pressing a
# modifier key in addition to the tab key
ev = Event(mc_state=True)
self.assertIsNone(autocomplete.autocomplete_event(ev))
del ev.mc_state
# If autocomplete window is open, complete() method is called
self.text.insert('1.0', 're.')
# This must call autocomplete._make_autocomplete_window()
Equal(self.autocomplete.autocomplete_event(ev), 'break')
# If autocomplete window is not active or does not exist,
# open_completions is called. Return depends on its return.
autocomplete._remove_autocomplete_window()
o_cs = Func() # .result = None
autocomplete.open_completions = o_cs
Equal(self.autocomplete.autocomplete_event(ev), None)
Equal(o_cs.args, (False, True, True))
o_cs.result = True
Equal(self.autocomplete.autocomplete_event(ev), 'break')
Equal(o_cs.args, (False, True, True))
def test_open_completions_later(self):
# Test that autocomplete._delayed_completion_id is set
pass
def test_delayed_open_completions(self):
# Test that autocomplete._delayed_completion_id set to None and that
# open_completions only called if insertion index is the same as
# _delayed_completion_index
pass
def test_open_completions(self):
# Test completions of files and attributes as well as non-completion
# of errors
pass
def test_fetch_completions(self):
# Test that fetch_completions returns 2 lists:
# For attribute completion, a large list containing all variables, and
# a small list containing non-private variables.
# For file completion, a large list containing all files in the path,
# and a small list containing files that do not start with '.'
pass
def test_get_entity(self):
# Test that a name is in the namespace of sys.modules and
# __main__.__dict__
pass
if __name__ == '__main__':
unittest.main(verbosity=2)

View File

@@ -0,0 +1,141 @@
"""Unit tests for idlelib.AutoExpand"""
import unittest
from test.test_support import requires
from Tkinter import Text, Tk
#from idlelib.idle_test.mock_tk import Text
from idlelib.AutoExpand import AutoExpand
class Dummy_Editwin:
# AutoExpand.__init__ only needs .text
def __init__(self, text):
self.text = text
class AutoExpandTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
if 'Tkinter' in str(Text):
requires('gui')
cls.tk = Tk()
cls.text = Text(cls.tk)
else:
cls.text = Text()
cls.auto_expand = AutoExpand(Dummy_Editwin(cls.text))
@classmethod
def tearDownClass(cls):
del cls.text, cls.auto_expand
if hasattr(cls, 'tk'):
cls.tk.destroy()
del cls.tk
def tearDown(self):
self.text.delete('1.0', 'end')
def test_get_prevword(self):
text = self.text
previous = self.auto_expand.getprevword
equal = self.assertEqual
equal(previous(), '')
text.insert('insert', 't')
equal(previous(), 't')
text.insert('insert', 'his')
equal(previous(), 'this')
text.insert('insert', ' ')
equal(previous(), '')
text.insert('insert', 'is')
equal(previous(), 'is')
text.insert('insert', '\nsample\nstring')
equal(previous(), 'string')
text.delete('3.0', 'insert')
equal(previous(), '')
text.delete('1.0', 'end')
equal(previous(), '')
def test_before_only(self):
previous = self.auto_expand.getprevword
expand = self.auto_expand.expand_word_event
equal = self.assertEqual
self.text.insert('insert', 'ab ac bx ad ab a')
equal(self.auto_expand.getwords(), ['ab', 'ad', 'ac', 'a'])
expand('event')
equal(previous(), 'ab')
expand('event')
equal(previous(), 'ad')
expand('event')
equal(previous(), 'ac')
expand('event')
equal(previous(), 'a')
def test_after_only(self):
# Also add punctuation 'noise' that shoud be ignored.
text = self.text
previous = self.auto_expand.getprevword
expand = self.auto_expand.expand_word_event
equal = self.assertEqual
text.insert('insert', 'a, [ab] ac: () bx"" cd ac= ad ya')
text.mark_set('insert', '1.1')
equal(self.auto_expand.getwords(), ['ab', 'ac', 'ad', 'a'])
expand('event')
equal(previous(), 'ab')
expand('event')
equal(previous(), 'ac')
expand('event')
equal(previous(), 'ad')
expand('event')
equal(previous(), 'a')
def test_both_before_after(self):
text = self.text
previous = self.auto_expand.getprevword
expand = self.auto_expand.expand_word_event
equal = self.assertEqual
text.insert('insert', 'ab xy yz\n')
text.insert('insert', 'a ac by ac')
text.mark_set('insert', '2.1')
equal(self.auto_expand.getwords(), ['ab', 'ac', 'a'])
expand('event')
equal(previous(), 'ab')
expand('event')
equal(previous(), 'ac')
expand('event')
equal(previous(), 'a')
def test_other_expand_cases(self):
text = self.text
expand = self.auto_expand.expand_word_event
equal = self.assertEqual
# no expansion candidate found
equal(self.auto_expand.getwords(), [])
equal(expand('event'), 'break')
text.insert('insert', 'bx cy dz a')
equal(self.auto_expand.getwords(), [])
# reset state by successfully expanding once
# move cursor to another position and expand again
text.insert('insert', 'ac xy a ac ad a')
text.mark_set('insert', '1.7')
expand('event')
initial_state = self.auto_expand.state
text.mark_set('insert', '1.end')
expand('event')
new_state = self.auto_expand.state
self.assertNotEqual(initial_state, new_state)
if __name__ == '__main__':
unittest.main(verbosity=2)

View File

@@ -0,0 +1,185 @@
import unittest
import idlelib.CallTips as ct
CTi = ct.CallTips() # needed for get_entity test in 2.7
import textwrap
import types
import warnings
default_tip = ''
# Test Class TC is used in multiple get_argspec test methods
class TC(object):
'doc'
tip = "(ai=None, *args)"
def __init__(self, ai=None, *b): 'doc'
__init__.tip = "(self, ai=None, *args)"
def t1(self): 'doc'
t1.tip = "(self)"
def t2(self, ai, b=None): 'doc'
t2.tip = "(self, ai, b=None)"
def t3(self, ai, *args): 'doc'
t3.tip = "(self, ai, *args)"
def t4(self, *args): 'doc'
t4.tip = "(self, *args)"
def t5(self, ai, b=None, *args, **kw): 'doc'
t5.tip = "(self, ai, b=None, *args, **kwargs)"
def t6(no, self): 'doc'
t6.tip = "(no, self)"
def __call__(self, ci): 'doc'
__call__.tip = "(self, ci)"
# attaching .tip to wrapped methods does not work
@classmethod
def cm(cls, a): 'doc'
@staticmethod
def sm(b): 'doc'
tc = TC()
signature = ct.get_arg_text # 2.7 and 3.x use different functions
class Get_signatureTest(unittest.TestCase):
# The signature function must return a string, even if blank.
# Test a variety of objects to be sure that none cause it to raise
# (quite aside from getting as correct an answer as possible).
# The tests of builtins may break if the docstrings change,
# but a red buildbot is better than a user crash (as has happened).
# For a simple mismatch, change the expected output to the actual.
def test_builtins(self):
# 2.7 puts '()\n' where 3.x does not, other minor differences
# Python class that inherits builtin methods
class List(list): "List() doc"
# Simulate builtin with no docstring for default argspec test
class SB: __call__ = None
def gtest(obj, out):
self.assertEqual(signature(obj), out)
if List.__doc__ is not None:
gtest(List, '()\n' + List.__doc__)
gtest(list.__new__,
'T.__new__(S, ...) -> a new object with type S, a subtype of T')
gtest(list.__init__,
'x.__init__(...) initializes x; see help(type(x)) for signature')
append_doc = "L.append(object) -- append object to end"
gtest(list.append, append_doc)
gtest([].append, append_doc)
gtest(List.append, append_doc)
gtest(types.MethodType, '()\ninstancemethod(function, instance, class)')
gtest(SB(), default_tip)
def test_signature_wrap(self):
# This is also a test of an old-style class
if textwrap.TextWrapper.__doc__ is not None:
self.assertEqual(signature(textwrap.TextWrapper), '''\
(width=70, initial_indent='', subsequent_indent='', expand_tabs=True,
replace_whitespace=True, fix_sentence_endings=False, break_long_words=True,
drop_whitespace=True, break_on_hyphens=True)''')
def test_docline_truncation(self):
def f(): pass
f.__doc__ = 'a'*300
self.assertEqual(signature(f), '()\n' + 'a' * (ct._MAX_COLS-3) + '...')
def test_multiline_docstring(self):
# Test fewer lines than max.
self.assertEqual(signature(list),
"()\nlist() -> new empty list\n"
"list(iterable) -> new list initialized from iterable's items")
# Test max lines and line (currently) too long.
def f():
pass
s = 'a\nb\nc\nd\n'
f.__doc__ = s + 300 * 'e' + 'f'
self.assertEqual(signature(f),
'()\n' + s + (ct._MAX_COLS - 3) * 'e' + '...')
def test_functions(self):
def t1(): 'doc'
t1.tip = "()"
def t2(a, b=None): 'doc'
t2.tip = "(a, b=None)"
def t3(a, *args): 'doc'
t3.tip = "(a, *args)"
def t4(*args): 'doc'
t4.tip = "(*args)"
def t5(a, b=None, *args, **kwds): 'doc'
t5.tip = "(a, b=None, *args, **kwargs)"
doc = '\ndoc' if t1.__doc__ is not None else ''
for func in (t1, t2, t3, t4, t5, TC):
self.assertEqual(signature(func), func.tip + doc)
def test_methods(self):
doc = '\ndoc' if TC.__doc__ is not None else ''
for meth in (TC.t1, TC.t2, TC.t3, TC.t4, TC.t5, TC.t6, TC.__call__):
self.assertEqual(signature(meth), meth.tip + doc)
self.assertEqual(signature(TC.cm), "(a)" + doc)
self.assertEqual(signature(TC.sm), "(b)" + doc)
def test_bound_methods(self):
# test that first parameter is correctly removed from argspec
doc = '\ndoc' if TC.__doc__ is not None else ''
for meth, mtip in ((tc.t1, "()"), (tc.t4, "(*args)"), (tc.t6, "(self)"),
(tc.__call__, '(ci)'), (tc, '(ci)'), (TC.cm, "(a)"),):
self.assertEqual(signature(meth), mtip + doc)
def test_starred_parameter(self):
# test that starred first parameter is *not* removed from argspec
class C:
def m1(*args): pass
def m2(**kwds): pass
def f1(args, kwargs, *a, **k): pass
def f2(args, kwargs, args1, kwargs1, *a, **k): pass
c = C()
self.assertEqual(signature(C.m1), '(*args)')
self.assertEqual(signature(c.m1), '(*args)')
self.assertEqual(signature(C.m2), '(**kwargs)')
self.assertEqual(signature(c.m2), '(**kwargs)')
self.assertEqual(signature(f1), '(args, kwargs, *args1, **kwargs1)')
self.assertEqual(signature(f2),
'(args, kwargs, args1, kwargs1, *args2, **kwargs2)')
def test_no_docstring(self):
def nd(s): pass
TC.nd = nd
self.assertEqual(signature(nd), "(s)")
self.assertEqual(signature(TC.nd), "(s)")
self.assertEqual(signature(tc.nd), "()")
def test_attribute_exception(self):
class NoCall(object):
def __getattr__(self, name):
raise BaseException
class Call(NoCall):
def __call__(self, ci):
pass
for meth, mtip in ((NoCall, '()'), (Call, '()'),
(NoCall(), ''), (Call(), '(ci)')):
self.assertEqual(signature(meth), mtip)
def test_non_callables(self):
for obj in (0, 0.0, '0', b'0', [], {}):
self.assertEqual(signature(obj), '')
class Get_entityTest(unittest.TestCase):
# In 3.x, get_entity changed from 'instance method' to module function
# since 'self' not used. Use dummy instance until change 2.7 also.
def test_bad_entity(self):
self.assertIsNone(CTi.get_entity('1//0'))
def test_good_entity(self):
self.assertIs(CTi.get_entity('int'), int)
class Py2Test(unittest.TestCase):
def test_paramtuple_float(self):
# 18539: (a,b) becomes '.0' in code object; change that but not 0.0
with warnings.catch_warnings():
# Suppess message of py3 deprecation of parameter unpacking
warnings.simplefilter("ignore")
exec "def f((a,b), c=0.0): pass"
self.assertEqual(signature(f), '(<tuple>, c=0.0)')
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)

View File

@@ -0,0 +1,77 @@
"""Unit tests for idlelib.configSectionNameDialog"""
import unittest
from idlelib.idle_test.mock_tk import Var, Mbox
from idlelib import configSectionNameDialog as name_dialog_module
name_dialog = name_dialog_module.GetCfgSectionNameDialog
class Dummy_name_dialog(object):
# Mock for testing the following methods of name_dialog
name_ok = name_dialog.name_ok.im_func
Ok = name_dialog.Ok.im_func
Cancel = name_dialog.Cancel.im_func
# Attributes, constant or variable, needed for tests
used_names = ['used']
name = Var()
result = None
destroyed = False
def grab_release(self):
pass
def destroy(self):
self.destroyed = True
# name_ok calls Mbox.showerror if name is not ok
orig_mbox = name_dialog_module.tkMessageBox
showerror = Mbox.showerror
class ConfigNameTest(unittest.TestCase):
dialog = Dummy_name_dialog()
@classmethod
def setUpClass(cls):
name_dialog_module.tkMessageBox = Mbox
@classmethod
def tearDownClass(cls):
name_dialog_module.tkMessageBox = orig_mbox
def test_blank_name(self):
self.dialog.name.set(' ')
self.assertEqual(self.dialog.name_ok(), '')
self.assertEqual(showerror.title, 'Name Error')
self.assertIn('No', showerror.message)
def test_used_name(self):
self.dialog.name.set('used')
self.assertEqual(self.dialog.name_ok(), '')
self.assertEqual(showerror.title, 'Name Error')
self.assertIn('use', showerror.message)
def test_long_name(self):
self.dialog.name.set('good'*8)
self.assertEqual(self.dialog.name_ok(), '')
self.assertEqual(showerror.title, 'Name Error')
self.assertIn('too long', showerror.message)
def test_good_name(self):
self.dialog.name.set(' good ')
showerror.title = 'No Error' # should not be called
self.assertEqual(self.dialog.name_ok(), 'good')
self.assertEqual(showerror.title, 'No Error')
def test_ok(self):
self.dialog.destroyed = False
self.dialog.name.set('good')
self.dialog.Ok()
self.assertEqual(self.dialog.result, 'good')
self.assertTrue(self.dialog.destroyed)
def test_cancel(self):
self.dialog.destroyed = False
self.dialog.Cancel()
self.assertEqual(self.dialog.result, '')
self.assertTrue(self.dialog.destroyed)
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)

View File

@@ -0,0 +1,33 @@
'''Unittests for idlelib/configHandler.py
Coverage: 46% just by creating dialog. The other half is change code.
'''
import unittest
from test.test_support import requires
from Tkinter import Tk
from idlelib.configDialog import ConfigDialog
from idlelib.macosxSupport import _initializeTkVariantTests
class ConfigDialogTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
_initializeTkVariantTests(cls.root)
@classmethod
def tearDownClass(cls):
cls.root.destroy()
del cls.root
def test_dialog(self):
d = ConfigDialog(self.root, 'Test', _utest=True)
d.remove_var_callbacks()
if __name__ == '__main__':
unittest.main(verbosity=2)

View File

@@ -0,0 +1,37 @@
import unittest
from idlelib.Delegator import Delegator
class DelegatorTest(unittest.TestCase):
def test_mydel(self):
# test a simple use scenario
# initialize
mydel = Delegator(int)
self.assertIs(mydel.delegate, int)
self.assertEqual(mydel._Delegator__cache, set())
# add an attribute:
self.assertRaises(AttributeError, mydel.__getattr__, 'xyz')
bl = mydel.bit_length
self.assertIs(bl, int.bit_length)
self.assertIs(mydel.__dict__['bit_length'], int.bit_length)
self.assertEqual(mydel._Delegator__cache, {'bit_length'})
# add a second attribute
mydel.numerator
self.assertEqual(mydel._Delegator__cache, {'bit_length', 'numerator'})
# delete the second (which, however, leaves it in the name cache)
del mydel.numerator
self.assertNotIn('numerator', mydel.__dict__)
self.assertIn('numerator', mydel._Delegator__cache)
# reset by calling .setdelegate, which calls .resetcache
mydel.setdelegate(float)
self.assertIs(mydel.delegate, float)
self.assertNotIn('bit_length', mydel.__dict__)
self.assertEqual(mydel._Delegator__cache, set())
if __name__ == '__main__':
unittest.main(verbosity=2, exit=2)

View File

@@ -0,0 +1,101 @@
'''Test (selected) IDLE Edit menu items.
Edit modules have their own test files files
'''
from test.test_support import requires
import Tkinter as tk
import unittest
from idlelib import PyShell
class PasteTest(unittest.TestCase):
'''Test pasting into widgets that allow pasting.
On X11, replacing selections requires tk fix.
'''
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = root = tk.Tk()
root.withdraw()
PyShell.fix_x11_paste(root)
cls.text = tk.Text(root)
cls.entry = tk.Entry(root)
cls.spin = tk.Spinbox(root)
root.clipboard_clear()
root.clipboard_append('two')
@classmethod
def tearDownClass(cls):
del cls.text, cls.entry, cls.spin
cls.root.clipboard_clear()
cls.root.update_idletasks()
cls.root.update()
cls.root.destroy()
del cls.root
def test_paste_text_no_selection(self):
"Test pasting into text without a selection."
text = self.text
tag, ans = '', 'onetwo\n'
text.delete('1.0', 'end')
text.insert('1.0', 'one', tag)
text.event_generate('<<Paste>>')
self.assertEqual(text.get('1.0', 'end'), ans)
def test_paste_text_selection(self):
"Test pasting into text with a selection."
text = self.text
tag, ans = 'sel', 'two\n'
text.delete('1.0', 'end')
text.insert('1.0', 'one', tag)
text.event_generate('<<Paste>>')
self.assertEqual(text.get('1.0', 'end'), ans)
def test_paste_entry_no_selection(self):
"Test pasting into an entry without a selection."
# On 3.6, generated <<Paste>> fails without empty select range
# for 'no selection'. Live widget works fine.
entry = self.entry
end, ans = 0, 'onetwo'
entry.delete(0, 'end')
entry.insert(0, 'one')
entry.select_range(0, end) # see note
entry.event_generate('<<Paste>>')
self.assertEqual(entry.get(), ans)
def test_paste_entry_selection(self):
"Test pasting into an entry with a selection."
entry = self.entry
end, ans = 'end', 'two'
entry.delete(0, 'end')
entry.insert(0, 'one')
entry.select_range(0, end)
entry.event_generate('<<Paste>>')
self.assertEqual(entry.get(), ans)
def test_paste_spin_no_selection(self):
"Test pasting into a spinbox without a selection."
# See note above for entry.
spin = self.spin
end, ans = 0, 'onetwo'
spin.delete(0, 'end')
spin.insert(0, 'one')
spin.selection('range', 0, end) # see note
spin.event_generate('<<Paste>>')
self.assertEqual(spin.get(), ans)
def test_paste_spin_selection(self):
"Test pasting into a spinbox with a selection."
spin = self.spin
end, ans = 'end', 'two'
spin.delete(0, 'end')
spin.insert(0, 'one')
spin.selection('range', 0, end)
spin.event_generate('<<Paste>>')
self.assertEqual(spin.get(), ans)
if __name__ == '__main__':
unittest.main(verbosity=2)

View File

@@ -0,0 +1,376 @@
# Test the functions and main class method of FormatParagraph.py
import unittest
from idlelib import FormatParagraph as fp
from idlelib.EditorWindow import EditorWindow
from Tkinter import Tk, Text
from test.test_support import requires
class Is_Get_Test(unittest.TestCase):
"""Test the is_ and get_ functions"""
test_comment = '# This is a comment'
test_nocomment = 'This is not a comment'
trailingws_comment = '# This is a comment '
leadingws_comment = ' # This is a comment'
leadingws_nocomment = ' This is not a comment'
def test_is_all_white(self):
self.assertTrue(fp.is_all_white(''))
self.assertTrue(fp.is_all_white('\t\n\r\f\v'))
self.assertFalse(fp.is_all_white(self.test_comment))
def test_get_indent(self):
Equal = self.assertEqual
Equal(fp.get_indent(self.test_comment), '')
Equal(fp.get_indent(self.trailingws_comment), '')
Equal(fp.get_indent(self.leadingws_comment), ' ')
Equal(fp.get_indent(self.leadingws_nocomment), ' ')
def test_get_comment_header(self):
Equal = self.assertEqual
# Test comment strings
Equal(fp.get_comment_header(self.test_comment), '#')
Equal(fp.get_comment_header(self.trailingws_comment), '#')
Equal(fp.get_comment_header(self.leadingws_comment), ' #')
# Test non-comment strings
Equal(fp.get_comment_header(self.leadingws_nocomment), ' ')
Equal(fp.get_comment_header(self.test_nocomment), '')
class FindTest(unittest.TestCase):
"""Test the find_paragraph function in FormatParagraph.
Using the runcase() function, find_paragraph() is called with 'mark' set at
multiple indexes before and inside the test paragraph.
It appears that code with the same indentation as a quoted string is grouped
as part of the same paragraph, which is probably incorrect behavior.
"""
@classmethod
def setUpClass(cls):
from idlelib.idle_test.mock_tk import Text
cls.text = Text()
def runcase(self, inserttext, stopline, expected):
# Check that find_paragraph returns the expected paragraph when
# the mark index is set to beginning, middle, end of each line
# up to but not including the stop line
text = self.text
text.insert('1.0', inserttext)
for line in range(1, stopline):
linelength = int(text.index("%d.end" % line).split('.')[1])
for col in (0, linelength//2, linelength):
tempindex = "%d.%d" % (line, col)
self.assertEqual(fp.find_paragraph(text, tempindex), expected)
text.delete('1.0', 'end')
def test_find_comment(self):
comment = (
"# Comment block with no blank lines before\n"
"# Comment line\n"
"\n")
self.runcase(comment, 3, ('1.0', '3.0', '#', comment[0:58]))
comment = (
"\n"
"# Comment block with whitespace line before and after\n"
"# Comment line\n"
"\n")
self.runcase(comment, 4, ('2.0', '4.0', '#', comment[1:70]))
comment = (
"\n"
" # Indented comment block with whitespace before and after\n"
" # Comment line\n"
"\n")
self.runcase(comment, 4, ('2.0', '4.0', ' #', comment[1:82]))
comment = (
"\n"
"# Single line comment\n"
"\n")
self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:23]))
comment = (
"\n"
" # Single line comment with leading whitespace\n"
"\n")
self.runcase(comment, 3, ('2.0', '3.0', ' #', comment[1:51]))
comment = (
"\n"
"# Comment immediately followed by code\n"
"x = 42\n"
"\n")
self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:40]))
comment = (
"\n"
" # Indented comment immediately followed by code\n"
"x = 42\n"
"\n")
self.runcase(comment, 3, ('2.0', '3.0', ' #', comment[1:53]))
comment = (
"\n"
"# Comment immediately followed by indented code\n"
" x = 42\n"
"\n")
self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:49]))
def test_find_paragraph(self):
teststring = (
'"""String with no blank lines before\n'
'String line\n'
'"""\n'
'\n')
self.runcase(teststring, 4, ('1.0', '4.0', '', teststring[0:53]))
teststring = (
"\n"
'"""String with whitespace line before and after\n'
'String line.\n'
'"""\n'
'\n')
self.runcase(teststring, 5, ('2.0', '5.0', '', teststring[1:66]))
teststring = (
'\n'
' """Indented string with whitespace before and after\n'
' Comment string.\n'
' """\n'
'\n')
self.runcase(teststring, 5, ('2.0', '5.0', ' ', teststring[1:85]))
teststring = (
'\n'
'"""Single line string."""\n'
'\n')
self.runcase(teststring, 3, ('2.0', '3.0', '', teststring[1:27]))
teststring = (
'\n'
' """Single line string with leading whitespace."""\n'
'\n')
self.runcase(teststring, 3, ('2.0', '3.0', ' ', teststring[1:55]))
class ReformatFunctionTest(unittest.TestCase):
"""Test the reformat_paragraph function without the editor window."""
def test_reformat_paragraph(self):
Equal = self.assertEqual
reform = fp.reformat_paragraph
hw = "O hello world"
Equal(reform(' ', 1), ' ')
Equal(reform("Hello world", 20), "Hello world")
# Test without leading newline
Equal(reform(hw, 1), "O\nhello\nworld")
Equal(reform(hw, 6), "O\nhello\nworld")
Equal(reform(hw, 7), "O hello\nworld")
Equal(reform(hw, 12), "O hello\nworld")
Equal(reform(hw, 13), "O hello world")
# Test with leading newline
hw = "\nO hello world"
Equal(reform(hw, 1), "\nO\nhello\nworld")
Equal(reform(hw, 6), "\nO\nhello\nworld")
Equal(reform(hw, 7), "\nO hello\nworld")
Equal(reform(hw, 12), "\nO hello\nworld")
Equal(reform(hw, 13), "\nO hello world")
class ReformatCommentTest(unittest.TestCase):
"""Test the reformat_comment function without the editor window."""
def test_reformat_comment(self):
Equal = self.assertEqual
# reformat_comment formats to a minimum of 20 characters
test_string = (
" \"\"\"this is a test of a reformat for a triple quoted string"
" will it reformat to less than 70 characters for me?\"\"\"")
result = fp.reformat_comment(test_string, 70, " ")
expected = (
" \"\"\"this is a test of a reformat for a triple quoted string will it\n"
" reformat to less than 70 characters for me?\"\"\"")
Equal(result, expected)
test_comment = (
"# this is a test of a reformat for a triple quoted string will "
"it reformat to less than 70 characters for me?")
result = fp.reformat_comment(test_comment, 70, "#")
expected = (
"# this is a test of a reformat for a triple quoted string will it\n"
"# reformat to less than 70 characters for me?")
Equal(result, expected)
class FormatClassTest(unittest.TestCase):
def test_init_close(self):
instance = fp.FormatParagraph('editor')
self.assertEqual(instance.editwin, 'editor')
instance.close()
self.assertEqual(instance.editwin, None)
# For testing format_paragraph_event, Initialize FormatParagraph with
# a mock Editor with .text and .get_selection_indices. The text must
# be a Text wrapper that adds two methods
# A real EditorWindow creates unneeded, time-consuming baggage and
# sometimes emits shutdown warnings like this:
# "warning: callback failed in WindowList <class '_tkinter.TclError'>
# : invalid command name ".55131368.windows".
# Calling EditorWindow._close in tearDownClass prevents this but causes
# other problems (windows left open).
class TextWrapper:
def __init__(self, master):
self.text = Text(master=master)
def __getattr__(self, name):
return getattr(self.text, name)
def undo_block_start(self): pass
def undo_block_stop(self): pass
class Editor:
def __init__(self, root):
self.text = TextWrapper(root)
get_selection_indices = EditorWindow. get_selection_indices.im_func
class FormatEventTest(unittest.TestCase):
"""Test the formatting of text inside a Text widget.
This is done with FormatParagraph.format.paragraph_event,
which calls functions in the module as appropriate.
"""
test_string = (
" '''this is a test of a reformat for a triple "
"quoted string will it reformat to less than 70 "
"characters for me?'''\n")
multiline_test_string = (
" '''The first line is under the max width.\n"
" The second line's length is way over the max width. It goes "
"on and on until it is over 100 characters long.\n"
" Same thing with the third line. It is also way over the max "
"width, but FormatParagraph will fix it.\n"
" '''\n")
multiline_test_comment = (
"# The first line is under the max width.\n"
"# The second line's length is way over the max width. It goes on "
"and on until it is over 100 characters long.\n"
"# Same thing with the third line. It is also way over the max "
"width, but FormatParagraph will fix it.\n"
"# The fourth line is short like the first line.")
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
editor = Editor(root=cls.root)
cls.text = editor.text.text # Test code does not need the wrapper.
cls.formatter = fp.FormatParagraph(editor).format_paragraph_event
# Sets the insert mark just after the re-wrapped and inserted text.
@classmethod
def tearDownClass(cls):
del cls.text, cls.formatter
cls.root.destroy()
del cls.root
def test_short_line(self):
self.text.insert('1.0', "Short line\n")
self.formatter("Dummy")
self.assertEqual(self.text.get('1.0', 'insert'), "Short line\n" )
self.text.delete('1.0', 'end')
def test_long_line(self):
text = self.text
# Set cursor ('insert' mark) to '1.0', within text.
text.insert('1.0', self.test_string)
text.mark_set('insert', '1.0')
self.formatter('ParameterDoesNothing', limit=70)
result = text.get('1.0', 'insert')
# find function includes \n
expected = (
" '''this is a test of a reformat for a triple quoted string will it\n"
" reformat to less than 70 characters for me?'''\n") # yes
self.assertEqual(result, expected)
text.delete('1.0', 'end')
# Select from 1.11 to line end.
text.insert('1.0', self.test_string)
text.tag_add('sel', '1.11', '1.end')
self.formatter('ParameterDoesNothing', limit=70)
result = text.get('1.0', 'insert')
# selection excludes \n
expected = (
" '''this is a test of a reformat for a triple quoted string will it reformat\n"
" to less than 70 characters for me?'''") # no
self.assertEqual(result, expected)
text.delete('1.0', 'end')
def test_multiple_lines(self):
text = self.text
# Select 2 long lines.
text.insert('1.0', self.multiline_test_string)
text.tag_add('sel', '2.0', '4.0')
self.formatter('ParameterDoesNothing', limit=70)
result = text.get('2.0', 'insert')
expected = (
" The second line's length is way over the max width. It goes on and\n"
" on until it is over 100 characters long. Same thing with the third\n"
" line. It is also way over the max width, but FormatParagraph will\n"
" fix it.\n")
self.assertEqual(result, expected)
text.delete('1.0', 'end')
def test_comment_block(self):
text = self.text
# Set cursor ('insert') to '1.0', within block.
text.insert('1.0', self.multiline_test_comment)
self.formatter('ParameterDoesNothing', limit=70)
result = text.get('1.0', 'insert')
expected = (
"# The first line is under the max width. The second line's length is\n"
"# way over the max width. It goes on and on until it is over 100\n"
"# characters long. Same thing with the third line. It is also way over\n"
"# the max width, but FormatParagraph will fix it. The fourth line is\n"
"# short like the first line.\n")
self.assertEqual(result, expected)
text.delete('1.0', 'end')
# Select line 2, verify line 1 unaffected.
text.insert('1.0', self.multiline_test_comment)
text.tag_add('sel', '2.0', '3.0')
self.formatter('ParameterDoesNothing', limit=70)
result = text.get('1.0', 'insert')
expected = (
"# The first line is under the max width.\n"
"# The second line's length is way over the max width. It goes on and\n"
"# on until it is over 100 characters long.\n")
self.assertEqual(result, expected)
text.delete('1.0', 'end')
# The following block worked with EditorWindow but fails with the mock.
# Lines 2 and 3 get pasted together even though the previous block left
# the previous line alone. More investigation is needed.
## # Select lines 3 and 4
## text.insert('1.0', self.multiline_test_comment)
## text.tag_add('sel', '3.0', '5.0')
## self.formatter('ParameterDoesNothing')
## result = text.get('3.0', 'insert')
## expected = (
##"# Same thing with the third line. It is also way over the max width,\n"
##"# but FormatParagraph will fix it. The fourth line is short like the\n"
##"# first line.\n")
## self.assertEqual(result, expected)
## text.delete('1.0', 'end')
if __name__ == '__main__':
unittest.main(verbosity=2, exit=2)

View File

@@ -0,0 +1,82 @@
""" !Changing this line will break Test_findfile.test_found!
Non-gui unit tests for idlelib.GrepDialog methods.
dummy_command calls grep_it calls findfiles.
An exception raised in one method will fail callers.
Otherwise, tests are mostly independent.
*** Currently only test grep_it.
"""
import unittest
from test.test_support import captured_stdout, findfile
from idlelib.idle_test.mock_tk import Var
from idlelib.GrepDialog import GrepDialog
import re
__file__ = findfile('idlelib/idle_test') + '/test_grep.py'
class Dummy_searchengine:
'''GrepDialog.__init__ calls parent SearchDiabolBase which attaches the
passed in SearchEngine instance as attribute 'engine'. Only a few of the
many possible self.engine.x attributes are needed here.
'''
def getpat(self):
return self._pat
searchengine = Dummy_searchengine()
class Dummy_grep:
# Methods tested
#default_command = GrepDialog.default_command
grep_it = GrepDialog.grep_it.im_func
findfiles = GrepDialog.findfiles.im_func
# Other stuff needed
recvar = Var(False)
engine = searchengine
def close(self): # gui method
pass
grep = Dummy_grep()
class FindfilesTest(unittest.TestCase):
# findfiles is really a function, not a method, could be iterator
# test that filename return filename
# test that idlelib has many .py files
# test that recursive flag adds idle_test .py files
pass
class Grep_itTest(unittest.TestCase):
# Test captured reports with 0 and some hits.
# Should test file names, but Windows reports have mixed / and \ separators
# from incomplete replacement, so 'later'.
def report(self, pat):
grep.engine._pat = pat
with captured_stdout() as s:
grep.grep_it(re.compile(pat), __file__)
lines = s.getvalue().split('\n')
lines.pop() # remove bogus '' after last \n
return lines
def test_unfound(self):
pat = 'xyz*'*7
lines = self.report(pat)
self.assertEqual(len(lines), 2)
self.assertIn(pat, lines[0])
self.assertEqual(lines[1], 'No hits.')
def test_found(self):
pat = '""" !Changing this line will break Test_findfile.test_found!'
lines = self.report(pat)
self.assertEqual(len(lines), 5)
self.assertIn(pat, lines[0])
self.assertIn('py: 1:', lines[1]) # line number 1
self.assertIn('2', lines[3]) # hits found 2
self.assertTrue(lines[4].startswith('(Hint:'))
class Default_commandTest(unittest.TestCase):
# To write this, mode OutputWindow import to top of GrepDialog
# so it can be replaced by captured_stdout in class setup/teardown.
pass
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)

View File

@@ -0,0 +1,52 @@
'''Test idlelib.help_about.
Coverage:
'''
from idlelib import aboutDialog as help_about
from idlelib import textView as textview
from idlelib.idle_test.mock_idle import Func
from idlelib.idle_test.mock_tk import Mbox
import unittest
About = help_about.AboutDialog
class Dummy_about_dialog():
# Dummy class for testing file display functions.
idle_credits = About.ShowIDLECredits.im_func
idle_readme = About.ShowIDLEAbout.im_func
idle_news = About.ShowIDLENEWS.im_func
# Called by the above
display_file_text = About.display_file_text.im_func
class DisplayFileTest(unittest.TestCase):
"Test that .txt files are found and properly decoded."
dialog = Dummy_about_dialog()
@classmethod
def setUpClass(cls):
cls.orig_mbox = textview.tkMessageBox
cls.orig_view = textview.view_text
cls.mbox = Mbox()
cls.view = Func()
textview.tkMessageBox = cls.mbox
textview.view_text = cls.view
cls.About = Dummy_about_dialog()
@classmethod
def tearDownClass(cls):
textview.tkMessageBox = cls.orig_mbox
textview.view_text = cls.orig_view.im_func
def test_file_isplay(self):
for handler in (self.dialog.idle_credits,
self.dialog.idle_readme,
self.dialog.idle_news):
self.mbox.showerror.message = ''
self.view.called = False
handler()
self.assertEqual(self.mbox.showerror.message, '')
self.assertEqual(self.view.called, True)
if __name__ == '__main__':
unittest.main(verbosity=2)

View File

@@ -0,0 +1,192 @@
"""Unittest for idlelib.HyperParser"""
import unittest
from test.test_support import requires
from Tkinter import Tk, Text
from idlelib.EditorWindow import EditorWindow
from idlelib.HyperParser import HyperParser
class DummyEditwin:
def __init__(self, text):
self.text = text
self.indentwidth = 8
self.tabwidth = 8
self.context_use_ps1 = True
self.num_context_lines = 50, 500, 1000
_build_char_in_string_func = EditorWindow._build_char_in_string_func.im_func
is_char_in_string = EditorWindow.is_char_in_string.im_func
class HyperParserTest(unittest.TestCase):
code = (
'"""This is a module docstring"""\n'
'# this line is a comment\n'
'x = "this is a string"\n'
"y = 'this is also a string'\n"
'l = [i for i in range(10)]\n'
'm = [py*py for # comment\n'
' py in l]\n'
'x.__len__\n'
"z = ((r'asdf')+('a')))\n"
'[x for x in\n'
'for = False\n'
)
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.text = Text(cls.root)
cls.editwin = DummyEditwin(cls.text)
@classmethod
def tearDownClass(cls):
del cls.text, cls.editwin
cls.root.destroy()
del cls.root
def setUp(self):
self.text.insert('insert', self.code)
def tearDown(self):
self.text.delete('1.0', 'end')
self.editwin.context_use_ps1 = True
def get_parser(self, index):
"""
Return a parser object with index at 'index'
"""
return HyperParser(self.editwin, index)
def test_init(self):
"""
test corner cases in the init method
"""
with self.assertRaises(ValueError) as ve:
self.text.tag_add('console', '1.0', '1.end')
p = self.get_parser('1.5')
self.assertIn('precedes', str(ve.exception))
# test without ps1
self.editwin.context_use_ps1 = False
# number of lines lesser than 50
p = self.get_parser('end')
self.assertEqual(p.rawtext, self.text.get('1.0', 'end'))
# number of lines greater than 50
self.text.insert('end', self.text.get('1.0', 'end')*4)
p = self.get_parser('54.5')
def test_is_in_string(self):
get = self.get_parser
p = get('1.0')
self.assertFalse(p.is_in_string())
p = get('1.4')
self.assertTrue(p.is_in_string())
p = get('2.3')
self.assertFalse(p.is_in_string())
p = get('3.3')
self.assertFalse(p.is_in_string())
p = get('3.7')
self.assertTrue(p.is_in_string())
p = get('4.6')
self.assertTrue(p.is_in_string())
def test_is_in_code(self):
get = self.get_parser
p = get('1.0')
self.assertTrue(p.is_in_code())
p = get('1.1')
self.assertFalse(p.is_in_code())
p = get('2.5')
self.assertFalse(p.is_in_code())
p = get('3.4')
self.assertTrue(p.is_in_code())
p = get('3.6')
self.assertFalse(p.is_in_code())
p = get('4.14')
self.assertFalse(p.is_in_code())
def test_get_surrounding_bracket(self):
get = self.get_parser
def without_mustclose(parser):
# a utility function to get surrounding bracket
# with mustclose=False
return parser.get_surrounding_brackets(mustclose=False)
def with_mustclose(parser):
# a utility function to get surrounding bracket
# with mustclose=True
return parser.get_surrounding_brackets(mustclose=True)
p = get('3.2')
self.assertIsNone(with_mustclose(p))
self.assertIsNone(without_mustclose(p))
p = get('5.6')
self.assertTupleEqual(without_mustclose(p), ('5.4', '5.25'))
self.assertTupleEqual(without_mustclose(p), with_mustclose(p))
p = get('5.23')
self.assertTupleEqual(without_mustclose(p), ('5.21', '5.24'))
self.assertTupleEqual(without_mustclose(p), with_mustclose(p))
p = get('6.15')
self.assertTupleEqual(without_mustclose(p), ('6.4', '6.end'))
self.assertIsNone(with_mustclose(p))
p = get('9.end')
self.assertIsNone(with_mustclose(p))
self.assertIsNone(without_mustclose(p))
def test_get_expression(self):
get = self.get_parser
p = get('4.2')
self.assertEqual(p.get_expression(), 'y ')
p = get('4.7')
with self.assertRaises(ValueError) as ve:
p.get_expression()
self.assertIn('is inside a code', str(ve.exception))
p = get('5.25')
self.assertEqual(p.get_expression(), 'range(10)')
p = get('6.7')
self.assertEqual(p.get_expression(), 'py')
p = get('6.8')
self.assertEqual(p.get_expression(), '')
p = get('7.9')
self.assertEqual(p.get_expression(), 'py')
p = get('8.end')
self.assertEqual(p.get_expression(), 'x.__len__')
p = get('9.13')
self.assertEqual(p.get_expression(), "r'asdf'")
p = get('9.17')
with self.assertRaises(ValueError) as ve:
p.get_expression()
self.assertIn('is inside a code', str(ve.exception))
p = get('10.0')
self.assertEqual(p.get_expression(), '')
p = get('11.3')
self.assertEqual(p.get_expression(), '')
p = get('11.11')
self.assertEqual(p.get_expression(), 'False')
if __name__ == '__main__':
unittest.main(verbosity=2)

View File

@@ -0,0 +1,168 @@
import unittest
from test.test_support import requires
import Tkinter as tk
from Tkinter import Text as tkText
from idlelib.idle_test.mock_tk import Text as mkText
from idlelib.IdleHistory import History
from idlelib.configHandler import idleConf
line1 = 'a = 7'
line2 = 'b = a'
class StoreTest(unittest.TestCase):
'''Tests History.__init__ and History.store with mock Text'''
@classmethod
def setUpClass(cls):
cls.text = mkText()
cls.history = History(cls.text)
def tearDown(self):
self.text.delete('1.0', 'end')
self.history.history = []
def test_init(self):
self.assertIs(self.history.text, self.text)
self.assertEqual(self.history.history, [])
self.assertIsNone(self.history.prefix)
self.assertIsNone(self.history.pointer)
self.assertEqual(self.history.cyclic,
idleConf.GetOption("main", "History", "cyclic", 1, "bool"))
def test_store_short(self):
self.history.store('a')
self.assertEqual(self.history.history, [])
self.history.store(' a ')
self.assertEqual(self.history.history, [])
def test_store_dup(self):
self.history.store(line1)
self.assertEqual(self.history.history, [line1])
self.history.store(line2)
self.assertEqual(self.history.history, [line1, line2])
self.history.store(line1)
self.assertEqual(self.history.history, [line2, line1])
def test_store_reset(self):
self.history.prefix = line1
self.history.pointer = 0
self.history.store(line2)
self.assertIsNone(self.history.prefix)
self.assertIsNone(self.history.pointer)
class TextWrapper:
def __init__(self, master):
self.text = tkText(master=master)
self._bell = False
def __getattr__(self, name):
return getattr(self.text, name)
def bell(self):
self._bell = True
class FetchTest(unittest.TestCase):
'''Test History.fetch with wrapped tk.Text.
'''
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = tk.Tk()
cls.root.withdraw()
def setUp(self):
self.text = text = TextWrapper(self.root)
text.insert('1.0', ">>> ")
text.mark_set('iomark', '1.4')
text.mark_gravity('iomark', 'left')
self.history = History(text)
self.history.history = [line1, line2]
@classmethod
def tearDownClass(cls):
cls.root.destroy()
del cls.root
def fetch_test(self, reverse, line, prefix, index, bell=False):
# Perform one fetch as invoked by Alt-N or Alt-P
# Test the result. The line test is the most important.
# The last two are diagnostic of fetch internals.
History = self.history
History.fetch(reverse)
Equal = self.assertEqual
Equal(self.text.get('iomark', 'end-1c'), line)
Equal(self.text._bell, bell)
if bell:
self.text._bell = False
Equal(History.prefix, prefix)
Equal(History.pointer, index)
Equal(self.text.compare("insert", '==', "end-1c"), 1)
def test_fetch_prev_cyclic(self):
prefix = ''
test = self.fetch_test
test(True, line2, prefix, 1)
test(True, line1, prefix, 0)
test(True, prefix, None, None, bell=True)
def test_fetch_next_cyclic(self):
prefix = ''
test = self.fetch_test
test(False, line1, prefix, 0)
test(False, line2, prefix, 1)
test(False, prefix, None, None, bell=True)
# Prefix 'a' tests skip line2, which starts with 'b'
def test_fetch_prev_prefix(self):
prefix = 'a'
self.text.insert('iomark', prefix)
self.fetch_test(True, line1, prefix, 0)
self.fetch_test(True, prefix, None, None, bell=True)
def test_fetch_next_prefix(self):
prefix = 'a'
self.text.insert('iomark', prefix)
self.fetch_test(False, line1, prefix, 0)
self.fetch_test(False, prefix, None, None, bell=True)
def test_fetch_prev_noncyclic(self):
prefix = ''
self.history.cyclic = False
test = self.fetch_test
test(True, line2, prefix, 1)
test(True, line1, prefix, 0)
test(True, line1, prefix, 0, bell=True)
def test_fetch_next_noncyclic(self):
prefix = ''
self.history.cyclic = False
test = self.fetch_test
test(False, prefix, None, None, bell=True)
test(True, line2, prefix, 1)
test(False, prefix, None, None, bell=True)
test(False, prefix, None, None, bell=True)
def test_fetch_cursor_move(self):
# Move cursor after fetch
self.history.fetch(reverse=True) # initialization
self.text.mark_set('insert', 'iomark')
self.fetch_test(True, line2, None, None, bell=True)
def test_fetch_edit(self):
# Edit after fetch
self.history.fetch(reverse=True) # initialization
self.text.delete('iomark', 'insert', )
self.text.insert('iomark', 'a =')
self.fetch_test(True, line1, 'a =', 0) # prefix is reset
def test_history_prev_next(self):
# Minimally test functions bound to events
self.history.history_prev('dummy event')
self.assertEqual(self.history.pointer, 1)
self.history.history_next('dummy event')
self.assertEqual(self.history.pointer, None)
if __name__ == '__main__':
unittest.main(verbosity=2, exit=2)

View File

@@ -0,0 +1,267 @@
import unittest
import io
from idlelib.PyShell import PseudoInputFile, PseudoOutputFile
from test import test_support as support
class Base(object):
def __str__(self):
return '%s:str' % type(self).__name__
def __unicode__(self):
return '%s:unicode' % type(self).__name__
def __len__(self):
return 3
def __iter__(self):
return iter('abc')
def __getitem__(self, *args):
return '%s:item' % type(self).__name__
def __getslice__(self, *args):
return '%s:slice' % type(self).__name__
class S(Base, str):
pass
class U(Base, unicode):
pass
class BA(Base, bytearray):
pass
class MockShell:
def __init__(self):
self.reset()
def write(self, *args):
self.written.append(args)
def readline(self):
return self.lines.pop()
def close(self):
pass
def reset(self):
self.written = []
def push(self, lines):
self.lines = list(lines)[::-1]
class PseudeOutputFilesTest(unittest.TestCase):
def test_misc(self):
shell = MockShell()
f = PseudoOutputFile(shell, 'stdout', 'utf-8')
self.assertIsInstance(f, io.TextIOBase)
self.assertEqual(f.encoding, 'utf-8')
self.assertIsNone(f.errors)
self.assertIsNone(f.newlines)
self.assertEqual(f.name, '<stdout>')
self.assertFalse(f.closed)
self.assertTrue(f.isatty())
self.assertFalse(f.readable())
self.assertTrue(f.writable())
self.assertFalse(f.seekable())
def test_unsupported(self):
shell = MockShell()
f = PseudoOutputFile(shell, 'stdout', 'utf-8')
self.assertRaises(IOError, f.fileno)
self.assertRaises(IOError, f.tell)
self.assertRaises(IOError, f.seek, 0)
self.assertRaises(IOError, f.read, 0)
self.assertRaises(IOError, f.readline, 0)
def test_write(self):
shell = MockShell()
f = PseudoOutputFile(shell, 'stdout', 'utf-8')
f.write('test')
self.assertEqual(shell.written, [('test', 'stdout')])
shell.reset()
f.write('t\xe8st')
self.assertEqual(shell.written, [('t\xe8st', 'stdout')])
shell.reset()
f.write(u't\xe8st')
self.assertEqual(shell.written, [(u't\xe8st', 'stdout')])
shell.reset()
f.write(S('t\xe8st'))
self.assertEqual(shell.written, [('t\xe8st', 'stdout')])
self.assertEqual(type(shell.written[0][0]), str)
shell.reset()
f.write(BA('t\xe8st'))
self.assertEqual(shell.written, [('t\xe8st', 'stdout')])
self.assertEqual(type(shell.written[0][0]), str)
shell.reset()
f.write(U(u't\xe8st'))
self.assertEqual(shell.written, [(u't\xe8st', 'stdout')])
self.assertEqual(type(shell.written[0][0]), unicode)
shell.reset()
self.assertRaises(TypeError, f.write)
self.assertEqual(shell.written, [])
self.assertRaises(TypeError, f.write, 123)
self.assertEqual(shell.written, [])
self.assertRaises(TypeError, f.write, 'test', 'spam')
self.assertEqual(shell.written, [])
def test_writelines(self):
shell = MockShell()
f = PseudoOutputFile(shell, 'stdout', 'utf-8')
f.writelines([])
self.assertEqual(shell.written, [])
shell.reset()
f.writelines(['one\n', 'two'])
self.assertEqual(shell.written,
[('one\n', 'stdout'), ('two', 'stdout')])
shell.reset()
f.writelines(['on\xe8\n', 'tw\xf2'])
self.assertEqual(shell.written,
[('on\xe8\n', 'stdout'), ('tw\xf2', 'stdout')])
shell.reset()
f.writelines([u'on\xe8\n', u'tw\xf2'])
self.assertEqual(shell.written,
[(u'on\xe8\n', 'stdout'), (u'tw\xf2', 'stdout')])
shell.reset()
f.writelines([S('t\xe8st')])
self.assertEqual(shell.written, [('t\xe8st', 'stdout')])
self.assertEqual(type(shell.written[0][0]), str)
shell.reset()
f.writelines([BA('t\xe8st')])
self.assertEqual(shell.written, [('t\xe8st', 'stdout')])
self.assertEqual(type(shell.written[0][0]), str)
shell.reset()
f.writelines([U(u't\xe8st')])
self.assertEqual(shell.written, [(u't\xe8st', 'stdout')])
self.assertEqual(type(shell.written[0][0]), unicode)
shell.reset()
self.assertRaises(TypeError, f.writelines)
self.assertEqual(shell.written, [])
self.assertRaises(TypeError, f.writelines, 123)
self.assertEqual(shell.written, [])
self.assertRaises(TypeError, f.writelines, [123])
self.assertEqual(shell.written, [])
self.assertRaises(TypeError, f.writelines, [], [])
self.assertEqual(shell.written, [])
def test_close(self):
shell = MockShell()
f = PseudoOutputFile(shell, 'stdout', 'utf-8')
self.assertFalse(f.closed)
f.write('test')
f.close()
self.assertTrue(f.closed)
self.assertRaises(ValueError, f.write, 'x')
self.assertEqual(shell.written, [('test', 'stdout')])
f.close()
self.assertRaises(TypeError, f.close, 1)
class PseudeInputFilesTest(unittest.TestCase):
def test_misc(self):
shell = MockShell()
f = PseudoInputFile(shell, 'stdin', 'utf-8')
self.assertIsInstance(f, io.TextIOBase)
self.assertEqual(f.encoding, 'utf-8')
self.assertIsNone(f.errors)
self.assertIsNone(f.newlines)
self.assertEqual(f.name, '<stdin>')
self.assertFalse(f.closed)
self.assertTrue(f.isatty())
self.assertTrue(f.readable())
self.assertFalse(f.writable())
self.assertFalse(f.seekable())
def test_unsupported(self):
shell = MockShell()
f = PseudoInputFile(shell, 'stdin', 'utf-8')
self.assertRaises(IOError, f.fileno)
self.assertRaises(IOError, f.tell)
self.assertRaises(IOError, f.seek, 0)
self.assertRaises(IOError, f.write, 'x')
self.assertRaises(IOError, f.writelines, ['x'])
def test_read(self):
shell = MockShell()
f = PseudoInputFile(shell, 'stdin', 'utf-8')
shell.push(['one\n', 'two\n', ''])
self.assertEqual(f.read(), 'one\ntwo\n')
shell.push(['one\n', 'two\n', ''])
self.assertEqual(f.read(-1), 'one\ntwo\n')
shell.push(['one\n', 'two\n', ''])
self.assertEqual(f.read(None), 'one\ntwo\n')
shell.push(['one\n', 'two\n', 'three\n', ''])
self.assertEqual(f.read(2), 'on')
self.assertEqual(f.read(3), 'e\nt')
self.assertEqual(f.read(10), 'wo\nthree\n')
shell.push(['one\n', 'two\n'])
self.assertEqual(f.read(0), '')
self.assertRaises(TypeError, f.read, 1.5)
self.assertRaises(TypeError, f.read, '1')
self.assertRaises(TypeError, f.read, 1, 1)
def test_readline(self):
shell = MockShell()
f = PseudoInputFile(shell, 'stdin', 'utf-8')
shell.push(['one\n', 'two\n', 'three\n', 'four\n'])
self.assertEqual(f.readline(), 'one\n')
self.assertEqual(f.readline(-1), 'two\n')
self.assertEqual(f.readline(None), 'three\n')
shell.push(['one\ntwo\n'])
self.assertEqual(f.readline(), 'one\n')
self.assertEqual(f.readline(), 'two\n')
shell.push(['one', 'two', 'three'])
self.assertEqual(f.readline(), 'one')
self.assertEqual(f.readline(), 'two')
shell.push(['one\n', 'two\n', 'three\n'])
self.assertEqual(f.readline(2), 'on')
self.assertEqual(f.readline(1), 'e')
self.assertEqual(f.readline(1), '\n')
self.assertEqual(f.readline(10), 'two\n')
shell.push(['one\n', 'two\n'])
self.assertEqual(f.readline(0), '')
self.assertRaises(TypeError, f.readlines, 1.5)
self.assertRaises(TypeError, f.readlines, '1')
self.assertRaises(TypeError, f.readlines, 1, 1)
def test_readlines(self):
shell = MockShell()
f = PseudoInputFile(shell, 'stdin', 'utf-8')
shell.push(['one\n', 'two\n', ''])
self.assertEqual(f.readlines(), ['one\n', 'two\n'])
shell.push(['one\n', 'two\n', ''])
self.assertEqual(f.readlines(-1), ['one\n', 'two\n'])
shell.push(['one\n', 'two\n', ''])
self.assertEqual(f.readlines(None), ['one\n', 'two\n'])
shell.push(['one\n', 'two\n', ''])
self.assertEqual(f.readlines(0), ['one\n', 'two\n'])
shell.push(['one\n', 'two\n', ''])
self.assertEqual(f.readlines(3), ['one\n'])
shell.push(['one\n', 'two\n', ''])
self.assertEqual(f.readlines(4), ['one\n', 'two\n'])
shell.push(['one\n', 'two\n', ''])
self.assertRaises(TypeError, f.readlines, 1.5)
self.assertRaises(TypeError, f.readlines, '1')
self.assertRaises(TypeError, f.readlines, 1, 1)
def test_close(self):
shell = MockShell()
f = PseudoInputFile(shell, 'stdin', 'utf-8')
shell.push(['one\n', 'two\n', ''])
self.assertFalse(f.closed)
self.assertEqual(f.readline(), 'one\n')
f.close()
self.assertFalse(f.closed)
self.assertEqual(f.readline(), 'two\n')
self.assertRaises(TypeError, f.close, 1)
def test_main():
support.run_unittest(PseudeOutputFilesTest, PseudeInputFilesTest)
if __name__ == '__main__':
test_main()

View File

@@ -0,0 +1,121 @@
"""Test idlelib.ParenMatch."""
# This must currently be a gui test because ParenMatch methods use
# several text methods not defined on idlelib.idle_test.mock_tk.Text.
import unittest
from test.test_support import requires
from Tkinter import Tk, Text
from idlelib.ParenMatch import ParenMatch
class Mock: # 2.7 does not have unittest.mock
def __init__(self, *args, **kwargs):
self.called = False
def __call__(self, *args, **kwargs):
self.called = True
def reset_mock(self, *args, **kwargs):
self.called = False
def after(self, *args, **kwargs):
pass
class DummyEditwin:
def __init__(self, text):
self.text = text
self.indentwidth = 8
self.tabwidth = 8
self.context_use_ps1 = True
class ParenMatchTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.text = Text(cls.root)
cls.editwin = DummyEditwin(cls.text)
cls.editwin.text_frame = Mock()
@classmethod
def tearDownClass(cls):
del cls.text, cls.editwin
cls.root.destroy()
del cls.root
def tearDown(self):
self.text.delete('1.0', 'end')
def test_paren_expression(self):
"""
Test ParenMatch with 'expression' style.
"""
text = self.text
pm = ParenMatch(self.editwin)
pm.set_style('expression')
text.insert('insert', 'def foobar(a, b')
pm.flash_paren_event('event')
self.assertIn('<<parenmatch-check-restore>>', text.event_info())
self.assertTupleEqual(text.tag_prevrange('paren', 'end'),
('1.10', '1.15'))
text.insert('insert', ')')
pm.restore_event()
self.assertNotIn('<<parenmatch-check-restore>>', text.event_info())
self.assertEqual(text.tag_prevrange('paren', 'end'), ())
# paren_closed_event can only be tested as below
pm.paren_closed_event('event')
self.assertTupleEqual(text.tag_prevrange('paren', 'end'),
('1.10', '1.16'))
def test_paren_default(self):
"""
Test ParenMatch with 'default' style.
"""
text = self.text
pm = ParenMatch(self.editwin)
pm.set_style('default')
text.insert('insert', 'def foobar(a, b')
pm.flash_paren_event('event')
self.assertIn('<<parenmatch-check-restore>>', text.event_info())
self.assertTupleEqual(text.tag_prevrange('paren', 'end'),
('1.10', '1.11'))
text.insert('insert', ')')
pm.restore_event()
self.assertNotIn('<<parenmatch-check-restore>>', text.event_info())
self.assertEqual(text.tag_prevrange('paren', 'end'), ())
def test_paren_corner(self):
"""
Test corner cases in flash_paren_event and paren_closed_event.
These cases force conditional expression and alternate paths.
"""
text = self.text
pm = ParenMatch(self.editwin)
text.insert('insert', '# this is a commen)')
self.assertIsNone(pm.paren_closed_event('event'))
text.insert('insert', '\ndef')
self.assertIsNone(pm.flash_paren_event('event'))
self.assertIsNone(pm.paren_closed_event('event'))
text.insert('insert', ' a, *arg)')
self.assertIsNone(pm.paren_closed_event('event'))
def test_handle_restore_timer(self):
pm = ParenMatch(self.editwin)
pm.restore_event = Mock()
pm.handle_restore_timer(0)
self.assertTrue(pm.restore_event.called)
pm.restore_event.reset_mock()
pm.handle_restore_timer(1)
self.assertFalse(pm.restore_event.called)
if __name__ == '__main__':
unittest.main(verbosity=2)

Some files were not shown because too many files have changed in this diff Show More