Kaydet (Commit) b1754455 authored tarafından Kurt B. Kaiser's avatar Kurt B. Kaiser

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
üst c85c74cd
"""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)
This diff is collapsed.
......@@ -6,33 +6,65 @@ Used by the CallTips IDLE extension.
"""
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:
def __init__(self, widget):
self.widget = widget
self.tipwindow = None
self.id = None
self.x = self.y = 0
self.tipwindow = self.label = None
self.parenline = self.parencol = None
self.lastline = None
self.hideid = self.checkhideid = None
def position_window(self):
"""Check if needs to reposition the window, and if so - do it."""
curline = int(self.widget.index("insert").split('.')[0])
if curline == self.lastline:
return
self.lastline = curline
self.widget.see("insert")
if curline == self.parenline:
box = self.widget.bbox("%d.%d" % (self.parenline,
self.parencol))
else:
box = self.widget.bbox("%d.0" % curline)
if not box:
box = list(self.widget.bbox("insert"))
# align to left of window
box[0] = 0
box[2] = 0
x = box[0] + self.widget.winfo_rootx() + 2
y = box[1] + box[3] + self.widget.winfo_rooty()
self.tipwindow.wm_geometry("+%d+%d" % (x, y))
def showtip(self, text):
" Display text in calltip window"
def showtip(self, text, parenleft, parenright):
"""Show the calltip, bind events which will close it and reposition it.
"""
# truncate overly long calltip
if len(text) >= 79:
text = text[:75] + ' ...'
self.text = text
if self.tipwindow or not self.text:
return
self.widget.see("insert")
x, y, cx, cy = self.widget.bbox("insert")
x = x + self.widget.winfo_rootx() + 2
y = y + cy + self.widget.winfo_rooty()
self.widget.mark_set(MARK_RIGHT, parenright)
self.parenline, self.parencol = map(
int, self.widget.index(parenleft).split("."))
self.tipwindow = tw = Toplevel(self.widget)
self.position_window()
# XXX 12 Dec 2002 KBK The following command has two effects: It removes
# the calltip window border (good) but also causes (at least on
# Linux) the calltip to show as a top level window, burning through
# any other window dragged over it. Also, shows on all viewports!
tw.wm_overrideredirect(1)
tw.wm_geometry("+%d+%d" % (x, y))
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
......@@ -41,16 +73,66 @@ class CallTip:
"help", "noActivates")
except TclError:
pass
label = Label(tw, text=self.text, justify=LEFT,
background="#ffffe0", relief=SOLID, borderwidth=1,
font = self.widget['font'])
label.pack()
self.label = Label(tw, text=self.text, justify=LEFT,
background="#ffffe0", relief=SOLID, borderwidth=1,
font = self.widget['font'])
self.label.pack()
self.checkhideid = self.widget.bind(CHECKHIDE_VIRTUAL_EVENT_NAME,
self.checkhide_event)
for seq in CHECKHIDE_SEQUENCES:
self.widget.event_add(CHECKHIDE_VIRTUAL_EVENT_NAME, seq)
self.widget.after(CHECKHIDE_TIME, self.checkhide_event)
self.hideid = self.widget.bind(HIDE_VIRTUAL_EVENT_NAME,
self.hide_event)
for seq in HIDE_SEQUENCES:
self.widget.event_add(HIDE_VIRTUAL_EVENT_NAME, seq)
def checkhide_event(self, event=None):
if not self.tipwindow:
# If the event was triggered by the same event that unbinded
# this function, the function will be called nevertheless,
# so do nothing in this case.
return
curline, curcol = map(int, self.widget.index("insert").split('.'))
if curline < self.parenline or \
(curline == self.parenline and curcol <= self.parencol) or \
self.widget.compare("insert", ">", MARK_RIGHT):
self.hidetip()
else:
self.position_window()
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):
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
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
parameter and docstring information when you type an opening parenthesis, and
which disappear when you type a closing parenthesis.
Future plans include extending the functionality to include class attributes.
"""
import sys
import string
import types
import CallTipWindow
from HyperParser import HyperParser
import __main__
class CallTips:
menudefs = [
('edit', [
("Show call tip", "<<force-open-calltip>>"),
])
]
def __init__(self, editwin=None):
......@@ -36,51 +36,47 @@ class CallTips:
# See __init__ for usage
return CallTipWindow.CallTip(self.text)
def _remove_calltip_window(self):
def _remove_calltip_window(self, event=None):
if self.calltip:
self.calltip.hidetip()
self.calltip = None
def paren_open_event(self, event):
self._remove_calltip_window()
name = self.get_name_at_cursor()
arg_text = self.fetch_tip(name)
if arg_text:
self.calltip_start = self.text.index("insert")
self.calltip = self._make_calltip_window()
self.calltip.showtip(arg_text)
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 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 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.
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)
__IDCHARS = "._" + string.ascii_letters + string.digits
def open_calltip(self, evalfuncs):
self._remove_calltip_window()
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:]
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)
if not arg_text:
return
self.calltip = self._make_calltip_window()
self.calltip.showtip(arg_text, sur_paren[0], sur_paren[1])
def fetch_tip(self, name):
"""Return the argument list and docstring of a function or class
......@@ -127,7 +123,7 @@ def _find_constructor(class_ob):
return None
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 = ""
if ob is not None:
argOffset = 0
......@@ -150,7 +146,7 @@ def get_arg_text(ob):
try:
realArgs = fob.func_code.co_varnames[argOffset:fob.func_code.co_argcount]
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
items = map(lambda arg, dflt: arg+dflt, realArgs, defaults)
if fob.func_code.co_flags & 0x4:
......
......@@ -6,6 +6,7 @@ from itertools import count
from Tkinter import *
import tkSimpleDialog
import tkMessageBox
from MultiCall import MultiCallCreator
import webbrowser
import idlever
......@@ -89,7 +90,8 @@ class EditorWindow(object):
self.vbar = vbar = Scrollbar(top, name='vbar')
self.text_frame = text_frame = Frame(top)
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,
'normal',fgBg='fg'),
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('line', 'Ln: ?', side=RIGHT)
self.status_bar.pack(side=BOTTOM, fill=X)
self.text.bind('<KeyRelease>', self.set_line_and_column)
self.text.bind('<ButtonRelease>', self.set_line_and_column)
self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
self.text.event_add("<<set-line-and-column>>",
"<KeyRelease>", "<ButtonRelease>")
self.text.after_idle(self.set_line_and_column)
def set_line_and_column(self, event=None):
......@@ -355,6 +358,9 @@ class EditorWindow(object):
return "break"
def copy(self,event):
if not self.text.tag_ranges("sel"):
# There is no selection, so do nothing and maybe interrupt.
return
self.text.event_generate("<<Copy>>")
return "break"
......@@ -557,14 +563,28 @@ class EditorWindow(object):
idleConf.GetOption('main','EditorWindow','font-size'),
fontWeight))
def ResetKeybindings(self):
"Update the keybindings if they are changed"
def RemoveKeybindings(self):
"Remove the keybindings before they are changed."
# Called from configDialog.py
self.Bindings.default_keydefs=idleConf.GetCurrentKeySet()
keydefs = self.Bindings.default_keydefs
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()
for extensionName in self.get_standard_extension_names():
keydefs = idleConf.GetExtensionBindings(extensionName)
if keydefs:
self.apply_bindings(keydefs)
#update menu accelerators
menuEventDict={}
for menu in self.Bindings.menudefs:
......@@ -1064,17 +1084,28 @@ class EditorWindow(object):
# open/close first need to find the last stmt
lno = index2line(text.index('insert'))
y = PyParse.Parser(self.indentwidth, self.tabwidth)
for context in self.num_context_lines:
startat = max(lno - context, 1)
startatindex = repr(startat) + ".0"
if not self.context_use_ps1:
for context in self.num_context_lines:
startat = max(lno - context, 1)
startatindex = `startat` + ".0"
rawtext = text.get(startatindex, "insert")
y.set_str(rawtext)
bod = y.find_good_parse_start(
self.context_use_ps1,
self._build_char_in_string_func(startatindex))
if bod is not None or startat == 1:
break
y.set_lo(bod or 0)
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)
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)
y.set_lo(0)
c = y.get_continuation_type()
if c != PyParse.C_NONE:
# The current stmt hasn't ended yet.
......
This diff is collapsed.
This diff is collapsed.
......@@ -3,17 +3,14 @@
When you hit a right paren, the cursor should move briefly to the left
paren. Paren here is used generically; the matching applies to
parentheses, square brackets, and curly braces.
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 EditorWindow import EditorWindow, index2line
from HyperParser import HyperParser
from configHandler import idleConf
keysym_opener = {"parenright":'(', "bracketright":'[', "braceright":'{'}
CHECK_DELAY = 100 # miliseconds
class ParenMatch:
"""Highlight matching parentheses
......@@ -31,7 +28,6 @@ class ParenMatch:
expression from the left paren to the right paren.
TODO:
- fix interaction with CallTips
- extend IDLE with configuration dialog to change options
- implement rest of Emacs highlight styles (see below)
- 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,
so I haven't bothered.
"""
menudefs = []
menudefs = [
('edit', [
("Show surrounding parens", "<<flash-paren>>"),
])
]
STYLE = idleConf.GetOption('extensions','ParenMatch','style',
default='expression')
FLASH_DELAY = idleConf.GetOption('extensions','ParenMatch','flash-delay',
......@@ -50,14 +50,36 @@ class ParenMatch:
BELL = idleConf.GetOption('extensions','ParenMatch','bell',
type='bool',default=1)
RESTORE_VIRTUAL_EVENT_NAME = "<<parenmatch-check-restore>>"
# We want the restore event be called before the usual return and
# backspace events.
RESTORE_SEQUENCES = ("<KeyPress>", "<ButtonPress>",
"<Key-Return>", "<Key-BackSpace>")
def __init__(self, editwin):
self.editwin = editwin
self.text = editwin.text
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._restore = None
self.is_restore_active = 0
self.set_style(self.STYLE)
def activate_restore(self):
if not self.is_restore_active:
for seq in self.RESTORE_SEQUENCES:
self.text.event_add(self.RESTORE_VIRTUAL_EVENT_NAME, seq)
self.is_restore_active = True
def deactivate_restore(self):
if self.is_restore_active:
for seq in self.RESTORE_SEQUENCES:
self.text.event_delete(self.RESTORE_VIRTUAL_EVENT_NAME, seq)
self.is_restore_active = False
def set_style(self, style):
self.STYLE = style
if style == "default":
......@@ -67,23 +89,38 @@ class ParenMatch:
self.create_tag = self.create_tag_expression
self.set_timeout = self.set_timeout_none
def flash_open_paren_event(self, event):
index = self.finder.find(keysym_type(event.keysym))
if index is None:
def flash_paren_event(self, event):
indices = HyperParser(self.editwin, "insert").get_surrounding_brackets()
if indices is None:
self.warn_mismatched()
return
self._restore = 1
self.create_tag(index)
self.activate_restore()
self.create_tag(indices)
self.set_timeout_last()
def paren_closed_event(self, event):
# If it was a shortcut and not really a closing paren, quit.
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()
def check_restore_event(self, event=None):
if self._restore:
self.text.tag_delete("paren")
self._restore = None
def restore_event(self, event=None):
self.text.tag_delete("paren")
self.deactivate_restore()
self.counter += 1 # disable the last timer, if there is one.
def handle_restore_timer(self, timer_count):
if timer_count + 1 == self.counter:
self.check_restore_event()
if timer_count == self.counter:
self.restore_event()
def warn_mismatched(self):
if self.BELL:
......@@ -92,87 +129,43 @@ class ParenMatch:
# any one of the create_tag_XXX methods can be used depending on
# the style
def create_tag_default(self, index):
def create_tag_default(self, indices):
"""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)
def create_tag_expression(self, index):
def create_tag_expression(self, indices):
"""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)
# any one of the set_timeout_XXX methods can be used depending on
# the style
def set_timeout_none(self):
"""Highlight will remain until user input turns it off"""
pass
"""Highlight will remain until user input turns it off
or the insert has moved"""
# After CHECK_DELAY, call a function which disables the "paren" tag
# if the event is for the most recent timer and the insert has changed,
# or schedules another call for itself.
self.counter += 1
def callme(callme, self=self, c=self.counter,
index=self.text.index("insert")):
if index != self.text.index("insert"):
self.handle_restore_timer(c)
else:
self.editwin.text_frame.after(CHECK_DELAY, callme, callme)
self.editwin.text_frame.after(CHECK_DELAY, callme, callme)
def set_timeout_last(self):
"""The last highlight created will be removed after .5 sec"""
# associate a counter with an event; only disable the "paren"
# tag if the event is for the most recent timer.
self.counter += 1
self.editwin.text_frame.after(self.FLASH_DELAY,
lambda self=self, c=self.counter: \
self.handle_restore_timer(c))
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"""
^
[ \t]*
(?: if
| for
| while
(?: while
| else
| def
| return
......@@ -145,29 +143,11 @@ class Parser:
# This will be reliable iff given a reliable is_char_in_string
# function, meaning that when it says "no", it's absolutely
# guaranteed that the char is not in a string.
#
# 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):
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:
# no clue -- make the caller pass everything
return None
......@@ -363,6 +343,11 @@ class Parser:
# Creates:
# self.stmt_start, stmt_end
# slice indices of last interesting stmt
# self.stmt_bracketing
# the bracketing structure of the last interesting stmt;
# for example, for the statement "say(boo) or die", stmt_bracketing
# will be [(0, 0), (3, 1), (8, 0)]. Strings and comments are
# treated as brackets, for the matter.
# self.lastch
# last non-whitespace character before optional trailing
# comment
......@@ -404,6 +389,7 @@ class Parser:
lastch = ""
stack = [] # stack of open bracket indices
push_stack = stack.append
bracketing = [(p, 0)]
while p < q:
# suck up all except ()[]{}'"#\\
m = _chew_ordinaryre(str, p, q)
......@@ -424,6 +410,7 @@ class Parser:
if ch in "([{":
push_stack(p)
bracketing.append((p, len(stack)))
lastch = ch
p = p+1
continue
......@@ -433,6 +420,7 @@ class Parser:
del stack[-1]
lastch = ch
p = p+1
bracketing.append((p, len(stack)))
continue
if ch == '"' or ch == "'":
......@@ -443,14 +431,18 @@ class Parser:
# strings to a couple of characters per line. study1
# also needed to keep track of newlines, and we don't
# have to.
bracketing.append((p, len(stack)+1))
lastch = ch
p = _match_stringre(str, p, q).end()
bracketing.append((p, len(stack)))
continue
if ch == '#':
# consume comment and trailing newline
bracketing.append((p, len(stack)+1))
p = str.find('\n', p, q) + 1
assert p > 0
bracketing.append((p, len(stack)))
continue
assert ch == '\\'
......@@ -466,6 +458,7 @@ class Parser:
self.lastch = lastch
if stack:
self.lastopenbracketpos = stack[-1]
self.stmt_bracketing = tuple(bracketing)
# Assuming continuation is C_BRACKET, return the number
# of spaces the next line should be indented.
......@@ -590,3 +583,12 @@ class Parser:
def get_last_open_bracket_pos(self):
self._study2()
return self.lastopenbracketpos
# the structure of the bracketing of the last interesting statement,
# in the format defined in _study2, or None if the text didn't contain
# anything
stmt_bracketing = None
def get_last_stmt_bracketing(self):
self._study2()
return self.stmt_bracketing
......@@ -1091,11 +1091,12 @@ class PyShell(OutputWindow):
self.recall(self.text.get(next[0], next[1]), event)
return "break"
# No stdin mark -- just get the current line, less any prompt
line = self.text.get("insert linestart", "insert lineend")
last_line_of_prompt = sys.ps1.split('\n')[-1]
if line.startswith(last_line_of_prompt):
line = line[len(last_line_of_prompt):]
self.recall(line, event)
indices = self.text.tag_nextrange("console", "insert linestart")
if indices and \
self.text.compare(indices[0], "<=", "insert linestart"):
self.recall(self.text.get(indices[1], "insert lineend"), event)
else:
self.recall(self.text.get("insert linestart", "insert lineend"), event)
return "break"
# If we're between the beginning of the line and the iomark, i.e.
# in the prompt area, move to the end of the prompt
......
......@@ -52,22 +52,30 @@ check-module=<Alt-Key-x>
[CallTips]
enable=1
[CallTips_cfgBindings]
force-open-calltip=<Control-Key-backslash>
[CallTips_bindings]
paren-open=<Key-parenleft>
paren-close=<Key-parenright>
check-calltip-cancel=<KeyRelease>
calltip-cancel=<ButtonPress> <Key-Escape>
try-open-calltip=<KeyRelease-parenleft>
refresh-calltip=<KeyRelease-parenright> <KeyRelease-0>
[ParenMatch]
enable=0
enable=1
style= expression
flash-delay= 500
bell= 1
hilite-foreground= black
hilite-background= #43cd80
[ParenMatch_cfgBindings]
flash-paren=<Control-Key-0>
[ParenMatch_bindings]
flash-open-paren=<KeyRelease-parenright> <KeyRelease-bracketright> <KeyRelease-braceright>
check-restore=<KeyPress>
paren-closed=<KeyRelease-parenright> <KeyRelease-bracketright> <KeyRelease-braceright>
[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]
enable=1
......
......@@ -1106,6 +1106,13 @@ class ConfigDialog(Toplevel):
idleConf.userCfg[configType].Save()
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):
"Dynamically apply configuration changes"
winInstances=self.parent.instance_dict.keys()
......@@ -1113,7 +1120,7 @@ class ConfigDialog(Toplevel):
instance.ResetColorizer()
instance.ResetFont()
instance.set_notabs_indentwidth()
instance.ResetKeybindings()
instance.ApplyKeybindings()
instance.reset_help_menu_entries()
def Cancel(self):
......@@ -1124,6 +1131,7 @@ class ConfigDialog(Toplevel):
self.destroy()
def Apply(self):
self.DeactivateCurrentConfig()
self.SaveAllChangedConfigs()
self.ActivateConfigChanges()
......
......@@ -9,6 +9,8 @@ import threading
import Queue
import CallTips
import AutoComplete
import RemoteDebugger
import RemoteObjectBrowser
import StackViewer
......@@ -275,6 +277,7 @@ class Executive(object):
self.rpchandler = rpchandler
self.locals = __main__.__dict__
self.calltip = CallTips.CallTips()
self.autocomplete = AutoComplete.AutoComplete()
def runcode(self, code):
try:
......@@ -305,6 +308,9 @@ class Executive(object):
def get_the_calltip(self, name):
return self.calltip.fetch_tip(name)
def get_the_completion_list(self, what, mode):
return self.autocomplete.fetch_completions(what, mode)
def stackviewer(self, flist_oid=None):
if self.usr_exc_info:
typ, val, tb = self.usr_exc_info
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment