mirror of
https://gitee.com/kekingcn/file-online-preview.git
synced 2026-06-19 04:17:07 +00:00
优化项目结构、优化 maven 结构
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
"""AutoComplete.py - An IDLE extension for automatically completing names.
|
||||
|
||||
This extension can complete either attribute names of 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 an 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)
|
||||
@@ -0,0 +1,406 @@
|
||||
"""
|
||||
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)
|
||||
|
||||
# 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
|
||||
@@ -0,0 +1,83 @@
|
||||
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):
|
||||
curinsert = self.text.index("insert")
|
||||
curline = self.text.get("insert linestart", "insert lineend")
|
||||
if not self.state:
|
||||
words = self.getwords()
|
||||
index = 0
|
||||
else:
|
||||
words, index, insert, line = self.state
|
||||
if insert != curinsert or line != curline:
|
||||
words = self.getwords()
|
||||
index = 0
|
||||
if not words:
|
||||
self.text.bell()
|
||||
return "break"
|
||||
word = self.getprevword()
|
||||
self.text.delete("insert - %d chars" % len(word), "insert")
|
||||
newword = words[index]
|
||||
index = (index + 1) % len(words)
|
||||
if index == 0:
|
||||
self.text.bell() # Warn we cycled around
|
||||
self.text.insert("insert", newword)
|
||||
curinsert = self.text.index("insert")
|
||||
curline = self.text.get("insert linestart", "insert lineend")
|
||||
self.state = words, index, curinsert, curline
|
||||
return "break"
|
||||
|
||||
def getwords(self):
|
||||
word = self.getprevword()
|
||||
if not word:
|
||||
return []
|
||||
before = self.text.get("1.0", "insert wordstart")
|
||||
wbefore = re.findall(r"\b" + word + r"\w+\b", before)
|
||||
del before
|
||||
after = self.text.get("insert wordend", "end")
|
||||
wafter = re.findall(r"\b" + word + r"\w+\b", after)
|
||||
del after
|
||||
if not wbefore and not wafter:
|
||||
return []
|
||||
words = []
|
||||
dict = {}
|
||||
# search backwards through words before
|
||||
wbefore.reverse()
|
||||
for w in wbefore:
|
||||
if dict.get(w):
|
||||
continue
|
||||
words.append(w)
|
||||
dict[w] = w
|
||||
# search onwards through words after
|
||||
for w in wafter:
|
||||
if dict.get(w):
|
||||
continue
|
||||
words.append(w)
|
||||
dict[w] = w
|
||||
words.append(word)
|
||||
return words
|
||||
|
||||
def getprevword(self):
|
||||
line = self.text.get("insert linestart", "insert")
|
||||
i = len(line)
|
||||
while i > 0 and line[i-1] in self.wordchars:
|
||||
i = i-1
|
||||
return line[i:]
|
||||
@@ -0,0 +1,107 @@
|
||||
"""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.
|
||||
|
||||
"""
|
||||
import sys
|
||||
from idlelib.configHandler import idleConf
|
||||
from idlelib import macosxSupport
|
||||
|
||||
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>>'),
|
||||
]),
|
||||
('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>>'),
|
||||
]),
|
||||
]
|
||||
|
||||
if macosxSupport.runningAsOSXApp():
|
||||
# Running as a proper MacOS application bundle. This block restructures
|
||||
# the menus a little to make them conform better to the HIG.
|
||||
|
||||
quitItem = menudefs[0][1][-1]
|
||||
closeItem = 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 menudefs[0][1][-3:]
|
||||
menudefs[0][1].insert(6, closeItem)
|
||||
|
||||
# Remove the 'About' entry from the help menu, it is in the application
|
||||
# menu
|
||||
del menudefs[-1][1][0:2]
|
||||
|
||||
# Remove the 'Configure' entry from the options menu, it is in the
|
||||
# application menu as 'Preferences'
|
||||
del menudefs[-2][1][0:2]
|
||||
|
||||
default_keydefs = idleConf.GetCurrentKeySet()
|
||||
|
||||
del sys
|
||||
@@ -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!
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
"""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 *
|
||||
|
||||
HIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-hide>>"
|
||||
HIDE_SEQUENCES = ("<Key-Escape>", "<FocusOut>")
|
||||
CHECKHIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-checkhide>>"
|
||||
CHECKHIDE_SEQUENCES = ("<KeyRelease>", "<ButtonRelease>")
|
||||
CHECKHIDE_TIME = 100 # miliseconds
|
||||
|
||||
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.
|
||||
"""
|
||||
# truncate overly long calltip
|
||||
if len(text) >= 79:
|
||||
textlines = text.splitlines()
|
||||
for i, line in enumerate(textlines):
|
||||
if len(line) > 79:
|
||||
textlines[i] = line[:75] + ' ...'
|
||||
text = '\n'.join(textlines)
|
||||
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()
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
||||
###############################
|
||||
#
|
||||
# Test Code
|
||||
#
|
||||
class container: # Conceptually an editor_window
|
||||
def __init__(self):
|
||||
root = Tk()
|
||||
text = self.text = Text(root)
|
||||
text.pack(side=LEFT, fill=BOTH, expand=1)
|
||||
text.insert("insert", "string.split")
|
||||
root.update()
|
||||
self.calltip = CallTip(text)
|
||||
|
||||
text.event_add("<<calltip-show>>", "(")
|
||||
text.event_add("<<calltip-hide>>", ")")
|
||||
text.bind("<<calltip-show>>", self.calltip_show)
|
||||
text.bind("<<calltip-hide>>", self.calltip_hide)
|
||||
|
||||
text.focus_set()
|
||||
root.mainloop()
|
||||
|
||||
def calltip_show(self, event):
|
||||
self.calltip.showtip("Hello world")
|
||||
|
||||
def calltip_hide(self, event):
|
||||
self.calltip.hidetip()
|
||||
|
||||
def main():
|
||||
# Test code
|
||||
c=container()
|
||||
|
||||
if __name__=='__main__':
|
||||
main()
|
||||
@@ -0,0 +1,228 @@
|
||||
"""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 re
|
||||
import sys
|
||||
import types
|
||||
|
||||
from idlelib import CallTipWindow
|
||||
from idlelib.HyperParser import HyperParser
|
||||
|
||||
import __main__
|
||||
|
||||
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
|
||||
|
||||
def get_arg_text(ob):
|
||||
"""Get a string describing the arguments for the given object,
|
||||
only if it is callable."""
|
||||
arg_text = ""
|
||||
if ob is not None and hasattr(ob, '__call__'):
|
||||
arg_offset = 0
|
||||
if type(ob) in (types.ClassType, types.TypeType):
|
||||
# Look for the highest __init__ in the class chain.
|
||||
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
|
||||
# but we drop the "self" param.
|
||||
fob = ob.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)
|
||||
if fob.func_code.co_flags & 0x4:
|
||||
items.append("...")
|
||||
if fob.func_code.co_flags & 0x8:
|
||||
items.append("***")
|
||||
arg_text = ", ".join(items)
|
||||
arg_text = "(%s)" % re.sub("(?<!\d)\.\d+", "<tuple>", arg_text)
|
||||
# See if we can use the docstring
|
||||
doc = getattr(ob, "__doc__", "")
|
||||
if doc:
|
||||
doc = doc.lstrip()
|
||||
pos = doc.find("\n")
|
||||
if pos < 0 or pos > 70:
|
||||
pos = 70
|
||||
if arg_text:
|
||||
arg_text += "\n"
|
||||
arg_text += doc[:pos]
|
||||
return arg_text
|
||||
|
||||
#################################################
|
||||
#
|
||||
# Test code
|
||||
#
|
||||
if __name__=='__main__':
|
||||
|
||||
def t1(): "()"
|
||||
def t2(a, b=None): "(a, b=None)"
|
||||
def t3(a, *args): "(a, ...)"
|
||||
def t4(*args): "(...)"
|
||||
def t5(a, *args): "(a, ...)"
|
||||
def t6(a, b=None, *args, **kw): "(a, b=None, ..., ***)"
|
||||
def t7((a, b), c, (d, e)): "(<tuple>, c, <tuple>)"
|
||||
|
||||
class TC(object):
|
||||
"(ai=None, ...)"
|
||||
def __init__(self, ai=None, *b): "(ai=None, ...)"
|
||||
def t1(self): "()"
|
||||
def t2(self, ai, b=None): "(ai, b=None)"
|
||||
def t3(self, ai, *args): "(ai, ...)"
|
||||
def t4(self, *args): "(...)"
|
||||
def t5(self, ai, *args): "(ai, ...)"
|
||||
def t6(self, ai, b=None, *args, **kw): "(ai, b=None, ..., ***)"
|
||||
def t7(self, (ai, b), c, (d, e)): "(<tuple>, c, <tuple>)"
|
||||
|
||||
def test(tests):
|
||||
ct = CallTips()
|
||||
failed=[]
|
||||
for t in tests:
|
||||
expected = t.__doc__ + "\n" + t.__doc__
|
||||
name = t.__name__
|
||||
# exercise fetch_tip(), not just get_arg_text()
|
||||
try:
|
||||
qualified_name = "%s.%s" % (t.im_class.__name__, name)
|
||||
except AttributeError:
|
||||
qualified_name = name
|
||||
arg_text = ct.fetch_tip(qualified_name)
|
||||
if arg_text != expected:
|
||||
failed.append(t)
|
||||
fmt = "%s - expected %s, but got %s"
|
||||
print fmt % (t.__name__, expected, get_arg_text(t))
|
||||
print "%d of %d tests failed" % (len(failed), len(tests))
|
||||
|
||||
tc = TC()
|
||||
tests = (t1, t2, t3, t4, t5, t6, t7,
|
||||
TC, tc.t1, tc.t2, tc.t3, tc.t4, tc.t5, tc.t6, tc.t7)
|
||||
|
||||
# test(tests)
|
||||
from unittest import main
|
||||
main('idlelib.idle_test.test_calltips', verbosity=2, exit=False)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,221 @@
|
||||
"""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
|
||||
|
||||
class ClassBrowser:
|
||||
|
||||
def __init__(self, flist, name, path):
|
||||
# XXX This API should change, if the file doesn't end in ".py"
|
||||
# XXX the code here is bogus!
|
||||
self.name = name
|
||||
self.file = os.path.join(path[0], self.name + ".py")
|
||||
self.init(flist)
|
||||
|
||||
def close(self, event=None):
|
||||
self.top.destroy()
|
||||
self.node.destroy()
|
||||
|
||||
def init(self, flist):
|
||||
self.flist = flist
|
||||
# reset pyclbr
|
||||
pyclbr._modules.clear()
|
||||
# create top
|
||||
self.top = top = ListedToplevel(flist.root)
|
||||
top.protocol("WM_DELETE_WINDOW", self.close)
|
||||
top.bind("<Escape>", self.close)
|
||||
self.settitle()
|
||||
top.focus_set()
|
||||
# create scrolled canvas
|
||||
theme = idleConf.GetOption('main','Theme','name')
|
||||
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, msg:
|
||||
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 = PyShell.flist.open(self.file)
|
||||
if hasattr(self.cl, 'lineno'):
|
||||
lineno = self.cl.lineno
|
||||
edit.gotoline(lineno)
|
||||
|
||||
def listmethods(self):
|
||||
if not self.cl:
|
||||
return []
|
||||
items = []
|
||||
for name, lineno in self.cl.methods.items():
|
||||
items.append((lineno, name))
|
||||
items.sort()
|
||||
list = []
|
||||
for item, name in items:
|
||||
list.append(name)
|
||||
return list
|
||||
|
||||
class MethodBrowserTreeItem(TreeItem):
|
||||
|
||||
def __init__(self, name, cl, file):
|
||||
self.name = name
|
||||
self.cl = cl
|
||||
self.file = file
|
||||
|
||||
def GetText(self):
|
||||
return "def " + self.name + "(...)"
|
||||
|
||||
def GetIconName(self):
|
||||
return "python" # XXX
|
||||
|
||||
def IsExpandable(self):
|
||||
return 0
|
||||
|
||||
def OnDoubleClick(self):
|
||||
if not os.path.exists(self.file):
|
||||
return
|
||||
edit = PyShell.flist.open(self.file)
|
||||
edit.gotoline(self.cl.methods[self.name])
|
||||
|
||||
def main():
|
||||
try:
|
||||
file = __file__
|
||||
except NameError:
|
||||
file = sys.argv[0]
|
||||
if sys.argv[1:]:
|
||||
file = sys.argv[1]
|
||||
else:
|
||||
file = sys.argv[0]
|
||||
dir, file = os.path.split(file)
|
||||
name = os.path.splitext(file)[0]
|
||||
ClassBrowser(PyShell.flist, name, [dir])
|
||||
if sys.stdin is sys.__stdin__:
|
||||
mainloop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,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 = set(["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)
|
||||
@@ -0,0 +1,268 @@
|
||||
import time
|
||||
import re
|
||||
import keyword
|
||||
import __builtin__
|
||||
from Tkinter import *
|
||||
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('_')]
|
||||
# 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)
|
||||
asprog = re.compile(r".*?\b(as)\b")
|
||||
|
||||
class ColorDelegator(Delegator):
|
||||
|
||||
def __init__(self):
|
||||
Delegator.__init__(self)
|
||||
self.prog = prog
|
||||
self.idprog = idprog
|
||||
self.asprog = asprog
|
||||
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.GetOption('main','Theme','name')
|
||||
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},
|
||||
"BREAK": idleConf.GetHighlight(theme, "break"),
|
||||
"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)
|
||||
elif value == "import":
|
||||
# color all the "as" words on same line, except
|
||||
# if in a comment; cheap approximation to the
|
||||
# truth
|
||||
if '#' in chars:
|
||||
endpos = chars.index('#')
|
||||
else:
|
||||
endpos = len(chars)
|
||||
while True:
|
||||
m1 = self.asprog.match(chars, b, endpos)
|
||||
if not m1:
|
||||
break
|
||||
a, b = m1.span(1)
|
||||
self.tag_add("KEYWORD",
|
||||
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 main():
|
||||
from idlelib.Percolator import Percolator
|
||||
root = Tk()
|
||||
root.wm_protocol("WM_DELETE_WINDOW", root.quit)
|
||||
text = Text(background="white")
|
||||
text.pack(expand=1, fill="both")
|
||||
text.focus_set()
|
||||
p = Percolator(text)
|
||||
d = ColorDelegator()
|
||||
p.insertfilter(d)
|
||||
root.mainloop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,481 @@
|
||||
import os
|
||||
import bdb
|
||||
import types
|
||||
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)
|
||||
self.gui.interaction(message, frame)
|
||||
|
||||
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
|
||||
|
||||
def run(self, *args):
|
||||
try:
|
||||
self.interacting = 1
|
||||
return self.idb.run(*args)
|
||||
finally:
|
||||
self.interacting = 0
|
||||
|
||||
def close(self, event=None):
|
||||
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()
|
||||
self.root.mainloop()
|
||||
#
|
||||
for b in self.buttons:
|
||||
b.configure(state="disabled")
|
||||
self.status.configure(text="")
|
||||
self.error.configure(text="", background=self.errorbg)
|
||||
self.frame = None
|
||||
|
||||
def sync_source_line(self):
|
||||
frame = self.frame
|
||||
if not frame:
|
||||
return
|
||||
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.root.quit()
|
||||
|
||||
def step(self):
|
||||
self.idb.set_step()
|
||||
self.root.quit()
|
||||
|
||||
def next(self):
|
||||
self.idb.set_next(self.frame)
|
||||
self.root.quit()
|
||||
|
||||
def ret(self):
|
||||
self.idb.set_return(self.frame)
|
||||
self.root.quit()
|
||||
|
||||
def quit(self):
|
||||
self.idb.set_quit()
|
||||
self.root.quit()
|
||||
|
||||
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, (frame, lineno)):
|
||||
self.frame = frame
|
||||
self.show_variables()
|
||||
|
||||
localsviewer = None
|
||||
globalsviewer = None
|
||||
|
||||
def show_locals(self):
|
||||
lv = self.localsviewer
|
||||
if self.vlocals.get():
|
||||
if not lv:
|
||||
self.localsviewer = 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.runningAsOSXApp():
|
||||
# At least on with the stock AquaTk version on OSX 10.4 you'll
|
||||
# get an 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()
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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()
|
||||
@@ -0,0 +1,191 @@
|
||||
"""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):
|
||||
"""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.
|
||||
"""
|
||||
maxformatwidth = idleConf.GetOption(
|
||||
'main', 'FormatParagraph', 'paragraph', type='int')
|
||||
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, maxformatwidth, comment_header)
|
||||
else:
|
||||
newdata = reformat_paragraph(data, maxformatwidth)
|
||||
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__":
|
||||
from test import support; support.use_resources = ['gui']
|
||||
import unittest
|
||||
unittest.main('idlelib.idle_test.test_formatparagraph',
|
||||
verbosity=2, exit=False)
|
||||
@@ -0,0 +1,127 @@
|
||||
import os
|
||||
import fnmatch
|
||||
import sys
|
||||
from Tkinter import *
|
||||
from idlelib import SearchEngine
|
||||
from idlelib.SearchDialogBase import SearchDialogBase
|
||||
|
||||
def grep(text, io=None, flist=None):
|
||||
root = text._root()
|
||||
engine = SearchEngine.get(root)
|
||||
if not hasattr(engine, "_grepdialog"):
|
||||
engine._grepdialog = GrepDialog(root, engine, flist)
|
||||
dialog = engine._grepdialog
|
||||
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)
|
||||
|
||||
def create_other_buttons(self):
|
||||
f = self.make_frame()
|
||||
|
||||
btn = Checkbutton(f, anchor="w",
|
||||
variable=self.recvar,
|
||||
text="Recurse down subdirectories")
|
||||
btn.pack(side="top", fill="both")
|
||||
btn.select()
|
||||
|
||||
def create_command_buttons(self):
|
||||
SearchDialogBase.create_command_buttons(self)
|
||||
self.make_button("Search Files", self.default_command, 1)
|
||||
|
||||
def default_command(self, event=None):
|
||||
prog = self.engine.getprog()
|
||||
if not prog:
|
||||
return
|
||||
path = self.globvar.get()
|
||||
if not path:
|
||||
self.top.bell()
|
||||
return
|
||||
from idlelib.OutputWindow import OutputWindow
|
||||
save = sys.stdout
|
||||
try:
|
||||
sys.stdout = OutputWindow(self.flist)
|
||||
self.grep_it(prog, path)
|
||||
finally:
|
||||
sys.stdout = save
|
||||
|
||||
def grep_it(self, prog, path):
|
||||
dir, base = os.path.split(path)
|
||||
list = self.findfiles(dir, base, self.recvar.get())
|
||||
list.sort()
|
||||
self.close()
|
||||
pat = self.engine.getpat()
|
||||
print "Searching %r in %s ..." % (pat, path)
|
||||
hits = 0
|
||||
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.")
|
||||
|
||||
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()
|
||||
|
||||
if __name__ == "__main__":
|
||||
# A human test is a bit tricky since EditorWindow() imports this module.
|
||||
# Hence Idle must be restarted after editing this file for a live test.
|
||||
import unittest
|
||||
unittest.main('idlelib.idle_test.test_grep', verbosity=2, exit=False)
|
||||
@@ -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.
|
||||
|
||||
======================================================================
|
||||
@@ -0,0 +1,246 @@
|
||||
"""
|
||||
HyperParser
|
||||
===========
|
||||
This module defines the HyperParser class, which provides advanced parsing
|
||||
abilities for the ParenMatch and other extensions.
|
||||
The HyperParser uses PyParser. PyParser is intended mostly to give information
|
||||
on the proper indentation of code. HyperParser gives some information on the
|
||||
structure of code, used by extensions to help the user.
|
||||
"""
|
||||
|
||||
import string
|
||||
import keyword
|
||||
from idlelib import PyParse
|
||||
|
||||
class HyperParser:
|
||||
|
||||
def __init__(self, editwin, index):
|
||||
"""Initialize the HyperParser to 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 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')
|
||||
parser.set_lo(0)
|
||||
|
||||
# We want what the parser has, except for the last newline and space.
|
||||
self.rawtext = parser.str[:-2]
|
||||
# As far as I can see, parser.str 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. Note that it must be
|
||||
in the same statement.
|
||||
"""
|
||||
indexinrawtext = \
|
||||
len(self.rawtext) - len(self.text.get(index, self.stopatindex))
|
||||
if indexinrawtext < 0:
|
||||
raise ValueError("The index given is before the analyzed statement")
|
||||
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 is 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 is in a 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):
|
||||
"""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
|
||||
|
||||
# This string includes all chars that may be in a white space
|
||||
_whitespace_chars = " \t\n\\"
|
||||
# This string includes all chars that may be in an identifier
|
||||
_id_chars = string.ascii_letters + string.digits + "_"
|
||||
# This string includes all 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 - one 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, in order 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]
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 120 B |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 96 B |
Binary file not shown.
|
After Width: | Height: | Size: 125 B |
Binary file not shown.
|
After Width: | Height: | Size: 79 B |
Binary file not shown.
|
After Width: | Height: | Size: 125 B |
Binary file not shown.
|
After Width: | Height: | Size: 85 B |
@@ -0,0 +1,106 @@
|
||||
"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 test import test_support as support
|
||||
support.use_resources = ['gui']
|
||||
from unittest import main
|
||||
main('idlelib.idle_test.test_idlehistory', verbosity=2, exit=False)
|
||||
@@ -0,0 +1,423 @@
|
||||
"""
|
||||
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
|
||||
from idlelib import macosxSupport
|
||||
|
||||
# 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 macosxSupport.runningAsOSXApp():
|
||||
_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 significent 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
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Test
|
||||
root = Tkinter.Tk()
|
||||
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("<Alt-Button-1>")
|
||||
bindseq("<FocusOut>")
|
||||
bindseq("<Enter>")
|
||||
bindseq("<Leave>")
|
||||
root.mainloop()
|
||||
@@ -0,0 +1,32 @@
|
||||
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):
|
||||
if name not in self.labels:
|
||||
label = Label(self, bd=1, relief=SUNKEN, anchor=W)
|
||||
label.pack(side=side)
|
||||
self.labels[name] = label
|
||||
else:
|
||||
label = self.labels[name]
|
||||
label.config(text=text)
|
||||
|
||||
def _test():
|
||||
b = Frame()
|
||||
c = Text(b)
|
||||
c.pack(side=TOP)
|
||||
a = MultiStatusBar(b)
|
||||
a.set_label("one", "hello")
|
||||
a.set_label("two", "world")
|
||||
a.pack(side=BOTTOM, fill=X)
|
||||
b.pack()
|
||||
b.mainloop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
_test()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,151 @@
|
||||
# XXX TO DO:
|
||||
# - popup menu
|
||||
# - support partial or total redisplay
|
||||
# - more doc strings
|
||||
# - tooltips
|
||||
|
||||
# object browser
|
||||
|
||||
# XXX TO DO:
|
||||
# - for classes/modules, add "open source" to object browser
|
||||
|
||||
from 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)
|
||||
|
||||
# Test script
|
||||
|
||||
def _test():
|
||||
import sys
|
||||
from Tkinter import Tk
|
||||
root = Tk()
|
||||
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__':
|
||||
_test()
|
||||
@@ -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.",
|
||||
master=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
|
||||
@@ -0,0 +1,172 @@
|
||||
"""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 # miliseconds
|
||||
|
||||
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))
|
||||
@@ -0,0 +1,96 @@
|
||||
import os
|
||||
import sys
|
||||
import imp
|
||||
|
||||
from idlelib.TreeWidget import TreeItem
|
||||
from idlelib.ClassBrowser import ClassBrowser, ModuleBrowserTreeItem
|
||||
|
||||
class PathBrowser(ClassBrowser):
|
||||
|
||||
def __init__(self, flist):
|
||||
self.init(flist)
|
||||
|
||||
def settitle(self):
|
||||
self.top.wm_title("Path Browser")
|
||||
self.top.wm_iconname("Path Browser")
|
||||
|
||||
def rootnode(self):
|
||||
return PathBrowserTreeItem()
|
||||
|
||||
class PathBrowserTreeItem(TreeItem):
|
||||
|
||||
def GetText(self):
|
||||
return "sys.path"
|
||||
|
||||
def GetSubList(self):
|
||||
sublist = []
|
||||
for dir in sys.path:
|
||||
item = DirBrowserTreeItem(dir)
|
||||
sublist.append(item)
|
||||
return sublist
|
||||
|
||||
class DirBrowserTreeItem(TreeItem):
|
||||
|
||||
def __init__(self, dir, packages=[]):
|
||||
self.dir = dir
|
||||
self.packages = packages
|
||||
|
||||
def GetText(self):
|
||||
if not self.packages:
|
||||
return self.dir
|
||||
else:
|
||||
return self.packages[-1] + ": package"
|
||||
|
||||
def GetSubList(self):
|
||||
try:
|
||||
names = os.listdir(self.dir or os.curdir)
|
||||
except os.error:
|
||||
return []
|
||||
packages = []
|
||||
for name in names:
|
||||
file = os.path.join(self.dir, name)
|
||||
if self.ispackagedir(file):
|
||||
nn = os.path.normcase(name)
|
||||
packages.append((nn, name, file))
|
||||
packages.sort()
|
||||
sublist = []
|
||||
for nn, name, file in packages:
|
||||
item = DirBrowserTreeItem(file, self.packages + [name])
|
||||
sublist.append(item)
|
||||
for nn, name in self.listmodules(names):
|
||||
item = ModuleBrowserTreeItem(os.path.join(self.dir, name))
|
||||
sublist.append(item)
|
||||
return sublist
|
||||
|
||||
def ispackagedir(self, file):
|
||||
if not os.path.isdir(file):
|
||||
return 0
|
||||
init = os.path.join(file, "__init__.py")
|
||||
return os.path.exists(init)
|
||||
|
||||
def listmodules(self, allnames):
|
||||
modules = {}
|
||||
suffixes = imp.get_suffixes()
|
||||
sorted = []
|
||||
for suff, mode, flag in suffixes:
|
||||
i = -len(suff)
|
||||
for name in allnames[:]:
|
||||
normed_name = os.path.normcase(name)
|
||||
if normed_name[i:] == suff:
|
||||
mod_name = name[:i]
|
||||
if mod_name not in modules:
|
||||
modules[mod_name] = None
|
||||
sorted.append((normed_name, name))
|
||||
allnames.remove(name)
|
||||
sorted.sort()
|
||||
return sorted
|
||||
|
||||
def main():
|
||||
from idlelib import PyShell
|
||||
PathBrowser(PyShell.flist)
|
||||
if sys.stdin is sys.__stdin__:
|
||||
mainloop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
from unittest import main
|
||||
main('idlelib.idle_test.test_pathbrowser', verbosity=2, exit=False)
|
||||
@@ -0,0 +1,85 @@
|
||||
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 main():
|
||||
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()
|
||||
root.wm_protocol("WM_DELETE_WINDOW", root.quit)
|
||||
text = Text()
|
||||
text.pack()
|
||||
text.focus_set()
|
||||
p = Percolator(text)
|
||||
t1 = Tracer("t1")
|
||||
t2 = Tracer("t2")
|
||||
p.insertfilter(t1)
|
||||
p.insertfilter(t2)
|
||||
root.mainloop()
|
||||
p.removefilter(t2)
|
||||
root.mainloop()
|
||||
p.insertfilter(t2)
|
||||
p.removefilter(t1)
|
||||
root.mainloop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
from Tkinter import *
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,63 @@
|
||||
IDLE is Python's Tkinter-based Integrated DeveLopment Environment.
|
||||
|
||||
IDLE emphasizes a lightweight, clean design with a simple user interface.
|
||||
Although it is suitable for beginners, even advanced users will find that
|
||||
IDLE has everything they really need to develop pure Python code.
|
||||
|
||||
IDLE features a multi-window text editor with multiple undo, Python colorizing,
|
||||
and many other capabilities, e.g. smart indent, call tips, and autocompletion.
|
||||
|
||||
The editor has comprehensive search functions, including searching through
|
||||
multiple files. Class browsers and path browsers provide fast access to
|
||||
code objects from a top level viewpoint without dealing with code folding.
|
||||
|
||||
There is a Python Shell window which features colorizing and command recall.
|
||||
|
||||
IDLE executes Python code in a separate process, which is restarted for each
|
||||
Run (F5) initiated from an editor window. The environment can also be
|
||||
restarted from the Shell window without restarting IDLE.
|
||||
|
||||
This enhancement has often been requested, and is now finally available. The
|
||||
magic "reload/import *" incantations are no longer required when editing and
|
||||
testing a module two or three steps down the import chain.
|
||||
|
||||
(Personal firewall software may warn about the connection IDLE makes to its
|
||||
subprocess using this computer's internal loopback interface. This connection
|
||||
is not visible on any external interface and no data is sent to or received
|
||||
from the Internet.)
|
||||
|
||||
It is possible to interrupt tightly looping user code, even on Windows.
|
||||
|
||||
Applications which cannot support subprocesses and/or sockets can still run
|
||||
IDLE in a single process.
|
||||
|
||||
IDLE has an integrated debugger with stepping, persistent breakpoints, and call
|
||||
stack visibility.
|
||||
|
||||
There is a GUI configuration manager which makes it easy to select fonts,
|
||||
colors, keybindings, and startup options. This facility includes a feature
|
||||
which allows the user to specify additional help sources, either locally or on
|
||||
the web.
|
||||
|
||||
IDLE is coded in 100% pure Python, using the Tkinter GUI toolkit (Tk/Tcl)
|
||||
and is cross-platform, working on Unix, Mac, and Windows.
|
||||
|
||||
IDLE accepts command line arguments. Try idle -h to see the options.
|
||||
|
||||
|
||||
If you find bugs or have suggestions, let us know about them by using the
|
||||
Python Bug Tracker:
|
||||
|
||||
http://sourceforge.net/projects/python
|
||||
|
||||
Patches are always appreciated at the Python Patch Tracker, and change
|
||||
requests should be posted to the RFE Tracker.
|
||||
|
||||
For further details and links, read the Help files and check the IDLE home
|
||||
page at
|
||||
|
||||
http://www.python.org/idle/
|
||||
|
||||
There is a mail list for IDLE: idle-dev@python.org. You can join at
|
||||
|
||||
http://mail.python.org/mailman/listinfo/idle-dev
|
||||
@@ -0,0 +1,380 @@
|
||||
"""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 rpc
|
||||
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(frame), k) for frame, k in stack]
|
||||
##print >>sys.__stderr__, "get_stack() ->", stack
|
||||
return stack, i
|
||||
|
||||
def run(self, cmd):
|
||||
import __main__
|
||||
self.idb.run(cmd, __main__.__dict__)
|
||||
|
||||
def set_break(self, filename, lineno):
|
||||
msg = self.idb.set_break(filename, lineno)
|
||||
return msg
|
||||
|
||||
def clear_break(self, filename, lineno):
|
||||
msg = self.idb.clear_break(filename, lineno)
|
||||
return msg
|
||||
|
||||
def clear_all_file_breaks(self, filename):
|
||||
msg = self.idb.clear_all_file_breaks(filename)
|
||||
return msg
|
||||
|
||||
#----------called by a FrameProxy----------
|
||||
|
||||
def frame_attr(self, fid, name):
|
||||
frame = frametable[fid]
|
||||
return getattr(frame, name)
|
||||
|
||||
def frame_globals(self, fid):
|
||||
frame = frametable[fid]
|
||||
dict = frame.f_globals
|
||||
did = id(dict)
|
||||
dicttable[did] = dict
|
||||
return did
|
||||
|
||||
def frame_locals(self, fid):
|
||||
frame = frametable[fid]
|
||||
dict = frame.f_locals
|
||||
did = id(dict)
|
||||
dicttable[did] = dict
|
||||
return did
|
||||
|
||||
def frame_code(self, fid):
|
||||
frame = frametable[fid]
|
||||
code = frame.f_code
|
||||
cid = id(code)
|
||||
codetable[cid] = code
|
||||
return cid
|
||||
|
||||
#----------called by a CodeProxy----------
|
||||
|
||||
def code_name(self, cid):
|
||||
code = codetable[cid]
|
||||
return code.co_name
|
||||
|
||||
def code_filename(self, cid):
|
||||
code = codetable[cid]
|
||||
return code.co_filename
|
||||
|
||||
#----------called by a DictProxy----------
|
||||
|
||||
def dict_keys(self, did):
|
||||
dict = dicttable[did]
|
||||
return dict.keys()
|
||||
|
||||
def dict_item(self, did, key):
|
||||
dict = dicttable[did]
|
||||
value = dict[key]
|
||||
value = repr(value)
|
||||
return value
|
||||
|
||||
#----------end class IdbAdapter----------
|
||||
|
||||
|
||||
def start_debugger(rpchandler, gui_adap_oid):
|
||||
"""Start the debugger and its RPC link in the Python subprocess
|
||||
|
||||
Start the subprocess side of the split debugger and set up that side of the
|
||||
RPC link by instantiating the GUIProxy, Idb debugger, and IdbAdapter
|
||||
objects and linking them together. Register the IdbAdapter with the
|
||||
RPCServer to handle RPC requests from the split debugger GUI via the
|
||||
IdbProxy.
|
||||
|
||||
"""
|
||||
gui_proxy = GUIProxy(rpchandler, gui_adap_oid)
|
||||
idb = Debugger.Idb(gui_proxy)
|
||||
idb_adap = IdbAdapter(idb)
|
||||
rpchandler.register(idb_adap_oid, idb_adap)
|
||||
return idb_adap_oid
|
||||
|
||||
|
||||
#=======================================
|
||||
#
|
||||
# In the IDLE process:
|
||||
|
||||
|
||||
class FrameProxy:
|
||||
|
||||
def __init__(self, conn, fid):
|
||||
self._conn = conn
|
||||
self._fid = fid
|
||||
self._oid = "idb_adapter"
|
||||
self._dictcache = {}
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name[:1] == "_":
|
||||
raise AttributeError, name
|
||||
if name == "f_code":
|
||||
return self._get_f_code()
|
||||
if name == "f_globals":
|
||||
return self._get_f_globals()
|
||||
if name == "f_locals":
|
||||
return self._get_f_locals()
|
||||
return self._conn.remotecall(self._oid, "frame_attr",
|
||||
(self._fid, name), {})
|
||||
|
||||
def _get_f_code(self):
|
||||
cid = self._conn.remotecall(self._oid, "frame_code", (self._fid,), {})
|
||||
return CodeProxy(self._conn, self._oid, cid)
|
||||
|
||||
def _get_f_globals(self):
|
||||
did = self._conn.remotecall(self._oid, "frame_globals",
|
||||
(self._fid,), {})
|
||||
return self._get_dict_proxy(did)
|
||||
|
||||
def _get_f_locals(self):
|
||||
did = self._conn.remotecall(self._oid, "frame_locals",
|
||||
(self._fid,), {})
|
||||
return self._get_dict_proxy(did)
|
||||
|
||||
def _get_dict_proxy(self, did):
|
||||
if 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'
|
||||
@@ -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]
|
||||
@@ -0,0 +1,189 @@
|
||||
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)
|
||||
|
||||
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 a 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")
|
||||
@@ -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)
|
||||
@@ -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.runningAsOSXApp():
|
||||
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
|
||||
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)
|
||||
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.runningAsOSXApp():
|
||||
# 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,
|
||||
master=self.editwin.text)
|
||||
return confirm
|
||||
|
||||
def errorbox(self, title, message):
|
||||
# XXX This should really be a function of EditorWindow...
|
||||
tkMessageBox.showerror(title, message, master=self.editwin.text)
|
||||
self.editwin.text.focus_set()
|
||||
@@ -0,0 +1,139 @@
|
||||
from Tkinter import *
|
||||
|
||||
class ScrolledList:
|
||||
|
||||
default = "(None)"
|
||||
|
||||
def __init__(self, master, **options):
|
||||
# Create top frame, with scrollbar and listbox
|
||||
self.master = master
|
||||
self.frame = frame = Frame(master)
|
||||
self.frame.pack(fill="both", expand=1)
|
||||
self.vbar = vbar = Scrollbar(frame, name="vbar")
|
||||
self.vbar.pack(side="right", fill="y")
|
||||
self.listbox = listbox = Listbox(frame, exportselection=0,
|
||||
background="white")
|
||||
if options:
|
||||
listbox.configure(options)
|
||||
listbox.pack(expand=1, fill="both")
|
||||
# Tie listbox and scrollbar together
|
||||
vbar["command"] = listbox.yview
|
||||
listbox["yscrollcommand"] = vbar.set
|
||||
# Bind events to the list box
|
||||
listbox.bind("<ButtonRelease-1>", self.click_event)
|
||||
listbox.bind("<Double-ButtonRelease-1>", self.double_click_event)
|
||||
listbox.bind("<ButtonPress-3>", self.popup_event)
|
||||
listbox.bind("<Key-Up>", self.up_event)
|
||||
listbox.bind("<Key-Down>", self.down_event)
|
||||
# Mark as empty
|
||||
self.clear()
|
||||
|
||||
def close(self):
|
||||
self.frame.destroy()
|
||||
|
||||
def clear(self):
|
||||
self.listbox.delete(0, "end")
|
||||
self.empty = 1
|
||||
self.listbox.insert("end", self.default)
|
||||
|
||||
def append(self, item):
|
||||
if self.empty:
|
||||
self.listbox.delete(0, "end")
|
||||
self.empty = 0
|
||||
self.listbox.insert("end", str(item))
|
||||
|
||||
def get(self, index):
|
||||
return self.listbox.get(index)
|
||||
|
||||
def click_event(self, event):
|
||||
self.listbox.activate("@%d,%d" % (event.x, event.y))
|
||||
index = self.listbox.index("active")
|
||||
self.select(index)
|
||||
self.on_select(index)
|
||||
return "break"
|
||||
|
||||
def double_click_event(self, event):
|
||||
index = self.listbox.index("active")
|
||||
self.select(index)
|
||||
self.on_double(index)
|
||||
return "break"
|
||||
|
||||
menu = None
|
||||
|
||||
def popup_event(self, event):
|
||||
if not self.menu:
|
||||
self.make_menu()
|
||||
menu = self.menu
|
||||
self.listbox.activate("@%d,%d" % (event.x, event.y))
|
||||
index = self.listbox.index("active")
|
||||
self.select(index)
|
||||
menu.tk_popup(event.x_root, event.y_root)
|
||||
|
||||
def make_menu(self):
|
||||
menu = Menu(self.listbox, tearoff=0)
|
||||
self.menu = menu
|
||||
self.fill_menu()
|
||||
|
||||
def up_event(self, event):
|
||||
index = self.listbox.index("active")
|
||||
if self.listbox.selection_includes(index):
|
||||
index = index - 1
|
||||
else:
|
||||
index = self.listbox.size() - 1
|
||||
if index < 0:
|
||||
self.listbox.bell()
|
||||
else:
|
||||
self.select(index)
|
||||
self.on_select(index)
|
||||
return "break"
|
||||
|
||||
def down_event(self, event):
|
||||
index = self.listbox.index("active")
|
||||
if self.listbox.selection_includes(index):
|
||||
index = index + 1
|
||||
else:
|
||||
index = 0
|
||||
if index >= self.listbox.size():
|
||||
self.listbox.bell()
|
||||
else:
|
||||
self.select(index)
|
||||
self.on_select(index)
|
||||
return "break"
|
||||
|
||||
def select(self, index):
|
||||
self.listbox.focus_set()
|
||||
self.listbox.activate(index)
|
||||
self.listbox.selection_clear(0, "end")
|
||||
self.listbox.selection_set(index)
|
||||
self.listbox.see(index)
|
||||
|
||||
# Methods to override for specific actions
|
||||
|
||||
def fill_menu(self):
|
||||
pass
|
||||
|
||||
def on_select(self, index):
|
||||
pass
|
||||
|
||||
def on_double(self, index):
|
||||
pass
|
||||
|
||||
|
||||
def test():
|
||||
root = Tk()
|
||||
root.protocol("WM_DELETE_WINDOW", root.destroy)
|
||||
class MyScrolledList(ScrolledList):
|
||||
def fill_menu(self): self.menu.add_command(label="pass")
|
||||
def on_select(self, index): print "select", self.get(index)
|
||||
def on_double(self, index): print "double", self.get(index)
|
||||
s = MyScrolledList(root)
|
||||
for i in range(30):
|
||||
s.append("item %02d" % i)
|
||||
return root
|
||||
|
||||
def main():
|
||||
root = test()
|
||||
root.mainloop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,67 @@
|
||||
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):
|
||||
f = 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)
|
||||
@@ -0,0 +1,157 @@
|
||||
'''Define SearchDialogBase used by Search, Replace, and Grep dialogs.'''
|
||||
from Tkinter import *
|
||||
|
||||
class SearchDialogBase:
|
||||
'''Create most of a modal search dialog (make_frame, create_widgets).
|
||||
|
||||
The wide left column contains:
|
||||
1 or 2 text entry lines (create_entries, make_entry);
|
||||
a row of standard radiobuttons (create_option_buttons);
|
||||
a row of dialog specific radiobuttons (create_other_buttons).
|
||||
|
||||
The narrow right column contains command buttons
|
||||
(create_command_buttons, make_button).
|
||||
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 and its execution function.
|
||||
The other dialogs override methods to replace and add widgets.
|
||||
'''
|
||||
|
||||
title = "Search Dialog"
|
||||
icon = "Search"
|
||||
needwrapbutton = 1
|
||||
|
||||
def __init__(self, root, engine):
|
||||
self.root = root
|
||||
self.engine = engine
|
||||
self.top = None
|
||||
|
||||
def open(self, text, searchphrase=None):
|
||||
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):
|
||||
if self.top:
|
||||
self.top.grab_release()
|
||||
self.top.withdraw()
|
||||
|
||||
def create_widgets(self):
|
||||
top = Toplevel(self.root)
|
||||
top.bind("<Return>", self.default_command)
|
||||
top.bind("<Escape>", self.close)
|
||||
top.protocol("WM_DELETE_WINDOW", self.close)
|
||||
top.wm_title(self.title)
|
||||
top.wm_iconname(self.icon)
|
||||
self.top = top
|
||||
|
||||
self.row = 0
|
||||
self.top.grid_columnconfigure(0, pad=2, weight=0)
|
||||
self.top.grid_columnconfigure(1, pad=2, minsize=100, weight=100)
|
||||
|
||||
self.create_entries()
|
||||
self.create_option_buttons()
|
||||
self.create_other_buttons()
|
||||
return self.create_command_buttons()
|
||||
|
||||
def make_entry(self, label, var):
|
||||
l = Label(self.top, text=label)
|
||||
l.grid(row=self.row, column=0, sticky="nw")
|
||||
e = Entry(self.top, textvariable=var, exportselection=0)
|
||||
e.grid(row=self.row, column=1, sticky="nwe")
|
||||
self.row = self.row + 1
|
||||
return e
|
||||
|
||||
def make_frame(self,labeltext=None):
|
||||
if labeltext:
|
||||
l = Label(self.top, text=labeltext)
|
||||
l.grid(row=self.row, column=0, sticky="nw")
|
||||
f = Frame(self.top)
|
||||
f.grid(row=self.row, column=1, columnspan=1, sticky="nwe")
|
||||
self.row = self.row + 1
|
||||
return f
|
||||
|
||||
def make_button(self, label, command, isdef=0):
|
||||
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_entries(self):
|
||||
self.ent = self.make_entry("Find:", self.engine.patvar)
|
||||
|
||||
def create_option_buttons(self):
|
||||
f = self.make_frame("Options")
|
||||
|
||||
btn = Checkbutton(f, anchor="w",
|
||||
variable=self.engine.revar,
|
||||
text="Regular expression")
|
||||
btn.pack(side="left", fill="both")
|
||||
if self.engine.isre():
|
||||
btn.select()
|
||||
|
||||
btn = Checkbutton(f, anchor="w",
|
||||
variable=self.engine.casevar,
|
||||
text="Match case")
|
||||
btn.pack(side="left", fill="both")
|
||||
if self.engine.iscase():
|
||||
btn.select()
|
||||
|
||||
btn = Checkbutton(f, anchor="w",
|
||||
variable=self.engine.wordvar,
|
||||
text="Whole word")
|
||||
btn.pack(side="left", fill="both")
|
||||
if self.engine.isword():
|
||||
btn.select()
|
||||
|
||||
if self.needwrapbutton:
|
||||
btn = Checkbutton(f, anchor="w",
|
||||
variable=self.engine.wrapvar,
|
||||
text="Wrap around")
|
||||
btn.pack(side="left", fill="both")
|
||||
if self.engine.iswrap():
|
||||
btn.select()
|
||||
|
||||
def create_other_buttons(self):
|
||||
f = self.make_frame("Direction")
|
||||
|
||||
#lbl = Label(f, text="Direction: ")
|
||||
#lbl.pack(side="left")
|
||||
|
||||
btn = Radiobutton(f, anchor="w",
|
||||
variable=self.engine.backvar, value=1,
|
||||
text="Up")
|
||||
btn.pack(side="left", fill="both")
|
||||
if self.engine.isback():
|
||||
btn.select()
|
||||
|
||||
btn = Radiobutton(f, anchor="w",
|
||||
variable=self.engine.backvar, value=0,
|
||||
text="Down")
|
||||
btn.pack(side="left", fill="both")
|
||||
if not self.engine.isback():
|
||||
btn.select()
|
||||
|
||||
def create_command_buttons(self):
|
||||
#
|
||||
# place button frame on the 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()
|
||||
@@ -0,0 +1,236 @@
|
||||
'''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:
|
||||
try:
|
||||
msg, col = what
|
||||
except:
|
||||
msg = str(what)
|
||||
col = -1
|
||||
self.report_error(pat, msg, col)
|
||||
return None
|
||||
return prog
|
||||
|
||||
def report_error(self, pat, msg, col=-1):
|
||||
# Derived class could 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 parameteris 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__":
|
||||
from test import support; support.use_resources = ['gui']
|
||||
import unittest
|
||||
unittest.main('idlelib.idle_test.test_searchengine', verbosity=2, exit=False)
|
||||
@@ -0,0 +1,137 @@
|
||||
import os
|
||||
import sys
|
||||
import linecache
|
||||
|
||||
from idlelib.TreeWidget import TreeNode, TreeItem, ScrolledCanvas
|
||||
from idlelib.ObjectBrowser import ObjectTreeItem, make_objecttreeitem
|
||||
|
||||
def StackBrowser(root, flist=None, tb=None, top=None):
|
||||
if top is None:
|
||||
from Tkinter import Toplevel
|
||||
top = 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 keys(self):
|
||||
return self.object.keys()
|
||||
|
||||
def GetSubList(self):
|
||||
sublist = []
|
||||
for key in self.keys():
|
||||
try:
|
||||
value = self.object[key]
|
||||
except KeyError:
|
||||
continue
|
||||
def setfunction(value, key=key, object=self.object):
|
||||
object[key] = value
|
||||
item = make_objecttreeitem(key + " =", value, setfunction)
|
||||
sublist.append(item)
|
||||
return sublist
|
||||
|
||||
|
||||
def _test():
|
||||
try:
|
||||
import testcode
|
||||
reload(testcode)
|
||||
except:
|
||||
sys.last_type, sys.last_value, sys.last_traceback = sys.exc_info()
|
||||
from Tkinter import Tk
|
||||
root = Tk()
|
||||
StackBrowser(None, top=root)
|
||||
root.mainloop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
_test()
|
||||
@@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user