summaryrefslogtreecommitdiffstats
path: root/Lib/idlelib
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/idlelib')
-rw-r--r--Lib/idlelib/AutoComplete.py19
-rw-r--r--Lib/idlelib/AutoCompleteWindow.py9
-rw-r--r--Lib/idlelib/Bindings.py8
-rw-r--r--Lib/idlelib/CallTipWindow.py6
-rw-r--r--Lib/idlelib/CallTips.py214
-rw-r--r--Lib/idlelib/ColorDelegator.py11
-rw-r--r--Lib/idlelib/EditorWindow.py164
-rw-r--r--Lib/idlelib/FormatParagraph.py6
-rw-r--r--Lib/idlelib/GrepDialog.py2
-rw-r--r--Lib/idlelib/HyperParser.py5
-rw-r--r--Lib/idlelib/IOBinding.py39
-rw-r--r--Lib/idlelib/NEWS.txt49
-rw-r--r--Lib/idlelib/OutputWindow.py7
-rw-r--r--Lib/idlelib/PyShell.py278
-rw-r--r--Lib/idlelib/ReplaceDialog.py30
-rw-r--r--Lib/idlelib/ScriptBinding.py65
-rw-r--r--Lib/idlelib/config-extensions.def2
-rw-r--r--Lib/idlelib/config-main.def4
-rw-r--r--Lib/idlelib/configDialog.py28
-rw-r--r--Lib/idlelib/configHandler.py55
-rw-r--r--Lib/idlelib/extend.txt4
-rw-r--r--Lib/idlelib/help.txt24
-rw-r--r--Lib/idlelib/idlever.py2
-rw-r--r--Lib/idlelib/macosxSupport.py100
-rw-r--r--Lib/idlelib/rpc.py6
-rw-r--r--Lib/idlelib/run.py60
-rw-r--r--Lib/idlelib/tabbedpages.py4
-rw-r--r--Lib/idlelib/textView.py27
28 files changed, 853 insertions, 375 deletions
diff --git a/Lib/idlelib/AutoComplete.py b/Lib/idlelib/AutoComplete.py
index fa1733f..e4c1aff 100644
--- a/Lib/idlelib/AutoComplete.py
+++ b/Lib/idlelib/AutoComplete.py
@@ -124,19 +124,26 @@ class AutoComplete:
curline = self.text.get("insert linestart", "insert")
i = j = len(curline)
if hp.is_in_string() and (not mode or mode==COMPLETE_FILES):
+ # Find the beginning of the string
+ # fetch_completions will look at the file system to determine whether the
+ # string value constitutes an actual file name
+ # XXX could consider raw strings here and unescape the string value if it's
+ # not raw.
self._remove_autocomplete_window()
mode = COMPLETE_FILES
- while i and curline[i-1] in FILENAME_CHARS:
+ # Find last separator or string start
+ while i and curline[i-1] not in "'\"" + SEPS:
i -= 1
comp_start = curline[i:j]
j = i
- while i and curline[i-1] in FILENAME_CHARS + SEPS:
+ # Find string start
+ while i and curline[i-1] not in "'\"":
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:
+ while i and (curline[i-1] in ID_CHARS or ord(curline[i-1]) > 127):
i -= 1
comp_start = curline[i:j]
if i and curline[i-1] == '.':
@@ -190,8 +197,7 @@ class AutoComplete:
bigl = eval("dir()", namespace)
bigl.sort()
if "__all__" in bigl:
- smalll = eval("__all__", namespace)
- smalll.sort()
+ smalll = sorted(eval("__all__", namespace))
else:
smalll = [s for s in bigl if s[:1] != '_']
else:
@@ -200,8 +206,7 @@ class AutoComplete:
bigl = dir(entity)
bigl.sort()
if "__all__" in bigl:
- smalll = entity.__all__
- smalll.sort()
+ smalll = sorted(entity.__all__)
else:
smalll = [s for s in bigl if s[:1] != '_']
except:
diff --git a/Lib/idlelib/AutoCompleteWindow.py b/Lib/idlelib/AutoCompleteWindow.py
index 1ad8d15..7787e70 100644
--- a/Lib/idlelib/AutoCompleteWindow.py
+++ b/Lib/idlelib/AutoCompleteWindow.py
@@ -354,6 +354,15 @@ class AutoCompleteWindow:
# A modifier key, so ignore
return
+ elif event.char and event.char >= ' ':
+ # Regular character with a non-length-1 keycode
+ self._change_start(self.start + event.char)
+ 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"
+
else:
# Unknown event, close the window and let it through.
self.hide_window()
diff --git a/Lib/idlelib/Bindings.py b/Lib/idlelib/Bindings.py
index 74a93d3..ec2720b 100644
--- a/Lib/idlelib/Bindings.py
+++ b/Lib/idlelib/Bindings.py
@@ -98,14 +98,6 @@ if macosxSupport.runningAsOSXApp():
# menu
del menudefs[-1][1][0:2]
- menudefs.insert(0,
- ('application', [
- ('About IDLE', '<<about-idle>>'),
- None,
- ('_Preferences....', '<<open-config-dialog>>'),
- ]))
-
-
default_keydefs = idleConf.GetCurrentKeySet()
del sys
diff --git a/Lib/idlelib/CallTipWindow.py b/Lib/idlelib/CallTipWindow.py
index 27ed085..a2431f8 100644
--- a/Lib/idlelib/CallTipWindow.py
+++ b/Lib/idlelib/CallTipWindow.py
@@ -22,6 +22,7 @@ class CallTip:
self.parenline = self.parencol = None
self.lastline = None
self.hideid = self.checkhideid = None
+ self.checkhide_after_id = None
def position_window(self):
"""Check if needs to reposition the window, and if so - do it."""
@@ -102,7 +103,10 @@ class CallTip:
self.hidetip()
else:
self.position_window()
- self.widget.after(CHECKHIDE_TIME, self.checkhide_event)
+ if self.checkhide_after_id is not None:
+ self.widget.after_cancel(self.checkhide_after_id)
+ self.checkhide_after_id = \
+ self.widget.after(CHECKHIDE_TIME, self.checkhide_event)
def hide_event(self, event):
if not self.tipwindow:
diff --git a/Lib/idlelib/CallTips.py b/Lib/idlelib/CallTips.py
index cda2be9..3c8c096 100644
--- a/Lib/idlelib/CallTips.py
+++ b/Lib/idlelib/CallTips.py
@@ -67,18 +67,18 @@ class CallTips:
if not sur_paren:
return
hp.set_index(sur_paren[0])
- name = hp.get_expression()
- if not name:
+ expression = hp.get_expression()
+ if not expression:
return
- if not evalfuncs and (name.find('(') != -1):
+ if not evalfuncs and (expression.find('(') != -1):
return
- argspec = self.fetch_tip(name)
+ argspec = self.fetch_tip(expression)
if not argspec:
return
self.active_calltip = self._calltip_window()
self.active_calltip.showtip(argspec, sur_paren[0], sur_paren[1])
- def fetch_tip(self, name):
+ def fetch_tip(self, expression):
"""Return the argument list and docstring of a function or class.
If there is a Python subprocess, get the calltip there. Otherwise,
@@ -94,53 +94,59 @@ class CallTips:
"""
try:
rpcclt = self.editwin.flist.pyshell.interp.rpcclt
- except:
+ except AttributeError:
rpcclt = None
if rpcclt:
return rpcclt.remotecall("exec", "get_the_calltip",
- (name,), {})
+ (expression,), {})
else:
- entity = self.get_entity(name)
- return get_argspec(entity)
-
- def get_entity(self, name):
- "Lookup name in a namespace spanning sys.modules and __main.dict__."
- if name:
- namespace = sys.modules.copy()
- namespace.update(__main__.__dict__)
- try:
- return eval(name, namespace)
- except (NameError, AttributeError):
- return None
-
-def _find_constructor(class_ob):
- "Find the nearest __init__() in the class tree."
- try:
- return class_ob.__init__.__func__
- except AttributeError:
- for base in class_ob.__bases__:
- init = _find_constructor(base)
- if init:
- return init
- return None
+ return get_argspec(get_entity(expression))
+
+def get_entity(expression):
+ """Return the object corresponding to expression evaluated
+ in a namespace spanning sys.modules and __main.dict__.
+ """
+ if expression:
+ namespace = sys.modules.copy()
+ namespace.update(__main__.__dict__)
+ try:
+ return eval(expression, namespace)
+ except BaseException:
+ # An uncaught exception closes idle, and eval can raise any
+ # exception, especially if user classes are involved.
+ return None
+
+# The following are used in both get_argspec and tests
+_first_param = re.compile('(?<=\()\w*\,?\s*')
+_default_callable_argspec = "No docstring, see docs."
def get_argspec(ob):
- """Get a string describing the arguments for the given object."""
+ '''Return a string describing the arguments and return of a callable object.
+
+ For Python-coded functions and methods, the first line is introspected.
+ Delete 'self' parameter for classes (.__init__) and bound methods.
+ The last line is the first line of the doc string. For builtins, this typically
+ includes the arguments in addition to the return value.
+
+ '''
argspec = ""
- if ob is not None:
+ if hasattr(ob, '__call__'):
if isinstance(ob, type):
- fob = _find_constructor(ob)
- if fob is None:
- fob = lambda: None
- elif isinstance(ob, types.MethodType):
- fob = ob.__func__
+ fob = getattr(ob, '__init__', None)
+ elif isinstance(ob.__call__, types.MethodType):
+ fob = ob.__call__
else:
fob = ob
- if isinstance(fob, (types.FunctionType, types.LambdaType)):
+ if isinstance(fob, (types.FunctionType, types.MethodType)):
argspec = inspect.formatargspec(*inspect.getfullargspec(fob))
- pat = re.compile('self\,?\s*')
- argspec = pat.sub("", argspec)
- doc = getattr(ob, "__doc__", "")
+ if (isinstance(ob, (type, types.MethodType)) or
+ isinstance(ob.__call__, types.MethodType)):
+ argspec = _first_param.sub("", argspec)
+
+ if isinstance(ob.__call__, types.MethodType):
+ doc = ob.__call__.__doc__
+ else:
+ doc = getattr(ob, "__doc__", "")
if doc:
doc = doc.lstrip()
pos = doc.find("\n")
@@ -149,55 +155,113 @@ def get_argspec(ob):
if argspec:
argspec += "\n"
argspec += doc[:pos]
+ if not argspec:
+ argspec = _default_callable_argspec
return argspec
#################################################
#
-# Test code
-#
+# Test code tests CallTips.fetch_tip, get_entity, and get_argspec
+
def main():
+ # Putting expected in docstrings results in doubled tips for test
def t1(): "()"
def t2(a, b=None): "(a, b=None)"
def t3(a, *args): "(a, *args)"
def t4(*args): "(*args)"
- def t5(a, *args): "(a, *args)"
- def t6(a, b=None, *args, **kw): "(a, b=None, *args, **kw)"
+ def t5(a, b=None, *args, **kw): "(a, b=None, *args, **kw)"
class TC(object):
"(ai=None, *b)"
- def __init__(self, ai=None, *b): "(ai=None, *b)"
- def t1(self): "()"
- def t2(self, ai, b=None): "(ai, b=None)"
- def t3(self, ai, *args): "(ai, *args)"
- def t4(self, *args): "(*args)"
- def t5(self, ai, *args): "(ai, *args)"
- def t6(self, ai, b=None, *args, **kw): "(ai, b=None, *args, **kw)"
-
- __main__.__dict__.update(locals())
-
- def test(tests):
- ct = CallTips()
- failed=[]
- for t in tests:
- expected = t.__doc__ + "\n" + t.__doc__
- name = t.__name__
- # exercise fetch_tip(), not just get_argspec()
- try:
- qualified_name = "%s.%s" % (t.__self__.__class__.__name__, name)
- except AttributeError:
- qualified_name = name
- argspec = ct.fetch_tip(qualified_name)
- if argspec != expected:
- failed.append(t)
- fmt = "%s - expected %s, but got %s"
- print(fmt % (t.__name__, expected, get_argspec(t)))
- print("%d of %d tests failed" % (len(failed), len(tests)))
+ def __init__(self, ai=None, *b): "(self, ai=None, *b)"
+ def t1(self): "(self)"
+ def t2(self, ai, b=None): "(self, ai, b=None)"
+ def t3(self, ai, *args): "(self, ai, *args)"
+ def t4(self, *args): "(self, *args)"
+ def t5(self, ai, b=None, *args, **kw): "(self, ai, b=None, *args, **kw)"
+ def t6(no, self): "(no, self)"
+ @classmethod
+ def cm(cls, a): "(cls, a)"
+ @staticmethod
+ def sm(b): "(b)"
+ def __call__(self, ci): "(self, ci)"
tc = TC()
- tests = (t1, t2, t3, t4, t5, t6,
- TC, tc.t1, tc.t2, tc.t3, tc.t4, tc.t5, tc.t6)
- test(tests)
+ # Python classes that inherit builtin methods
+ class Int(int): "Int(x[, base]) -> integer"
+ class List(list): "List() -> new empty list"
+ # Simulate builtin with no docstring for default argspec test
+ class SB: __call__ = None
+
+ __main__.__dict__.update(locals()) # required for get_entity eval()
+
+ num_tests = num_fail = 0
+ tip = CallTips().fetch_tip
+
+ def test(expression, expected):
+ nonlocal num_tests, num_fail
+ num_tests += 1
+ argspec = tip(expression)
+ if argspec != expected:
+ num_fail += 1
+ fmt = "%s - expected\n%r\n - but got\n%r"
+ print(fmt % (expression, expected, argspec))
+
+ def test_builtins():
+ # if first line of a possibly multiline compiled docstring changes,
+ # must change corresponding test string
+ test('int', "int(x=0) -> integer")
+ test('Int', Int.__doc__)
+ test('types.MethodType', "method(function, instance)")
+ test('list', "list() -> new empty list")
+ test('List', List.__doc__)
+ test('list.__new__',
+ 'T.__new__(S, ...) -> a new object with type S, a subtype of T')
+ test('list.__init__',
+ 'x.__init__(...) initializes x; see help(type(x)) for signature')
+ append_doc = "L.append(object) -> None -- append object to end"
+ test('list.append', append_doc)
+ test('[].append', append_doc)
+ test('List.append', append_doc)
+ test('SB()', _default_callable_argspec)
+
+ def test_funcs():
+ for func in (t1, t2, t3, t4, t5, TC,):
+ fdoc = func.__doc__
+ test(func.__name__, fdoc + "\n" + fdoc)
+ for func in (TC.t1, TC.t2, TC.t3, TC.t4, TC.t5, TC.t6, TC.sm,
+ TC.__call__):
+ fdoc = func.__doc__
+ test('TC.'+func.__name__, fdoc + "\n" + fdoc)
+ fdoc = TC.cm.__func__.__doc__
+ test('TC.cm.__func__', fdoc + "\n" + fdoc)
+
+ def test_methods():
+ # test that first parameter is correctly removed from argspec
+ # using _first_param re to calculate expected masks re errors
+ for meth, mdoc in ((tc.t1, "()"), (tc.t4, "(*args)"), (tc.t6, "(self)"),
+ (TC.cm, "(a)"),):
+ test('tc.'+meth.__name__, mdoc + "\n" + meth.__doc__)
+ test('tc', "(ci)" + "\n" + tc.__call__.__doc__)
+ # directly test that re works to delete unicode parameter name
+ uni = "(A\u0391\u0410\u05d0\u0627\u0905\u1e00\u3042, a)" # various As
+ assert _first_param.sub('', uni) == '(a)'
+
+ def test_non_callables():
+ # expression evaluates, but not to a callable
+ for expr in ('0', '0.0' 'num_tests', b'num_tests', '[]', '{}'):
+ test(expr, '')
+ # expression does not evaluate, but raises an exception
+ for expr in ('1a', 'xyx', 'num_tests.xyz', '[int][1]', '{0:int}[1]'):
+ test(expr, '')
+
+ test_builtins()
+ test_funcs()
+ test_non_callables()
+ test_methods()
+
+ print("%d of %d tests failed" % (num_fail, num_tests))
if __name__ == '__main__':
main()
diff --git a/Lib/idlelib/ColorDelegator.py b/Lib/idlelib/ColorDelegator.py
index ab69b8a..e188192 100644
--- a/Lib/idlelib/ColorDelegator.py
+++ b/Lib/idlelib/ColorDelegator.py
@@ -15,15 +15,16 @@ def any(name, alternates):
def make_pat():
kw = r"\b" + any("KEYWORD", keyword.kwlist) + r"\b"
builtinlist = [str(name) for name in dir(builtins)
- if not name.startswith('_')]
+ if not name.startswith('_') and \
+ name not in keyword.kwlist]
# self.file = open("file") :
# 1st 'file' colorized normal, 2nd as builtin, 3rd as string
builtin = r"([^.'\"\\#]\b|^)" + any("BUILTIN", builtinlist) + r"\b"
comment = any("COMMENT", [r"#[^\n]*"])
- sqstring = r"(\b[rRuU])?'[^'\\\n]*(\\.[^'\\\n]*)*'?"
- dqstring = r'(\b[rRuU])?"[^"\\\n]*(\\.[^"\\\n]*)*"?'
- sq3string = r"(\b[rRuU])?'''[^'\\]*((\\.|'(?!''))[^'\\]*)*(''')?"
- dq3string = r'(\b[rRuU])?"""[^"\\]*((\\.|"(?!""))[^"\\]*)*(""")?'
+ sqstring = r"(\b[rRbB])?'[^'\\\n]*(\\.[^'\\\n]*)*'?"
+ dqstring = r'(\b[rRbB])?"[^"\\\n]*(\\.[^"\\\n]*)*"?'
+ sq3string = r"(\b[rRbB])?'''[^'\\]*((\\.|'(?!''))[^'\\]*)*(''')?"
+ dq3string = r'(\b[rRbB])?"""[^"\\]*((\\.|"(?!""))[^"\\]*)*(""")?'
string = any("STRING", [sq3string, dq3string, sqstring, dqstring])
return kw + "|" + builtin + "|" + comment + "|" + string +\
"|" + any("SYNC", [r"\n"])
diff --git a/Lib/idlelib/EditorWindow.py b/Lib/idlelib/EditorWindow.py
index 98ec02b..16f63c5 100644
--- a/Lib/idlelib/EditorWindow.py
+++ b/Lib/idlelib/EditorWindow.py
@@ -3,7 +3,6 @@ import os
import re
import string
import imp
-from itertools import count
from tkinter import *
import tkinter.simpledialog as tkSimpleDialog
import tkinter.messagebox as tkMessageBox
@@ -51,8 +50,63 @@ def _find_module(fullname, path=None):
path = module.__path__
except AttributeError:
raise ImportError('No source for module ' + module.__name__)
+ if descr[2] != imp.PY_SOURCE:
+ # If all of the above fails and didn't raise an exception,fallback
+ # to a straight import which can find __init__.py in a package.
+ m = __import__(fullname)
+ try:
+ filename = m.__file__
+ except AttributeError:
+ pass
+ else:
+ file = None
+ descr = os.path.splitext(filename)[1], None, imp.PY_SOURCE
return file, filename, descr
+
+class HelpDialog(object):
+
+ def __init__(self):
+ self.parent = None # parent of help window
+ self.dlg = None # the help window iteself
+
+ def display(self, parent, near=None):
+ """ Display the help dialog.
+
+ parent - parent widget for the help window
+
+ near - a Toplevel widget (e.g. EditorWindow or PyShell)
+ to use as a reference for placing the help window
+ """
+ if self.dlg is None:
+ self.show_dialog(parent)
+ if near:
+ self.nearwindow(near)
+
+ def show_dialog(self, parent):
+ self.parent = parent
+ fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
+ self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
+ dlg.bind('<Destroy>', self.destroy, '+')
+
+ def nearwindow(self, near):
+ # Place the help dialog near the window specified by parent.
+ # Note - this may not reposition the window in Metacity
+ # if "/apps/metacity/general/disable_workarounds" is enabled
+ dlg = self.dlg
+ geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
+ dlg.withdraw()
+ dlg.geometry("=+%d+%d" % geom)
+ dlg.deiconify()
+ dlg.lift()
+
+ def destroy(self, ev=None):
+ self.dlg = None
+ self.parent = None
+
+helpDialog = HelpDialog() # singleton instance
+
+
class EditorWindow(object):
from idlelib.Percolator import Percolator
from idlelib.ColorDelegator import ColorDelegator
@@ -116,13 +170,15 @@ class EditorWindow(object):
'recent-files.lst')
self.text_frame = text_frame = Frame(top)
self.vbar = vbar = Scrollbar(text_frame, name='vbar')
- self.width = idleConf.GetOption('main','EditorWindow','width')
+ self.width = idleConf.GetOption('main', 'EditorWindow',
+ 'width', type='int')
text_options = {
'name': 'text',
'padx': 5,
'wrap': 'none',
'width': self.width,
- 'height': idleConf.GetOption('main', 'EditorWindow', 'height')}
+ 'height': idleConf.GetOption('main', 'EditorWindow',
+ 'height', type='int')}
if TkVersion >= 8.5:
# Starting with tk 8.5 we have to set the new tabstyle option
# to 'wordprocessor' to achieve the same display of tabs as in
@@ -199,7 +255,8 @@ class EditorWindow(object):
if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
fontWeight='bold'
text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
- idleConf.GetOption('main', 'EditorWindow', 'font-size'),
+ idleConf.GetOption('main', 'EditorWindow',
+ 'font-size', type='int'),
fontWeight))
text_frame.pack(side=LEFT, fill=BOTH, expand=1)
text.pack(side=TOP, fill=BOTH, expand=1)
@@ -214,7 +271,8 @@ class EditorWindow(object):
# Although use-spaces=0 can be configured manually in config-main.def,
# configuration of tabs v. spaces is not supported in the configuration
# dialog. IDLE promotes the preferred Python indentation: use spaces!
- usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
+ usespaces = idleConf.GetOption('main', 'Indent',
+ 'use-spaces', type='bool')
self.usetabs = not usespaces
# tabwidth is the display width of a literal tab character.
@@ -328,9 +386,11 @@ class EditorWindow(object):
self.text.tag_remove("sel", "1.0", "end")
else:
if not self.text.index("sel.first"):
- self.text.mark_set("my_anchor", "insert") # there was no previous selection
+ # there was no previous selection
+ self.text.mark_set("my_anchor", "insert")
else:
- if self.text.compare(self.text.index("sel.first"), "<", self.text.index("insert")):
+ if self.text.compare(self.text.index("sel.first"), "<",
+ self.text.index("insert")):
self.text.mark_set("my_anchor", "sel.first") # extend back
else:
self.text.mark_set("my_anchor", "sel.last") # extend forward
@@ -385,7 +445,7 @@ class EditorWindow(object):
underline, label = prepstr(label)
menudict[name] = menu = Menu(mbar, name=name)
mbar.add_cascade(label=label, menu=menu, underline=underline)
- if macosxSupport.runningAsOSXApp():
+ if macosxSupport.isCarbonAquaTk(self.root):
# Insert the application menu
menudict['application'] = menu = Menu(mbar, name='apple')
mbar.add_cascade(label='IDLE', menu=menu)
@@ -410,7 +470,6 @@ class EditorWindow(object):
rmenu = None
def right_menu_event(self, event):
- self.text.tag_remove("sel", "1.0", "end")
self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
if not self.rmenu:
self.make_rmenu()
@@ -419,23 +478,53 @@ class EditorWindow(object):
iswin = sys.platform[:3] == 'win'
if iswin:
self.text.config(cursor="arrow")
+
+ for label, eventname, verify_state in self.rmenu_specs:
+ if verify_state is None:
+ continue
+ state = getattr(self, verify_state)()
+ rmenu.entryconfigure(label, state=state)
+
+
rmenu.tk_popup(event.x_root, event.y_root)
if iswin:
self.text.config(cursor="ibeam")
rmenu_specs = [
- # ("Label", "<<virtual-event>>"), ...
- ("Close", "<<close-window>>"), # Example
+ # ("Label", "<<virtual-event>>", "statefuncname"), ...
+ ("Close", "<<close-window>>", None), # Example
]
def make_rmenu(self):
rmenu = Menu(self.text, tearoff=0)
- for label, eventname in self.rmenu_specs:
- def command(text=self.text, eventname=eventname):
- text.event_generate(eventname)
- rmenu.add_command(label=label, command=command)
+ for label, eventname, _ in self.rmenu_specs:
+ if label is not None:
+ def command(text=self.text, eventname=eventname):
+ text.event_generate(eventname)
+ rmenu.add_command(label=label, command=command)
+ else:
+ rmenu.add_separator()
self.rmenu = rmenu
+ def rmenu_check_cut(self):
+ return self.rmenu_check_copy()
+
+ def rmenu_check_copy(self):
+ try:
+ indx = self.text.index('sel.first')
+ except TclError:
+ return 'disabled'
+ else:
+ return 'normal' if indx else 'disabled'
+
+ def rmenu_check_paste(self):
+ try:
+ self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
+ except TclError:
+ return 'disabled'
+ else:
+ return 'normal'
+
def about_dialog(self, event=None):
aboutDialog.AboutDialog(self.top,'About IDLE')
@@ -443,8 +532,11 @@ class EditorWindow(object):
configDialog.ConfigDialog(self.top,'Settings')
def help_dialog(self, event=None):
- fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
- textView.view_file(self.top,'Help',fn)
+ if self.root:
+ parent = self.root
+ else:
+ parent = self.top
+ helpDialog.display(parent, near=self.top)
def python_docs(self, event=None):
if sys.platform[:3] == 'win':
@@ -680,7 +772,8 @@ class EditorWindow(object):
if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
fontWeight='bold'
self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
- idleConf.GetOption('main','EditorWindow','font-size'),
+ idleConf.GetOption('main','EditorWindow','font-size',
+ type='int'),
fontWeight))
def RemoveKeybindings(self):
@@ -789,18 +882,23 @@ class EditorWindow(object):
rf_list = [path for path in rf_list if path not in bad_paths]
ulchars = "1234567890ABCDEFGHIJK"
rf_list = rf_list[0:len(ulchars)]
- rf_file = open(self.recent_files_path, 'w',
- encoding='utf_8', errors='replace')
try:
- rf_file.writelines(rf_list)
- finally:
- rf_file.close()
+ with open(self.recent_files_path, 'w',
+ encoding='utf_8', errors='replace') as rf_file:
+ rf_file.writelines(rf_list)
+ except IOError as err:
+ if not getattr(self.root, "recentfilelist_error_displayed", False):
+ self.root.recentfilelist_error_displayed = True
+ tkMessageBox.showerror(title='IDLE Error',
+ message='Unable to update Recent Files list:\n%s'
+ % str(err),
+ parent=self.text)
# for each edit window instance, construct the recent files menu
for instance in self.top.instance_dict:
menu = instance.recent_files_menu
- menu.delete(1, END) # clear, and rebuild:
- for i, file in zip(count(), rf_list):
- file_name = file[0:-1] # zap \n
+ menu.delete(0, END) # clear, and rebuild:
+ for i, file_name in enumerate(rf_list):
+ file_name = file_name.rstrip() # zap \n
# make unicode string to display non-ASCII chars correctly
ufile_name = self._filename_to_unicode(file_name)
callback = instance.__recent_file_callback(file_name)
@@ -1119,7 +1217,10 @@ class EditorWindow(object):
assert have > 0
want = ((have - 1) // self.indentwidth) * self.indentwidth
# Debug prompt is multilined....
- last_line_of_prompt = sys.ps1.split('\n')[-1]
+ if self.context_use_ps1:
+ last_line_of_prompt = sys.ps1.split('\n')[-1]
+ else:
+ last_line_of_prompt = ''
ncharsdeleted = 0
while 1:
if chars == last_line_of_prompt:
@@ -1517,7 +1618,7 @@ class IndentSearcher(object):
tokens = _tokenize.generate_tokens(self.readline)
for token in tokens:
self.tokeneater(*token)
- except _tokenize.TokenError:
+ except (_tokenize.TokenError, SyntaxError):
# since we cut off the tokenizer early, we can trigger
# spurious errors
pass
@@ -1544,7 +1645,12 @@ keynames = {
def get_accelerator(keydefs, eventname):
keylist = keydefs.get(eventname)
- if not keylist:
+ # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
+ # if not keylist:
+ if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
+ "<<open-module>>",
+ "<<goto-line>>",
+ "<<change-indentwidth>>"}):
return ""
s = keylist[0]
s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
diff --git a/Lib/idlelib/FormatParagraph.py b/Lib/idlelib/FormatParagraph.py
index 6a5f9b5..e3ca7b9 100644
--- a/Lib/idlelib/FormatParagraph.py
+++ b/Lib/idlelib/FormatParagraph.py
@@ -32,7 +32,8 @@ class FormatParagraph:
self.editwin = None
def format_paragraph_event(self, event):
- maxformatwidth = int(idleConf.GetOption('main','FormatParagraph','paragraph'))
+ maxformatwidth = int(idleConf.GetOption('main', 'FormatParagraph',
+ 'paragraph', type='int'))
text = self.editwin.text
first, last = self.editwin.get_selection_indices()
if first and last:
@@ -46,7 +47,8 @@ class FormatParagraph:
lines = data.split("\n")
lines = map(lambda st, l=len(comment_header): st[l:], lines)
data = "\n".join(lines)
- # Reformat to maxformatwidth chars or a 20 char width, whichever is greater.
+ # Reformat to maxformatwidth chars or a 20 char width,
+ # whichever is greater.
format_width = max(maxformatwidth - len(comment_header), 20)
newdata = reformat_paragraph(data, format_width)
# re-split and re-insert the comment header.
diff --git a/Lib/idlelib/GrepDialog.py b/Lib/idlelib/GrepDialog.py
index 01e0483..27fcc33 100644
--- a/Lib/idlelib/GrepDialog.py
+++ b/Lib/idlelib/GrepDialog.py
@@ -81,7 +81,7 @@ class GrepDialog(SearchDialogBase):
hits = 0
for fn in list:
try:
- f = open(fn)
+ f = open(fn, errors='replace')
except IOError as msg:
print(msg)
continue
diff --git a/Lib/idlelib/HyperParser.py b/Lib/idlelib/HyperParser.py
index 38a19f2..4414de7 100644
--- a/Lib/idlelib/HyperParser.py
+++ b/Lib/idlelib/HyperParser.py
@@ -232,6 +232,11 @@ class HyperParser:
pass
else:
# We can't continue after other types of brackets
+ if rawtext[pos] in "'\"":
+ # Scan a string prefix
+ while pos > 0 and rawtext[pos - 1] in "rRbB":
+ pos -= 1
+ last_identifier_pos = pos
break
else:
diff --git a/Lib/idlelib/IOBinding.py b/Lib/idlelib/IOBinding.py
index 3f5d556..c4f14ef 100644
--- a/Lib/idlelib/IOBinding.py
+++ b/Lib/idlelib/IOBinding.py
@@ -1,5 +1,6 @@
import os
import types
+import pipes
import sys
import codecs
import tempfile
@@ -156,29 +157,33 @@ class IOBinding:
self.filename_change_hook()
def open(self, event=None, editFile=None):
- if self.editwin.flist:
+ flist = self.editwin.flist
+ # Save in case parent window is closed (ie, during askopenfile()).
+ if flist:
if not editFile:
filename = self.askopenfile()
else:
filename=editFile
if filename:
- # If the current window has no filename and hasn't been
- # modified, we replace its contents (no loss). Otherwise
- # we open a new window. But we won't replace the
- # shell window (which has an interp(reter) attribute), which
- # gets set to "not modified" at every new prompt.
- try:
- interp = self.editwin.interp
- except AttributeError:
- interp = None
- if not self.filename and self.get_saved() and not interp:
- self.editwin.flist.open(filename, self.loadfile)
+ # If editFile is valid and already open, flist.open will
+ # shift focus to its existing window.
+ # If the current window exists and is a fresh unnamed,
+ # unmodified editor window (not an interpreter shell),
+ # pass self.loadfile to flist.open so it will load the file
+ # in the current window (if the file is not already open)
+ # instead of a new window.
+ if (self.editwin and
+ not getattr(self.editwin, 'interp', None) and
+ not self.filename and
+ self.get_saved()):
+ flist.open(filename, self.loadfile)
else:
- self.editwin.flist.open(filename)
+ flist.open(filename)
else:
- self.text.focus_set()
+ if self.text:
+ self.text.focus_set()
return "break"
- #
+
# Code for use outside IDLE:
if self.get_saved():
reply = self.maybesave()
@@ -232,7 +237,7 @@ class IOBinding:
# before being able to execute the code
self.set_saved(False)
self.text.mark_set("insert", "1.0")
- self.text.see("insert")
+ self.text.yview("insert")
self.updaterecentfileslist(filename)
return True
@@ -454,7 +459,7 @@ class IOBinding:
else: #no printing for this platform
printPlatform = False
if printPlatform: #we can try to print for this platform
- command = command % filename
+ command = command % pipes.quote(filename)
pipe = os.popen(command, "r")
# things can get ugly on NT if there is no printer available.
output = pipe.read().strip()
diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt
index 2888294..87c099f 100644
--- a/Lib/idlelib/NEWS.txt
+++ b/Lib/idlelib/NEWS.txt
@@ -1,7 +1,48 @@
-What's New in IDLE 3.1.4?
+What's New in IDLE 3.2.4?
=========================
-*Release date: XX-XXX-11*
+- Issue #17625: Close the replace dialog after it is used.
+
+- Issue #7163: Propagate return value of sys.stdout.write.
+
+- Issue #15318: Prevent writing to sys.stdin.
+
+- Issue #13532, #15319: Check that arguments to sys.stdout.write are strings.
+
+- Issue # 12510: Attempt to get certain tool tips no longer crashes IDLE.
+ Erroneous tool tips have been corrected. Default added for callables.
+
+- Issue10365: File open dialog now works instead of crashing even when
+ parent window is closed while dialog is open.
+
+- Issue 14876: use user-selected font for highlight configuration.
+
+- Issue #14937: Perform auto-completion of filenames in strings even for
+ non-ASCII filenames. Likewise for identifiers.
+
+- Issue #14018: Update checks for unstable system Tcl/Tk versions on OS X
+ to include versions shipped with OS X 10.7 and 10.8 in addition to 10.6.
+
+- Issue #15853: Prevent IDLE crash on OS X when opening Preferences menu
+ with certain versions of Tk 8.5. Initial patch by Kevin Walzer.
+
+
+What's New in IDLE 3.2.3?
+=========================
+
+- Issue #14409: IDLE now properly executes commands in the Shell window
+ when it cannot read the normal config files on startup and
+ has to use the built-in default key bindings.
+ There was previously a bug in one of the defaults.
+
+- Issue #3573: IDLE hangs when passing invalid command line args
+ (directory(ies) instead of file(s)).
+
+
+What's New in IDLE 3.2.1?
+=========================
+
+*Release date: 15-May-11*
- Issue #6378: Further adjust idle.bat to start associated Python
@@ -817,7 +858,3 @@ What's New in IDLEfork 0.9 Alpha 1?
Refer to HISTORY.txt for additional information on earlier releases.
--------------------------------------------------------------------
-
-
-
-
diff --git a/Lib/idlelib/OutputWindow.py b/Lib/idlelib/OutputWindow.py
index 565cc9b..745ccd2 100644
--- a/Lib/idlelib/OutputWindow.py
+++ b/Lib/idlelib/OutputWindow.py
@@ -40,6 +40,7 @@ class OutputWindow(EditorWindow):
self.text.insert(mark, s, tags)
self.text.see(mark)
self.text.update()
+ return len(s)
def writelines(self, lines):
for line in lines:
@@ -51,7 +52,11 @@ class OutputWindow(EditorWindow):
# Our own right-button menu
rmenu_specs = [
- ("Go to file/line", "<<goto-file-line>>"),
+ ("Cut", "<<cut>>", "rmenu_check_cut"),
+ ("Copy", "<<copy>>", "rmenu_check_copy"),
+ ("Paste", "<<paste>>", "rmenu_check_paste"),
+ (None, None, None),
+ ("Go to file/line", "<<goto-file-line>>", None),
]
file_line_pats = [
diff --git a/Lib/idlelib/PyShell.py b/Lib/idlelib/PyShell.py
index 0fa3d76..865472e 100644
--- a/Lib/idlelib/PyShell.py
+++ b/Lib/idlelib/PyShell.py
@@ -1,15 +1,18 @@
-#! /usr/bin/env python
+#! /usr/bin/env python3
+import getopt
import os
import os.path
-import sys
-import getopt
import re
import socket
-import time
+import subprocess
+import sys
import threading
+import time
+import tokenize
import traceback
import types
+import io
import linecache
from code import InteractiveInterpreter
@@ -37,11 +40,6 @@ from idlelib import macosxSupport
HOST = '127.0.0.1' # python execution server on localhost loopback
PORT = 0 # someday pass in host, port for remote debug capability
-try:
- from signal import SIGTERM
-except ImportError:
- SIGTERM = 15
-
# Override warnings module to write to warning_stream. Initialize to send IDLE
# internal warnings to the console. ScriptBinding.check_syntax() will
# temporarily redirect the stream to the shell window to display warnings when
@@ -55,20 +53,21 @@ except ImportError:
else:
def idle_showwarning(message, category, filename, lineno,
file=None, line=None):
- file = warning_stream
+ if file is None:
+ file = warning_stream
try:
- file.write(warnings.formatwarning(message, category, filename,\
- lineno, file=file, line=line))
+ file.write(warnings.formatwarning(message, category, filename,
+ lineno, line=line))
except IOError:
pass ## file (probably __stderr__) is invalid, warning dropped.
warnings.showwarning = idle_showwarning
- def idle_formatwarning(message, category, filename, lineno,
- file=None, line=None):
+ def idle_formatwarning(message, category, filename, lineno, line=None):
"""Format warnings the IDLE way"""
s = "\nWarning (from warnings module):\n"
s += ' File \"%s\", line %s\n' % (filename, lineno)
- line = linecache.getline(filename, lineno).strip() \
- if line is None else line
+ if line is None:
+ line = linecache.getline(filename, lineno)
+ line = line.strip()
if line:
s += " %s\n" % line
s += "%s: %s\n>>> " % (category.__name__, message)
@@ -81,18 +80,17 @@ def extended_linecache_checkcache(filename=None,
Rather than repeating the linecache code, patch it to save the
<pyshell#...> entries, call the original linecache.checkcache()
- (which destroys them), and then restore the saved entries.
+ (skipping them), and then restore the saved entries.
orig_checkcache is bound at definition time to the original
method, allowing it to be patched.
-
"""
cache = linecache.cache
save = {}
- for filename in cache:
- if filename[:1] + filename[-1:] == '<>':
- save[filename] = cache[filename]
- orig_checkcache()
+ for key in list(cache):
+ if key[:1] + key[-1:] == '<>':
+ save[key] = cache.pop(key)
+ orig_checkcache(filename)
cache.update(save)
# Patch linecache.checkcache():
@@ -119,8 +117,14 @@ class PyShellEditorWindow(EditorWindow):
old_hook()
self.io.set_filename_change_hook(filename_changed_hook)
- rmenu_specs = [("Set Breakpoint", "<<set-breakpoint-here>>"),
- ("Clear Breakpoint", "<<clear-breakpoint-here>>")]
+ rmenu_specs = [
+ ("Cut", "<<cut>>", "rmenu_check_cut"),
+ ("Copy", "<<copy>>", "rmenu_check_copy"),
+ ("Paste", "<<paste>>", "rmenu_check_paste"),
+ (None, None, None),
+ ("Set Breakpoint", "<<set-breakpoint-here>>", None),
+ ("Clear Breakpoint", "<<clear-breakpoint-here>>", None)
+ ]
def set_breakpoint(self, lineno):
text = self.text
@@ -205,18 +209,26 @@ class PyShellEditorWindow(EditorWindow):
breaks = self.breakpoints
filename = self.io.filename
try:
- lines = open(self.breakpointPath,"r").readlines()
+ with open(self.breakpointPath, "r") as fp:
+ lines = fp.readlines()
except IOError:
lines = []
- new_file = open(self.breakpointPath,"w")
- for line in lines:
- if not line.startswith(filename + '='):
- new_file.write(line)
- self.update_breakpoints()
- breaks = self.breakpoints
- if breaks:
- new_file.write(filename + '=' + str(breaks) + '\n')
- new_file.close()
+ try:
+ with open(self.breakpointPath, "w") as new_file:
+ for line in lines:
+ if not line.startswith(filename + '='):
+ new_file.write(line)
+ self.update_breakpoints()
+ breaks = self.breakpoints
+ if breaks:
+ new_file.write(filename + '=' + str(breaks) + '\n')
+ except IOError as err:
+ if not getattr(self.root, "breakpoint_error_displayed", False):
+ self.root.breakpoint_error_displayed = True
+ tkMessageBox.showerror(title='IDLE Error',
+ message='Unable to update breakpoint list:\n%s'
+ % str(err),
+ parent=self.text)
def restore_file_breaks(self):
self.text.update() # this enables setting "BREAK" tags to be visible
@@ -224,7 +236,8 @@ class PyShellEditorWindow(EditorWindow):
if filename is None:
return
if os.path.isfile(self.breakpointPath):
- lines = open(self.breakpointPath,"r").readlines()
+ with open(self.breakpointPath, "r") as fp:
+ lines = fp.readlines()
for line in lines:
if line.startswith(filename + '='):
breakpoint_linenumbers = eval(line[len(filename)+1:])
@@ -241,8 +254,8 @@ class PyShellEditorWindow(EditorWindow):
def ranges_to_linenumbers(self, ranges):
lines = []
for index in range(0, len(ranges), 2):
- lineno = int(float(ranges[index]))
- end = int(float(ranges[index+1]))
+ lineno = int(float(ranges[index].string))
+ end = int(float(ranges[index+1].string))
while lineno < end:
lines.append(lineno)
lineno += 1
@@ -303,6 +316,11 @@ class ModifiedColorDelegator(ColorDelegator):
"console": idleConf.GetHighlight(theme, "console"),
})
+ def removecolors(self):
+ # Don't remove shell color tags before "iomark"
+ for tag in self.tagdefs:
+ self.tag_remove(tag, "iomark", "end")
+
class ModifiedUndoDelegator(UndoDelegator):
"Extend base class: forbid insert/delete before the I/O mark"
@@ -342,15 +360,15 @@ class ModifiedInterpreter(InteractiveInterpreter):
self.restarting = False
self.subprocess_arglist = None
self.port = PORT
+ self.original_compiler_flags = self.compile.compiler.flags
rpcclt = None
- rpcpid = None
+ rpcsubproc = None
def spawn_subprocess(self):
if self.subprocess_arglist is None:
self.subprocess_arglist = self.build_subprocess_arglist()
- args = self.subprocess_arglist
- self.rpcpid = os.spawnv(os.P_NOWAIT, sys.executable, args)
+ self.rpcsubproc = subprocess.Popen(self.subprocess_arglist)
def build_subprocess_arglist(self):
assert (self.port!=0), (
@@ -365,12 +383,7 @@ class ModifiedInterpreter(InteractiveInterpreter):
command = "__import__('idlelib.run').run.main(%r)" % (del_exitf,)
else:
command = "__import__('run').main(%r)" % (del_exitf,)
- if sys.platform[:3] == 'win' and ' ' in sys.executable:
- # handle embedded space in path by quoting the argument
- decorated_exec = '"%s"' % sys.executable
- else:
- decorated_exec = sys.executable
- return [decorated_exec] + w + ["-c", command, str(self.port)]
+ return [sys.executable] + w + ["-c", command, str(self.port)]
def start_subprocess(self):
addr = (HOST, self.port)
@@ -404,17 +417,18 @@ class ModifiedInterpreter(InteractiveInterpreter):
except socket.timeout as err:
self.display_no_subprocess_error()
return None
- self.rpcclt.register("stdin", self.tkconsole)
+ self.rpcclt.register("console", self.tkconsole)
+ self.rpcclt.register("stdin", self.tkconsole.stdin)
self.rpcclt.register("stdout", self.tkconsole.stdout)
self.rpcclt.register("stderr", self.tkconsole.stderr)
self.rpcclt.register("flist", self.tkconsole.flist)
self.rpcclt.register("linecache", linecache)
self.rpcclt.register("interp", self)
- self.transfer_path()
+ self.transfer_path(with_cwd=True)
self.poll_subprocess()
return self.rpcclt
- def restart_subprocess(self):
+ def restart_subprocess(self, with_cwd=False):
if self.restarting:
return self.rpcclt
self.restarting = True
@@ -428,7 +442,7 @@ class ModifiedInterpreter(InteractiveInterpreter):
pass
# Kill subprocess, spawn a new one, accept connection.
self.rpcclt.close()
- self.unix_terminate()
+ self.terminate_subprocess()
console = self.tkconsole
was_executing = console.executing
console.executing = False
@@ -438,7 +452,7 @@ class ModifiedInterpreter(InteractiveInterpreter):
except socket.timeout as err:
self.display_no_subprocess_error()
return None
- self.transfer_path()
+ self.transfer_path(with_cwd=with_cwd)
# annotate restart in shell window and mark it
console.text.delete("iomark", "end-1c")
if was_executing:
@@ -455,6 +469,7 @@ class ModifiedInterpreter(InteractiveInterpreter):
gui = RemoteDebugger.restart_subprocess_debugger(self.rpcclt)
# reload remote debugger breakpoints for all PyShellEditWindows
debug.load_breakpoints()
+ self.compile.compiler.flags = self.original_compiler_flags
self.restarting = False
return self.rpcclt
@@ -469,30 +484,35 @@ class ModifiedInterpreter(InteractiveInterpreter):
self.rpcclt.close()
except AttributeError: # no socket
pass
- self.unix_terminate()
+ self.terminate_subprocess()
self.tkconsole.executing = False
self.rpcclt = None
- def unix_terminate(self):
- "UNIX: make sure subprocess is terminated and collect status"
- if hasattr(os, 'kill'):
+ def terminate_subprocess(self):
+ "Make sure subprocess is terminated"
+ try:
+ self.rpcsubproc.kill()
+ except OSError:
+ # process already terminated
+ return
+ else:
try:
- os.kill(self.rpcpid, SIGTERM)
+ self.rpcsubproc.wait()
except OSError:
- # process already terminated:
return
- else:
- try:
- os.waitpid(self.rpcpid, 0)
- except OSError:
- return
- def transfer_path(self):
+ def transfer_path(self, with_cwd=False):
+ if with_cwd: # Issue 13506
+ path = [''] # include Current Working Directory
+ path.extend(sys.path)
+ else:
+ path = sys.path
+
self.runcommand("""if 1:
import sys as _sys
_sys.path = %r
del _sys
- \n""" % (sys.path,))
+ \n""" % (path,))
active_seq = None
@@ -582,7 +602,8 @@ class ModifiedInterpreter(InteractiveInterpreter):
def execfile(self, filename, source=None):
"Execute an existing file"
if source is None:
- source = open(filename, "r").read()
+ with tokenize.open(filename) as fp:
+ source = fp.read()
try:
code = compile(source, filename, "exec")
except (OverflowError, SyntaxError):
@@ -651,9 +672,9 @@ class ModifiedInterpreter(InteractiveInterpreter):
text = tkconsole.text
text.tag_remove("ERROR", "1.0", "end")
type, value, tb = sys.exc_info()
- msg = value.msg or "<no detail available>"
- lineno = value.lineno or 1
- offset = value.offset or 0
+ msg = getattr(value, 'msg', '') or value or "<no detail available>"
+ lineno = getattr(value, 'lineno', '') or 1
+ offset = getattr(value, 'offset', '') or 0
if offset == 0:
lineno += 1 #mark end of offending line
if lineno == 1:
@@ -743,7 +764,7 @@ class ModifiedInterpreter(InteractiveInterpreter):
def write(self, s):
"Override base class method"
- self.tkconsole.stderr.write(s)
+ return self.tkconsole.stderr.write(s)
def display_port_binding_error(self):
tkMessageBox.showerror(
@@ -837,13 +858,14 @@ class PyShell(OutputWindow):
self.save_stderr = sys.stderr
self.save_stdin = sys.stdin
from idlelib import IOBinding
- self.stdout = PseudoFile(self, "stdout", IOBinding.encoding)
- self.stderr = PseudoFile(self, "stderr", IOBinding.encoding)
- self.console = PseudoFile(self, "console", IOBinding.encoding)
+ self.stdin = PseudoInputFile(self, "stdin", IOBinding.encoding)
+ self.stdout = PseudoOutputFile(self, "stdout", IOBinding.encoding)
+ self.stderr = PseudoOutputFile(self, "stderr", IOBinding.encoding)
+ self.console = PseudoOutputFile(self, "console", IOBinding.encoding)
if not use_subprocess:
sys.stdout = self.stdout
sys.stderr = self.stderr
- sys.stdin = self
+ sys.stdin = self.stdin
try:
# page help() text to shell.
import pydoc # import must be done here to capture i/o rebinding.
@@ -1185,7 +1207,8 @@ class PyShell(OutputWindow):
self.text.see("restart")
def restart_shell(self, event=None):
- self.interp.restart_subprocess()
+ "Callback for Run/Restart Shell Cntl-F6"
+ self.interp.restart_subprocess(with_cwd=True)
def showprompt(self):
self.resetoutput()
@@ -1210,7 +1233,7 @@ class PyShell(OutputWindow):
def write(self, s, tags=()):
try:
self.text.mark_gravity("iomark", "right")
- OutputWindow.write(self, s, tags, "iomark")
+ count = OutputWindow.write(self, s, tags, "iomark")
self.text.mark_gravity("iomark", "left")
except:
raise ###pass # ### 11Aug07 KBK if we are expecting exceptions
@@ -1219,27 +1242,98 @@ class PyShell(OutputWindow):
self.canceled = 0
if not use_subprocess:
raise KeyboardInterrupt
+ return count
+
+ def rmenu_check_cut(self):
+ try:
+ if self.text.compare('sel.first', '<', 'iomark'):
+ return 'disabled'
+ except TclError: # no selection, so the index 'sel.first' doesn't exist
+ return 'disabled'
+ return super().rmenu_check_cut()
+
+ def rmenu_check_paste(self):
+ if self.text.compare('insert','<','iomark'):
+ return 'disabled'
+ return super().rmenu_check_paste()
-class PseudoFile(object):
+class PseudoFile(io.TextIOBase):
def __init__(self, shell, tags, encoding=None):
self.shell = shell
self.tags = tags
- self.encoding = encoding
+ self._encoding = encoding
+
+ @property
+ def encoding(self):
+ return self._encoding
+
+ @property
+ def name(self):
+ return '<%s>' % self.tags
+
+ def isatty(self):
+ return True
+
+
+class PseudoOutputFile(PseudoFile):
+
+ def writable(self):
+ return True
def write(self, s):
- self.shell.write(s, self.tags)
+ if self.closed:
+ raise ValueError("write to closed file")
+ if not isinstance(s, str):
+ raise TypeError('must be str, not ' + type(s).__name__)
+ return self.shell.write(s, self.tags)
- def writelines(self, lines):
- for line in lines:
- self.write(line)
- def flush(self):
- pass
+class PseudoInputFile(PseudoFile):
- def isatty(self):
+ def __init__(self, shell, tags, encoding=None):
+ PseudoFile.__init__(self, shell, tags, encoding)
+ self._line_buffer = ''
+
+ def readable(self):
return True
+ def read(self, size=-1):
+ if self.closed:
+ raise ValueError("read from closed file")
+ if size is None:
+ size = -1
+ elif not isinstance(size, int):
+ raise TypeError('must be int, not ' + type(size).__name__)
+ result = self._line_buffer
+ self._line_buffer = ''
+ if size < 0:
+ while True:
+ line = self.shell.readline()
+ if not line: break
+ result += line
+ else:
+ while len(result) < size:
+ line = self.shell.readline()
+ if not line: break
+ result += line
+ self._line_buffer = result[size:]
+ result = result[:size]
+ return result
+
+ def readline(self, size=-1):
+ if self.closed:
+ raise ValueError("read from closed file")
+ if size is None:
+ size = -1
+ elif not isinstance(size, int):
+ raise TypeError('must be int, not ' + type(size).__name__)
+ line = self._line_buffer or self.shell.readline()
+ if size < 0:
+ size = len(line)
+ self._line_buffer = line[size:]
+ return line[:size]
+
usage_msg = """\
@@ -1380,8 +1474,10 @@ def main():
if enable_edit:
if not (cmd or script):
- for filename in args:
- flist.open(filename)
+ for filename in args[:]:
+ if flist.open(filename) is None:
+ # filename is a directory actually, disconsider it
+ args.remove(filename)
if not args:
flist.new()
if enable_shell:
@@ -1417,7 +1513,15 @@ def main():
shell.interp.prepend_syspath(script)
shell.interp.execfile(script)
- root.mainloop()
+ # Check for problematic OS X Tk versions and print a warning message
+ # in the IDLE shell window; this is less intrusive than always opening
+ # a separate window.
+ tkversionwarning = macosxSupport.tkVersionWarning(root)
+ if tkversionwarning:
+ shell.interp.runcommand(''.join(("print('", tkversionwarning, "')")))
+
+ while flist.inversedict: # keep IDLE running while files are open.
+ root.mainloop()
root.destroy()
if __name__ == "__main__":
diff --git a/Lib/idlelib/ReplaceDialog.py b/Lib/idlelib/ReplaceDialog.py
index d713e61..e73f2c5 100644
--- a/Lib/idlelib/ReplaceDialog.py
+++ b/Lib/idlelib/ReplaceDialog.py
@@ -2,6 +2,8 @@ from tkinter import *
from idlelib import SearchEngine
from idlelib.SearchDialogBase import SearchDialogBase
+import re
+
def replace(text):
root = text._root()
@@ -11,6 +13,7 @@ def replace(text):
dialog = engine._replacedialog
dialog.open(text)
+
class ReplaceDialog(SearchDialogBase):
title = "Replace Dialog"
@@ -55,8 +58,23 @@ class ReplaceDialog(SearchDialogBase):
def default_command(self, event=None):
if self.do_find(self.ok):
- self.do_replace()
- self.do_find(0)
+ if self.do_replace(): # Only find next match if replace succeeded.
+ # A bad re can cause a it to fail.
+ self.do_find(0)
+
+ def _replace_expand(self, m, repl):
+ """ Helper function for expanding a regular expression
+ in the replace field, if needed. """
+ if self.engine.isre():
+ try:
+ new = m.expand(repl)
+ except re.error:
+ self.engine.report_error(repl, 'Invalid Replace Expression')
+ new = None
+ else:
+ new = repl
+
+ return new
def replace_all(self, event=None):
prog = self.engine.getprog()
@@ -86,7 +104,9 @@ class ReplaceDialog(SearchDialogBase):
line, m = res
chars = text.get("%d.0" % line, "%d.0" % (line+1))
orig = m.group()
- new = m.expand(repl)
+ new = self._replace_expand(m, repl)
+ if new is None:
+ break
i, j = m.span()
first = "%d.%d" % (line, i)
last = "%d.%d" % (line, j)
@@ -138,7 +158,9 @@ class ReplaceDialog(SearchDialogBase):
m = prog.match(chars, col)
if not prog:
return False
- new = m.expand(self.replvar.get())
+ new = self._replace_expand(m, self.replvar.get())
+ if new is None:
+ return False
text.mark_set("insert", first)
text.undo_block_start()
if m.group():
diff --git a/Lib/idlelib/ScriptBinding.py b/Lib/idlelib/ScriptBinding.py
index 41e6a59..18ce965 100644
--- a/Lib/idlelib/ScriptBinding.py
+++ b/Lib/idlelib/ScriptBinding.py
@@ -27,6 +27,7 @@ from idlelib.EditorWindow import EditorWindow
from idlelib import PyShell, IOBinding
from idlelib.configHandler import idleConf
+from idlelib import macosxSupport
indent_message = """Error: Inconsistent indentation detected!
@@ -52,6 +53,9 @@ class ScriptBinding:
self.flist = self.editwin.flist
self.root = self.editwin.root
+ if macosxSupport.runningAsOSXApp():
+ self.editwin.text_frame.bind('<<run-module-event-2>>', self._run_module_event)
+
def check_module_event(self, event):
filename = self.getfilename()
if not filename:
@@ -63,25 +67,20 @@ class ScriptBinding:
def tabnanny(self, filename):
# XXX: tabnanny should work on binary files as well
- with open(filename, 'r', encoding='iso-8859-1') as f:
- two_lines = f.readline() + f.readline()
- encoding = IOBinding.coding_spec(two_lines)
- if not encoding:
- encoding = 'utf-8'
- f = open(filename, 'r', encoding=encoding)
- try:
- tabnanny.process_tokens(tokenize.generate_tokens(f.readline))
- except tokenize.TokenError as msg:
- msgtxt, (lineno, start) = msg
- self.editwin.gotoline(lineno)
- self.errorbox("Tabnanny Tokenizing Error",
- "Token Error: %s" % msgtxt)
- return False
- except tabnanny.NannyNag as nag:
- # The error messages from tabnanny are too confusing...
- self.editwin.gotoline(nag.get_lineno())
- self.errorbox("Tab/space error", indent_message)
- return False
+ with tokenize.open(filename) as f:
+ try:
+ tabnanny.process_tokens(tokenize.generate_tokens(f.readline))
+ except tokenize.TokenError as msg:
+ msgtxt, (lineno, start) = msg
+ self.editwin.gotoline(lineno)
+ self.errorbox("Tabnanny Tokenizing Error",
+ "Token Error: %s" % msgtxt)
+ return False
+ except tabnanny.NannyNag as nag:
+ # The error messages from tabnanny are too confusing...
+ self.editwin.gotoline(nag.get_lineno())
+ self.errorbox("Tab/space error", indent_message)
+ return False
return True
def checksyntax(self, filename):
@@ -102,10 +101,10 @@ class ScriptBinding:
try:
# If successful, return the compiled code
return compile(source, filename, "exec")
- except (SyntaxError, OverflowError) as value:
- msg = value.msg or "<no detail available>"
- lineno = value.lineno or 1
- offset = value.offset or 0
+ except (SyntaxError, OverflowError, ValueError) as value:
+ msg = getattr(value, 'msg', '') or value or "<no detail available>"
+ lineno = getattr(value, 'lineno', '') or 1
+ offset = getattr(value, 'offset', '') or 0
if offset == 0:
lineno += 1 #mark end of offending line
pos = "0.0 + %d lines + %d chars" % (lineno-1, offset-1)
@@ -116,14 +115,27 @@ class ScriptBinding:
shell.set_warning_stream(saved_stream)
def run_module_event(self, event):
+ if macosxSupport.runningAsOSXApp():
+ # Tk-Cocoa in MacOSX is broken until at least
+ # Tk 8.5.9, and without this rather
+ # crude workaround IDLE would hang when a user
+ # tries to run a module using the keyboard shortcut
+ # (the menu item works fine).
+ self.editwin.text_frame.after(200,
+ lambda: self.editwin.text_frame.event_generate('<<run-module-event-2>>'))
+ return 'break'
+ else:
+ return self._run_module_event(event)
+
+ def _run_module_event(self, event):
"""Run the module after setting up the environment.
First check the syntax. If OK, make sure the shell is active and
then transfer the arguments, set the run environment's working
directory to the directory of the module being executed and also
add that directory to its sys.path if not already included.
-
"""
+
filename = self.getfilename()
if not filename:
return 'break'
@@ -132,10 +144,9 @@ class ScriptBinding:
return 'break'
if not self.tabnanny(filename):
return 'break'
- shell = self.shell
- interp = shell.interp
+ interp = self.shell.interp
if PyShell.use_subprocess:
- shell.restart_shell()
+ interp.restart_subprocess(with_cwd=False)
dirname = os.path.dirname(filename)
# XXX Too often this discards arguments the user just set...
interp.runcommand("""if 1:
diff --git a/Lib/idlelib/config-extensions.def b/Lib/idlelib/config-extensions.def
index 78b68f6..39e69ce 100644
--- a/Lib/idlelib/config-extensions.def
+++ b/Lib/idlelib/config-extensions.def
@@ -46,6 +46,8 @@ zoom-height=<Alt-Key-2>
[ScriptBinding]
enable=1
+enable_shell=0
+enable_editor=1
[ScriptBinding_cfgBindings]
run-module=<Key-F5>
check-module=<Alt-Key-x>
diff --git a/Lib/idlelib/config-main.def b/Lib/idlelib/config-main.def
index 5ddd098..9546e2b 100644
--- a/Lib/idlelib/config-main.def
+++ b/Lib/idlelib/config-main.def
@@ -46,8 +46,8 @@
[General]
editor-on-startup= 0
autosave= 0
-print-command-posix=lpr %s
-print-command-win=start /min notepad /p %s
+print-command-posix=lpr %%s
+print-command-win=start /min notepad /p %%s
delete-exitfunc= 1
[EditorWindow]
diff --git a/Lib/idlelib/configDialog.py b/Lib/idlelib/configDialog.py
index 6482119..1f4a3a5 100644
--- a/Lib/idlelib/configDialog.py
+++ b/Lib/idlelib/configDialog.py
@@ -187,7 +187,7 @@ class ConfigDialog(Toplevel):
text=' Highlighting Theme ')
#frameCustom
self.textHighlightSample=Text(frameCustom,relief=SOLID,borderwidth=1,
- font=('courier',12,''),cursor='hand2',width=21,height=10,
+ font=('courier',12,''),cursor='hand2',width=21,height=11,
takefocus=FALSE,highlightthickness=0,wrap=NONE)
text=self.textHighlightSample
text.bind('<Double-Button-1>',lambda e: 'break')
@@ -199,7 +199,7 @@ class ConfigDialog(Toplevel):
("'string'",'string'),('\n var1 = ','normal'),("'selected'",'hilite'),
('\n var2 = ','normal'),("'found'",'hit'),
('\n var3 = ','normal'),('list', 'builtin'), ('(','normal'),
- ('None', 'builtin'),(')\n\n','normal'),
+ ('None', 'keyword'),(')\n\n','normal'),
(' error ','error'),(' ','normal'),('cursor |','cursor'),
('\n ','normal'),('shell','console'),(' ','normal'),('stdout','stdout'),
(' ','normal'),('stderr','stderr'),('\n','normal'))
@@ -821,8 +821,9 @@ class ConfigDialog(Toplevel):
fontWeight=tkFont.BOLD
else:
fontWeight=tkFont.NORMAL
- self.editFont.config(size=self.fontSize.get(),
- weight=fontWeight,family=fontName)
+ newFont = (fontName, self.fontSize.get(), fontWeight)
+ self.labelFontSample.config(font=newFont)
+ self.textHighlightSample.configure(font=newFont)
def SetHighlightTarget(self):
if self.highlightTarget.get()=='Cursor': #bg not possible
@@ -924,7 +925,7 @@ class ConfigDialog(Toplevel):
for font in fonts:
self.listFontName.insert(END,font)
configuredFont=idleConf.GetOption('main','EditorWindow','font',
- default='courier')
+ default='courier')
lc_configuredFont = configuredFont.lower()
self.fontName.set(lc_configuredFont)
lc_fonts = [s.lower() for s in fonts]
@@ -934,13 +935,13 @@ class ConfigDialog(Toplevel):
self.listFontName.select_set(currentFontIndex)
self.listFontName.select_anchor(currentFontIndex)
##font size dropdown
- fontSize=idleConf.GetOption('main','EditorWindow','font-size',
- default='10')
+ fontSize=idleConf.GetOption('main', 'EditorWindow', 'font-size',
+ type='int', default='10')
self.optMenuFontSize.SetMenu(('7','8','9','10','11','12','13','14',
- '16','18','20','22'),fontSize )
+ '16','18','20','22'), fontSize )
##fontWeight
self.fontBold.set(idleConf.GetOption('main','EditorWindow',
- 'font-bold',default=0,type='bool'))
+ 'font-bold',default=0,type='bool'))
##font sample
self.SetFontSample()
@@ -1021,10 +1022,13 @@ class ConfigDialog(Toplevel):
self.autoSave.set(idleConf.GetOption('main', 'General', 'autosave',
default=0, type='bool'))
#initial window size
- self.winWidth.set(idleConf.GetOption('main','EditorWindow','width'))
- self.winHeight.set(idleConf.GetOption('main','EditorWindow','height'))
+ self.winWidth.set(idleConf.GetOption('main','EditorWindow','width',
+ type='int'))
+ self.winHeight.set(idleConf.GetOption('main','EditorWindow','height',
+ type='int'))
#initial paragraph reformat size
- self.paraWidth.set(idleConf.GetOption('main','FormatParagraph','paragraph'))
+ self.paraWidth.set(idleConf.GetOption('main','FormatParagraph','paragraph',
+ type='int'))
# default source encoding
self.encoding.set(idleConf.GetOption('main', 'EditorWindow',
'encoding', default='none'))
diff --git a/Lib/idlelib/configHandler.py b/Lib/idlelib/configHandler.py
index 73b8db5..7fa481d 100644
--- a/Lib/idlelib/configHandler.py
+++ b/Lib/idlelib/configHandler.py
@@ -37,7 +37,7 @@ class IdleConfParser(ConfigParser):
cfgFile - string, fully specified configuration file name
"""
self.file=cfgFile
- ConfigParser.__init__(self,defaults=cfgDefaults)
+ ConfigParser.__init__(self, defaults=cfgDefaults, strict=False)
def Get(self, section, option, type=None, default=None, raw=False):
"""
@@ -237,24 +237,39 @@ class IdleConf:
printed to stderr.
"""
- if self.userCfg[configType].has_option(section,option):
- return self.userCfg[configType].Get(section, option,
- type=type, raw=raw)
- elif self.defaultCfg[configType].has_option(section,option):
- return self.defaultCfg[configType].Get(section, option,
- type=type, raw=raw)
- else: #returning default, print warning
- if warn_on_default:
- warning = ('\n Warning: configHandler.py - IdleConf.GetOption -\n'
- ' problem retrieving configuration option %r\n'
- ' from section %r.\n'
- ' returning default value: %r\n' %
- (option, section, default))
- try:
- sys.stderr.write(warning)
- except IOError:
- pass
- return default
+ try:
+ if self.userCfg[configType].has_option(section,option):
+ return self.userCfg[configType].Get(section, option,
+ type=type, raw=raw)
+ except ValueError:
+ warning = ('\n Warning: configHandler.py - IdleConf.GetOption -\n'
+ ' invalid %r value for configuration option %r\n'
+ ' from section %r: %r\n' %
+ (type, option, section,
+ self.userCfg[configType].Get(section, option,
+ raw=raw)))
+ try:
+ sys.stderr.write(warning)
+ except IOError:
+ pass
+ try:
+ if self.defaultCfg[configType].has_option(section,option):
+ return self.defaultCfg[configType].Get(section, option,
+ type=type, raw=raw)
+ except ValueError:
+ pass
+ #returning default, print warning
+ if warn_on_default:
+ warning = ('\n Warning: configHandler.py - IdleConf.GetOption -\n'
+ ' problem retrieving configuration option %r\n'
+ ' from section %r.\n'
+ ' returning default value: %r\n' %
+ (option, section, default))
+ try:
+ sys.stderr.write(warning)
+ except IOError:
+ pass
+ return default
def SetOption(self, configType, section, option, value):
"""In user's config file, set section's option to value.
@@ -595,7 +610,7 @@ class IdleConf:
'<<replace>>': ['<Control-h>'],
'<<goto-line>>': ['<Alt-g>'],
'<<smart-backspace>>': ['<Key-BackSpace>'],
- '<<newline-and-indent>>': ['<Key-Return> <Key-KP_Enter>'],
+ '<<newline-and-indent>>': ['<Key-Return>', '<Key-KP_Enter>'],
'<<smart-indent>>': ['<Key-Tab>'],
'<<indent-region>>': ['<Control-Key-bracketright>'],
'<<dedent-region>>': ['<Control-Key-bracketleft>'],
diff --git a/Lib/idlelib/extend.txt b/Lib/idlelib/extend.txt
index 165e044..c9cb2e8 100644
--- a/Lib/idlelib/extend.txt
+++ b/Lib/idlelib/extend.txt
@@ -54,7 +54,7 @@ Extensions are not required to define menu entries for all the events they
implement. (They are also not required to create keybindings, but in that
case there must be empty bindings in cofig-extensions.def)
-Here is a complete example example:
+Here is a complete example:
class ZoomHeight:
@@ -72,7 +72,7 @@ class ZoomHeight:
"...Do what you want here..."
The final piece of the puzzle is the file "config-extensions.def", which is
-used to to configure the loading of extensions and to establish key (or, more
+used to configure the loading of extensions and to establish key (or, more
generally, event) bindings to the virtual events defined in the extensions.
See the comments at the top of config-extensions.def for information. It's
diff --git a/Lib/idlelib/help.txt b/Lib/idlelib/help.txt
index 7bfd2ca..815ee40 100644
--- a/Lib/idlelib/help.txt
+++ b/Lib/idlelib/help.txt
@@ -80,7 +80,7 @@ Shell Menu (only in Shell window):
Debug Menu (only in Shell window):
Go to File/Line -- look around the insert point for a filename
- and linenumber, open the file, and show the line
+ and line number, open the file, and show the line
Debugger (toggle) -- Run commands in the shell under the debugger
Stack Viewer -- Show the stack traceback of the last exception
Auto-open Stack Viewer (toggle) -- Open stack viewer on traceback
@@ -92,7 +92,7 @@ Options Menu:
Startup Preferences may be set, and Additional Help
Sources can be specified.
- On MacOS X this menu is not present, use
+ On OS X this menu is not present, use
menu 'IDLE -> Preferences...' instead.
---
Code Context -- Open a pane at the top of the edit window which
@@ -120,6 +120,24 @@ Help Menu:
---
(Additional Help Sources may be added here)
+Edit context menu (Right-click / Control-click on OS X in Edit window):
+
+ Cut -- Copy a selection into system-wide clipboard,
+ then delete the selection
+ Copy -- Copy selection into system-wide clipboard
+ Paste -- Insert system-wide clipboard into window
+ Set Breakpoint -- Sets a breakpoint (when debugger open)
+ Clear Breakpoint -- Clears the breakpoint on that line
+
+Shell context menu (Right-click / Control-click on OS X in Shell window):
+
+ Cut -- Copy a selection into system-wide clipboard,
+ then delete the selection
+ Copy -- Copy selection into system-wide clipboard
+ Paste -- Insert system-wide clipboard into window
+ ---
+ Go to file/line -- Same as in Debug menu
+
** TIPS **
==========
@@ -222,7 +240,7 @@ Python Shell window:
Alt-p retrieves previous command matching what you have typed.
Alt-n retrieves next.
- (These are Control-p, Control-n on the Mac)
+ (These are Control-p, Control-n on OS X)
Return while cursor is on a previous command retrieves that command.
Expand word is also useful to reduce typing.
diff --git a/Lib/idlelib/idlever.py b/Lib/idlelib/idlever.py
index 5e3095b..4a7c336 100644
--- a/Lib/idlelib/idlever.py
+++ b/Lib/idlelib/idlever.py
@@ -1 +1 @@
-IDLE_VERSION = "3.1.5"
+IDLE_VERSION = "3.2.5"
diff --git a/Lib/idlelib/macosxSupport.py b/Lib/idlelib/macosxSupport.py
index da519f7..9690442 100644
--- a/Lib/idlelib/macosxSupport.py
+++ b/Lib/idlelib/macosxSupport.py
@@ -4,6 +4,10 @@ GUI application (as opposed to an X11 application).
"""
import sys
import tkinter
+from os import path
+
+
+_appbundle = None
def runningAsOSXApp():
"""
@@ -11,7 +15,45 @@ def runningAsOSXApp():
If so, assume that Python was built with Aqua Tcl/Tk rather than
X11 Tcl/Tk.
"""
- return (sys.platform == 'darwin' and '.app' in sys.executable)
+ global _appbundle
+ if _appbundle is None:
+ _appbundle = (sys.platform == 'darwin' and '.app' in sys.executable)
+ return _appbundle
+
+_carbonaquatk = None
+
+def isCarbonAquaTk(root):
+ """
+ Returns True if IDLE is using a Carbon Aqua Tk (instead of the
+ newer Cocoa Aqua Tk).
+ """
+ global _carbonaquatk
+ if _carbonaquatk is None:
+ _carbonaquatk = (runningAsOSXApp() and
+ 'aqua' in root.tk.call('tk', 'windowingsystem') and
+ 'AppKit' not in root.tk.call('winfo', 'server', '.'))
+ return _carbonaquatk
+
+def tkVersionWarning(root):
+ """
+ Returns a string warning message if the Tk version in use appears to
+ be one known to cause problems with IDLE.
+ 1. Apple Cocoa-based Tk 8.5.7 shipped with Mac OS X 10.6 is unusable.
+ 2. Apple Cocoa-based Tk 8.5.9 in OS X 10.7 and 10.8 is better but
+ can still crash unexpectedly.
+ """
+
+ if (runningAsOSXApp() and
+ ('AppKit' in root.tk.call('winfo', 'server', '.')) ):
+ patchlevel = root.tk.call('info', 'patchlevel')
+ if patchlevel not in ('8.5.7', '8.5.9'):
+ return False
+ return (r"WARNING: The version of Tcl/Tk ({0}) in use may"
+ r" be unstable.\n"
+ r"Visit http://www.python.org/download/mac/tcltk/"
+ r" for current information.".format(patchlevel))
+ else:
+ return False
def addOpenEventSupport(root, flist):
"""
@@ -73,9 +115,6 @@ def overrideRootMenu(root, flist):
WindowList.add_windows_to_menu(menu)
WindowList.register_callback(postwindowsmenu)
- menudict['application'] = menu = Menu(menubar, name='apple')
- menubar.add_cascade(label='IDLE', menu=menu)
-
def about_dialog(event=None):
from idlelib import aboutDialog
aboutDialog.AboutDialog(root, 'About IDLE')
@@ -91,9 +130,14 @@ def overrideRootMenu(root, flist):
root.instance_dict = flist.inversedict
configDialog.ConfigDialog(root, 'Settings')
+ def help_dialog(event=None):
+ from idlelib import textView
+ fn = path.join(path.abspath(path.dirname(__file__)), 'help.txt')
+ textView.view_file(root, 'Help', fn)
root.bind('<<about-idle>>', about_dialog)
root.bind('<<open-config-dialog>>', config_dialog)
+ root.createcommand('::tk::mac::ShowPreferences', config_dialog)
if flist:
root.bind('<<close-all-windows>>', flist.close_all_callback)
@@ -102,35 +146,29 @@ def overrideRootMenu(root, flist):
# right thing for now.
root.createcommand('exit', flist.close_all_callback)
-
- ###check if Tk version >= 8.4.14; if so, use hard-coded showprefs binding
- tkversion = root.tk.eval('info patchlevel')
- # Note: we cannot check if the string tkversion >= '8.4.14', because
- # the string '8.4.7' is greater than the string '8.4.14'.
- if tuple(map(int, tkversion.split('.'))) >= (8, 4, 14):
- Bindings.menudefs[0] = ('application', [
+ if isCarbonAquaTk(root):
+ # for Carbon AquaTk, replace the default Tk apple menu
+ menudict['application'] = menu = Menu(menubar, name='apple')
+ menubar.add_cascade(label='IDLE', menu=menu)
+ Bindings.menudefs.insert(0,
+ ('application', [
('About IDLE', '<<about-idle>>'),
- None,
- ])
- root.createcommand('::tk::mac::ShowPreferences', config_dialog)
+ None,
+ ]))
+ tkversion = root.tk.eval('info patchlevel')
+ if tuple(map(int, tkversion.split('.'))) < (8, 4, 14):
+ # for earlier AquaTk versions, supply a Preferences menu item
+ Bindings.menudefs[0][1].append(
+ ('_Preferences....', '<<open-config-dialog>>'),
+ )
else:
- for mname, entrylist in Bindings.menudefs:
- menu = menudict.get(mname)
- if not menu:
- continue
- else:
- for entry in entrylist:
- if not entry:
- menu.add_separator()
- else:
- label, eventname = entry
- underline, label = prepstr(label)
- accelerator = get_accelerator(Bindings.default_keydefs,
- eventname)
- def command(text=root, eventname=eventname):
- text.event_generate(eventname)
- menu.add_command(label=label, underline=underline,
- command=command, accelerator=accelerator)
+ # assume Cocoa AquaTk
+ # replace default About dialog with About IDLE one
+ root.createcommand('tkAboutDialog', about_dialog)
+ # replace default "Help" item in Help menu
+ root.createcommand('::tk::mac::ShowHelp', help_dialog)
+ # remove redundant "IDLE Help" from menu
+ del Bindings.menudefs[-1][1][0]
def setupApp(root, flist):
"""
diff --git a/Lib/idlelib/rpc.py b/Lib/idlelib/rpc.py
index 0c56ccd..29e687e 100644
--- a/Lib/idlelib/rpc.py
+++ b/Lib/idlelib/rpc.py
@@ -2,7 +2,7 @@
For security reasons, GvR requested that Idle's Python execution server process
connect to the Idle process, which listens for the connection. Since Idle has
-has only one client per server, this was not a limitation.
+only one client per server, this was not a limitation.
+---------------------------------+ +-------------+
| socketserver.BaseRequestHandler | | SocketIO |
@@ -570,7 +570,7 @@ def _getmethods(obj, methods):
# Adds names to dictionary argument 'methods'
for name in dir(obj):
attr = getattr(obj, name)
- if hasattr(attr, '__call__'):
+ if callable(attr):
methods[name] = 1
if isinstance(obj, type):
for super in obj.__bases__:
@@ -579,7 +579,7 @@ def _getmethods(obj, methods):
def _getattributes(obj, attributes):
for name in dir(obj):
attr = getattr(obj, name)
- if not hasattr(attr, '__call__'):
+ if not callable(attr):
attributes[name] = 1
class MethodProxy(object):
diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py
index fd2cc09..7d0941e 100644
--- a/Lib/idlelib/run.py
+++ b/Lib/idlelib/run.py
@@ -1,4 +1,5 @@
import sys
+import io
import linecache
import time
import socket
@@ -14,6 +15,8 @@ from idlelib import RemoteDebugger
from idlelib import RemoteObjectBrowser
from idlelib import StackViewer
from idlelib import rpc
+from idlelib import PyShell
+from idlelib import IOBinding
import __main__
@@ -25,12 +28,13 @@ except ImportError:
pass
else:
def idle_formatwarning_subproc(message, category, filename, lineno,
- file=None, line=None):
+ line=None):
"""Format warnings the IDLE way"""
s = "\nWarning (from warnings module):\n"
s += ' File \"%s\", line %s\n' % (filename, lineno)
- line = linecache.getline(filename, lineno).strip() \
- if line is None else line
+ if line is None:
+ line = linecache.getline(filename, lineno)
+ line = line.strip()
if line:
s += " %s\n" % line
s += "%s: %s\n" % (category.__name__, message)
@@ -156,15 +160,32 @@ def print_exception():
efile = sys.stderr
typ, val, tb = excinfo = sys.exc_info()
sys.last_type, sys.last_value, sys.last_traceback = excinfo
- tbe = traceback.extract_tb(tb)
- print('Traceback (most recent call last):', file=efile)
- exclude = ("run.py", "rpc.py", "threading.py", "queue.py",
- "RemoteDebugger.py", "bdb.py")
- cleanup_traceback(tbe, exclude)
- traceback.print_list(tbe, file=efile)
- lines = traceback.format_exception_only(typ, val)
- for line in lines:
- print(line, end='', file=efile)
+ seen = set()
+
+ def print_exc(typ, exc, tb):
+ seen.add(exc)
+ context = exc.__context__
+ cause = exc.__cause__
+ if cause is not None and cause not in seen:
+ print_exc(type(cause), cause, cause.__traceback__)
+ print("\nThe above exception was the direct cause "
+ "of the following exception:\n", file=efile)
+ elif context is not None and context not in seen:
+ print_exc(type(context), context, context.__traceback__)
+ print("\nDuring handling of the above exception, "
+ "another exception occurred:\n", file=efile)
+ if tb:
+ tbe = traceback.extract_tb(tb)
+ print('Traceback (most recent call last):', file=efile)
+ exclude = ("run.py", "rpc.py", "threading.py", "queue.py",
+ "RemoteDebugger.py", "bdb.py")
+ cleanup_traceback(tbe, exclude)
+ traceback.print_list(tbe, file=efile)
+ lines = traceback.format_exception_only(typ, exc)
+ for line in lines:
+ print(line, end='', file=efile)
+
+ print_exc(typ, val, tb)
def cleanup_traceback(tb, exclude):
"Remove excluded traces from beginning/end of tb; get cached lines"
@@ -243,22 +264,23 @@ class MyRPCServer(rpc.RPCServer):
quitting = True
thread.interrupt_main()
-
class MyHandler(rpc.RPCHandler):
def handle(self):
"""Override base method"""
executive = Executive(self)
self.register("exec", executive)
- sys.stdin = self.console = self.get_remote_proxy("stdin")
- sys.stdout = self.get_remote_proxy("stdout")
- sys.stderr = self.get_remote_proxy("stderr")
+ self.console = self.get_remote_proxy("console")
+ sys.stdin = PyShell.PseudoInputFile(self.console, "stdin",
+ IOBinding.encoding)
+ sys.stdout = PyShell.PseudoOutputFile(self.console, "stdout",
+ IOBinding.encoding)
+ sys.stderr = PyShell.PseudoOutputFile(self.console, "stderr",
+ IOBinding.encoding)
+
# page help() text to shell.
import pydoc # import must be done here to capture i/o binding
pydoc.pager = pydoc.plainpager
- from idlelib import IOBinding
- sys.stdin.encoding = sys.stdout.encoding = \
- sys.stderr.encoding = IOBinding.encoding
self.interp = self.get_remote_proxy("interp")
rpc.RPCHandler.getresponse(self, myseq=None, wait=0.05)
diff --git a/Lib/idlelib/tabbedpages.py b/Lib/idlelib/tabbedpages.py
index f791702..2557732 100644
--- a/Lib/idlelib/tabbedpages.py
+++ b/Lib/idlelib/tabbedpages.py
@@ -78,7 +78,7 @@ class TabSet(Frame):
def remove_tab(self, tab_name):
"""Remove the tab named <tab_name>"""
if not tab_name in self._tab_names:
- raise KeyError("No such Tab: '%s" % page_name)
+ raise KeyError("No such Tab: '%s" % tab_name)
self._tab_names.remove(tab_name)
self._arrange_tabs()
@@ -88,7 +88,7 @@ class TabSet(Frame):
if tab_name == self._selected_tab:
return
if tab_name is not None and tab_name not in self._tabs:
- raise KeyError("No such Tab: '%s" % page_name)
+ raise KeyError("No such Tab: '%s" % tab_name)
# deselect the current selected tab
if self._selected_tab is not None:
diff --git a/Lib/idlelib/textView.py b/Lib/idlelib/textView.py
index e5c551a..1eaa464 100644
--- a/Lib/idlelib/textView.py
+++ b/Lib/idlelib/textView.py
@@ -9,7 +9,7 @@ class TextViewer(Toplevel):
"""A simple text viewer dialog for IDLE
"""
- def __init__(self, parent, title, text):
+ def __init__(self, parent, title, text, modal=True):
"""Show the given text in a scrollable window with a 'close' button
"""
@@ -24,8 +24,6 @@ class TextViewer(Toplevel):
self.CreateWidgets()
self.title(title)
- self.transient(parent)
- self.grab_set()
self.protocol("WM_DELETE_WINDOW", self.Ok)
self.parent = parent
self.textView.focus_set()
@@ -34,7 +32,11 @@ class TextViewer(Toplevel):
self.bind('<Escape>',self.Ok) #dismiss dialog
self.textView.insert(0.0, text)
self.textView.config(state=DISABLED)
- self.wait_window()
+
+ if modal:
+ self.transient(parent)
+ self.grab_set()
+ self.wait_window()
def CreateWidgets(self):
frameText = Frame(self, relief=SUNKEN, height=700)
@@ -57,19 +59,20 @@ class TextViewer(Toplevel):
self.destroy()
-def view_text(parent, title, text):
- TextViewer(parent, title, text)
+def view_text(parent, title, text, modal=True):
+ return TextViewer(parent, title, text, modal)
-def view_file(parent, title, filename, encoding=None):
+def view_file(parent, title, filename, encoding=None, modal=True):
try:
- textFile = open(filename, 'r', encoding=encoding)
+ with open(filename, 'r', encoding=encoding) as file:
+ contents = file.read()
except IOError:
import tkinter.messagebox as tkMessageBox
tkMessageBox.showerror(title='File Load Error',
message='Unable to load file %r .' % filename,
parent=parent)
else:
- return view_text(parent, title, textFile.read())
+ return view_text(parent, title, contents, modal)
if __name__ == '__main__':
@@ -79,11 +82,15 @@ if __name__ == '__main__':
filename = './textView.py'
text = file(filename, 'r').read()
btn1 = Button(root, text='view_text',
- command=lambda:view_text(root, 'view_text', text))
+ command=lambda:view_text(root, 'view_text', text))
btn1.pack(side=LEFT)
btn2 = Button(root, text='view_file',
command=lambda:view_file(root, 'view_file', filename))
btn2.pack(side=LEFT)
+ btn3 = Button(root, text='nonmodal view_text',
+ command=lambda:view_text(root, 'nonmodal view_text', text,
+ modal=False))
+ btn3.pack(side=LEFT)
close = Button(root, text='Close', command=root.destroy)
close.pack(side=RIGHT)
root.mainloop()