diff options
Diffstat (limited to 'Lib/idlelib')
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() |