mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
Merge IDLE-syntax-branch r39668:41449 into trunk
A idlelib/AutoCompleteWindow.py A idlelib/AutoComplete.py A idlelib/HyperParser.py M idlelib/PyShell.py M idlelib/ParenMatch.py M idlelib/configDialog.py M idlelib/EditorWindow.py M idlelib/PyParse.py M idlelib/CallTips.py M idlelib/CallTipWindow.py M idlelib/run.py M idlelib/config-extensions.def A idlelib/MultiCall.py
This commit is contained in:
parent
c85c74cd08
commit
b17544551f
13 changed files with 1593 additions and 202 deletions
226
Lib/idlelib/AutoComplete.py
Normal file
226
Lib/idlelib/AutoComplete.py
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
"""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 configHandler import idleConf
|
||||||
|
|
||||||
|
import AutoCompleteWindow
|
||||||
|
from HyperParser import HyperParser
|
||||||
|
|
||||||
|
import __main__
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
class AutoComplete:
|
||||||
|
|
||||||
|
menudefs = [
|
||||||
|
('edit', [
|
||||||
|
("Show completions", "<<force-open-completions>>"),
|
||||||
|
])
|
||||||
|
]
|
||||||
|
|
||||||
|
popupwait = idleConf.GetOption("extensions", "AutoComplete",
|
||||||
|
"popupwait", type="int", default=0)
|
||||||
|
|
||||||
|
def __init__(self, editwin=None):
|
||||||
|
if editwin == None: # subprocess and test
|
||||||
|
self.editwin = None
|
||||||
|
return
|
||||||
|
self.editwin = editwin
|
||||||
|
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 neccesary, 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 == os.sep:
|
||||||
|
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 neccesary,
|
||||||
|
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+os.sep:
|
||||||
|
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()
|
||||||
|
self.autocompletewindow.show_window(comp_lists,
|
||||||
|
"insert-%dc" % len(comp_start),
|
||||||
|
complete,
|
||||||
|
mode,
|
||||||
|
userWantsWin)
|
||||||
|
return True
|
||||||
|
|
||||||
|
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 = eval("__all__", namespace)
|
||||||
|
smalll.sort()
|
||||||
|
else:
|
||||||
|
smalll = filter(lambda s: s[:1] != '_', bigl)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
entity = self.get_entity(what)
|
||||||
|
bigl = dir(entity)
|
||||||
|
bigl.sort()
|
||||||
|
if "__all__" in bigl:
|
||||||
|
smalll = entity.__all__
|
||||||
|
smalll.sort()
|
||||||
|
else:
|
||||||
|
smalll = filter(lambda s: s[:1] != '_', bigl)
|
||||||
|
except:
|
||||||
|
return [], []
|
||||||
|
|
||||||
|
elif mode == COMPLETE_FILES:
|
||||||
|
if what == "":
|
||||||
|
what = "."
|
||||||
|
try:
|
||||||
|
expandedpath = os.path.expanduser(what)
|
||||||
|
bigl = os.listdir(expandedpath)
|
||||||
|
bigl.sort()
|
||||||
|
smalll = filter(lambda s: s[:1] != '.', bigl)
|
||||||
|
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)
|
393
Lib/idlelib/AutoCompleteWindow.py
Normal file
393
Lib/idlelib/AutoCompleteWindow.py
Normal file
|
@ -0,0 +1,393 @@
|
||||||
|
"""
|
||||||
|
An auto-completion window for IDLE, used by the AutoComplete extension
|
||||||
|
"""
|
||||||
|
from Tkinter import *
|
||||||
|
from MultiCall import MC_SHIFT
|
||||||
|
import AutoComplete
|
||||||
|
|
||||||
|
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-Up>", "<Key-Down>", "<Key-Home>", "<Key-End>")
|
||||||
|
KEYRELEASE_VIRTUAL_EVENT_NAME = "<<autocompletewindow-keyrelease>>"
|
||||||
|
KEYRELEASE_SEQUENCE = "<KeyRelease>"
|
||||||
|
LISTUPDATE_SEQUENCE = "<ButtonRelease>"
|
||||||
|
WINCONFIG_SEQUENCE = "<Configure>"
|
||||||
|
DOUBLECLICK_SEQUENCE = "<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
|
||||||
|
|
||||||
|
def _change_start(self, newstart):
|
||||||
|
i = 0
|
||||||
|
while i < len(self.start) and i < len(newstart) 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
|
||||||
|
|
||||||
|
# We should return the maximum prefix of first and last
|
||||||
|
i = len(s)
|
||||||
|
while len(self.completions[first]) > i and \
|
||||||
|
len(self.completions[last]) > i and \
|
||||||
|
self.completions[first][i] == self.completions[last][i]:
|
||||||
|
i += 1
|
||||||
|
return self.completions[first][: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:
|
||||||
|
i = 0
|
||||||
|
while i < len(lts) and i < len(selstart) and lts[i] == selstart[i]:
|
||||||
|
i += 1
|
||||||
|
while cursel > 0 and selstart[:i] <= self.completions[cursel-1]:
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
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.listupdate_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
|
||||||
|
acw = self.autocompletewindow
|
||||||
|
self.widget.see(self.startindex)
|
||||||
|
x, y, cx, cy = self.widget.bbox(self.startindex)
|
||||||
|
acw.wm_geometry("+%d+%d" % (x + self.widget.winfo_rootx(),
|
||||||
|
y + self.widget.winfo_rooty() \
|
||||||
|
-acw.winfo_height()))
|
||||||
|
|
||||||
|
|
||||||
|
def hide_event(self, event):
|
||||||
|
if not self.is_active():
|
||||||
|
return
|
||||||
|
self.hide_window()
|
||||||
|
|
||||||
|
def listupdate_event(self, event):
|
||||||
|
if not self.is_active():
|
||||||
|
return
|
||||||
|
self.userwantswindow = True
|
||||||
|
self._selection_changed()
|
||||||
|
|
||||||
|
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 (len(keysym) == 1 or keysym in ("underscore", "BackSpace")
|
||||||
|
or (self.mode==AutoComplete.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" and not state:
|
||||||
|
# If start is a prefix of the selection, or there was an indication
|
||||||
|
# that the user used the completion window, put the selected
|
||||||
|
# completion in the text, and close the list.
|
||||||
|
# Otherwise, close the window and let the event through.
|
||||||
|
cursel = int(self.listbox.curselection()[0])
|
||||||
|
if self.completions[cursel][:len(self.start)] == self.start or \
|
||||||
|
self.userwantswindow:
|
||||||
|
self._change_start(self.completions[cursel])
|
||||||
|
self.hide_window()
|
||||||
|
return "break"
|
||||||
|
else:
|
||||||
|
self.hide_window()
|
||||||
|
return
|
||||||
|
|
||||||
|
elif (self.mode == AutoComplete.COMPLETE_ATTRIBUTES and keysym in
|
||||||
|
("period", "space", "parenleft", "parenright", "bracketleft",
|
||||||
|
"bracketright")) or \
|
||||||
|
(self.mode == AutoComplete.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==AutoComplete.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()
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
elif (keysym == "Tab" and not state):
|
||||||
|
# The user wants a completion, but it is handled by AutoComplete
|
||||||
|
# (not AutoCompleteWindow), so ignore.
|
||||||
|
self.userwantswindow = True
|
||||||
|
return
|
||||||
|
|
||||||
|
elif reduce(lambda x, y: x or y,
|
||||||
|
[keysym.find(s) != -1 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
|
|
@ -6,33 +6,65 @@ Used by the CallTips IDLE extension.
|
||||||
"""
|
"""
|
||||||
from Tkinter import *
|
from Tkinter import *
|
||||||
|
|
||||||
|
HIDE_VIRTUAL_EVENT_NAME = "<<caltipwindow-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:
|
class CallTip:
|
||||||
|
|
||||||
def __init__(self, widget):
|
def __init__(self, widget):
|
||||||
self.widget = widget
|
self.widget = widget
|
||||||
self.tipwindow = None
|
self.tipwindow = self.label = None
|
||||||
self.id = None
|
self.parenline = self.parencol = None
|
||||||
self.x = self.y = 0
|
self.lastline = None
|
||||||
|
self.hideid = self.checkhideid = None
|
||||||
|
|
||||||
def showtip(self, text):
|
def position_window(self):
|
||||||
" Display text in calltip window"
|
"""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
|
# truncate overly long calltip
|
||||||
if len(text) >= 79:
|
if len(text) >= 79:
|
||||||
text = text[:75] + ' ...'
|
text = text[:75] + ' ...'
|
||||||
self.text = text
|
self.text = text
|
||||||
if self.tipwindow or not self.text:
|
if self.tipwindow or not self.text:
|
||||||
return
|
return
|
||||||
self.widget.see("insert")
|
|
||||||
x, y, cx, cy = self.widget.bbox("insert")
|
self.widget.mark_set(MARK_RIGHT, parenright)
|
||||||
x = x + self.widget.winfo_rootx() + 2
|
self.parenline, self.parencol = map(
|
||||||
y = y + cy + self.widget.winfo_rooty()
|
int, self.widget.index(parenleft).split("."))
|
||||||
|
|
||||||
self.tipwindow = tw = Toplevel(self.widget)
|
self.tipwindow = tw = Toplevel(self.widget)
|
||||||
|
self.position_window()
|
||||||
# XXX 12 Dec 2002 KBK The following command has two effects: It removes
|
# XXX 12 Dec 2002 KBK The following command has two effects: It removes
|
||||||
# the calltip window border (good) but also causes (at least on
|
# the calltip window border (good) but also causes (at least on
|
||||||
# Linux) the calltip to show as a top level window, burning through
|
# Linux) the calltip to show as a top level window, burning through
|
||||||
# any other window dragged over it. Also, shows on all viewports!
|
# any other window dragged over it. Also, shows on all viewports!
|
||||||
tw.wm_overrideredirect(1)
|
tw.wm_overrideredirect(1)
|
||||||
tw.wm_geometry("+%d+%d" % (x, y))
|
|
||||||
try:
|
try:
|
||||||
# This command is only needed and available on Tk >= 8.4.0 for OSX
|
# 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
|
# Without it, call tips intrude on the typing process by grabbing
|
||||||
|
@ -41,16 +73,66 @@ class CallTip:
|
||||||
"help", "noActivates")
|
"help", "noActivates")
|
||||||
except TclError:
|
except TclError:
|
||||||
pass
|
pass
|
||||||
label = Label(tw, text=self.text, justify=LEFT,
|
self.label = Label(tw, text=self.text, justify=LEFT,
|
||||||
background="#ffffe0", relief=SOLID, borderwidth=1,
|
background="#ffffe0", relief=SOLID, borderwidth=1,
|
||||||
font = self.widget['font'])
|
font = self.widget['font'])
|
||||||
label.pack()
|
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()
|
||||||
|
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):
|
def hidetip(self):
|
||||||
tw = self.tipwindow
|
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.tipwindow = None
|
||||||
if tw:
|
|
||||||
tw.destroy()
|
self.widget.mark_unset(MARK_RIGHT)
|
||||||
|
self.parenline = self.parencol = self.lastline = None
|
||||||
|
|
||||||
|
def is_active(self):
|
||||||
|
return bool(self.tipwindow)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###############################
|
###############################
|
||||||
|
|
|
@ -3,21 +3,21 @@
|
||||||
Call Tips are floating windows which display function, class, and method
|
Call Tips are floating windows which display function, class, and method
|
||||||
parameter and docstring information when you type an opening parenthesis, and
|
parameter and docstring information when you type an opening parenthesis, and
|
||||||
which disappear when you type a closing parenthesis.
|
which disappear when you type a closing parenthesis.
|
||||||
|
|
||||||
Future plans include extending the functionality to include class attributes.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import sys
|
import sys
|
||||||
import string
|
|
||||||
import types
|
import types
|
||||||
|
|
||||||
import CallTipWindow
|
import CallTipWindow
|
||||||
|
from HyperParser import HyperParser
|
||||||
|
|
||||||
import __main__
|
import __main__
|
||||||
|
|
||||||
class CallTips:
|
class CallTips:
|
||||||
|
|
||||||
menudefs = [
|
menudefs = [
|
||||||
|
('edit', [
|
||||||
|
("Show call tip", "<<force-open-calltip>>"),
|
||||||
|
])
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, editwin=None):
|
def __init__(self, editwin=None):
|
||||||
|
@ -36,51 +36,47 @@ class CallTips:
|
||||||
# See __init__ for usage
|
# See __init__ for usage
|
||||||
return CallTipWindow.CallTip(self.text)
|
return CallTipWindow.CallTip(self.text)
|
||||||
|
|
||||||
def _remove_calltip_window(self):
|
def _remove_calltip_window(self, event=None):
|
||||||
if self.calltip:
|
if self.calltip:
|
||||||
self.calltip.hidetip()
|
self.calltip.hidetip()
|
||||||
self.calltip = None
|
self.calltip = None
|
||||||
|
|
||||||
def paren_open_event(self, event):
|
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
|
||||||
|
neccesary, 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()
|
self._remove_calltip_window()
|
||||||
name = self.get_name_at_cursor()
|
|
||||||
|
hp = HyperParser(self.editwin, "insert")
|
||||||
|
sur_paren = hp.get_surrounding_brackets('(')
|
||||||
|
if not sur_paren:
|
||||||
|
return
|
||||||
|
hp.set_index(sur_paren[0])
|
||||||
|
name = hp.get_expression()
|
||||||
|
if not name or (not evalfuncs and name.find('(') != -1):
|
||||||
|
return
|
||||||
arg_text = self.fetch_tip(name)
|
arg_text = self.fetch_tip(name)
|
||||||
if arg_text:
|
if not arg_text:
|
||||||
self.calltip_start = self.text.index("insert")
|
return
|
||||||
self.calltip = self._make_calltip_window()
|
self.calltip = self._make_calltip_window()
|
||||||
self.calltip.showtip(arg_text)
|
self.calltip.showtip(arg_text, sur_paren[0], sur_paren[1])
|
||||||
return "" #so the event is handled normally.
|
|
||||||
|
|
||||||
def paren_close_event(self, event):
|
|
||||||
# Now just hides, but later we should check if other
|
|
||||||
# paren'd expressions remain open.
|
|
||||||
self._remove_calltip_window()
|
|
||||||
return "" #so the event is handled normally.
|
|
||||||
|
|
||||||
def check_calltip_cancel_event(self, event):
|
|
||||||
if self.calltip:
|
|
||||||
# If we have moved before the start of the calltip,
|
|
||||||
# or off the calltip line, then cancel the tip.
|
|
||||||
# (Later need to be smarter about multi-line, etc)
|
|
||||||
if self.text.compare("insert", "<=", self.calltip_start) or \
|
|
||||||
self.text.compare("insert", ">", self.calltip_start
|
|
||||||
+ " lineend"):
|
|
||||||
self._remove_calltip_window()
|
|
||||||
return "" #so the event is handled normally.
|
|
||||||
|
|
||||||
def calltip_cancel_event(self, event):
|
|
||||||
self._remove_calltip_window()
|
|
||||||
return "" #so the event is handled normally.
|
|
||||||
|
|
||||||
__IDCHARS = "._" + string.ascii_letters + string.digits
|
|
||||||
|
|
||||||
def get_name_at_cursor(self):
|
|
||||||
idchars = self.__IDCHARS
|
|
||||||
str = self.text.get("insert linestart", "insert")
|
|
||||||
i = len(str)
|
|
||||||
while i and str[i-1] in idchars:
|
|
||||||
i -= 1
|
|
||||||
return str[i:]
|
|
||||||
|
|
||||||
def fetch_tip(self, name):
|
def fetch_tip(self, name):
|
||||||
"""Return the argument list and docstring of a function or class
|
"""Return the argument list and docstring of a function or class
|
||||||
|
@ -127,7 +123,7 @@ def _find_constructor(class_ob):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_arg_text(ob):
|
def get_arg_text(ob):
|
||||||
"Get a string describing the arguments for the given object"
|
"""Get a string describing the arguments for the given object"""
|
||||||
argText = ""
|
argText = ""
|
||||||
if ob is not None:
|
if ob is not None:
|
||||||
argOffset = 0
|
argOffset = 0
|
||||||
|
@ -150,7 +146,7 @@ def get_arg_text(ob):
|
||||||
try:
|
try:
|
||||||
realArgs = fob.func_code.co_varnames[argOffset:fob.func_code.co_argcount]
|
realArgs = fob.func_code.co_varnames[argOffset:fob.func_code.co_argcount]
|
||||||
defaults = fob.func_defaults or []
|
defaults = fob.func_defaults or []
|
||||||
defaults = list(map(lambda name: "=%s" % name, defaults))
|
defaults = list(map(lambda name: "=%s" % repr(name), defaults))
|
||||||
defaults = [""] * (len(realArgs)-len(defaults)) + defaults
|
defaults = [""] * (len(realArgs)-len(defaults)) + defaults
|
||||||
items = map(lambda arg, dflt: arg+dflt, realArgs, defaults)
|
items = map(lambda arg, dflt: arg+dflt, realArgs, defaults)
|
||||||
if fob.func_code.co_flags & 0x4:
|
if fob.func_code.co_flags & 0x4:
|
||||||
|
|
|
@ -6,6 +6,7 @@ from itertools import count
|
||||||
from Tkinter import *
|
from Tkinter import *
|
||||||
import tkSimpleDialog
|
import tkSimpleDialog
|
||||||
import tkMessageBox
|
import tkMessageBox
|
||||||
|
from MultiCall import MultiCallCreator
|
||||||
|
|
||||||
import webbrowser
|
import webbrowser
|
||||||
import idlever
|
import idlever
|
||||||
|
@ -89,7 +90,8 @@ class EditorWindow(object):
|
||||||
self.vbar = vbar = Scrollbar(top, name='vbar')
|
self.vbar = vbar = Scrollbar(top, name='vbar')
|
||||||
self.text_frame = text_frame = Frame(top)
|
self.text_frame = text_frame = Frame(top)
|
||||||
self.width = idleConf.GetOption('main','EditorWindow','width')
|
self.width = idleConf.GetOption('main','EditorWindow','width')
|
||||||
self.text = text = Text(text_frame, name='text', padx=5, wrap='none',
|
self.text = text = MultiCallCreator(Text)(
|
||||||
|
text_frame, name='text', padx=5, wrap='none',
|
||||||
foreground=idleConf.GetHighlight(currentTheme,
|
foreground=idleConf.GetHighlight(currentTheme,
|
||||||
'normal',fgBg='fg'),
|
'normal',fgBg='fg'),
|
||||||
background=idleConf.GetHighlight(currentTheme,
|
background=idleConf.GetHighlight(currentTheme,
|
||||||
|
@ -264,8 +266,9 @@ class EditorWindow(object):
|
||||||
self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
|
self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
|
||||||
self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
|
self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
|
||||||
self.status_bar.pack(side=BOTTOM, fill=X)
|
self.status_bar.pack(side=BOTTOM, fill=X)
|
||||||
self.text.bind('<KeyRelease>', self.set_line_and_column)
|
self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
|
||||||
self.text.bind('<ButtonRelease>', self.set_line_and_column)
|
self.text.event_add("<<set-line-and-column>>",
|
||||||
|
"<KeyRelease>", "<ButtonRelease>")
|
||||||
self.text.after_idle(self.set_line_and_column)
|
self.text.after_idle(self.set_line_and_column)
|
||||||
|
|
||||||
def set_line_and_column(self, event=None):
|
def set_line_and_column(self, event=None):
|
||||||
|
@ -355,6 +358,9 @@ class EditorWindow(object):
|
||||||
return "break"
|
return "break"
|
||||||
|
|
||||||
def copy(self,event):
|
def copy(self,event):
|
||||||
|
if not self.text.tag_ranges("sel"):
|
||||||
|
# There is no selection, so do nothing and maybe interrupt.
|
||||||
|
return
|
||||||
self.text.event_generate("<<Copy>>")
|
self.text.event_generate("<<Copy>>")
|
||||||
return "break"
|
return "break"
|
||||||
|
|
||||||
|
@ -557,14 +563,28 @@ class EditorWindow(object):
|
||||||
idleConf.GetOption('main','EditorWindow','font-size'),
|
idleConf.GetOption('main','EditorWindow','font-size'),
|
||||||
fontWeight))
|
fontWeight))
|
||||||
|
|
||||||
def ResetKeybindings(self):
|
def RemoveKeybindings(self):
|
||||||
"Update the keybindings if they are changed"
|
"Remove the keybindings before they are changed."
|
||||||
# Called from configDialog.py
|
# Called from configDialog.py
|
||||||
self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
|
self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
|
||||||
keydefs = self.Bindings.default_keydefs
|
keydefs = self.Bindings.default_keydefs
|
||||||
for event, keylist in keydefs.items():
|
for event, keylist in keydefs.items():
|
||||||
self.text.event_delete(event)
|
self.text.event_delete(event, *keylist)
|
||||||
|
for extensionName in self.get_standard_extension_names():
|
||||||
|
keydefs = idleConf.GetExtensionBindings(extensionName)
|
||||||
|
if keydefs:
|
||||||
|
for event, keylist in keydefs.items():
|
||||||
|
self.text.event_delete(event, *keylist)
|
||||||
|
|
||||||
|
def ApplyKeybindings(self):
|
||||||
|
"Update the keybindings after they are changed"
|
||||||
|
# Called from configDialog.py
|
||||||
|
self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
|
||||||
self.apply_bindings()
|
self.apply_bindings()
|
||||||
|
for extensionName in self.get_standard_extension_names():
|
||||||
|
keydefs = idleConf.GetExtensionBindings(extensionName)
|
||||||
|
if keydefs:
|
||||||
|
self.apply_bindings(keydefs)
|
||||||
#update menu accelerators
|
#update menu accelerators
|
||||||
menuEventDict={}
|
menuEventDict={}
|
||||||
for menu in self.Bindings.menudefs:
|
for menu in self.Bindings.menudefs:
|
||||||
|
@ -1064,9 +1084,10 @@ class EditorWindow(object):
|
||||||
# open/close first need to find the last stmt
|
# open/close first need to find the last stmt
|
||||||
lno = index2line(text.index('insert'))
|
lno = index2line(text.index('insert'))
|
||||||
y = PyParse.Parser(self.indentwidth, self.tabwidth)
|
y = PyParse.Parser(self.indentwidth, self.tabwidth)
|
||||||
|
if not self.context_use_ps1:
|
||||||
for context in self.num_context_lines:
|
for context in self.num_context_lines:
|
||||||
startat = max(lno - context, 1)
|
startat = max(lno - context, 1)
|
||||||
startatindex = repr(startat) + ".0"
|
startatindex = `startat` + ".0"
|
||||||
rawtext = text.get(startatindex, "insert")
|
rawtext = text.get(startatindex, "insert")
|
||||||
y.set_str(rawtext)
|
y.set_str(rawtext)
|
||||||
bod = y.find_good_parse_start(
|
bod = y.find_good_parse_start(
|
||||||
|
@ -1075,6 +1096,16 @@ class EditorWindow(object):
|
||||||
if bod is not None or startat == 1:
|
if bod is not None or startat == 1:
|
||||||
break
|
break
|
||||||
y.set_lo(bod or 0)
|
y.set_lo(bod or 0)
|
||||||
|
else:
|
||||||
|
r = text.tag_prevrange("console", "insert")
|
||||||
|
if r:
|
||||||
|
startatindex = r[1]
|
||||||
|
else:
|
||||||
|
startatindex = "1.0"
|
||||||
|
rawtext = text.get(startatindex, "insert")
|
||||||
|
y.set_str(rawtext)
|
||||||
|
y.set_lo(0)
|
||||||
|
|
||||||
c = y.get_continuation_type()
|
c = y.get_continuation_type()
|
||||||
if c != PyParse.C_NONE:
|
if c != PyParse.C_NONE:
|
||||||
# The current stmt hasn't ended yet.
|
# The current stmt hasn't ended yet.
|
||||||
|
|
241
Lib/idlelib/HyperParser.py
Normal file
241
Lib/idlelib/HyperParser.py
Normal file
|
@ -0,0 +1,241 @@
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
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 = `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
|
||||||
|
break
|
||||||
|
|
||||||
|
else:
|
||||||
|
# We've found an operator or something.
|
||||||
|
break
|
||||||
|
|
||||||
|
return rawtext[last_identifier_pos:self.indexinrawtext]
|
404
Lib/idlelib/MultiCall.py
Normal file
404
Lib/idlelib/MultiCall.py
Normal file
|
@ -0,0 +1,404 @@
|
||||||
|
"""
|
||||||
|
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 os
|
||||||
|
import string
|
||||||
|
import re
|
||||||
|
import Tkinter
|
||||||
|
|
||||||
|
# the event type constants, which define the meaning of mc_type
|
||||||
|
MC_KEYPRESS=0; MC_KEYRELEASE=1; MC_BUTTONPRESS=2; MC_BUTTONRELEASE=3;
|
||||||
|
MC_ACTIVATE=4; MC_CIRCULATE=5; MC_COLORMAP=6; MC_CONFIGURE=7;
|
||||||
|
MC_DEACTIVATE=8; MC_DESTROY=9; MC_ENTER=10; MC_EXPOSE=11; MC_FOCUSIN=12;
|
||||||
|
MC_FOCUSOUT=13; MC_GRAVITY=14; MC_LEAVE=15; MC_MAP=16; MC_MOTION=17;
|
||||||
|
MC_MOUSEWHEEL=18; MC_PROPERTY=19; MC_REPARENT=20; MC_UNMAP=21; MC_VISIBILITY=22;
|
||||||
|
# the modifier state constants, which define the meaning of mc_state
|
||||||
|
MC_SHIFT = 1<<0; MC_CONTROL = 1<<2; MC_ALT = 1<<3; MC_META = 1<<5
|
||||||
|
MC_OPTION = 1<<6; MC_COMMAND = 1<<7
|
||||||
|
|
||||||
|
# define the list of modifiers, to be used in complex event types.
|
||||||
|
if sys.platform == "darwin" and sys.executable.count(".app"):
|
||||||
|
_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 = [reduce(lambda x, y: x + y,
|
||||||
|
[_modifiers[i][0]+'-' for i in range(len(_modifiers))
|
||||||
|
if (1 << i) & s],
|
||||||
|
"")
|
||||||
|
for s in _states]
|
||||||
|
_state_subsets = map(lambda i: filter(lambda j: not (j & (~i)), _states),
|
||||||
|
_states)
|
||||||
|
for l in _state_subsets:
|
||||||
|
l.sort(lambda a, b, nummod = lambda x: len(filter(lambda i: (1<<i) & x,
|
||||||
|
range(len(_modifiers)))):
|
||||||
|
nummod(b) - nummod(a))
|
||||||
|
# _state_codes gives for each state, the portable code to be passed as mc_state
|
||||||
|
_state_codes = [reduce(lambda x, y: x | y,
|
||||||
|
[_modifier_masks[i] for i in range(len(_modifiers))
|
||||||
|
if (1 << i) & s],
|
||||||
|
0)
|
||||||
|
for s in _states]
|
||||||
|
|
||||||
|
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
|
||||||
|
while doafterhandler:
|
||||||
|
doafterhandler.pop()()
|
||||||
|
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 not self.bindedfuncs.has_key(triplet[2]):
|
||||||
|
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):
|
||||||
|
apply(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):
|
||||||
|
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()
|
|
@ -3,17 +3,14 @@
|
||||||
When you hit a right paren, the cursor should move briefly to the left
|
When you hit a right paren, the cursor should move briefly to the left
|
||||||
paren. Paren here is used generically; the matching applies to
|
paren. Paren here is used generically; the matching applies to
|
||||||
parentheses, square brackets, and curly braces.
|
parentheses, square brackets, and curly braces.
|
||||||
|
|
||||||
WARNING: This extension will fight with the CallTips extension,
|
|
||||||
because they both are interested in the KeyRelease-parenright event.
|
|
||||||
We'll have to fix IDLE to do something reasonable when two or more
|
|
||||||
extensions what to capture the same event.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import PyParse
|
from HyperParser import HyperParser
|
||||||
from EditorWindow import EditorWindow, index2line
|
|
||||||
from configHandler import idleConf
|
from configHandler import idleConf
|
||||||
|
|
||||||
|
keysym_opener = {"parenright":'(', "bracketright":'[', "braceright":'{'}
|
||||||
|
CHECK_DELAY = 100 # miliseconds
|
||||||
|
|
||||||
class ParenMatch:
|
class ParenMatch:
|
||||||
"""Highlight matching parentheses
|
"""Highlight matching parentheses
|
||||||
|
|
||||||
|
@ -31,7 +28,6 @@ class ParenMatch:
|
||||||
expression from the left paren to the right paren.
|
expression from the left paren to the right paren.
|
||||||
|
|
||||||
TODO:
|
TODO:
|
||||||
- fix interaction with CallTips
|
|
||||||
- extend IDLE with configuration dialog to change options
|
- extend IDLE with configuration dialog to change options
|
||||||
- implement rest of Emacs highlight styles (see below)
|
- implement rest of Emacs highlight styles (see below)
|
||||||
- print mismatch warning in IDLE status window
|
- print mismatch warning in IDLE status window
|
||||||
|
@ -41,7 +37,11 @@ class ParenMatch:
|
||||||
to the right of a right paren. I don't know how to do that in Tk,
|
to the right of a right paren. I don't know how to do that in Tk,
|
||||||
so I haven't bothered.
|
so I haven't bothered.
|
||||||
"""
|
"""
|
||||||
menudefs = []
|
menudefs = [
|
||||||
|
('edit', [
|
||||||
|
("Show surrounding parens", "<<flash-paren>>"),
|
||||||
|
])
|
||||||
|
]
|
||||||
STYLE = idleConf.GetOption('extensions','ParenMatch','style',
|
STYLE = idleConf.GetOption('extensions','ParenMatch','style',
|
||||||
default='expression')
|
default='expression')
|
||||||
FLASH_DELAY = idleConf.GetOption('extensions','ParenMatch','flash-delay',
|
FLASH_DELAY = idleConf.GetOption('extensions','ParenMatch','flash-delay',
|
||||||
|
@ -50,14 +50,36 @@ class ParenMatch:
|
||||||
BELL = idleConf.GetOption('extensions','ParenMatch','bell',
|
BELL = idleConf.GetOption('extensions','ParenMatch','bell',
|
||||||
type='bool',default=1)
|
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):
|
def __init__(self, editwin):
|
||||||
self.editwin = editwin
|
self.editwin = editwin
|
||||||
self.text = editwin.text
|
self.text = editwin.text
|
||||||
self.finder = LastOpenBracketFinder(editwin)
|
# 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.counter = 0
|
||||||
self._restore = None
|
self.is_restore_active = 0
|
||||||
self.set_style(self.STYLE)
|
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):
|
def set_style(self, style):
|
||||||
self.STYLE = style
|
self.STYLE = style
|
||||||
if style == "default":
|
if style == "default":
|
||||||
|
@ -67,23 +89,38 @@ class ParenMatch:
|
||||||
self.create_tag = self.create_tag_expression
|
self.create_tag = self.create_tag_expression
|
||||||
self.set_timeout = self.set_timeout_none
|
self.set_timeout = self.set_timeout_none
|
||||||
|
|
||||||
def flash_open_paren_event(self, event):
|
def flash_paren_event(self, event):
|
||||||
index = self.finder.find(keysym_type(event.keysym))
|
indices = HyperParser(self.editwin, "insert").get_surrounding_brackets()
|
||||||
if index is None:
|
if indices is None:
|
||||||
self.warn_mismatched()
|
self.warn_mismatched()
|
||||||
return
|
return
|
||||||
self._restore = 1
|
self.activate_restore()
|
||||||
self.create_tag(index)
|
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.
|
||||||
|
if self.text.get("insert-1c") not in (')',']','}'):
|
||||||
|
return
|
||||||
|
hp = HyperParser(self.editwin, "insert-1c")
|
||||||
|
if not hp.is_in_code():
|
||||||
|
return
|
||||||
|
indices = hp.get_surrounding_brackets(keysym_opener[event.keysym], True)
|
||||||
|
if indices is None:
|
||||||
|
self.warn_mismatched()
|
||||||
|
return
|
||||||
|
self.activate_restore()
|
||||||
|
self.create_tag(indices)
|
||||||
self.set_timeout()
|
self.set_timeout()
|
||||||
|
|
||||||
def check_restore_event(self, event=None):
|
def restore_event(self, event=None):
|
||||||
if self._restore:
|
|
||||||
self.text.tag_delete("paren")
|
self.text.tag_delete("paren")
|
||||||
self._restore = None
|
self.deactivate_restore()
|
||||||
|
self.counter += 1 # disable the last timer, if there is one.
|
||||||
|
|
||||||
def handle_restore_timer(self, timer_count):
|
def handle_restore_timer(self, timer_count):
|
||||||
if timer_count + 1 == self.counter:
|
if timer_count == self.counter:
|
||||||
self.check_restore_event()
|
self.restore_event()
|
||||||
|
|
||||||
def warn_mismatched(self):
|
def warn_mismatched(self):
|
||||||
if self.BELL:
|
if self.BELL:
|
||||||
|
@ -92,87 +129,43 @@ class ParenMatch:
|
||||||
# any one of the create_tag_XXX methods can be used depending on
|
# any one of the create_tag_XXX methods can be used depending on
|
||||||
# the style
|
# the style
|
||||||
|
|
||||||
def create_tag_default(self, index):
|
def create_tag_default(self, indices):
|
||||||
"""Highlight the single paren that matches"""
|
"""Highlight the single paren that matches"""
|
||||||
self.text.tag_add("paren", index)
|
self.text.tag_add("paren", indices[0])
|
||||||
self.text.tag_config("paren", self.HILITE_CONFIG)
|
self.text.tag_config("paren", self.HILITE_CONFIG)
|
||||||
|
|
||||||
def create_tag_expression(self, index):
|
def create_tag_expression(self, indices):
|
||||||
"""Highlight the entire expression"""
|
"""Highlight the entire expression"""
|
||||||
self.text.tag_add("paren", index, "insert")
|
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)
|
self.text.tag_config("paren", self.HILITE_CONFIG)
|
||||||
|
|
||||||
# any one of the set_timeout_XXX methods can be used depending on
|
# any one of the set_timeout_XXX methods can be used depending on
|
||||||
# the style
|
# the style
|
||||||
|
|
||||||
def set_timeout_none(self):
|
def set_timeout_none(self):
|
||||||
"""Highlight will remain until user input turns it off"""
|
"""Highlight will remain until user input turns it off
|
||||||
pass
|
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):
|
def set_timeout_last(self):
|
||||||
"""The last highlight created will be removed after .5 sec"""
|
"""The last highlight created will be removed after .5 sec"""
|
||||||
# associate a counter with an event; only disable the "paren"
|
# associate a counter with an event; only disable the "paren"
|
||||||
# tag if the event is for the most recent timer.
|
# tag if the event is for the most recent timer.
|
||||||
|
self.counter += 1
|
||||||
self.editwin.text_frame.after(self.FLASH_DELAY,
|
self.editwin.text_frame.after(self.FLASH_DELAY,
|
||||||
lambda self=self, c=self.counter: \
|
lambda self=self, c=self.counter: \
|
||||||
self.handle_restore_timer(c))
|
self.handle_restore_timer(c))
|
||||||
self.counter = self.counter + 1
|
|
||||||
|
|
||||||
def keysym_type(ks):
|
|
||||||
# Not all possible chars or keysyms are checked because of the
|
|
||||||
# limited context in which the function is used.
|
|
||||||
if ks == "parenright" or ks == "(":
|
|
||||||
return "paren"
|
|
||||||
if ks == "bracketright" or ks == "[":
|
|
||||||
return "bracket"
|
|
||||||
if ks == "braceright" or ks == "{":
|
|
||||||
return "brace"
|
|
||||||
|
|
||||||
class LastOpenBracketFinder:
|
|
||||||
num_context_lines = EditorWindow.num_context_lines
|
|
||||||
indentwidth = EditorWindow.indentwidth
|
|
||||||
tabwidth = EditorWindow.tabwidth
|
|
||||||
context_use_ps1 = EditorWindow.context_use_ps1
|
|
||||||
|
|
||||||
def __init__(self, editwin):
|
|
||||||
self.editwin = editwin
|
|
||||||
self.text = editwin.text
|
|
||||||
|
|
||||||
def _find_offset_in_buf(self, lno):
|
|
||||||
y = PyParse.Parser(self.indentwidth, self.tabwidth)
|
|
||||||
for context in self.num_context_lines:
|
|
||||||
startat = max(lno - context, 1)
|
|
||||||
startatindex = repr(startat) + ".0"
|
|
||||||
# rawtext needs to contain everything up to the last
|
|
||||||
# character, which was the close paren. the parser also
|
|
||||||
# requires that the last line ends with "\n"
|
|
||||||
rawtext = self.text.get(startatindex, "insert")[:-1] + "\n"
|
|
||||||
y.set_str(rawtext)
|
|
||||||
bod = y.find_good_parse_start(
|
|
||||||
self.context_use_ps1,
|
|
||||||
self._build_char_in_string_func(startatindex))
|
|
||||||
if bod is not None or startat == 1:
|
|
||||||
break
|
|
||||||
y.set_lo(bod or 0)
|
|
||||||
i = y.get_last_open_bracket_pos()
|
|
||||||
return i, y.str
|
|
||||||
|
|
||||||
def find(self, right_keysym_type):
|
|
||||||
"""Return the location of the last open paren"""
|
|
||||||
lno = index2line(self.text.index("insert"))
|
|
||||||
i, buf = self._find_offset_in_buf(lno)
|
|
||||||
if i is None \
|
|
||||||
or keysym_type(buf[i]) != right_keysym_type:
|
|
||||||
return None
|
|
||||||
lines_back = buf[i:].count("\n") - 1
|
|
||||||
# subtract one for the "\n" added to please the parser
|
|
||||||
upto_open = buf[:i]
|
|
||||||
j = upto_open.rfind("\n") + 1 # offset of column 0 of line
|
|
||||||
offset = i - j
|
|
||||||
return "%d.%d" % (lno - lines_back, offset)
|
|
||||||
|
|
||||||
def _build_char_in_string_func(self, startindex):
|
|
||||||
def inner(offset, startindex=startindex,
|
|
||||||
icis=self.editwin.is_char_in_string):
|
|
||||||
return icis(startindex + "%dc" % offset)
|
|
||||||
return inner
|
|
||||||
|
|
|
@ -14,9 +14,7 @@ if 0: # for throwaway debugging output
|
||||||
_synchre = re.compile(r"""
|
_synchre = re.compile(r"""
|
||||||
^
|
^
|
||||||
[ \t]*
|
[ \t]*
|
||||||
(?: if
|
(?: while
|
||||||
| for
|
|
||||||
| while
|
|
||||||
| else
|
| else
|
||||||
| def
|
| def
|
||||||
| return
|
| return
|
||||||
|
@ -145,29 +143,11 @@ class Parser:
|
||||||
# This will be reliable iff given a reliable is_char_in_string
|
# This will be reliable iff given a reliable is_char_in_string
|
||||||
# function, meaning that when it says "no", it's absolutely
|
# function, meaning that when it says "no", it's absolutely
|
||||||
# guaranteed that the char is not in a string.
|
# guaranteed that the char is not in a string.
|
||||||
#
|
|
||||||
# Ack, hack: in the shell window this kills us, because there's
|
|
||||||
# no way to tell the differences between output, >>> etc and
|
|
||||||
# user input. Indeed, IDLE's first output line makes the rest
|
|
||||||
# look like it's in an unclosed paren!:
|
|
||||||
# Python 1.5.2 (#0, Apr 13 1999, ...
|
|
||||||
|
|
||||||
def find_good_parse_start(self, use_ps1, is_char_in_string=None,
|
def find_good_parse_start(self, is_char_in_string=None,
|
||||||
_synchre=_synchre):
|
_synchre=_synchre):
|
||||||
str, pos = self.str, None
|
str, pos = self.str, None
|
||||||
if use_ps1:
|
|
||||||
# shell window
|
|
||||||
ps1 = '\n' + sys.ps1
|
|
||||||
i = str.rfind(ps1)
|
|
||||||
if i >= 0:
|
|
||||||
pos = i + len(ps1)
|
|
||||||
# make it look like there's a newline instead
|
|
||||||
# of ps1 at the start -- hacking here once avoids
|
|
||||||
# repeated hackery later
|
|
||||||
self.str = str[:pos-1] + '\n' + str[pos:]
|
|
||||||
return pos
|
|
||||||
|
|
||||||
# File window -- real work.
|
|
||||||
if not is_char_in_string:
|
if not is_char_in_string:
|
||||||
# no clue -- make the caller pass everything
|
# no clue -- make the caller pass everything
|
||||||
return None
|
return None
|
||||||
|
@ -363,6 +343,11 @@ class Parser:
|
||||||
# Creates:
|
# Creates:
|
||||||
# self.stmt_start, stmt_end
|
# self.stmt_start, stmt_end
|
||||||
# slice indices of last interesting stmt
|
# slice indices of last interesting stmt
|
||||||
|
# self.stmt_bracketing
|
||||||
|
# the bracketing structure of the last interesting stmt;
|
||||||
|
# for example, for the statement "say(boo) or die", stmt_bracketing
|
||||||
|
# will be [(0, 0), (3, 1), (8, 0)]. Strings and comments are
|
||||||
|
# treated as brackets, for the matter.
|
||||||
# self.lastch
|
# self.lastch
|
||||||
# last non-whitespace character before optional trailing
|
# last non-whitespace character before optional trailing
|
||||||
# comment
|
# comment
|
||||||
|
@ -404,6 +389,7 @@ class Parser:
|
||||||
lastch = ""
|
lastch = ""
|
||||||
stack = [] # stack of open bracket indices
|
stack = [] # stack of open bracket indices
|
||||||
push_stack = stack.append
|
push_stack = stack.append
|
||||||
|
bracketing = [(p, 0)]
|
||||||
while p < q:
|
while p < q:
|
||||||
# suck up all except ()[]{}'"#\\
|
# suck up all except ()[]{}'"#\\
|
||||||
m = _chew_ordinaryre(str, p, q)
|
m = _chew_ordinaryre(str, p, q)
|
||||||
|
@ -424,6 +410,7 @@ class Parser:
|
||||||
|
|
||||||
if ch in "([{":
|
if ch in "([{":
|
||||||
push_stack(p)
|
push_stack(p)
|
||||||
|
bracketing.append((p, len(stack)))
|
||||||
lastch = ch
|
lastch = ch
|
||||||
p = p+1
|
p = p+1
|
||||||
continue
|
continue
|
||||||
|
@ -433,6 +420,7 @@ class Parser:
|
||||||
del stack[-1]
|
del stack[-1]
|
||||||
lastch = ch
|
lastch = ch
|
||||||
p = p+1
|
p = p+1
|
||||||
|
bracketing.append((p, len(stack)))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if ch == '"' or ch == "'":
|
if ch == '"' or ch == "'":
|
||||||
|
@ -443,14 +431,18 @@ class Parser:
|
||||||
# strings to a couple of characters per line. study1
|
# strings to a couple of characters per line. study1
|
||||||
# also needed to keep track of newlines, and we don't
|
# also needed to keep track of newlines, and we don't
|
||||||
# have to.
|
# have to.
|
||||||
|
bracketing.append((p, len(stack)+1))
|
||||||
lastch = ch
|
lastch = ch
|
||||||
p = _match_stringre(str, p, q).end()
|
p = _match_stringre(str, p, q).end()
|
||||||
|
bracketing.append((p, len(stack)))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if ch == '#':
|
if ch == '#':
|
||||||
# consume comment and trailing newline
|
# consume comment and trailing newline
|
||||||
|
bracketing.append((p, len(stack)+1))
|
||||||
p = str.find('\n', p, q) + 1
|
p = str.find('\n', p, q) + 1
|
||||||
assert p > 0
|
assert p > 0
|
||||||
|
bracketing.append((p, len(stack)))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
assert ch == '\\'
|
assert ch == '\\'
|
||||||
|
@ -466,6 +458,7 @@ class Parser:
|
||||||
self.lastch = lastch
|
self.lastch = lastch
|
||||||
if stack:
|
if stack:
|
||||||
self.lastopenbracketpos = stack[-1]
|
self.lastopenbracketpos = stack[-1]
|
||||||
|
self.stmt_bracketing = tuple(bracketing)
|
||||||
|
|
||||||
# Assuming continuation is C_BRACKET, return the number
|
# Assuming continuation is C_BRACKET, return the number
|
||||||
# of spaces the next line should be indented.
|
# of spaces the next line should be indented.
|
||||||
|
@ -590,3 +583,12 @@ class Parser:
|
||||||
def get_last_open_bracket_pos(self):
|
def get_last_open_bracket_pos(self):
|
||||||
self._study2()
|
self._study2()
|
||||||
return self.lastopenbracketpos
|
return self.lastopenbracketpos
|
||||||
|
|
||||||
|
# the structure of the bracketing of the last interesting statement,
|
||||||
|
# in the format defined in _study2, or None if the text didn't contain
|
||||||
|
# anything
|
||||||
|
stmt_bracketing = None
|
||||||
|
|
||||||
|
def get_last_stmt_bracketing(self):
|
||||||
|
self._study2()
|
||||||
|
return self.stmt_bracketing
|
||||||
|
|
|
@ -1091,11 +1091,12 @@ class PyShell(OutputWindow):
|
||||||
self.recall(self.text.get(next[0], next[1]), event)
|
self.recall(self.text.get(next[0], next[1]), event)
|
||||||
return "break"
|
return "break"
|
||||||
# No stdin mark -- just get the current line, less any prompt
|
# No stdin mark -- just get the current line, less any prompt
|
||||||
line = self.text.get("insert linestart", "insert lineend")
|
indices = self.text.tag_nextrange("console", "insert linestart")
|
||||||
last_line_of_prompt = sys.ps1.split('\n')[-1]
|
if indices and \
|
||||||
if line.startswith(last_line_of_prompt):
|
self.text.compare(indices[0], "<=", "insert linestart"):
|
||||||
line = line[len(last_line_of_prompt):]
|
self.recall(self.text.get(indices[1], "insert lineend"), event)
|
||||||
self.recall(line, event)
|
else:
|
||||||
|
self.recall(self.text.get("insert linestart", "insert lineend"), event)
|
||||||
return "break"
|
return "break"
|
||||||
# If we're between the beginning of the line and the iomark, i.e.
|
# If we're between the beginning of the line and the iomark, i.e.
|
||||||
# in the prompt area, move to the end of the prompt
|
# in the prompt area, move to the end of the prompt
|
||||||
|
|
|
@ -52,22 +52,30 @@ check-module=<Alt-Key-x>
|
||||||
|
|
||||||
[CallTips]
|
[CallTips]
|
||||||
enable=1
|
enable=1
|
||||||
|
[CallTips_cfgBindings]
|
||||||
|
force-open-calltip=<Control-Key-backslash>
|
||||||
[CallTips_bindings]
|
[CallTips_bindings]
|
||||||
paren-open=<Key-parenleft>
|
try-open-calltip=<KeyRelease-parenleft>
|
||||||
paren-close=<Key-parenright>
|
refresh-calltip=<KeyRelease-parenright> <KeyRelease-0>
|
||||||
check-calltip-cancel=<KeyRelease>
|
|
||||||
calltip-cancel=<ButtonPress> <Key-Escape>
|
|
||||||
|
|
||||||
[ParenMatch]
|
[ParenMatch]
|
||||||
enable=0
|
enable=1
|
||||||
style= expression
|
style= expression
|
||||||
flash-delay= 500
|
flash-delay= 500
|
||||||
bell= 1
|
bell= 1
|
||||||
hilite-foreground= black
|
[ParenMatch_cfgBindings]
|
||||||
hilite-background= #43cd80
|
flash-paren=<Control-Key-0>
|
||||||
[ParenMatch_bindings]
|
[ParenMatch_bindings]
|
||||||
flash-open-paren=<KeyRelease-parenright> <KeyRelease-bracketright> <KeyRelease-braceright>
|
paren-closed=<KeyRelease-parenright> <KeyRelease-bracketright> <KeyRelease-braceright>
|
||||||
check-restore=<KeyPress>
|
|
||||||
|
[AutoComplete]
|
||||||
|
enable=1
|
||||||
|
popupwait=0
|
||||||
|
[AutoComplete_cfgBindings]
|
||||||
|
force-open-completions=<Control-Key-space>
|
||||||
|
[AutoComplete_bindings]
|
||||||
|
autocomplete=<Key-Tab>
|
||||||
|
try-open-completions=<KeyRelease-period> <KeyRelease-slash> <KeyRelease-backslash>
|
||||||
|
|
||||||
[CodeContext]
|
[CodeContext]
|
||||||
enable=1
|
enable=1
|
||||||
|
|
|
@ -1106,6 +1106,13 @@ class ConfigDialog(Toplevel):
|
||||||
idleConf.userCfg[configType].Save()
|
idleConf.userCfg[configType].Save()
|
||||||
self.ResetChangedItems() #clear the changed items dict
|
self.ResetChangedItems() #clear the changed items dict
|
||||||
|
|
||||||
|
def DeactivateCurrentConfig(self):
|
||||||
|
#Before a config is saved, some cleanup of current
|
||||||
|
#config must be done - remove the previous keybindings
|
||||||
|
winInstances=self.parent.instance_dict.keys()
|
||||||
|
for instance in winInstances:
|
||||||
|
instance.RemoveKeybindings()
|
||||||
|
|
||||||
def ActivateConfigChanges(self):
|
def ActivateConfigChanges(self):
|
||||||
"Dynamically apply configuration changes"
|
"Dynamically apply configuration changes"
|
||||||
winInstances=self.parent.instance_dict.keys()
|
winInstances=self.parent.instance_dict.keys()
|
||||||
|
@ -1113,7 +1120,7 @@ class ConfigDialog(Toplevel):
|
||||||
instance.ResetColorizer()
|
instance.ResetColorizer()
|
||||||
instance.ResetFont()
|
instance.ResetFont()
|
||||||
instance.set_notabs_indentwidth()
|
instance.set_notabs_indentwidth()
|
||||||
instance.ResetKeybindings()
|
instance.ApplyKeybindings()
|
||||||
instance.reset_help_menu_entries()
|
instance.reset_help_menu_entries()
|
||||||
|
|
||||||
def Cancel(self):
|
def Cancel(self):
|
||||||
|
@ -1124,6 +1131,7 @@ class ConfigDialog(Toplevel):
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def Apply(self):
|
def Apply(self):
|
||||||
|
self.DeactivateCurrentConfig()
|
||||||
self.SaveAllChangedConfigs()
|
self.SaveAllChangedConfigs()
|
||||||
self.ActivateConfigChanges()
|
self.ActivateConfigChanges()
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@ import threading
|
||||||
import Queue
|
import Queue
|
||||||
|
|
||||||
import CallTips
|
import CallTips
|
||||||
|
import AutoComplete
|
||||||
|
|
||||||
import RemoteDebugger
|
import RemoteDebugger
|
||||||
import RemoteObjectBrowser
|
import RemoteObjectBrowser
|
||||||
import StackViewer
|
import StackViewer
|
||||||
|
@ -275,6 +277,7 @@ class Executive(object):
|
||||||
self.rpchandler = rpchandler
|
self.rpchandler = rpchandler
|
||||||
self.locals = __main__.__dict__
|
self.locals = __main__.__dict__
|
||||||
self.calltip = CallTips.CallTips()
|
self.calltip = CallTips.CallTips()
|
||||||
|
self.autocomplete = AutoComplete.AutoComplete()
|
||||||
|
|
||||||
def runcode(self, code):
|
def runcode(self, code):
|
||||||
try:
|
try:
|
||||||
|
@ -305,6 +308,9 @@ class Executive(object):
|
||||||
def get_the_calltip(self, name):
|
def get_the_calltip(self, name):
|
||||||
return self.calltip.fetch_tip(name)
|
return self.calltip.fetch_tip(name)
|
||||||
|
|
||||||
|
def get_the_completion_list(self, what, mode):
|
||||||
|
return self.autocomplete.fetch_completions(what, mode)
|
||||||
|
|
||||||
def stackviewer(self, flist_oid=None):
|
def stackviewer(self, flist_oid=None):
|
||||||
if self.usr_exc_info:
|
if self.usr_exc_info:
|
||||||
typ, val, tb = self.usr_exc_info
|
typ, val, tb = self.usr_exc_info
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue