diff --git a/Doc/library/idle.rst b/Doc/library/idle.rst index a945b6d771225b..4f51f3f0ac2eaa 100644 --- a/Doc/library/idle.rst +++ b/Doc/library/idle.rst @@ -672,23 +672,6 @@ Extensions IDLE contains an extension facility. Preferences for extensions can be changed with Configure Extensions. See the beginning of config-extensions.def -in the idlelib directory for further information. The default extensions -are currently: - -* FormatParagraph - -* AutoExpand - -* ZoomHeight - -* ScriptBinding - -* CallTips - -* ParenMatch - -* AutoComplete - -* CodeContext - -* RstripExtension +in the idlelib directory for further information. The only current default +extension is zoomheight. It exists as an extension primarily to be an example +and for testing purposes. \ No newline at end of file diff --git a/Lib/idlelib/autocomplete.py b/Lib/idlelib/autocomplete.py index cd212ccb143a3c..edf445f08b5869 100644 --- a/Lib/idlelib/autocomplete.py +++ b/Lib/idlelib/autocomplete.py @@ -1,7 +1,7 @@ -"""autocomplete.py - An IDLE extension for automatically completing names. +"""Complete either attribute names or file names. -This extension can complete either attribute names or file names. It can pop -a window with all available names, for the user to select from. +Either on demand or after a user-selected delay after a key character, +pop up a list of candidates. """ import os import string @@ -27,18 +27,9 @@ class AutoComplete: - menudefs = [ - ('edit', [ - ("Show Completions", "<>"), - ]) - ] - - popupwait = idleConf.GetOption("extensions", "AutoComplete", - "popupwait", type="int", default=0) - def __init__(self, editwin=None): self.editwin = editwin - if editwin is not None: # not in subprocess or test + if editwin is not None: # not in subprocess or test self.text = editwin.text self.autocompletewindow = None # id of delayed call, and the index of the text insert when @@ -47,6 +38,11 @@ def __init__(self, editwin=None): self._delayed_completion_id = None self._delayed_completion_index = None + @classmethod + def reload(cls): + cls.popupwait = idleConf.GetOption( + "extensions", "AutoComplete", "popupwait", type="int", default=0) + def _make_autocomplete_window(self): return autocomplete_w.AutoCompleteWindow(self.text) @@ -228,6 +224,9 @@ def get_entity(self, name): return eval(name, namespace) +AutoComplete.reload() + + if __name__ == '__main__': from unittest import main main('idlelib.idle_test.test_autocomplete', verbosity=2) diff --git a/Lib/idlelib/autoexpand.py b/Lib/idlelib/autoexpand.py index 6b46bee69c95f5..42e733a1a9e735 100644 --- a/Lib/idlelib/autoexpand.py +++ b/Lib/idlelib/autoexpand.py @@ -10,23 +10,13 @@ place before requesting the next selection causes AutoExpand to reset its state. -This is an extension file and there is only one instance of AutoExpand. +There is only one instance of Autoexpand. ''' import re import string -###$ event <> -###$ win -###$ unix class AutoExpand: - - menudefs = [ - ('edit', [ - ('E_xpand Word', '<>'), - ]), - ] - wordchars = string.ascii_letters + string.digits + "_" def __init__(self, editwin): @@ -100,6 +90,7 @@ def getprevword(self): i = i-1 return line[i:] + if __name__ == '__main__': import unittest unittest.main('idlelib.idle_test.test_autoexpand', verbosity=2) diff --git a/Lib/idlelib/calltips.py b/Lib/idlelib/calltips.py index 49625eac158c03..ec8f6169895b88 100644 --- a/Lib/idlelib/calltips.py +++ b/Lib/idlelib/calltips.py @@ -1,9 +1,8 @@ -"""calltips.py - An IDLE Extension to Jog Your Memory +"""Pop up a reminder of how to call a function. Call Tips are floating windows which display function, class, and method parameter and docstring information when you type an opening parenthesis, and which disappear when you type a closing parenthesis. - """ import inspect import re @@ -15,13 +14,8 @@ from idlelib.hyperparser import HyperParser import __main__ -class CallTips: - menudefs = [ - ('edit', [ - ("Show call tip", "<>"), - ]) - ] +class CallTips: def __init__(self, editwin=None): if editwin is None: # subprocess and test @@ -103,6 +97,7 @@ def fetch_tip(self, expression): else: 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__. @@ -126,7 +121,6 @@ def get_entity(expression): _invalid_method = "invalid method signature" _argument_positional = "\n['/' marks preceding arguments as positional-only]\n" - def get_argspec(ob): '''Return a string describing the signature of a callable object, or ''. diff --git a/Lib/idlelib/codecontext.py b/Lib/idlelib/codecontext.py index 09dc078d63f191..779b5b88cf35fa 100644 --- a/Lib/idlelib/codecontext.py +++ b/Lib/idlelib/codecontext.py @@ -1,4 +1,4 @@ -"""codecontext - Extension to display the block context above the edit window +"""codecontext - display the block context above the edit window Once code has scrolled off the top of a window, it can be difficult to determine which block you are in. This extension implements a pane at the top @@ -25,10 +25,8 @@ getspacesfirstword =\ lambda s, c=re.compile(r"^(\s*)(\w*)"): c.match(s).groups() + class CodeContext: - menudefs = [('options', [('!Code Conte_xt', '<>')])] - context_depth = idleConf.GetOption("extensions", "CodeContext", - "numlines", type="int", default=3) bgcolor = idleConf.GetOption("extensions", "CodeContext", "bgcolor", type="str", default="LightGray") fgcolor = idleConf.GetOption("extensions", "CodeContext", @@ -45,15 +43,20 @@ def __init__(self, editwin): # starts the toplevel 'block' of the module. self.info = [(0, -1, "", False)] self.topvisible = 1 - visible = idleConf.GetOption("extensions", "CodeContext", - "visible", type="bool", default=False) - if visible: - self.toggle_code_context_event() - self.editwin.setvar('<>', True) + self.reload() # Start two update cycles, one for context lines, one for font changes. self.text.after(UPDATEINTERVAL, self.timer_event) self.text.after(FONTUPDATEINTERVAL, self.font_timer_event) + @classmethod + def reload(cls): + cls.context_depth = idleConf.GetOption("extensions", "CodeContext", + "numlines", type="int", default=3) + cls.bgcolor = idleConf.GetOption("extensions", "CodeContext", + "bgcolor", type="str", default="LightGray") + cls.fgcolor = idleConf.GetOption("extensions", "CodeContext", + "fgcolor", type="str", default="Black") + def toggle_code_context_event(self, event=None): if not self.label: # Calculate the border width and horizontal padding required to @@ -86,7 +89,7 @@ def toggle_code_context_event(self, event=None): else: self.label.destroy() self.label = None - idleConf.SetOption("extensions", "CodeContext", "visible", + idleConf.SetOption("main", "Theme", "contexton", str(self.label is not None)) idleConf.SaveUserCfgFiles() return "break" @@ -177,3 +180,6 @@ def font_timer_event(self): self.textfont = newtextfont self.label["font"] = self.textfont self.text.after(FONTUPDATEINTERVAL, self.font_timer_event) + + +CodeContext.reload() diff --git a/Lib/idlelib/config-extensions.def b/Lib/idlelib/config-extensions.def index a24b8c9316ba07..e8d417bac0d497 100644 --- a/Lib/idlelib/config-extensions.def +++ b/Lib/idlelib/config-extensions.def @@ -1,5 +1,25 @@ # config-extensions.def # +# The following sections are for features that are no longer extensions. +# Their options values are left here for back-compatibility. + +[AutoComplete] +popupwait= 2000 + +[CodeContext] +numlines= 3 +visible= False +bgcolor= LightGray +fgcolor= Black + +[FormatParagraph] +max-width= 72 + +[ParenMatch] +style= expression +flash-delay= 500 +bell= True + # IDLE reads several config files to determine user preferences. This # file is the default configuration file for IDLE extensions settings. # @@ -19,7 +39,7 @@ # extension that may be sensibly re-configured. # # If there are no keybindings for a menus' virtual events, include lines -# like <>= (See [CodeContext], below.) +# like <>=. # # Currently it is necessary to manually modify this file to change # extension key bindings and default values. To customize, create @@ -32,68 +52,14 @@ # See config-keys.def for notes on specifying keys and extend.txt for # information on creating IDLE extensions. -[AutoComplete] -enable=True -popupwait=2000 -[AutoComplete_cfgBindings] -force-open-completions= -[AutoComplete_bindings] -autocomplete= -try-open-completions= - -[AutoExpand] -enable=True -[AutoExpand_cfgBindings] -expand-word= - -[CallTips] -enable=True -[CallTips_cfgBindings] -force-open-calltip= -[CallTips_bindings] -try-open-calltip= -refresh-calltip= - -[CodeContext] -enable=True -enable_shell=False -numlines=3 -visible=False -bgcolor=LightGray -fgcolor=Black -[CodeContext_bindings] -toggle-code-context= - -[FormatParagraph] -enable=True -max-width=72 -[FormatParagraph_cfgBindings] -format-paragraph= - -[ParenMatch] -enable=True -style= expression -flash-delay= 500 -bell=True -[ParenMatch_cfgBindings] -flash-paren= -[ParenMatch_bindings] -paren-closed= - -[RstripExtension] -enable=True -enable_shell=False -enable_editor=True - -[ScriptBinding] -enable=True -enable_shell=False -enable_editor=True -[ScriptBinding_cfgBindings] -run-module= -check-module= - -[ZoomHeight] -enable=True -[ZoomHeight_cfgBindings] -zoom-height= +# A fake extension for testing and example purposes. When enabled and +# invoked, inserts or deletes z-text at beginning of every line. +[ZzDummy] +enable= True +enable_shell = False +enable_editor = True +z-text= Z +[ZzDummy_cfgBindings] +z-in= +[ZzDummy_bindings] +z-out= diff --git a/Lib/idlelib/config-keys.def b/Lib/idlelib/config-keys.def index 64788f9adf015c..fd235194dfc950 100644 --- a/Lib/idlelib/config-keys.def +++ b/Lib/idlelib/config-keys.def @@ -57,6 +57,14 @@ toggle-tabs= change-indentwidth= del-word-left= del-word-right= +force-open-completions= +expand-word= +force-open-calltip= +format-paragraph= +flash-paren= +run-module= +check-module= +zoom-height= [IDLE Classic Unix] copy= @@ -108,6 +116,14 @@ toggle-tabs= change-indentwidth= del-word-left= del-word-right= +force-open-completions= +expand-word= +force-open-calltip= +format-paragraph= +flash-paren= +run-module= +check-module= +zoom-height= [IDLE Modern Unix] copy = @@ -159,6 +175,14 @@ toggle-tabs = change-indentwidth = del-word-left = del-word-right = +force-open-completions= +expand-word= +force-open-calltip= +format-paragraph= +flash-paren= +run-module= +check-module= +zoom-height= [IDLE Classic Mac] copy= @@ -210,6 +234,14 @@ toggle-tabs= change-indentwidth= del-word-left= del-word-right= +force-open-completions= +expand-word= +force-open-calltip= +format-paragraph= +flash-paren= +run-module= +check-module= +zoom-height= [IDLE Classic OSX] toggle-tabs = @@ -262,4 +294,11 @@ python-context-help = save-copy-of-window-as-file = open-window-from-file = python-docs = - +force-open-completions= +expand-word= +force-open-calltip= +format-paragraph= +flash-paren= +run-module= +check-module= +zoom-height= diff --git a/Lib/idlelib/config.py b/Lib/idlelib/config.py index 63d9a44234560b..c3e57bc692d801 100644 --- a/Lib/idlelib/config.py +++ b/Lib/idlelib/config.py @@ -359,7 +359,8 @@ def GetThemeDict(self, type, themeName): 'stderr-foreground':'#000000', 'stderr-background':'#ffffff', 'console-foreground':'#000000', - 'console-background':'#ffffff' } + 'console-background':'#ffffff', + } for element in theme: if not cfgParser.has_option(themeName, element): # Print warning that will return a default color @@ -443,6 +444,11 @@ def GetExtensions(self, active_only=True, for extn in userExtns: if extn not in extns: #user has added own extension extns.append(extn) + for extn in ('AutoComplete','CodeContext', + 'FormatParagraph','ParenMatch'): + extns.remove(extn) + # specific exclusions because we are storing config for mainlined old + # extensions in config-extensions.def for backward compatibility if active_only: activeExtns = [] for extn in extns: @@ -594,7 +600,12 @@ def IsCoreBinding(self, virtualEvent): return ('<<'+virtualEvent+'>>') in self.GetCoreKeys() # TODO make keyBindins a file or class attribute used for test above -# and copied in function below +# and copied in function below. + + former_extension_events = { # Those with user-configurable keys. + '<>', '<>', + '<>', '<>', '<>', + '<>', '<>', '<>'} def GetCoreKeys(self, keySetName=None): """Return dict of core virtual-key keybindings for keySetName. @@ -654,8 +665,17 @@ def GetCoreKeys(self, keySetName=None): '<>': [''], '<>': [''], '<>': [''], - '<>': [''] + '<>': [''], + '<>': [''], + '<>': [''], + '<>': [''], + '<>': [''], + '<>': [''], + '<>': [''], + '<>': [''], + '<>': [''], } + if keySetName: if not (self.userCfg['keys'].has_section(keySetName) or self.defaultCfg['keys'].has_section(keySetName)): @@ -670,7 +690,8 @@ def GetCoreKeys(self, keySetName=None): binding = self.GetKeyBinding(keySetName, event) if binding: keyBindings[event] = binding - else: #we are going to return a default, print warning + # Otherwise return default in keyBindings. + elif event not in self.former_extension_events: warning = ( '\n Warning: config.py - IdleConf.GetCoreKeys -\n' ' problem retrieving key binding for event %r\n' diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py index 4ef6197e7140c9..604719f0453b5c 100644 --- a/Lib/idlelib/configdialog.py +++ b/Lib/idlelib/configdialog.py @@ -15,7 +15,7 @@ NONE, BOTH, X, Y, W, E, EW, NS, NSEW, NW, HORIZONTAL, VERTICAL, ANCHOR, ACTIVE, END) from tkinter.ttk import (Button, Checkbutton, Entry, Frame, Label, LabelFrame, - Notebook, Radiobutton, Scrollbar, Style) + OptionMenu, Notebook, Radiobutton, Scrollbar, Style) import tkinter.colorchooser as tkColorChooser import tkinter.font as tkFont from tkinter import messagebox @@ -198,7 +198,6 @@ def help(self): def deactivate_current_config(self): """Remove current key bindings. - Iterate over window instances defined in parent and remove the keybindings. """ @@ -288,6 +287,7 @@ def load_extensions(self): "Fill self.extensions with data from the default and user configs." self.extensions = {} for ext_name in idleConf.GetExtensions(active_only=False): + # Former built-in extensions are already filtered out. self.extensions[ext_name] = [] for ext_name in self.extensions: @@ -796,7 +796,8 @@ def create_page_highlight(self): takefocus=FALSE, highlightthickness=0, wrap=NONE) text.bind('', lambda e: 'break') text.bind('', lambda e: 'break') - text_and_tags=(('\n', 'normal'), + text_and_tags=( + ('\n', 'normal'), ('#you can click here', 'comment'), ('\n', 'normal'), ('#to choose items', 'comment'), ('\n', 'normal'), ('def', 'keyword'), (' ', 'normal'), @@ -858,11 +859,10 @@ def tem(event, elem=element): frame_theme, text='Delete Custom Theme', command=self.delete_custom) self.theme_message = Label(frame_theme, borderwidth=2) - # Pack widgets: # body. frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH) - frame_theme.pack(side=LEFT, padx=5, pady=5, fill=Y) + frame_theme.pack(side=TOP, padx=5, pady=5, fill=X) # frame_custom. self.frame_color_set.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=X) frame_fg_bg_toggle.pack(side=TOP, padx=5, pady=0) @@ -1764,15 +1764,30 @@ def create_page_general(self): (*)helplist: ListBox scroll_helplist: Scrollbar """ + # Integer values need StringVar because int('') raises. self.startup_edit = tracers.add( IntVar(self), ('main', 'General', 'editor-on-startup')) - self.autosave = tracers.add( - IntVar(self), ('main', 'General', 'autosave')) self.win_width = tracers.add( StringVar(self), ('main', 'EditorWindow', 'width')) self.win_height = tracers.add( StringVar(self), ('main', 'EditorWindow', 'height')) + self.autocomplete_wait = tracers.add( + StringVar(self), ('extensions', 'AutoComplete', 'popupwait')) + self.paren_style = tracers.add( + StringVar(self), ('extensions', 'ParenMatch', 'style')) + self.flash_delay = tracers.add( + StringVar(self), ('extensions', 'ParenMatch', 'flash-delay')) + self.paren_bell = tracers.add( + BooleanVar(self), ('extensions', 'ParenMatch', 'bell')) + + self.autosave = tracers.add( + IntVar(self), ('main', 'General', 'autosave')) + self.format_width = tracers.add( + StringVar(self), ('extensions', 'FormatParagraph', 'max-width')) + self.context_lines = tracers.add( + StringVar(self), ('extensions', 'CodeContext', 'numlines')) + # Create widgets: # Section frames. frame_window = LabelFrame(self, borderwidth=2, relief=GROOVE, text=' Window Preferences') @@ -1790,7 +1805,7 @@ def create_page_general(self): frame_run, variable=self.startup_edit, value=0, text='Open Shell Window') - frame_win_size = Frame(frame_window, borderwidth=0,) + frame_win_size = Frame(frame_window, borderwidth=0) win_size_title = Label( frame_win_size, text='Initial Window Size (in characters)') win_width_title = Label(frame_win_size, text='Width') @@ -1800,6 +1815,26 @@ def create_page_general(self): self.win_height_int = Entry( frame_win_size, textvariable=self.win_height, width=3) + frame_autocomplete = Frame(frame_window, borderwidth=0,) + auto_wait_title = Label(frame_autocomplete, + text='Completions Popup Wait (milliseconds)') + self.auto_wait_int = Entry(frame_autocomplete, width=6, + textvariable=self.autocomplete_wait) + + frame_paren1 = Frame(frame_window, borderwidth=0) + paren_style_title = Label(frame_paren1, text='Paren Match Style') + self.paren_style_type = OptionMenu( + frame_paren1, self.paren_style, 'expression', + "opener","parens","expression") + frame_paren2 = Frame(frame_window, borderwidth=0) + paren_time_title = Label( + frame_paren2, text='Time Match Displayed (milliseconds)\n' + '(0 is until next input)') + self.paren_flash_time = Entry( + frame_paren2, textvariable=self.flash_delay, width=6) + self.bell_on = Checkbutton( + frame_paren2, text="Bell on Mismatch", variable=self.paren_bell) + # Frame_editor. frame_save = Frame(frame_editor, borderwidth=0) run_save_title = Label(frame_save, text='At Start of Run (F5) ') @@ -1810,6 +1845,18 @@ def create_page_general(self): frame_save, variable=self.autosave, value=1, text='No Prompt') + frame_format = Frame(frame_editor, borderwidth=0) + format_width_title = Label(frame_format, + text='Format Paragraph Max Width') + self.format_width_int = Entry( + frame_format, textvariable=self.format_width, width=4) + + frame_context = Frame(frame_editor, borderwidth=0) + context_title = Label(frame_context, text='Context Lines :') + self.context_int = Entry( + frame_context, textvariable=self.context_lines, width=3) + + # frame_help. frame_helplist = Frame(frame_help) frame_helplist_buttons = Frame(frame_helplist) @@ -1847,11 +1894,33 @@ def create_page_general(self): win_height_title.pack(side=RIGHT, anchor=E, pady=5) self.win_width_int.pack(side=RIGHT, anchor=E, padx=10, pady=5) win_width_title.pack(side=RIGHT, anchor=E, pady=5) + # frame_autocomplete. + frame_autocomplete.pack(side=TOP, padx=5, pady=0, fill=X) + auto_wait_title.pack(side=LEFT, anchor=W, padx=5, pady=5) + self.auto_wait_int.pack(side=TOP, padx=10, pady=5) + # frame_paren. + frame_paren1.pack(side=TOP, padx=5, pady=0, fill=X) + paren_style_title.pack(side=LEFT, anchor=W, padx=5, pady=5) + self.paren_style_type.pack(side=TOP, padx=10, pady=5) + frame_paren2.pack(side=TOP, padx=5, pady=0, fill=X) + paren_time_title.pack(side=LEFT, anchor=W, padx=5) + self.bell_on.pack(side=RIGHT, anchor=E, padx=15, pady=5) + self.paren_flash_time.pack(side=TOP, anchor=W, padx=15, pady=5) + # frame_save. frame_save.pack(side=TOP, padx=5, pady=0, fill=X) run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5) self.save_auto_on.pack(side=RIGHT, anchor=W, padx=5, pady=5) self.save_ask_on.pack(side=RIGHT, anchor=W, padx=5, pady=5) + # frame_format. + frame_format.pack(side=TOP, padx=5, pady=0, fill=X) + format_width_title.pack(side=LEFT, anchor=W, padx=5, pady=5) + self.format_width_int.pack(side=TOP, padx=10, pady=5) + # frame_context. + frame_context.pack(side=TOP, padx=5, pady=0, fill=X) + context_title.pack(side=LEFT, anchor=W, padx=5, pady=5) + self.context_int.pack(side=TOP, padx=5, pady=5) + # frame_help. frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y) frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH) @@ -1863,17 +1932,30 @@ def create_page_general(self): def load_general_cfg(self): "Load current configuration settings for the general options." - # Set startup state. + # Set variables for all windows. self.startup_edit.set(idleConf.GetOption( - 'main', 'General', 'editor-on-startup', default=0, type='bool')) - # Set autosave state. - self.autosave.set(idleConf.GetOption( - 'main', 'General', 'autosave', default=0, type='bool')) - # Set initial window size. + 'main', 'General', 'editor-on-startup', type='bool')) self.win_width.set(idleConf.GetOption( 'main', 'EditorWindow', 'width', type='int')) self.win_height.set(idleConf.GetOption( 'main', 'EditorWindow', 'height', type='int')) + self.autocomplete_wait.set(idleConf.GetOption( + 'extensions', 'AutoComplete', 'popupwait', type='int')) + self.paren_style.set(idleConf.GetOption( + 'extensions', 'ParenMatch', 'style')) + self.flash_delay.set(idleConf.GetOption( + 'extensions', 'ParenMatch', 'flash-delay', type='int')) + self.paren_bell.set(idleConf.GetOption( + 'extensions', 'ParenMatch', 'bell')) + + # Set variables for editor windows. + self.autosave.set(idleConf.GetOption( + 'main', 'General', 'autosave', default=0, type='bool')) + self.format_width.set(idleConf.GetOption( + 'extensions', 'FormatParagraph', 'max-width', type='int')) + self.context_lines.set(idleConf.GetOption( + 'extensions', 'CodeContext', 'numlines', type='int')) + # Set additional help sources. self.user_helplist = idleConf.GetAllExtraHelpSourcesList() self.helplist.delete(0, 'end') @@ -2034,10 +2116,10 @@ def detach(self): be used with older IDLE releases if it is saved as a custom key set, with a different name. ''', - 'Extensions': ''' -Extensions: + 'General': ''' +General: -Autocomplete: Popupwait is milleseconds to wait after key char, without +AutoComplete: Popupwait is milleseconds to wait after key char, without cursor movement, before popping up completion box. Key char is '.' after identifier or a '/' (or '\\' on Windows) within a string. diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index 43b105f726573d..855d375055653a 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -31,7 +31,6 @@ TK_TABWIDTH_DEFAULT = 8 _py_version = ' (%s)' % platform.python_version() - def _sphinx_version(): "Format sys.version_info to produce the Sphinx version string used to install the chm docs" major, minor, micro, level, serial = sys.version_info @@ -52,11 +51,22 @@ class EditorWindow(object): from idlelib import mainmenu from tkinter import Toplevel from idlelib.statusbar import MultiStatusBar + from idlelib.autocomplete import AutoComplete + from idlelib.autoexpand import AutoExpand + from idlelib.calltips import CallTips + from idlelib.codecontext import CodeContext + from idlelib.paragraph import FormatParagraph + from idlelib.parenmatch import ParenMatch + from idlelib.rstrip import RstripExtension + from idlelib.zoomheight import ZoomHeight filesystemencoding = sys.getfilesystemencoding() # for file names help_url = None def __init__(self, flist=None, filename=None, key=None, root=None): + # Delay import: runscript imports pyshell imports EditorWindow. + from idlelib.runscript import ScriptBinding + if EditorWindow.help_url is None: dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html') if sys.platform.count('linux'): @@ -84,7 +94,8 @@ def __init__(self, flist=None, filename=None, key=None, root=None): # Safari requires real file:-URLs EditorWindow.help_url = 'file://' + EditorWindow.help_url else: - EditorWindow.help_url = "https://docs.python.org/%d.%d/" % sys.version_info[:2] + EditorWindow.help_url = ("https://docs.python.org/%d.%d/" + % sys.version_info[:2]) self.flist = flist root = root or flist.root self.root = root @@ -270,6 +281,43 @@ def __init__(self, flist=None, filename=None, key=None, root=None): self.askinteger = tkSimpleDialog.askinteger self.showerror = tkMessageBox.showerror + # Add pseudoevents for former extension fixed keys. + # (This probably needs to be done once in the process.) + text.event_add('<>', '') + text.event_add('<>', '', + '', '') + text.event_add('<>', '') + text.event_add('<>', '') + text.event_add('<>', '', + '', '') + + # Former extension bindings depends on frame.text being packed + # (called from self.ResetColorizer()). + autocomplete = self.AutoComplete(self) + text.bind("<>", autocomplete.autocomplete_event) + text.bind("<>", + autocomplete.try_open_completions_event) + text.bind("<>", + autocomplete.force_open_completions_event) + text.bind("<>", self.AutoExpand(self).expand_word_event) + text.bind("<>", + self.FormatParagraph(self).format_paragraph_event) + parenmatch = self.ParenMatch(self) + text.bind("<>", parenmatch.flash_paren_event) + text.bind("<>", parenmatch.paren_closed_event) + scriptbinding = ScriptBinding(self) + text.bind("<>", scriptbinding.check_module_event) + text.bind("<>", scriptbinding.run_module_event) + text.bind("<>", self.RstripExtension(self).do_rstrip) + calltips = self.CallTips(self) + text.bind("<>", calltips.try_open_calltip_event) + #refresh-calltips must come after paren-closed to work right + text.bind("<>", calltips.refresh_calltip_event) + text.bind("<>", calltips.force_open_calltip_event) + text.bind("<>", self.ZoomHeight(self).zoom_height_event) + text.bind("<>", + self.CodeContext(self).toggle_code_context_event) + def _filename_to_unicode(self, filename): """Return filename as BMP unicode so diplayable in Tk.""" # Decode bytes to unicode. @@ -981,16 +1029,8 @@ def load_standard_extensions(self): def get_standard_extension_names(self): return idleConf.GetExtensions(editor_only=True) - extfiles = { # map config-extension section names to new file names - 'AutoComplete': 'autocomplete', - 'AutoExpand': 'autoexpand', - 'CallTips': 'calltips', - 'CodeContext': 'codecontext', - 'FormatParagraph': 'paragraph', - 'ParenMatch': 'parenmatch', - 'RstripExtension': 'rstrip', - 'ScriptBinding': 'runscript', - 'ZoomHeight': 'zoomheight', + extfiles = { # Map built-in config-extension section names to file names. + 'ZzDummy': 'zzdummy', } def load_extension(self, name): diff --git a/Lib/idlelib/idle_test/test_config.py b/Lib/idlelib/idle_test/test_config.py index bbf06504fc2552..84b45a6376749d 100644 --- a/Lib/idlelib/idle_test/test_config.py +++ b/Lib/idlelib/idle_test/test_config.py @@ -437,78 +437,57 @@ def test_get_extensions(self): eq = self.assertEqual eq(conf.GetExtensions(), - ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext', - 'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding', - 'ZoomHeight']) + ['ZzDummy']) eq(conf.GetExtensions(active_only=False), - ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext', - 'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding', - 'ZoomHeight', 'DISABLE']) + ['ZzDummy', 'DISABLE']) eq(conf.GetExtensions(editor_only=True), - ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext', - 'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding', - 'ZoomHeight']) + ['ZzDummy']) eq(conf.GetExtensions(shell_only=True), - ['AutoComplete', 'AutoExpand', 'CallTips', 'FormatParagraph', - 'ParenMatch', 'ZoomHeight']) + []) eq(conf.GetExtensions(active_only=False, editor_only=True), - ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext', - 'FormatParagraph', 'ParenMatch', 'RstripExtension', - 'ScriptBinding', 'ZoomHeight', 'DISABLE']) - eq(conf.GetExtensions(active_only=False, shell_only=True), - ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext', - 'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding', - 'ZoomHeight', 'DISABLE']) + ['ZzDummy', 'DISABLE']) # Add user extensions conf.SetOption('extensions', 'Foobar', 'enable', 'True') eq(conf.GetExtensions(), - ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext', - 'FormatParagraph', 'ParenMatch', 'RstripExtension', - 'ScriptBinding', 'ZoomHeight', 'Foobar']) # User extensions didn't sort + ['ZzDummy', 'Foobar']) # User extensions didn't sort eq(conf.GetExtensions(active_only=False), - ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext', - 'FormatParagraph', 'ParenMatch', 'RstripExtension', - 'ScriptBinding', 'ZoomHeight', 'DISABLE', 'Foobar']) + ['ZzDummy', 'DISABLE', 'Foobar']) def test_remove_key_bind_names(self): conf = self.mock_config() self.assertCountEqual( conf.RemoveKeyBindNames(conf.GetSectionList('default', 'extensions')), - ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext', - 'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding', - 'ZoomHeight']) + ['AutoComplete', 'CodeContext', 'FormatParagraph', 'ParenMatch','ZzDummy']) def test_get_extn_name_for_event(self): conf = self.mock_config() eq = self.assertEqual - eq(conf.GetExtnNameForEvent('force-open-completions'), 'AutoComplete') - eq(conf.GetExtnNameForEvent('expand-word'), 'AutoExpand') - eq(conf.GetExtnNameForEvent('force-open-calltip'), 'CallTips') - eq(conf.GetExtnNameForEvent('zoom-height'), 'ZoomHeight') + eq(conf.GetExtnNameForEvent('z-in'), 'ZzDummy') + eq(conf.GetExtnNameForEvent('z-out'), None) def test_get_extension_keys(self): conf = self.mock_config() eq = self.assertEqual - eq(conf.GetExtensionKeys('AutoComplete'), - {'<>': ['']}) - eq(conf.GetExtensionKeys('ParenMatch'), - {'<>': ['']}) - - key = [''] if sys.platform == 'darwin' else [''] - eq(conf.GetExtensionKeys('ZoomHeight'), {'<>': key}) + eq(conf.GetExtensionKeys('ZzDummy'), + {'<>': ['']}) +# need option key test +## key = [''] if sys.platform == 'darwin' else [''] +## eq(conf.GetExtensionKeys('ZoomHeight'), {'<>': key}) def test_get_extension_bindings(self): conf = self.mock_config() self.assertEqual(conf.GetExtensionBindings('NotExists'), {}) - key = [''] if sys.platform == 'darwin' else [''] + #key = [''] if sys.platform == 'darwin' else [''] + expect = {'<>': [''], + '<>': ['']} self.assertEqual( - conf.GetExtensionBindings('ZoomHeight'), {'<>': key}) + conf.GetExtensionBindings('ZzDummy'), expect) # Add non-configuarable bindings conf.defaultCfg['extensions'].add_section('Foobar') @@ -542,9 +521,11 @@ def test_get_current_keyset(self): sys.platform = 'some-linux' self.assertEqual(conf.GetCurrentKeySet(), conf.GetKeySet(conf.CurrentKeys())) - # This should not be the same, sicne replace ') self.assertEqual(keyspage, {'my custom keys': {'copy': ''}}) + # Not a core binding - adds to extensions. d.bindingslist.selection_set(1) d.bindingslist.selection_anchor(1) d.keybinding.set('') self.assertEqual(extpage, - {'AutoExpand_cfgBindings': {'expand-word': ''}}) + {'ZzDummy_cfgBindings': {'z-in': ''}}) def test_set_keys_type(self): eq = self.assertEqual @@ -1125,13 +1126,6 @@ def test_startup(self): self.assertEqual(mainpage, {'General': {'editor-on-startup': '0'}}) - def test_autosave(self): - d = self.page - d.save_auto_on.invoke() - self.assertEqual(mainpage, {'General': {'autosave': '1'}}) - d.save_ask_on.invoke() - self.assertEqual(mainpage, {'General': {'autosave': '0'}}) - def test_editor_size(self): d = self.page d.win_height_int.insert(0, '1') @@ -1140,6 +1134,37 @@ def test_editor_size(self): d.win_width_int.insert(0, '1') self.assertEqual(mainpage, {'EditorWindow': {'width': '180'}}) + def test_autocomplete_wait(self): + self.page.auto_wait_int.insert(0, '1') + self.assertEqual(extpage, {'AutoComplete': {'popupwait': '12000'}}) + + def test_parenmatch(self): + d = self.page + eq = self.assertEqual + d.paren_style_type['menu'].invoke(0) + eq(extpage, {'ParenMatch': {'style': 'opener'}}) + changes.clear() + d.paren_flash_time.insert(0, '2') + eq(extpage, {'ParenMatch': {'flash-delay': '2500'}}) + changes.clear() + d.bell_on.invoke() + eq(extpage, {'ParenMatch': {'bell': 'False'}}) + + def test_autosave(self): + d = self.page + d.save_auto_on.invoke() + self.assertEqual(mainpage, {'General': {'autosave': '1'}}) + d.save_ask_on.invoke() + self.assertEqual(mainpage, {'General': {'autosave': '0'}}) + + def test_paragraph(self): + self.page.format_width_int.insert(0, '1') + self.assertEqual(extpage, {'FormatParagraph': {'max-width': '172'}}) + + def test_context(self): + self.page.context_int.insert(0, '1') + self.assertEqual(extpage, {'CodeContext': {'numlines': '13'}}) + def test_source_selected(self): d = self.page d.set = d.set_add_delete_state diff --git a/Lib/idlelib/mainmenu.py b/Lib/idlelib/mainmenu.py index 65345cd1f16188..d1dcb83d9324f4 100644 --- a/Lib/idlelib/mainmenu.py +++ b/Lib/idlelib/mainmenu.py @@ -52,6 +52,11 @@ ('Find in Files...', '<>'), ('R_eplace...', '<>'), ('Go to _Line', '<>'), + ('S_how Completions', '<>'), + ('E_xpand Word', '<>'), + ('Show C_all Tip', '<>'), + ('Show Surrounding P_arens', '<>'), + ]), ('format', [ ('_Indent Region', '<>'), @@ -62,9 +67,13 @@ ('Untabify Region', '<>'), ('Toggle Tabs', '<>'), ('New Indent Width', '<>'), + ('F_ormat Paragraph', '<>'), + ('S_trip Trailing Whitespace', '<>'), ]), ('run', [ ('Python Shell', '<>'), + ('C_heck Module', '<>'), + ('R_un Module', '<>'), ]), ('shell', [ ('_View Last Restart', '<>'), @@ -80,7 +89,10 @@ ]), ('options', [ ('Configure _IDLE', '<>'), - None, + ('_Code Context', '<>'), + ]), + ('windows', [ + ('Zoom Height', '<>'), ]), ('help', [ ('_About IDLE', '<>'), diff --git a/Lib/idlelib/outwin.py b/Lib/idlelib/outwin.py index 5f7c09fb92cdad..6c2a792d86b99a 100644 --- a/Lib/idlelib/outwin.py +++ b/Lib/idlelib/outwin.py @@ -77,6 +77,7 @@ class OutputWindow(EditorWindow): def __init__(self, *args): EditorWindow.__init__(self, *args) self.text.bind("<>", self.goto_file_line) + self.text.unbind("<>") # Customize EditorWindow def ispythonsource(self, filename): diff --git a/Lib/idlelib/paragraph.py b/Lib/idlelib/paragraph.py index f11bdaeb77ac38..cf8dfdb641f69d 100644 --- a/Lib/idlelib/paragraph.py +++ b/Lib/idlelib/paragraph.py @@ -1,4 +1,4 @@ -"""Extension to format a paragraph or selection to a max width. +"""Format a paragraph, comment block, or selection to a max width. Does basic, standard text formatting, and also understands Python comment blocks. Thus, for editing Python source code, this @@ -21,15 +21,14 @@ class FormatParagraph: - menudefs = [ - ('format', [ # /s/edit/format dscherer@cmu.edu - ('Format Paragraph', '<>'), - ]) - ] - def __init__(self, editwin): self.editwin = editwin + @classmethod + def reload(cls): + cls.max_width = idleConf.GetOption('extensions', 'FormatParagraph', + 'max-width', type='int', default=72) + def close(self): self.editwin = None @@ -45,11 +44,7 @@ def format_paragraph_event(self, event, limit=None): The length limit parameter is for testing with a known value. """ - if limit is None: - # The default length limit is that defined by pep8 - limit = idleConf.GetOption( - 'extensions', 'FormatParagraph', 'max-width', - type='int', default=72) + limit = self.max_width if limit is None else limit text = self.editwin.text first, last = self.editwin.get_selection_indices() if first and last: @@ -75,6 +70,9 @@ def format_paragraph_event(self, event, limit=None): text.see("insert") return "break" + +FormatParagraph.reload() + def find_paragraph(text, mark): """Returns the start/stop indices enclosing the paragraph that mark is in. diff --git a/Lib/idlelib/parenmatch.py b/Lib/idlelib/parenmatch.py index 7f25880ae5ec1b..12212150c6dc8e 100644 --- a/Lib/idlelib/parenmatch.py +++ b/Lib/idlelib/parenmatch.py @@ -1,4 +1,4 @@ -"""ParenMatch -- An IDLE extension for parenthesis matching. +"""ParenMatch -- for parenthesis matching. When you hit a right paren, the cursor should move briefly to the left paren. Paren here is used generically; the matching applies to @@ -30,18 +30,6 @@ class ParenMatch: - Highlight when cursor is moved to the right of a closer. This might be too expensive to check. """ - menudefs = [ - ('edit', [ - ("Show surrounding parens", "<>"), - ]) - ] - STYLE = idleConf.GetOption( - 'extensions','ParenMatch','style', default='expression') - FLASH_DELAY = idleConf.GetOption( - 'extensions','ParenMatch','flash-delay', type='int',default=500) - BELL = idleConf.GetOption( - 'extensions','ParenMatch','bell', type='bool',default=1) - HILITE_CONFIG = idleConf.GetHighlight(idleConf.CurrentTheme(),'hilite') RESTORE_VIRTUAL_EVENT_NAME = "<>" # We want the restore event be called before the usual return and @@ -62,6 +50,17 @@ def __init__(self, editwin): self.is_restore_active = 0 self.set_style(self.STYLE) + @classmethod + def reload(cls): + cls.STYLE = idleConf.GetOption( + 'extensions','ParenMatch','style', default='opener') + cls.FLASH_DELAY = idleConf.GetOption( + 'extensions','ParenMatch','flash-delay', type='int',default=500) + cls.BELL = idleConf.GetOption( + 'extensions','ParenMatch','bell', type='bool', default=1) + cls.HILITE_CONFIG = idleConf.GetHighlight(idleConf.CurrentTheme(), + 'hilite') + def activate_restore(self): "Activate mechanism to restore text from highlighting." if not self.is_restore_active: @@ -181,6 +180,9 @@ def set_timeout_last(self): lambda self=self, c=self.counter: self.handle_restore_timer(c)) +ParenMatch.reload() + + if __name__ == '__main__': import unittest unittest.main('idlelib.idle_test.test_parenmatch', verbosity=2) diff --git a/Lib/idlelib/rstrip.py b/Lib/idlelib/rstrip.py index 2ce3c7eafe5a81..18c86f9b2c8896 100644 --- a/Lib/idlelib/rstrip.py +++ b/Lib/idlelib/rstrip.py @@ -2,12 +2,8 @@ class RstripExtension: - menudefs = [ - ('format', [None, ('Strip trailing whitespace', '<>'), ] ), ] - def __init__(self, editwin): self.editwin = editwin - self.editwin.text.bind("<>", self.do_rstrip) def do_rstrip(self, event=None): diff --git a/Lib/idlelib/runscript.py b/Lib/idlelib/runscript.py index 3355f17d90bdb7..45bf56345825a1 100644 --- a/Lib/idlelib/runscript.py +++ b/Lib/idlelib/runscript.py @@ -1,22 +1,14 @@ -"""Extension to execute code outside the Python shell window. +"""Execute code from an editor. -This adds the following commands: +Check module: do a full syntax check of the current module. +Also run the tabnanny to catch any inconsistent tabs. -- Check module does a full syntax check of the current module. - It also runs the tabnanny to catch any inconsistent tabs. - -- Run module executes the module's code in the __main__ namespace. The window - must have been saved previously. The module is added to sys.modules, and is - also added to the __main__ namespace. - -XXX GvR Redesign this interface (yet again) as follows: - -- Present a dialog box for ``Run Module'' - -- Allow specify command line arguments in the dialog box +Run module: also execute the module's code in the __main__ namespace. +The window must have been saved previously. The module is added to +sys.modules, and is also added to the __main__ namespace. +TODO: Specify command line arguments in a dialog box. """ - import os import tabnanny import tokenize @@ -40,11 +32,6 @@ class ScriptBinding: - menudefs = [ - ('run', [None, - ('Check Module', '<>'), - ('Run Module', '<>'), ]), ] - def __init__(self, editwin): self.editwin = editwin # Provide instance variables referenced by debugger diff --git a/Lib/idlelib/zoomheight.py b/Lib/idlelib/zoomheight.py index d01c9e964aa60f..74fbc888a80d3f 100644 --- a/Lib/idlelib/zoomheight.py +++ b/Lib/idlelib/zoomheight.py @@ -1,4 +1,4 @@ -# Sample extension: zoom a window to maximum height +"Zoom a window to maximum height." import re import sys @@ -8,12 +8,6 @@ class ZoomHeight: - menudefs = [ - ('windows', [ - ('_Zoom Height', '<>'), - ]) - ] - def __init__(self, editwin): self.editwin = editwin diff --git a/Lib/idlelib/zzdummy.py b/Lib/idlelib/zzdummy.py new file mode 100644 index 00000000000000..8084499646653d --- /dev/null +++ b/Lib/idlelib/zzdummy.py @@ -0,0 +1,42 @@ +"Example extension, also used for testing." + +from idlelib.config import idleConf + +ztext = idleConf.GetOption('extensions', 'ZzDummy', 'z-text') + + +class ZzDummy: + +## menudefs = [ +## ('format', [ +## ('Z in', '<>'), +## ('Z out', '<>'), +## ] ) +## ] + + def __init__(self, editwin): + self.text = editwin.text + z_in = False + + @classmethod + def reload(cls): + cls.ztext = idleConf.GetOption('extensions', 'ZzDummy', 'z-text') + + def z_in_event(self, event): + """ + """ + text = self.text + text.undo_block_start() + for line in range(1, text.index('end')): + text.insert('%d.0', ztest) + text.undo_block_stop() + return "break" + + def z_out_event(self, event): pass + +ZzDummy.reload() + +##if __name__ == "__main__": +## import unittest +## unittest.main('idlelib.idle_test.test_zzdummy', +## verbosity=2, exit=False) diff --git a/Misc/NEWS.d/next/IDLE/2017-08-24-13-48-16.bpo-27099.rENefC.rst b/Misc/NEWS.d/next/IDLE/2017-08-24-13-48-16.bpo-27099.rENefC.rst new file mode 100644 index 00000000000000..9b59fbabe531b0 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2017-08-24-13-48-16.bpo-27099.rENefC.rst @@ -0,0 +1,20 @@ +Convert IDLE's built-in 'extensions' to regular features. + +About 10 IDLE features were implemented as supposedly optional +extensions. Their different behavior could be confusing or worse for +users and not good for maintenance. Hence the conversion. + +The main difference for users is that user configurable key bindings +for builtin features are now handled uniformly. Now, editing a binding +in a keyset only affects its value in the keyset. All bindings are +defined together in the system-specific default keysets in config- +extensions.def. All custom keysets are saved as a whole in config- +extension.cfg. All take effect as soon as one clicks Apply or Ok. + +The affected events are '<>', '<>', +'<>', '<>', '<>', +'<>', '<>', and '<>'. Any +(global) customizations made before 3.6.3 will not affect their keyset- +specific customization after 3.6.3. and vice versa. + +Inital patch by Charles Wohlganger.