diff options
Diffstat (limited to 'lib/python2.7/idlelib')
115 files changed, 26110 insertions, 0 deletions
diff --git a/lib/python2.7/idlelib/AutoComplete.py b/lib/python2.7/idlelib/AutoComplete.py new file mode 100644 index 0000000..9381bda --- /dev/null +++ b/lib/python2.7/idlelib/AutoComplete.py @@ -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) diff --git a/lib/python2.7/idlelib/AutoCompleteWindow.py b/lib/python2.7/idlelib/AutoCompleteWindow.py new file mode 100644 index 0000000..205a29b --- /dev/null +++ b/lib/python2.7/idlelib/AutoCompleteWindow.py @@ -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 diff --git a/lib/python2.7/idlelib/AutoExpand.py b/lib/python2.7/idlelib/AutoExpand.py new file mode 100644 index 0000000..7059054 --- /dev/null +++ b/lib/python2.7/idlelib/AutoExpand.py @@ -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) diff --git a/lib/python2.7/idlelib/Bindings.py b/lib/python2.7/idlelib/Bindings.py new file mode 100644 index 0000000..2fd8532 --- /dev/null +++ b/lib/python2.7/idlelib/Bindings.py @@ -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() diff --git a/lib/python2.7/idlelib/CREDITS.txt b/lib/python2.7/idlelib/CREDITS.txt new file mode 100644 index 0000000..3a50eb8 --- /dev/null +++ b/lib/python2.7/idlelib/CREDITS.txt @@ -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! + + + diff --git a/lib/python2.7/idlelib/CallTipWindow.py b/lib/python2.7/idlelib/CallTipWindow.py new file mode 100644 index 0000000..e63164b --- /dev/null +++ b/lib/python2.7/idlelib/CallTipWindow.py @@ -0,0 +1,161 @@ +"""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.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) diff --git a/lib/python2.7/idlelib/CallTips.py b/lib/python2.7/idlelib/CallTips.py new file mode 100644 index 0000000..3db2636 --- /dev/null +++ b/lib/python2.7/idlelib/CallTips.py @@ -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) diff --git a/lib/python2.7/idlelib/ChangeLog b/lib/python2.7/idlelib/ChangeLog new file mode 100644 index 0000000..968a344 --- /dev/null +++ b/lib/python2.7/idlelib/ChangeLog @@ -0,0 +1,1591 @@ +Please refer to the IDLEfork and IDLE CVS repositories for +change details subsequent to the 0.8.1 release. + + +IDLEfork ChangeLog +================== + +2001-07-20 11:35 elguavas + + * README.txt, NEWS.txt: bring up to date for 0.8.1 release + +2001-07-19 16:40 elguavas + + * IDLEFORK.html: replaced by IDLEFORK-index.html + +2001-07-19 16:39 elguavas + + * IDLEFORK-index.html: updated placeholder idlefork homepage + +2001-07-19 14:49 elguavas + + * ChangeLog, EditorWindow.py, INSTALLATION, NEWS.txt, README.txt, + TODO.txt, idlever.py: + minor tidy-ups ready for 0.8.1 alpha tarball release + +2001-07-17 15:12 kbk + + * INSTALLATION, setup.py: INSTALLATION: Remove the coexist.patch + instructions + + **************** setup.py: + + Remove the idles script, add some words on IDLE Fork to the + long_description, and clean up some line spacing. + +2001-07-17 15:01 kbk + + * coexist.patch: Put this in the attic, at least for now... + +2001-07-17 14:59 kbk + + * PyShell.py, idle, idles: Implement idle command interface as + suggested by GvR [idle-dev] 16 July **************** PyShell: Added + functionality: + + usage: idle.py [-c command] [-d] [-i] [-r script] [-s] [-t title] + [arg] ... + + idle file(s) (without options) edit the file(s) + + -c cmd run the command in a shell -d enable the + debugger -i open an interactive shell -i file(s) open a + shell and also an editor window for each file -r script run a file + as a script in a shell -s run $IDLESTARTUP or + $PYTHONSTARTUP before anything else -t title set title of shell + window + + Remaining arguments are applied to the command (-c) or script (-r). + + ****************** idles: Removed the idles script, not needed + + ****************** idle: Removed the IdleConf references, not + required anymore + +2001-07-16 17:08 kbk + + * INSTALLATION, coexist.patch: Added installation instructions. + + Added a patch which modifies idlefork so that it can co-exist with + "official" IDLE in the site-packages directory. This patch is not + necessary if only idlefork IDLE is installed. See INSTALLATION for + further details. + +2001-07-16 15:50 kbk + + * idles: Add a script "idles" which opens a Python Shell window. + + The default behaviour of idlefork idle is to open an editor window + instead of a shell. Complex expressions may be run in a fresh + environment by selecting "run". There are times, however, when a + shell is desired. Though one can be started by "idle -t 'foo'", + this script is more convenient. In addition, a shell and an editor + window can be started in parallel by "idles -e foo.py". + +2001-07-16 15:25 kbk + + * PyShell.py: Call out IDLE Fork in startup message. + +2001-07-16 14:00 kbk + + * PyShell.py, setup.py: Add a script "idles" which opens a Python + Shell window. + + The default behaviour of idlefork idle is to open an editor window + instead of a shell. Complex expressions may be run in a fresh + environment by selecting "run". There are times, however, when a + shell is desired. Though one can be started by "idle -t 'foo'", + this script is more convenient. In addition, a shell and an editor + window can be started in parallel by "idles -e foo.py". + +2001-07-15 03:06 kbk + + * pyclbr.py, tabnanny.py: tabnanny and pyclbr are now found in /Lib + +2001-07-15 02:29 kbk + + * BrowserControl.py: Remove, was retained for 1.5.2 support + +2001-07-14 15:48 kbk + + * setup.py: Installing Idle to site-packages via Distutils does not + copy the Idle help.txt file. + + Ref SF Python Patch 422471 + +2001-07-14 15:26 kbk + + * keydefs.py: py-cvs-2001_07_13 (Rev 1.3) merge + + "Make copy, cut and paste events case insensitive. Reported by + Patrick K. O'Brien on idle-dev. (Should other bindings follow + suit?)" --GvR + +2001-07-14 15:21 kbk + + * idle.py: py-cvs-2001_07_13 (Rev 1.4) merge + + "Move the action of loading the configuration to the IdleConf + module rather than the idle.py script. This has advantages and + disadvantages; the biggest advantage being that we can more easily + have an alternative main program." --GvR + +2001-07-14 15:18 kbk + + * extend.txt: py-cvs-2001_07_13 (Rev 1.4) merge + + "Quick update to the extension mechanism (extend.py is gone, long + live config.txt)" --GvR + +2001-07-14 15:15 kbk + + * StackViewer.py: py-cvs-2001_07_13 (Rev 1.16) merge + + "Refactored, with some future plans in mind. This now uses the new + gotofileline() method defined in FileList.py" --GvR + +2001-07-14 15:10 kbk + + * PyShell.py: py-cvs-2001_07_13 (Rev 1.34) merge + + "Amazing. A very subtle change in policy in descr-branch actually + found a bug here. Here's the deal: Class PyShell derives from + class OutputWindow. Method PyShell.close() wants to invoke its + parent method, but because PyShell long ago was inherited from + class PyShellEditorWindow, it invokes + PyShelEditorWindow.close(self). Now, class PyShellEditorWindow + itself derives from class OutputWindow, and inherits the close() + method from there without overriding it. Under the old rules, + PyShellEditorWindow.close would return an unbound method restricted + to the class that defined the implementation of close(), which was + OutputWindow.close. Under the new rules, the unbound method is + restricted to the class whose method was requested, that is + PyShellEditorWindow, and this was correctly trapped as an error." + --GvR + +2001-07-14 14:59 kbk + + * PyParse.py: py-cvs-2001_07_13 (Rel 1.9) merge + + "Taught IDLE's autoident parser that "yield" is a keyword that + begins a stmt. Along w/ the preceding change to keyword.py, making + all this work w/ a future-stmt just looks harder and harder." + --tim_one + + (From Rel 1.8: "Hack to make this still work with Python 1.5.2. + ;-( " --fdrake) + +2001-07-14 14:51 kbk + + * IdleConf.py: py-cvs-2001_07_13 (Rel 1.7) merge + + "Move the action of loading the configuration to the IdleConf + module rather than the idle.py script. This has advantages and + disadvantages; the biggest advantage being that we can more easily + have an alternative main program." --GvR + +2001-07-14 14:45 kbk + + * FileList.py: py-cvs-2000_07_13 (Rev 1.9) merge + + "Delete goodname() method, which is unused. Add gotofileline(), a + convenience method which I intend to use in a variant. Rename + test() to _test()." --GvR + + This was an interesting merge. The join completely missed removing + goodname(), which was adjacent, but outside of, a small conflict. + I only caught it by comparing the 1.1.3.2/1.1.3.3 diff. CVS ain't + infallible. + +2001-07-14 13:58 kbk + + * EditorWindow.py: py-cvs-2000_07_13 (Rev 1.38) merge "Remove + legacy support for the BrowserControl module; the webbrowser module + has been included since Python 2.0, and that is the preferred + interface." --fdrake + +2001-07-14 13:32 kbk + + * EditorWindow.py, FileList.py, IdleConf.py, PyParse.py, + PyShell.py, StackViewer.py, extend.txt, idle.py, keydefs.py: Import + the 2001 July 13 23:59 GMT version of Python CVS IDLE on the + existing 1.1.3 vendor branch named py-cvs-vendor-branch. Release + tag is py-cvs-2001_07_13. + +2001-07-14 12:02 kbk + + * Icons/python.gif: py-cvs-rel2_1 (Rev 1.2) merge Copied py-cvs rev + 1.2 changed file to idlefork MAIN + +2001-07-14 11:58 kbk + + * Icons/minusnode.gif: py-cvs-rel2_1 (Rev 1.2) merge Copied py-cvs + 1.2 changed file to idlefork MAIN + +2001-07-14 11:23 kbk + + * ScrolledList.py: py-cvs-rel2_1 (rev 1.5) merge - whitespace + normalization + +2001-07-14 11:20 kbk + + * Separator.py: py-cvs-rel2_1 (Rev 1.3) merge - whitespace + normalization + +2001-07-14 11:16 kbk + + * StackViewer.py: py-cvs-rel2_1 (Rev 1.15) merge - whitespace + normalization + +2001-07-14 11:14 kbk + + * ToolTip.py: py-cvs-rel2_1 (Rev 1.2) merge - whitespace + normalization + +2001-07-14 10:13 kbk + + * PyShell.py: cvs-py-rel2_1 (Rev 1.29 - 1.33) merge + + Merged the following py-cvs revs without conflict: 1.29 Reduce + copyright text output at startup 1.30 Delay setting sys.args until + Tkinter is fully initialized 1.31 Whitespace normalization 1.32 + Turn syntax warning into error when interactive 1.33 Fix warning + initialization bug + + Note that module is extensively modified wrt py-cvs + +2001-07-14 06:33 kbk + + * PyParse.py: py-cvs-rel2_1 (Rev 1.6 - 1.8) merge Fix autoindent + bug and deflect Unicode from text.get() + +2001-07-14 06:00 kbk + + * Percolator.py: py-cvs-rel2_1 (Rev 1.3) "move "from Tkinter import + *" to module level" --jhylton + +2001-07-14 05:57 kbk + + * PathBrowser.py: py-cvs-rel2_1 (Rev 1.6) merge - whitespace + normalization + +2001-07-14 05:49 kbk + + * ParenMatch.py: cvs-py-rel2_1 (Rev 1.5) merge - whitespace + normalization + +2001-07-14 03:57 kbk + + * ObjectBrowser.py: py-cvs-rel2_1 (Rev 1.3) merge "Make the test + program work outside IDLE." -- GvR + +2001-07-14 03:52 kbk + + * MultiStatusBar.py: py-cvs-rel2_1 (Rev 1.2) merge - whitespace + normalization + +2001-07-14 03:44 kbk + + * MultiScrolledLists.py: py-cvs-rel2_1 (Rev 1.2) merge - whitespace + normalization + +2001-07-14 03:40 kbk + + * IdleHistory.py: py-cvs-rel2_1 (Rev 1.4) merge - whitespace + normalization + +2001-07-14 03:38 kbk + + * IdleConf.py: py-cvs-rel2_1 (Rev 1.6) merge - whitespace + normalization + +2001-07-13 14:18 kbk + + * IOBinding.py: py-cvs-rel2_1 (Rev 1.4) merge - move "import *" to + module level + +2001-07-13 14:12 kbk + + * FormatParagraph.py: py-cvs-rel2_1 (Rev 1.9) merge - whitespace + normalization + +2001-07-13 14:07 kbk + + * FileList.py: py-cvs-rel2_1 (Rev 1.8) merge - whitespace + normalization + +2001-07-13 13:35 kbk + + * EditorWindow.py: py-cvs-rel2_1 (Rev 1.33 - 1.37) merge + + VP IDLE version depended on VP's ExecBinding.py and spawn.py to get + the path to the Windows Doc directory (relative to python.exe). + Removed this conflicting code in favor of py-cvs updates which on + Windows use a hard coded path relative to the location of this + module. py-cvs updates include support for webbrowser.py. Module + still has BrowserControl.py for 1.5.2 support. + + At this point, the differences wrt py-cvs relate to menu + functionality. + +2001-07-13 11:30 kbk + + * ConfigParser.py: py-cvs-rel2_1 merge - Remove, lives in /Lib + +2001-07-13 10:10 kbk + + * Delegator.py: py-cvs-rel2_1 (Rev 1.3) merge - whitespace + normalization + +2001-07-13 10:07 kbk + + * Debugger.py: py-cvs-rel2_1 (Rev 1.15) merge - whitespace + normalization + +2001-07-13 10:04 kbk + + * ColorDelegator.py: py-cvs-rel2_1 (Rev 1.11 and 1.12) merge + Colorize "as" after "import" / use DEBUG instead of __debug__ + +2001-07-13 09:54 kbk + + * ClassBrowser.py: py-cvs-rel2_1 (Rev 1.12) merge - whitespace + normalization + +2001-07-13 09:41 kbk + + * BrowserControl.py: py-cvs-rel2_1 (Rev 1.1) merge - New File - + Force HEAD to trunk with -f Note: browser.py was renamed + BrowserControl.py 10 May 2000. It provides a collection of classes + and convenience functions to control external browsers "for 1.5.2 + support". It was removed from py-cvs 18 April 2001. + +2001-07-13 09:10 kbk + + * CallTips.py: py-cvs-rel2_1 (Rev 1.8) merge - whitespace + normalization + +2001-07-13 08:26 kbk + + * CallTipWindow.py: py-cvs-rel2_1 (Rev 1.3) merge - whitespace + normalization + +2001-07-13 08:13 kbk + + * AutoExpand.py: py-cvs-rel1_2 (Rev 1.4) merge, "Add Alt-slash to + Unix keydefs (I somehow need it on RH 6.2). Get rid of assignment + to unused self.text.wordlist." --GvR + +2001-07-12 16:54 elguavas + + * ReplaceDialog.py: py-cvs merge, python 1.5.2 compatibility + +2001-07-12 16:46 elguavas + + * ScriptBinding.py: py-cvs merge, better error dialog + +2001-07-12 16:38 elguavas + + * TODO.txt: py-cvs merge, additions + +2001-07-12 15:35 elguavas + + * WindowList.py: py-cvs merge, correct indentation + +2001-07-12 15:24 elguavas + + * config.txt: py-cvs merge, correct typo + +2001-07-12 15:21 elguavas + + * help.txt: py-cvs merge, update colour changing info + +2001-07-12 14:51 elguavas + + * idle.py: py-cvs merge, idle_dir loading changed + +2001-07-12 14:44 elguavas + + * idlever.py: py-cvs merge, version update + +2001-07-11 12:53 kbk + + * BrowserControl.py: Initial revision + +2001-07-11 12:53 kbk + + * AutoExpand.py, BrowserControl.py, CallTipWindow.py, CallTips.py, + ClassBrowser.py, ColorDelegator.py, Debugger.py, Delegator.py, + EditorWindow.py, FileList.py, FormatParagraph.py, IOBinding.py, + IdleConf.py, IdleHistory.py, MultiScrolledLists.py, + MultiStatusBar.py, ObjectBrowser.py, OutputWindow.py, + ParenMatch.py, PathBrowser.py, Percolator.py, PyParse.py, + PyShell.py, RemoteInterp.py, ReplaceDialog.py, ScriptBinding.py, + ScrolledList.py, Separator.py, StackViewer.py, TODO.txt, + ToolTip.py, WindowList.py, config.txt, help.txt, idle, idle.bat, + idle.py, idlever.py, setup.py, Icons/minusnode.gif, + Icons/python.gif: Import the release 2.1 version of Python CVS IDLE + on the existing 1.1.3 vendor branch named py-cvs-vendor-branch, + with release tag py-cvs-rel2_1. + +2001-07-11 12:34 kbk + + * AutoExpand.py, AutoIndent.py, Bindings.py, CallTipWindow.py, + CallTips.py, ChangeLog, ClassBrowser.py, ColorDelegator.py, + Debugger.py, Delegator.py, EditorWindow.py, FileList.py, + FormatParagraph.py, FrameViewer.py, GrepDialog.py, IOBinding.py, + IdleConf.py, IdleHistory.py, MultiScrolledLists.py, + MultiStatusBar.py, NEWS.txt, ObjectBrowser.py, OldStackViewer.py, + OutputWindow.py, ParenMatch.py, PathBrowser.py, Percolator.py, + PyParse.py, PyShell.py, README.txt, RemoteInterp.py, + ReplaceDialog.py, ScriptBinding.py, ScrolledList.py, + SearchBinding.py, SearchDialog.py, SearchDialogBase.py, + SearchEngine.py, Separator.py, StackViewer.py, TODO.txt, + ToolTip.py, TreeWidget.py, UndoDelegator.py, WidgetRedirector.py, + WindowList.py, ZoomHeight.py, __init__.py, config-unix.txt, + config-win.txt, config.txt, eventparse.py, extend.txt, help.txt, + idle.bat, idle.py, idle.pyw, idlever.py, keydefs.py, pyclbr.py, + tabnanny.py, testcode.py, Icons/folder.gif, Icons/minusnode.gif, + Icons/openfolder.gif, Icons/plusnode.gif, Icons/python.gif, + Icons/tk.gif: Import the 9 March 2000 version of Python CVS IDLE as + 1.1.3 vendor branch named py-cvs-vendor-branch. + +2001-07-04 13:43 kbk + + * Icons/: folder.gif, minusnode.gif, openfolder.gif, plusnode.gif, + python.gif, tk.gif: Null commit with -f option to force an uprev + and put HEADs firmly on the trunk. + +2001-07-04 13:15 kbk + + * AutoExpand.py, AutoIndent.py, Bindings.py, CallTipWindow.py, + CallTips.py, ChangeLog, ClassBrowser.py, ColorDelegator.py, + ConfigParser.py, Debugger.py, Delegator.py, EditorWindow.py, + ExecBinding.py, FileList.py, FormatParagraph.py, FrameViewer.py, + GrepDialog.py, IDLEFORK.html, IOBinding.py, IdleConf.py, + IdleHistory.py, MultiScrolledLists.py, MultiStatusBar.py, NEWS.txt, + ObjectBrowser.py, OldStackViewer.py, OutputWindow.py, + ParenMatch.py, PathBrowser.py, Percolator.py, PyParse.py, + PyShell.py, README.txt, Remote.py, RemoteInterp.py, + ReplaceDialog.py, ScriptBinding.py, ScrolledList.py, + SearchBinding.py, SearchDialog.py, SearchDialogBase.py, + SearchEngine.py, Separator.py, StackViewer.py, TODO.txt, + ToolTip.py, TreeWidget.py, UndoDelegator.py, WidgetRedirector.py, + WindowList.py, ZoomHeight.py, __init__.py, config-unix.txt, + config-win.txt, config.txt, eventparse.py, extend.txt, help.txt, + idle, idle.bat, idle.py, idle.pyw, idlever.py, keydefs.py, + loader.py, protocol.py, pyclbr.py, setup.py, spawn.py, tabnanny.py, + testcode.py: Null commit with -f option to force an uprev and put + HEADs firmly on the trunk. + +2001-06-27 10:24 elguavas + + * IDLEFORK.html: updated contact details + +2001-06-25 17:23 elguavas + + * idle, RemoteInterp.py, setup.py: Initial revision + +2001-06-25 17:23 elguavas + + * idle, RemoteInterp.py, setup.py: import current python cvs idle + as a vendor branch + +2001-06-24 15:10 elguavas + + * IDLEFORK.html: tiny change to test new syncmail setup + +2001-06-24 14:41 elguavas + + * IDLEFORK.html: change to new developer contact, also a test + commit for new syncmail setup + +2001-06-23 18:15 elguavas + + * IDLEFORK.html: tiny test update for revitalised idle-fork + +2000-09-24 17:29 nriley + + * protocol.py: Fixes for Python 1.6 compatibility - socket bind and + connect get a tuple instead two arguments. + +2000-09-24 17:28 nriley + + * spawn.py: Change for Python 1.6 compatibility - UNIX's 'os' + module defines 'spawnv' now, so we check for 'fork' first. + +2000-08-15 22:51 nowonder + + * IDLEFORK.html: + corrected email address + +2000-08-15 22:47 nowonder + + * IDLEFORK.html: + added .html file for http://idlefork.sourceforge.net + +2000-08-15 11:13 dscherer + + * AutoExpand.py, AutoIndent.py, Bindings.py, CallTipWindow.py, + CallTips.py, __init__.py, ChangeLog, ClassBrowser.py, + ColorDelegator.py, ConfigParser.py, Debugger.py, Delegator.py, + FileList.py, FormatParagraph.py, FrameViewer.py, GrepDialog.py, + IOBinding.py, IdleConf.py, IdleHistory.py, MultiScrolledLists.py, + MultiStatusBar.py, NEWS.txt, ObjectBrowser.py, OldStackViewer.py, + OutputWindow.py, ParenMatch.py, PathBrowser.py, Percolator.py, + PyParse.py, PyShell.py, README.txt, ReplaceDialog.py, + ScriptBinding.py, ScrolledList.py, SearchBinding.py, + SearchDialog.py, SearchDialogBase.py, SearchEngine.py, + Separator.py, StackViewer.py, TODO.txt, ToolTip.py, TreeWidget.py, + UndoDelegator.py, WidgetRedirector.py, WindowList.py, help.txt, + ZoomHeight.py, config-unix.txt, config-win.txt, config.txt, + eventparse.py, extend.txt, idle.bat, idle.py, idle.pyw, idlever.py, + keydefs.py, loader.py, pyclbr.py, tabnanny.py, testcode.py, + EditorWindow.py, ExecBinding.py, Remote.py, protocol.py, spawn.py, + Icons/folder.gif, Icons/minusnode.gif, Icons/openfolder.gif, + Icons/plusnode.gif, Icons/python.gif, Icons/tk.gif: Initial + revision + +2000-08-15 11:13 dscherer + + * AutoExpand.py, AutoIndent.py, Bindings.py, CallTipWindow.py, + CallTips.py, __init__.py, ChangeLog, ClassBrowser.py, + ColorDelegator.py, ConfigParser.py, Debugger.py, Delegator.py, + FileList.py, FormatParagraph.py, FrameViewer.py, GrepDialog.py, + IOBinding.py, IdleConf.py, IdleHistory.py, MultiScrolledLists.py, + MultiStatusBar.py, NEWS.txt, ObjectBrowser.py, OldStackViewer.py, + OutputWindow.py, ParenMatch.py, PathBrowser.py, Percolator.py, + PyParse.py, PyShell.py, README.txt, ReplaceDialog.py, + ScriptBinding.py, ScrolledList.py, SearchBinding.py, + SearchDialog.py, SearchDialogBase.py, SearchEngine.py, + Separator.py, StackViewer.py, TODO.txt, ToolTip.py, TreeWidget.py, + UndoDelegator.py, WidgetRedirector.py, WindowList.py, help.txt, + ZoomHeight.py, config-unix.txt, config-win.txt, config.txt, + eventparse.py, extend.txt, idle.bat, idle.py, idle.pyw, idlever.py, + keydefs.py, loader.py, pyclbr.py, tabnanny.py, testcode.py, + EditorWindow.py, ExecBinding.py, Remote.py, protocol.py, spawn.py, + Icons/folder.gif, Icons/minusnode.gif, Icons/openfolder.gif, + Icons/plusnode.gif, Icons/python.gif, Icons/tk.gif: Modified IDLE + from VPython 0.2 + + +original IDLE ChangeLog: +======================== + +Tue Feb 15 18:08:19 2000 Guido van Rossum <guido@cnri.reston.va.us> + + * NEWS.txt: Notice status bar and stack viewer. + + * EditorWindow.py: Support for Moshe's status bar. + + * MultiStatusBar.py: Status bar code -- by Moshe Zadka. + + * OldStackViewer.py: + Adding the old stack viewer implementation back, for the debugger. + + * StackViewer.py: New stack viewer, uses a tree widget. + (XXX: the debugger doesn't yet use this.) + + * WindowList.py: + Correct a typo and remove an unqualified except that was hiding the error. + + * ClassBrowser.py: Add an XXX comment about the ClassBrowser AIP. + + * ChangeLog: Updated change log. + + * NEWS.txt: News update. Probably incomplete; what else is new? + + * README.txt: + Updated for pending IDLE 0.5 release (still very rough -- just getting + it out in a more convenient format than CVS). + + * TODO.txt: Tiny addition. + +Thu Sep 9 14:16:02 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * TODO.txt: A few new TODO entries. + +Thu Aug 26 23:06:22 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * Bindings.py: Add Python Documentation entry to Help menu. + + * EditorWindow.py: + Find the help.txt file relative to __file__ or ".", not in sys.path. + (Suggested by Moshe Zadka, but implemented differently.) + + Add <<python-docs>> event which, on Unix, brings up Netscape pointing + to http://www.python.doc/current/ (a local copy would be nice but its + location can't be predicted). Windows solution TBD. + +Wed Aug 11 14:55:43 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * TreeWidget.py: + Moshe noticed an inconsistency in his comment, so I'm rephrasing it to + be clearer. + + * TreeWidget.py: + Patch inspired by Moshe Zadka to search for the Icons directory in the + same directory as __file__, rather than searching for it along sys.path. + This works better when idle is a package. + +Thu Jul 15 13:11:02 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * TODO.txt: New wishes. + +Sat Jul 10 13:17:35 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * IdlePrefs.py: + Make the color for stderr red (i.e. the standard warning/danger/stop + color) rather than green. Suggested by Sam Schulenburg. + +Fri Jun 25 17:26:34 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * PyShell.py: Close debugger when closing. This may break a cycle. + + * Debugger.py: Break cycle on close. + + * ClassBrowser.py: Destroy the tree when closing. + + * TreeWidget.py: Add destroy() method to recursively destroy a tree. + + * PyShell.py: Extend _close() to break cycles. + Break some other cycles too (and destroy the root when done). + + * EditorWindow.py: + Add _close() method that does the actual cleanup (close() asks the + user what they want first if there's unsaved stuff, and may cancel). + It closes more than before. + + Add unload_extensions() method to unload all extensions; called from + _close(). It calls an extension's close() method if it has one. + + * Percolator.py: Add close() method that breaks cycles. + + * WidgetRedirector.py: Add unregister() method. + Unregister everything at closing. + Don't call close() in __del__, rely on explicit call to close(). + + * IOBinding.py, FormatParagraph.py, CallTips.py: + Add close() method that breaks a cycle. + +Fri Jun 11 15:03:00 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * AutoIndent.py, EditorWindow.py, FormatParagraph.py: + Tim Peters smart.patch: + + EditorWindow.py: + + + Added get_tabwidth & set_tabwidth "virtual text" methods, that get/set the + widget's view of what a tab means. + + + Moved TK_TABWIDTH_DEFAULT here from AutoIndent. + + + Renamed Mark's get_selection_index to get_selection_indices (sorry, Mark, + but the name was plain wrong <wink>). + + FormatParagraph.py: renamed use of get_selection_index. + + AutoIndent.py: + + + Moved TK_TABWIDTH_DEFAULT to EditorWindow. + + + Rewrote set_indentation_params to use new VTW get/set_tabwidth methods. + + + Changed smart_backspace_event to delete whitespace back to closest + preceding virtual tab stop or real character (note that this may require + inserting characters if backspacing over a tab!). + + + Nuked almost references to the selection tag, in favor of using + get_selection_indices. The sole exception is in set_region, for which no + "set_selection" abstraction has yet been agreed upon. + + + Had too much fun using the spiffy new features of the format-paragraph + cmd. + +Thu Jun 10 17:48:02 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * FormatParagraph.py: + Code by Mark Hammond to format paragraphs embedded in comments. + Read the comments (which I reformatted using the new feature :-) + for some limitations. + + * EditorWindow.py: + Added abstraction get_selection_index() (Mark Hammond). Also + reformatted some comment blocks to show off a cool feature I'm about + to check in next. + + * ClassBrowser.py: + Adapt to the new pyclbr's support of listing top-level functions. If + this functionality is not present (e.g. when used with a vintage + Python 1.5.2 installation) top-level functions are not listed. + + (Hmm... Any distribution of IDLE 0.5 should probably include a copy + of the new pyclbr.py!) + + * AutoIndent.py: + Fix off-by-one error in Tim's recent change to comment_region(): the + list of lines returned by get_region() contains an empty line at the + end representing the start of the next line, and this shouldn't be + commented out! + + * CallTips.py: + Mark Hammond writes: Here is another change that allows it to work for + class creation - tries to locate an __init__ function. Also updated + the test code to reflect your new "***" change. + + * CallTipWindow.py: + Mark Hammond writes: Tim's suggestion of copying the font for the + CallTipWindow from the text control makes sense, and actually makes + the control look better IMO. + +Wed Jun 9 20:34:57 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * CallTips.py: + Append "..." if the appropriate flag (for varargs) in co_flags is set. + Ditto "***" for kwargs. + +Tue Jun 8 13:06:07 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * ReplaceDialog.py: + Hmm... Tim didn't turn "replace all" into a single undo block. + I think I like it better if it os, so here. + + * ReplaceDialog.py: Tim Peters: made replacement atomic for undo/redo. + + * AutoIndent.py: Tim Peters: + + + Set usetabs=1. Editing pyclbr.py was driving me nuts <0.6 wink>. + usetabs=1 is the Emacs pymode default too, and thanks to indentwidth != + tabwidth magical usetabs disabling, new files are still created with tabs + turned off. The only implication is that if you open a file whose first + indent is a single tab, IDLE will now magically use tabs for that file (and + set indentwidth to 8). Note that the whole scheme doesn't work right for + PythonWin, though, since Windows users typically set tabwidth to 4; Mark + probably has to hide the IDLE algorithm from them (which he already knows). + + + Changed comment_region_event to stick "##" in front of every line. The + "holes" previously left on blank lines were visually confusing (made it + needlessly hard to figure out what to uncomment later). + +Mon Jun 7 15:38:40 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * TreeWidget.py, ObjectBrowser.py: + Remove unnecessary reference to pyclbr from test() code. + + * PyParse.py: Tim Peters: + + Smarter logic for finding a parse synch point. + + Does a half to a fifth the work in normal cases; don't notice the speedup, + but makes more breathing room for other extensions. + + Speeds terrible cases by at least a factor of 10. "Terrible" == e.g. you put + """ at the start of Tkinter.py, undo it, zoom to the bottom, and start + typing in code. Used to take about 8 seconds for ENTER to respond, now some + large fraction of a second. The new code gets indented correctly, despite + that it all remains "string colored" until the colorizer catches up (after + which, ENTER appears instantaneous again). + +Fri Jun 4 19:21:19 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * extend.py: Might as well enable CallTips by default. + If there are too many complaints I'll remove it again or fix it. + +Thu Jun 3 14:32:16 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * AutoIndent.py, EditorWindow.py, PyParse.py: + New offerings by Tim Peters; he writes: + + IDLE is now the first Python editor in the Universe not confused by my + doctest.py <wink>. + + As threatened, this defines IDLE's is_char_in_string function as a + method of EditorWindow. You just need to define one similarly in + whatever it is you pass as editwin to AutoIndent; looking at the + EditorWindow.py part of the patch should make this clear. + + * GrepDialog.py: Enclose pattern in quotes in status message. + + * CallTips.py: + Mark Hammond fixed some comments and improved the way the tip text is + constructed. + +Wed Jun 2 18:18:57 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * CallTips.py: + My fix to Mark's code: restore the universal check on <KeyRelease>. + Always cancel on <Key-Escape> or <ButtonPress>. + + * CallTips.py: + A version that Mark Hammond posted to the newsgroup. Has some newer + stuff for getting the tip. Had to fix the Key-( and Key-) events + for Unix. Will have to re-apply my patch for catching KeyRelease and + ButtonRelease events. + + * CallTipWindow.py, CallTips.py: + Call tips by Mark Hammond (plus tiny fix by me.) + + * IdleHistory.py: + Changes by Mark Hammond: (1) support optional output_sep argument to + the constructor so he can eliminate the sys.ps2 that PythonWin leaves + in the source; (2) remove duplicate history items. + + * AutoIndent.py: + Changes by Mark Hammond to allow using IDLE extensions in PythonWin as + well: make three dialog routines instance variables. + + * EditorWindow.py: + Change by Mark Hammond to allow using IDLE extensions in PythonWin as + well: make three dialog routines instance variables. + +Tue Jun 1 20:06:44 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * AutoIndent.py: Hah! A fix of my own to Tim's code! + Unix bindings for <<toggle-tabs>> and <<change-indentwidth>> were + missing, and somehow that meant the events were never generated, + even though they were in the menu. The new Unix bindings are now + the same as the Windows bindings (M-t and M-u). + + * AutoIndent.py, PyParse.py, PyShell.py: Tim Peters again: + + The new version (attached) is fast enough all the time in every real module + I have <whew!>. You can make it slow by, e.g., creating an open list with + 5,000 90-character identifiers (+ trailing comma) each on its own line, then + adding an item to the end -- but that still consumes less than a second on + my P5-166. Response time in real code appears instantaneous. + + Fixed some bugs. + + New feature: when hitting ENTER and the cursor is beyond the line's leading + indentation, whitespace is removed on both sides of the cursor; before + whitespace was removed only on the left; e.g., assuming the cursor is + between the comma and the space: + + def something(arg1, arg2): + ^ cursor to the left of here, and hit ENTER + arg2): # new line used to end up here + arg2): # but now lines up the way you expect + + New hack: AutoIndent has grown a context_use_ps1 Boolean config option, + defaulting to 0 (false) and set to 1 (only) by PyShell. Reason: handling + the fancy stuff requires looking backward for a parsing synch point; ps1 + lines are the only sensible thing to look for in a shell window, but are a + bad thing to look for in a file window (ps1 lines show up in my module + docstrings often). PythonWin's shell should set this true too. + + Persistent problem: strings containing def/class can still screw things up + completely. No improvement. Simplest workaround is on the user's head, and + consists of inserting e.g. + + def _(): pass + + (or any other def/class) after the end of the multiline string that's + screwing them up. This is especially irksome because IDLE's syntax coloring + is *not* confused, so when this happens the colors don't match the + indentation behavior they see. + + * AutoIndent.py: Tim Peters again: + + [Tim, after adding some bracket smarts to AutoIndent.py] + > ... + > What it can't possibly do without reparsing large gobs of text is + > suggest a reasonable indent level after you've *closed* a bracket + > left open on some previous line. + > ... + + The attached can, and actually fast enough to use -- most of the time. The + code is tricky beyond belief to achieve that, but it works so far; e.g., + + return len(string.expandtabs(str[self.stmt_start : + ^ indents to caret + i], + ^ indents to caret + self.tabwidth)) + 1 + ^ indents to caret + + It's about as smart as pymode now, wrt both bracket and backslash + continuation rules. It does require reparsing large gobs of text, and if it + happens to find something that looks like a "def" or "class" or sys.ps1 + buried in a multiline string, but didn't suck up enough preceding text to + see the start of the string, it's completely hosed. I can't repair that -- + it's just too slow to reparse from the start of the file all the time. + + AutoIndent has grown a new num_context_lines tuple attribute that controls + how far to look back, and-- like other params --this could/should be made + user-overridable at startup and per-file on the fly. + + * PyParse.py: New file by Tim Peters: + + One new file in the attached, PyParse.py. The LineStudier (whatever it was + called <wink>) class was removed from AutoIndent; PyParse subsumes its + functionality. + + * AutoIndent.py: Tim Peters keeps revising this module (more to come): + + Removed "New tabwidth" menu binding. + + Added "a tab means how many spaces?" dialog to block tabify and untabify. I + think prompting for this is good now: they're usually at-most-once-per-file + commands, and IDLE can't let them change tabwidth from the Tk default + anymore, so IDLE can no longer presume to have any idea what a tab means. + + Irony: for the purpose of keeping comments aligned via tabs, Tk's + non-default approach is much nicer than the Emacs/Notepad/Codewright/vi/etc + approach. + + * EditorWindow.py: + 1. Catch NameError on import (could be raised by case mismatch on Windows). + 2. No longer need to reset pyclbr cache and show watch cursor when calling + ClassBrowser -- the ClassBrowser takes care of pyclbr and the TreeWidget + takes care of the watch cursor. + 3. Reset the focus to the current window after error message about class + browser on buffer without filename. + + * Icons/minusnode.gif, Icons/plusnode.gif: Missed a few. + + * ClassBrowser.py, PathBrowser.py: Rewritten based on TreeWidget.py + + * ObjectBrowser.py: Object browser, based on TreeWidget.py. + + * TreeWidget.py: Tree widget done right. + + * ToolTip.py: As yet unused code for tool tips. + + * ScriptBinding.py: + Ensure sys.argv[0] is the script name on Run Script. + + * ZoomHeight.py: Move zoom height functionality to separate function. + + * Icons/folder.gif, Icons/openfolder.gif, Icons/python.gif, Icons/tk.gif: + A few icons used by ../TreeWidget.py and its callers. + + * AutoIndent.py: New version by Tim Peters improves block opening test. + +Fri May 21 04:46:17 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * Attic/History.py, PyShell.py: Rename History to IdleHistory. + Add isatty() to pseudo files. + + * StackViewer.py: Make initial stack viewer wider + + * TODO.txt: New wishes + + * AutoIndent.py, EditorWindow.py, PyShell.py: + Much improved autoindent and handling of tabs, + by Tim Peters. + +Mon May 3 15:49:52 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * AutoIndent.py, EditorWindow.py, FormatParagraph.py, UndoDelegator.py: + Tim Peters writes: + + I'm still unsure, but couldn't stand the virtual event trickery so tried a + different sin (adding undo_block_start/stop methods to the Text instance in + EditorWindow.py). Like it or not, it's efficient and works <wink>. Better + idea? + + Give the attached a whirl. Even if you hate the implementation, I think + you'll like the results. Think I caught all the "block edit" cmds, + including Format Paragraph, plus subtler ones involving smart indents and + backspacing. + + * WidgetRedirector.py: Tim Peters writes: + + [W]hile trying to dope out how redirection works, stumbled into two + possible glitches. In the first, it doesn't appear to make sense to try to + rename a command that's already been destroyed; in the second, the name + "previous" doesn't really bring to mind "ignore the previous value" <wink>. + +Fri Apr 30 19:39:25 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * __init__.py: Support for using idle as a package. + + * PathBrowser.py: + Avoid listing files more than once (e.g. foomodule.so has two hits: + once for foo + module.so, once for foomodule + .so). + +Mon Apr 26 22:20:38 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * ChangeLog, ColorDelegator.py, PyShell.py: Tim Peters strikes again: + + Ho ho ho -- that's trickier than it sounded! The colorizer is working with + "line.col" strings instead of Text marks, and the absolute coordinates of + the point of interest can change across the self.update call (voice of + baffled experience, when two quick backspaces no longer fooled it, but a + backspace followed by a quick ENTER did <wink>). + + Anyway, the attached appears to do the trick. CPU usage goes way up when + typing quickly into a long triple-quoted string, but the latency is fine for + me (a relatively fast typist on a relatively slow machine). Most of the + changes here are left over from reducing the # of vrbl names to help me + reason about the logic better; I hope the code is a *little* easier to + +Fri Apr 23 14:01:25 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * EditorWindow.py: + Provide full arguments to __import__ so it works in packagized IDLE. + +Thu Apr 22 23:20:17 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * help.txt: + Bunch of updates necessary due to recent changes; added docs for File + menu, command line and color preferences. + + * Bindings.py: Remove obsolete 'script' menu. + + * TODO.txt: Several wishes fulfilled. + + * OutputWindow.py: + Moved classes OnDemandOutputWindow and PseudoFile here, + from ScriptBinding.py where they are no longer needed. + + * ScriptBinding.py: + Mostly rewritten. Instead of the old Run module and Debug module, + there are two new commands: + + Import module (F5) imports or reloads the module and also adds its + name to the __main__ namespace. This gets executed in the PyShell + window under control of its debug settings. + + Run script (Control-F5) is similar but executes the contents of the + file directly in the __main__ namespace. + + * PyShell.py: Nits: document use of $IDLESTARTUP; display idle version + + * idlever.py: New version to celebrate new command line + + * OutputWindow.py: Added flush(), for completeness. + + * PyShell.py: + A lot of changes to make the command line more useful. You can now do: + idle.py -e file ... -- to edit files + idle.py script arg ... -- to run a script + idle.py -c cmd arg ... -- to run a command + Other options, see also the usage message (also new!) for more details: + -d -- enable debugger + -s -- run $IDLESTARTUP or $PYTHONSTARTUP + -t title -- set Python Shell window's title + sys.argv is set accordingly, unless -e is used. + sys.path is absolutized, and all relevant paths are inserted into it. + + Other changes: + - the environment in which commands are executed is now the + __main__ module + - explicitly save sys.stdout etc., don't restore from sys.__stdout__ + - new interpreter methods execsource(), execfile(), stuffsource() + - a few small nits + + * TODO.txt: + Some more TODO items. Made up my mind about command line args, + Run/Import, __main__. + + * ColorDelegator.py: + Super-elegant patch by Tim Peters that speeds up colorization + dramatically (up to 15 times he claims). Works by reading more than + one line at a time, up to 100-line chunks (starting with one line and + then doubling up to the limit). On a typical machine (e.g. Tim's + P5-166) this doesn't reduce interactive responsiveness in a noticeable + way. + +Wed Apr 21 15:49:34 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * ColorDelegator.py: + Patch by Tim Peters to speed up colorizing of big multiline strings. + +Tue Apr 20 17:32:52 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * extend.txt: + For an event 'foo-bar', the corresponding method must be called + foo_bar_event(). Therefore, fix the references to zoom_height() in + the example. + + * IdlePrefs.py: Restored the original IDLE color scheme. + + * PyShell.py, IdlePrefs.py, ColorDelegator.py, EditorWindow.py: + Color preferences code by Loren Luke (massaged by me somewhat) + + * SearchEngine.py: + Patch by Mark Favas: it fixes the search engine behaviour where an + unsuccessful search wraps around and re-searches that part of the file + between the start of the search and the end of the file - only really + an issue for very large files, but... (also removes a redundant + m.span() call). + +Mon Apr 19 16:26:02 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * TODO.txt: A few wishes are now fulfilled. + + * AutoIndent.py: Tim Peters implements some of my wishes: + + o Makes the tab key intelligently insert spaces when appropriate + (see Help list banter twixt David Ascher and me; idea stolen from + every other editor on earth <wink>). + + o newline_and_indent_event trims trailing whitespace on the old + line (pymode and Codewright). + + o newline_and_indent_event no longer fooled by trailing whitespace or + comment after ":" (pymode, PTUI). + + o newline_and_indent_event now reduces the new line's indentation after + return, break, continue, raise and pass stmts (pymode). + + The last two are easy to fool in the presence of strings & + continuations, but pymode requires Emacs's high-powered C parsing + functions to avoid that in finite time. + +====================================================================== + Python release 1.5.2c1, IDLE version 0.4 +====================================================================== + +Wed Apr 7 18:41:59 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * README.txt, NEWS.txt: New version. + + * idlever.py: Version bump awaiting impending new release. + (Not much has changed :-( ) + +Mon Mar 29 14:52:28 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * ScriptBinding.py, PyShell.py: + At Tim Peters' recommendation, add a dummy flush() method to + PseudoFile. + +Thu Mar 11 23:21:23 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * PathBrowser.py: Don't crash when sys.path contains an empty string. + + * Attic/Outline.py: This file was never supposed to be part of IDLE. + + * PathBrowser.py: + - Don't crash in the case where a superclass is a string instead of a + pyclbr.Class object; this can happen when the superclass is + unrecognizable (to pyclbr), e.g. when module renaming is used. + + - Show a watch cursor when calling pyclbr (since it may take a while + recursively parsing imported modules!). + +Wed Mar 10 05:18:02 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * EditorWindow.py, Bindings.py: Add PathBrowser to File module + + * PathBrowser.py: "Path browser" - 4 scrolled lists displaying: + directories on sys.path + modules in selected directory + classes in selected module + methods of selected class + + Sinlge clicking in a directory, module or class item updates the next + column with info about the selected item. Double clicking in a + module, class or method item opens the file (and selects the clicked + item if it is a class or method). + + I guess eventually I should be using a tree widget for this, but the + ones I've seen don't work well enough, so for now I use the old + Smalltalk or NeXT style multi-column hierarchical browser. + + * MultiScrolledLists.py: + New utility: multiple scrolled lists in parallel + + * ScrolledList.py: - White background. + - Display "(None)" (or text of your choosing) when empty. + - Don't set the focus. + +====================================================================== + Python release 1.5.2b2, IDLE version 0.3 +====================================================================== + +Wed Feb 17 22:47:41 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * NEWS.txt: News in 0.3. + + * README.txt, idlever.py: Bump version to 0.3. + + * EditorWindow.py: + After all, we don't need to call the callbacks ourselves! + + * WindowList.py: + When deleting, call the callbacks *after* deleting the window from our list! + + * EditorWindow.py: + Fix up the Windows menu via the new callback mechanism instead of + depending on menu post commands (which don't work when the menu is + torn off). + + * WindowList.py: + Support callbacks to patch up Windows menus everywhere. + + * ChangeLog: Oh, why not. Checking in the Emacs-generated change log. + +Tue Feb 16 22:34:17 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * ScriptBinding.py: + Only pop up the stack viewer when requested in the Debug menu. + +Mon Feb 8 22:27:49 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * WindowList.py: Don't crash if a window no longer exists. + + * TODO.txt: Restructured a bit. + +Mon Feb 1 23:06:17 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * PyShell.py: Add current dir or paths of file args to sys.path. + + * Debugger.py: Add canonic() function -- for brand new bdb.py feature. + + * StackViewer.py: Protect against accessing an empty stack. + +Fri Jan 29 20:44:45 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * ZoomHeight.py: + Use only the height to decide whether to zoom in or out. + +Thu Jan 28 22:24:30 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * EditorWindow.py, FileList.py: + Make sure the Tcl variables are shared between windows. + + * PyShell.py, EditorWindow.py, Bindings.py: + Move menu/key binding code from Bindings.py to EditorWindow.py, + with changed APIs -- it makes much more sense there. + Also add a new feature: if the first character of a menu label is + a '!', it gets a checkbox. Checkboxes are bound to Boolean Tcl variables + that can be accessed through the new getvar/setvar/getrawvar API; + the variable is named after the event to which the menu is bound. + + * Debugger.py: Add Quit button to the debugger window. + + * SearchDialog.py: + When find_again() finds exactly the current selection, it's a failure. + + * idle.py, Attic/idle: Rename idle -> idle.py + +Mon Jan 18 15:18:57 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * EditorWindow.py, WindowList.py: Only deiconify when iconic. + + * TODO.txt: Misc + +Tue Jan 12 22:14:34 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * testcode.py, Attic/test.py: + Renamed test.py to testcode.py so one can import Python's + test package from inside IDLE. (Suggested by Jack Jansen.) + + * EditorWindow.py, ColorDelegator.py: + Hack to close a window that is colorizing. + + * Separator.py: Vladimir Marangozov's patch: + The separator dances too much and seems to jump by arbitrary amounts + in arbitrary directions when I try to move it for resizing the frames. + This patch makes it more quiet. + +Mon Jan 11 14:52:40 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * TODO.txt: Some requests have been fulfilled. + + * EditorWindow.py: + Set the cursor to a watch when opening the class browser (which may + take quite a while, browsing multiple files). + + Newer, better center() -- but assumes no wrapping. + + * SearchBinding.py: + Got rid of debug print statement in goto_line_event(). + + * ScriptBinding.py: + I think I like it better if it prints the traceback even when it displays + the stack viewer. + + * Debugger.py: Bind ESC to close-window. + + * ClassBrowser.py: Use a HSeparator between the classes and the items. + Make the list of classes wider by default (40 chars). + Bind ESC to close-window. + + * Separator.py: + Separator classes (draggable divider between two panes). + +Sat Jan 9 22:01:33 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * WindowList.py: + Don't traceback when wakeup() is called when the window has been destroyed. + This can happen when a torn-of Windows menu references closed windows. + And Tim Peters claims that the Windows menu is his favorite to tear off... + + * EditorWindow.py: Allow tearing off of the Windows menu. + + * StackViewer.py: Close on ESC. + + * help.txt: Updated a bunch of things (it was mostly still 0.1!) + + * extend.py: Added ScriptBinding to standard bindings. + + * ScriptBinding.py: + This now actually works. See doc string. It can run a module (i.e. + import or reload) or debug it (same with debugger control). Output + goes to a fresh output window, only created when needed. + +====================================================================== + Python release 1.5.2b1, IDLE version 0.2 +====================================================================== + +Fri Jan 8 17:26:02 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * README.txt, NEWS.txt: What's new in this release. + + * Bindings.py, PyShell.py: + Paul Prescod's patches to allow the stack viewer to pop up when a + traceback is printed. + +Thu Jan 7 00:12:15 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * FormatParagraph.py: + Change paragraph width limit to 70 (like Emacs M-Q). + + * README.txt: + Separating TODO from README. Slight reformulation of features. No + exact release date. + + * TODO.txt: Separating TODO from README. + +Mon Jan 4 21:19:09 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * FormatParagraph.py: + Hm. There was a boundary condition error at the end of the file too. + + * SearchBinding.py: Hm. Add Unix binding for replace, too. + + * keydefs.py: Ran eventparse.py again. + + * FormatParagraph.py: Added Unix Meta-q key binding; + fix find_paragraph when at start of file. + + * AutoExpand.py: Added Meta-/ binding for Unix as alt for Alt-/. + + * SearchBinding.py: + Add unix binding for grep (otherwise the menu entry doesn't work!) + + * ZoomHeight.py: Adjusted Unix height to work with fvwm96. :=( + + * GrepDialog.py: Need to import sys! + + * help.txt, extend.txt, README.txt: Formatted some paragraphs + + * extend.py, FormatParagraph.py: + Add new extension to reformat a (text) paragraph. + + * ZoomHeight.py: Typo in Win specific height setting. + +Sun Jan 3 00:47:35 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * AutoIndent.py: Added something like Tim Peters' backspace patch. + + * ZoomHeight.py: Adapted to Unix (i.e., more hardcoded constants). + +Sat Jan 2 21:28:54 1999 Guido van Rossum <guido@cnri.reston.va.us> + + * keydefs.py, idlever.py, idle.pyw, idle.bat, help.txt, extend.txt, extend.py, eventparse.py, ZoomHeight.py, WindowList.py, UndoDelegator.py, StackViewer.py, SearchEngine.py, SearchDialogBase.py, SearchDialog.py, ScrolledList.py, SearchBinding.py, ScriptBinding.py, ReplaceDialog.py, Attic/README, README.txt, PyShell.py, Attic/PopupMenu.py, OutputWindow.py, IOBinding.py, Attic/HelpWindow.py, History.py, GrepDialog.py, FileList.py, FrameViewer.py, EditorWindow.py, Debugger.py, Delegator.py, ColorDelegator.py, Bindings.py, ClassBrowser.py, AutoExpand.py, AutoIndent.py: + Checking in IDLE 0.2. + + Much has changed -- too much, in fact, to write down. + The big news is that there's a standard way to write IDLE extensions; + see extend.txt. Some sample extensions have been provided, and + some existing code has been converted to extensions. Probably the + biggest new user feature is a new search dialog with more options, + search and replace, and even search in files (grep). + + This is exactly as downloaded from my laptop after returning + from the holidays -- it hasn't even been tested on Unix yet. + +Fri Dec 18 15:52:54 1998 Guido van Rossum <guido@cnri.reston.va.us> + + * FileList.py, ClassBrowser.py: + Fix the class browser to work even when the file is not on sys.path. + +Tue Dec 8 20:39:36 1998 Guido van Rossum <guido@cnri.reston.va.us> + + * Attic/turtle.py: Moved to Python 1.5.2/Lib + +Fri Nov 27 03:19:20 1998 Guido van Rossum <guido@cnri.reston.va.us> + + * help.txt: Typo + + * EditorWindow.py, FileList.py: Support underlining of menu labels + + * Bindings.py: + New approach, separate tables for menus (platform-independent) and key + definitions (platform-specific), and generating accelerator strings + automatically from the key definitions. + +Mon Nov 16 18:37:42 1998 Guido van Rossum <guido@cnri.reston.va.us> + + * Attic/README: Clarify portability and main program. + + * Attic/README: Added intro for 0.1 release and append Grail notes. + +Mon Oct 26 18:49:00 1998 Guido van Rossum <guido@cnri.reston.va.us> + + * Attic/turtle.py: root is now a global called _root + +Sat Oct 24 16:38:38 1998 Guido van Rossum <guido@cnri.reston.va.us> + + * Attic/turtle.py: Raise the root window on reset(). + Different action on WM_DELETE_WINDOW is more likely to do the right thing, + allowing us to destroy old windows. + + * Attic/turtle.py: + Split the goto() function in two: _goto() is the internal one, + using Canvas coordinates, and goto() uses turtle coordinates + and accepts variable argument lists. + + * Attic/turtle.py: Cope with destruction of the window + + * Attic/turtle.py: Turtle graphics + + * Debugger.py: Use of Breakpoint class should be bdb.Breakpoint. + +Mon Oct 19 03:33:40 1998 Guido van Rossum <guido@cnri.reston.va.us> + + * SearchBinding.py: + Speed up the search a bit -- don't drag a mark around... + + * PyShell.py: + Change our special entries from <console#N> to <pyshell#N>. + Patch linecache.checkcache() to keep our special entries alive. + Add popup menu to all editor windows to set a breakpoint. + + * Debugger.py: + Use and pass through the 'force' flag to set_dict() where appropriate. + Default source and globals checkboxes to false. + Don't interact in user_return(). + Add primitive set_breakpoint() method. + + * ColorDelegator.py: + Raise priority of 'sel' tag so its foreground (on Windows) will take + priority over text colorization (which on Windows is almost the + same color as the selection background). + + Define a tag and color for breakpoints ("BREAK"). + + * Attic/PopupMenu.py: Disable "Open stack viewer" and "help" commands. + + * StackViewer.py: + Add optional 'force' argument (default 0) to load_dict(). + If set, redo the display even if it's the same dict. + +Fri Oct 16 21:10:12 1998 Guido van Rossum <guido@cnri.reston.va.us> + + * StackViewer.py: Do nothing when loading the same dict as before. + + * PyShell.py: Details for debugger interface. + + * Debugger.py: + Restructured and more consistent. Save checkboxes across instantiations. + + * EditorWindow.py, Attic/README, Bindings.py: + Get rid of conflicting ^X binding. Use ^W. + + * Debugger.py, StackViewer.py: + Debugger can now show local and global variables. + + * Debugger.py: Oops + + * Debugger.py, PyShell.py: Better debugger support (show stack etc). + + * Attic/PopupMenu.py: Follow renames in StackViewer module + + * StackViewer.py: + Rename classes to StackViewer (the widget) and StackBrowser (the toplevel). + + * ScrolledList.py: Add close() method + + * EditorWindow.py: Clarify 'Open Module' dialog text + + * StackViewer.py: Restructured into a browser and a widget. + +Thu Oct 15 23:27:08 1998 Guido van Rossum <guido@cnri.reston.va.us> + + * ClassBrowser.py, ScrolledList.py: + Generalized the scrolled list which is the base for the class and + method browser into a separate class in its own module. + + * Attic/test.py: Cosmetic change + + * Debugger.py: Don't show function name if there is none + +Wed Oct 14 03:43:05 1998 Guido van Rossum <guido@cnri.reston.va.us> + + * Debugger.py, PyShell.py: Polish the Debugger GUI a bit. + Closing it now also does the right thing. + +Tue Oct 13 23:51:13 1998 Guido van Rossum <guido@cnri.reston.va.us> + + * Debugger.py, PyShell.py, Bindings.py: + Ad primitive debugger interface (so far it will step and show you the + source, but it doesn't yet show the stack). + + * Attic/README: Misc + + * StackViewer.py: Whoops -- referenced self.top before it was set. + + * help.txt: Added history and completion commands. + + * help.txt: Updated + + * FileList.py: Add class browser functionality. + + * StackViewer.py: + Add a close() method and bind to WM_DELETE_WINDOW protocol + + * PyShell.py: Clear the linecache before printing a traceback + + * Bindings.py: Added class browser binding. + + * ClassBrowser.py: Much improved, much left to do. + + * PyShell.py: Make the return key do what I mean more often. + + * ClassBrowser.py: + Adding the beginnings of a Class browser. Incomplete, yet. + + * EditorWindow.py, Bindings.py: + Add new command, "Open module". You select or type a module name, + and it opens the source. + +Mon Oct 12 23:59:27 1998 Guido van Rossum <guido@cnri.reston.va.us> + + * PyShell.py: Subsume functionality from Popup menu in Debug menu. + Other stuff so the PyShell window can be resurrected from the Windows menu. + + * FileList.py: Get rid of PopUp menu. + Create a simple Windows menu. (Imperfect when Untitled windows exist.) + Add wakeup() method: deiconify, raise, focus. + + * EditorWindow.py: Generalize menu creation. + + * Bindings.py: Add Debug and Help menu items. + + * EditorWindow.py: Added a menu bar to every window. + + * Bindings.py: Add menu configuration to the event configuration. + + * Attic/PopupMenu.py: Pass a root to the help window. + + * SearchBinding.py: + Add parent argument to 'go to line number' dialog box. + +Sat Oct 10 19:15:32 1998 Guido van Rossum <guido@cnri.reston.va.us> + + * StackViewer.py: + Add a label at the top showing (very basic) help for the stack viewer. + Add a label at the bottom showing the exception info. + + * Attic/test.py, Attic/idle: Add Unix main script and test program. + + * idle.pyw, help.txt, WidgetRedirector.py, UndoDelegator.py, StackViewer.py, SearchBinding.py, Attic/README, PyShell.py, Attic/PopupMenu.py, Percolator.py, Outline.py, IOBinding.py, History.py, Attic/HelpWindow.py, FrameViewer.py, FileList.py, EditorWindow.py, Delegator.py, ColorDelegator.py, Bindings.py, AutoIndent.py, AutoExpand.py: + Initial checking of Tk-based Python IDE. + Features: text editor with syntax coloring and undo; + subclassed into interactive Python shell which adds history. + diff --git a/lib/python2.7/idlelib/ClassBrowser.py b/lib/python2.7/idlelib/ClassBrowser.py new file mode 100644 index 0000000..d09c52f --- /dev/null +++ b/lib/python2.7/idlelib/ClassBrowser.py @@ -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) diff --git a/lib/python2.7/idlelib/CodeContext.py b/lib/python2.7/idlelib/CodeContext.py new file mode 100644 index 0000000..bb0cc9c --- /dev/null +++ b/lib/python2.7/idlelib/CodeContext.py @@ -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) diff --git a/lib/python2.7/idlelib/ColorDelegator.py b/lib/python2.7/idlelib/ColorDelegator.py new file mode 100644 index 0000000..fec2670 --- /dev/null +++ b/lib/python2.7/idlelib/ColorDelegator.py @@ -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) diff --git a/lib/python2.7/idlelib/Debugger.py b/lib/python2.7/idlelib/Debugger.py new file mode 100644 index 0000000..c517065 --- /dev/null +++ b/lib/python2.7/idlelib/Debugger.py @@ -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() diff --git a/lib/python2.7/idlelib/Delegator.py b/lib/python2.7/idlelib/Delegator.py new file mode 100644 index 0000000..c476516 --- /dev/null +++ b/lib/python2.7/idlelib/Delegator.py @@ -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 diff --git a/lib/python2.7/idlelib/EditorWindow.py b/lib/python2.7/idlelib/EditorWindow.py new file mode 100644 index 0000000..8a33719 --- /dev/null +++ b/lib/python2.7/idlelib/EditorWindow.py @@ -0,0 +1,1704 @@ +import sys +import os +import platform +import re +import imp +from Tkinter import * +import tkSimpleDialog +import tkMessageBox +import webbrowser + +from idlelib.MultiCall import MultiCallCreator +from idlelib import WindowList +from idlelib import SearchDialog +from idlelib import GrepDialog +from idlelib import ReplaceDialog +from idlelib import PyParse +from idlelib.configHandler import idleConf +from idlelib import aboutDialog, textView, configDialog +from idlelib import macosxSupport +from idlelib import help + +# The default tab setting for a Text widget, in average-width characters. +TK_TABWIDTH_DEFAULT = 8 + +_py_version = ' (%s)' % platform.python_version() + +def _sphinx_version(): + "Format sys.version_info to produce the Sphinx version string used to install the chm docs" + major, minor, micro, level, serial = sys.version_info + release = '%s%s' % (major, minor) + if micro: + release += '%s' % (micro,) + if level == 'candidate': + release += 'rc%s' % (serial,) + elif level != 'final': + release += '%s%s' % (level[0], serial) + return release + +def _find_module(fullname, path=None): + """Version of imp.find_module() that handles hierarchical module names""" + + file = None + for tgt in fullname.split('.'): + if file is not None: + file.close() # close intermediate files + (file, filename, descr) = imp.find_module(tgt, path) + if descr[2] == imp.PY_SOURCE: + break # find but not load the source file + module = imp.load_module(tgt, file, filename, descr) + try: + path = module.__path__ + except AttributeError: + raise ImportError, 'No source for module ' + module.__name__ + if descr[2] != imp.PY_SOURCE: + # If all of the above fails and didn't raise an exception,fallback + # to a straight import which can find __init__.py in a package. + m = __import__(fullname) + try: + filename = m.__file__ + except AttributeError: + pass + else: + file = None + base, ext = os.path.splitext(filename) + if ext == '.pyc': + ext = '.py' + filename = base + ext + descr = filename, None, imp.PY_SOURCE + return file, filename, descr + + +class HelpDialog(object): + + def __init__(self): + self.parent = None # parent of help window + self.dlg = None # the help window iteself + + def display(self, parent, near=None): + """ Display the help dialog. + + parent - parent widget for the help window + + near - a Toplevel widget (e.g. EditorWindow or PyShell) + to use as a reference for placing the help window + """ + import warnings as w + w.warn("EditorWindow.HelpDialog is no longer used by Idle.\n" + "It will be removed in 3.6 or later.\n" + "It has been replaced by private help.HelpWindow\n", + DeprecationWarning, stacklevel=2) + if self.dlg is None: + self.show_dialog(parent) + if near: + self.nearwindow(near) + + def show_dialog(self, parent): + self.parent = parent + fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt') + self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False) + dlg.bind('<Destroy>', self.destroy, '+') + + def nearwindow(self, near): + # Place the help dialog near the window specified by parent. + # Note - this may not reposition the window in Metacity + # if "/apps/metacity/general/disable_workarounds" is enabled + dlg = self.dlg + geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10) + dlg.withdraw() + dlg.geometry("=+%d+%d" % geom) + dlg.deiconify() + dlg.lift() + + def destroy(self, ev=None): + self.dlg = None + self.parent = None + +helpDialog = HelpDialog() # singleton instance, no longer used + + +class EditorWindow(object): + from idlelib.Percolator import Percolator + from idlelib.ColorDelegator import ColorDelegator + from idlelib.UndoDelegator import UndoDelegator + from idlelib.IOBinding import IOBinding, filesystemencoding, encoding + from idlelib import Bindings + from Tkinter import Toplevel + from idlelib.MultiStatusBar import MultiStatusBar + + help_url = None + + def __init__(self, flist=None, filename=None, key=None, root=None): + if EditorWindow.help_url is None: + dochome = os.path.join(sys.prefix, 'Doc', 'index.html') + if sys.platform.count('linux'): + # look for html docs in a couple of standard places + pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3] + if os.path.isdir('/var/www/html/python/'): # "python2" rpm + dochome = '/var/www/html/python/index.html' + else: + basepath = '/usr/share/doc/' # standard location + dochome = os.path.join(basepath, pyver, + 'Doc', 'index.html') + elif sys.platform[:3] == 'win': + chmfile = os.path.join(sys.prefix, 'Doc', + 'Python%s.chm' % _sphinx_version()) + if os.path.isfile(chmfile): + dochome = chmfile + elif sys.platform == 'darwin': + # documentation may be stored inside a python framework + dochome = os.path.join(sys.prefix, + 'Resources/English.lproj/Documentation/index.html') + dochome = os.path.normpath(dochome) + if os.path.isfile(dochome): + EditorWindow.help_url = dochome + if sys.platform == 'darwin': + # Safari requires real file:-URLs + EditorWindow.help_url = 'file://' + EditorWindow.help_url + else: + EditorWindow.help_url = "https://docs.python.org/%d.%d/" % sys.version_info[:2] + self.flist = flist + root = root or flist.root + self.root = root + try: + sys.ps1 + except AttributeError: + sys.ps1 = '>>> ' + self.menubar = Menu(root) + self.top = top = WindowList.ListedToplevel(root, menu=self.menubar) + if flist: + self.tkinter_vars = flist.vars + #self.top.instance_dict makes flist.inversedict available to + #configDialog.py so it can access all EditorWindow instances + self.top.instance_dict = flist.inversedict + else: + self.tkinter_vars = {} # keys: Tkinter event names + # values: Tkinter variable instances + self.top.instance_dict = {} + self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(), + 'recent-files.lst') + self.text_frame = text_frame = Frame(top) + self.vbar = vbar = Scrollbar(text_frame, name='vbar') + self.width = idleConf.GetOption('main','EditorWindow','width', type='int') + text_options = { + 'name': 'text', + 'padx': 5, + 'wrap': 'none', + 'highlightthickness': 0, + 'width': self.width, + 'height': idleConf.GetOption('main', 'EditorWindow', 'height', type='int')} + if TkVersion >= 8.5: + # Starting with tk 8.5 we have to set the new tabstyle option + # to 'wordprocessor' to achieve the same display of tabs as in + # older tk versions. + text_options['tabstyle'] = 'wordprocessor' + self.text = text = MultiCallCreator(Text)(text_frame, **text_options) + self.top.focused_widget = self.text + + self.createmenubar() + self.apply_bindings() + + self.top.protocol("WM_DELETE_WINDOW", self.close) + self.top.bind("<<close-window>>", self.close_event) + if macosxSupport.isAquaTk(): + # Command-W on editorwindows doesn't work without this. + text.bind('<<close-window>>', self.close_event) + # Some OS X systems have only one mouse button, so use + # control-click for popup context menus there. For two + # buttons, AquaTk defines <2> as the right button, not <3>. + text.bind("<Control-Button-1>",self.right_menu_event) + text.bind("<2>", self.right_menu_event) + else: + # Elsewhere, use right-click for popup menus. + text.bind("<3>",self.right_menu_event) + text.bind("<<cut>>", self.cut) + text.bind("<<copy>>", self.copy) + text.bind("<<paste>>", self.paste) + text.bind("<<center-insert>>", self.center_insert_event) + text.bind("<<help>>", self.help_dialog) + text.bind("<<python-docs>>", self.python_docs) + text.bind("<<about-idle>>", self.about_dialog) + text.bind("<<open-config-dialog>>", self.config_dialog) + text.bind("<<open-module>>", self.open_module) + text.bind("<<do-nothing>>", lambda event: "break") + text.bind("<<select-all>>", self.select_all) + text.bind("<<remove-selection>>", self.remove_selection) + text.bind("<<find>>", self.find_event) + text.bind("<<find-again>>", self.find_again_event) + text.bind("<<find-in-files>>", self.find_in_files_event) + text.bind("<<find-selection>>", self.find_selection_event) + text.bind("<<replace>>", self.replace_event) + text.bind("<<goto-line>>", self.goto_line_event) + text.bind("<<smart-backspace>>",self.smart_backspace_event) + text.bind("<<newline-and-indent>>",self.newline_and_indent_event) + text.bind("<<smart-indent>>",self.smart_indent_event) + text.bind("<<indent-region>>",self.indent_region_event) + text.bind("<<dedent-region>>",self.dedent_region_event) + text.bind("<<comment-region>>",self.comment_region_event) + text.bind("<<uncomment-region>>",self.uncomment_region_event) + text.bind("<<tabify-region>>",self.tabify_region_event) + text.bind("<<untabify-region>>",self.untabify_region_event) + text.bind("<<toggle-tabs>>",self.toggle_tabs_event) + text.bind("<<change-indentwidth>>",self.change_indentwidth_event) + text.bind("<Left>", self.move_at_edge_if_selection(0)) + text.bind("<Right>", self.move_at_edge_if_selection(1)) + text.bind("<<del-word-left>>", self.del_word_left) + text.bind("<<del-word-right>>", self.del_word_right) + text.bind("<<beginning-of-line>>", self.home_callback) + + if flist: + flist.inversedict[self] = key + if key: + flist.dict[key] = self + text.bind("<<open-new-window>>", self.new_callback) + text.bind("<<close-all-windows>>", self.flist.close_all_callback) + text.bind("<<open-class-browser>>", self.open_class_browser) + text.bind("<<open-path-browser>>", self.open_path_browser) + + self.set_status_bar() + vbar['command'] = text.yview + vbar.pack(side=RIGHT, fill=Y) + text['yscrollcommand'] = vbar.set + text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow') + text_frame.pack(side=LEFT, fill=BOTH, expand=1) + text.pack(side=TOP, fill=BOTH, expand=1) + text.focus_set() + + # usetabs true -> literal tab characters are used by indent and + # dedent cmds, possibly mixed with spaces if + # indentwidth is not a multiple of tabwidth, + # which will cause Tabnanny to nag! + # false -> tab characters are converted to spaces by indent + # and dedent cmds, and ditto TAB keystrokes + # Although use-spaces=0 can be configured manually in config-main.def, + # configuration of tabs v. spaces is not supported in the configuration + # dialog. IDLE promotes the preferred Python indentation: use spaces! + usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool') + self.usetabs = not usespaces + + # tabwidth is the display width of a literal tab character. + # CAUTION: telling Tk to use anything other than its default + # tab setting causes it to use an entirely different tabbing algorithm, + # treating tab stops as fixed distances from the left margin. + # Nobody expects this, so for now tabwidth should never be changed. + self.tabwidth = 8 # must remain 8 until Tk is fixed. + + # indentwidth is the number of screen characters per indent level. + # The recommended Python indentation is four spaces. + self.indentwidth = self.tabwidth + self.set_notabs_indentwidth() + + # If context_use_ps1 is true, parsing searches back for a ps1 line; + # else searches for a popular (if, def, ...) Python stmt. + self.context_use_ps1 = False + + # When searching backwards for a reliable place to begin parsing, + # first start num_context_lines[0] lines back, then + # num_context_lines[1] lines back if that didn't work, and so on. + # The last value should be huge (larger than the # of lines in a + # conceivable file). + # Making the initial values larger slows things down more often. + self.num_context_lines = 50, 500, 5000000 + + self.per = per = self.Percolator(text) + + self.undo = undo = self.UndoDelegator() + per.insertfilter(undo) + text.undo_block_start = undo.undo_block_start + text.undo_block_stop = undo.undo_block_stop + undo.set_saved_change_hook(self.saved_change_hook) + + # IOBinding implements file I/O and printing functionality + self.io = io = self.IOBinding(self) + io.set_filename_change_hook(self.filename_change_hook) + + # Create the recent files submenu + self.recent_files_menu = Menu(self.menubar, tearoff=0) + self.menudict['file'].insert_cascade(3, label='Recent Files', + underline=0, + menu=self.recent_files_menu) + self.update_recent_files_list() + + self.color = None # initialized below in self.ResetColorizer + if filename: + if os.path.exists(filename) and not os.path.isdir(filename): + io.loadfile(filename) + else: + io.set_filename(filename) + self.ResetColorizer() + self.saved_change_hook() + + self.set_indentation_params(self.ispythonsource(filename)) + + self.load_extensions() + + menu = self.menudict.get('windows') + if menu: + end = menu.index("end") + if end is None: + end = -1 + if end >= 0: + menu.add_separator() + end = end + 1 + self.wmenu_end = end + WindowList.register_callback(self.postwindowsmenu) + + # Some abstractions so IDLE extensions are cross-IDE + self.askyesno = tkMessageBox.askyesno + self.askinteger = tkSimpleDialog.askinteger + self.showerror = tkMessageBox.showerror + + def _filename_to_unicode(self, filename): + """convert filename to unicode in order to display it in Tk""" + if isinstance(filename, unicode) or not filename: + return filename + else: + try: + return filename.decode(self.filesystemencoding) + except UnicodeDecodeError: + # XXX + try: + return filename.decode(self.encoding) + except UnicodeDecodeError: + # byte-to-byte conversion + return filename.decode('iso8859-1') + + def new_callback(self, event): + dirname, basename = self.io.defaultfilename() + self.flist.new(dirname) + return "break" + + def home_callback(self, event): + if (event.state & 4) != 0 and event.keysym == "Home": + # state&4==Control. If <Control-Home>, use the Tk binding. + return + if self.text.index("iomark") and \ + self.text.compare("iomark", "<=", "insert lineend") and \ + self.text.compare("insert linestart", "<=", "iomark"): + # In Shell on input line, go to just after prompt + insertpt = int(self.text.index("iomark").split(".")[1]) + else: + line = self.text.get("insert linestart", "insert lineend") + for insertpt in xrange(len(line)): + if line[insertpt] not in (' ','\t'): + break + else: + insertpt=len(line) + lineat = int(self.text.index("insert").split('.')[1]) + if insertpt == lineat: + insertpt = 0 + dest = "insert linestart+"+str(insertpt)+"c" + if (event.state&1) == 0: + # shift was not pressed + self.text.tag_remove("sel", "1.0", "end") + else: + if not self.text.index("sel.first"): + self.text.mark_set("my_anchor", "insert") # there was no previous selection + else: + if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")): + self.text.mark_set("my_anchor", "sel.first") # extend back + else: + self.text.mark_set("my_anchor", "sel.last") # extend forward + first = self.text.index(dest) + last = self.text.index("my_anchor") + if self.text.compare(first,">",last): + first,last = last,first + self.text.tag_remove("sel", "1.0", "end") + self.text.tag_add("sel", first, last) + self.text.mark_set("insert", dest) + self.text.see("insert") + return "break" + + def set_status_bar(self): + self.status_bar = self.MultiStatusBar(self.top) + sep = Frame(self.top, height=1, borderwidth=1, background='grey75') + if sys.platform == "darwin": + # Insert some padding to avoid obscuring some of the statusbar + # by the resize widget. + self.status_bar.set_label('_padding1', ' ', side=RIGHT) + self.status_bar.set_label('column', 'Col: ?', side=RIGHT) + self.status_bar.set_label('line', 'Ln: ?', side=RIGHT) + self.status_bar.pack(side=BOTTOM, fill=X) + sep.pack(side=BOTTOM, fill=X) + self.text.bind("<<set-line-and-column>>", self.set_line_and_column) + self.text.event_add("<<set-line-and-column>>", + "<KeyRelease>", "<ButtonRelease>") + self.text.after_idle(self.set_line_and_column) + + def set_line_and_column(self, event=None): + line, column = self.text.index(INSERT).split('.') + self.status_bar.set_label('column', 'Col: %s' % column) + self.status_bar.set_label('line', 'Ln: %s' % line) + + menu_specs = [ + ("file", "_File"), + ("edit", "_Edit"), + ("format", "F_ormat"), + ("run", "_Run"), + ("options", "_Options"), + ("windows", "_Window"), + ("help", "_Help"), + ] + + + def createmenubar(self): + mbar = self.menubar + self.menudict = menudict = {} + for name, label in self.menu_specs: + underline, label = prepstr(label) + menudict[name] = menu = Menu(mbar, name=name, tearoff=0) + mbar.add_cascade(label=label, menu=menu, underline=underline) + + if macosxSupport.isCarbonTk(): + # Insert the application menu + menudict['application'] = menu = Menu(mbar, name='apple', + tearoff=0) + mbar.add_cascade(label='IDLE', menu=menu) + + self.fill_menus() + self.base_helpmenu_length = self.menudict['help'].index(END) + self.reset_help_menu_entries() + + def postwindowsmenu(self): + # Only called when Windows menu exists + menu = self.menudict['windows'] + end = menu.index("end") + if end is None: + end = -1 + if end > self.wmenu_end: + menu.delete(self.wmenu_end+1, end) + WindowList.add_windows_to_menu(menu) + + rmenu = None + + def right_menu_event(self, event): + self.text.mark_set("insert", "@%d,%d" % (event.x, event.y)) + if not self.rmenu: + self.make_rmenu() + rmenu = self.rmenu + self.event = event + iswin = sys.platform[:3] == 'win' + if iswin: + self.text.config(cursor="arrow") + + for item in self.rmenu_specs: + try: + label, eventname, verify_state = item + except ValueError: # see issue1207589 + continue + + if verify_state is None: + continue + state = getattr(self, verify_state)() + rmenu.entryconfigure(label, state=state) + + rmenu.tk_popup(event.x_root, event.y_root) + if iswin: + self.text.config(cursor="ibeam") + + rmenu_specs = [ + # ("Label", "<<virtual-event>>", "statefuncname"), ... + ("Close", "<<close-window>>", None), # Example + ] + + def make_rmenu(self): + rmenu = Menu(self.text, tearoff=0) + for item in self.rmenu_specs: + label, eventname = item[0], item[1] + if label is not None: + def command(text=self.text, eventname=eventname): + text.event_generate(eventname) + rmenu.add_command(label=label, command=command) + else: + rmenu.add_separator() + self.rmenu = rmenu + + def rmenu_check_cut(self): + return self.rmenu_check_copy() + + def rmenu_check_copy(self): + try: + indx = self.text.index('sel.first') + except TclError: + return 'disabled' + else: + return 'normal' if indx else 'disabled' + + def rmenu_check_paste(self): + try: + self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD') + except TclError: + return 'disabled' + else: + return 'normal' + + def about_dialog(self, event=None): + "Handle Help 'About IDLE' event." + # Synchronize with macosxSupport.overrideRootMenu.about_dialog. + aboutDialog.AboutDialog(self.top,'About IDLE') + + def config_dialog(self, event=None): + "Handle Options 'Configure IDLE' event." + # Synchronize with macosxSupport.overrideRootMenu.config_dialog. + configDialog.ConfigDialog(self.top,'Settings') + + def help_dialog(self, event=None): + "Handle Help 'IDLE Help' event." + # Synchronize with macosxSupport.overrideRootMenu.help_dialog. + if self.root: + parent = self.root + else: + parent = self.top + help.show_idlehelp(parent) + + def python_docs(self, event=None): + if sys.platform[:3] == 'win': + try: + os.startfile(self.help_url) + except WindowsError as why: + tkMessageBox.showerror(title='Document Start Failure', + message=str(why), parent=self.text) + else: + webbrowser.open(self.help_url) + return "break" + + def cut(self,event): + self.text.event_generate("<<Cut>>") + return "break" + + def copy(self,event): + if not self.text.tag_ranges("sel"): + # There is no selection, so do nothing and maybe interrupt. + return + self.text.event_generate("<<Copy>>") + return "break" + + def paste(self,event): + self.text.event_generate("<<Paste>>") + self.text.see("insert") + return "break" + + def select_all(self, event=None): + self.text.tag_add("sel", "1.0", "end-1c") + self.text.mark_set("insert", "1.0") + self.text.see("insert") + return "break" + + def remove_selection(self, event=None): + self.text.tag_remove("sel", "1.0", "end") + self.text.see("insert") + + def move_at_edge_if_selection(self, edge_index): + """Cursor move begins at start or end of selection + + When a left/right cursor key is pressed create and return to Tkinter a + function which causes a cursor move from the associated edge of the + selection. + + """ + self_text_index = self.text.index + self_text_mark_set = self.text.mark_set + edges_table = ("sel.first+1c", "sel.last-1c") + def move_at_edge(event): + if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed + try: + self_text_index("sel.first") + self_text_mark_set("insert", edges_table[edge_index]) + except TclError: + pass + return move_at_edge + + def del_word_left(self, event): + self.text.event_generate('<Meta-Delete>') + return "break" + + def del_word_right(self, event): + self.text.event_generate('<Meta-d>') + return "break" + + def find_event(self, event): + SearchDialog.find(self.text) + return "break" + + def find_again_event(self, event): + SearchDialog.find_again(self.text) + return "break" + + def find_selection_event(self, event): + SearchDialog.find_selection(self.text) + return "break" + + def find_in_files_event(self, event): + GrepDialog.grep(self.text, self.io, self.flist) + return "break" + + def replace_event(self, event): + ReplaceDialog.replace(self.text) + return "break" + + def goto_line_event(self, event): + text = self.text + lineno = tkSimpleDialog.askinteger("Goto", + "Go to line number:",parent=text) + if lineno is None: + return "break" + if lineno <= 0: + text.bell() + return "break" + text.mark_set("insert", "%d.0" % lineno) + text.see("insert") + + def open_module(self, event=None): + # XXX Shouldn't this be in IOBinding or in FileList? + try: + name = self.text.get("sel.first", "sel.last") + except TclError: + name = "" + else: + name = name.strip() + name = tkSimpleDialog.askstring("Module", + "Enter the name of a Python module\n" + "to search on sys.path and open:", + parent=self.text, initialvalue=name) + if name: + name = name.strip() + if not name: + return + # XXX Ought to insert current file's directory in front of path + try: + (f, file_path, (suffix, mode, mtype)) = _find_module(name) + except (NameError, ImportError) as msg: + tkMessageBox.showerror("Import error", str(msg), parent=self.text) + return + if mtype != imp.PY_SOURCE: + tkMessageBox.showerror("Unsupported type", + "%s is not a source module" % name, parent=self.text) + return + if f: + f.close() + if self.flist: + self.flist.open(file_path) + else: + self.io.loadfile(file_path) + return file_path + + def open_class_browser(self, event=None): + filename = self.io.filename + if not (self.__class__.__name__ == 'PyShellEditorWindow' + and filename): + filename = self.open_module() + if filename is None: + return + head, tail = os.path.split(filename) + base, ext = os.path.splitext(tail) + from idlelib import ClassBrowser + ClassBrowser.ClassBrowser(self.flist, base, [head]) + + def open_path_browser(self, event=None): + from idlelib import PathBrowser + PathBrowser.PathBrowser(self.flist) + + def gotoline(self, lineno): + if lineno is not None and lineno > 0: + self.text.mark_set("insert", "%d.0" % lineno) + self.text.tag_remove("sel", "1.0", "end") + self.text.tag_add("sel", "insert", "insert +1l") + self.center() + + def ispythonsource(self, filename): + if not filename or os.path.isdir(filename): + return True + base, ext = os.path.splitext(os.path.basename(filename)) + if os.path.normcase(ext) in (".py", ".pyw"): + return True + try: + f = open(filename) + line = f.readline() + f.close() + except IOError: + return False + return line.startswith('#!') and line.find('python') >= 0 + + def close_hook(self): + if self.flist: + self.flist.unregister_maybe_terminate(self) + self.flist = None + + def set_close_hook(self, close_hook): + self.close_hook = close_hook + + def filename_change_hook(self): + if self.flist: + self.flist.filename_changed_edit(self) + self.saved_change_hook() + self.top.update_windowlist_registry(self) + self.ResetColorizer() + + def _addcolorizer(self): + if self.color: + return + if self.ispythonsource(self.io.filename): + self.color = self.ColorDelegator() + # can add more colorizers here... + if self.color: + self.per.removefilter(self.undo) + self.per.insertfilter(self.color) + self.per.insertfilter(self.undo) + + def _rmcolorizer(self): + if not self.color: + return + self.color.removecolors() + self.per.removefilter(self.color) + self.color = None + + def ResetColorizer(self): + "Update the color theme" + # Called from self.filename_change_hook and from configDialog.py + self._rmcolorizer() + self._addcolorizer() + theme = idleConf.CurrentTheme() + normal_colors = idleConf.GetHighlight(theme, 'normal') + cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg') + select_colors = idleConf.GetHighlight(theme, 'hilite') + self.text.config( + foreground=normal_colors['foreground'], + background=normal_colors['background'], + insertbackground=cursor_color, + selectforeground=select_colors['foreground'], + selectbackground=select_colors['background'], + ) + if TkVersion >= 8.5: + self.text.config( + inactiveselectbackground=select_colors['background']) + + def ResetFont(self): + "Update the text widgets' font if it is changed" + # Called from configDialog.py + + self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow') + + def RemoveKeybindings(self): + "Remove the keybindings before they are changed." + # Called from configDialog.py + self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet() + for event, keylist in keydefs.items(): + self.text.event_delete(event, *keylist) + for extensionName in self.get_standard_extension_names(): + xkeydefs = idleConf.GetExtensionBindings(extensionName) + if xkeydefs: + for event, keylist in xkeydefs.items(): + self.text.event_delete(event, *keylist) + + def ApplyKeybindings(self): + "Update the keybindings after they are changed" + # Called from configDialog.py + self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet() + self.apply_bindings() + for extensionName in self.get_standard_extension_names(): + xkeydefs = idleConf.GetExtensionBindings(extensionName) + if xkeydefs: + self.apply_bindings(xkeydefs) + #update menu accelerators + menuEventDict = {} + for menu in self.Bindings.menudefs: + menuEventDict[menu[0]] = {} + for item in menu[1]: + if item: + menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1] + for menubarItem in self.menudict.keys(): + menu = self.menudict[menubarItem] + end = menu.index(END) + if end is None: + # Skip empty menus + continue + end += 1 + for index in range(0, end): + if menu.type(index) == 'command': + accel = menu.entrycget(index, 'accelerator') + if accel: + itemName = menu.entrycget(index, 'label') + event = '' + if menubarItem in menuEventDict: + if itemName in menuEventDict[menubarItem]: + event = menuEventDict[menubarItem][itemName] + if event: + accel = get_accelerator(keydefs, event) + menu.entryconfig(index, accelerator=accel) + + def set_notabs_indentwidth(self): + "Update the indentwidth if changed and not using tabs in this window" + # Called from configDialog.py + if not self.usetabs: + self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces', + type='int') + + def reset_help_menu_entries(self): + "Update the additional help entries on the Help menu" + help_list = idleConf.GetAllExtraHelpSourcesList() + helpmenu = self.menudict['help'] + # first delete the extra help entries, if any + helpmenu_length = helpmenu.index(END) + if helpmenu_length > self.base_helpmenu_length: + helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length) + # then rebuild them + if help_list: + helpmenu.add_separator() + for entry in help_list: + cmd = self.__extra_help_callback(entry[1]) + helpmenu.add_command(label=entry[0], command=cmd) + # and update the menu dictionary + self.menudict['help'] = helpmenu + + def __extra_help_callback(self, helpfile): + "Create a callback with the helpfile value frozen at definition time" + def display_extra_help(helpfile=helpfile): + if not helpfile.startswith(('www', 'http')): + helpfile = os.path.normpath(helpfile) + if sys.platform[:3] == 'win': + try: + os.startfile(helpfile) + except WindowsError as why: + tkMessageBox.showerror(title='Document Start Failure', + message=str(why), parent=self.text) + else: + webbrowser.open(helpfile) + return display_extra_help + + def update_recent_files_list(self, new_file=None): + "Load and update the recent files list and menus" + rf_list = [] + if os.path.exists(self.recent_files_path): + with open(self.recent_files_path, 'r') as rf_list_file: + rf_list = rf_list_file.readlines() + if new_file: + new_file = os.path.abspath(new_file) + '\n' + if new_file in rf_list: + rf_list.remove(new_file) # move to top + rf_list.insert(0, new_file) + # clean and save the recent files list + bad_paths = [] + for path in rf_list: + if '\0' in path or not os.path.exists(path[0:-1]): + bad_paths.append(path) + rf_list = [path for path in rf_list if path not in bad_paths] + ulchars = "1234567890ABCDEFGHIJK" + rf_list = rf_list[0:len(ulchars)] + try: + with open(self.recent_files_path, 'w') as rf_file: + rf_file.writelines(rf_list) + except IOError as err: + if not getattr(self.root, "recentfilelist_error_displayed", False): + self.root.recentfilelist_error_displayed = True + tkMessageBox.showwarning(title='IDLE Warning', + message="Cannot update File menu Recent Files list. " + "Your operating system says:\n%s\n" + "Select OK and IDLE will continue without updating." + % str(err), + parent=self.text) + # for each edit window instance, construct the recent files menu + for instance in self.top.instance_dict.keys(): + menu = instance.recent_files_menu + menu.delete(0, END) # clear, and rebuild: + for i, file_name in enumerate(rf_list): + file_name = file_name.rstrip() # zap \n + # make unicode string to display non-ASCII chars correctly + ufile_name = self._filename_to_unicode(file_name) + callback = instance.__recent_file_callback(file_name) + menu.add_command(label=ulchars[i] + " " + ufile_name, + command=callback, + underline=0) + + def __recent_file_callback(self, file_name): + def open_recent_file(fn_closure=file_name): + self.io.open(editFile=fn_closure) + return open_recent_file + + def saved_change_hook(self): + short = self.short_title() + long = self.long_title() + if short and long: + title = short + " - " + long + _py_version + elif short: + title = short + elif long: + title = long + else: + title = "Untitled" + icon = short or long or title + if not self.get_saved(): + title = "*%s*" % title + icon = "*%s" % icon + self.top.wm_title(title) + self.top.wm_iconname(icon) + + def get_saved(self): + return self.undo.get_saved() + + def set_saved(self, flag): + self.undo.set_saved(flag) + + def reset_undo(self): + self.undo.reset_undo() + + def short_title(self): + filename = self.io.filename + if filename: + filename = os.path.basename(filename) + else: + filename = "Untitled" + # return unicode string to display non-ASCII chars correctly + return self._filename_to_unicode(filename) + + def long_title(self): + # return unicode string to display non-ASCII chars correctly + return self._filename_to_unicode(self.io.filename or "") + + def center_insert_event(self, event): + self.center() + + def center(self, mark="insert"): + text = self.text + top, bot = self.getwindowlines() + lineno = self.getlineno(mark) + height = bot - top + newtop = max(1, lineno - height//2) + text.yview(float(newtop)) + + def getwindowlines(self): + text = self.text + top = self.getlineno("@0,0") + bot = self.getlineno("@0,65535") + if top == bot and text.winfo_height() == 1: + # Geometry manager hasn't run yet + height = int(text['height']) + bot = top + height - 1 + return top, bot + + def getlineno(self, mark="insert"): + text = self.text + return int(float(text.index(mark))) + + def get_geometry(self): + "Return (width, height, x, y)" + geom = self.top.wm_geometry() + m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom) + tuple = (map(int, m.groups())) + return tuple + + def close_event(self, event): + self.close() + + def maybesave(self): + if self.io: + if not self.get_saved(): + if self.top.state()!='normal': + self.top.deiconify() + self.top.lower() + self.top.lift() + return self.io.maybesave() + + def close(self): + reply = self.maybesave() + if str(reply) != "cancel": + self._close() + return reply + + def _close(self): + if self.io.filename: + self.update_recent_files_list(new_file=self.io.filename) + WindowList.unregister_callback(self.postwindowsmenu) + self.unload_extensions() + self.io.close() + self.io = None + self.undo = None + if self.color: + self.color.close(False) + self.color = None + self.text = None + self.tkinter_vars = None + self.per.close() + self.per = None + self.top.destroy() + if self.close_hook: + # unless override: unregister from flist, terminate if last window + self.close_hook() + + def load_extensions(self): + self.extensions = {} + self.load_standard_extensions() + + def unload_extensions(self): + for ins in self.extensions.values(): + if hasattr(ins, "close"): + ins.close() + self.extensions = {} + + def load_standard_extensions(self): + for name in self.get_standard_extension_names(): + try: + self.load_extension(name) + except: + print "Failed to load extension", repr(name) + import traceback + traceback.print_exc() + + def get_standard_extension_names(self): + return idleConf.GetExtensions(editor_only=True) + + def load_extension(self, name): + try: + mod = __import__(name, globals(), locals(), []) + except ImportError: + print "\nFailed to import extension: ", name + return + cls = getattr(mod, name) + keydefs = idleConf.GetExtensionBindings(name) + if hasattr(cls, "menudefs"): + self.fill_menus(cls.menudefs, keydefs) + ins = cls(self) + self.extensions[name] = ins + if keydefs: + self.apply_bindings(keydefs) + for vevent in keydefs.keys(): + methodname = vevent.replace("-", "_") + while methodname[:1] == '<': + methodname = methodname[1:] + while methodname[-1:] == '>': + methodname = methodname[:-1] + methodname = methodname + "_event" + if hasattr(ins, methodname): + self.text.bind(vevent, getattr(ins, methodname)) + + def apply_bindings(self, keydefs=None): + if keydefs is None: + keydefs = self.Bindings.default_keydefs + text = self.text + text.keydefs = keydefs + for event, keylist in keydefs.items(): + if keylist: + text.event_add(event, *keylist) + + def fill_menus(self, menudefs=None, keydefs=None): + """Add appropriate entries to the menus and submenus + + Menus that are absent or None in self.menudict are ignored. + """ + if menudefs is None: + menudefs = self.Bindings.menudefs + if keydefs is None: + keydefs = self.Bindings.default_keydefs + menudict = self.menudict + text = self.text + for mname, entrylist in menudefs: + menu = menudict.get(mname) + if not menu: + continue + for entry in entrylist: + if not entry: + menu.add_separator() + else: + label, eventname = entry + checkbutton = (label[:1] == '!') + if checkbutton: + label = label[1:] + underline, label = prepstr(label) + accelerator = get_accelerator(keydefs, eventname) + def command(text=text, eventname=eventname): + text.event_generate(eventname) + if checkbutton: + var = self.get_var_obj(eventname, BooleanVar) + menu.add_checkbutton(label=label, underline=underline, + command=command, accelerator=accelerator, + variable=var) + else: + menu.add_command(label=label, underline=underline, + command=command, + accelerator=accelerator) + + def getvar(self, name): + var = self.get_var_obj(name) + if var: + value = var.get() + return value + else: + raise NameError, name + + def setvar(self, name, value, vartype=None): + var = self.get_var_obj(name, vartype) + if var: + var.set(value) + else: + raise NameError, name + + def get_var_obj(self, name, vartype=None): + var = self.tkinter_vars.get(name) + if not var and vartype: + # create a Tkinter variable object with self.text as master: + self.tkinter_vars[name] = var = vartype(self.text) + return var + + # Tk implementations of "virtual text methods" -- each platform + # reusing IDLE's support code needs to define these for its GUI's + # flavor of widget. + + # Is character at text_index in a Python string? Return 0 for + # "guaranteed no", true for anything else. This info is expensive + # to compute ab initio, but is probably already known by the + # platform's colorizer. + + def is_char_in_string(self, text_index): + if self.color: + # Return true iff colorizer hasn't (re)gotten this far + # yet, or the character is tagged as being in a string + return self.text.tag_prevrange("TODO", text_index) or \ + "STRING" in self.text.tag_names(text_index) + else: + # The colorizer is missing: assume the worst + return 1 + + # If a selection is defined in the text widget, return (start, + # end) as Tkinter text indices, otherwise return (None, None) + def get_selection_indices(self): + try: + first = self.text.index("sel.first") + last = self.text.index("sel.last") + return first, last + except TclError: + return None, None + + # Return the text widget's current view of what a tab stop means + # (equivalent width in spaces). + + def get_tabwidth(self): + current = self.text['tabs'] or TK_TABWIDTH_DEFAULT + return int(current) + + # Set the text widget's current view of what a tab stop means. + + def set_tabwidth(self, newtabwidth): + text = self.text + if self.get_tabwidth() != newtabwidth: + pixels = text.tk.call("font", "measure", text["font"], + "-displayof", text.master, + "n" * newtabwidth) + text.configure(tabs=pixels) + + # If ispythonsource and guess are true, guess a good value for + # indentwidth based on file content (if possible), and if + # indentwidth != tabwidth set usetabs false. + # In any case, adjust the Text widget's view of what a tab + # character means. + + def set_indentation_params(self, ispythonsource, guess=True): + if guess and ispythonsource: + i = self.guess_indent() + if 2 <= i <= 8: + self.indentwidth = i + if self.indentwidth != self.tabwidth: + self.usetabs = False + self.set_tabwidth(self.tabwidth) + + def smart_backspace_event(self, event): + text = self.text + first, last = self.get_selection_indices() + if first and last: + text.delete(first, last) + text.mark_set("insert", first) + return "break" + # Delete whitespace left, until hitting a real char or closest + # preceding virtual tab stop. + chars = text.get("insert linestart", "insert") + if chars == '': + if text.compare("insert", ">", "1.0"): + # easy: delete preceding newline + text.delete("insert-1c") + else: + text.bell() # at start of buffer + return "break" + if chars[-1] not in " \t": + # easy: delete preceding real char + text.delete("insert-1c") + return "break" + # Ick. It may require *inserting* spaces if we back up over a + # tab character! This is written to be clear, not fast. + tabwidth = self.tabwidth + have = len(chars.expandtabs(tabwidth)) + assert have > 0 + want = ((have - 1) // self.indentwidth) * self.indentwidth + # Debug prompt is multilined.... + if self.context_use_ps1: + last_line_of_prompt = sys.ps1.split('\n')[-1] + else: + last_line_of_prompt = '' + ncharsdeleted = 0 + while 1: + if chars == last_line_of_prompt: + break + chars = chars[:-1] + ncharsdeleted = ncharsdeleted + 1 + have = len(chars.expandtabs(tabwidth)) + if have <= want or chars[-1] not in " \t": + break + text.undo_block_start() + text.delete("insert-%dc" % ncharsdeleted, "insert") + if have < want: + text.insert("insert", ' ' * (want - have)) + text.undo_block_stop() + return "break" + + def smart_indent_event(self, event): + # if intraline selection: + # delete it + # elif multiline selection: + # do indent-region + # else: + # indent one level + text = self.text + first, last = self.get_selection_indices() + text.undo_block_start() + try: + if first and last: + if index2line(first) != index2line(last): + return self.indent_region_event(event) + text.delete(first, last) + text.mark_set("insert", first) + prefix = text.get("insert linestart", "insert") + raw, effective = classifyws(prefix, self.tabwidth) + if raw == len(prefix): + # only whitespace to the left + self.reindent_to(effective + self.indentwidth) + else: + # tab to the next 'stop' within or to right of line's text: + if self.usetabs: + pad = '\t' + else: + effective = len(prefix.expandtabs(self.tabwidth)) + n = self.indentwidth + pad = ' ' * (n - effective % n) + text.insert("insert", pad) + text.see("insert") + return "break" + finally: + text.undo_block_stop() + + def newline_and_indent_event(self, event): + text = self.text + first, last = self.get_selection_indices() + text.undo_block_start() + try: + if first and last: + text.delete(first, last) + text.mark_set("insert", first) + line = text.get("insert linestart", "insert") + i, n = 0, len(line) + while i < n and line[i] in " \t": + i = i+1 + if i == n: + # the cursor is in or at leading indentation in a continuation + # line; just inject an empty line at the start + text.insert("insert linestart", '\n') + return "break" + indent = line[:i] + # strip whitespace before insert point unless it's in the prompt + i = 0 + last_line_of_prompt = sys.ps1.split('\n')[-1] + while line and line[-1] in " \t" and line != last_line_of_prompt: + line = line[:-1] + i = i+1 + if i: + text.delete("insert - %d chars" % i, "insert") + # strip whitespace after insert point + while text.get("insert") in " \t": + text.delete("insert") + # start new line + text.insert("insert", '\n') + + # adjust indentation for continuations and block + # open/close first need to find the last stmt + lno = index2line(text.index('insert')) + y = PyParse.Parser(self.indentwidth, self.tabwidth) + if not self.context_use_ps1: + for context in self.num_context_lines: + startat = max(lno - context, 1) + startatindex = repr(startat) + ".0" + rawtext = text.get(startatindex, "insert") + y.set_str(rawtext) + bod = y.find_good_parse_start( + self.context_use_ps1, + self._build_char_in_string_func(startatindex)) + if bod is not None or startat == 1: + break + y.set_lo(bod or 0) + else: + r = text.tag_prevrange("console", "insert") + if r: + startatindex = r[1] + else: + startatindex = "1.0" + rawtext = text.get(startatindex, "insert") + y.set_str(rawtext) + y.set_lo(0) + + c = y.get_continuation_type() + if c != PyParse.C_NONE: + # The current stmt hasn't ended yet. + if c == PyParse.C_STRING_FIRST_LINE: + # after the first line of a string; do not indent at all + pass + elif c == PyParse.C_STRING_NEXT_LINES: + # inside a string which started before this line; + # just mimic the current indent + text.insert("insert", indent) + elif c == PyParse.C_BRACKET: + # line up with the first (if any) element of the + # last open bracket structure; else indent one + # level beyond the indent of the line with the + # last open bracket + self.reindent_to(y.compute_bracket_indent()) + elif c == PyParse.C_BACKSLASH: + # if more than one line in this stmt already, just + # mimic the current indent; else if initial line + # has a start on an assignment stmt, indent to + # beyond leftmost =; else to beyond first chunk of + # non-whitespace on initial line + if y.get_num_lines_in_stmt() > 1: + text.insert("insert", indent) + else: + self.reindent_to(y.compute_backslash_indent()) + else: + assert 0, "bogus continuation type %r" % (c,) + return "break" + + # This line starts a brand new stmt; indent relative to + # indentation of initial line of closest preceding + # interesting stmt. + indent = y.get_base_indent_string() + text.insert("insert", indent) + if y.is_block_opener(): + self.smart_indent_event(event) + elif indent and y.is_block_closer(): + self.smart_backspace_event(event) + return "break" + finally: + text.see("insert") + text.undo_block_stop() + + # Our editwin provides an is_char_in_string function that works + # with a Tk text index, but PyParse only knows about offsets into + # a string. This builds a function for PyParse that accepts an + # offset. + + def _build_char_in_string_func(self, startindex): + def inner(offset, _startindex=startindex, + _icis=self.is_char_in_string): + return _icis(_startindex + "+%dc" % offset) + return inner + + def indent_region_event(self, event): + head, tail, chars, lines = self.get_region() + for pos in range(len(lines)): + line = lines[pos] + if line: + raw, effective = classifyws(line, self.tabwidth) + effective = effective + self.indentwidth + lines[pos] = self._make_blanks(effective) + line[raw:] + self.set_region(head, tail, chars, lines) + return "break" + + def dedent_region_event(self, event): + head, tail, chars, lines = self.get_region() + for pos in range(len(lines)): + line = lines[pos] + if line: + raw, effective = classifyws(line, self.tabwidth) + effective = max(effective - self.indentwidth, 0) + lines[pos] = self._make_blanks(effective) + line[raw:] + self.set_region(head, tail, chars, lines) + return "break" + + def comment_region_event(self, event): + head, tail, chars, lines = self.get_region() + for pos in range(len(lines) - 1): + line = lines[pos] + lines[pos] = '##' + line + self.set_region(head, tail, chars, lines) + + def uncomment_region_event(self, event): + head, tail, chars, lines = self.get_region() + for pos in range(len(lines)): + line = lines[pos] + if not line: + continue + if line[:2] == '##': + line = line[2:] + elif line[:1] == '#': + line = line[1:] + lines[pos] = line + self.set_region(head, tail, chars, lines) + + def tabify_region_event(self, event): + head, tail, chars, lines = self.get_region() + tabwidth = self._asktabwidth() + if tabwidth is None: return + for pos in range(len(lines)): + line = lines[pos] + if line: + raw, effective = classifyws(line, tabwidth) + ntabs, nspaces = divmod(effective, tabwidth) + lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:] + self.set_region(head, tail, chars, lines) + + def untabify_region_event(self, event): + head, tail, chars, lines = self.get_region() + tabwidth = self._asktabwidth() + if tabwidth is None: return + for pos in range(len(lines)): + lines[pos] = lines[pos].expandtabs(tabwidth) + self.set_region(head, tail, chars, lines) + + def toggle_tabs_event(self, event): + if self.askyesno( + "Toggle tabs", + "Turn tabs " + ("on", "off")[self.usetabs] + + "?\nIndent width " + + ("will be", "remains at")[self.usetabs] + " 8." + + "\n Note: a tab is always 8 columns", + parent=self.text): + self.usetabs = not self.usetabs + # Try to prevent inconsistent indentation. + # User must change indent width manually after using tabs. + self.indentwidth = 8 + return "break" + + # XXX this isn't bound to anything -- see tabwidth comments +## def change_tabwidth_event(self, event): +## new = self._asktabwidth() +## if new != self.tabwidth: +## self.tabwidth = new +## self.set_indentation_params(0, guess=0) +## return "break" + + def change_indentwidth_event(self, event): + new = self.askinteger( + "Indent width", + "New indent width (2-16)\n(Always use 8 when using tabs)", + parent=self.text, + initialvalue=self.indentwidth, + minvalue=2, + maxvalue=16) + if new and new != self.indentwidth and not self.usetabs: + self.indentwidth = new + return "break" + + def get_region(self): + text = self.text + first, last = self.get_selection_indices() + if first and last: + head = text.index(first + " linestart") + tail = text.index(last + "-1c lineend +1c") + else: + head = text.index("insert linestart") + tail = text.index("insert lineend +1c") + chars = text.get(head, tail) + lines = chars.split("\n") + return head, tail, chars, lines + + def set_region(self, head, tail, chars, lines): + text = self.text + newchars = "\n".join(lines) + if newchars == chars: + text.bell() + return + text.tag_remove("sel", "1.0", "end") + text.mark_set("insert", head) + text.undo_block_start() + text.delete(head, tail) + text.insert(head, newchars) + text.undo_block_stop() + text.tag_add("sel", head, "insert") + + # Make string that displays as n leading blanks. + + def _make_blanks(self, n): + if self.usetabs: + ntabs, nspaces = divmod(n, self.tabwidth) + return '\t' * ntabs + ' ' * nspaces + else: + return ' ' * n + + # Delete from beginning of line to insert point, then reinsert + # column logical (meaning use tabs if appropriate) spaces. + + def reindent_to(self, column): + text = self.text + text.undo_block_start() + if text.compare("insert linestart", "!=", "insert"): + text.delete("insert linestart", "insert") + if column: + text.insert("insert", self._make_blanks(column)) + text.undo_block_stop() + + def _asktabwidth(self): + return self.askinteger( + "Tab width", + "Columns per tab? (2-16)", + parent=self.text, + initialvalue=self.indentwidth, + minvalue=2, + maxvalue=16) + + # Guess indentwidth from text content. + # Return guessed indentwidth. This should not be believed unless + # it's in a reasonable range (e.g., it will be 0 if no indented + # blocks are found). + + def guess_indent(self): + opener, indented = IndentSearcher(self.text, self.tabwidth).run() + if opener and indented: + raw, indentsmall = classifyws(opener, self.tabwidth) + raw, indentlarge = classifyws(indented, self.tabwidth) + else: + indentsmall = indentlarge = 0 + return indentlarge - indentsmall + +# "line.col" -> line, as an int +def index2line(index): + return int(float(index)) + +# Look at the leading whitespace in s. +# Return pair (# of leading ws characters, +# effective # of leading blanks after expanding +# tabs to width tabwidth) + +def classifyws(s, tabwidth): + raw = effective = 0 + for ch in s: + if ch == ' ': + raw = raw + 1 + effective = effective + 1 + elif ch == '\t': + raw = raw + 1 + effective = (effective // tabwidth + 1) * tabwidth + else: + break + return raw, effective + +import tokenize +_tokenize = tokenize +del tokenize + +class IndentSearcher(object): + + # .run() chews over the Text widget, looking for a block opener + # and the stmt following it. Returns a pair, + # (line containing block opener, line containing stmt) + # Either or both may be None. + + def __init__(self, text, tabwidth): + self.text = text + self.tabwidth = tabwidth + self.i = self.finished = 0 + self.blkopenline = self.indentedline = None + + def readline(self): + if self.finished: + return "" + i = self.i = self.i + 1 + mark = repr(i) + ".0" + if self.text.compare(mark, ">=", "end"): + return "" + return self.text.get(mark, mark + " lineend+1c") + + def tokeneater(self, type, token, start, end, line, + INDENT=_tokenize.INDENT, + NAME=_tokenize.NAME, + OPENERS=('class', 'def', 'for', 'if', 'try', 'while')): + if self.finished: + pass + elif type == NAME and token in OPENERS: + self.blkopenline = line + elif type == INDENT and self.blkopenline: + self.indentedline = line + self.finished = 1 + + def run(self): + save_tabsize = _tokenize.tabsize + _tokenize.tabsize = self.tabwidth + try: + try: + _tokenize.tokenize(self.readline, self.tokeneater) + except (_tokenize.TokenError, SyntaxError): + # since we cut off the tokenizer early, we can trigger + # spurious errors + pass + finally: + _tokenize.tabsize = save_tabsize + return self.blkopenline, self.indentedline + +### end autoindent code ### + +def prepstr(s): + # Helper to extract the underscore from a string, e.g. + # prepstr("Co_py") returns (2, "Copy"). + i = s.find('_') + if i >= 0: + s = s[:i] + s[i+1:] + return i, s + + +keynames = { + 'bracketleft': '[', + 'bracketright': ']', + 'slash': '/', +} + +def get_accelerator(keydefs, eventname): + keylist = keydefs.get(eventname) + # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5 + # if not keylist: + if (not keylist) or (macosxSupport.isCocoaTk() and eventname in { + "<<open-module>>", + "<<goto-line>>", + "<<change-indentwidth>>"}): + return "" + s = keylist[0] + s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s) + s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s) + s = re.sub("Key-", "", s) + s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu + s = re.sub("Control-", "Ctrl-", s) + s = re.sub("-", "+", s) + s = re.sub("><", " ", s) + s = re.sub("<", "", s) + s = re.sub(">", "", s) + return s + + +def fixwordbreaks(root): + # Make sure that Tk's double-click and next/previous word + # operations use our definition of a word (i.e. an identifier) + tk = root.tk + tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded + tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]') + tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]') + + +def _editor_window(parent): # htest # + # error if close master window first - timer event, after script + root = parent + fixwordbreaks(root) + if sys.argv[1:]: + filename = sys.argv[1] + else: + filename = None + macosxSupport.setupApp(root, None) + edit = EditorWindow(root=root, filename=filename) + edit.text.bind("<<close-all-windows>>", edit.close_event) + # Does not stop error, neither does following + # edit.text.bind("<<close-window>>", edit.close_event) + + +if __name__ == '__main__': + from idlelib.idle_test.htest import run + run(_editor_window) diff --git a/lib/python2.7/idlelib/FileList.py b/lib/python2.7/idlelib/FileList.py new file mode 100644 index 0000000..8318ff1 --- /dev/null +++ b/lib/python2.7/idlelib/FileList.py @@ -0,0 +1,124 @@ +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 + import sys + root = Tk() + fixwordbreaks(root) + root.withdraw() + flist = FileList(root) + if sys.argv[1:]: + for filename in sys.argv[1:]: + flist.open(filename) + else: + flist.new() + if flist.inversedict: + root.mainloop() + +if __name__ == '__main__': + _test() diff --git a/lib/python2.7/idlelib/FormatParagraph.py b/lib/python2.7/idlelib/FormatParagraph.py new file mode 100644 index 0000000..7a9d185 --- /dev/null +++ b/lib/python2.7/idlelib/FormatParagraph.py @@ -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) diff --git a/lib/python2.7/idlelib/GrepDialog.py b/lib/python2.7/idlelib/GrepDialog.py new file mode 100644 index 0000000..d86d50d --- /dev/null +++ b/lib/python2.7/idlelib/GrepDialog.py @@ -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) diff --git a/lib/python2.7/idlelib/HISTORY.txt b/lib/python2.7/idlelib/HISTORY.txt new file mode 100644 index 0000000..01d73ed --- /dev/null +++ b/lib/python2.7/idlelib/HISTORY.txt @@ -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. + +====================================================================== diff --git a/lib/python2.7/idlelib/HyperParser.py b/lib/python2.7/idlelib/HyperParser.py new file mode 100644 index 0000000..5816d00 --- /dev/null +++ b/lib/python2.7/idlelib/HyperParser.py @@ -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) diff --git a/lib/python2.7/idlelib/IOBinding.py b/lib/python2.7/idlelib/IOBinding.py new file mode 100644 index 0000000..2aba46e --- /dev/null +++ b/lib/python2.7/idlelib/IOBinding.py @@ -0,0 +1,610 @@ +# 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) + 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) diff --git a/lib/python2.7/idlelib/Icons/folder.gif b/lib/python2.7/idlelib/Icons/folder.gif Binary files differnew file mode 100644 index 0000000..effe8dc --- /dev/null +++ b/lib/python2.7/idlelib/Icons/folder.gif diff --git a/lib/python2.7/idlelib/Icons/idle.icns b/lib/python2.7/idlelib/Icons/idle.icns Binary files differnew file mode 100644 index 0000000..f65e313 --- /dev/null +++ b/lib/python2.7/idlelib/Icons/idle.icns diff --git a/lib/python2.7/idlelib/Icons/idle.ico b/lib/python2.7/idlelib/Icons/idle.ico Binary files differnew file mode 100644 index 0000000..3357aef --- /dev/null +++ b/lib/python2.7/idlelib/Icons/idle.ico diff --git a/lib/python2.7/idlelib/Icons/idle_16.gif b/lib/python2.7/idlelib/Icons/idle_16.gif Binary files differnew file mode 100644 index 0000000..9f001b1 --- /dev/null +++ b/lib/python2.7/idlelib/Icons/idle_16.gif diff --git a/lib/python2.7/idlelib/Icons/idle_16.png b/lib/python2.7/idlelib/Icons/idle_16.png Binary files differnew file mode 100644 index 0000000..6abde0a --- /dev/null +++ b/lib/python2.7/idlelib/Icons/idle_16.png diff --git a/lib/python2.7/idlelib/Icons/idle_32.gif b/lib/python2.7/idlelib/Icons/idle_32.gif Binary files differnew file mode 100644 index 0000000..af5b2d5 --- /dev/null +++ b/lib/python2.7/idlelib/Icons/idle_32.gif diff --git a/lib/python2.7/idlelib/Icons/idle_32.png b/lib/python2.7/idlelib/Icons/idle_32.png Binary files differnew file mode 100644 index 0000000..41b70db --- /dev/null +++ b/lib/python2.7/idlelib/Icons/idle_32.png diff --git a/lib/python2.7/idlelib/Icons/idle_48.gif b/lib/python2.7/idlelib/Icons/idle_48.gif Binary files differnew file mode 100644 index 0000000..fc5304f --- /dev/null +++ b/lib/python2.7/idlelib/Icons/idle_48.gif diff --git a/lib/python2.7/idlelib/Icons/idle_48.png b/lib/python2.7/idlelib/Icons/idle_48.png Binary files differnew file mode 100644 index 0000000..e5fa928 --- /dev/null +++ b/lib/python2.7/idlelib/Icons/idle_48.png diff --git a/lib/python2.7/idlelib/Icons/minusnode.gif b/lib/python2.7/idlelib/Icons/minusnode.gif Binary files differnew file mode 100644 index 0000000..c72e46f --- /dev/null +++ b/lib/python2.7/idlelib/Icons/minusnode.gif diff --git a/lib/python2.7/idlelib/Icons/openfolder.gif b/lib/python2.7/idlelib/Icons/openfolder.gif Binary files differnew file mode 100644 index 0000000..24aea1b --- /dev/null +++ b/lib/python2.7/idlelib/Icons/openfolder.gif diff --git a/lib/python2.7/idlelib/Icons/plusnode.gif b/lib/python2.7/idlelib/Icons/plusnode.gif Binary files differnew file mode 100644 index 0000000..13ace90 --- /dev/null +++ b/lib/python2.7/idlelib/Icons/plusnode.gif diff --git a/lib/python2.7/idlelib/Icons/python.gif b/lib/python2.7/idlelib/Icons/python.gif Binary files differnew file mode 100644 index 0000000..b189c2c --- /dev/null +++ b/lib/python2.7/idlelib/Icons/python.gif diff --git a/lib/python2.7/idlelib/Icons/tk.gif b/lib/python2.7/idlelib/Icons/tk.gif Binary files differnew file mode 100644 index 0000000..a603f5e --- /dev/null +++ b/lib/python2.7/idlelib/Icons/tk.gif diff --git a/lib/python2.7/idlelib/IdleHistory.py b/lib/python2.7/idlelib/IdleHistory.py new file mode 100644 index 0000000..078af29 --- /dev/null +++ b/lib/python2.7/idlelib/IdleHistory.py @@ -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) diff --git a/lib/python2.7/idlelib/MultiCall.py b/lib/python2.7/idlelib/MultiCall.py new file mode 100644 index 0000000..a157d7a --- /dev/null +++ b/lib/python2.7/idlelib/MultiCall.py @@ -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) diff --git a/lib/python2.7/idlelib/MultiStatusBar.py b/lib/python2.7/idlelib/MultiStatusBar.py new file mode 100644 index 0000000..e3d59ee --- /dev/null +++ b/lib/python2.7/idlelib/MultiStatusBar.py @@ -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) diff --git a/lib/python2.7/idlelib/NEWS.txt b/lib/python2.7/idlelib/NEWS.txt new file mode 100644 index 0000000..35e2a7e --- /dev/null +++ b/lib/python2.7/idlelib/NEWS.txt @@ -0,0 +1,1165 @@ +What's New in IDLE 2.7.13? +========================== +*Release date: 2017-01-01?* + +- Issue #27854: Make Help => IDLE Help work again on Windows. + Include idlelib/help.html in 2.7 Windows installer. + +- Issue #25507: Add back import needed for 2.x encoding warning box. + Add pointer to 'Encoding declaration' in Language Reference. + +- Issue #15308: Add 'interrupt execution' (^C) to Shell menu. + Patch by Roger Serwy, updated by Bayard Randel. + +- Issue #27922: Stop IDLE tests from 'flashing' gui widgets on the screen. + +- Issue #17642: add larger font sizes for classroom projection. + +- Add version to title of IDLE help window. + +- Issue #25564: In section on IDLE -- console differences, mention that + using exec means that __builtins__ is defined for each statement. + +- Issue #27714: text_textview and test_autocomplete now pass when re-run + in the same process. This occurs when test_idle fails when run with the + -w option but without -jn. Fix warning from test_config. + +- Issue #27452: add line counter and crc to IDLE configHandler test dump. + +- Issue #27365: Allow non-ascii chars in IDLE NEWS.txt, for contributor names. + +- Issue #27245: IDLE: Cleanly delete custom themes and key bindings. + Previously, when IDLE was started from a console or by import, a cascade + of warnings was emitted. Patch by Serhiy Storchaka. + + +What's New in IDLE 2.7.12? +========================== +*Release date: 2015-06-25* + +- Issue #5124: Paste with text selected now replaces the selection on X11. + This matches how paste works on Windows, Mac, most modern Linux apps, + and ttk widgets. Original patch by Serhiy Storchaka. + +- Issue #24759: Make clear in idlelib.idle_test.__init__ that the directory + is a private implementation of test.test_idle and tool for maintainers. + +- Issue #26673: When tk reports font size as 0, change to size 10. + Such fonts on Linux prevented the configuration dialog from opening. + +- Issue #27044: Add ConfigDialog.remove_var_callbacks to stop memory leaks. + +- In the 'IDLE-console differences' section of the IDLE doc, clarify + how running with IDLE affects sys.modules and the standard streams. + +- Issue #25507: fix incorrect change in IOBinding that prevented printing. + Change also prevented saving shell window with non-ascii characters. + Augment IOBinding htest to include all major IOBinding functions. + +- Issue #25905: Revert unwanted conversion of ' to ’ RIGHT SINGLE QUOTATION + MARK in README.txt and open this and NEWS.txt with 'ascii'. + Re-encode CREDITS.txt to utf-8 and open it with 'utf-8'. + +- Issue #26417: Prevent spurious errors and incorrect defaults when + installing IDLE 2.7 on OS X: default configuration settings are + no longer installed from OS X specific copies. + + +What's New in IDLE 2.7.11? +========================== +*Release date: 2015-12-06* + +- Issue 15348: Stop the debugger engine (normally in a user process) + before closing the debugger window (running in the IDLE process). + This prevents the RuntimeErrors that were being caught and ignored. + +- Issue #24455: Prevent IDLE from hanging when a) closing the shell while the + debugger is active (15347); b) closing the debugger with the [X] button + (15348); and c) activating the debugger when already active (24455). + The patch by Mark Roseman does this by making two changes. + 1. Suspend and resume the gui.interaction method with the tcl vwait + mechanism intended for this purpose (instead of root.mainloop & .quit). + 2. In gui.run, allow any existing interaction to terminate first. + +- Change 'The program' to 'Your program' in an IDLE 'kill program?' message + to make it clearer that the program referred to is the currently running + user program, not IDLE itself. + +- Issue #24750: Improve the appearance of the IDLE editor window status bar. + Patch by Mark Roseman. + +- Issue #25313: Change the handling of new built-in text color themes to better + address the compatibility problem introduced by the addition of IDLE Dark. + Consistently use the revised idleConf.CurrentTheme everywhere in idlelib. + +- Issue #24782: Extension configuration is now a tab in the IDLE Preferences + dialog rather than a separate dialog. The former tabs are now a sorted + list. Patch by Mark Roseman. + +- Issue #22726: Re-activate the config dialog help button with some content + about the other buttons and the new IDLE Dark theme. + +- Issue #24820: IDLE now has an 'IDLE Dark' built-in text color theme. + It is more or less IDLE Classic inverted, with a cobalt blue background. + Strings, comments, keywords, ... are still green, red, orange, ... . + To use it with IDLEs released before November 2015, hit the + 'Save as New Custom Theme' button and enter a new name, + such as 'Custom Dark'. The custom theme will work with any IDLE + release, and can be modified. + +- Issue #25224: README.txt is now an idlelib index for IDLE developers and + curious users. The previous user content is now in the IDLE doc chapter. + 'IDLE' now means 'Integrated Development and Learning Environment'. + +- Issue #24820: Users can now set breakpoint colors in + Settings -> Custom Highlighting. Original patch by Mark Roseman. + +- Issue #24972: Inactive selection background now matches active selection + background, as configured by users, on all systems. Found items are now + always highlighted on Windows. Initial patch by Mark Roseman. + +- Issue #24570: Idle: make calltip and completion boxes appear on Macs + affected by a tk regression. Initial patch by Mark Roseman. + +- Issue #24988: Idle ScrolledList context menus (used in debugger) + now work on Mac Aqua. Patch by Mark Roseman. + +- Issue #24801: Make right-click for context menu work on Mac Aqua. + Patch by Mark Roseman. + +- Issue #25173: Associate tkinter messageboxes with a specific widget. + For Mac OSX, make them a 'sheet'. Patch by Mark Roseman. + +- Issue #25198: Enhance the initial html viewer now used for Idle Help. + * Properly indent fixed-pitch text (patch by Mark Roseman). + * Give code snippet a very Sphinx-like light blueish-gray background. + * Re-use initial width and height set by users for shell and editor. + * When the Table of Contents (TOC) menu is used, put the section header + at the top of the screen. + +- Issue #25225: Condense and rewrite Idle doc section on text colors. + +- Issue #21995: Explain some differences between IDLE and console Python. + +- Issue #22820: Explain need for *print* when running file from Idle editor. + +- Issue #25224: Doc: augment Idle feature list and no-subprocess section. + +- Issue #25219: Update doc for Idle command line options. + Some were missing and notes were not correct. + +- Issue #24861: Most of idlelib is private and subject to change. + Use idleib.idle.* to start Idle. See idlelib.__init__.__doc__. + +- Issue #25199: Idle: add synchronization comments for future maintainers. + +- Issue #16893: Replace help.txt with help.html for Idle doc display. + The new idlelib/help.html is rstripped Doc/build/html/library/idle.html. + It looks better than help.txt and will better document Idle as released. + The tkinter html viewer that works for this file was written by Mark Roseman. + The now unused EditorWindow.HelpDialog class and helt.txt file are deprecated. + +- Issue #24199: Deprecate unused idlelib.idlever with possible removal in 3.6. + +- Issue #24790: Remove extraneous code (which also create 2 & 3 conflicts). + +- Issue #23672: Allow Idle to edit and run files with astral chars in name. + Patch by Mohd Sanad Zaki Rizvi. + +- Issue 24745: Idle editor default font. Switch from Courier to + platform-sensitive TkFixedFont. This should not affect current customized + font selections. If there is a problem, edit $HOME/.idlerc/config-main.cfg + and remove 'fontxxx' entries from [Editor Window]. Patch by Mark Roseman. + +- Issue #21192: Idle editor. When a file is run, put its name in the restart bar. + Do not print false prompts. Original patch by Adnan Umer. + +- Issue #13884: Idle menus. Remove tearoff lines. Patch by Roger Serwy. + +- Issue #15809: IDLE shell now uses locale encoding instead of Latin1 for + decoding unicode literals. + + +What's New in IDLE 2.7.10? +========================= +*Release date: 2015-05-23* + +- Issue #23583: Fixed writing unicode to standard output stream in IDLE. + +- Issue #20577: Configuration of the max line length for the FormatParagraph + extension has been moved from the General tab of the Idle preferences dialog + to the FormatParagraph tab of the Config Extensions dialog. + Patch by Tal Einat. + +- Issue #16893: Update Idle doc chapter to match current Idle and add new + information. + +- Issue #23180: Rename IDLE "Windows" menu item to "Window". + Patch by Al Sweigart. + + +What's New in IDLE 2.7.9? +========================= +*Release date: 2014-12-10* + +- Issue #16893: Update Idle doc chapter to match current Idle and add new + information. + +- Issue #3068: Add Idle extension configuration dialog to Options menu. + Changes are written to HOME/.idlerc/config-extensions.cfg. + Original patch by Tal Einat. + +- Issue #16233: A module browser (File : Class Browser, Alt+C) requires an + editor window with a filename. When Class Browser is requested otherwise, + from a shell, output window, or 'Untitled' editor, Idle no longer displays + an error box. It now pops up an Open Module box (Alt+M). If a valid name + is entered and a module is opened, a corresponding browser is also opened. + +- Issue #4832: Save As to type Python files automatically adds .py to the + name you enter (even if your system does not display it). Some systems + automatically add .txt when type is Text files. + +- Issue #21986: Code objects are not normally pickled by the pickle module. + To match this, they are no longer pickled when running under Idle. + +- Issue #22221: IDLE now ignores the source encoding declaration on the second + line if the first line contains anything except a comment. + +- Issue #17390: Adjust Editor window title; remove 'Python', + move version to end. + +- Issue #14105: Idle debugger breakpoints no longer disappear + when inserting or deleting lines. + + +What's New in IDLE 2.7.8? +========================= +*Release date: 2014-06-29* + +- Issue #21940: Add unittest for WidgetRedirector. Initial patch by Saimadhav + Heblikar. + +- Issue #18592: Add unittest for SearchDialogBase. Patch by Phil Webster. + +- Issue #21694: Add unittest for ParenMatch. Patch by Saimadhav Heblikar. + +- Issue #21686: add unittest for HyperParser. Original patch by Saimadhav + Heblikar. + +- Issue #12387: Add missing upper(lower)case versions of default Windows key + bindings for Idle so Caps Lock does not disable them. Patch by Roger Serwy. + +- Issue #21695: Closing a Find-in-files output window while the search is + still in progress no longer closes Idle. + +- Issue #18910: Add unittest for textView. Patch by Phil Webster. + +- Issue #18292: Add unittest for AutoExpand. Patch by Saihadhav Heblikar. + +- Issue #18409: Add unittest for AutoComplete. Patch by Phil Webster. + + +What's New in IDLE 2.7.7? +========================= +*Release date: 2014-05-31* + +- Issue #18104: Add idlelib/idle_test/htest.py with a few sample tests to begin + consolidating and improving human-validated tests of Idle. Change other files + as needed to work with htest. Running the module as __main__ runs all tests. + +- Issue #21139: Change default paragraph width to 72, the PEP 8 recommendation. + +- Issue #21284: Paragraph reformat test passes after user changes reformat width. + +- Issue #20406: Use Python application icons for Idle window title bars. + Patch mostly by Serhiy Storchaka. + +- Issue #21029: Occurrences of "print" are now consistently colored as + being a keyword (the colorizer doesn't know if print functions are + enabled in the source). + +- Issue #17721: Remove non-functional configuration dialog help button until we + make it actually gives some help when clicked. Patch by Guilherme Simões. + +- Issue #17390: Add Python version to Idle editor window title bar. + Original patches by Edmond Burnett and Kent Johnson. + +- Issue #20058: sys.stdin.readline() in IDLE now always returns only one line. + +- Issue #19481: print() of unicode, str or bytearray subclass instance in IDLE + no more hangs. + +- Issue #18270: Prevent possible IDLE AttributeError on OS X when no initial + shell window is present. + +- Issue #17654: Ensure IDLE menus are customized properly on OS X for + non-framework builds and for all variants of Tk. + + +What's New in IDLE 2.7.6? +========================= +*Release date: 2013-11-10* + +- Issue #19426: Fixed the opening of Python source file with specified encoding. + +- Issue #18873: IDLE now detects Python source code encoding only in comment + lines. + +- Issue #18988: The "Tab" key now works when a word is already autocompleted. + +- Issue #18489: Add tests for SearchEngine. Original patch by Phil Webster. + +- Issue #18429: Format / Format Paragraph, now works when comment blocks + are selected. As with text blocks, this works best when the selection + only includes complete lines. + +- Issue #18226: Add docstrings and unittests for FormatParagraph.py. + Original patches by Todd Rovito and Phil Webster. + +- Issue #18279: Format - Strip trailing whitespace no longer marks a file as + changed when it has not been changed. This fix followed the addition of a + test file originally written by Phil Webster (the issue's main goal). + +- Issue #18539: Calltips now work for float default arguments. + +- Issue #7136: In the Idle File menu, "New Window" is renamed "New File". + Patch by Tal Einat, Roget Serwy, and Todd Rovito. + +- Issue #8515: Set __file__ when run file in IDLE. + Initial patch by Bruce Frederiksen. + +- Issue #5492: Avoid traceback when exiting IDLE caused by a race condition. + +- Issue #17511: Keep IDLE find dialog open after clicking "Find Next". + Original patch by Sarah K. + +- Issue #15392: Create a unittest framework for IDLE. + Preliminary patch by Rajagopalasarma Jayakrishnan + See Lib/idlelib/idle_test/README.txt for how to run Idle tests. + +- Issue #14146: Highlight source line while debugging on Windows. + +- Issue #17532: Always include Options menu for IDLE on OS X. + Patch by Guilherme Simões. + + +What's New in IDLE 2.7.5? +========================= +*Release date: 2013-05-12* + +- Issue #17838: Allow sys.stdin to be reassigned. + +- Issue #14735: Update IDLE docs to omit "Control-z on Windows". + +- Issue #17585: Fixed IDLE regression. Now closes when using exit() or quit(). + +- Issue #17657: Show full Tk version in IDLE's about dialog. + Patch by Todd Rovito. + +- Issue #17613: Prevent traceback when removing syntax colorizer in IDLE. + +- Issue #1207589: Backwards-compatibility patch for right-click menu in IDLE. + +- Issue #16887: IDLE now accepts Cancel in tabify/untabify dialog box. + +- Issue #14254: IDLE now handles readline correctly across shell restarts. + +- Issue #17614: IDLE no longer raises exception when quickly closing a file. + +- Issue #6698: IDLE now opens just an editor window when configured to do so. + +- Issue #8900: Using keyboard shortcuts in IDLE to open a file no longer + raises an exception. + +- Issue #6649: Fixed missing exit status in IDLE. Patch by Guilherme Polo. + +- Issue #17390: Display Python version on Idle title bar. + Initial patch by Edmond Burnett. + + +What's New in IDLE 2.7.4? +========================= +*Release date: 2013-04-06* + +- Issue #17625: In IDLE, close the replace dialog after it is used. + +- IDLE was displaying spurious SystemExit tracebacks when running scripts + that terminated by raising SystemExit (i.e. unittest and turtledemo). + +- Issue #9290: In IDLE the sys.std* streams now implement io.TextIOBase + interface and support all mandatory methods and properties. + +- Issue #16829: IDLE printing no longer fails if there are spaces or other + special characters in the file path. + +- Issue #16819: IDLE method completion now correctly works for unicode literals. + +- Issue #16504: IDLE now catches SyntaxErrors raised by tokenizer. Patch by + Roger Serwy. + +- Issue #1207589: Add Cut/Copy/Paste items to IDLE right click Context Menu + Patch by Todd Rovito. + +- Issue #13052: Fix IDLE crashing when replace string in Search/Replace dialog + ended with '\'. Patch by Roger Serwy. + +- Issue #9803: Don't close IDLE on saving if breakpoint is open. + Patch by Roger Serwy. + +- Issue #14958: Change IDLE systax highlighting to recognize all string and byte + literals currently supported in Python 2.7. + +- Issue #14962: Update text coloring in IDLE shell window after changing + options. Patch by Roger Serwy. + +- Issue #10997: Prevent a duplicate entry in IDLE's "Recent Files" menu. + +- Issue #12510: Attempting to get invalid tooltip no longer closes IDLE. + Original patch by Roger Serwy. + +- Issue #10365: File open dialog now works instead of crashing + even when parent window is closed. Patch by Roger Serwy. + +- Issue #14876: Use user-selected font for highlight configuration. + Patch by Roger Serwy. + +- Issue #14409: IDLE now properly executes commands in the Shell window + when it cannot read the normal config files on startup and + has to use the built-in default key bindings. + There was previously a bug in one of the defaults. + +- Issue #3573: IDLE hangs when passing invalid command line args + (directory(ies) instead of file(s)) (Patch by Guilherme Polo) + +- Issue #5219: Prevent event handler cascade in IDLE. + +- Issue #15318: Prevent writing to sys.stdin. + +- Issue #13532, #15319: Check that arguments to sys.stdout.write are strings. + +- Issue #10365: File open dialog now works instead of crashing even when + parent window is closed while dialog is open. + +- Issue #14018: Update checks for unstable system Tcl/Tk versions on OS X + to include versions shipped with OS X 10.7 and 10.8 in addition to 10.6. + +- Issue #15853: Prevent IDLE crash on OS X when opening Preferences menu + with certain versions of Tk 8.5. Initial patch by Kevin Walzer. + + +What's New in IDLE 2.7.3? +========================= +*Release date: 2012-04-09* + +- Issue #964437 Make IDLE help window non-modal. + Patch by Guilherme Polo and Roger Serwy. + +- Issue #13933: IDLE auto-complete did not work with some imported + module, like hashlib. (Patch by Roger Serwy) + +- Issue #13506: Add '' to path for IDLE Shell when started and restarted with Restart Shell. + Original patches by Marco Scataglini and Roger Serwy. + +- Issue #4625: If IDLE cannot write to its recent file or breakpoint + files, display a message popup and continue rather than crash. + (original patch by Roger Serwy) + +- Issue #8793: Prevent IDLE crash when given strings with invalid hex escape + sequences. + +- Issue #13296: Fix IDLE to clear compile __future__ flags on shell restart. + (Patch by Roger Serwy) + +- Issue #14409: IDLE now properly executes commands in the Shell window + when it cannot read the normal config files on startup and + has to use the built-in default key bindings. + There was previously a bug in one of the defaults. + +- Issue #3573: IDLE hangs when passing invalid command line args + (directory(ies) instead of file(s)). + + +What's New in IDLE 2.7.2? +========================= +*Release date: 2011-06-11* + +- Issue #11718: IDLE's open module dialog couldn't find the __init__.py + file in a package. + +- Issue #12590: IDLE editor window now always displays the first line + when opening a long file. With Tk 8.5, the first line was hidden. + +- Issue #11088: don't crash when using F5 to run a script in IDLE on MacOSX + with Tk 8.5. + +- Issue #10940: Workaround an IDLE hang on Mac OS X 10.6 when using the + menu accelerators for Open Module, Go to Line, and New Indent Width. + The accelerators still work but no longer appear in the menu items. + +- Issue #10907: Warn OS X 10.6 IDLE users to use ActiveState Tcl/Tk 8.5, rather + than the currently problematic Apple-supplied one, when running with the + 64-/32-bit installer variant. + +- Issue #11052: Correct IDLE menu accelerators on Mac OS X for Save + commands. + +- Issue #6075: IDLE on Mac OS X now works with both Carbon AquaTk and + Cocoa AquaTk. + +- Issue #10404: Use ctl-button-1 on OSX for the context menu in Idle. + +- Issue #10107: Warn about unsaved files in IDLE on OSX. + +- Issue #10406: Enable Rstrip IDLE extension on OSX (just like on other + platforms). + +- Issue #6378: Further adjust idle.bat to start associated Python + +- Issue #11896: Save on Close failed despite selecting "Yes" in dialog. + +- Issue #4676: <Home> toggle failing on Tk 8.5, causing IDLE exits and + strange selection behavior. Improve selection extension behaviour. + +- Issue #3851 <Home> toggle non-functional when NumLock set on Windows. + + +What's New in Python 2.7.1? +=========================== +*Release date: 2010-11-27* + +- Issue #6378: idle.bat now runs with the appropriate Python version rather than + the system default. Patch by Sridhar Ratnakumar. + + +What's New in IDLE 2.7? +======================= +*Release date: 2010-07-03* + +- Issue #5150: IDLE's format menu now has an option to strip trailing + whitespace. + +- Issue #5847: Remove -n switch on "Edit with IDLE" menu item. + +- idle.py modified and simplified to better support developing experimental + versions of IDLE which are not installed in the standard location. + +- Issue #5559: OutputWindow/PyShell right click menu "Go to file/line" + wasn't working with file paths containing spaces. + +- Issue #5783: Windows: Version string for the .chm help file changed, + file not being accessed Patch by Guilherme Polo/ + +- Issue #1529142: Allow multiple IDLE GUI/subprocess pairs to exist + simultaneously. Thanks to David Scherer for suggesting the use of an + ephemeral port for the GUI. Patch by Weeble. + +- Remove port spec from run.py and fix bug where subprocess fails to + extract port from command line when warnings are present. + +- Issue #5129: Tk 8.5 Text widget requires 'wordprocessor' tabstyle attr + to handle mixed space/tab properly. Patch by Guilherme Polo. + +- Issue #3549: On MacOS the preferences menu was not present + + +What's New in IDLE 2.6? +======================= +*Release date: 01-Oct-2008* + +- Issue #2665: On Windows, an IDLE installation upgraded from an old version + would not start if a custom theme was defined. + +- Home / Control-A toggles between left margin and end of leading white + space. Patch 1196903 Jeff Shute. + +- Improved AutoCompleteWindow logic. Patch 2062 Tal Einat. + +- Autocompletion of filenames now support alternate separators, e.g. the + '/' char on Windows. Patch 2061 Tal Einat. + +- Configured selection highlighting colors were ignored; updating highlighting + in the config dialog would cause non-Python files to be colored as if they + were Python source; improve use of ColorDelagator. Patch 1334. Tal Einat. + +- ScriptBinding event handlers weren't returning 'break'. Patch 2050, Tal Einat. + +- There was an error on exit if no sys.exitfunc was defined. Issue 1647. + +- Could not open files in .idlerc directory if latter was hidden on Windows. + Issue 1743, Issue 1862. + +- Configure Dialog: improved layout for keybinding. Patch 1457 Tal Einat. + +- tabpage.py updated: tabbedPages.py now supports multiple dynamic rows + of tabs. Patch 1612746 Tal Einat. + +- Add confirmation dialog before printing. Patch 1717170 Tal Einat. + +- Show paste position if > 80 col. Patch 1659326 Tal Einat. + +- Update cursor color without restarting. Patch 1725576 Tal Einat. + +- Allow keyboard interrupt only when user code is executing in subprocess. + Patch 1225 Tal Einat (reworked from IDLE-Spoon). + +- configDialog cleanup. Patch 1730217 Tal Einat. + +- textView cleanup. Patch 1718043 Tal Einat. + +- Clean up EditorWindow close. + +- Patch 1693258: Fix for duplicate "preferences" menu-OS X. Backport of r56204. + +- OSX: Avoid crash for those versions of Tcl/Tk which don't have a console + +- Bug in idlelib.MultiCall: Options dialog was crashing IDLE if there was an + option in config-extensions w/o a value. Patch #1672481, Tal Einat + +- Corrected some bugs in AutoComplete. Also, Page Up/Down in ACW implemented; + mouse and cursor selection in ACWindow implemented; double Tab inserts + current selection and closes ACW (similar to double-click and Return); scroll + wheel now works in ACW. Added AutoComplete instructions to IDLE Help. + +- AutoCompleteWindow moved below input line, will move above if there + isn't enough space. Patch 1621265 Tal Einat + +- Calltips now 'handle' tuples in the argument list (display '<tuple>' :) + Suggested solution by Christos Georgiou, Bug 791968. + +- Add 'raw' support to configHandler. Patch 1650174 Tal Einat. + +- Avoid hang when encountering a duplicate in a completion list. Bug 1571112. + +- Patch #1362975: Rework CodeContext indentation algorithm to + avoid hard-coding pixel widths. + +- Bug #813342: Start the IDLE subprocess with -Qnew if the parent + is started with that option. + +- Honor the "Cancel" action in the save dialog (Debian bug #299092) + +- Some syntax errors were being caught by tokenize during the tabnanny + check, resulting in obscure error messages. Do the syntax check + first. Bug 1562716, 1562719 + +- IDLE's version number takes a big jump to match the version number of + the Python release of which it's a part. + + +What's New in IDLE 1.2? +======================= +*Release date: 19-SEP-2006* + +- File menu hotkeys: there were three 'p' assignments. Reassign the + 'Save Copy As' and 'Print' hotkeys to 'y' and 't'. Change the + Shell hotkey from 's' to 'l'. + +- IDLE honors new quit() and exit() commands from site.py Quitter() object. + Patch 1540892, Jim Jewett + +- The 'with' statement is now a Code Context block opener. + Patch 1540851, Jim Jewett + +- Retrieval of previous shell command was not always preserving indentation + (since 1.2a1) Patch 1528468 Tal Einat. + +- Changing tokenize (39046) to detect dedent broke tabnanny check (since 1.2a1) + +- ToggleTab dialog was setting indent to 8 even if cancelled (since 1.2a1). + +- When used w/o subprocess, all exceptions were preceded by an error + message claiming they were IDLE internal errors (since 1.2a1). + +- Bug #1525817: Don't truncate short lines in IDLE's tool tips. + +- Bug #1517990: IDLE keybindings on MacOS X now work correctly + +- Bug #1517996: IDLE now longer shows the default Tk menu when a + path browser, class browser or debugger is the frontmost window on MacOS X + +- EditorWindow.test() was failing. Bug 1417598 + +- EditorWindow failed when used stand-alone if sys.ps1 not set. + Bug 1010370 Dave Florek + +- Tooltips failed on new-syle class __init__ args. Bug 1027566 Loren Guthrie + +- Avoid occasional failure to detect closing paren properly. + Patch 1407280 Tal Einat + +- Rebinding Tab key was inserting 'tab' instead of 'Tab'. Bug 1179168. + +- Colorizer now handles #<builtin> correctly, also unicode strings and + 'as' keyword in comment directly following import command. Closes 1325071. + Patch 1479219 Tal Einat + +- Patch #1162825: Support non-ASCII characters in IDLE window titles. + +- Source file f.flush() after writing; trying to avoid lossage if user + kills GUI. + +- Options / Keys / Advanced dialog made functional. Also, allow binding + of 'movement' keys. + +- 'syntax' patch adds improved calltips and a new class attribute listbox. + MultiCall module allows binding multiple actions to an event. + Patch 906702 Noam Raphael + +- Better indentation after first line of string continuation. + IDLEfork Patch 681992, Noam Raphael + +- Fixed CodeContext alignment problem, following suggestion from Tal Einat. + +- Increased performance in CodeContext extension Patch 936169 Noam Raphael + +- Mac line endings were incorrect when pasting code from some browsers + when using X11 and the Fink distribution. Python Bug 1263656. + +- <Enter> when cursor is on a previous command retrieves that command. Instead + of replacing the input line, the previous command is now appended to the + input line. Indentation is preserved, and undo is enabled. + Patch 1196917 Jeff Shute + +- Clarify "tab/space" Error Dialog and "Tab Width" Dialog associated with + the Untabify command. + +- Corrected "tab/space" Error Dialog to show correct menu for Untabify. + Patch 1196980 Jeff Shute + +- New files are colorized by default, and colorizing is removed when + saving as non-Python files. Patch 1196895 Jeff Shute + Closes Python Bugs 775012 and 800432, partial fix IDLEfork 763524 + +- Improve subprocess link error notification. + +- run.py: use Queue's blocking feature instead of sleeping in the main + loop. Patch # 1190163 Michiel de Hoon + +- Add config-main option to make the 'history' feature non-cyclic. + Default remains cyclic. Python Patch 914546 Noam Raphael. + +- Removed ability to configure tabs indent from Options dialog. This 'feature' + has never worked and no one has complained. It is still possible to set a + default tabs (v. spaces) indent 'manually' via config-main.def (or to turn on + tabs for the current EditorWindow via the Format menu) but IDLE will + encourage indentation via spaces. + +- Enable setting the indentation width using the Options dialog. + Bug # 783877 + +- Add keybindings for del-word-left and del-word-right. + +- Discourage using an indent width other than 8 when using tabs to indent + Python code. + +- Restore use of EditorWindow.set_indentation_params(), was dead code since + Autoindent was merged into EditorWindow. This allows IDLE to conform to the + indentation width of a loaded file. (But it still will not switch to tabs + even if the file uses tabs.) Any change in indent width is local to that + window. + +- Add Tabnanny check before Run/F5, not just when Checking module. + +- If an extension can't be loaded, print warning and skip it instead of + erroring out. + +- Improve error handling when .idlerc can't be created (warn and exit). + +- The GUI was hanging if the shell window was closed while a raw_input() + was pending. Restored the quit() of the readline() mainloop(). + http://mail.python.org/pipermail/idle-dev/2004-December/002307.html + +- The remote procedure call module rpc.py can now access data attributes of + remote registered objects. Changes to these attributes are local, however. + + +What's New in IDLE 1.1? +======================= +*Release date: 30-NOV-2004* + +- On OpenBSD, terminating IDLE with ctrl-c from the command line caused a + stuck subprocess MainThread because only the SocketThread was exiting. + +- Saving a Keyset w/o making changes (by using the "Save as New Custom Key Set" + button) caused IDLE to fail on restart (no new keyset was created in + config-keys.cfg). Also true for Theme/highlights. Python Bug 1064535. + +- A change to the linecache.py API caused IDLE to exit when an exception was + raised while running without the subprocess (-n switch). Python Bug 1063840. + +- When paragraph reformat width was made configurable, a bug was + introduced that caused reformatting of comment blocks to ignore how + far the block was indented, effectively adding the indentation width + to the reformat width. This has been repaired, and the reformat + width is again a bound on the total width of reformatted lines. + +- Improve keyboard focus binding, especially in Windows menu. Improve + window raising, especially in the Windows menu and in the debugger. + IDLEfork 763524. + +- If user passes a non-existent filename on the commandline, just + open a new file, don't raise a dialog. IDLEfork 854928. + +- EditorWindow.py was not finding the .chm help file on Windows. Typo + at Rev 1.54. Python Bug 990954 + +- checking sys.platform for substring 'win' was breaking IDLE docs on Mac + (darwin). Also, Mac Safari browser requires full file:// URIs. SF 900580. + +- Redirect the warning stream to the shell during the ScriptBinding check of + user code and format the warning similarly to an exception for both that + check and for runtime warnings raised in the subprocess. + +- CodeContext hint pane visibility state is now persistent across sessions. + The pane no longer appears in the shell window. Added capability to limit + extensions to shell window or editor windows. Noam Raphael addition + to Patch 936169. + +- Paragraph reformat width is now a configurable parameter in the + Options GUI. + +- New Extension: CodeContext. Provides block structuring hints for code + which has scrolled above an edit window. Patch 936169 Noam Raphael. + +- If nulls somehow got into the strings in recent-files.lst + EditorWindow.update_recent_files_list() was failing. Python Bug 931336. + +- If the normal background is changed via Configure/Highlighting, it will + update immediately, thanks to the previously mentioned patch by Nigel Rowe. + +- Add a highlight theme for builtin keywords. Python Patch 805830 Nigel Rowe + This also fixed IDLEfork bug [ 693418 ] Normal text background color not + refreshed and Python bug [897872 ] Unknown color name on HP-UX + +- rpc.py:SocketIO - Large modules were generating large pickles when downloaded + to the execution server. The return of the OK response from the subprocess + initialization was interfering and causing the sending socket to be not + ready. Add an IO ready test to fix this. Moved the polling IO ready test + into pollpacket(). + +- Fix typo in rpc.py, s/b "pickle.PicklingError" not "pickle.UnpicklingError". + +- Added a Tk error dialog to run.py inform the user if the subprocess can't + connect to the user GUI process. Added a timeout to the GUI's listening + socket. Added Tk error dialogs to PyShell.py to announce a failure to bind + the port or connect to the subprocess. Clean up error handling during + connection initiation phase. This is an update of Python Patch 778323. + +- Print correct exception even if source file changed since shell was + restarted. IDLEfork Patch 869012 Noam Raphael + +- Keybindings with the Shift modifier now work correctly. So do bindings which + use the Space key. Limit unmodified user keybindings to the function keys. + Python Bug 775353, IDLEfork Bugs 755647, 761557 + +- After an exception, run.py was not setting the exception vector. Noam + Raphael suggested correcting this so pdb's postmortem pm() would work. + IDLEfork Patch 844675 + +- IDLE now does not fail to save the file anymore if the Tk buffer is not a + Unicode string, yet eol_convention is. Python Bugs 774680, 788378 + +- IDLE didn't start correctly when Python was installed in "Program Files" on + W2K and XP. Python Bugs 780451, 784183 + +- config-main.def documentation incorrectly referred to idle- instead of + config- filenames. SF 782759 Also added note about .idlerc location. + + +What's New in IDLE 1.0? +======================= +*Release date: 29-Jul-2003* + +- Calltip error when docstring was None Python Bug 775541 + +- Updated extend.txt, help.txt, and config-extensions.def to correctly + reflect the current status of the configuration system. Python Bug 768469 + +- Fixed: Call Tip Trimming May Loop Forever. Python Patch 769142 (Daniels) + +- Replaced apply(f, args, kwds) with f(*args, **kwargs) to improve performance + Python Patch 768187 + +- Break or continue statements outside a loop were causing IDLE crash + Python Bug 767794 + +- Convert Unicode strings from readline to IOBinding.encoding. Also set + sys.std{in|out|err}.encoding, for both the local and the subprocess case. + SF IDLEfork patch 682347. + +- Extend AboutDialog.ViewFile() to support file encodings. Make the CREDITS + file Latin-1. + +- Updated the About dialog to reflect re-integration into Python. Provide + buttons to display Python's NEWS, License, and Credits, plus additional + buttons for IDLE's README and NEWS. + +- TextViewer() now has a third parameter which allows inserting text into the + viewer instead of reading from a file. + +- (Created the .../Lib/idlelib directory in the Python CVS, which is a clone of + IDLEfork modified to install in the Python environment. The code in the + interrupt module has been moved to thread.interrupt_main(). ) + +- Printing the Shell window was failing if it was not saved first SF 748975 + +- When using the Search in Files dialog, if the user had a selection + highlighted in his Editor window, insert it into the dialog search field. + +- The Python Shell entry was disappearing from the Windows menu. + +- Update the Windows file list when a file name change occurs + +- Change to File / Open Module: always pop up the dialog, using the current + selection as the default value. This is easier to use habitually. + +- Avoided a problem with starting the subprocess when 'localhost' doesn't + resolve to the user's loopback interface. SF 747772 + +- Fixed an issue with highlighted errors never de-colorizing. SF 747677. Also + improved notification of Tabnanny Token Error. + +- File / New will by default save in the directory of the Edit window from + which it was initiated. SF 748973 Guido van Rossum patch. + + +What's New in IDLEfork 0.9b1? +============================= +*Release date: 02-Jun-2003* + +- The current working directory of the execution environment (and shell + following completion of execution) is now that of the module being run. + +- Added the delete-exitfunc option to config-main.def. (This option is not + included in the Options dialog.) Setting this to True (the default) will + cause IDLE to not run sys.exitfunc/atexit when the subprocess exits. + +- IDLE now preserves the line ending codes when editing a file produced on + a different platform. SF 661759, SF 538584 + +- Reduced default editor font size to 10 point and increased window height + to provide a better initial impression on Windows. + +- Options / Fonts/Tabs / Set Base Editor Font: List box was not highlighting + the default font when first installed on Windows. SF 661676 + +- Added Autosave feature: when user runs code from edit window, if the file + has been modified IDLE will silently save it if Autosave is enabled. The + option is set in the Options dialog, and the default is to prompt the + user to save the file. SF 661318 Bruce Sherwood patch. + +- Improved the RESTART annotation in the shell window when the user restarts + the shell while it is generating output. Also improved annotation when user + repeatedly hammers the Ctrl-F6 restart. + +- Allow IDLE to run when not installed and cwd is not the IDLE directory + SF Patch 686254 "Run IDLEfork from any directory without set-up" - Raphael + +- When a module is run from an EditorWindow: if its directory is not in + sys.path, prepend it. This allows the module to import other modules in + the same directory. Do the same for a script run from the command line. + +- Correctly restart the subprocess if it is running user code and the user + attempts to run some other module or restarts the shell. Do the same if + the link is broken and it is possible to restart the subprocess and re- + connect to the GUI. SF RFE 661321. + +- Improved exception reporting when running commands or scripts from the + command line. + +- Added a -n command line switch to start IDLE without the subprocess. + Removed the Shell menu when running in that mode. Updated help messages. + +- Added a comment to the shell startup header to indicate when IDLE is not + using the subprocess. + +- Restore the ability to run without the subprocess. This can be important for + some platforms or configurations. (Running without the subprocess allows the + debugger to trace through parts of IDLE itself, which may or may not be + desirable, depending on your point of view. In addition, the traditional + reload/import tricks must be use if user source code is changed.) This is + helpful for developing IDLE using IDLE, because one instance can be used to + edit the code and a separate instance run to test changes. (Multiple + concurrent IDLE instances with subprocesses is a future feature) + +- Improve the error message a user gets when saving a file with non-ASCII + characters and no source encoding is specified. Done by adding a dialog + 'EncodingMessage', which contains the line to add in a fixed-font entry + widget, and which has a button to add that line to the file automatically. + Also, add a configuration option 'EditorWindow/encoding', which has three + possible values: none, utf-8, and locale. None is the default: IDLE will show + this dialog when non-ASCII characters are encountered. utf-8 means that files + with non-ASCII characters are saved as utf-8-with-bom. locale means that + files are saved in the locale's encoding; the dialog is only displayed if the + source contains characters outside the locale's charset. SF 710733 - Loewis + +- Improved I/O response by tweaking the wait parameter in various + calls to signal.signal(). + +- Implemented a threaded subprocess which allows interrupting a pass + loop in user code using the 'interrupt' extension. User code runs + in MainThread, while the RPCServer is handled by SockThread. This is + necessary because Windows doesn't support signals. + +- Implemented the 'interrupt' extension module, which allows a subthread + to raise a KeyboardInterrupt in the main thread. + +- Attempting to save the shell raised an error related to saving + breakpoints, which are not implemented in the shell + +- Provide a correct message when 'exit' or 'quit' are entered at the + IDLE command prompt SF 695861 + +- Eliminate extra blank line in shell output caused by not flushing + stdout when user code ends with an unterminated print. SF 695861 + +- Moved responsibility for exception formatting (i.e. pruning IDLE internal + calls) out of rpc.py into the client and server. + +- Exit IDLE cleanly even when doing subprocess I/O + +- Handle subprocess interrupt with an RPC message. + +- Restart the subprocess if it terminates itself. (VPython programs do that) + +- Support subclassing of exceptions, including in the shell, by moving the + exception formatting to the subprocess. + + +What's New in IDLEfork 0.9 Alpha 2? +=================================== +*Release date: 27-Jan-2003* + +- Updated INSTALL.txt to claify use of the python2 rpm. + +- Improved formatting in IDLE Help. + +- Run menu: Replace "Run Script" with "Run Module". + +- Code encountering an unhandled exception under the debugger now shows + the correct traceback, with IDLE internal levels pruned out. + +- If an exception occurs entirely in IDLE, don't prune the IDLE internal + modules from the traceback displayed. + +- Class Browser and Path Browser now use Alt-Key-2 for vertical zoom. + +- IDLE icons will now install correctly even when setup.py is run from the + build directory + +- Class Browser now compatible with Python2.3 version of pyclbr.py + +- Left cursor move in presence of selected text now moves from left end + of the selection. + +- Add Meta keybindings to "IDLE Classic Windows" to handle reversed + Alt/Meta on some Linux distros. + +- Change default: IDLE now starts with Python Shell. + +- Removed the File Path from the Additional Help Sources scrolled list. + +- Add capability to access Additional Help Sources on the web if the + Help File Path begins with //http or www. (Otherwise local path is + validated, as before.) + +- Additional Help Sources were not being posted on the Help menu in the + order entered. Implement sorting the list by [HelpFiles] 'option' + number. + +- Add Browse button to New Help Source dialog. Arrange to start in + Python/Doc if platform is Windows, otherwise start in current directory. + +- Put the Additional Help Sources directly on the Help menu instead of in + an Extra Help cascade menu. Rearrange the Help menu so the Additional + Help Sources come last. Update help.txt appropriately. + +- Fix Tk root pop-ups in configSectionNameDialog.py and configDialog.py + +- Uniform capitalization in General tab of ConfigDialog, update the doc string. + +- Fix bug in ConfigDialog where SaveAllChangedConfig() was unexpectedly + deleting Additional Help Sources from the user's config file. + +- Make configHelpSourceEdit OK button the default and bind <Return> + +- Fix Tk root pop-ups in configHelpSourceEdit: error dialogs not attached + to parents. + +- Use os.startfile() to open both Additional Help and Python Help on the + Windows platform. The application associated with the file type will act as + the viewer. Windows help files (.chm) are now supported via the + Settings/General/Additional Help facility. + +- If Python Help files are installed locally on Linux, use them instead of + accessing python.org. + +- Make the methods for finding the Python help docs more robust, and make + them work in the installed configuration, also. + +- On the Save Before Run dialog, make the OK button the default. One + less mouse action! + +- Add a method: EditorWindow.get_geometry() for future use in implementing + window location persistence. + +- Removed the "Help/Advice" menu entry. Thanks, David! We'll remember! + +- Change the "Classic Windows" theme's paste key to be <ctrl-v>. + +- Rearrange the Shell menu to put Stack Viewer entries adjacent. + +- Add the ability to restart the subprocess interpreter from the shell window; + add an associated menu entry "Shell/Restart" with binding Control-F6. Update + IDLE help. + +- Upon a restart, annotate the shell window with a "restart boundary". Add a + shell window menu "Shell/View Restart" with binding F6 to jump to the most + recent restart boundary. + +- Add Shell menu to Python Shell; change "Settings" to "Options". + +- Remove incorrect comment in setup.py: IDLEfork is now installed as a package. + +- Add INSTALL.txt, HISTORY.txt, NEWS.txt to installed configuration. + +- In installer text, fix reference to Visual Python, should be VPython. + Properly credit David Scherer. + +- Modified idle, idle.py, idle.pyw to improve exception handling. + + +What's New in IDLEfork 0.9 Alpha 1? +=================================== +*Release date: 31-Dec-2002* + +- First release of major new functionality. For further details refer to + Idle-dev and/or the Sourceforge CVS. + +- Adapted to the Mac platform. + +- Overhauled the IDLE startup options and revised the idle -h help message, + which provides details of command line usage. + +- Multiple bug fixes and usability enhancements. + +- Introduced the new RPC implementation, which includes a debugger. The output + of user code is to the shell, and the shell may be used to inspect the + environment after the run has finished. (In version 0.8.1 the shell + environment was separate from the environment of the user code.) + +- Introduced the configuration GUI and a new About dialog. + +- Removed David Scherer's Remote Procedure Call code and replaced with Guido + van Rossum's. GvR code has support for the IDLE debugger and uses the shell + to inspect the environment of code Run from an Edit window. Files removed: + ExecBinding.py, loader.py, protocol.py, Remote.py, spawn.py + +-------------------------------------------------------------------- +Refer to HISTORY.txt for additional information on earlier releases. +-------------------------------------------------------------------- + + + + + diff --git a/lib/python2.7/idlelib/ObjectBrowser.py b/lib/python2.7/idlelib/ObjectBrowser.py new file mode 100644 index 0000000..e69365c --- /dev/null +++ b/lib/python2.7/idlelib/ObjectBrowser.py @@ -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) diff --git a/lib/python2.7/idlelib/OutputWindow.py b/lib/python2.7/idlelib/OutputWindow.py new file mode 100644 index 0000000..63dc737 --- /dev/null +++ b/lib/python2.7/idlelib/OutputWindow.py @@ -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 diff --git a/lib/python2.7/idlelib/ParenMatch.py b/lib/python2.7/idlelib/ParenMatch.py new file mode 100644 index 0000000..47e10f3 --- /dev/null +++ b/lib/python2.7/idlelib/ParenMatch.py @@ -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) diff --git a/lib/python2.7/idlelib/PathBrowser.py b/lib/python2.7/idlelib/PathBrowser.py new file mode 100644 index 0000000..ae26714 --- /dev/null +++ b/lib/python2.7/idlelib/PathBrowser.py @@ -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) diff --git a/lib/python2.7/idlelib/Percolator.py b/lib/python2.7/idlelib/Percolator.py new file mode 100644 index 0000000..e0e8cad --- /dev/null +++ b/lib/python2.7/idlelib/Percolator.py @@ -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) diff --git a/lib/python2.7/idlelib/PyParse.py b/lib/python2.7/idlelib/PyParse.py new file mode 100644 index 0000000..1a9db67 --- /dev/null +++ b/lib/python2.7/idlelib/PyParse.py @@ -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 diff --git a/lib/python2.7/idlelib/PyShell.py b/lib/python2.7/idlelib/PyShell.py new file mode 100644 index 0000000..41b3a5e --- /dev/null +++ b/lib/python2.7/idlelib/PyShell.py @@ -0,0 +1,1641 @@ +#!/usr/bin/env python2 +from __future__ import print_function + +import os +import os.path +import sys +import string +import getopt +import re +import socket +import time +import threading +import io + +import linecache +from code import InteractiveInterpreter +from platform import python_version, system + +try: + from Tkinter import * +except ImportError: + print("** IDLE can't import Tkinter.\n" + "Your Python may not be configured for Tk. **", file=sys.__stderr__) + sys.exit(1) +import tkMessageBox + +from idlelib.EditorWindow import EditorWindow, fixwordbreaks +from idlelib.FileList import FileList +from idlelib.ColorDelegator import ColorDelegator +from idlelib.UndoDelegator import UndoDelegator +from idlelib.OutputWindow import OutputWindow +from idlelib.configHandler import idleConf +from idlelib import rpc +from idlelib import Debugger +from idlelib import RemoteDebugger +from idlelib import macosxSupport +from idlelib import IOBinding + +IDENTCHARS = string.ascii_letters + string.digits + "_" +HOST = '127.0.0.1' # python execution server on localhost loopback +PORT = 0 # someday pass in host, port for remote debug capability + +try: + from signal import SIGTERM +except ImportError: + SIGTERM = 15 + +# Override warnings module to write to warning_stream. Initialize to send IDLE +# internal warnings to the console. ScriptBinding.check_syntax() will +# temporarily redirect the stream to the shell window to display warnings when +# checking user's code. +warning_stream = sys.__stderr__ # None, at least on Windows, if no console. +import warnings + +def idle_formatwarning(message, category, filename, lineno, line=None): + """Format warnings the IDLE way.""" + + s = "\nWarning (from warnings module):\n" + s += ' File \"%s\", line %s\n' % (filename, lineno) + if line is None: + line = linecache.getline(filename, lineno) + line = line.strip() + if line: + s += " %s\n" % line + s += "%s: %s\n" % (category.__name__, message) + return s + +def idle_showwarning( + message, category, filename, lineno, file=None, line=None): + """Show Idle-format warning (after replacing warnings.showwarning). + + The differences are the formatter called, the file=None replacement, + which can be None, the capture of the consequence AttributeError, + and the output of a hard-coded prompt. + """ + if file is None: + file = warning_stream + try: + file.write(idle_formatwarning( + message, category, filename, lineno, line=line)) + file.write(">>> ") + except (AttributeError, IOError): + pass # if file (probably __stderr__) is invalid, skip warning. + +_warnings_showwarning = None + +def capture_warnings(capture): + "Replace warning.showwarning with idle_showwarning, or reverse." + + global _warnings_showwarning + if capture: + if _warnings_showwarning is None: + _warnings_showwarning = warnings.showwarning + warnings.showwarning = idle_showwarning + else: + if _warnings_showwarning is not None: + warnings.showwarning = _warnings_showwarning + _warnings_showwarning = None + +capture_warnings(True) + +def extended_linecache_checkcache(filename=None, + orig_checkcache=linecache.checkcache): + """Extend linecache.checkcache to preserve the <pyshell#...> entries + + Rather than repeating the linecache code, patch it to save the + <pyshell#...> entries, call the original linecache.checkcache() + (skipping them), and then restore the saved entries. + + orig_checkcache is bound at definition time to the original + method, allowing it to be patched. + """ + cache = linecache.cache + save = {} + for key in list(cache): + if key[:1] + key[-1:] == '<>': + save[key] = cache.pop(key) + orig_checkcache(filename) + cache.update(save) + +# Patch linecache.checkcache(): +linecache.checkcache = extended_linecache_checkcache + + +class PyShellEditorWindow(EditorWindow): + "Regular text edit window in IDLE, supports breakpoints" + + def __init__(self, *args): + self.breakpoints = [] + EditorWindow.__init__(self, *args) + self.text.bind("<<set-breakpoint-here>>", self.set_breakpoint_here) + self.text.bind("<<clear-breakpoint-here>>", self.clear_breakpoint_here) + self.text.bind("<<open-python-shell>>", self.flist.open_shell) + + self.breakpointPath = os.path.join(idleConf.GetUserCfgDir(), + 'breakpoints.lst') + # whenever a file is changed, restore breakpoints + def filename_changed_hook(old_hook=self.io.filename_change_hook, + self=self): + self.restore_file_breaks() + old_hook() + self.io.set_filename_change_hook(filename_changed_hook) + if self.io.filename: + self.restore_file_breaks() + self.color_breakpoint_text() + + rmenu_specs = [ + ("Cut", "<<cut>>", "rmenu_check_cut"), + ("Copy", "<<copy>>", "rmenu_check_copy"), + ("Paste", "<<paste>>", "rmenu_check_paste"), + ("Set Breakpoint", "<<set-breakpoint-here>>", None), + ("Clear Breakpoint", "<<clear-breakpoint-here>>", None) + ] + + def color_breakpoint_text(self, color=True): + "Turn colorizing of breakpoint text on or off" + if self.io is None: + # possible due to update in restore_file_breaks + return + if color: + theme = idleConf.CurrentTheme() + cfg = idleConf.GetHighlight(theme, "break") + else: + cfg = {'foreground': '', 'background': ''} + self.text.tag_config('BREAK', cfg) + + def set_breakpoint(self, lineno): + text = self.text + filename = self.io.filename + text.tag_add("BREAK", "%d.0" % lineno, "%d.0" % (lineno+1)) + try: + self.breakpoints.index(lineno) + except ValueError: # only add if missing, i.e. do once + self.breakpoints.append(lineno) + try: # update the subprocess debugger + debug = self.flist.pyshell.interp.debugger + debug.set_breakpoint_here(filename, lineno) + except: # but debugger may not be active right now.... + pass + + def set_breakpoint_here(self, event=None): + text = self.text + filename = self.io.filename + if not filename: + text.bell() + return + lineno = int(float(text.index("insert"))) + self.set_breakpoint(lineno) + + def clear_breakpoint_here(self, event=None): + text = self.text + filename = self.io.filename + if not filename: + text.bell() + return + lineno = int(float(text.index("insert"))) + try: + self.breakpoints.remove(lineno) + except: + pass + text.tag_remove("BREAK", "insert linestart",\ + "insert lineend +1char") + try: + debug = self.flist.pyshell.interp.debugger + debug.clear_breakpoint_here(filename, lineno) + except: + pass + + def clear_file_breaks(self): + if self.breakpoints: + text = self.text + filename = self.io.filename + if not filename: + text.bell() + return + self.breakpoints = [] + text.tag_remove("BREAK", "1.0", END) + try: + debug = self.flist.pyshell.interp.debugger + debug.clear_file_breaks(filename) + except: + pass + + def store_file_breaks(self): + "Save breakpoints when file is saved" + # XXX 13 Dec 2002 KBK Currently the file must be saved before it can + # be run. The breaks are saved at that time. If we introduce + # a temporary file save feature the save breaks functionality + # needs to be re-verified, since the breaks at the time the + # temp file is created may differ from the breaks at the last + # permanent save of the file. Currently, a break introduced + # after a save will be effective, but not persistent. + # This is necessary to keep the saved breaks synched with the + # saved file. + # + # Breakpoints are set as tagged ranges in the text. + # Since a modified file has to be saved before it is + # run, and since self.breakpoints (from which the subprocess + # debugger is loaded) is updated during the save, the visible + # breaks stay synched with the subprocess even if one of these + # unexpected breakpoint deletions occurs. + breaks = self.breakpoints + filename = self.io.filename + try: + with open(self.breakpointPath,"r") as old_file: + lines = old_file.readlines() + except IOError: + lines = [] + try: + with open(self.breakpointPath,"w") as new_file: + for line in lines: + if not line.startswith(filename + '='): + new_file.write(line) + self.update_breakpoints() + breaks = self.breakpoints + if breaks: + new_file.write(filename + '=' + str(breaks) + '\n') + except IOError as err: + if not getattr(self.root, "breakpoint_error_displayed", False): + self.root.breakpoint_error_displayed = True + tkMessageBox.showerror(title='IDLE Error', + message='Unable to update breakpoint list:\n%s' + % str(err), + parent=self.text) + + def restore_file_breaks(self): + self.text.update() # this enables setting "BREAK" tags to be visible + if self.io is None: + # can happen if IDLE closes due to the .update() call + return + filename = self.io.filename + if filename is None: + return + if os.path.isfile(self.breakpointPath): + lines = open(self.breakpointPath,"r").readlines() + for line in lines: + if line.startswith(filename + '='): + breakpoint_linenumbers = eval(line[len(filename)+1:]) + for breakpoint_linenumber in breakpoint_linenumbers: + self.set_breakpoint(breakpoint_linenumber) + + def update_breakpoints(self): + "Retrieves all the breakpoints in the current window" + text = self.text + ranges = text.tag_ranges("BREAK") + linenumber_list = self.ranges_to_linenumbers(ranges) + self.breakpoints = linenumber_list + + def ranges_to_linenumbers(self, ranges): + lines = [] + for index in range(0, len(ranges), 2): + lineno = int(float(ranges[index].string)) + end = int(float(ranges[index+1].string)) + while lineno < end: + lines.append(lineno) + lineno += 1 + return lines + +# XXX 13 Dec 2002 KBK Not used currently +# def saved_change_hook(self): +# "Extend base method - clear breaks if module is modified" +# if not self.get_saved(): +# self.clear_file_breaks() +# EditorWindow.saved_change_hook(self) + + def _close(self): + "Extend base method - clear breaks when module is closed" + self.clear_file_breaks() + EditorWindow._close(self) + + +class PyShellFileList(FileList): + "Extend base class: IDLE supports a shell and breakpoints" + + # override FileList's class variable, instances return PyShellEditorWindow + # instead of EditorWindow when new edit windows are created. + EditorWindow = PyShellEditorWindow + + pyshell = None + + def open_shell(self, event=None): + if self.pyshell: + self.pyshell.top.wakeup() + else: + self.pyshell = PyShell(self) + if self.pyshell: + if not self.pyshell.begin(): + return None + return self.pyshell + + +class ModifiedColorDelegator(ColorDelegator): + "Extend base class: colorizer for the shell window itself" + + def __init__(self): + ColorDelegator.__init__(self) + self.LoadTagDefs() + + def recolorize_main(self): + self.tag_remove("TODO", "1.0", "iomark") + self.tag_add("SYNC", "1.0", "iomark") + ColorDelegator.recolorize_main(self) + + def LoadTagDefs(self): + ColorDelegator.LoadTagDefs(self) + theme = idleConf.CurrentTheme() + self.tagdefs.update({ + "stdin": {'background':None,'foreground':None}, + "stdout": idleConf.GetHighlight(theme, "stdout"), + "stderr": idleConf.GetHighlight(theme, "stderr"), + "console": idleConf.GetHighlight(theme, "console"), + }) + + def removecolors(self): + # Don't remove shell color tags before "iomark" + for tag in self.tagdefs: + self.tag_remove(tag, "iomark", "end") + +class ModifiedUndoDelegator(UndoDelegator): + "Extend base class: forbid insert/delete before the I/O mark" + + def insert(self, index, chars, tags=None): + try: + if self.delegate.compare(index, "<", "iomark"): + self.delegate.bell() + return + except TclError: + pass + UndoDelegator.insert(self, index, chars, tags) + + def delete(self, index1, index2=None): + try: + if self.delegate.compare(index1, "<", "iomark"): + self.delegate.bell() + return + except TclError: + pass + UndoDelegator.delete(self, index1, index2) + + +class MyRPCClient(rpc.RPCClient): + + def handle_EOF(self): + "Override the base class - just re-raise EOFError" + raise EOFError + + +class ModifiedInterpreter(InteractiveInterpreter): + + def __init__(self, tkconsole): + self.tkconsole = tkconsole + locals = sys.modules['__main__'].__dict__ + InteractiveInterpreter.__init__(self, locals=locals) + self.save_warnings_filters = None + self.restarting = False + self.subprocess_arglist = None + self.port = PORT + self.original_compiler_flags = self.compile.compiler.flags + + _afterid = None + rpcclt = None + rpcpid = None + + def spawn_subprocess(self): + if self.subprocess_arglist is None: + self.subprocess_arglist = self.build_subprocess_arglist() + args = self.subprocess_arglist + self.rpcpid = os.spawnv(os.P_NOWAIT, sys.executable, args) + + def build_subprocess_arglist(self): + assert (self.port!=0), ( + "Socket should have been assigned a port number.") + w = ['-W' + s for s in sys.warnoptions] + if 1/2 > 0: # account for new division + w.append('-Qnew') + # Maybe IDLE is installed and is being accessed via sys.path, + # or maybe it's not installed and the idle.py script is being + # run from the IDLE source directory. + del_exitf = idleConf.GetOption('main', 'General', 'delete-exitfunc', + default=False, type='bool') + if __name__ == 'idlelib.PyShell': + command = "__import__('idlelib.run').run.main(%r)" % (del_exitf,) + else: + command = "__import__('run').main(%r)" % (del_exitf,) + if sys.platform[:3] == 'win' and ' ' in sys.executable: + # handle embedded space in path by quoting the argument + decorated_exec = '"%s"' % sys.executable + else: + decorated_exec = sys.executable + return [decorated_exec] + w + ["-c", command, str(self.port)] + + def start_subprocess(self): + addr = (HOST, self.port) + # GUI makes several attempts to acquire socket, listens for connection + for i in range(3): + time.sleep(i) + try: + self.rpcclt = MyRPCClient(addr) + break + except socket.error: + pass + else: + self.display_port_binding_error() + return None + # if PORT was 0, system will assign an 'ephemeral' port. Find it out: + self.port = self.rpcclt.listening_sock.getsockname()[1] + # if PORT was not 0, probably working with a remote execution server + if PORT != 0: + # To allow reconnection within the 2MSL wait (cf. Stevens TCP + # V1, 18.6), set SO_REUSEADDR. Note that this can be problematic + # on Windows since the implementation allows two active sockets on + # the same address! + self.rpcclt.listening_sock.setsockopt(socket.SOL_SOCKET, + socket.SO_REUSEADDR, 1) + self.spawn_subprocess() + #time.sleep(20) # test to simulate GUI not accepting connection + # Accept the connection from the Python execution server + self.rpcclt.listening_sock.settimeout(10) + try: + self.rpcclt.accept() + except socket.timeout: + self.display_no_subprocess_error() + return None + self.rpcclt.register("console", self.tkconsole) + self.rpcclt.register("stdin", self.tkconsole.stdin) + self.rpcclt.register("stdout", self.tkconsole.stdout) + self.rpcclt.register("stderr", self.tkconsole.stderr) + self.rpcclt.register("flist", self.tkconsole.flist) + self.rpcclt.register("linecache", linecache) + self.rpcclt.register("interp", self) + self.transfer_path(with_cwd=True) + self.poll_subprocess() + return self.rpcclt + + def restart_subprocess(self, with_cwd=False, filename=''): + if self.restarting: + return self.rpcclt + self.restarting = True + # close only the subprocess debugger + debug = self.getdebugger() + if debug: + try: + # Only close subprocess debugger, don't unregister gui_adap! + RemoteDebugger.close_subprocess_debugger(self.rpcclt) + except: + pass + # Kill subprocess, spawn a new one, accept connection. + self.rpcclt.close() + self.unix_terminate() + console = self.tkconsole + was_executing = console.executing + console.executing = False + self.spawn_subprocess() + try: + self.rpcclt.accept() + except socket.timeout: + self.display_no_subprocess_error() + return None + self.transfer_path(with_cwd=with_cwd) + console.stop_readline() + # annotate restart in shell window and mark it + console.text.delete("iomark", "end-1c") + tag = 'RESTART: ' + (filename if filename else 'Shell') + halfbar = ((int(console.width) -len(tag) - 4) // 2) * '=' + console.write("\n{0} {1} {0}".format(halfbar, tag)) + console.text.mark_set("restart", "end-1c") + console.text.mark_gravity("restart", "left") + if not filename: + console.showprompt() + # restart subprocess debugger + if debug: + # Restarted debugger connects to current instance of debug GUI + RemoteDebugger.restart_subprocess_debugger(self.rpcclt) + # reload remote debugger breakpoints for all PyShellEditWindows + debug.load_breakpoints() + self.compile.compiler.flags = self.original_compiler_flags + self.restarting = False + return self.rpcclt + + def __request_interrupt(self): + self.rpcclt.remotecall("exec", "interrupt_the_server", (), {}) + + def interrupt_subprocess(self): + threading.Thread(target=self.__request_interrupt).start() + + def kill_subprocess(self): + if self._afterid is not None: + self.tkconsole.text.after_cancel(self._afterid) + try: + self.rpcclt.close() + except AttributeError: # no socket + pass + self.unix_terminate() + self.tkconsole.executing = False + self.rpcclt = None + + def unix_terminate(self): + "UNIX: make sure subprocess is terminated and collect status" + if hasattr(os, 'kill'): + try: + os.kill(self.rpcpid, SIGTERM) + except OSError: + # process already terminated: + return + else: + try: + os.waitpid(self.rpcpid, 0) + except OSError: + return + + def transfer_path(self, with_cwd=False): + if with_cwd: # Issue 13506 + path = [''] # include Current Working Directory + path.extend(sys.path) + else: + path = sys.path + + self.runcommand("""if 1: + import sys as _sys + _sys.path = %r + del _sys + \n""" % (path,)) + + active_seq = None + + def poll_subprocess(self): + clt = self.rpcclt + if clt is None: + return + try: + response = clt.pollresponse(self.active_seq, wait=0.05) + except (EOFError, IOError, KeyboardInterrupt): + # lost connection or subprocess terminated itself, restart + # [the KBI is from rpc.SocketIO.handle_EOF()] + if self.tkconsole.closing: + return + response = None + self.restart_subprocess() + if response: + self.tkconsole.resetoutput() + self.active_seq = None + how, what = response + console = self.tkconsole.console + if how == "OK": + if what is not None: + print(repr(what), file=console) + elif how == "EXCEPTION": + if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"): + self.remote_stack_viewer() + elif how == "ERROR": + errmsg = "PyShell.ModifiedInterpreter: Subprocess ERROR:\n" + print(errmsg, what, file=sys.__stderr__) + print(errmsg, what, file=console) + # we received a response to the currently active seq number: + try: + self.tkconsole.endexecuting() + except AttributeError: # shell may have closed + pass + # Reschedule myself + if not self.tkconsole.closing: + self._afterid = self.tkconsole.text.after( + self.tkconsole.pollinterval, self.poll_subprocess) + + debugger = None + + def setdebugger(self, debugger): + self.debugger = debugger + + def getdebugger(self): + return self.debugger + + def open_remote_stack_viewer(self): + """Initiate the remote stack viewer from a separate thread. + + This method is called from the subprocess, and by returning from this + method we allow the subprocess to unblock. After a bit the shell + requests the subprocess to open the remote stack viewer which returns a + static object looking at the last exception. It is queried through + the RPC mechanism. + + """ + self.tkconsole.text.after(300, self.remote_stack_viewer) + return + + def remote_stack_viewer(self): + from idlelib import RemoteObjectBrowser + oid = self.rpcclt.remotequeue("exec", "stackviewer", ("flist",), {}) + if oid is None: + self.tkconsole.root.bell() + return + item = RemoteObjectBrowser.StubObjectTreeItem(self.rpcclt, oid) + from idlelib.TreeWidget import ScrolledCanvas, TreeNode + top = Toplevel(self.tkconsole.root) + theme = idleConf.CurrentTheme() + background = idleConf.GetHighlight(theme, 'normal')['background'] + sc = ScrolledCanvas(top, bg=background, highlightthickness=0) + sc.frame.pack(expand=1, fill="both") + node = TreeNode(sc.canvas, None, item) + node.expand() + # XXX Should GC the remote tree when closing the window + + gid = 0 + + def execsource(self, source): + "Like runsource() but assumes complete exec source" + filename = self.stuffsource(source) + self.execfile(filename, source) + + def execfile(self, filename, source=None): + "Execute an existing file" + if source is None: + source = open(filename, "r").read() + try: + code = compile(source, filename, "exec", dont_inherit=True) + except (OverflowError, SyntaxError): + self.tkconsole.resetoutput() + print('*** Error in script or command!\n' + 'Traceback (most recent call last):', + file=self.tkconsole.stderr) + InteractiveInterpreter.showsyntaxerror(self, filename) + self.tkconsole.showprompt() + else: + self.runcode(code) + + def runsource(self, source): + "Extend base class method: Stuff the source in the line cache first" + filename = self.stuffsource(source) + self.more = 0 + self.save_warnings_filters = warnings.filters[:] + warnings.filterwarnings(action="error", category=SyntaxWarning) + if isinstance(source, unicode) and IOBinding.encoding != 'utf-8': + try: + source = '# -*- coding: %s -*-\n%s' % ( + IOBinding.encoding, + source.encode(IOBinding.encoding)) + except UnicodeError: + self.tkconsole.resetoutput() + self.write("Unsupported characters in input\n") + return + try: + # InteractiveInterpreter.runsource() calls its runcode() method, + # which is overridden (see below) + return InteractiveInterpreter.runsource(self, source, filename) + finally: + if self.save_warnings_filters is not None: + warnings.filters[:] = self.save_warnings_filters + self.save_warnings_filters = None + + def stuffsource(self, source): + "Stuff source in the filename cache" + filename = "<pyshell#%d>" % self.gid + self.gid = self.gid + 1 + lines = source.split("\n") + linecache.cache[filename] = len(source)+1, 0, lines, filename + return filename + + def prepend_syspath(self, filename): + "Prepend sys.path with file's directory if not already included" + self.runcommand("""if 1: + _filename = %r + import sys as _sys + from os.path import dirname as _dirname + _dir = _dirname(_filename) + if not _dir in _sys.path: + _sys.path.insert(0, _dir) + del _filename, _sys, _dirname, _dir + \n""" % (filename,)) + + def showsyntaxerror(self, filename=None): + """Extend base class method: Add Colorizing + + Color the offending position instead of printing it and pointing at it + with a caret. + + """ + text = self.tkconsole.text + stuff = self.unpackerror() + if stuff: + msg, lineno, offset, line = stuff + if lineno == 1: + pos = "iomark + %d chars" % (offset-1) + else: + pos = "iomark linestart + %d lines + %d chars" % \ + (lineno-1, offset-1) + text.tag_add("ERROR", pos) + text.see(pos) + char = text.get(pos) + if char and char in IDENTCHARS: + text.tag_add("ERROR", pos + " wordstart", pos) + self.tkconsole.resetoutput() + self.write("SyntaxError: %s\n" % str(msg)) + else: + self.tkconsole.resetoutput() + InteractiveInterpreter.showsyntaxerror(self, filename) + self.tkconsole.showprompt() + + def unpackerror(self): + type, value, tb = sys.exc_info() + ok = type is SyntaxError + if ok: + try: + msg, (dummy_filename, lineno, offset, line) = value + if not offset: + offset = 0 + except: + ok = 0 + if ok: + return msg, lineno, offset, line + else: + return None + + def showtraceback(self): + "Extend base class method to reset output properly" + self.tkconsole.resetoutput() + self.checklinecache() + InteractiveInterpreter.showtraceback(self) + if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"): + self.tkconsole.open_stack_viewer() + + def checklinecache(self): + c = linecache.cache + for key in c.keys(): + if key[:1] + key[-1:] != "<>": + del c[key] + + def runcommand(self, code): + "Run the code without invoking the debugger" + # The code better not raise an exception! + if self.tkconsole.executing: + self.display_executing_dialog() + return 0 + if self.rpcclt: + self.rpcclt.remotequeue("exec", "runcode", (code,), {}) + else: + exec code in self.locals + return 1 + + def runcode(self, code): + "Override base class method" + if self.tkconsole.executing: + self.interp.restart_subprocess() + self.checklinecache() + if self.save_warnings_filters is not None: + warnings.filters[:] = self.save_warnings_filters + self.save_warnings_filters = None + debugger = self.debugger + try: + self.tkconsole.beginexecuting() + if not debugger and self.rpcclt is not None: + self.active_seq = self.rpcclt.asyncqueue("exec", "runcode", + (code,), {}) + elif debugger: + debugger.run(code, self.locals) + else: + exec code in self.locals + except SystemExit: + if not self.tkconsole.closing: + if tkMessageBox.askyesno( + "Exit?", + "Do you want to exit altogether?", + default="yes", + parent=self.tkconsole.text): + raise + else: + self.showtraceback() + else: + raise + except: + if use_subprocess: + print("IDLE internal error in runcode()", + file=self.tkconsole.stderr) + self.showtraceback() + self.tkconsole.endexecuting() + else: + if self.tkconsole.canceled: + self.tkconsole.canceled = False + print("KeyboardInterrupt", file=self.tkconsole.stderr) + else: + self.showtraceback() + finally: + if not use_subprocess: + try: + self.tkconsole.endexecuting() + except AttributeError: # shell may have closed + pass + + def write(self, s): + "Override base class method" + self.tkconsole.stderr.write(s) + + def display_port_binding_error(self): + tkMessageBox.showerror( + "Port Binding Error", + "IDLE can't bind to a TCP/IP port, which is necessary to " + "communicate with its Python execution server. This might be " + "because no networking is installed on this computer. " + "Run IDLE with the -n command line switch to start without a " + "subprocess and refer to Help/IDLE Help 'Running without a " + "subprocess' for further details.", + parent=self.tkconsole.text) + + def display_no_subprocess_error(self): + tkMessageBox.showerror( + "Subprocess Startup Error", + "IDLE's subprocess didn't make connection. Either IDLE can't " + "start a subprocess or personal firewall software is blocking " + "the connection.", + parent=self.tkconsole.text) + + def display_executing_dialog(self): + tkMessageBox.showerror( + "Already executing", + "The Python Shell window is already executing a command; " + "please wait until it is finished.", + parent=self.tkconsole.text) + + +class PyShell(OutputWindow): + + shell_title = "Python " + python_version() + " Shell" + + # Override classes + ColorDelegator = ModifiedColorDelegator + UndoDelegator = ModifiedUndoDelegator + + # Override menus + menu_specs = [ + ("file", "_File"), + ("edit", "_Edit"), + ("debug", "_Debug"), + ("options", "_Options"), + ("windows", "_Window"), + ("help", "_Help"), + ] + + + # New classes + from idlelib.IdleHistory import History + + def __init__(self, flist=None): + if use_subprocess: + ms = self.menu_specs + if ms[2][0] != "shell": + ms.insert(2, ("shell", "She_ll")) + self.interp = ModifiedInterpreter(self) + if flist is None: + root = Tk() + fixwordbreaks(root) + root.withdraw() + flist = PyShellFileList(root) + # + OutputWindow.__init__(self, flist, None, None) + # +## self.config(usetabs=1, indentwidth=8, context_use_ps1=1) + self.usetabs = True + # indentwidth must be 8 when using tabs. See note in EditorWindow: + self.indentwidth = 8 + self.context_use_ps1 = True + # + text = self.text + text.configure(wrap="char") + text.bind("<<newline-and-indent>>", self.enter_callback) + text.bind("<<plain-newline-and-indent>>", self.linefeed_callback) + text.bind("<<interrupt-execution>>", self.cancel_callback) + text.bind("<<end-of-file>>", self.eof_callback) + text.bind("<<open-stack-viewer>>", self.open_stack_viewer) + text.bind("<<toggle-debugger>>", self.toggle_debugger) + text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer) + if use_subprocess: + text.bind("<<view-restart>>", self.view_restart_mark) + text.bind("<<restart-shell>>", self.restart_shell) + # + self.save_stdout = sys.stdout + self.save_stderr = sys.stderr + self.save_stdin = sys.stdin + from idlelib import IOBinding + self.stdin = PseudoInputFile(self, "stdin", IOBinding.encoding) + self.stdout = PseudoOutputFile(self, "stdout", IOBinding.encoding) + self.stderr = PseudoOutputFile(self, "stderr", IOBinding.encoding) + self.console = PseudoOutputFile(self, "console", IOBinding.encoding) + if not use_subprocess: + sys.stdout = self.stdout + sys.stderr = self.stderr + sys.stdin = self.stdin + # + self.history = self.History(self.text) + # + self.pollinterval = 50 # millisec + + def get_standard_extension_names(self): + return idleConf.GetExtensions(shell_only=True) + + reading = False + executing = False + canceled = False + endoffile = False + closing = False + _stop_readline_flag = False + + def set_warning_stream(self, stream): + global warning_stream + warning_stream = stream + + def get_warning_stream(self): + return warning_stream + + def toggle_debugger(self, event=None): + if self.executing: + tkMessageBox.showerror("Don't debug now", + "You can only toggle the debugger when idle", + parent=self.text) + self.set_debugger_indicator() + return "break" + else: + db = self.interp.getdebugger() + if db: + self.close_debugger() + else: + self.open_debugger() + + def set_debugger_indicator(self): + db = self.interp.getdebugger() + self.setvar("<<toggle-debugger>>", not not db) + + def toggle_jit_stack_viewer(self, event=None): + pass # All we need is the variable + + def close_debugger(self): + db = self.interp.getdebugger() + if db: + self.interp.setdebugger(None) + db.close() + if self.interp.rpcclt: + RemoteDebugger.close_remote_debugger(self.interp.rpcclt) + self.resetoutput() + self.console.write("[DEBUG OFF]\n") + sys.ps1 = ">>> " + self.showprompt() + self.set_debugger_indicator() + + def open_debugger(self): + if self.interp.rpcclt: + dbg_gui = RemoteDebugger.start_remote_debugger(self.interp.rpcclt, + self) + else: + dbg_gui = Debugger.Debugger(self) + self.interp.setdebugger(dbg_gui) + dbg_gui.load_breakpoints() + sys.ps1 = "[DEBUG ON]\n>>> " + self.showprompt() + self.set_debugger_indicator() + + def beginexecuting(self): + "Helper for ModifiedInterpreter" + self.resetoutput() + self.executing = 1 + + def endexecuting(self): + "Helper for ModifiedInterpreter" + self.executing = 0 + self.canceled = 0 + self.showprompt() + + def close(self): + "Extend EditorWindow.close()" + if self.executing: + response = tkMessageBox.askokcancel( + "Kill?", + "Your program is still running!\n Do you want to kill it?", + default="ok", + parent=self.text) + if response is False: + return "cancel" + self.stop_readline() + self.canceled = True + self.closing = True + return EditorWindow.close(self) + + def _close(self): + "Extend EditorWindow._close(), shut down debugger and execution server" + self.close_debugger() + if use_subprocess: + self.interp.kill_subprocess() + # Restore std streams + sys.stdout = self.save_stdout + sys.stderr = self.save_stderr + sys.stdin = self.save_stdin + # Break cycles + self.interp = None + self.console = None + self.flist.pyshell = None + self.history = None + EditorWindow._close(self) + + def ispythonsource(self, filename): + "Override EditorWindow method: never remove the colorizer" + return True + + def short_title(self): + return self.shell_title + + COPYRIGHT = \ + 'Type "copyright", "credits" or "license()" for more information.' + + def begin(self): + self.resetoutput() + if use_subprocess: + nosub = '' + client = self.interp.start_subprocess() + if not client: + self.close() + return False + else: + nosub = "==== No Subprocess ====" + self.write("Python %s on %s\n%s\n%s" % + (sys.version, sys.platform, self.COPYRIGHT, nosub)) + self.text.focus_force() + self.showprompt() + import Tkinter + Tkinter._default_root = None # 03Jan04 KBK What's this? + return True + + def stop_readline(self): + if not self.reading: # no nested mainloop to exit. + return + self._stop_readline_flag = True + self.top.quit() + + def readline(self): + save = self.reading + try: + self.reading = 1 + self.top.mainloop() # nested mainloop() + finally: + self.reading = save + if self._stop_readline_flag: + self._stop_readline_flag = False + return "" + line = self.text.get("iomark", "end-1c") + if len(line) == 0: # may be EOF if we quit our mainloop with Ctrl-C + line = "\n" + if isinstance(line, unicode): + from idlelib import IOBinding + try: + line = line.encode(IOBinding.encoding) + except UnicodeError: + pass + self.resetoutput() + if self.canceled: + self.canceled = 0 + if not use_subprocess: + raise KeyboardInterrupt + if self.endoffile: + self.endoffile = 0 + line = "" + return line + + def isatty(self): + return True + + def cancel_callback(self, event=None): + try: + if self.text.compare("sel.first", "!=", "sel.last"): + return # Active selection -- always use default binding + except: + pass + if not (self.executing or self.reading): + self.resetoutput() + self.interp.write("KeyboardInterrupt\n") + self.showprompt() + return "break" + self.endoffile = 0 + self.canceled = 1 + if (self.executing and self.interp.rpcclt): + if self.interp.getdebugger(): + self.interp.restart_subprocess() + else: + self.interp.interrupt_subprocess() + if self.reading: + self.top.quit() # exit the nested mainloop() in readline() + return "break" + + def eof_callback(self, event): + if self.executing and not self.reading: + return # Let the default binding (delete next char) take over + if not (self.text.compare("iomark", "==", "insert") and + self.text.compare("insert", "==", "end-1c")): + return # Let the default binding (delete next char) take over + if not self.executing: + self.resetoutput() + self.close() + else: + self.canceled = 0 + self.endoffile = 1 + self.top.quit() + return "break" + + def linefeed_callback(self, event): + # Insert a linefeed without entering anything (still autoindented) + if self.reading: + self.text.insert("insert", "\n") + self.text.see("insert") + else: + self.newline_and_indent_event(event) + return "break" + + def enter_callback(self, event): + if self.executing and not self.reading: + return # Let the default binding (insert '\n') take over + # If some text is selected, recall the selection + # (but only if this before the I/O mark) + try: + sel = self.text.get("sel.first", "sel.last") + if sel: + if self.text.compare("sel.last", "<=", "iomark"): + self.recall(sel, event) + return "break" + except: + pass + # If we're strictly before the line containing iomark, recall + # the current line, less a leading prompt, less leading or + # trailing whitespace + if self.text.compare("insert", "<", "iomark linestart"): + # Check if there's a relevant stdin range -- if so, use it + prev = self.text.tag_prevrange("stdin", "insert") + if prev and self.text.compare("insert", "<", prev[1]): + self.recall(self.text.get(prev[0], prev[1]), event) + return "break" + next = self.text.tag_nextrange("stdin", "insert") + if next and self.text.compare("insert lineend", ">=", next[0]): + self.recall(self.text.get(next[0], next[1]), event) + return "break" + # No stdin mark -- just get the current line, less any prompt + indices = self.text.tag_nextrange("console", "insert linestart") + if indices and \ + self.text.compare(indices[0], "<=", "insert linestart"): + self.recall(self.text.get(indices[1], "insert lineend"), event) + else: + self.recall(self.text.get("insert linestart", "insert lineend"), event) + return "break" + # If we're between the beginning of the line and the iomark, i.e. + # in the prompt area, move to the end of the prompt + if self.text.compare("insert", "<", "iomark"): + self.text.mark_set("insert", "iomark") + # If we're in the current input and there's only whitespace + # beyond the cursor, erase that whitespace first + s = self.text.get("insert", "end-1c") + if s and not s.strip(): + self.text.delete("insert", "end-1c") + # If we're in the current input before its last line, + # insert a newline right at the insert point + if self.text.compare("insert", "<", "end-1c linestart"): + self.newline_and_indent_event(event) + return "break" + # We're in the last line; append a newline and submit it + self.text.mark_set("insert", "end-1c") + if self.reading: + self.text.insert("insert", "\n") + self.text.see("insert") + else: + self.newline_and_indent_event(event) + self.text.tag_add("stdin", "iomark", "end-1c") + self.text.update_idletasks() + if self.reading: + self.top.quit() # Break out of recursive mainloop() in raw_input() + else: + self.runit() + return "break" + + def recall(self, s, event): + # remove leading and trailing empty or whitespace lines + s = re.sub(r'^\s*\n', '' , s) + s = re.sub(r'\n\s*$', '', s) + lines = s.split('\n') + self.text.undo_block_start() + try: + self.text.tag_remove("sel", "1.0", "end") + self.text.mark_set("insert", "end-1c") + prefix = self.text.get("insert linestart", "insert") + if prefix.rstrip().endswith(':'): + self.newline_and_indent_event(event) + prefix = self.text.get("insert linestart", "insert") + self.text.insert("insert", lines[0].strip()) + if len(lines) > 1: + orig_base_indent = re.search(r'^([ \t]*)', lines[0]).group(0) + new_base_indent = re.search(r'^([ \t]*)', prefix).group(0) + for line in lines[1:]: + if line.startswith(orig_base_indent): + # replace orig base indentation with new indentation + line = new_base_indent + line[len(orig_base_indent):] + self.text.insert('insert', '\n'+line.rstrip()) + finally: + self.text.see("insert") + self.text.undo_block_stop() + + def runit(self): + line = self.text.get("iomark", "end-1c") + # Strip off last newline and surrounding whitespace. + # (To allow you to hit return twice to end a statement.) + i = len(line) + while i > 0 and line[i-1] in " \t": + i = i-1 + if i > 0 and line[i-1] == "\n": + i = i-1 + while i > 0 and line[i-1] in " \t": + i = i-1 + line = line[:i] + self.interp.runsource(line) + + def open_stack_viewer(self, event=None): + if self.interp.rpcclt: + return self.interp.remote_stack_viewer() + try: + sys.last_traceback + except: + tkMessageBox.showerror("No stack trace", + "There is no stack trace yet.\n" + "(sys.last_traceback is not defined)", + parent=self.text) + return + from idlelib.StackViewer import StackBrowser + StackBrowser(self.root, self.flist) + + def view_restart_mark(self, event=None): + self.text.see("iomark") + self.text.see("restart") + + def restart_shell(self, event=None): + "Callback for Run/Restart Shell Cntl-F6" + self.interp.restart_subprocess(with_cwd=True) + + def showprompt(self): + self.resetoutput() + try: + s = str(sys.ps1) + except: + s = "" + self.console.write(s) + self.text.mark_set("insert", "end-1c") + self.set_line_and_column() + self.io.reset_undo() + + def resetoutput(self): + source = self.text.get("iomark", "end-1c") + if self.history: + self.history.store(source) + if self.text.get("end-2c") != "\n": + self.text.insert("end-1c", "\n") + self.text.mark_set("iomark", "end-1c") + self.set_line_and_column() + sys.stdout.softspace = 0 + + def write(self, s, tags=()): + try: + self.text.mark_gravity("iomark", "right") + OutputWindow.write(self, s, tags, "iomark") + self.text.mark_gravity("iomark", "left") + except: + pass + if self.canceled: + self.canceled = 0 + if not use_subprocess: + raise KeyboardInterrupt + + def rmenu_check_cut(self): + try: + if self.text.compare('sel.first', '<', 'iomark'): + return 'disabled' + except TclError: # no selection, so the index 'sel.first' doesn't exist + return 'disabled' + return super(PyShell, self).rmenu_check_cut() + + def rmenu_check_paste(self): + if self.text.compare('insert', '<', 'iomark'): + return 'disabled' + return super(PyShell, self).rmenu_check_paste() + +class PseudoFile(io.TextIOBase): + + def __init__(self, shell, tags, encoding=None): + self.shell = shell + self.tags = tags + self.softspace = 0 + self._encoding = encoding + + @property + def encoding(self): + return self._encoding + + @property + def name(self): + return '<%s>' % self.tags + + def isatty(self): + return True + + +class PseudoOutputFile(PseudoFile): + + def writable(self): + return True + + def write(self, s): + if self.closed: + raise ValueError("write to closed file") + if type(s) not in (unicode, str, bytearray): + # See issue #19481 + if isinstance(s, unicode): + s = unicode.__getitem__(s, slice(None)) + elif isinstance(s, str): + s = str.__str__(s) + elif isinstance(s, bytearray): + s = bytearray.__str__(s) + else: + raise TypeError('must be string, not ' + type(s).__name__) + return self.shell.write(s, self.tags) + + +class PseudoInputFile(PseudoFile): + + def __init__(self, shell, tags, encoding=None): + PseudoFile.__init__(self, shell, tags, encoding) + self._line_buffer = '' + + def readable(self): + return True + + def read(self, size=-1): + if self.closed: + raise ValueError("read from closed file") + if size is None: + size = -1 + elif not isinstance(size, int): + raise TypeError('must be int, not ' + type(size).__name__) + result = self._line_buffer + self._line_buffer = '' + if size < 0: + while True: + line = self.shell.readline() + if not line: break + result += line + else: + while len(result) < size: + line = self.shell.readline() + if not line: break + result += line + self._line_buffer = result[size:] + result = result[:size] + return result + + def readline(self, size=-1): + if self.closed: + raise ValueError("read from closed file") + if size is None: + size = -1 + elif not isinstance(size, int): + raise TypeError('must be int, not ' + type(size).__name__) + line = self._line_buffer or self.shell.readline() + if size < 0: + size = len(line) + eol = line.find('\n', 0, size) + if eol >= 0: + size = eol + 1 + self._line_buffer = line[size:] + return line[:size] + + def close(self): + self.shell.close() + + +def fix_x11_paste(root): + "Make paste replace selection on x11. See issue #5124." + if root._windowingsystem == 'x11': + for cls in 'Text', 'Entry', 'Spinbox': + root.bind_class( + cls, + '<<Paste>>', + 'catch {%W delete sel.first sel.last}\n' + + root.bind_class(cls, '<<Paste>>')) + + +usage_msg = """\ + +USAGE: idle [-deins] [-t title] [file]* + idle [-dns] [-t title] (-c cmd | -r file) [arg]* + idle [-dns] [-t title] - [arg]* + + -h print this help message and exit + -n run IDLE without a subprocess (see Help/IDLE Help for details) + +The following options will override the IDLE 'settings' configuration: + + -e open an edit window + -i open a shell window + +The following options imply -i and will open a shell: + + -c cmd run the command in a shell, or + -r file run script from file + + -d enable the debugger + -s run $IDLESTARTUP or $PYTHONSTARTUP before anything else + -t title set title of shell window + +A default edit window will be bypassed when -c, -r, or - are used. + +[arg]* are passed to the command (-c) or script (-r) in sys.argv[1:]. + +Examples: + +idle + Open an edit window or shell depending on IDLE's configuration. + +idle foo.py foobar.py + Edit the files, also open a shell if configured to start with shell. + +idle -est "Baz" foo.py + Run $IDLESTARTUP or $PYTHONSTARTUP, edit foo.py, and open a shell + window with the title "Baz". + +idle -c "import sys; print sys.argv" "foo" + Open a shell window and run the command, passing "-c" in sys.argv[0] + and "foo" in sys.argv[1]. + +idle -d -s -r foo.py "Hello World" + Open a shell window, run a startup script, enable the debugger, and + run foo.py, passing "foo.py" in sys.argv[0] and "Hello World" in + sys.argv[1]. + +echo "import sys; print sys.argv" | idle - "foobar" + Open a shell window, run the script piped in, passing '' in sys.argv[0] + and "foobar" in sys.argv[1]. +""" + +def main(): + global flist, root, use_subprocess + + capture_warnings(True) + use_subprocess = True + enable_shell = False + enable_edit = False + debug = False + cmd = None + script = None + startup = False + try: + opts, args = getopt.getopt(sys.argv[1:], "c:deihnr:st:") + except getopt.error as msg: + print("Error: %s\n%s" % (msg, usage_msg), file=sys.stderr) + sys.exit(2) + for o, a in opts: + if o == '-c': + cmd = a + enable_shell = True + if o == '-d': + debug = True + enable_shell = True + if o == '-e': + enable_edit = True + if o == '-h': + sys.stdout.write(usage_msg) + sys.exit() + if o == '-i': + enable_shell = True + if o == '-n': + use_subprocess = False + if o == '-r': + script = a + if os.path.isfile(script): + pass + else: + print("No script file: ", script, file=sys.stderr) + sys.exit() + enable_shell = True + if o == '-s': + startup = True + enable_shell = True + if o == '-t': + PyShell.shell_title = a + enable_shell = True + if args and args[0] == '-': + cmd = sys.stdin.read() + enable_shell = True + # process sys.argv and sys.path: + for i in range(len(sys.path)): + sys.path[i] = os.path.abspath(sys.path[i]) + if args and args[0] == '-': + sys.argv = [''] + args[1:] + elif cmd: + sys.argv = ['-c'] + args + elif script: + sys.argv = [script] + args + elif args: + enable_edit = True + pathx = [] + for filename in args: + pathx.append(os.path.dirname(filename)) + for dir in pathx: + dir = os.path.abspath(dir) + if dir not in sys.path: + sys.path.insert(0, dir) + else: + dir = os.getcwd() + if not dir in sys.path: + sys.path.insert(0, dir) + # check the IDLE settings configuration (but command line overrides) + edit_start = idleConf.GetOption('main', 'General', + 'editor-on-startup', type='bool') + enable_edit = enable_edit or edit_start + enable_shell = enable_shell or not enable_edit + + # start editor and/or shell windows: + root = Tk(className="Idle") + root.withdraw() + + # set application icon + icondir = os.path.join(os.path.dirname(__file__), 'Icons') + if system() == 'Windows': + iconfile = os.path.join(icondir, 'idle.ico') + root.wm_iconbitmap(default=iconfile) + elif TkVersion >= 8.5: + ext = '.png' if TkVersion >= 8.6 else '.gif' + iconfiles = [os.path.join(icondir, 'idle_%d%s' % (size, ext)) + for size in (16, 32, 48)] + icons = [PhotoImage(file=iconfile) for iconfile in iconfiles] + root.tk.call('wm', 'iconphoto', str(root), "-default", *icons) + + fixwordbreaks(root) + fix_x11_paste(root) + flist = PyShellFileList(root) + macosxSupport.setupApp(root, flist) + + if macosxSupport.isAquaTk(): + # There are some screwed up <2> class bindings for text + # widgets defined in Tk which we need to do away with. + # See issue #24801. + root.unbind_class('Text', '<B2>') + root.unbind_class('Text', '<B2-Motion>') + root.unbind_class('Text', '<<PasteSelection>>') + + if enable_edit: + if not (cmd or script): + for filename in args[:]: + if flist.open(filename) is None: + # filename is a directory actually, disconsider it + args.remove(filename) + if not args: + flist.new() + + if enable_shell: + shell = flist.open_shell() + if not shell: + return # couldn't open shell + if macosxSupport.isAquaTk() and flist.dict: + # On OSX: when the user has double-clicked on a file that causes + # IDLE to be launched the shell window will open just in front of + # the file she wants to see. Lower the interpreter window when + # there are open files. + shell.top.lower() + else: + shell = flist.pyshell + + # Handle remaining options. If any of these are set, enable_shell + # was set also, so shell must be true to reach here. + if debug: + shell.open_debugger() + if startup: + filename = os.environ.get("IDLESTARTUP") or \ + os.environ.get("PYTHONSTARTUP") + if filename and os.path.isfile(filename): + shell.interp.execfile(filename) + if cmd or script: + shell.interp.runcommand("""if 1: + import sys as _sys + _sys.argv = %r + del _sys + \n""" % (sys.argv,)) + if cmd: + shell.interp.execsource(cmd) + elif script: + shell.interp.prepend_syspath(script) + shell.interp.execfile(script) + elif shell: + # If there is a shell window and no cmd or script in progress, + # check for problematic OS X Tk versions and print a warning + # message in the IDLE shell window; this is less intrusive + # than always opening a separate window. + tkversionwarning = macosxSupport.tkVersionWarning(root) + if tkversionwarning: + shell.interp.runcommand("print('%s')" % tkversionwarning) + + while flist.inversedict: # keep IDLE running while files are open. + root.mainloop() + root.destroy() + capture_warnings(False) + +if __name__ == "__main__": + sys.modules['PyShell'] = sys.modules['__main__'] + main() + +capture_warnings(False) # Make sure turned off; see issue 18081 diff --git a/lib/python2.7/idlelib/README.txt b/lib/python2.7/idlelib/README.txt new file mode 100644 index 0000000..bc169c8 --- /dev/null +++ b/lib/python2.7/idlelib/README.txt @@ -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 diff --git a/lib/python2.7/idlelib/RemoteDebugger.py b/lib/python2.7/idlelib/RemoteDebugger.py new file mode 100644 index 0000000..8c71a21 --- /dev/null +++ b/lib/python2.7/idlelib/RemoteDebugger.py @@ -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' diff --git a/lib/python2.7/idlelib/RemoteObjectBrowser.py b/lib/python2.7/idlelib/RemoteObjectBrowser.py new file mode 100644 index 0000000..43e2c68 --- /dev/null +++ b/lib/python2.7/idlelib/RemoteObjectBrowser.py @@ -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] diff --git a/lib/python2.7/idlelib/ReplaceDialog.py b/lib/python2.7/idlelib/ReplaceDialog.py new file mode 100644 index 0000000..66a871a --- /dev/null +++ b/lib/python2.7/idlelib/ReplaceDialog.py @@ -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) diff --git a/lib/python2.7/idlelib/RstripExtension.py b/lib/python2.7/idlelib/RstripExtension.py new file mode 100644 index 0000000..2ce3c7e --- /dev/null +++ b/lib/python2.7/idlelib/RstripExtension.py @@ -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) diff --git a/lib/python2.7/idlelib/ScriptBinding.py b/lib/python2.7/idlelib/ScriptBinding.py new file mode 100644 index 0000000..0309a8a --- /dev/null +++ b/lib/python2.7/idlelib/ScriptBinding.py @@ -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() diff --git a/lib/python2.7/idlelib/ScrolledList.py b/lib/python2.7/idlelib/ScrolledList.py new file mode 100644 index 0000000..fd9f0ff --- /dev/null +++ b/lib/python2.7/idlelib/ScrolledList.py @@ -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) diff --git a/lib/python2.7/idlelib/SearchDialog.py b/lib/python2.7/idlelib/SearchDialog.py new file mode 100644 index 0000000..043168a --- /dev/null +++ b/lib/python2.7/idlelib/SearchDialog.py @@ -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) diff --git a/lib/python2.7/idlelib/SearchDialogBase.py b/lib/python2.7/idlelib/SearchDialogBase.py new file mode 100644 index 0000000..651e7f4 --- /dev/null +++ b/lib/python2.7/idlelib/SearchDialogBase.py @@ -0,0 +1,184 @@ +'''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() + 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.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) diff --git a/lib/python2.7/idlelib/SearchEngine.py b/lib/python2.7/idlelib/SearchEngine.py new file mode 100644 index 0000000..ad43130 --- /dev/null +++ b/lib/python2.7/idlelib/SearchEngine.py @@ -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) diff --git a/lib/python2.7/idlelib/StackViewer.py b/lib/python2.7/idlelib/StackViewer.py new file mode 100644 index 0000000..555a08c --- /dev/null +++ b/lib/python2.7/idlelib/StackViewer.py @@ -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) diff --git a/lib/python2.7/idlelib/TODO.txt b/lib/python2.7/idlelib/TODO.txt new file mode 100644 index 0000000..e2f1ac0 --- /dev/null +++ b/lib/python2.7/idlelib/TODO.txt @@ -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 diff --git a/lib/python2.7/idlelib/ToolTip.py b/lib/python2.7/idlelib/ToolTip.py new file mode 100644 index 0000000..11136c4 --- /dev/null +++ b/lib/python2.7/idlelib/ToolTip.py @@ -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) diff --git a/lib/python2.7/idlelib/TreeWidget.py b/lib/python2.7/idlelib/TreeWidget.py new file mode 100644 index 0000000..9d9d4d9 --- /dev/null +++ b/lib/python2.7/idlelib/TreeWidget.py @@ -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) diff --git a/lib/python2.7/idlelib/UndoDelegator.py b/lib/python2.7/idlelib/UndoDelegator.py new file mode 100644 index 0000000..cdeacea --- /dev/null +++ b/lib/python2.7/idlelib/UndoDelegator.py @@ -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) diff --git a/lib/python2.7/idlelib/WidgetRedirector.py b/lib/python2.7/idlelib/WidgetRedirector.py new file mode 100644 index 0000000..54431f7 --- /dev/null +++ b/lib/python2.7/idlelib/WidgetRedirector.py @@ -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) diff --git a/lib/python2.7/idlelib/WindowList.py b/lib/python2.7/idlelib/WindowList.py new file mode 100644 index 0000000..658502b --- /dev/null +++ b/lib/python2.7/idlelib/WindowList.py @@ -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 diff --git a/lib/python2.7/idlelib/ZoomHeight.py b/lib/python2.7/idlelib/ZoomHeight.py new file mode 100644 index 0000000..a5d679e --- /dev/null +++ b/lib/python2.7/idlelib/ZoomHeight.py @@ -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) diff --git a/lib/python2.7/idlelib/__init__.py b/lib/python2.7/idlelib/__init__.py new file mode 100644 index 0000000..32b7eac --- /dev/null +++ b/lib/python2.7/idlelib/__init__.py @@ -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. +""" diff --git a/lib/python2.7/idlelib/aboutDialog.py b/lib/python2.7/idlelib/aboutDialog.py new file mode 100644 index 0000000..c9adc08 --- /dev/null +++ b/lib/python2.7/idlelib/aboutDialog.py @@ -0,0 +1,150 @@ +"""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.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) diff --git a/lib/python2.7/idlelib/config-extensions.def b/lib/python2.7/idlelib/config-extensions.def new file mode 100644 index 0000000..a24b8c9 --- /dev/null +++ b/lib/python2.7/idlelib/config-extensions.def @@ -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> diff --git a/lib/python2.7/idlelib/config-highlight.def b/lib/python2.7/idlelib/config-highlight.def new file mode 100644 index 0000000..4146e28 --- /dev/null +++ b/lib/python2.7/idlelib/config-highlight.def @@ -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 diff --git a/lib/python2.7/idlelib/config-keys.def b/lib/python2.7/idlelib/config-keys.def new file mode 100644 index 0000000..3bfcb69 --- /dev/null +++ b/lib/python2.7/idlelib/config-keys.def @@ -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> + diff --git a/lib/python2.7/idlelib/config-main.def b/lib/python2.7/idlelib/config-main.def new file mode 100644 index 0000000..f241199 --- /dev/null +++ b/lib/python2.7/idlelib/config-main.def @@ -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] diff --git a/lib/python2.7/idlelib/configDialog.py b/lib/python2.7/idlelib/configDialog.py new file mode 100644 index 0000000..d53f5ff --- /dev/null +++ b/lib/python2.7/idlelib/configDialog.py @@ -0,0 +1,1455 @@ +"""IDLE Configuration Dialog: support user customization of IDLE by GUI + +Customize font faces, sizes, and colorization attributes. Set indentation +defaults. Customize keybindings. Colorization and keybindings can be +saved as user defined sets. Select startup options including shell/editor +and default window size. Define additional help sources. + +Note that tab width in IDLE is currently fixed at eight due to Tk issues. +Refer to comments in EditorWindow autoindent code for details. + +""" +from Tkinter import * +import tkMessageBox, tkColorChooser, tkFont + +from idlelib.configHandler import idleConf +from idlelib.dynOptionMenuWidget import DynOptionMenu +from idlelib.keybindingDialog import GetKeysDialog +from idlelib.configSectionNameDialog import GetCfgSectionNameDialog +from idlelib.configHelpSourceEdit import GetHelpSourceDialog +from idlelib.tabbedpages import TabbedPageSet +from idlelib.textView import view_text +from idlelib import macosxSupport + +class ConfigDialog(Toplevel): + + def __init__(self, parent, title='', _htest=False, _utest=False): + """ + _htest - bool, change box location when running htest + _utest - bool, don't wait_window when running unittest + """ + Toplevel.__init__(self, parent) + self.parent = parent + if _htest: + parent.instance_dict = {} + self.wm_withdraw() + + self.configure(borderwidth=5) + self.title(title or 'IDLE Preferences') + self.geometry( + "+%d+%d" % (parent.winfo_rootx() + 20, + parent.winfo_rooty() + (30 if not _htest else 150))) + #Theme Elements. Each theme element key is its display name. + #The first value of the tuple is the sample area tag name. + #The second value is the display name list sort index. + self.themeElements={ + 'Normal Text': ('normal', '00'), + 'Python Keywords': ('keyword', '01'), + 'Python Definitions': ('definition', '02'), + 'Python Builtins': ('builtin', '03'), + 'Python Comments': ('comment', '04'), + 'Python Strings': ('string', '05'), + 'Selected Text': ('hilite', '06'), + 'Found Text': ('hit', '07'), + 'Cursor': ('cursor', '08'), + 'Editor Breakpoint': ('break', '09'), + 'Shell Normal Text': ('console', '10'), + 'Shell Error Text': ('error', '11'), + 'Shell Stdout Text': ('stdout', '12'), + 'Shell Stderr Text': ('stderr', '13'), + } + self.ResetChangedItems() #load initial values in changed items dict + self.CreateWidgets() + self.resizable(height=FALSE, width=FALSE) + self.transient(parent) + self.grab_set() + self.protocol("WM_DELETE_WINDOW", self.Cancel) + self.tabPages.focus_set() + #key bindings for this dialog + #self.bind('<Escape>', self.Cancel) #dismiss dialog, no save + #self.bind('<Alt-a>', self.Apply) #apply changes, save + #self.bind('<F1>', self.Help) #context help + self.LoadConfigs() + self.AttachVarCallbacks() #avoid callbacks during LoadConfigs + + if not _utest: + self.wm_deiconify() + self.wait_window() + + def CreateWidgets(self): + self.tabPages = TabbedPageSet(self, + page_names=['Fonts/Tabs', 'Highlighting', 'Keys', 'General', + 'Extensions']) + self.tabPages.pack(side=TOP, expand=TRUE, fill=BOTH) + self.CreatePageFontTab() + self.CreatePageHighlight() + self.CreatePageKeys() + self.CreatePageGeneral() + self.CreatePageExtensions() + self.create_action_buttons().pack(side=BOTTOM) + + def create_action_buttons(self): + if macosxSupport.isAquaTk(): + # Changing the default padding on OSX results in unreadable + # text in the buttons + paddingArgs = {} + else: + paddingArgs = {'padx':6, 'pady':3} + outer = Frame(self, pady=2) + buttons = Frame(outer, pady=2) + for txt, cmd in ( + ('Ok', self.Ok), + ('Apply', self.Apply), + ('Cancel', self.Cancel), + ('Help', self.Help)): + Button(buttons, text=txt, command=cmd, takefocus=FALSE, + **paddingArgs).pack(side=LEFT, padx=5) + # add space above buttons + Frame(outer, height=2, borderwidth=0).pack(side=TOP) + buttons.pack(side=BOTTOM) + return outer + + def CreatePageFontTab(self): + parent = self.parent + self.fontSize = StringVar(parent) + self.fontBold = BooleanVar(parent) + self.fontName = StringVar(parent) + self.spaceNum = IntVar(parent) + self.editFont = tkFont.Font(parent, ('courier', 10, 'normal')) + + ##widget creation + #body frame + frame = self.tabPages.pages['Fonts/Tabs'].frame + #body section frames + frameFont = LabelFrame( + frame, borderwidth=2, relief=GROOVE, text=' Base Editor Font ') + frameIndent = LabelFrame( + frame, borderwidth=2, relief=GROOVE, text=' Indentation Width ') + #frameFont + frameFontName = Frame(frameFont) + frameFontParam = Frame(frameFont) + labelFontNameTitle = Label( + frameFontName, justify=LEFT, text='Font Face :') + self.listFontName = Listbox( + frameFontName, height=5, takefocus=FALSE, exportselection=FALSE) + self.listFontName.bind( + '<ButtonRelease-1>', self.OnListFontButtonRelease) + scrollFont = Scrollbar(frameFontName) + scrollFont.config(command=self.listFontName.yview) + self.listFontName.config(yscrollcommand=scrollFont.set) + labelFontSizeTitle = Label(frameFontParam, text='Size :') + self.optMenuFontSize = DynOptionMenu( + frameFontParam, self.fontSize, None, command=self.SetFontSample) + checkFontBold = Checkbutton( + frameFontParam, variable=self.fontBold, onvalue=1, + offvalue=0, text='Bold', command=self.SetFontSample) + frameFontSample = Frame(frameFont, relief=SOLID, borderwidth=1) + self.labelFontSample = Label( + frameFontSample, justify=LEFT, font=self.editFont, + text='AaBbCcDdEe\nFfGgHhIiJjK\n1234567890\n#:+=(){}[]') + #frameIndent + frameIndentSize = Frame(frameIndent) + labelSpaceNumTitle = Label( + frameIndentSize, justify=LEFT, + text='Python Standard: 4 Spaces!') + self.scaleSpaceNum = Scale( + frameIndentSize, variable=self.spaceNum, + orient='horizontal', tickinterval=2, from_=2, to=16) + + #widget packing + #body + frameFont.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH) + frameIndent.pack(side=LEFT, padx=5, pady=5, fill=Y) + #frameFont + frameFontName.pack(side=TOP, padx=5, pady=5, fill=X) + frameFontParam.pack(side=TOP, padx=5, pady=5, fill=X) + labelFontNameTitle.pack(side=TOP, anchor=W) + self.listFontName.pack(side=LEFT, expand=TRUE, fill=X) + scrollFont.pack(side=LEFT, fill=Y) + labelFontSizeTitle.pack(side=LEFT, anchor=W) + self.optMenuFontSize.pack(side=LEFT, anchor=W) + checkFontBold.pack(side=LEFT, anchor=W, padx=20) + frameFontSample.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH) + self.labelFontSample.pack(expand=TRUE, fill=BOTH) + #frameIndent + frameIndentSize.pack(side=TOP, fill=X) + labelSpaceNumTitle.pack(side=TOP, anchor=W, padx=5) + self.scaleSpaceNum.pack(side=TOP, padx=5, fill=X) + return frame + + def CreatePageHighlight(self): + parent = self.parent + self.builtinTheme = StringVar(parent) + self.customTheme = StringVar(parent) + self.fgHilite = BooleanVar(parent) + self.colour = StringVar(parent) + self.fontName = StringVar(parent) + self.themeIsBuiltin = BooleanVar(parent) + self.highlightTarget = StringVar(parent) + + ##widget creation + #body frame + frame = self.tabPages.pages['Highlighting'].frame + #body section frames + frameCustom = LabelFrame(frame, borderwidth=2, relief=GROOVE, + text=' Custom Highlighting ') + frameTheme = LabelFrame(frame, borderwidth=2, relief=GROOVE, + text=' Highlighting Theme ') + #frameCustom + self.textHighlightSample=Text( + frameCustom, relief=SOLID, borderwidth=1, + font=('courier', 12, ''), cursor='hand2', width=21, height=11, + takefocus=FALSE, highlightthickness=0, wrap=NONE) + text=self.textHighlightSample + text.bind('<Double-Button-1>', lambda e: 'break') + text.bind('<B1-Motion>', lambda e: 'break') + textAndTags=( + ('#you can click here', 'comment'), ('\n', 'normal'), + ('#to choose items', 'comment'), ('\n', 'normal'), + ('def', 'keyword'), (' ', 'normal'), + ('func', 'definition'), ('(param):\n ', 'normal'), + ('"""string"""', 'string'), ('\n var0 = ', 'normal'), + ("'string'", 'string'), ('\n var1 = ', 'normal'), + ("'selected'", 'hilite'), ('\n var2 = ', 'normal'), + ("'found'", 'hit'), ('\n var3 = ', 'normal'), + ('list', 'builtin'), ('(', 'normal'), + ('None', 'builtin'), (')\n', 'normal'), + (' breakpoint("line")', 'break'), ('\n\n', 'normal'), + (' error ', 'error'), (' ', 'normal'), + ('cursor |', 'cursor'), ('\n ', 'normal'), + ('shell', 'console'), (' ', 'normal'), + ('stdout', 'stdout'), (' ', 'normal'), + ('stderr', 'stderr'), ('\n', 'normal')) + for txTa in textAndTags: + text.insert(END, txTa[0], txTa[1]) + for element in self.themeElements: + def tem(event, elem=element): + event.widget.winfo_toplevel().highlightTarget.set(elem) + text.tag_bind( + self.themeElements[element][0], '<ButtonPress-1>', tem) + text.config(state=DISABLED) + self.frameColourSet = Frame(frameCustom, relief=SOLID, borderwidth=1) + frameFgBg = Frame(frameCustom) + buttonSetColour = Button( + self.frameColourSet, text='Choose Colour for :', + command=self.GetColour, highlightthickness=0) + self.optMenuHighlightTarget = DynOptionMenu( + self.frameColourSet, self.highlightTarget, None, + highlightthickness=0) #, command=self.SetHighlightTargetBinding + self.radioFg = Radiobutton( + frameFgBg, variable=self.fgHilite, value=1, + text='Foreground', command=self.SetColourSampleBinding) + self.radioBg=Radiobutton( + frameFgBg, variable=self.fgHilite, value=0, + text='Background', command=self.SetColourSampleBinding) + self.fgHilite.set(1) + buttonSaveCustomTheme = Button( + frameCustom, text='Save as New Custom Theme', + command=self.SaveAsNewTheme) + #frameTheme + labelTypeTitle = Label(frameTheme, text='Select : ') + self.radioThemeBuiltin = Radiobutton( + frameTheme, variable=self.themeIsBuiltin, value=1, + command=self.SetThemeType, text='a Built-in Theme') + self.radioThemeCustom = Radiobutton( + frameTheme, variable=self.themeIsBuiltin, value=0, + command=self.SetThemeType, text='a Custom Theme') + self.optMenuThemeBuiltin = DynOptionMenu( + frameTheme, self.builtinTheme, None, command=None) + self.optMenuThemeCustom=DynOptionMenu( + frameTheme, self.customTheme, None, command=None) + self.buttonDeleteCustomTheme=Button( + frameTheme, text='Delete Custom Theme', + command=self.DeleteCustomTheme) + self.new_custom_theme = Label(frameTheme, bd=2) + + ##widget packing + #body + frameCustom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH) + frameTheme.pack(side=LEFT, padx=5, pady=5, fill=Y) + #frameCustom + self.frameColourSet.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=X) + frameFgBg.pack(side=TOP, padx=5, pady=0) + self.textHighlightSample.pack( + side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH) + buttonSetColour.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4) + self.optMenuHighlightTarget.pack( + side=TOP, expand=TRUE, fill=X, padx=8, pady=3) + self.radioFg.pack(side=LEFT, anchor=E) + self.radioBg.pack(side=RIGHT, anchor=W) + buttonSaveCustomTheme.pack(side=BOTTOM, fill=X, padx=5, pady=5) + #frameTheme + labelTypeTitle.pack(side=TOP, anchor=W, padx=5, pady=5) + self.radioThemeBuiltin.pack(side=TOP, anchor=W, padx=5) + self.radioThemeCustom.pack(side=TOP, anchor=W, padx=5, pady=2) + self.optMenuThemeBuiltin.pack(side=TOP, fill=X, padx=5, pady=5) + self.optMenuThemeCustom.pack(side=TOP, fill=X, anchor=W, padx=5, pady=5) + self.buttonDeleteCustomTheme.pack(side=TOP, fill=X, padx=5, pady=5) + self.new_custom_theme.pack(side=TOP, fill=X, pady=5) + return frame + + def CreatePageKeys(self): + parent = self.parent + self.bindingTarget = StringVar(parent) + self.builtinKeys = StringVar(parent) + self.customKeys = StringVar(parent) + self.keysAreBuiltin = BooleanVar(parent) + self.keyBinding = StringVar(parent) + + ##widget creation + #body frame + frame = self.tabPages.pages['Keys'].frame + #body section frames + frameCustom = LabelFrame( + frame, borderwidth=2, relief=GROOVE, + text=' Custom Key Bindings ') + frameKeySets = LabelFrame( + frame, borderwidth=2, relief=GROOVE, text=' Key Set ') + #frameCustom + frameTarget = Frame(frameCustom) + labelTargetTitle = Label(frameTarget, text='Action - Key(s)') + scrollTargetY = Scrollbar(frameTarget) + scrollTargetX = Scrollbar(frameTarget, orient=HORIZONTAL) + self.listBindings = Listbox( + frameTarget, takefocus=FALSE, exportselection=FALSE) + self.listBindings.bind('<ButtonRelease-1>', self.KeyBindingSelected) + scrollTargetY.config(command=self.listBindings.yview) + scrollTargetX.config(command=self.listBindings.xview) + self.listBindings.config(yscrollcommand=scrollTargetY.set) + self.listBindings.config(xscrollcommand=scrollTargetX.set) + self.buttonNewKeys = Button( + frameCustom, text='Get New Keys for Selection', + command=self.GetNewKeys, state=DISABLED) + #frameKeySets + frames = [Frame(frameKeySets, padx=2, pady=2, borderwidth=0) + for i in range(2)] + self.radioKeysBuiltin = Radiobutton( + frames[0], variable=self.keysAreBuiltin, value=1, + command=self.SetKeysType, text='Use a Built-in Key Set') + self.radioKeysCustom = Radiobutton( + frames[0], variable=self.keysAreBuiltin, value=0, + command=self.SetKeysType, text='Use a Custom Key Set') + self.optMenuKeysBuiltin = DynOptionMenu( + frames[0], self.builtinKeys, None, command=None) + self.optMenuKeysCustom = DynOptionMenu( + frames[0], self.customKeys, None, command=None) + self.buttonDeleteCustomKeys = Button( + frames[1], text='Delete Custom Key Set', + command=self.DeleteCustomKeys) + buttonSaveCustomKeys = Button( + frames[1], text='Save as New Custom Key Set', + command=self.SaveAsNewKeySet) + + ##widget packing + #body + frameCustom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH) + frameKeySets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH) + #frameCustom + self.buttonNewKeys.pack(side=BOTTOM, fill=X, padx=5, pady=5) + frameTarget.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH) + #frame target + frameTarget.columnconfigure(0, weight=1) + frameTarget.rowconfigure(1, weight=1) + labelTargetTitle.grid(row=0, column=0, columnspan=2, sticky=W) + self.listBindings.grid(row=1, column=0, sticky=NSEW) + scrollTargetY.grid(row=1, column=1, sticky=NS) + scrollTargetX.grid(row=2, column=0, sticky=EW) + #frameKeySets + self.radioKeysBuiltin.grid(row=0, column=0, sticky=W+NS) + self.radioKeysCustom.grid(row=1, column=0, sticky=W+NS) + self.optMenuKeysBuiltin.grid(row=0, column=1, sticky=NSEW) + self.optMenuKeysCustom.grid(row=1, column=1, sticky=NSEW) + self.buttonDeleteCustomKeys.pack(side=LEFT, fill=X, expand=True, padx=2) + buttonSaveCustomKeys.pack(side=LEFT, fill=X, expand=True, padx=2) + frames[0].pack(side=TOP, fill=BOTH, expand=True) + frames[1].pack(side=TOP, fill=X, expand=True, pady=2) + return frame + + def CreatePageGeneral(self): + parent = self.parent + self.winWidth = StringVar(parent) + self.winHeight = StringVar(parent) + self.startupEdit = IntVar(parent) + self.autoSave = IntVar(parent) + self.encoding = StringVar(parent) + self.userHelpBrowser = BooleanVar(parent) + self.helpBrowser = StringVar(parent) + + #widget creation + #body + frame = self.tabPages.pages['General'].frame + #body section frames + frameRun = LabelFrame(frame, borderwidth=2, relief=GROOVE, + text=' Startup Preferences ') + frameSave = LabelFrame(frame, borderwidth=2, relief=GROOVE, + text=' Autosave Preferences ') + frameWinSize = Frame(frame, borderwidth=2, relief=GROOVE) + frameEncoding = Frame(frame, borderwidth=2, relief=GROOVE) + frameHelp = LabelFrame(frame, borderwidth=2, relief=GROOVE, + text=' Additional Help Sources ') + #frameRun + labelRunChoiceTitle = Label(frameRun, text='At Startup') + radioStartupEdit = Radiobutton( + frameRun, variable=self.startupEdit, value=1, + command=self.SetKeysType, text="Open Edit Window") + radioStartupShell = Radiobutton( + frameRun, variable=self.startupEdit, value=0, + command=self.SetKeysType, text='Open Shell Window') + #frameSave + labelRunSaveTitle = Label(frameSave, text='At Start of Run (F5) ') + radioSaveAsk = Radiobutton( + frameSave, variable=self.autoSave, value=0, + command=self.SetKeysType, text="Prompt to Save") + radioSaveAuto = Radiobutton( + frameSave, variable=self.autoSave, value=1, + command=self.SetKeysType, text='No Prompt') + #frameWinSize + labelWinSizeTitle = Label( + frameWinSize, text='Initial Window Size (in characters)') + labelWinWidthTitle = Label(frameWinSize, text='Width') + entryWinWidth = Entry( + frameWinSize, textvariable=self.winWidth, width=3) + labelWinHeightTitle = Label(frameWinSize, text='Height') + entryWinHeight = Entry( + frameWinSize, textvariable=self.winHeight, width=3) + #frameEncoding + labelEncodingTitle = Label( + frameEncoding, text="Default Source Encoding") + radioEncLocale = Radiobutton( + frameEncoding, variable=self.encoding, + value="locale", text="Locale-defined") + radioEncUTF8 = Radiobutton( + frameEncoding, variable=self.encoding, + value="utf-8", text="UTF-8") + radioEncNone = Radiobutton( + frameEncoding, variable=self.encoding, + value="none", text="None") + #frameHelp + frameHelpList = Frame(frameHelp) + frameHelpListButtons = Frame(frameHelpList) + scrollHelpList = Scrollbar(frameHelpList) + self.listHelp = Listbox( + frameHelpList, height=5, takefocus=FALSE, + exportselection=FALSE) + scrollHelpList.config(command=self.listHelp.yview) + self.listHelp.config(yscrollcommand=scrollHelpList.set) + self.listHelp.bind('<ButtonRelease-1>', self.HelpSourceSelected) + self.buttonHelpListEdit = Button( + frameHelpListButtons, text='Edit', state=DISABLED, + width=8, command=self.HelpListItemEdit) + self.buttonHelpListAdd = Button( + frameHelpListButtons, text='Add', + width=8, command=self.HelpListItemAdd) + self.buttonHelpListRemove = Button( + frameHelpListButtons, text='Remove', state=DISABLED, + width=8, command=self.HelpListItemRemove) + + #widget packing + #body + frameRun.pack(side=TOP, padx=5, pady=5, fill=X) + frameSave.pack(side=TOP, padx=5, pady=5, fill=X) + frameWinSize.pack(side=TOP, padx=5, pady=5, fill=X) + frameEncoding.pack(side=TOP, padx=5, pady=5, fill=X) + frameHelp.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH) + #frameRun + labelRunChoiceTitle.pack(side=LEFT, anchor=W, padx=5, pady=5) + radioStartupShell.pack(side=RIGHT, anchor=W, padx=5, pady=5) + radioStartupEdit.pack(side=RIGHT, anchor=W, padx=5, pady=5) + #frameSave + labelRunSaveTitle.pack(side=LEFT, anchor=W, padx=5, pady=5) + radioSaveAuto.pack(side=RIGHT, anchor=W, padx=5, pady=5) + radioSaveAsk.pack(side=RIGHT, anchor=W, padx=5, pady=5) + #frameWinSize + labelWinSizeTitle.pack(side=LEFT, anchor=W, padx=5, pady=5) + entryWinHeight.pack(side=RIGHT, anchor=E, padx=10, pady=5) + labelWinHeightTitle.pack(side=RIGHT, anchor=E, pady=5) + entryWinWidth.pack(side=RIGHT, anchor=E, padx=10, pady=5) + labelWinWidthTitle.pack(side=RIGHT, anchor=E, pady=5) + #frameEncoding + labelEncodingTitle.pack(side=LEFT, anchor=W, padx=5, pady=5) + radioEncNone.pack(side=RIGHT, anchor=E, pady=5) + radioEncUTF8.pack(side=RIGHT, anchor=E, pady=5) + radioEncLocale.pack(side=RIGHT, anchor=E, pady=5) + #frameHelp + frameHelpListButtons.pack(side=RIGHT, padx=5, pady=5, fill=Y) + frameHelpList.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH) + scrollHelpList.pack(side=RIGHT, anchor=W, fill=Y) + self.listHelp.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH) + self.buttonHelpListEdit.pack(side=TOP, anchor=W, pady=5) + self.buttonHelpListAdd.pack(side=TOP, anchor=W) + self.buttonHelpListRemove.pack(side=TOP, anchor=W, pady=5) + return frame + + def AttachVarCallbacks(self): + self.fontSize.trace_variable('w', self.VarChanged_font) + self.fontName.trace_variable('w', self.VarChanged_font) + self.fontBold.trace_variable('w', self.VarChanged_font) + self.spaceNum.trace_variable('w', self.VarChanged_spaceNum) + self.colour.trace_variable('w', self.VarChanged_colour) + self.builtinTheme.trace_variable('w', self.VarChanged_builtinTheme) + self.customTheme.trace_variable('w', self.VarChanged_customTheme) + self.themeIsBuiltin.trace_variable('w', self.VarChanged_themeIsBuiltin) + self.highlightTarget.trace_variable('w', self.VarChanged_highlightTarget) + self.keyBinding.trace_variable('w', self.VarChanged_keyBinding) + self.builtinKeys.trace_variable('w', self.VarChanged_builtinKeys) + self.customKeys.trace_variable('w', self.VarChanged_customKeys) + self.keysAreBuiltin.trace_variable('w', self.VarChanged_keysAreBuiltin) + self.winWidth.trace_variable('w', self.VarChanged_winWidth) + self.winHeight.trace_variable('w', self.VarChanged_winHeight) + self.startupEdit.trace_variable('w', self.VarChanged_startupEdit) + self.autoSave.trace_variable('w', self.VarChanged_autoSave) + self.encoding.trace_variable('w', self.VarChanged_encoding) + + def remove_var_callbacks(self): + for var in ( + self.fontSize, self.fontName, self.fontBold, + self.spaceNum, self.colour, self.builtinTheme, + self.customTheme, self.themeIsBuiltin, self.highlightTarget, + self.keyBinding, self.builtinKeys, self.customKeys, + self.keysAreBuiltin, self.winWidth, self.winHeight, + self.startupEdit, self.autoSave, self.encoding,): + var.trace_vdelete('w', var.trace_vinfo()[0][1]) + + def VarChanged_font(self, *params): + '''When one font attribute changes, save them all, as they are + not independent from each other. In particular, when we are + overriding the default font, we need to write out everything. + ''' + value = self.fontName.get() + self.AddChangedItem('main', 'EditorWindow', 'font', value) + value = self.fontSize.get() + self.AddChangedItem('main', 'EditorWindow', 'font-size', value) + value = self.fontBold.get() + self.AddChangedItem('main', 'EditorWindow', 'font-bold', value) + + def VarChanged_spaceNum(self, *params): + value = self.spaceNum.get() + self.AddChangedItem('main', 'Indent', 'num-spaces', value) + + def VarChanged_colour(self, *params): + self.OnNewColourSet() + + def VarChanged_builtinTheme(self, *params): + value = self.builtinTheme.get() + if value == 'IDLE Dark': + if idleConf.GetOption('main', 'Theme', 'name') != 'IDLE New': + self.AddChangedItem('main', 'Theme', 'name', 'IDLE Classic') + self.AddChangedItem('main', 'Theme', 'name2', value) + self.new_custom_theme.config(text='New theme, see Help', + fg='#500000') + else: + self.AddChangedItem('main', 'Theme', 'name', value) + self.AddChangedItem('main', 'Theme', 'name2', '') + self.new_custom_theme.config(text='', fg='black') + self.PaintThemeSample() + + def VarChanged_customTheme(self, *params): + value = self.customTheme.get() + if value != '- no custom themes -': + self.AddChangedItem('main', 'Theme', 'name', value) + self.PaintThemeSample() + + def VarChanged_themeIsBuiltin(self, *params): + value = self.themeIsBuiltin.get() + self.AddChangedItem('main', 'Theme', 'default', value) + if value: + self.VarChanged_builtinTheme() + else: + self.VarChanged_customTheme() + + def VarChanged_highlightTarget(self, *params): + self.SetHighlightTarget() + + def VarChanged_keyBinding(self, *params): + value = self.keyBinding.get() + keySet = self.customKeys.get() + event = self.listBindings.get(ANCHOR).split()[0] + if idleConf.IsCoreBinding(event): + #this is a core keybinding + self.AddChangedItem('keys', keySet, event, value) + else: #this is an extension key binding + extName = idleConf.GetExtnNameForEvent(event) + extKeybindSection = extName + '_cfgBindings' + self.AddChangedItem('extensions', extKeybindSection, event, value) + + def VarChanged_builtinKeys(self, *params): + value = self.builtinKeys.get() + self.AddChangedItem('main', 'Keys', 'name', value) + self.LoadKeysList(value) + + def VarChanged_customKeys(self, *params): + value = self.customKeys.get() + if value != '- no custom keys -': + self.AddChangedItem('main', 'Keys', 'name', value) + self.LoadKeysList(value) + + def VarChanged_keysAreBuiltin(self, *params): + value = self.keysAreBuiltin.get() + self.AddChangedItem('main', 'Keys', 'default', value) + if value: + self.VarChanged_builtinKeys() + else: + self.VarChanged_customKeys() + + def VarChanged_winWidth(self, *params): + value = self.winWidth.get() + self.AddChangedItem('main', 'EditorWindow', 'width', value) + + def VarChanged_winHeight(self, *params): + value = self.winHeight.get() + self.AddChangedItem('main', 'EditorWindow', 'height', value) + + def VarChanged_startupEdit(self, *params): + value = self.startupEdit.get() + self.AddChangedItem('main', 'General', 'editor-on-startup', value) + + def VarChanged_autoSave(self, *params): + value = self.autoSave.get() + self.AddChangedItem('main', 'General', 'autosave', value) + + def VarChanged_encoding(self, *params): + value = self.encoding.get() + self.AddChangedItem('main', 'EditorWindow', 'encoding', value) + + def ResetChangedItems(self): + #When any config item is changed in this dialog, an entry + #should be made in the relevant section (config type) of this + #dictionary. The key should be the config file section name and the + #value a dictionary, whose key:value pairs are item=value pairs for + #that config file section. + self.changedItems = {'main':{}, 'highlight':{}, 'keys':{}, + 'extensions':{}} + + def AddChangedItem(self, typ, section, item, value): + value = str(value) #make sure we use a string + if section not in self.changedItems[typ]: + self.changedItems[typ][section] = {} + self.changedItems[typ][section][item] = value + + def GetDefaultItems(self): + dItems={'main':{}, 'highlight':{}, 'keys':{}, 'extensions':{}} + for configType in dItems: + sections = idleConf.GetSectionList('default', configType) + for section in sections: + dItems[configType][section] = {} + options = idleConf.defaultCfg[configType].GetOptionList(section) + for option in options: + dItems[configType][section][option] = ( + idleConf.defaultCfg[configType].Get(section, option)) + return dItems + + def SetThemeType(self): + if self.themeIsBuiltin.get(): + self.optMenuThemeBuiltin.config(state=NORMAL) + self.optMenuThemeCustom.config(state=DISABLED) + self.buttonDeleteCustomTheme.config(state=DISABLED) + else: + self.optMenuThemeBuiltin.config(state=DISABLED) + self.radioThemeCustom.config(state=NORMAL) + self.optMenuThemeCustom.config(state=NORMAL) + self.buttonDeleteCustomTheme.config(state=NORMAL) + + def SetKeysType(self): + if self.keysAreBuiltin.get(): + self.optMenuKeysBuiltin.config(state=NORMAL) + self.optMenuKeysCustom.config(state=DISABLED) + self.buttonDeleteCustomKeys.config(state=DISABLED) + else: + self.optMenuKeysBuiltin.config(state=DISABLED) + self.radioKeysCustom.config(state=NORMAL) + self.optMenuKeysCustom.config(state=NORMAL) + self.buttonDeleteCustomKeys.config(state=NORMAL) + + def GetNewKeys(self): + listIndex = self.listBindings.index(ANCHOR) + binding = self.listBindings.get(listIndex) + bindName = binding.split()[0] #first part, up to first space + if self.keysAreBuiltin.get(): + currentKeySetName = self.builtinKeys.get() + else: + currentKeySetName = self.customKeys.get() + currentBindings = idleConf.GetCurrentKeySet() + if currentKeySetName in self.changedItems['keys']: #unsaved changes + keySetChanges = self.changedItems['keys'][currentKeySetName] + for event in keySetChanges: + currentBindings[event] = keySetChanges[event].split() + currentKeySequences = currentBindings.values() + newKeys = GetKeysDialog(self, 'Get New Keys', bindName, + currentKeySequences).result + if newKeys: #new keys were specified + if self.keysAreBuiltin.get(): #current key set is a built-in + message = ('Your changes will be saved as a new Custom Key Set.' + ' Enter a name for your new Custom Key Set below.') + newKeySet = self.GetNewKeysName(message) + if not newKeySet: #user cancelled custom key set creation + self.listBindings.select_set(listIndex) + self.listBindings.select_anchor(listIndex) + return + else: #create new custom key set based on previously active key set + self.CreateNewKeySet(newKeySet) + self.listBindings.delete(listIndex) + self.listBindings.insert(listIndex, bindName+' - '+newKeys) + self.listBindings.select_set(listIndex) + self.listBindings.select_anchor(listIndex) + self.keyBinding.set(newKeys) + else: + self.listBindings.select_set(listIndex) + self.listBindings.select_anchor(listIndex) + + def GetNewKeysName(self, message): + usedNames = (idleConf.GetSectionList('user', 'keys') + + idleConf.GetSectionList('default', 'keys')) + newKeySet = GetCfgSectionNameDialog( + self, 'New Custom Key Set', message, usedNames).result + return newKeySet + + def SaveAsNewKeySet(self): + newKeysName = self.GetNewKeysName('New Key Set Name:') + if newKeysName: + self.CreateNewKeySet(newKeysName) + + def KeyBindingSelected(self, event): + self.buttonNewKeys.config(state=NORMAL) + + def CreateNewKeySet(self, newKeySetName): + #creates new custom key set based on the previously active key set, + #and makes the new key set active + if self.keysAreBuiltin.get(): + prevKeySetName = self.builtinKeys.get() + else: + prevKeySetName = self.customKeys.get() + prevKeys = idleConf.GetCoreKeys(prevKeySetName) + newKeys = {} + for event in prevKeys: #add key set to changed items + eventName = event[2:-2] #trim off the angle brackets + binding = ' '.join(prevKeys[event]) + newKeys[eventName] = binding + #handle any unsaved changes to prev key set + if prevKeySetName in self.changedItems['keys']: + keySetChanges = self.changedItems['keys'][prevKeySetName] + for event in keySetChanges: + newKeys[event] = keySetChanges[event] + #save the new theme + self.SaveNewKeySet(newKeySetName, newKeys) + #change gui over to the new key set + customKeyList = idleConf.GetSectionList('user', 'keys') + customKeyList.sort() + self.optMenuKeysCustom.SetMenu(customKeyList, newKeySetName) + self.keysAreBuiltin.set(0) + self.SetKeysType() + + def LoadKeysList(self, keySetName): + reselect = 0 + newKeySet = 0 + if self.listBindings.curselection(): + reselect = 1 + listIndex = self.listBindings.index(ANCHOR) + keySet = idleConf.GetKeySet(keySetName) + bindNames = keySet.keys() + bindNames.sort() + self.listBindings.delete(0, END) + for bindName in bindNames: + key = ' '.join(keySet[bindName]) #make key(s) into a string + bindName = bindName[2:-2] #trim off the angle brackets + if keySetName in self.changedItems['keys']: + #handle any unsaved changes to this key set + if bindName in self.changedItems['keys'][keySetName]: + key = self.changedItems['keys'][keySetName][bindName] + self.listBindings.insert(END, bindName+' - '+key) + if reselect: + self.listBindings.see(listIndex) + self.listBindings.select_set(listIndex) + self.listBindings.select_anchor(listIndex) + + def DeleteCustomKeys(self): + keySetName=self.customKeys.get() + delmsg = 'Are you sure you wish to delete the key set %r ?' + if not tkMessageBox.askyesno( + 'Delete Key Set', delmsg % keySetName, parent=self): + return + self.DeactivateCurrentConfig() + #remove key set from config + idleConf.userCfg['keys'].remove_section(keySetName) + if keySetName in self.changedItems['keys']: + del(self.changedItems['keys'][keySetName]) + #write changes + idleConf.userCfg['keys'].Save() + #reload user key set list + itemList = idleConf.GetSectionList('user', 'keys') + itemList.sort() + if not itemList: + self.radioKeysCustom.config(state=DISABLED) + self.optMenuKeysCustom.SetMenu(itemList, '- no custom keys -') + else: + self.optMenuKeysCustom.SetMenu(itemList, itemList[0]) + #revert to default key set + self.keysAreBuiltin.set(idleConf.defaultCfg['main'].Get('Keys', 'default')) + self.builtinKeys.set(idleConf.defaultCfg['main'].Get('Keys', 'name')) + #user can't back out of these changes, they must be applied now + self.SaveAllChangedConfigs() + self.ActivateConfigChanges() + self.SetKeysType() + + def DeleteCustomTheme(self): + themeName = self.customTheme.get() + delmsg = 'Are you sure you wish to delete the theme %r ?' + if not tkMessageBox.askyesno( + 'Delete Theme', delmsg % themeName, parent=self): + return + self.DeactivateCurrentConfig() + #remove theme from config + idleConf.userCfg['highlight'].remove_section(themeName) + if themeName in self.changedItems['highlight']: + del(self.changedItems['highlight'][themeName]) + #write changes + idleConf.userCfg['highlight'].Save() + #reload user theme list + itemList = idleConf.GetSectionList('user', 'highlight') + itemList.sort() + if not itemList: + self.radioThemeCustom.config(state=DISABLED) + self.optMenuThemeCustom.SetMenu(itemList, '- no custom themes -') + else: + self.optMenuThemeCustom.SetMenu(itemList, itemList[0]) + #revert to default theme + self.themeIsBuiltin.set(idleConf.defaultCfg['main'].Get('Theme', 'default')) + self.builtinTheme.set(idleConf.defaultCfg['main'].Get('Theme', 'name')) + #user can't back out of these changes, they must be applied now + self.SaveAllChangedConfigs() + self.ActivateConfigChanges() + self.SetThemeType() + + def GetColour(self): + target = self.highlightTarget.get() + prevColour = self.frameColourSet.cget('bg') + rgbTuplet, colourString = tkColorChooser.askcolor( + parent=self, title='Pick new colour for : '+target, + initialcolor=prevColour) + if colourString and (colourString != prevColour): + #user didn't cancel, and they chose a new colour + if self.themeIsBuiltin.get(): #current theme is a built-in + message = ('Your changes will be saved as a new Custom Theme. ' + 'Enter a name for your new Custom Theme below.') + newTheme = self.GetNewThemeName(message) + if not newTheme: #user cancelled custom theme creation + return + else: #create new custom theme based on previously active theme + self.CreateNewTheme(newTheme) + self.colour.set(colourString) + else: #current theme is user defined + self.colour.set(colourString) + + def OnNewColourSet(self): + newColour=self.colour.get() + self.frameColourSet.config(bg=newColour) #set sample + plane ='foreground' if self.fgHilite.get() else 'background' + sampleElement = self.themeElements[self.highlightTarget.get()][0] + self.textHighlightSample.tag_config(sampleElement, **{plane:newColour}) + theme = self.customTheme.get() + themeElement = sampleElement + '-' + plane + self.AddChangedItem('highlight', theme, themeElement, newColour) + + def GetNewThemeName(self, message): + usedNames = (idleConf.GetSectionList('user', 'highlight') + + idleConf.GetSectionList('default', 'highlight')) + newTheme = GetCfgSectionNameDialog( + self, 'New Custom Theme', message, usedNames).result + return newTheme + + def SaveAsNewTheme(self): + newThemeName = self.GetNewThemeName('New Theme Name:') + if newThemeName: + self.CreateNewTheme(newThemeName) + + def CreateNewTheme(self, newThemeName): + #creates new custom theme based on the previously active theme, + #and makes the new theme active + if self.themeIsBuiltin.get(): + themeType = 'default' + themeName = self.builtinTheme.get() + else: + themeType = 'user' + themeName = self.customTheme.get() + newTheme = idleConf.GetThemeDict(themeType, themeName) + #apply any of the old theme's unsaved changes to the new theme + if themeName in self.changedItems['highlight']: + themeChanges = self.changedItems['highlight'][themeName] + for element in themeChanges: + newTheme[element] = themeChanges[element] + #save the new theme + self.SaveNewTheme(newThemeName, newTheme) + #change gui over to the new theme + customThemeList = idleConf.GetSectionList('user', 'highlight') + customThemeList.sort() + self.optMenuThemeCustom.SetMenu(customThemeList, newThemeName) + self.themeIsBuiltin.set(0) + self.SetThemeType() + + def OnListFontButtonRelease(self, event): + font = self.listFontName.get(ANCHOR) + self.fontName.set(font.lower()) + self.SetFontSample() + + def SetFontSample(self, event=None): + fontName = self.fontName.get() + fontWeight = tkFont.BOLD if self.fontBold.get() else tkFont.NORMAL + newFont = (fontName, self.fontSize.get(), fontWeight) + self.labelFontSample.config(font=newFont) + self.textHighlightSample.configure(font=newFont) + + def SetHighlightTarget(self): + if self.highlightTarget.get() == 'Cursor': #bg not possible + self.radioFg.config(state=DISABLED) + self.radioBg.config(state=DISABLED) + self.fgHilite.set(1) + else: #both fg and bg can be set + self.radioFg.config(state=NORMAL) + self.radioBg.config(state=NORMAL) + self.fgHilite.set(1) + self.SetColourSample() + + def SetColourSampleBinding(self, *args): + self.SetColourSample() + + def SetColourSample(self): + #set the colour smaple area + tag = self.themeElements[self.highlightTarget.get()][0] + plane = 'foreground' if self.fgHilite.get() else 'background' + colour = self.textHighlightSample.tag_cget(tag, plane) + self.frameColourSet.config(bg=colour) + + def PaintThemeSample(self): + if self.themeIsBuiltin.get(): #a default theme + theme = self.builtinTheme.get() + else: #a user theme + theme = self.customTheme.get() + for elementTitle in self.themeElements: + element = self.themeElements[elementTitle][0] + colours = idleConf.GetHighlight(theme, element) + if element == 'cursor': #cursor sample needs special painting + colours['background'] = idleConf.GetHighlight( + theme, 'normal', fgBg='bg') + #handle any unsaved changes to this theme + if theme in self.changedItems['highlight']: + themeDict = self.changedItems['highlight'][theme] + if element + '-foreground' in themeDict: + colours['foreground'] = themeDict[element + '-foreground'] + if element + '-background' in themeDict: + colours['background'] = themeDict[element + '-background'] + self.textHighlightSample.tag_config(element, **colours) + self.SetColourSample() + + def HelpSourceSelected(self, event): + self.SetHelpListButtonStates() + + def SetHelpListButtonStates(self): + if self.listHelp.size() < 1: #no entries in list + self.buttonHelpListEdit.config(state=DISABLED) + self.buttonHelpListRemove.config(state=DISABLED) + else: #there are some entries + if self.listHelp.curselection(): #there currently is a selection + self.buttonHelpListEdit.config(state=NORMAL) + self.buttonHelpListRemove.config(state=NORMAL) + else: #there currently is not a selection + self.buttonHelpListEdit.config(state=DISABLED) + self.buttonHelpListRemove.config(state=DISABLED) + + def HelpListItemAdd(self): + helpSource = GetHelpSourceDialog(self, 'New Help Source').result + if helpSource: + self.userHelpList.append((helpSource[0], helpSource[1])) + self.listHelp.insert(END, helpSource[0]) + self.UpdateUserHelpChangedItems() + self.SetHelpListButtonStates() + + def HelpListItemEdit(self): + itemIndex = self.listHelp.index(ANCHOR) + helpSource = self.userHelpList[itemIndex] + newHelpSource = GetHelpSourceDialog( + self, 'Edit Help Source', menuItem=helpSource[0], + filePath=helpSource[1]).result + if (not newHelpSource) or (newHelpSource == helpSource): + return #no changes + self.userHelpList[itemIndex] = newHelpSource + self.listHelp.delete(itemIndex) + self.listHelp.insert(itemIndex, newHelpSource[0]) + self.UpdateUserHelpChangedItems() + self.SetHelpListButtonStates() + + def HelpListItemRemove(self): + itemIndex = self.listHelp.index(ANCHOR) + del(self.userHelpList[itemIndex]) + self.listHelp.delete(itemIndex) + self.UpdateUserHelpChangedItems() + self.SetHelpListButtonStates() + + def UpdateUserHelpChangedItems(self): + "Clear and rebuild the HelpFiles section in self.changedItems" + self.changedItems['main']['HelpFiles'] = {} + for num in range(1, len(self.userHelpList) + 1): + self.AddChangedItem( + 'main', 'HelpFiles', str(num), + ';'.join(self.userHelpList[num-1][:2])) + + def LoadFontCfg(self): + ##base editor font selection list + fonts = list(tkFont.families(self)) + fonts.sort() + for font in fonts: + self.listFontName.insert(END, font) + configuredFont = idleConf.GetFont(self, 'main', 'EditorWindow') + fontName = configuredFont[0].lower() + fontSize = configuredFont[1] + fontBold = configuredFont[2]=='bold' + self.fontName.set(fontName) + lc_fonts = [s.lower() for s in fonts] + try: + currentFontIndex = lc_fonts.index(fontName) + self.listFontName.see(currentFontIndex) + self.listFontName.select_set(currentFontIndex) + self.listFontName.select_anchor(currentFontIndex) + except ValueError: + pass + ##font size dropdown + self.optMenuFontSize.SetMenu(('7', '8', '9', '10', '11', '12', '13', + '14', '16', '18', '20', '22', + '25', '29', '34', '40'), fontSize ) + ##fontWeight + self.fontBold.set(fontBold) + ##font sample + self.SetFontSample() + + def LoadTabCfg(self): + ##indent sizes + spaceNum = idleConf.GetOption( + 'main', 'Indent', 'num-spaces', default=4, type='int') + self.spaceNum.set(spaceNum) + + def LoadThemeCfg(self): + ##current theme type radiobutton + self.themeIsBuiltin.set(idleConf.GetOption( + 'main', 'Theme', 'default', type='bool', default=1)) + ##currently set theme + currentOption = idleConf.CurrentTheme() + ##load available theme option menus + if self.themeIsBuiltin.get(): #default theme selected + itemList = idleConf.GetSectionList('default', 'highlight') + itemList.sort() + self.optMenuThemeBuiltin.SetMenu(itemList, currentOption) + itemList = idleConf.GetSectionList('user', 'highlight') + itemList.sort() + if not itemList: + self.radioThemeCustom.config(state=DISABLED) + self.customTheme.set('- no custom themes -') + else: + self.optMenuThemeCustom.SetMenu(itemList, itemList[0]) + else: #user theme selected + itemList = idleConf.GetSectionList('user', 'highlight') + itemList.sort() + self.optMenuThemeCustom.SetMenu(itemList, currentOption) + itemList = idleConf.GetSectionList('default', 'highlight') + itemList.sort() + self.optMenuThemeBuiltin.SetMenu(itemList, itemList[0]) + self.SetThemeType() + ##load theme element option menu + themeNames = self.themeElements.keys() + themeNames.sort(key=lambda x: self.themeElements[x][1]) + self.optMenuHighlightTarget.SetMenu(themeNames, themeNames[0]) + self.PaintThemeSample() + self.SetHighlightTarget() + + def LoadKeyCfg(self): + ##current keys type radiobutton + self.keysAreBuiltin.set(idleConf.GetOption( + 'main', 'Keys', 'default', type='bool', default=1)) + ##currently set keys + currentOption = idleConf.CurrentKeys() + ##load available keyset option menus + if self.keysAreBuiltin.get(): #default theme selected + itemList = idleConf.GetSectionList('default', 'keys') + itemList.sort() + self.optMenuKeysBuiltin.SetMenu(itemList, currentOption) + itemList = idleConf.GetSectionList('user', 'keys') + itemList.sort() + if not itemList: + self.radioKeysCustom.config(state=DISABLED) + self.customKeys.set('- no custom keys -') + else: + self.optMenuKeysCustom.SetMenu(itemList, itemList[0]) + else: #user key set selected + itemList = idleConf.GetSectionList('user', 'keys') + itemList.sort() + self.optMenuKeysCustom.SetMenu(itemList, currentOption) + itemList = idleConf.GetSectionList('default', 'keys') + itemList.sort() + self.optMenuKeysBuiltin.SetMenu(itemList, itemList[0]) + self.SetKeysType() + ##load keyset element list + keySetName = idleConf.CurrentKeys() + self.LoadKeysList(keySetName) + + def LoadGeneralCfg(self): + #startup state + self.startupEdit.set(idleConf.GetOption( + 'main', 'General', 'editor-on-startup', default=1, type='bool')) + #autosave state + self.autoSave.set(idleConf.GetOption( + 'main', 'General', 'autosave', default=0, type='bool')) + #initial window size + self.winWidth.set(idleConf.GetOption( + 'main', 'EditorWindow', 'width', type='int')) + self.winHeight.set(idleConf.GetOption( + 'main', 'EditorWindow', 'height', type='int')) + # default source encoding + self.encoding.set(idleConf.GetOption( + 'main', 'EditorWindow', 'encoding', default='none')) + # additional help sources + self.userHelpList = idleConf.GetAllExtraHelpSourcesList() + for helpItem in self.userHelpList: + self.listHelp.insert(END, helpItem[0]) + self.SetHelpListButtonStates() + + def LoadConfigs(self): + """ + load configuration from default and user config files and populate + the widgets on the config dialog pages. + """ + ### fonts / tabs page + self.LoadFontCfg() + self.LoadTabCfg() + ### highlighting page + self.LoadThemeCfg() + ### keys page + self.LoadKeyCfg() + ### general page + self.LoadGeneralCfg() + # note: extension page handled separately + + def SaveNewKeySet(self, keySetName, keySet): + """ + save a newly created core key set. + keySetName - string, the name of the new key set + keySet - dictionary containing the new key set + """ + if not idleConf.userCfg['keys'].has_section(keySetName): + idleConf.userCfg['keys'].add_section(keySetName) + for event in keySet: + value = keySet[event] + idleConf.userCfg['keys'].SetOption(keySetName, event, value) + + def SaveNewTheme(self, themeName, theme): + """ + save a newly created theme. + themeName - string, the name of the new theme + theme - dictionary containing the new theme + """ + if not idleConf.userCfg['highlight'].has_section(themeName): + idleConf.userCfg['highlight'].add_section(themeName) + for element in theme: + value = theme[element] + idleConf.userCfg['highlight'].SetOption(themeName, element, value) + + def SetUserValue(self, configType, section, item, value): + if idleConf.defaultCfg[configType].has_option(section, item): + if idleConf.defaultCfg[configType].Get(section, item) == value: + #the setting equals a default setting, remove it from user cfg + return idleConf.userCfg[configType].RemoveOption(section, item) + #if we got here set the option + return idleConf.userCfg[configType].SetOption(section, item, value) + + def SaveAllChangedConfigs(self): + "Save configuration changes to the user config file." + idleConf.userCfg['main'].Save() + for configType in self.changedItems: + cfgTypeHasChanges = False + for section in self.changedItems[configType]: + if section == 'HelpFiles': + #this section gets completely replaced + idleConf.userCfg['main'].remove_section('HelpFiles') + cfgTypeHasChanges = True + for item in self.changedItems[configType][section]: + value = self.changedItems[configType][section][item] + if self.SetUserValue(configType, section, item, value): + cfgTypeHasChanges = True + if cfgTypeHasChanges: + idleConf.userCfg[configType].Save() + for configType in ['keys', 'highlight']: + # save these even if unchanged! + idleConf.userCfg[configType].Save() + self.ResetChangedItems() #clear the changed items dict + self.save_all_changed_extensions() # uses a different mechanism + + def DeactivateCurrentConfig(self): + #Before a config is saved, some cleanup of current + #config must be done - remove the previous keybindings + winInstances = self.parent.instance_dict + for instance in winInstances: + instance.RemoveKeybindings() + + def ActivateConfigChanges(self): + "Dynamically apply configuration changes" + winInstances = self.parent.instance_dict.keys() + for instance in winInstances: + instance.ResetColorizer() + instance.ResetFont() + instance.set_notabs_indentwidth() + instance.ApplyKeybindings() + instance.reset_help_menu_entries() + + def Cancel(self): + self.destroy() + + def Ok(self): + self.Apply() + self.destroy() + + def Apply(self): + self.DeactivateCurrentConfig() + self.SaveAllChangedConfigs() + self.ActivateConfigChanges() + + def Help(self): + page = self.tabPages._current_page + view_text(self, title='Help for IDLE preferences', + text=help_common+help_pages.get(page, '')) + + def CreatePageExtensions(self): + """Part of the config dialog used for configuring IDLE extensions. + + This code is generic - it works for any and all IDLE extensions. + + IDLE extensions save their configuration options using idleConf. + This code reads the current configuration using idleConf, supplies a + GUI interface to change the configuration values, and saves the + changes using idleConf. + + Not all changes take effect immediately - some may require restarting IDLE. + This depends on each extension's implementation. + + All values are treated as text, and it is up to the user to supply + reasonable values. The only exception to this are the 'enable*' options, + which are boolean, and can be toggled with a True/False button. + """ + parent = self.parent + frame = self.tabPages.pages['Extensions'].frame + self.ext_defaultCfg = idleConf.defaultCfg['extensions'] + self.ext_userCfg = idleConf.userCfg['extensions'] + self.is_int = self.register(is_int) + self.load_extensions() + # create widgets - a listbox shows all available extensions, with the + # controls for the extension selected in the listbox to the right + self.extension_names = StringVar(self) + frame.rowconfigure(0, weight=1) + frame.columnconfigure(2, weight=1) + self.extension_list = Listbox(frame, listvariable=self.extension_names, + selectmode='browse') + self.extension_list.bind('<<ListboxSelect>>', self.extension_selected) + scroll = Scrollbar(frame, command=self.extension_list.yview) + self.extension_list.yscrollcommand=scroll.set + self.details_frame = LabelFrame(frame, width=250, height=250) + self.extension_list.grid(column=0, row=0, sticky='nws') + scroll.grid(column=1, row=0, sticky='ns') + self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0]) + frame.configure(padx=10, pady=10) + self.config_frame = {} + self.current_extension = None + + self.outerframe = self # TEMPORARY + self.tabbed_page_set = self.extension_list # TEMPORARY + + # create the frame holding controls for each extension + ext_names = '' + for ext_name in sorted(self.extensions): + self.create_extension_frame(ext_name) + ext_names = ext_names + '{' + ext_name + '} ' + self.extension_names.set(ext_names) + self.extension_list.selection_set(0) + self.extension_selected(None) + + def load_extensions(self): + "Fill self.extensions with data from the default and user configs." + self.extensions = {} + for ext_name in idleConf.GetExtensions(active_only=False): + self.extensions[ext_name] = [] + + for ext_name in self.extensions: + opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name)) + + # bring 'enable' options to the beginning of the list + enables = [opt_name for opt_name in opt_list + if opt_name.startswith('enable')] + for opt_name in enables: + opt_list.remove(opt_name) + opt_list = enables + opt_list + + for opt_name in opt_list: + def_str = self.ext_defaultCfg.Get( + ext_name, opt_name, raw=True) + try: + def_obj = {'True':True, 'False':False}[def_str] + opt_type = 'bool' + except KeyError: + try: + def_obj = int(def_str) + opt_type = 'int' + except ValueError: + def_obj = def_str + opt_type = None + try: + value = self.ext_userCfg.Get( + ext_name, opt_name, type=opt_type, raw=True, + default=def_obj) + except ValueError: # Need this until .Get fixed + value = def_obj # bad values overwritten by entry + var = StringVar(self) + var.set(str(value)) + + self.extensions[ext_name].append({'name': opt_name, + 'type': opt_type, + 'default': def_str, + 'value': value, + 'var': var, + }) + + def extension_selected(self, event): + newsel = self.extension_list.curselection() + if newsel: + newsel = self.extension_list.get(newsel) + if newsel is None or newsel != self.current_extension: + if self.current_extension: + self.details_frame.config(text='') + self.config_frame[self.current_extension].grid_forget() + self.current_extension = None + if newsel: + self.details_frame.config(text=newsel) + self.config_frame[newsel].grid(column=0, row=0, sticky='nsew') + self.current_extension = newsel + + def create_extension_frame(self, ext_name): + """Create a frame holding the widgets to configure one extension""" + f = VerticalScrolledFrame(self.details_frame, height=250, width=250) + self.config_frame[ext_name] = f + entry_area = f.interior + # create an entry for each configuration option + for row, opt in enumerate(self.extensions[ext_name]): + # create a row with a label and entry/checkbutton + label = Label(entry_area, text=opt['name']) + label.grid(row=row, column=0, sticky=NW) + var = opt['var'] + if opt['type'] == 'bool': + Checkbutton(entry_area, textvariable=var, variable=var, + onvalue='True', offvalue='False', + indicatoron=FALSE, selectcolor='', width=8 + ).grid(row=row, column=1, sticky=W, padx=7) + elif opt['type'] == 'int': + Entry(entry_area, textvariable=var, validate='key', + validatecommand=(self.is_int, '%P') + ).grid(row=row, column=1, sticky=NSEW, padx=7) + + else: + Entry(entry_area, textvariable=var + ).grid(row=row, column=1, sticky=NSEW, padx=7) + return + + def set_extension_value(self, section, opt): + name = opt['name'] + default = opt['default'] + value = opt['var'].get().strip() or default + opt['var'].set(value) + # if self.defaultCfg.has_section(section): + # Currently, always true; if not, indent to return + if (value == default): + return self.ext_userCfg.RemoveOption(section, name) + # set the option + return self.ext_userCfg.SetOption(section, name, value) + + def save_all_changed_extensions(self): + """Save configuration changes to the user config file.""" + has_changes = False + for ext_name in self.extensions: + options = self.extensions[ext_name] + for opt in options: + if self.set_extension_value(ext_name, opt): + has_changes = True + if has_changes: + self.ext_userCfg.Save() + + +help_common = '''\ +When you click either the Apply or Ok buttons, settings in this +dialog that are different from IDLE's default are saved in +a .idlerc directory in your home directory. Except as noted, +these changes apply to all versions of IDLE installed on this +machine. Some do not take affect until IDLE is restarted. +[Cancel] only cancels changes made since the last save. +''' +help_pages = { + 'Highlighting':''' +Highlighting: +The IDLE Dark color theme is new in October 2015. It can only +be used with older IDLE releases if it is saved as a custom +theme, with a different name. +''' +} + + +def is_int(s): + "Return 's is blank or represents an int'" + if not s: + return True + try: + int(s) + return True + except ValueError: + return False + + +class VerticalScrolledFrame(Frame): + """A pure Tkinter vertically scrollable frame. + + * Use the 'interior' attribute to place widgets inside the scrollable frame + * Construct and pack/place/grid normally + * This frame only allows vertical scrolling + """ + def __init__(self, parent, *args, **kw): + Frame.__init__(self, parent, *args, **kw) + + # create a canvas object and a vertical scrollbar for scrolling it + vscrollbar = Scrollbar(self, orient=VERTICAL) + vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE) + canvas = Canvas(self, bd=0, highlightthickness=0, + yscrollcommand=vscrollbar.set, width=240) + canvas.pack(side=LEFT, fill=BOTH, expand=TRUE) + vscrollbar.config(command=canvas.yview) + + # reset the view + canvas.xview_moveto(0) + canvas.yview_moveto(0) + + # create a frame inside the canvas which will be scrolled with it + self.interior = interior = Frame(canvas) + interior_id = canvas.create_window(0, 0, window=interior, anchor=NW) + + # track changes to the canvas and frame width and sync them, + # also updating the scrollbar + def _configure_interior(event): + # update the scrollbars to match the size of the inner frame + size = (interior.winfo_reqwidth(), interior.winfo_reqheight()) + canvas.config(scrollregion="0 0 %s %s" % size) + interior.bind('<Configure>', _configure_interior) + + def _configure_canvas(event): + if interior.winfo_reqwidth() != canvas.winfo_width(): + # update the inner frame's width to fill the canvas + canvas.itemconfigure(interior_id, width=canvas.winfo_width()) + canvas.bind('<Configure>', _configure_canvas) + + return + + +if __name__ == '__main__': + import unittest + unittest.main('idlelib.idle_test.test_configdialog', + verbosity=2, exit=False) + from idlelib.idle_test.htest import run + run(ConfigDialog) diff --git a/lib/python2.7/idlelib/configHandler.py b/lib/python2.7/idlelib/configHandler.py new file mode 100644 index 0000000..ca885ed --- /dev/null +++ b/lib/python2.7/idlelib/configHandler.py @@ -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='') diff --git a/lib/python2.7/idlelib/configHelpSourceEdit.py b/lib/python2.7/idlelib/configHelpSourceEdit.py new file mode 100644 index 0000000..5816449 --- /dev/null +++ b/lib/python2.7/idlelib/configHelpSourceEdit.py @@ -0,0 +1,166 @@ +"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.destroy() + + def Cancel(self, event=None): + self.result = None + self.destroy() + +if __name__ == '__main__': + from idlelib.idle_test.htest import run + run(GetHelpSourceDialog) diff --git a/lib/python2.7/idlelib/configSectionNameDialog.py b/lib/python2.7/idlelib/configSectionNameDialog.py new file mode 100644 index 0000000..c09dca8 --- /dev/null +++ b/lib/python2.7/idlelib/configSectionNameDialog.py @@ -0,0 +1,92 @@ +""" +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.destroy() + def Cancel(self, event=None): + self.result = '' + 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) diff --git a/lib/python2.7/idlelib/dynOptionMenuWidget.py b/lib/python2.7/idlelib/dynOptionMenuWidget.py new file mode 100644 index 0000000..beca9e2 --- /dev/null +++ b/lib/python2.7/idlelib/dynOptionMenuWidget.py @@ -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) diff --git a/lib/python2.7/idlelib/extend.txt b/lib/python2.7/idlelib/extend.txt new file mode 100644 index 0000000..c9cb2e8 --- /dev/null +++ b/lib/python2.7/idlelib/extend.txt @@ -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. diff --git a/lib/python2.7/idlelib/help.html b/lib/python2.7/idlelib/help.html new file mode 100644 index 0000000..1c4598b --- /dev/null +++ b/lib/python2.7/idlelib/help.html @@ -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 — 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> »</li> + <li> + <a href="../index.html">Python 2.7.12 documentation</a> » + </li> + + <li class="nav-item nav-item-1"><a href="index.html" >The Python Standard Library</a> »</li> + <li class="nav-item nav-item-2"><a href="tk.html" accesskey="U">24. Graphical User Interfaces with Tk</a> »</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’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’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’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’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, ‘C’ 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’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 ‘.’ 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>‘Show Completions’ 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>“Hidden” attributes can be accessed by typing the beginning of hidden +name after a ‘.’, e.g. ‘_’. 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 ‘Expand Word’ 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’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">>>></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’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’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’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’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 “Python Docs” 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> — 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> »</li> + <li> + <a href="../index.html">Python 2.7.12 documentation</a> » + </li> + + <li class="nav-item nav-item-1"><a href="index.html" >The Python Standard Library</a> »</li> + <li class="nav-item nav-item-2"><a href="tk.html" >24. Graphical User Interfaces with Tk</a> »</li> + </ul> + </div> + <div class="footer"> + © <a href="../copyright.html">Copyright</a> 1990-2016, 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> diff --git a/lib/python2.7/idlelib/help.py b/lib/python2.7/idlelib/help.py new file mode 100644 index 0000000..3ab4851 --- /dev/null +++ b/lib/python2.7/idlelib/help.py @@ -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) diff --git a/lib/python2.7/idlelib/help.txt b/lib/python2.7/idlelib/help.txt new file mode 100644 index 0000000..296c78b --- /dev/null +++ b/lib/python2.7/idlelib/help.txt @@ -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 diff --git a/lib/python2.7/idlelib/idle.bat b/lib/python2.7/idlelib/idle.bat new file mode 100644 index 0000000..e77b96e --- /dev/null +++ b/lib/python2.7/idlelib/idle.bat @@ -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 diff --git a/lib/python2.7/idlelib/idle.py b/lib/python2.7/idlelib/idle.py new file mode 100644 index 0000000..141534d --- /dev/null +++ b/lib/python2.7/idlelib/idle.py @@ -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() diff --git a/lib/python2.7/idlelib/idle.pyw b/lib/python2.7/idlelib/idle.pyw new file mode 100644 index 0000000..9ce4c9f --- /dev/null +++ b/lib/python2.7/idlelib/idle.pyw @@ -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() diff --git a/lib/python2.7/idlelib/idle_test/README.txt b/lib/python2.7/idlelib/idle_test/README.txt new file mode 100644 index 0000000..6967d70 --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/README.txt @@ -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 diff --git a/lib/python2.7/idlelib/idle_test/__init__.py b/lib/python2.7/idlelib/idle_test/__init__.py new file mode 100644 index 0000000..845c92d --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/__init__.py @@ -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 diff --git a/lib/python2.7/idlelib/idle_test/htest.py b/lib/python2.7/idlelib/idle_test/htest.py new file mode 100644 index 0000000..f341409 --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/htest.py @@ -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() diff --git a/lib/python2.7/idlelib/idle_test/mock_idle.py b/lib/python2.7/idlelib/idle_test/mock_idle.py new file mode 100644 index 0000000..7b09f83 --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/mock_idle.py @@ -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 diff --git a/lib/python2.7/idlelib/idle_test/mock_tk.py b/lib/python2.7/idlelib/idle_test/mock_tk.py new file mode 100644 index 0000000..f42a039 --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/mock_tk.py @@ -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 diff --git a/lib/python2.7/idlelib/idle_test/test_autocomplete.py b/lib/python2.7/idlelib/idle_test/test_autocomplete.py new file mode 100644 index 0000000..002751e --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/test_autocomplete.py @@ -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) diff --git a/lib/python2.7/idlelib/idle_test/test_autoexpand.py b/lib/python2.7/idlelib/idle_test/test_autoexpand.py new file mode 100644 index 0000000..6be4fbf --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/test_autoexpand.py @@ -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) diff --git a/lib/python2.7/idlelib/idle_test/test_calltips.py b/lib/python2.7/idlelib/idle_test/test_calltips.py new file mode 100644 index 0000000..147119c --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/test_calltips.py @@ -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) diff --git a/lib/python2.7/idlelib/idle_test/test_config_name.py b/lib/python2.7/idlelib/idle_test/test_config_name.py new file mode 100644 index 0000000..4403f87 --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/test_config_name.py @@ -0,0 +1,75 @@ +"""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 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) diff --git a/lib/python2.7/idlelib/idle_test/test_configdialog.py b/lib/python2.7/idlelib/idle_test/test_configdialog.py new file mode 100644 index 0000000..ba65100 --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/test_configdialog.py @@ -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) diff --git a/lib/python2.7/idlelib/idle_test/test_delegator.py b/lib/python2.7/idlelib/idle_test/test_delegator.py new file mode 100644 index 0000000..b8ae5ee --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/test_delegator.py @@ -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) diff --git a/lib/python2.7/idlelib/idle_test/test_editmenu.py b/lib/python2.7/idlelib/idle_test/test_editmenu.py new file mode 100644 index 0000000..51d5c16 --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/test_editmenu.py @@ -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) diff --git a/lib/python2.7/idlelib/idle_test/test_formatparagraph.py b/lib/python2.7/idlelib/idle_test/test_formatparagraph.py new file mode 100644 index 0000000..068ae38 --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/test_formatparagraph.py @@ -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) diff --git a/lib/python2.7/idlelib/idle_test/test_grep.py b/lib/python2.7/idlelib/idle_test/test_grep.py new file mode 100644 index 0000000..e9f4f22 --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/test_grep.py @@ -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) diff --git a/lib/python2.7/idlelib/idle_test/test_helpabout.py b/lib/python2.7/idlelib/idle_test/test_helpabout.py new file mode 100644 index 0000000..0046f87 --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/test_helpabout.py @@ -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) diff --git a/lib/python2.7/idlelib/idle_test/test_hyperparser.py b/lib/python2.7/idlelib/idle_test/test_hyperparser.py new file mode 100644 index 0000000..0a1809d --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/test_hyperparser.py @@ -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) diff --git a/lib/python2.7/idlelib/idle_test/test_idlehistory.py b/lib/python2.7/idlelib/idle_test/test_idlehistory.py new file mode 100644 index 0000000..b076757 --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/test_idlehistory.py @@ -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) diff --git a/lib/python2.7/idlelib/idle_test/test_io.py b/lib/python2.7/idlelib/idle_test/test_io.py new file mode 100644 index 0000000..ee017bb --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/test_io.py @@ -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() diff --git a/lib/python2.7/idlelib/idle_test/test_parenmatch.py b/lib/python2.7/idlelib/idle_test/test_parenmatch.py new file mode 100644 index 0000000..1621981 --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/test_parenmatch.py @@ -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) diff --git a/lib/python2.7/idlelib/idle_test/test_pathbrowser.py b/lib/python2.7/idlelib/idle_test/test_pathbrowser.py new file mode 100644 index 0000000..f028414 --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/test_pathbrowser.py @@ -0,0 +1,28 @@ +import unittest +import os +import sys +import idlelib +from idlelib import PathBrowser + +class PathBrowserTest(unittest.TestCase): + + def test_DirBrowserTreeItem(self): + # Issue16226 - make sure that getting a sublist works + d = PathBrowser.DirBrowserTreeItem('') + d.GetSubList() + self.assertEqual('', d.GetText()) + + dir = os.path.split(os.path.abspath(idlelib.__file__))[0] + self.assertEqual(d.ispackagedir(dir), True) + self.assertEqual(d.ispackagedir(dir + '/Icons'), False) + + def test_PathBrowserTreeItem(self): + p = PathBrowser.PathBrowserTreeItem() + self.assertEqual(p.GetText(), 'sys.path') + sub = p.GetSubList() + self.assertEqual(len(sub), len(sys.path)) + # Following fails in 2.7 because old-style class + #self.assertEqual(type(sub[0]), PathBrowser.DirBrowserTreeItem) + +if __name__ == '__main__': + unittest.main(verbosity=2, exit=False) diff --git a/lib/python2.7/idlelib/idle_test/test_rstrip.py b/lib/python2.7/idlelib/idle_test/test_rstrip.py new file mode 100644 index 0000000..1c90b93 --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/test_rstrip.py @@ -0,0 +1,49 @@ +import unittest +import idlelib.RstripExtension as rs +from idlelib.idle_test.mock_idle import Editor + +class rstripTest(unittest.TestCase): + + def test_rstrip_line(self): + editor = Editor() + text = editor.text + do_rstrip = rs.RstripExtension(editor).do_rstrip + + do_rstrip() + self.assertEqual(text.get('1.0', 'insert'), '') + text.insert('1.0', ' ') + do_rstrip() + self.assertEqual(text.get('1.0', 'insert'), '') + text.insert('1.0', ' \n') + do_rstrip() + self.assertEqual(text.get('1.0', 'insert'), '\n') + + def test_rstrip_multiple(self): + editor = Editor() + # Uncomment following to verify that test passes with real widgets. +## from idlelib.EditorWindow import EditorWindow as Editor +## from tkinter import Tk +## editor = Editor(root=Tk()) + text = editor.text + do_rstrip = rs.RstripExtension(editor).do_rstrip + + original = ( + "Line with an ending tab \n" + "Line ending in 5 spaces \n" + "Linewithnospaces\n" + " indented line\n" + " indented line with trailing space \n" + " ") + stripped = ( + "Line with an ending tab\n" + "Line ending in 5 spaces\n" + "Linewithnospaces\n" + " indented line\n" + " indented line with trailing space\n") + + text.insert('1.0', original) + do_rstrip() + self.assertEqual(text.get('1.0', 'insert'), stripped) + +if __name__ == '__main__': + unittest.main(verbosity=2, exit=False) diff --git a/lib/python2.7/idlelib/idle_test/test_searchdialogbase.py b/lib/python2.7/idlelib/idle_test/test_searchdialogbase.py new file mode 100644 index 0000000..32abfe6 --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/test_searchdialogbase.py @@ -0,0 +1,164 @@ +'''Unittests for idlelib/SearchDialogBase.py + +Coverage: 99%. The only thing not covered is inconsequential -- +testing skipping of suite when self.needwrapbutton is false. + +''' +import unittest +from test.test_support import requires +from Tkinter import Tk, Toplevel, Frame ## BooleanVar, StringVar +from idlelib import SearchEngine as se +from idlelib import SearchDialogBase as sdb +from idlelib.idle_test.mock_idle import Func +##from idlelib.idle_test.mock_tk import Var + +# The ## imports above & following could help make some tests gui-free.# However, they currently make radiobutton tests fail. +##def setUpModule(): +## # Replace tk objects used to initialize se.SearchEngine. +## se.BooleanVar = Var +## se.StringVar = Var +## +##def tearDownModule(): +## se.BooleanVar = BooleanVar +## se.StringVar = StringVar + +class SearchDialogBaseTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = Tk() + + @classmethod + def tearDownClass(cls): + cls.root.destroy() + del cls.root + + def setUp(self): + self.engine = se.SearchEngine(self.root) # None also seems to work + self.dialog = sdb.SearchDialogBase(root=self.root, engine=self.engine) + + def tearDown(self): + self.dialog.close() + + def test_open_and_close(self): + # open calls create_widgets, which needs default_command + self.dialog.default_command = None + + # Since text parameter of .open is not used in base class, + # pass dummy 'text' instead of tk.Text(). + self.dialog.open('text') + self.assertEqual(self.dialog.top.state(), 'normal') + self.dialog.close() + self.assertEqual(self.dialog.top.state(), 'withdrawn') + + self.dialog.open('text', searchphrase="hello") + self.assertEqual(self.dialog.ent.get(), 'hello') + self.dialog.close() + + def test_create_widgets(self): + self.dialog.create_entries = Func() + self.dialog.create_option_buttons = Func() + self.dialog.create_other_buttons = Func() + self.dialog.create_command_buttons = Func() + + self.dialog.default_command = None + self.dialog.create_widgets() + + self.assertTrue(self.dialog.create_entries.called) + self.assertTrue(self.dialog.create_option_buttons.called) + self.assertTrue(self.dialog.create_other_buttons.called) + self.assertTrue(self.dialog.create_command_buttons.called) + + def test_make_entry(self): + equal = self.assertEqual + self.dialog.row = 0 + self.dialog.top = Toplevel(self.root) + entry, label = self.dialog.make_entry("Test:", 'hello') + equal(label['text'], 'Test:') + + self.assertIn(entry.get(), 'hello') + egi = entry.grid_info() + equal(int(egi['row']), 0) + equal(int(egi['column']), 1) + equal(int(egi['rowspan']), 1) + equal(int(egi['columnspan']), 1) + equal(self.dialog.row, 1) + + def test_create_entries(self): + self.dialog.row = 0 + self.engine.setpat('hello') + self.dialog.create_entries() + self.assertIn(self.dialog.ent.get(), 'hello') + + def test_make_frame(self): + self.dialog.row = 0 + self.dialog.top = Toplevel(self.root) + frame, label = self.dialog.make_frame() + self.assertEqual(label, '') + self.assertIsInstance(frame, Frame) + + frame, label = self.dialog.make_frame('testlabel') + self.assertEqual(label['text'], 'testlabel') + self.assertIsInstance(frame, Frame) + + def btn_test_setup(self, meth): + self.dialog.top = Toplevel(self.root) + self.dialog.row = 0 + return meth() + + def test_create_option_buttons(self): + e = self.engine + for state in (0, 1): + for var in (e.revar, e.casevar, e.wordvar, e.wrapvar): + var.set(state) + frame, options = self.btn_test_setup( + self.dialog.create_option_buttons) + for spec, button in zip (options, frame.pack_slaves()): + var, label = spec + self.assertEqual(button['text'], label) + self.assertEqual(var.get(), state) + if state == 1: + button.deselect() + else: + button.select() + self.assertEqual(var.get(), 1 - state) + + def test_create_other_buttons(self): + for state in (False, True): + var = self.engine.backvar + var.set(state) + frame, others = self.btn_test_setup( + self.dialog.create_other_buttons) + buttons = frame.pack_slaves() + for spec, button in zip(others, buttons): + val, label = spec + self.assertEqual(button['text'], label) + if val == state: + # hit other button, then this one + # indexes depend on button order + self.assertEqual(var.get(), state) + buttons[val].select() + self.assertEqual(var.get(), 1 - state) + buttons[1-val].select() + self.assertEqual(var.get(), state) + + def test_make_button(self): + self.dialog.top = Toplevel(self.root) + self.dialog.buttonframe = Frame(self.dialog.top) + btn = self.dialog.make_button('Test', self.dialog.close) + self.assertEqual(btn['text'], 'Test') + + def test_create_command_buttons(self): + self.dialog.create_command_buttons() + # Look for close button command in buttonframe + closebuttoncommand = '' + for child in self.dialog.buttonframe.winfo_children(): + if child['text'] == 'close': + closebuttoncommand = child['command'] + self.assertIn('close', closebuttoncommand) + + + +if __name__ == '__main__': + unittest.main(verbosity=2, exit=2) diff --git a/lib/python2.7/idlelib/idle_test/test_searchengine.py b/lib/python2.7/idlelib/idle_test/test_searchengine.py new file mode 100644 index 0000000..8bf9d47 --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/test_searchengine.py @@ -0,0 +1,329 @@ +'''Test functions and SearchEngine class in SearchEngine.py.''' + +# With mock replacements, the module does not use any gui widgets. +# The use of tk.Text is avoided (for now, until mock Text is improved) +# by patching instances with an index function returning what is needed. +# This works because mock Text.get does not use .index. + +import re +import unittest +#from test.test_support import requires +from Tkinter import BooleanVar, StringVar, TclError # ,Tk, Text +import tkMessageBox +from idlelib import SearchEngine as se +from idlelib.idle_test.mock_tk import Var, Mbox +from idlelib.idle_test.mock_tk import Text as mockText + +def setUpModule(): + # Replace s-e module tkinter imports other than non-gui TclError. + se.BooleanVar = Var + se.StringVar = Var + se.tkMessageBox = Mbox + +def tearDownModule(): + # Restore 'just in case', though other tests should also replace. + se.BooleanVar = BooleanVar + se.StringVar = StringVar + se.tkMessageBox = tkMessageBox + + +class Mock: + def __init__(self, *args, **kwargs): pass + +class GetTest(unittest.TestCase): + # SearchEngine.get returns singleton created & saved on first call. + def test_get(self): + saved_Engine = se.SearchEngine + se.SearchEngine = Mock # monkey-patch class + try: + root = Mock() + engine = se.get(root) + self.assertIsInstance(engine, se.SearchEngine) + self.assertIs(root._searchengine, engine) + self.assertIs(se.get(root), engine) + finally: + se.SearchEngine = saved_Engine # restore class to module + +class GetLineColTest(unittest.TestCase): + # Test simple text-independent helper function + def test_get_line_col(self): + self.assertEqual(se.get_line_col('1.0'), (1, 0)) + self.assertEqual(se.get_line_col('1.11'), (1, 11)) + + self.assertRaises(ValueError, se.get_line_col, ('1.0 lineend')) + self.assertRaises(ValueError, se.get_line_col, ('end')) + +class GetSelectionTest(unittest.TestCase): + # Test text-dependent helper function. +## # Need gui for text.index('sel.first/sel.last/insert'). +## @classmethod +## def setUpClass(cls): +## requires('gui') +## cls.root = Tk() +## +## @classmethod +## def tearDownClass(cls): +## cls.root.destroy() +## del cls.root + + def test_get_selection(self): + # text = Text(master=self.root) + text = mockText() + text.insert('1.0', 'Hello World!') + + # fix text.index result when called in get_selection + def sel(s): + # select entire text, cursor irrelevant + if s == 'sel.first': return '1.0' + if s == 'sel.last': return '1.12' + raise TclError + text.index = sel # replaces .tag_add('sel', '1.0, '1.12') + self.assertEqual(se.get_selection(text), ('1.0', '1.12')) + + def mark(s): + # no selection, cursor after 'Hello' + if s == 'insert': return '1.5' + raise TclError + text.index = mark # replaces .mark_set('insert', '1.5') + self.assertEqual(se.get_selection(text), ('1.5', '1.5')) + + +class ReverseSearchTest(unittest.TestCase): + # Test helper function that searches backwards within a line. + def test_search_reverse(self): + Equal = self.assertEqual + line = "Here is an 'is' test text." + prog = re.compile('is') + Equal(se.search_reverse(prog, line, len(line)).span(), (12, 14)) + Equal(se.search_reverse(prog, line, 14).span(), (12, 14)) + Equal(se.search_reverse(prog, line, 13).span(), (5, 7)) + Equal(se.search_reverse(prog, line, 7).span(), (5, 7)) + Equal(se.search_reverse(prog, line, 6), None) + + +class SearchEngineTest(unittest.TestCase): + # Test class methods that do not use Text widget. + + def setUp(self): + self.engine = se.SearchEngine(root=None) + # Engine.root is only used to create error message boxes. + # The mock replacement ignores the root argument. + + def test_is_get(self): + engine = self.engine + Equal = self.assertEqual + + Equal(engine.getpat(), '') + engine.setpat('hello') + Equal(engine.getpat(), 'hello') + + Equal(engine.isre(), False) + engine.revar.set(1) + Equal(engine.isre(), True) + + Equal(engine.iscase(), False) + engine.casevar.set(1) + Equal(engine.iscase(), True) + + Equal(engine.isword(), False) + engine.wordvar.set(1) + Equal(engine.isword(), True) + + Equal(engine.iswrap(), True) + engine.wrapvar.set(0) + Equal(engine.iswrap(), False) + + Equal(engine.isback(), False) + engine.backvar.set(1) + Equal(engine.isback(), True) + + def test_setcookedpat(self): + engine = self.engine + engine.setcookedpat('\s') + self.assertEqual(engine.getpat(), '\s') + engine.revar.set(1) + engine.setcookedpat('\s') + self.assertEqual(engine.getpat(), r'\\s') + + def test_getcookedpat(self): + engine = self.engine + Equal = self.assertEqual + + Equal(engine.getcookedpat(), '') + engine.setpat('hello') + Equal(engine.getcookedpat(), 'hello') + engine.wordvar.set(True) + Equal(engine.getcookedpat(), r'\bhello\b') + engine.wordvar.set(False) + + engine.setpat('\s') + Equal(engine.getcookedpat(), r'\\s') + engine.revar.set(True) + Equal(engine.getcookedpat(), '\s') + + def test_getprog(self): + engine = self.engine + Equal = self.assertEqual + + engine.setpat('Hello') + temppat = engine.getprog() + Equal(temppat.pattern, re.compile('Hello', re.IGNORECASE).pattern) + engine.casevar.set(1) + temppat = engine.getprog() + Equal(temppat.pattern, re.compile('Hello').pattern, 0) + + engine.setpat('') + Equal(engine.getprog(), None) + engine.setpat('+') + engine.revar.set(1) + Equal(engine.getprog(), None) + self.assertEqual(Mbox.showerror.message, + 'Error: nothing to repeat\nPattern: +') + + def test_report_error(self): + showerror = Mbox.showerror + Equal = self.assertEqual + pat = '[a-z' + msg = 'unexpected end of regular expression' + + Equal(self.engine.report_error(pat, msg), None) + Equal(showerror.title, 'Regular expression error') + expected_message = ("Error: " + msg + "\nPattern: [a-z") + Equal(showerror.message, expected_message) + + Equal(self.engine.report_error(pat, msg, 5), None) + Equal(showerror.title, 'Regular expression error') + expected_message += "\nOffset: 5" + Equal(showerror.message, expected_message) + + +class SearchTest(unittest.TestCase): + # Test that search_text makes right call to right method. + + @classmethod + def setUpClass(cls): +## requires('gui') +## cls.root = Tk() +## cls.text = Text(master=cls.root) + cls.text = mockText() + test_text = ( + 'First line\n' + 'Line with target\n' + 'Last line\n') + cls.text.insert('1.0', test_text) + cls.pat = re.compile('target') + + cls.engine = se.SearchEngine(None) + cls.engine.search_forward = lambda *args: ('f', args) + cls.engine.search_backward = lambda *args: ('b', args) + +## @classmethod +## def tearDownClass(cls): +## cls.root.destroy() +## del cls.root + + def test_search(self): + Equal = self.assertEqual + engine = self.engine + search = engine.search_text + text = self.text + pat = self.pat + + engine.patvar.set(None) + #engine.revar.set(pat) + Equal(search(text), None) + + def mark(s): + # no selection, cursor after 'Hello' + if s == 'insert': return '1.5' + raise TclError + text.index = mark + Equal(search(text, pat), ('f', (text, pat, 1, 5, True, False))) + engine.wrapvar.set(False) + Equal(search(text, pat), ('f', (text, pat, 1, 5, False, False))) + engine.wrapvar.set(True) + engine.backvar.set(True) + Equal(search(text, pat), ('b', (text, pat, 1, 5, True, False))) + engine.backvar.set(False) + + def sel(s): + if s == 'sel.first': return '2.10' + if s == 'sel.last': return '2.16' + raise TclError + text.index = sel + Equal(search(text, pat), ('f', (text, pat, 2, 16, True, False))) + Equal(search(text, pat, True), ('f', (text, pat, 2, 10, True, True))) + engine.backvar.set(True) + Equal(search(text, pat), ('b', (text, pat, 2, 10, True, False))) + Equal(search(text, pat, True), ('b', (text, pat, 2, 16, True, True))) + + +class ForwardBackwardTest(unittest.TestCase): + # Test that search_forward method finds the target. +## @classmethod +## def tearDownClass(cls): +## cls.root.destroy() +## del cls.root + + @classmethod + def setUpClass(cls): + cls.engine = se.SearchEngine(None) +## requires('gui') +## cls.root = Tk() +## cls.text = Text(master=cls.root) + cls.text = mockText() + # search_backward calls index('end-1c') + cls.text.index = lambda index: '4.0' + test_text = ( + 'First line\n' + 'Line with target\n' + 'Last line\n') + cls.text.insert('1.0', test_text) + cls.pat = re.compile('target') + cls.res = (2, (10, 16)) # line, slice indexes of 'target' + cls.failpat = re.compile('xyz') # not in text + cls.emptypat = re.compile('\w*') # empty match possible + + def make_search(self, func): + def search(pat, line, col, wrap, ok=0): + res = func(self.text, pat, line, col, wrap, ok) + # res is (line, matchobject) or None + return (res[0], res[1].span()) if res else res + return search + + def test_search_forward(self): + # search for non-empty match + Equal = self.assertEqual + forward = self.make_search(self.engine.search_forward) + pat = self.pat + Equal(forward(pat, 1, 0, True), self.res) + Equal(forward(pat, 3, 0, True), self.res) # wrap + Equal(forward(pat, 3, 0, False), None) # no wrap + Equal(forward(pat, 2, 10, False), self.res) + + Equal(forward(self.failpat, 1, 0, True), None) + Equal(forward(self.emptypat, 2, 9, True, ok=True), (2, (9, 9))) + #Equal(forward(self.emptypat, 2, 9, True), self.res) + # While the initial empty match is correctly ignored, skipping + # the rest of the line and returning (3, (0,4)) seems buggy - tjr. + Equal(forward(self.emptypat, 2, 10, True), self.res) + + def test_search_backward(self): + # search for non-empty match + Equal = self.assertEqual + backward = self.make_search(self.engine.search_backward) + pat = self.pat + Equal(backward(pat, 3, 5, True), self.res) + Equal(backward(pat, 2, 0, True), self.res) # wrap + Equal(backward(pat, 2, 0, False), None) # no wrap + Equal(backward(pat, 2, 16, False), self.res) + + Equal(backward(self.failpat, 3, 9, True), None) + Equal(backward(self.emptypat, 2, 10, True, ok=True), (2, (9,9))) + # Accepted because 9 < 10, not because ok=True. + # It is not clear that ok=True is useful going back - tjr + Equal(backward(self.emptypat, 2, 9, True), (2, (5, 9))) + + +if __name__ == '__main__': + unittest.main(verbosity=2, exit=2) diff --git a/lib/python2.7/idlelib/idle_test/test_text.py b/lib/python2.7/idlelib/idle_test/test_text.py new file mode 100644 index 0000000..50d3fac --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/test_text.py @@ -0,0 +1,227 @@ +# Test mock_tk.Text class against tkinter.Text class by running same tests with both. +import unittest +from test.test_support import requires + +from _tkinter import TclError + +class TextTest(object): + + hw = 'hello\nworld' # usual initial insert after initialization + hwn = hw+'\n' # \n present at initialization, before insert + + Text = None + def setUp(self): + self.text = self.Text() + + def test_init(self): + self.assertEqual(self.text.get('1.0'), '\n') + self.assertEqual(self.text.get('end'), '') + + def test_index_empty(self): + index = self.text.index + + for dex in (-1.0, 0.3, '1.-1', '1.0', '1.0 lineend', '1.end', '1.33', + 'insert'): + self.assertEqual(index(dex), '1.0') + + for dex in 'end', 2.0, '2.1', '33.44': + self.assertEqual(index(dex), '2.0') + + def test_index_data(self): + index = self.text.index + self.text.insert('1.0', self.hw) + + for dex in -1.0, 0.3, '1.-1', '1.0': + self.assertEqual(index(dex), '1.0') + + for dex in '1.0 lineend', '1.end', '1.33': + self.assertEqual(index(dex), '1.5') + + for dex in 'end', '33.44': + self.assertEqual(index(dex), '3.0') + + def test_get(self): + get = self.text.get + Equal = self.assertEqual + self.text.insert('1.0', self.hw) + + Equal(get('end'), '') + Equal(get('end', 'end'), '') + Equal(get('1.0'), 'h') + Equal(get('1.0', '1.1'), 'h') + Equal(get('1.0', '1.3'), 'hel') + Equal(get('1.1', '1.3'), 'el') + Equal(get('1.0', '1.0 lineend'), 'hello') + Equal(get('1.0', '1.10'), 'hello') + Equal(get('1.0 lineend'), '\n') + Equal(get('1.1', '2.3'), 'ello\nwor') + Equal(get('1.0', '2.5'), self.hw) + Equal(get('1.0', 'end'), self.hwn) + Equal(get('0.0', '5.0'), self.hwn) + + def test_insert(self): + insert = self.text.insert + get = self.text.get + Equal = self.assertEqual + + insert('1.0', self.hw) + Equal(get('1.0', 'end'), self.hwn) + + insert('1.0', '') # nothing + Equal(get('1.0', 'end'), self.hwn) + + insert('1.0', '*') + Equal(get('1.0', 'end'), '*hello\nworld\n') + + insert('1.0 lineend', '*') + Equal(get('1.0', 'end'), '*hello*\nworld\n') + + insert('2.3', '*') + Equal(get('1.0', 'end'), '*hello*\nwor*ld\n') + + insert('end', 'x') + Equal(get('1.0', 'end'), '*hello*\nwor*ldx\n') + + insert('1.4', 'x\n') + Equal(get('1.0', 'end'), '*helx\nlo*\nwor*ldx\n') + + def test_no_delete(self): + # if index1 == 'insert' or 'end' or >= end, there is no deletion + delete = self.text.delete + get = self.text.get + Equal = self.assertEqual + self.text.insert('1.0', self.hw) + + delete('insert') + Equal(get('1.0', 'end'), self.hwn) + + delete('end') + Equal(get('1.0', 'end'), self.hwn) + + delete('insert', 'end') + Equal(get('1.0', 'end'), self.hwn) + + delete('insert', '5.5') + Equal(get('1.0', 'end'), self.hwn) + + delete('1.4', '1.0') + Equal(get('1.0', 'end'), self.hwn) + + delete('1.4', '1.4') + Equal(get('1.0', 'end'), self.hwn) + + def test_delete_char(self): + delete = self.text.delete + get = self.text.get + Equal = self.assertEqual + self.text.insert('1.0', self.hw) + + delete('1.0') + Equal(get('1.0', '1.end'), 'ello') + + delete('1.0', '1.1') + Equal(get('1.0', '1.end'), 'llo') + + # delete \n and combine 2 lines into 1 + delete('1.end') + Equal(get('1.0', '1.end'), 'lloworld') + + self.text.insert('1.3', '\n') + delete('1.10') + Equal(get('1.0', '1.end'), 'lloworld') + + self.text.insert('1.3', '\n') + delete('1.3', '2.0') + Equal(get('1.0', '1.end'), 'lloworld') + + def test_delete_slice(self): + delete = self.text.delete + get = self.text.get + Equal = self.assertEqual + self.text.insert('1.0', self.hw) + + delete('1.0', '1.0 lineend') + Equal(get('1.0', 'end'), '\nworld\n') + + delete('1.0', 'end') + Equal(get('1.0', 'end'), '\n') + + self.text.insert('1.0', self.hw) + delete('1.0', '2.0') + Equal(get('1.0', 'end'), 'world\n') + + delete('1.0', 'end') + Equal(get('1.0', 'end'), '\n') + + self.text.insert('1.0', self.hw) + delete('1.2', '2.3') + Equal(get('1.0', 'end'), 'held\n') + + def test_multiple_lines(self): # insert and delete + self.text.insert('1.0', 'hello') + + self.text.insert('1.3', '1\n2\n3\n4\n5') + self.assertEqual(self.text.get('1.0', 'end'), 'hel1\n2\n3\n4\n5lo\n') + + self.text.delete('1.3', '5.1') + self.assertEqual(self.text.get('1.0', 'end'), 'hello\n') + + def test_compare(self): + compare = self.text.compare + Equal = self.assertEqual + # need data so indexes not squished to 1,0 + self.text.insert('1.0', 'First\nSecond\nThird\n') + + self.assertRaises(TclError, compare, '2.2', 'op', '2.2') + + for op, less1, less0, equal, greater0, greater1 in ( + ('<', True, True, False, False, False), + ('<=', True, True, True, False, False), + ('>', False, False, False, True, True), + ('>=', False, False, True, True, True), + ('==', False, False, True, False, False), + ('!=', True, True, False, True, True), + ): + Equal(compare('1.1', op, '2.2'), less1, op) + Equal(compare('2.1', op, '2.2'), less0, op) + Equal(compare('2.2', op, '2.2'), equal, op) + Equal(compare('2.3', op, '2.2'), greater0, op) + Equal(compare('3.3', op, '2.2'), greater1, op) + + +class MockTextTest(TextTest, unittest.TestCase): + + @classmethod + def setUpClass(cls): + from idlelib.idle_test.mock_tk import Text + cls.Text = Text + + def test_decode(self): + # test endflags (-1, 0) not tested by test_index (which uses +1) + decode = self.text._decode + Equal = self.assertEqual + self.text.insert('1.0', self.hw) + + Equal(decode('end', -1), (2, 5)) + Equal(decode('3.1', -1), (2, 5)) + Equal(decode('end', 0), (2, 6)) + Equal(decode('3.1', 0), (2, 6)) + + +class TkTextTest(TextTest, unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + from Tkinter import Tk, Text + cls.Text = Text + cls.root = Tk() + + @classmethod + def tearDownClass(cls): + cls.root.destroy() + del cls.root + + +if __name__ == '__main__': + unittest.main(verbosity=2, exit=False) diff --git a/lib/python2.7/idlelib/idle_test/test_textview.py b/lib/python2.7/idlelib/idle_test/test_textview.py new file mode 100644 index 0000000..fa437fc --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/test_textview.py @@ -0,0 +1,96 @@ +'''Test the functions and main class method of textView.py.''' + +import unittest +import os +from test.test_support import requires +from Tkinter import Tk +from idlelib import textView as tv +from idlelib.idle_test.mock_idle import Func +from idlelib.idle_test.mock_tk import Mbox + + +class TV(tv.TextViewer): # Use in TextViewTest + transient = Func() + grab_set = Func() + wait_window = Func() + +class textviewClassTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = Tk() + cls.root.withdraw() + + @classmethod + def tearDownClass(cls): + cls.root.destroy() + del cls.root + + def setUp(self): + TV.transient.__init__() + TV.grab_set.__init__() + TV.wait_window.__init__() + + def test_init_modal(self): + view = TV(self.root, 'Title', 'test text') + self.assertTrue(TV.transient.called) + self.assertTrue(TV.grab_set.called) + self.assertTrue(TV.wait_window.called) + view.Ok() + + def test_init_nonmodal(self): + view = TV(self.root, 'Title', 'test text', modal=False) + self.assertFalse(TV.transient.called) + self.assertFalse(TV.grab_set.called) + self.assertFalse(TV.wait_window.called) + view.Ok() + + def test_ok(self): + view = TV(self.root, 'Title', 'test text', modal=False) + view.destroy = Func() + view.Ok() + self.assertTrue(view.destroy.called) + del view.destroy # Unmask the real function. + view.destroy() + + +class ViewFunctionTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = Tk() + cls.root.withdraw() + cls.orig_mbox = tv.tkMessageBox + tv.tkMessageBox = Mbox + + @classmethod + def tearDownClass(cls): + cls.root.destroy() + del cls.root + tv.tkMessageBox = cls.orig_mbox + del cls.orig_mbox + + def test_view_text(self): + # If modal True, get tkinter error 'can't invoke "event" command'. + view = tv.view_text(self.root, 'Title', 'test text', modal=False) + self.assertIsInstance(view, tv.TextViewer) + view.Ok() + + def test_view_file(self): + test_dir = os.path.dirname(__file__) + testfile = os.path.join(test_dir, 'test_textview.py') + view = tv.view_file(self.root, 'Title', testfile, modal=False) + self.assertIsInstance(view, tv.TextViewer) + self.assertIn('Test', view.textView.get('1.0', '1.end')) + view.Ok() + + # Mock messagebox will be used; view_file will return None. + testfile = os.path.join(test_dir, '../notthere.py') + view = tv.view_file(self.root, 'Title', testfile, modal=False) + self.assertIsNone(view) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/lib/python2.7/idlelib/idle_test/test_warning.py b/lib/python2.7/idlelib/idle_test/test_warning.py new file mode 100644 index 0000000..da1d8a1 --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/test_warning.py @@ -0,0 +1,73 @@ +'''Test warnings replacement in PyShell.py and run.py. + +This file could be expanded to include traceback overrides +(in same two modules). If so, change name. +Revise if output destination changes (http://bugs.python.org/issue18318). +Make sure warnings module is left unaltered (http://bugs.python.org/issue18081). +''' + +import unittest +from test.test_support import captured_stderr + +import warnings +# Try to capture default showwarning before Idle modules are imported. +showwarning = warnings.showwarning +# But if we run this file within idle, we are in the middle of the run.main loop +# and default showwarnings has already been replaced. +running_in_idle = 'idle' in showwarning.__name__ + +from idlelib import run +from idlelib import PyShell as shell + +# The following was generated from PyShell.idle_formatwarning +# and checked as matching expectation. +idlemsg = ''' +Warning (from warnings module): + File "test_warning.py", line 99 + Line of code +UserWarning: Test +''' +shellmsg = idlemsg + ">>> " + +class RunWarnTest(unittest.TestCase): + + @unittest.skipIf(running_in_idle, "Does not work when run within Idle.") + def test_showwarnings(self): + self.assertIs(warnings.showwarning, showwarning) + run.capture_warnings(True) + self.assertIs(warnings.showwarning, run.idle_showwarning_subproc) + run.capture_warnings(False) + self.assertIs(warnings.showwarning, showwarning) + + def test_run_show(self): + with captured_stderr() as f: + run.idle_showwarning_subproc( + 'Test', UserWarning, 'test_warning.py', 99, f, 'Line of code') + # The following uses .splitlines to erase line-ending differences + self.assertEqual(idlemsg.splitlines(), f.getvalue().splitlines()) + +class ShellWarnTest(unittest.TestCase): + + @unittest.skipIf(running_in_idle, "Does not work when run within Idle.") + def test_showwarnings(self): + self.assertIs(warnings.showwarning, showwarning) + shell.capture_warnings(True) + self.assertIs(warnings.showwarning, shell.idle_showwarning) + shell.capture_warnings(False) + self.assertIs(warnings.showwarning, showwarning) + + def test_idle_formatter(self): + # Will fail if format changed without regenerating idlemsg + s = shell.idle_formatwarning( + 'Test', UserWarning, 'test_warning.py', 99, 'Line of code') + self.assertEqual(idlemsg, s) + + def test_shell_show(self): + with captured_stderr() as f: + shell.idle_showwarning( + 'Test', UserWarning, 'test_warning.py', 99, f, 'Line of code') + self.assertEqual(shellmsg.splitlines(), f.getvalue().splitlines()) + + +if __name__ == '__main__': + unittest.main(verbosity=2, exit=False) diff --git a/lib/python2.7/idlelib/idle_test/test_widgetredir.py b/lib/python2.7/idlelib/idle_test/test_widgetredir.py new file mode 100644 index 0000000..e35ea41 --- /dev/null +++ b/lib/python2.7/idlelib/idle_test/test_widgetredir.py @@ -0,0 +1,124 @@ +"""Unittest for idlelib.WidgetRedirector + +100% coverage +""" +from test.test_support import requires +import unittest +from idlelib.idle_test.mock_idle import Func +from Tkinter import Tk, Text, TclError +from idlelib.WidgetRedirector import WidgetRedirector + + +class InitCloseTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = Tk() + cls.root.withdraw() + cls.text = Text(cls.root) + + @classmethod + def tearDownClass(cls): + del cls.text + cls.root.destroy() + del cls.root + + def test_init(self): + redir = WidgetRedirector(self.text) + self.assertEqual(redir.widget, self.text) + self.assertEqual(redir.tk, self.text.tk) + self.assertRaises(TclError, WidgetRedirector, self.text) + redir.close() # restore self.tk, self.text + + def test_close(self): + redir = WidgetRedirector(self.text) + redir.register('insert', Func) + redir.close() + self.assertEqual(redir._operations, {}) + self.assertFalse(hasattr(self.text, 'widget')) + + +class WidgetRedirectorTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = Tk() + cls.root.withdraw() + cls.text = Text(cls.root) + + @classmethod + def tearDownClass(cls): + del cls.text + cls.root.destroy() + del cls.root + + def setUp(self): + self.redir = WidgetRedirector(self.text) + self.func = Func() + self.orig_insert = self.redir.register('insert', self.func) + self.text.insert('insert', 'asdf') # leaves self.text empty + + def tearDown(self): + self.text.delete('1.0', 'end') + self.redir.close() + + def test_repr(self): # partly for 100% coverage + self.assertIn('Redirector', repr(self.redir)) + self.assertIn('Original', repr(self.orig_insert)) + + def test_register(self): + self.assertEqual(self.text.get('1.0', 'end'), '\n') + self.assertEqual(self.func.args, ('insert', 'asdf')) + self.assertIn('insert', self.redir._operations) + self.assertIn('insert', self.text.__dict__) + self.assertEqual(self.text.insert, self.func) + + def test_original_command(self): + self.assertEqual(self.orig_insert.operation, 'insert') + self.assertEqual(self.orig_insert.tk_call, self.text.tk.call) + self.orig_insert('insert', 'asdf') + self.assertEqual(self.text.get('1.0', 'end'), 'asdf\n') + + def test_unregister(self): + self.assertIsNone(self.redir.unregister('invalid operation name')) + self.assertEqual(self.redir.unregister('insert'), self.func) + self.assertNotIn('insert', self.redir._operations) + self.assertNotIn('insert', self.text.__dict__) + + def test_unregister_no_attribute(self): + del self.text.insert + self.assertEqual(self.redir.unregister('insert'), self.func) + + def test_dispatch_intercept(self): + self.func.__init__(True) + self.assertTrue(self.redir.dispatch('insert', False)) + self.assertFalse(self.func.args[0]) + + def test_dispatch_bypass(self): + self.orig_insert('insert', 'asdf') + # tk.call returns '' where Python would return None + self.assertEqual(self.redir.dispatch('delete', '1.0', 'end'), '') + self.assertEqual(self.text.get('1.0', 'end'), '\n') + + def test_dispatch_error(self): + self.func.__init__(TclError()) + self.assertEqual(self.redir.dispatch('insert', False), '') + self.assertEqual(self.redir.dispatch('invalid'), '') + + def test_command_dispatch(self): + # Test that .__init__ causes redirection of tk calls + # through redir.dispatch + self.root.call(self.text._w, 'insert', 'hello') + self.assertEqual(self.func.args, ('hello',)) + self.assertEqual(self.text.get('1.0', 'end'), '\n') + # Ensure that called through redir .dispatch and not through + # self.text.insert by having mock raise TclError. + self.func.__init__(TclError()) + self.assertEqual(self.root.call(self.text._w, 'insert', 'boo'), '') + + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/lib/python2.7/idlelib/idlever.py b/lib/python2.7/idlelib/idlever.py new file mode 100644 index 0000000..3e9f69a --- /dev/null +++ b/lib/python2.7/idlelib/idlever.py @@ -0,0 +1,12 @@ +""" +The separate Idle version was eliminated years ago; +idlelib.idlever is no longer used by Idle +and will be removed in 3.6 or later. Use + from sys import version + IDLE_VERSION = version[:version.index(' ')] +""" +# Kept for now only for possible existing extension use +import warnings as w +w.warn(__doc__, DeprecationWarning, stacklevel=2) +from sys import version +IDLE_VERSION = version[:version.index(' ')] diff --git a/lib/python2.7/idlelib/keybindingDialog.py b/lib/python2.7/idlelib/keybindingDialog.py new file mode 100644 index 0000000..4d32ca9 --- /dev/null +++ b/lib/python2.7/idlelib/keybindingDialog.py @@ -0,0 +1,266 @@ +""" +Dialog for building Tkinter accelerator key bindings +""" +from Tkinter import * +import tkMessageBox +import string +import sys + +class GetKeysDialog(Toplevel): + def __init__(self,parent,title,action,currentKeySequences,_htest=False): + """ + action - string, the name of the virtual event these keys will be + mapped to + currentKeys - list, a list of all key sequence lists currently mapped + to virtual events, for overlap checking + _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.action=action + self.currentKeySequences=currentKeySequences + self.result='' + self.keyString=StringVar(self) + self.keyString.set('') + self.SetModifiersForPlatform() # set self.modifiers, self.modifier_label + self.modifier_vars = [] + for modifier in self.modifiers: + variable = StringVar(self) + variable.set('') + self.modifier_vars.append(variable) + self.advanced = False + self.CreateWidgets() + self.LoadFinalKeyList() + self.withdraw() #hide while setting geometry + self.update_idletasks() + self.geometry( + "+%d+%d" % ( + parent.winfo_rootx() + + (parent.winfo_width()/2 - self.winfo_reqwidth()/2), + parent.winfo_rooty() + + ((parent.winfo_height()/2 - self.winfo_reqheight()/2) + if not _htest else 150) + ) ) #centre dialog over parent (or below htest box) + self.deiconify() #geometry set, unhide + self.wait_window() + + def CreateWidgets(self): + frameMain = Frame(self,borderwidth=2,relief=SUNKEN) + frameMain.pack(side=TOP,expand=TRUE,fill=BOTH) + frameButtons=Frame(self) + frameButtons.pack(side=BOTTOM,fill=X) + self.buttonOK = Button(frameButtons,text='OK', + width=8,command=self.OK) + self.buttonOK.grid(row=0,column=0,padx=5,pady=5) + self.buttonCancel = Button(frameButtons,text='Cancel', + width=8,command=self.Cancel) + self.buttonCancel.grid(row=0,column=1,padx=5,pady=5) + self.frameKeySeqBasic = Frame(frameMain) + self.frameKeySeqAdvanced = Frame(frameMain) + self.frameControlsBasic = Frame(frameMain) + self.frameHelpAdvanced = Frame(frameMain) + self.frameKeySeqAdvanced.grid(row=0,column=0,sticky=NSEW,padx=5,pady=5) + self.frameKeySeqBasic.grid(row=0,column=0,sticky=NSEW,padx=5,pady=5) + self.frameKeySeqBasic.lift() + self.frameHelpAdvanced.grid(row=1,column=0,sticky=NSEW,padx=5) + self.frameControlsBasic.grid(row=1,column=0,sticky=NSEW,padx=5) + self.frameControlsBasic.lift() + self.buttonLevel = Button(frameMain,command=self.ToggleLevel, + text='Advanced Key Binding Entry >>') + self.buttonLevel.grid(row=2,column=0,stick=EW,padx=5,pady=5) + labelTitleBasic = Label(self.frameKeySeqBasic, + text="New keys for '"+self.action+"' :") + labelTitleBasic.pack(anchor=W) + labelKeysBasic = Label(self.frameKeySeqBasic,justify=LEFT, + textvariable=self.keyString,relief=GROOVE,borderwidth=2) + labelKeysBasic.pack(ipadx=5,ipady=5,fill=X) + self.modifier_checkbuttons = {} + column = 0 + for modifier, variable in zip(self.modifiers, self.modifier_vars): + label = self.modifier_label.get(modifier, modifier) + check=Checkbutton(self.frameControlsBasic, + command=self.BuildKeyString, + text=label,variable=variable,onvalue=modifier,offvalue='') + check.grid(row=0,column=column,padx=2,sticky=W) + self.modifier_checkbuttons[modifier] = check + column += 1 + labelFnAdvice=Label(self.frameControlsBasic,justify=LEFT, + text=\ + "Select the desired modifier keys\n"+ + "above, and the final key from the\n"+ + "list on the right.\n\n" + + "Use upper case Symbols when using\n" + + "the Shift modifier. (Letters will be\n" + + "converted automatically.)") + labelFnAdvice.grid(row=1,column=0,columnspan=4,padx=2,sticky=W) + self.listKeysFinal=Listbox(self.frameControlsBasic,width=15,height=10, + selectmode=SINGLE) + self.listKeysFinal.bind('<ButtonRelease-1>',self.FinalKeySelected) + self.listKeysFinal.grid(row=0,column=4,rowspan=4,sticky=NS) + scrollKeysFinal=Scrollbar(self.frameControlsBasic,orient=VERTICAL, + command=self.listKeysFinal.yview) + self.listKeysFinal.config(yscrollcommand=scrollKeysFinal.set) + scrollKeysFinal.grid(row=0,column=5,rowspan=4,sticky=NS) + self.buttonClear=Button(self.frameControlsBasic, + text='Clear Keys',command=self.ClearKeySeq) + self.buttonClear.grid(row=2,column=0,columnspan=4) + labelTitleAdvanced = Label(self.frameKeySeqAdvanced,justify=LEFT, + text="Enter new binding(s) for '"+self.action+"' :\n"+ + "(These bindings will not be checked for validity!)") + labelTitleAdvanced.pack(anchor=W) + self.entryKeysAdvanced=Entry(self.frameKeySeqAdvanced, + textvariable=self.keyString) + self.entryKeysAdvanced.pack(fill=X) + labelHelpAdvanced=Label(self.frameHelpAdvanced,justify=LEFT, + text="Key bindings are specified using Tkinter keysyms as\n"+ + "in these samples: <Control-f>, <Shift-F2>, <F12>,\n" + "<Control-space>, <Meta-less>, <Control-Alt-Shift-X>.\n" + "Upper case is used when the Shift modifier is present!\n\n" + + "'Emacs style' multi-keystroke bindings are specified as\n" + + "follows: <Control-x><Control-y>, where the first key\n" + + "is the 'do-nothing' keybinding.\n\n" + + "Multiple separate bindings for one action should be\n"+ + "separated by a space, eg., <Alt-v> <Meta-v>." ) + labelHelpAdvanced.grid(row=0,column=0,sticky=NSEW) + + def SetModifiersForPlatform(self): + """Determine list of names of key modifiers for this platform. + + The names are used to build Tk bindings -- it doesn't matter if the + keyboard has these keys, it matters if Tk understands them. The + order is also important: key binding equality depends on it, so + config-keys.def must use the same ordering. + """ + if sys.platform == "darwin": + self.modifiers = ['Shift', 'Control', 'Option', 'Command'] + else: + self.modifiers = ['Control', 'Alt', 'Shift'] + self.modifier_label = {'Control': 'Ctrl'} # short name + + def ToggleLevel(self): + if self.buttonLevel.cget('text')[:8]=='Advanced': + self.ClearKeySeq() + self.buttonLevel.config(text='<< Basic Key Binding Entry') + self.frameKeySeqAdvanced.lift() + self.frameHelpAdvanced.lift() + self.entryKeysAdvanced.focus_set() + self.advanced = True + else: + self.ClearKeySeq() + self.buttonLevel.config(text='Advanced Key Binding Entry >>') + self.frameKeySeqBasic.lift() + self.frameControlsBasic.lift() + self.advanced = False + + def FinalKeySelected(self,event): + self.BuildKeyString() + + def BuildKeyString(self): + keyList = modifiers = self.GetModifiers() + finalKey = self.listKeysFinal.get(ANCHOR) + if finalKey: + finalKey = self.TranslateKey(finalKey, modifiers) + keyList.append(finalKey) + self.keyString.set('<' + string.join(keyList,'-') + '>') + + def GetModifiers(self): + modList = [variable.get() for variable in self.modifier_vars] + return [mod for mod in modList if mod] + + def ClearKeySeq(self): + self.listKeysFinal.select_clear(0,END) + self.listKeysFinal.yview(MOVETO, '0.0') + for variable in self.modifier_vars: + variable.set('') + self.keyString.set('') + + def LoadFinalKeyList(self): + #these tuples are also available for use in validity checks + self.functionKeys=('F1','F2','F2','F4','F5','F6','F7','F8','F9', + 'F10','F11','F12') + self.alphanumKeys=tuple(string.ascii_lowercase+string.digits) + self.punctuationKeys=tuple('~!@#%^&*()_-+={}[]|;:,.<>/?') + self.whitespaceKeys=('Tab','Space','Return') + self.editKeys=('BackSpace','Delete','Insert') + self.moveKeys=('Home','End','Page Up','Page Down','Left Arrow', + 'Right Arrow','Up Arrow','Down Arrow') + #make a tuple of most of the useful common 'final' keys + keys=(self.alphanumKeys+self.punctuationKeys+self.functionKeys+ + self.whitespaceKeys+self.editKeys+self.moveKeys) + self.listKeysFinal.insert(END, *keys) + + def TranslateKey(self, key, modifiers): + "Translate from keycap symbol to the Tkinter keysym" + translateDict = {'Space':'space', + '~':'asciitilde','!':'exclam','@':'at','#':'numbersign', + '%':'percent','^':'asciicircum','&':'ampersand','*':'asterisk', + '(':'parenleft',')':'parenright','_':'underscore','-':'minus', + '+':'plus','=':'equal','{':'braceleft','}':'braceright', + '[':'bracketleft',']':'bracketright','|':'bar',';':'semicolon', + ':':'colon',',':'comma','.':'period','<':'less','>':'greater', + '/':'slash','?':'question','Page Up':'Prior','Page Down':'Next', + 'Left Arrow':'Left','Right Arrow':'Right','Up Arrow':'Up', + 'Down Arrow': 'Down', 'Tab':'Tab'} + if key in translateDict.keys(): + key = translateDict[key] + if 'Shift' in modifiers and key in string.ascii_lowercase: + key = key.upper() + key = 'Key-' + key + return key + + def OK(self, event=None): + if self.advanced or self.KeysOK(): # doesn't check advanced string yet + self.result=self.keyString.get() + self.destroy() + + def Cancel(self, event=None): + self.result='' + self.destroy() + + def KeysOK(self): + '''Validity check on user's 'basic' keybinding selection. + + Doesn't check the string produced by the advanced dialog because + 'modifiers' isn't set. + + ''' + keys = self.keyString.get() + keys.strip() + finalKey = self.listKeysFinal.get(ANCHOR) + modifiers = self.GetModifiers() + # create a key sequence list for overlap check: + keySequence = keys.split() + keysOK = False + title = 'Key Sequence Error' + if not keys: + tkMessageBox.showerror(title=title, parent=self, + message='No keys specified.') + elif not keys.endswith('>'): + tkMessageBox.showerror(title=title, parent=self, + message='Missing the final Key') + elif (not modifiers + and finalKey not in self.functionKeys + self.moveKeys): + tkMessageBox.showerror(title=title, parent=self, + message='No modifier key(s) specified.') + elif (modifiers == ['Shift']) \ + and (finalKey not in + self.functionKeys + self.moveKeys + ('Tab', 'Space')): + msg = 'The shift modifier by itself may not be used with'\ + ' this key symbol.' + tkMessageBox.showerror(title=title, parent=self, message=msg) + elif keySequence in self.currentKeySequences: + msg = 'This key combination is already in use.' + tkMessageBox.showerror(title=title, parent=self, message=msg) + else: + keysOK = True + return keysOK + +if __name__ == '__main__': + from idlelib.idle_test.htest import run + run(GetKeysDialog) diff --git a/lib/python2.7/idlelib/macosxSupport.py b/lib/python2.7/idlelib/macosxSupport.py new file mode 100644 index 0000000..041d700 --- /dev/null +++ b/lib/python2.7/idlelib/macosxSupport.py @@ -0,0 +1,237 @@ +""" +A number of functions that enhance IDLE on Mac OSX. +""" +import sys +import Tkinter +from os import path + + +import warnings + +def runningAsOSXApp(): + warnings.warn("runningAsOSXApp() is deprecated, use isAquaTk()", + DeprecationWarning, stacklevel=2) + return isAquaTk() + +def isCarbonAquaTk(root): + warnings.warn("isCarbonAquaTk(root) is deprecated, use isCarbonTk()", + DeprecationWarning, stacklevel=2) + return isCarbonTk() + +_tk_type = None + +def _initializeTkVariantTests(root): + """ + Initializes OS X Tk variant values for + isAquaTk(), isCarbonTk(), isCocoaTk(), and isXQuartz(). + """ + global _tk_type + if sys.platform == 'darwin': + ws = root.tk.call('tk', 'windowingsystem') + if 'x11' in ws: + _tk_type = "xquartz" + elif 'aqua' not in ws: + _tk_type = "other" + elif 'AppKit' in root.tk.call('winfo', 'server', '.'): + _tk_type = "cocoa" + else: + _tk_type = "carbon" + else: + _tk_type = "other" + +def isAquaTk(): + """ + Returns True if IDLE is using a native OS X Tk (Cocoa or Carbon). + """ + assert _tk_type is not None + return _tk_type == "cocoa" or _tk_type == "carbon" + +def isCarbonTk(): + """ + Returns True if IDLE is using a Carbon Aqua Tk (instead of the + newer Cocoa Aqua Tk). + """ + assert _tk_type is not None + return _tk_type == "carbon" + +def isCocoaTk(): + """ + Returns True if IDLE is using a Cocoa Aqua Tk. + """ + assert _tk_type is not None + return _tk_type == "cocoa" + +def isXQuartz(): + """ + Returns True if IDLE is using an OS X X11 Tk. + """ + assert _tk_type is not None + return _tk_type == "xquartz" + +def tkVersionWarning(root): + """ + Returns a string warning message if the Tk version in use appears to + be one known to cause problems with IDLE. + 1. Apple Cocoa-based Tk 8.5.7 shipped with Mac OS X 10.6 is unusable. + 2. Apple Cocoa-based Tk 8.5.9 in OS X 10.7 and 10.8 is better but + can still crash unexpectedly. + """ + + if isCocoaTk(): + patchlevel = root.tk.call('info', 'patchlevel') + if patchlevel not in ('8.5.7', '8.5.9'): + return False + return (r"WARNING: The version of Tcl/Tk ({0}) in use may" + r" be unstable.\n" + r"Visit http://www.python.org/download/mac/tcltk/" + r" for current information.".format(patchlevel)) + else: + return False + +def addOpenEventSupport(root, flist): + """ + This ensures that the application will respond to open AppleEvents, which + makes is feasible to use IDLE as the default application for python files. + """ + def doOpenFile(*args): + for fn in args: + flist.open(fn) + + # The command below is a hook in aquatk that is called whenever the app + # receives a file open event. The callback can have multiple arguments, + # one for every file that should be opened. + root.createcommand("::tk::mac::OpenDocument", doOpenFile) + +def hideTkConsole(root): + try: + root.tk.call('console', 'hide') + except Tkinter.TclError: + # Some versions of the Tk framework don't have a console object + pass + +def overrideRootMenu(root, flist): + """ + Replace the Tk root menu by something that is more appropriate for + IDLE with an Aqua Tk. + """ + # The menu that is attached to the Tk root (".") is also used by AquaTk for + # all windows that don't specify a menu of their own. The default menubar + # contains a number of menus, none of which are appropriate for IDLE. The + # Most annoying of those is an 'About Tck/Tk...' menu in the application + # menu. + # + # This function replaces the default menubar by a mostly empty one, it + # should only contain the correct application menu and the window menu. + # + # Due to a (mis-)feature of TkAqua the user will also see an empty Help + # menu. + from Tkinter import Menu + from idlelib import Bindings + from idlelib import WindowList + + closeItem = Bindings.menudefs[0][1][-2] + + # Remove the last 3 items of the file menu: a separator, close window and + # quit. Close window will be reinserted just above the save item, where + # it should be according to the HIG. Quit is in the application menu. + del Bindings.menudefs[0][1][-3:] + Bindings.menudefs[0][1].insert(6, closeItem) + + # Remove the 'About' entry from the help menu, it is in the application + # menu + del Bindings.menudefs[-1][1][0:2] + # Remove the 'Configure Idle' entry from the options menu, it is in the + # application menu as 'Preferences' + del Bindings.menudefs[-2][1][0] + menubar = Menu(root) + root.configure(menu=menubar) + menudict = {} + + menudict['windows'] = menu = Menu(menubar, name='windows', tearoff=0) + menubar.add_cascade(label='Window', menu=menu, underline=0) + + def postwindowsmenu(menu=menu): + end = menu.index('end') + if end is None: + end = -1 + + if end > 0: + menu.delete(0, end) + WindowList.add_windows_to_menu(menu) + WindowList.register_callback(postwindowsmenu) + + def about_dialog(event=None): + "Handle Help 'About IDLE' event." + # Synchronize with EditorWindow.EditorWindow.about_dialog. + from idlelib import aboutDialog + aboutDialog.AboutDialog(root, 'About IDLE') + + def config_dialog(event=None): + "Handle Options 'Configure IDLE' event." + # Synchronize with EditorWindow.EditorWindow.config_dialog. + from idlelib import configDialog + root.instance_dict = flist.inversedict + configDialog.ConfigDialog(root, 'Settings') + + def help_dialog(event=None): + "Handle Help 'IDLE Help' event." + # Synchronize with EditorWindow.EditorWindow.help_dialog. + from idlelib import help + help.show_idlehelp(root) + + root.bind('<<about-idle>>', about_dialog) + root.bind('<<open-config-dialog>>', config_dialog) + root.createcommand('::tk::mac::ShowPreferences', config_dialog) + if flist: + root.bind('<<close-all-windows>>', flist.close_all_callback) + + # The binding above doesn't reliably work on all versions of Tk + # on MacOSX. Adding command definition below does seem to do the + # right thing for now. + root.createcommand('exit', flist.close_all_callback) + + if isCarbonTk(): + # for Carbon AquaTk, replace the default Tk apple menu + menudict['application'] = menu = Menu(menubar, name='apple', + tearoff=0) + menubar.add_cascade(label='IDLE', menu=menu) + Bindings.menudefs.insert(0, + ('application', [ + ('About IDLE', '<<about-idle>>'), + None, + ])) + tkversion = root.tk.eval('info patchlevel') + if tuple(map(int, tkversion.split('.'))) < (8, 4, 14): + # for earlier AquaTk versions, supply a Preferences menu item + Bindings.menudefs[0][1].append( + ('_Preferences....', '<<open-config-dialog>>'), + ) + if isCocoaTk(): + # replace default About dialog with About IDLE one + root.createcommand('tkAboutDialog', about_dialog) + # replace default "Help" item in Help menu + root.createcommand('::tk::mac::ShowHelp', help_dialog) + # remove redundant "IDLE Help" from menu + del Bindings.menudefs[-1][1][0] + +def setupApp(root, flist): + """ + Perform initial OS X customizations if needed. + Called from PyShell.main() after initial calls to Tk() + + There are currently three major versions of Tk in use on OS X: + 1. Aqua Cocoa Tk (native default since OS X 10.6) + 2. Aqua Carbon Tk (original native, 32-bit only, deprecated) + 3. X11 (supported by some third-party distributors, deprecated) + There are various differences among the three that affect IDLE + behavior, primarily with menus, mouse key events, and accelerators. + Some one-time customizations are performed here. + Others are dynamically tested throughout idlelib by calls to the + isAquaTk(), isCarbonTk(), isCocoaTk(), isXQuartz() functions which + are initialized here as well. + """ + _initializeTkVariantTests(root) + if isAquaTk(): + hideTkConsole(root) + overrideRootMenu(root, flist) + addOpenEventSupport(root, flist) diff --git a/lib/python2.7/idlelib/rpc.py b/lib/python2.7/idlelib/rpc.py new file mode 100644 index 0000000..43328e7 --- /dev/null +++ b/lib/python2.7/idlelib/rpc.py @@ -0,0 +1,597 @@ +"""RPC Implementation, originally written for the Python Idle IDE + +For security reasons, GvR requested that Idle's Python execution server process +connect to the Idle process, which listens for the connection. Since Idle has +only one client per server, this was not a limitation. + + +---------------------------------+ +-------------+ + | SocketServer.BaseRequestHandler | | SocketIO | + +---------------------------------+ +-------------+ + ^ | register() | + | | unregister()| + | +-------------+ + | ^ ^ + | | | + | + -------------------+ | + | | | + +-------------------------+ +-----------------+ + | RPCHandler | | RPCClient | + | [attribute of RPCServer]| | | + +-------------------------+ +-----------------+ + +The RPCServer handler class is expected to provide register/unregister methods. +RPCHandler inherits the mix-in class SocketIO, which provides these methods. + +See the Idle run.main() docstring for further information on how this was +accomplished in Idle. + +""" + +import sys +import os +import socket +import select +import SocketServer +import struct +import cPickle as pickle +import threading +import Queue +import traceback +import copy_reg +import types +import marshal + + +def unpickle_code(ms): + co = marshal.loads(ms) + assert isinstance(co, types.CodeType) + return co + +def pickle_code(co): + assert isinstance(co, types.CodeType) + ms = marshal.dumps(co) + return unpickle_code, (ms,) + +# XXX KBK 24Aug02 function pickling capability not used in Idle +# def unpickle_function(ms): +# return ms + +# def pickle_function(fn): +# assert isinstance(fn, type.FunctionType) +# return repr(fn) + +copy_reg.pickle(types.CodeType, pickle_code, unpickle_code) +# copy_reg.pickle(types.FunctionType, pickle_function, unpickle_function) + +BUFSIZE = 8*1024 +LOCALHOST = '127.0.0.1' + +class RPCServer(SocketServer.TCPServer): + + def __init__(self, addr, handlerclass=None): + if handlerclass is None: + handlerclass = RPCHandler + SocketServer.TCPServer.__init__(self, addr, handlerclass) + + def server_bind(self): + "Override TCPServer method, no bind() phase for connecting entity" + pass + + def server_activate(self): + """Override TCPServer method, connect() instead of listen() + + Due to the reversed connection, self.server_address is actually the + address of the Idle Client to which we are connecting. + + """ + self.socket.connect(self.server_address) + + def get_request(self): + "Override TCPServer method, return already connected socket" + return self.socket, self.server_address + + def handle_error(self, request, client_address): + """Override TCPServer method + + Error message goes to __stderr__. No error message if exiting + normally or socket raised EOF. Other exceptions not handled in + server code will cause os._exit. + + """ + try: + raise + except SystemExit: + raise + except: + erf = sys.__stderr__ + print>>erf, '\n' + '-'*40 + print>>erf, 'Unhandled server exception!' + print>>erf, 'Thread: %s' % threading.currentThread().getName() + print>>erf, 'Client Address: ', client_address + print>>erf, 'Request: ', repr(request) + traceback.print_exc(file=erf) + print>>erf, '\n*** Unrecoverable, server exiting!' + print>>erf, '-'*40 + os._exit(0) + +#----------------- end class RPCServer -------------------- + +objecttable = {} +request_queue = Queue.Queue(0) +response_queue = Queue.Queue(0) + + +class SocketIO(object): + + nextseq = 0 + + def __init__(self, sock, objtable=None, debugging=None): + self.sockthread = threading.currentThread() + if debugging is not None: + self.debugging = debugging + self.sock = sock + if objtable is None: + objtable = objecttable + self.objtable = objtable + self.responses = {} + self.cvars = {} + + def close(self): + sock = self.sock + self.sock = None + if sock is not None: + sock.close() + + def exithook(self): + "override for specific exit action" + os._exit(0) + + def debug(self, *args): + if not self.debugging: + return + s = self.location + " " + str(threading.currentThread().getName()) + for a in args: + s = s + " " + str(a) + print>>sys.__stderr__, s + + def register(self, oid, object): + self.objtable[oid] = object + + def unregister(self, oid): + try: + del self.objtable[oid] + except KeyError: + pass + + def localcall(self, seq, request): + self.debug("localcall:", request) + try: + how, (oid, methodname, args, kwargs) = request + except TypeError: + return ("ERROR", "Bad request format") + if oid not in self.objtable: + return ("ERROR", "Unknown object id: %r" % (oid,)) + obj = self.objtable[oid] + if methodname == "__methods__": + methods = {} + _getmethods(obj, methods) + return ("OK", methods) + if methodname == "__attributes__": + attributes = {} + _getattributes(obj, attributes) + return ("OK", attributes) + if not hasattr(obj, methodname): + return ("ERROR", "Unsupported method name: %r" % (methodname,)) + method = getattr(obj, methodname) + try: + if how == 'CALL': + ret = method(*args, **kwargs) + if isinstance(ret, RemoteObject): + ret = remoteref(ret) + return ("OK", ret) + elif how == 'QUEUE': + request_queue.put((seq, (method, args, kwargs))) + return("QUEUED", None) + else: + return ("ERROR", "Unsupported message type: %s" % how) + except SystemExit: + raise + except socket.error: + raise + except: + msg = "*** Internal Error: rpc.py:SocketIO.localcall()\n\n"\ + " Object: %s \n Method: %s \n Args: %s\n" + print>>sys.__stderr__, msg % (oid, method, args) + traceback.print_exc(file=sys.__stderr__) + return ("EXCEPTION", None) + + def remotecall(self, oid, methodname, args, kwargs): + self.debug("remotecall:asynccall: ", oid, methodname) + seq = self.asynccall(oid, methodname, args, kwargs) + return self.asyncreturn(seq) + + def remotequeue(self, oid, methodname, args, kwargs): + self.debug("remotequeue:asyncqueue: ", oid, methodname) + seq = self.asyncqueue(oid, methodname, args, kwargs) + return self.asyncreturn(seq) + + def asynccall(self, oid, methodname, args, kwargs): + request = ("CALL", (oid, methodname, args, kwargs)) + seq = self.newseq() + if threading.currentThread() != self.sockthread: + cvar = threading.Condition() + self.cvars[seq] = cvar + self.debug(("asynccall:%d:" % seq), oid, methodname, args, kwargs) + self.putmessage((seq, request)) + return seq + + def asyncqueue(self, oid, methodname, args, kwargs): + request = ("QUEUE", (oid, methodname, args, kwargs)) + seq = self.newseq() + if threading.currentThread() != self.sockthread: + cvar = threading.Condition() + self.cvars[seq] = cvar + self.debug(("asyncqueue:%d:" % seq), oid, methodname, args, kwargs) + self.putmessage((seq, request)) + return seq + + def asyncreturn(self, seq): + self.debug("asyncreturn:%d:call getresponse(): " % seq) + response = self.getresponse(seq, wait=0.05) + self.debug(("asyncreturn:%d:response: " % seq), response) + return self.decoderesponse(response) + + def decoderesponse(self, response): + how, what = response + if how == "OK": + return what + if how == "QUEUED": + return None + if how == "EXCEPTION": + self.debug("decoderesponse: EXCEPTION") + return None + if how == "EOF": + self.debug("decoderesponse: EOF") + self.decode_interrupthook() + return None + if how == "ERROR": + self.debug("decoderesponse: Internal ERROR:", what) + raise RuntimeError, what + raise SystemError, (how, what) + + def decode_interrupthook(self): + "" + raise EOFError + + def mainloop(self): + """Listen on socket until I/O not ready or EOF + + pollresponse() will loop looking for seq number None, which + never comes, and exit on EOFError. + + """ + try: + self.getresponse(myseq=None, wait=0.05) + except EOFError: + self.debug("mainloop:return") + return + + def getresponse(self, myseq, wait): + response = self._getresponse(myseq, wait) + if response is not None: + how, what = response + if how == "OK": + response = how, self._proxify(what) + return response + + def _proxify(self, obj): + if isinstance(obj, RemoteProxy): + return RPCProxy(self, obj.oid) + if isinstance(obj, types.ListType): + return map(self._proxify, obj) + # XXX Check for other types -- not currently needed + return obj + + def _getresponse(self, myseq, wait): + self.debug("_getresponse:myseq:", myseq) + if threading.currentThread() is self.sockthread: + # this thread does all reading of requests or responses + while 1: + response = self.pollresponse(myseq, wait) + if response is not None: + return response + else: + # wait for notification from socket handling thread + cvar = self.cvars[myseq] + cvar.acquire() + while myseq not in self.responses: + cvar.wait() + response = self.responses[myseq] + self.debug("_getresponse:%s: thread woke up: response: %s" % + (myseq, response)) + del self.responses[myseq] + del self.cvars[myseq] + cvar.release() + return response + + def newseq(self): + self.nextseq = seq = self.nextseq + 2 + return seq + + def putmessage(self, message): + self.debug("putmessage:%d:" % message[0]) + try: + s = pickle.dumps(message) + except pickle.PicklingError: + print >>sys.__stderr__, "Cannot pickle:", repr(message) + raise + s = struct.pack("<i", len(s)) + s + while len(s) > 0: + try: + r, w, x = select.select([], [self.sock], []) + n = self.sock.send(s[:BUFSIZE]) + except (AttributeError, TypeError): + raise IOError, "socket no longer exists" + s = s[n:] + + buffer = "" + bufneed = 4 + bufstate = 0 # meaning: 0 => reading count; 1 => reading data + + def pollpacket(self, wait): + self._stage0() + if len(self.buffer) < self.bufneed: + r, w, x = select.select([self.sock.fileno()], [], [], wait) + if len(r) == 0: + return None + try: + s = self.sock.recv(BUFSIZE) + except socket.error: + raise EOFError + if len(s) == 0: + raise EOFError + self.buffer += s + self._stage0() + return self._stage1() + + def _stage0(self): + if self.bufstate == 0 and len(self.buffer) >= 4: + s = self.buffer[:4] + self.buffer = self.buffer[4:] + self.bufneed = struct.unpack("<i", s)[0] + self.bufstate = 1 + + def _stage1(self): + if self.bufstate == 1 and len(self.buffer) >= self.bufneed: + packet = self.buffer[:self.bufneed] + self.buffer = self.buffer[self.bufneed:] + self.bufneed = 4 + self.bufstate = 0 + return packet + + def pollmessage(self, wait): + packet = self.pollpacket(wait) + if packet is None: + return None + try: + message = pickle.loads(packet) + except pickle.UnpicklingError: + print >>sys.__stderr__, "-----------------------" + print >>sys.__stderr__, "cannot unpickle packet:", repr(packet) + traceback.print_stack(file=sys.__stderr__) + print >>sys.__stderr__, "-----------------------" + raise + return message + + def pollresponse(self, myseq, wait): + """Handle messages received on the socket. + + Some messages received may be asynchronous 'call' or 'queue' requests, + and some may be responses for other threads. + + 'call' requests are passed to self.localcall() with the expectation of + immediate execution, during which time the socket is not serviced. + + 'queue' requests are used for tasks (which may block or hang) to be + processed in a different thread. These requests are fed into + request_queue by self.localcall(). Responses to queued requests are + taken from response_queue and sent across the link with the associated + sequence numbers. Messages in the queues are (sequence_number, + request/response) tuples and code using this module removing messages + from the request_queue is responsible for returning the correct + sequence number in the response_queue. + + pollresponse() will loop until a response message with the myseq + sequence number is received, and will save other responses in + self.responses and notify the owning thread. + + """ + while 1: + # send queued response if there is one available + try: + qmsg = response_queue.get(0) + except Queue.Empty: + pass + else: + seq, response = qmsg + message = (seq, ('OK', response)) + self.putmessage(message) + # poll for message on link + try: + message = self.pollmessage(wait) + if message is None: # socket not ready + return None + except EOFError: + self.handle_EOF() + return None + except AttributeError: + return None + seq, resq = message + how = resq[0] + self.debug("pollresponse:%d:myseq:%s" % (seq, myseq)) + # process or queue a request + if how in ("CALL", "QUEUE"): + self.debug("pollresponse:%d:localcall:call:" % seq) + response = self.localcall(seq, resq) + self.debug("pollresponse:%d:localcall:response:%s" + % (seq, response)) + if how == "CALL": + self.putmessage((seq, response)) + elif how == "QUEUE": + # don't acknowledge the 'queue' request! + pass + continue + # return if completed message transaction + elif seq == myseq: + return resq + # must be a response for a different thread: + else: + cv = self.cvars.get(seq, None) + # response involving unknown sequence number is discarded, + # probably intended for prior incarnation of server + if cv is not None: + cv.acquire() + self.responses[seq] = resq + cv.notify() + cv.release() + continue + + def handle_EOF(self): + "action taken upon link being closed by peer" + self.EOFhook() + self.debug("handle_EOF") + for key in self.cvars: + cv = self.cvars[key] + cv.acquire() + self.responses[key] = ('EOF', None) + cv.notify() + cv.release() + # call our (possibly overridden) exit function + self.exithook() + + def EOFhook(self): + "Classes using rpc client/server can override to augment EOF action" + pass + +#----------------- end class SocketIO -------------------- + +class RemoteObject(object): + # Token mix-in class + pass + +def remoteref(obj): + oid = id(obj) + objecttable[oid] = obj + return RemoteProxy(oid) + +class RemoteProxy(object): + + def __init__(self, oid): + self.oid = oid + +class RPCHandler(SocketServer.BaseRequestHandler, SocketIO): + + debugging = False + location = "#S" # Server + + def __init__(self, sock, addr, svr): + svr.current_handler = self ## cgt xxx + SocketIO.__init__(self, sock) + SocketServer.BaseRequestHandler.__init__(self, sock, addr, svr) + + def handle(self): + "handle() method required by SocketServer" + self.mainloop() + + def get_remote_proxy(self, oid): + return RPCProxy(self, oid) + +class RPCClient(SocketIO): + + debugging = False + location = "#C" # Client + + nextseq = 1 # Requests coming from the client are odd numbered + + def __init__(self, address, family=socket.AF_INET, type=socket.SOCK_STREAM): + self.listening_sock = socket.socket(family, type) + self.listening_sock.bind(address) + self.listening_sock.listen(1) + + def accept(self): + working_sock, address = self.listening_sock.accept() + if self.debugging: + print>>sys.__stderr__, "****** Connection request from ", address + if address[0] == LOCALHOST: + SocketIO.__init__(self, working_sock) + else: + print>>sys.__stderr__, "** Invalid host: ", address + raise socket.error + + def get_remote_proxy(self, oid): + return RPCProxy(self, oid) + +class RPCProxy(object): + + __methods = None + __attributes = None + + def __init__(self, sockio, oid): + self.sockio = sockio + self.oid = oid + + def __getattr__(self, name): + if self.__methods is None: + self.__getmethods() + if self.__methods.get(name): + return MethodProxy(self.sockio, self.oid, name) + if self.__attributes is None: + self.__getattributes() + if name in self.__attributes: + value = self.sockio.remotecall(self.oid, '__getattribute__', + (name,), {}) + return value + else: + raise AttributeError, name + + def __getattributes(self): + self.__attributes = self.sockio.remotecall(self.oid, + "__attributes__", (), {}) + + def __getmethods(self): + self.__methods = self.sockio.remotecall(self.oid, + "__methods__", (), {}) + +def _getmethods(obj, methods): + # Helper to get a list of methods from an object + # Adds names to dictionary argument 'methods' + for name in dir(obj): + attr = getattr(obj, name) + if hasattr(attr, '__call__'): + methods[name] = 1 + if type(obj) == types.InstanceType: + _getmethods(obj.__class__, methods) + if type(obj) == types.ClassType: + for super in obj.__bases__: + _getmethods(super, methods) + +def _getattributes(obj, attributes): + for name in dir(obj): + attr = getattr(obj, name) + if not hasattr(attr, '__call__'): + attributes[name] = 1 + +class MethodProxy(object): + + def __init__(self, sockio, oid, name): + self.sockio = sockio + self.oid = oid + self.name = name + + def __call__(self, *args, **kwargs): + value = self.sockio.remotecall(self.oid, self.name, args, kwargs) + return value + + +# XXX KBK 09Sep03 We need a proper unit test for this module. Previously +# existing test code was removed at Rev 1.27 (r34098). diff --git a/lib/python2.7/idlelib/run.py b/lib/python2.7/idlelib/run.py new file mode 100644 index 0000000..466c61e --- /dev/null +++ b/lib/python2.7/idlelib/run.py @@ -0,0 +1,375 @@ +import sys +import linecache +import time +import socket +import traceback +import thread +import threading +import Queue + +from idlelib import CallTips +from idlelib import AutoComplete + +from idlelib import RemoteDebugger +from idlelib import RemoteObjectBrowser +from idlelib import StackViewer +from idlelib import rpc +from idlelib import PyShell +from idlelib import IOBinding + +import __main__ + +LOCALHOST = '127.0.0.1' + +import warnings + +def idle_showwarning_subproc( + message, category, filename, lineno, file=None, line=None): + """Show Idle-format warning after replacing warnings.showwarning. + + The only difference is the formatter called. + """ + if file is None: + file = sys.stderr + try: + file.write(PyShell.idle_formatwarning( + message, category, filename, lineno, line)) + except IOError: + pass # the file (probably stderr) is invalid - this warning gets lost. + +_warnings_showwarning = None + +def capture_warnings(capture): + "Replace warning.showwarning with idle_showwarning_subproc, or reverse." + + global _warnings_showwarning + if capture: + if _warnings_showwarning is None: + _warnings_showwarning = warnings.showwarning + warnings.showwarning = idle_showwarning_subproc + else: + if _warnings_showwarning is not None: + warnings.showwarning = _warnings_showwarning + _warnings_showwarning = None + +capture_warnings(True) + +# Thread shared globals: Establish a queue between a subthread (which handles +# the socket) and the main thread (which runs user code), plus global +# completion, exit and interruptable (the main thread) flags: + +exit_now = False +quitting = False +interruptable = False + +def main(del_exitfunc=False): + """Start the Python execution server in a subprocess + + In the Python subprocess, RPCServer is instantiated with handlerclass + MyHandler, which inherits register/unregister methods from RPCHandler via + the mix-in class SocketIO. + + When the RPCServer 'server' is instantiated, the TCPServer initialization + creates an instance of run.MyHandler and calls its handle() method. + handle() instantiates a run.Executive object, passing it a reference to the + MyHandler object. That reference is saved as attribute rpchandler of the + Executive instance. The Executive methods have access to the reference and + can pass it on to entities that they command + (e.g. RemoteDebugger.Debugger.start_debugger()). The latter, in turn, can + call MyHandler(SocketIO) register/unregister methods via the reference to + register and unregister themselves. + + """ + global exit_now + global quitting + global no_exitfunc + no_exitfunc = del_exitfunc + #time.sleep(15) # test subprocess not responding + try: + assert(len(sys.argv) > 1) + port = int(sys.argv[-1]) + except: + print>>sys.stderr, "IDLE Subprocess: no IP port passed in sys.argv." + return + + capture_warnings(True) + sys.argv[:] = [""] + sockthread = threading.Thread(target=manage_socket, + name='SockThread', + args=((LOCALHOST, port),)) + sockthread.setDaemon(True) + sockthread.start() + while 1: + try: + if exit_now: + try: + exit() + except KeyboardInterrupt: + # exiting but got an extra KBI? Try again! + continue + try: + seq, request = rpc.request_queue.get(block=True, timeout=0.05) + except Queue.Empty: + continue + method, args, kwargs = request + ret = method(*args, **kwargs) + rpc.response_queue.put((seq, ret)) + except KeyboardInterrupt: + if quitting: + exit_now = True + continue + except SystemExit: + capture_warnings(False) + raise + except: + type, value, tb = sys.exc_info() + try: + print_exception() + rpc.response_queue.put((seq, None)) + except: + # Link didn't work, print same exception to __stderr__ + traceback.print_exception(type, value, tb, file=sys.__stderr__) + exit() + else: + continue + +def manage_socket(address): + for i in range(3): + time.sleep(i) + try: + server = MyRPCServer(address, MyHandler) + break + except socket.error as err: + print>>sys.__stderr__,"IDLE Subprocess: socket error: "\ + + err.args[1] + ", retrying...." + else: + print>>sys.__stderr__, "IDLE Subprocess: Connection to "\ + "IDLE GUI failed, exiting." + show_socket_error(err, address) + global exit_now + exit_now = True + return + server.handle_request() # A single request only + +def show_socket_error(err, address): + import Tkinter + import tkMessageBox + root = Tkinter.Tk() + root.withdraw() + if err.args[0] == 61: # connection refused + msg = "IDLE's subprocess can't connect to %s:%d. This may be due "\ + "to your personal firewall configuration. It is safe to "\ + "allow this internal connection because no data is visible on "\ + "external ports." % address + tkMessageBox.showerror("IDLE Subprocess Error", msg, parent=root) + else: + tkMessageBox.showerror("IDLE Subprocess Error", + "Socket Error: %s" % err.args[1], parent=root) + root.destroy() + +def print_exception(): + import linecache + linecache.checkcache() + flush_stdout() + efile = sys.stderr + typ, val, tb = excinfo = sys.exc_info() + sys.last_type, sys.last_value, sys.last_traceback = excinfo + tbe = traceback.extract_tb(tb) + print>>efile, '\nTraceback (most recent call last):' + exclude = ("run.py", "rpc.py", "threading.py", "Queue.py", + "RemoteDebugger.py", "bdb.py") + cleanup_traceback(tbe, exclude) + traceback.print_list(tbe, file=efile) + lines = traceback.format_exception_only(typ, val) + for line in lines: + print>>efile, line, + +def cleanup_traceback(tb, exclude): + "Remove excluded traces from beginning/end of tb; get cached lines" + orig_tb = tb[:] + while tb: + for rpcfile in exclude: + if tb[0][0].count(rpcfile): + break # found an exclude, break for: and delete tb[0] + else: + break # no excludes, have left RPC code, break while: + del tb[0] + while tb: + for rpcfile in exclude: + if tb[-1][0].count(rpcfile): + break + else: + break + del tb[-1] + if len(tb) == 0: + # exception was in IDLE internals, don't prune! + tb[:] = orig_tb[:] + print>>sys.stderr, "** IDLE Internal Exception: " + rpchandler = rpc.objecttable['exec'].rpchandler + for i in range(len(tb)): + fn, ln, nm, line = tb[i] + if nm == '?': + nm = "-toplevel-" + if fn.startswith("<pyshell#") and IOBinding.encoding != 'utf-8': + ln -= 1 # correction for coding cookie + if not line and fn.startswith("<pyshell#"): + line = rpchandler.remotecall('linecache', 'getline', + (fn, ln), {}) + tb[i] = fn, ln, nm, line + +def flush_stdout(): + try: + if sys.stdout.softspace: + sys.stdout.softspace = 0 + sys.stdout.write("\n") + except (AttributeError, EOFError): + pass + +def exit(): + """Exit subprocess, possibly after first deleting sys.exitfunc + + If config-main.cfg/.def 'General' 'delete-exitfunc' is True, then any + sys.exitfunc will be removed before exiting. (VPython support) + + """ + if no_exitfunc: + try: + del sys.exitfunc + except AttributeError: + pass + capture_warnings(False) + sys.exit(0) + +class MyRPCServer(rpc.RPCServer): + + def handle_error(self, request, client_address): + """Override RPCServer method for IDLE + + Interrupt the MainThread and exit server if link is dropped. + + """ + global quitting + try: + raise + except SystemExit: + raise + except EOFError: + global exit_now + exit_now = True + thread.interrupt_main() + except: + erf = sys.__stderr__ + print>>erf, '\n' + '-'*40 + print>>erf, 'Unhandled server exception!' + print>>erf, 'Thread: %s' % threading.currentThread().getName() + print>>erf, 'Client Address: ', client_address + print>>erf, 'Request: ', repr(request) + traceback.print_exc(file=erf) + print>>erf, '\n*** Unrecoverable, server exiting!' + print>>erf, '-'*40 + quitting = True + thread.interrupt_main() + +class MyHandler(rpc.RPCHandler): + + def handle(self): + """Override base method""" + executive = Executive(self) + self.register("exec", executive) + self.console = self.get_remote_proxy("console") + sys.stdin = PyShell.PseudoInputFile(self.console, "stdin", + IOBinding.encoding) + sys.stdout = PyShell.PseudoOutputFile(self.console, "stdout", + IOBinding.encoding) + sys.stderr = PyShell.PseudoOutputFile(self.console, "stderr", + IOBinding.encoding) + + # Keep a reference to stdin so that it won't try to exit IDLE if + # sys.stdin gets changed from within IDLE's shell. See issue17838. + self._keep_stdin = sys.stdin + + self.interp = self.get_remote_proxy("interp") + rpc.RPCHandler.getresponse(self, myseq=None, wait=0.05) + + def exithook(self): + "override SocketIO method - wait for MainThread to shut us down" + time.sleep(10) + + def EOFhook(self): + "Override SocketIO method - terminate wait on callback and exit thread" + global quitting + quitting = True + thread.interrupt_main() + + def decode_interrupthook(self): + "interrupt awakened thread" + global quitting + quitting = True + thread.interrupt_main() + + +class Executive(object): + + def __init__(self, rpchandler): + self.rpchandler = rpchandler + self.locals = __main__.__dict__ + self.calltip = CallTips.CallTips() + self.autocomplete = AutoComplete.AutoComplete() + + def runcode(self, code): + global interruptable + try: + self.usr_exc_info = None + interruptable = True + try: + exec code in self.locals + finally: + interruptable = False + except SystemExit: + # Scripts that raise SystemExit should just + # return to the interactive prompt + pass + except: + self.usr_exc_info = sys.exc_info() + if quitting: + exit() + print_exception() + jit = self.rpchandler.console.getvar("<<toggle-jit-stack-viewer>>") + if jit: + self.rpchandler.interp.open_remote_stack_viewer() + else: + flush_stdout() + + def interrupt_the_server(self): + if interruptable: + thread.interrupt_main() + + def start_the_debugger(self, gui_adap_oid): + return RemoteDebugger.start_debugger(self.rpchandler, gui_adap_oid) + + def stop_the_debugger(self, idb_adap_oid): + "Unregister the Idb Adapter. Link objects and Idb then subject to GC" + self.rpchandler.unregister(idb_adap_oid) + + def get_the_calltip(self, name): + return self.calltip.fetch_tip(name) + + def get_the_completion_list(self, what, mode): + return self.autocomplete.fetch_completions(what, mode) + + def stackviewer(self, flist_oid=None): + if self.usr_exc_info: + typ, val, tb = self.usr_exc_info + else: + return None + flist = None + if flist_oid is not None: + flist = self.rpchandler.get_remote_proxy(flist_oid) + while tb and tb.tb_frame.f_globals["__name__"] in ["rpc", "run"]: + tb = tb.tb_next + sys.last_type = typ + sys.last_value = val + item = StackViewer.StackTreeItem(flist, tb) + return RemoteObjectBrowser.remote_object_tree_item(item) + +capture_warnings(False) # Make sure turned off; see issue 18081 diff --git a/lib/python2.7/idlelib/tabbedpages.py b/lib/python2.7/idlelib/tabbedpages.py new file mode 100644 index 0000000..0723d94 --- /dev/null +++ b/lib/python2.7/idlelib/tabbedpages.py @@ -0,0 +1,498 @@ +"""An implementation of tabbed pages using only standard Tkinter. + +Originally developed for use in IDLE. Based on tabpage.py. + +Classes exported: +TabbedPageSet -- A Tkinter implementation of a tabbed-page widget. +TabSet -- A widget containing tabs (buttons) in one or more rows. + +""" +from Tkinter import * + +class InvalidNameError(Exception): pass +class AlreadyExistsError(Exception): pass + + +class TabSet(Frame): + """A widget containing tabs (buttons) in one or more rows. + + Only one tab may be selected at a time. + + """ + def __init__(self, page_set, select_command, + tabs=None, n_rows=1, max_tabs_per_row=5, + expand_tabs=False, **kw): + """Constructor arguments: + + select_command -- A callable which will be called when a tab is + selected. It is called with the name of the selected tab as an + argument. + + tabs -- A list of strings, the names of the tabs. Should be specified in + the desired tab order. The first tab will be the default and first + active tab. If tabs is None or empty, the TabSet will be initialized + empty. + + n_rows -- Number of rows of tabs to be shown. If n_rows <= 0 or is + None, then the number of rows will be decided by TabSet. See + _arrange_tabs() for details. + + max_tabs_per_row -- Used for deciding how many rows of tabs are needed, + when the number of rows is not constant. See _arrange_tabs() for + details. + + """ + Frame.__init__(self, page_set, **kw) + self.select_command = select_command + self.n_rows = n_rows + self.max_tabs_per_row = max_tabs_per_row + self.expand_tabs = expand_tabs + self.page_set = page_set + + self._tabs = {} + self._tab2row = {} + if tabs: + self._tab_names = list(tabs) + else: + self._tab_names = [] + self._selected_tab = None + self._tab_rows = [] + + self.padding_frame = Frame(self, height=2, + borderwidth=0, relief=FLAT, + background=self.cget('background')) + self.padding_frame.pack(side=TOP, fill=X, expand=False) + + self._arrange_tabs() + + def add_tab(self, tab_name): + """Add a new tab with the name given in tab_name.""" + if not tab_name: + raise InvalidNameError("Invalid Tab name: '%s'" % tab_name) + if tab_name in self._tab_names: + raise AlreadyExistsError("Tab named '%s' already exists" %tab_name) + + self._tab_names.append(tab_name) + self._arrange_tabs() + + def remove_tab(self, tab_name): + """Remove the tab named <tab_name>""" + if not tab_name in self._tab_names: + raise KeyError("No such Tab: '%s" % page_name) + + self._tab_names.remove(tab_name) + self._arrange_tabs() + + def set_selected_tab(self, tab_name): + """Show the tab named <tab_name> as the selected one""" + if tab_name == self._selected_tab: + return + if tab_name is not None and tab_name not in self._tabs: + raise KeyError("No such Tab: '%s" % page_name) + + # deselect the current selected tab + if self._selected_tab is not None: + self._tabs[self._selected_tab].set_normal() + self._selected_tab = None + + if tab_name is not None: + # activate the tab named tab_name + self._selected_tab = tab_name + tab = self._tabs[tab_name] + tab.set_selected() + # move the tab row with the selected tab to the bottom + tab_row = self._tab2row[tab] + tab_row.pack_forget() + tab_row.pack(side=TOP, fill=X, expand=0) + + def _add_tab_row(self, tab_names, expand_tabs): + if not tab_names: + return + + tab_row = Frame(self) + tab_row.pack(side=TOP, fill=X, expand=0) + self._tab_rows.append(tab_row) + + for tab_name in tab_names: + tab = TabSet.TabButton(tab_name, self.select_command, + tab_row, self) + if expand_tabs: + tab.pack(side=LEFT, fill=X, expand=True) + else: + tab.pack(side=LEFT) + self._tabs[tab_name] = tab + self._tab2row[tab] = tab_row + + # tab is the last one created in the above loop + tab.is_last_in_row = True + + def _reset_tab_rows(self): + while self._tab_rows: + tab_row = self._tab_rows.pop() + tab_row.destroy() + self._tab2row = {} + + def _arrange_tabs(self): + """ + Arrange the tabs in rows, in the order in which they were added. + + If n_rows >= 1, this will be the number of rows used. Otherwise the + number of rows will be calculated according to the number of tabs and + max_tabs_per_row. In this case, the number of rows may change when + adding/removing tabs. + + """ + # remove all tabs and rows + for tab_name in self._tabs.keys(): + self._tabs.pop(tab_name).destroy() + self._reset_tab_rows() + + if not self._tab_names: + return + + if self.n_rows is not None and self.n_rows > 0: + n_rows = self.n_rows + else: + # calculate the required number of rows + n_rows = (len(self._tab_names) - 1) // self.max_tabs_per_row + 1 + + # not expanding the tabs with more than one row is very ugly + expand_tabs = self.expand_tabs or n_rows > 1 + i = 0 # index in self._tab_names + for row_index in xrange(n_rows): + # calculate required number of tabs in this row + n_tabs = (len(self._tab_names) - i - 1) // (n_rows - row_index) + 1 + tab_names = self._tab_names[i:i + n_tabs] + i += n_tabs + self._add_tab_row(tab_names, expand_tabs) + + # re-select selected tab so it is properly displayed + selected = self._selected_tab + self.set_selected_tab(None) + if selected in self._tab_names: + self.set_selected_tab(selected) + + class TabButton(Frame): + """A simple tab-like widget.""" + + bw = 2 # borderwidth + + def __init__(self, name, select_command, tab_row, tab_set): + """Constructor arguments: + + name -- The tab's name, which will appear in its button. + + select_command -- The command to be called upon selection of the + tab. It is called with the tab's name as an argument. + + """ + Frame.__init__(self, tab_row, borderwidth=self.bw, relief=RAISED) + + self.name = name + self.select_command = select_command + self.tab_set = tab_set + self.is_last_in_row = False + + self.button = Radiobutton( + self, text=name, command=self._select_event, + padx=5, pady=1, takefocus=FALSE, indicatoron=FALSE, + highlightthickness=0, selectcolor='', borderwidth=0) + self.button.pack(side=LEFT, fill=X, expand=True) + + self._init_masks() + self.set_normal() + + def _select_event(self, *args): + """Event handler for tab selection. + + With TabbedPageSet, this calls TabbedPageSet.change_page, so that + selecting a tab changes the page. + + Note that this does -not- call set_selected -- it will be called by + TabSet.set_selected_tab, which should be called when whatever the + tabs are related to changes. + + """ + self.select_command(self.name) + return + + def set_selected(self): + """Assume selected look""" + self._place_masks(selected=True) + + def set_normal(self): + """Assume normal look""" + self._place_masks(selected=False) + + def _init_masks(self): + page_set = self.tab_set.page_set + background = page_set.pages_frame.cget('background') + # mask replaces the middle of the border with the background color + self.mask = Frame(page_set, borderwidth=0, relief=FLAT, + background=background) + # mskl replaces the bottom-left corner of the border with a normal + # left border + self.mskl = Frame(page_set, borderwidth=0, relief=FLAT, + background=background) + self.mskl.ml = Frame(self.mskl, borderwidth=self.bw, + relief=RAISED) + self.mskl.ml.place(x=0, y=-self.bw, + width=2*self.bw, height=self.bw*4) + # mskr replaces the bottom-right corner of the border with a normal + # right border + self.mskr = Frame(page_set, borderwidth=0, relief=FLAT, + background=background) + self.mskr.mr = Frame(self.mskr, borderwidth=self.bw, + relief=RAISED) + + def _place_masks(self, selected=False): + height = self.bw + if selected: + height += self.bw + + self.mask.place(in_=self, + relx=0.0, x=0, + rely=1.0, y=0, + relwidth=1.0, width=0, + relheight=0.0, height=height) + + self.mskl.place(in_=self, + relx=0.0, x=-self.bw, + rely=1.0, y=0, + relwidth=0.0, width=self.bw, + relheight=0.0, height=height) + + page_set = self.tab_set.page_set + if selected and ((not self.is_last_in_row) or + (self.winfo_rootx() + self.winfo_width() < + page_set.winfo_rootx() + page_set.winfo_width()) + ): + # for a selected tab, if its rightmost edge isn't on the + # rightmost edge of the page set, the right mask should be one + # borderwidth shorter (vertically) + height -= self.bw + + self.mskr.place(in_=self, + relx=1.0, x=0, + rely=1.0, y=0, + relwidth=0.0, width=self.bw, + relheight=0.0, height=height) + + self.mskr.mr.place(x=-self.bw, y=-self.bw, + width=2*self.bw, height=height + self.bw*2) + + # finally, lower the tab set so that all of the frames we just + # placed hide it + self.tab_set.lower() + +class TabbedPageSet(Frame): + """A Tkinter tabbed-pane widget. + + Constains set of 'pages' (or 'panes') with tabs above for selecting which + page is displayed. Only one page will be displayed at a time. + + Pages may be accessed through the 'pages' attribute, which is a dictionary + of pages, using the name given as the key. A page is an instance of a + subclass of Tk's Frame widget. + + The page widgets will be created (and destroyed when required) by the + TabbedPageSet. Do not call the page's pack/place/grid/destroy methods. + + Pages may be added or removed at any time using the add_page() and + remove_page() methods. + + """ + class Page(object): + """Abstract base class for TabbedPageSet's pages. + + Subclasses must override the _show() and _hide() methods. + + """ + uses_grid = False + + def __init__(self, page_set): + self.frame = Frame(page_set, borderwidth=2, relief=RAISED) + + def _show(self): + raise NotImplementedError + + def _hide(self): + raise NotImplementedError + + class PageRemove(Page): + """Page class using the grid placement manager's "remove" mechanism.""" + uses_grid = True + + def _show(self): + self.frame.grid(row=0, column=0, sticky=NSEW) + + def _hide(self): + self.frame.grid_remove() + + class PageLift(Page): + """Page class using the grid placement manager's "lift" mechanism.""" + uses_grid = True + + def __init__(self, page_set): + super(TabbedPageSet.PageLift, self).__init__(page_set) + self.frame.grid(row=0, column=0, sticky=NSEW) + self.frame.lower() + + def _show(self): + self.frame.lift() + + def _hide(self): + self.frame.lower() + + class PagePackForget(Page): + """Page class using the pack placement manager's "forget" mechanism.""" + def _show(self): + self.frame.pack(fill=BOTH, expand=True) + + def _hide(self): + self.frame.pack_forget() + + def __init__(self, parent, page_names=None, page_class=PageLift, + n_rows=1, max_tabs_per_row=5, expand_tabs=False, + **kw): + """Constructor arguments: + + page_names -- A list of strings, each will be the dictionary key to a + page's widget, and the name displayed on the page's tab. Should be + specified in the desired page order. The first page will be the default + and first active page. If page_names is None or empty, the + TabbedPageSet will be initialized empty. + + n_rows, max_tabs_per_row -- Parameters for the TabSet which will + manage the tabs. See TabSet's docs for details. + + page_class -- Pages can be shown/hidden using three mechanisms: + + * PageLift - All pages will be rendered one on top of the other. When + a page is selected, it will be brought to the top, thus hiding all + other pages. Using this method, the TabbedPageSet will not be resized + when pages are switched. (It may still be resized when pages are + added/removed.) + + * PageRemove - When a page is selected, the currently showing page is + hidden, and the new page shown in its place. Using this method, the + TabbedPageSet may resize when pages are changed. + + * PagePackForget - This mechanism uses the pack placement manager. + When a page is shown it is packed, and when it is hidden it is + unpacked (i.e. pack_forget). This mechanism may also cause the + TabbedPageSet to resize when the page is changed. + + """ + Frame.__init__(self, parent, **kw) + + self.page_class = page_class + self.pages = {} + self._pages_order = [] + self._current_page = None + self._default_page = None + + self.columnconfigure(0, weight=1) + self.rowconfigure(1, weight=1) + + self.pages_frame = Frame(self) + self.pages_frame.grid(row=1, column=0, sticky=NSEW) + if self.page_class.uses_grid: + self.pages_frame.columnconfigure(0, weight=1) + self.pages_frame.rowconfigure(0, weight=1) + + # the order of the following commands is important + self._tab_set = TabSet(self, self.change_page, n_rows=n_rows, + max_tabs_per_row=max_tabs_per_row, + expand_tabs=expand_tabs) + if page_names: + for name in page_names: + self.add_page(name) + self._tab_set.grid(row=0, column=0, sticky=NSEW) + + self.change_page(self._default_page) + + def add_page(self, page_name): + """Add a new page with the name given in page_name.""" + if not page_name: + raise InvalidNameError("Invalid TabPage name: '%s'" % page_name) + if page_name in self.pages: + raise AlreadyExistsError( + "TabPage named '%s' already exists" % page_name) + + self.pages[page_name] = self.page_class(self.pages_frame) + self._pages_order.append(page_name) + self._tab_set.add_tab(page_name) + + if len(self.pages) == 1: # adding first page + self._default_page = page_name + self.change_page(page_name) + + def remove_page(self, page_name): + """Destroy the page whose name is given in page_name.""" + if not page_name in self.pages: + raise KeyError("No such TabPage: '%s" % page_name) + + self._pages_order.remove(page_name) + + # handle removing last remaining, default, or currently shown page + if len(self._pages_order) > 0: + if page_name == self._default_page: + # set a new default page + self._default_page = self._pages_order[0] + else: + self._default_page = None + + if page_name == self._current_page: + self.change_page(self._default_page) + + self._tab_set.remove_tab(page_name) + page = self.pages.pop(page_name) + page.frame.destroy() + + def change_page(self, page_name): + """Show the page whose name is given in page_name.""" + if self._current_page == page_name: + return + if page_name is not None and page_name not in self.pages: + raise KeyError("No such TabPage: '%s'" % page_name) + + if self._current_page is not None: + self.pages[self._current_page]._hide() + self._current_page = None + + if page_name is not None: + self._current_page = page_name + self.pages[page_name]._show() + + self._tab_set.set_selected_tab(page_name) + +def _tabbed_pages(parent): + # test dialog + root=Tk() + width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) + root.geometry("+%d+%d"%(x, y + 175)) + root.title("Test tabbed pages") + tabPage=TabbedPageSet(root, page_names=['Foobar','Baz'], n_rows=0, + expand_tabs=False, + ) + tabPage.pack(side=TOP, expand=TRUE, fill=BOTH) + Label(tabPage.pages['Foobar'].frame, text='Foo', pady=20).pack() + Label(tabPage.pages['Foobar'].frame, text='Bar', pady=20).pack() + Label(tabPage.pages['Baz'].frame, text='Baz').pack() + entryPgName=Entry(root) + buttonAdd=Button(root, text='Add Page', + command=lambda:tabPage.add_page(entryPgName.get())) + buttonRemove=Button(root, text='Remove Page', + command=lambda:tabPage.remove_page(entryPgName.get())) + labelPgName=Label(root, text='name of page to add/remove:') + buttonAdd.pack(padx=5, pady=5) + buttonRemove.pack(padx=5, pady=5) + labelPgName.pack(padx=5) + entryPgName.pack(padx=5) + root.mainloop() + + +if __name__ == '__main__': + from idlelib.idle_test.htest import run + run(_tabbed_pages) diff --git a/lib/python2.7/idlelib/textView.py b/lib/python2.7/idlelib/textView.py new file mode 100644 index 0000000..b8c4ac1 --- /dev/null +++ b/lib/python2.7/idlelib/textView.py @@ -0,0 +1,94 @@ +"""Simple text browser for IDLE + +""" + +from Tkinter import * +import tkMessageBox + +class TextViewer(Toplevel): + """A simple text viewer dialog for IDLE + + """ + def __init__(self, parent, title, text, modal=True, _htest=False): + """Show the given text in a scrollable window with a 'close' button + + If modal option set to False, user can interact with other windows, + otherwise they will be unable to interact with other windows until + the textview window is closed. + + _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("=%dx%d+%d+%d" % (750, 500, + parent.winfo_rootx() + 10, + parent.winfo_rooty() + (10 if not _htest else 100))) + #elguavas - config placeholders til config stuff completed + self.bg = '#ffffff' + self.fg = '#000000' + + self.CreateWidgets() + self.title(title) + self.protocol("WM_DELETE_WINDOW", self.Ok) + self.parent = parent + self.textView.focus_set() + #key bindings for this dialog + self.bind('<Return>',self.Ok) #dismiss dialog + self.bind('<Escape>',self.Ok) #dismiss dialog + self.textView.insert(0.0, text) + self.textView.config(state=DISABLED) + + if modal: + self.transient(parent) + self.grab_set() + self.wait_window() + + def CreateWidgets(self): + frameText = Frame(self, relief=SUNKEN, height=700) + frameButtons = Frame(self) + self.buttonOk = Button(frameButtons, text='Close', + command=self.Ok, takefocus=FALSE) + self.scrollbarView = Scrollbar(frameText, orient=VERTICAL, + takefocus=FALSE, highlightthickness=0) + self.textView = Text(frameText, wrap=WORD, highlightthickness=0, + fg=self.fg, bg=self.bg) + self.scrollbarView.config(command=self.textView.yview) + self.textView.config(yscrollcommand=self.scrollbarView.set) + self.buttonOk.pack() + self.scrollbarView.pack(side=RIGHT,fill=Y) + self.textView.pack(side=LEFT,expand=TRUE,fill=BOTH) + frameButtons.pack(side=BOTTOM,fill=X) + frameText.pack(side=TOP,expand=TRUE,fill=BOTH) + + def Ok(self, event=None): + self.destroy() + + +def view_text(parent, title, text, modal=True): + return TextViewer(parent, title, text, modal) + +def view_file(parent, title, filename, encoding=None, modal=True): + try: + if encoding: + import codecs + textFile = codecs.open(filename, 'r') + else: + textFile = open(filename, 'r') + except IOError: + tkMessageBox.showerror(title='File Load Error', + message='Unable to load file %r .' % filename, + parent=parent) + except UnicodeDecodeError as err: + showerror(title='Unicode Decode Error', + message=str(err), + parent=parent) + else: + return view_text(parent, title, textFile.read(), modal) + + +if __name__ == '__main__': + import unittest + unittest.main('idlelib.idle_test.test_textview', verbosity=2, exit=False) + from idlelib.idle_test.htest import run + run(TextViewer) |