Commit 58fc71c4 authored by wohlganger's avatar wohlganger Committed by Terry Jan Reedy

bpo-27099: IDLE - Convert built-in extensions to regular features (#2494)

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 '<<force-open-completions>>', '<<expand-word>>',
'<<force-open-calltip>>', '<<flash-paren>>', '<<format-paragraph>>',
'<<run-module>>', '<<check-module>>', and '<<zoom-height>>'.  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, revised by Terry Jan Reedy.
parent d39dbf4c
......@@ -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
"""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 @@ if os.altsep: # e.g. '/' on Windows...
class AutoComplete:
menudefs = [
('edit', [
("Show Completions", "<<force-open-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 @@ class AutoComplete:
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 @@ class AutoComplete:
return eval(name, namespace)
AutoComplete.reload()
if __name__ == '__main__':
from unittest import main
main('idlelib.idle_test.test_autocomplete', verbosity=2)
......@@ -10,23 +10,13 @@ Changing the current text line or leaving the cursor in a different
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 <<expand-word>>
###$ win <Alt-slash>
###$ unix <Alt-slash>
class AutoExpand:
menudefs = [
('edit', [
('E_xpand Word', '<<expand-word>>'),
]),
]
wordchars = string.ascii_letters + string.digits + "_"
def __init__(self, editwin):
......@@ -100,6 +90,7 @@ class AutoExpand:
i = i-1
return line[i:]
if __name__ == '__main__':
import unittest
unittest.main('idlelib.idle_test.test_autoexpand', verbosity=2)
"""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 import calltip_w
from idlelib.hyperparser import HyperParser
import __main__
class CallTips:
menudefs = [
('edit', [
("Show call tip", "<<force-open-calltip>>"),
])
]
class CallTips:
def __init__(self, editwin=None):
if editwin is None: # subprocess and test
......@@ -103,6 +97,7 @@ class CallTips:
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 @@ _default_callable_argspec = "See source or doc"
_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 ''.
......
"""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 @@ FONTUPDATEINTERVAL = 1000 # millisec
getspacesfirstword =\
lambda s, c=re.compile(r"^(\s*)(\w*)"): c.match(s).groups()
class CodeContext:
menudefs = [('options', [('!Code Conte_xt', '<<toggle-code-context>>')])]
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 @@ class CodeContext:
# 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('<<toggle-code-context>>', 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 @@ class CodeContext:
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 @@ class CodeContext:
self.textfont = newtextfont
self.label["font"] = self.textfont
self.text.after(FONTUPDATEINTERVAL, self.font_timer_event)
CodeContext.reload()
# 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 <<toggle-code-context>>= (See [CodeContext], below.)
# like <<toggle-code-context>>=.
#
# 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=<Control-Key-space>
[AutoComplete_bindings]
autocomplete=<Key-Tab>
try-open-completions=<KeyRelease-period> <KeyRelease-slash> <KeyRelease-backslash>
[AutoExpand]
enable=True
[AutoExpand_cfgBindings]
expand-word=<Alt-Key-slash>
[CallTips]
enable=True
[CallTips_cfgBindings]
force-open-calltip=<Control-Key-backslash>
[CallTips_bindings]
try-open-calltip=<KeyRelease-parenleft>
refresh-calltip=<KeyRelease-parenright> <KeyRelease-0>
[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=<Alt-Key-q>
[ParenMatch]
enable=True
style= expression
flash-delay= 500
bell=True
[ParenMatch_cfgBindings]
flash-paren=<Control-Key-0>
[ParenMatch_bindings]
paren-closed=<KeyRelease-parenright> <KeyRelease-bracketright> <KeyRelease-braceright>
[RstripExtension]
enable=True
enable_shell=False
enable_editor=True
[ScriptBinding]
enable=True
enable_shell=False
enable_editor=True
[ScriptBinding_cfgBindings]
run-module=<Key-F5>
check-module=<Alt-Key-x>
[ZoomHeight]
enable=True
[ZoomHeight_cfgBindings]
zoom-height=<Alt-Key-2>
# 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= <Control-Shift-KeyRelease-Insert>
[ZzDummy_bindings]
z-out= <Control-Shift-KeyRelease-Delete>
......@@ -57,6 +57,14 @@ toggle-tabs=<Alt-Key-t> <Meta-Key-t> <Alt-Key-T> <Meta-Key-T>
change-indentwidth=<Alt-Key-u> <Meta-Key-u> <Alt-Key-U> <Meta-Key-U>
del-word-left=<Control-Key-BackSpace>
del-word-right=<Control-Key-Delete>
force-open-completions= <Control-Key-space>
expand-word= <Alt-Key-slash>
force-open-calltip= <Control-Key-backslash>
format-paragraph= <Alt-Key-q>
flash-paren= <Control-Key-0>
run-module= <Key-F5>
check-module= <Alt-Key-x>
zoom-height= <Alt-Key-2>
[IDLE Classic Unix]
copy=<Alt-Key-w> <Meta-Key-w>
......@@ -108,6 +116,14 @@ toggle-tabs=<Alt-Key-t>
change-indentwidth=<Alt-Key-u>
del-word-left=<Alt-Key-BackSpace>
del-word-right=<Alt-Key-d>
force-open-completions= <Control-Key-space>
expand-word= <Alt-Key-slash>
force-open-calltip= <Control-Key-backslash>
format-paragraph= <Alt-Key-q>
flash-paren= <Control-Key-0>
run-module= <Key-F5>
check-module= <Alt-Key-x>
zoom-height= <Alt-Key-2>
[IDLE Modern Unix]
copy = <Control-Shift-Key-C> <Control-Key-Insert>
......@@ -159,6 +175,14 @@ toggle-tabs = <Control-Key-T>
change-indentwidth = <Alt-Key-u>
del-word-left = <Control-Key-BackSpace>
del-word-right = <Control-Key-Delete>
force-open-completions= <Control-Key-space>
expand-word= <Alt-Key-slash>
force-open-calltip= <Control-Key-backslash>
format-paragraph= <Alt-Key-q>
flash-paren= <Control-Key-0>
run-module= <Key-F5>
check-module= <Alt-Key-x>
zoom-height= <Alt-Key-2>
[IDLE Classic Mac]
copy=<Command-Key-c>
......@@ -210,6 +234,14 @@ toggle-tabs=<Control-Key-t>
change-indentwidth=<Control-Key-u>
del-word-left=<Control-Key-BackSpace>
del-word-right=<Control-Key-Delete>
force-open-completions= <Control-Key-space>
expand-word= <Option-Key-slash>
force-open-calltip= <Control-Key-backslash>
format-paragraph= <Option-Key-q>
flash-paren= <Control-Key-0>
run-module= <Key-F5>
check-module= <Option-Key-x>
zoom-height= <Option-Key-0>
[IDLE Classic OSX]
toggle-tabs = <Control-Key-t>
......@@ -262,4 +294,11 @@ python-context-help = <Shift-Key-F1>
save-copy-of-window-as-file = <Option-Command-Key-s>
open-window-from-file = <Command-Key-o>
python-docs = <Key-F1>
force-open-completions= <Control-Key-space>
expand-word= <Option-Key-slash>
force-open-calltip= <Control-Key-backslash>
format-paragraph= <Option-Key-q>
flash-paren= <Control-Key-0>
run-module= <Key-F5>
check-module= <Option-Key-x>
zoom-height= <Option-Key-0>
......@@ -359,7 +359,8 @@ class IdleConf:
'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 @@ class IdleConf:
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 @@ class IdleConf:
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.
'<<force-open-completions>>', '<<expand-word>>',
'<<force-open-calltip>>', '<<flash-paren>>', '<<format-paragraph>>',
'<<run-module>>', '<<check-module>>', '<<zoom-height>>'}
def GetCoreKeys(self, keySetName=None):
"""Return dict of core virtual-key keybindings for keySetName.
......@@ -654,8 +665,17 @@ class IdleConf:
'<<toggle-tabs>>': ['<Alt-Key-t>'],
'<<change-indentwidth>>': ['<Alt-Key-u>'],
'<<del-word-left>>': ['<Control-Key-BackSpace>'],
'<<del-word-right>>': ['<Control-Key-Delete>']
'<<del-word-right>>': ['<Control-Key-Delete>'],
'<<force-open-completions>>': ['<Control-Key-space>'],
'<<expand-word>>': ['<Alt-Key-slash>'],
'<<force-open-calltip>>': ['<Control-Key-backslash>'],
'<<flash-paren>>': ['<Control-Key-0>'],
'<<format-paragraph>>': ['<Alt-Key-q>'],
'<<run-module>>': ['<Key-F5>'],
'<<check-module>>': ['<Alt-Key-x>'],
'<<zoom-height>>': ['<Alt-Key-2>'],
}
if keySetName:
if not (self.userCfg['keys'].has_section(keySetName) or
self.defaultCfg['keys'].has_section(keySetName)):
......@@ -670,7 +690,8 @@ class IdleConf:
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'
......
This diff is collapsed.
......@@ -31,7 +31,6 @@ from idlelib import windows
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 @@ class EditorWindow(object):
# 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 @@ class EditorWindow(object):
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('<<autocomplete>>', '<Key-Tab>')
text.event_add('<<try-open-completions>>', '<KeyRelease-period>',
'<KeyRelease-slash>', '<KeyRelease-backslash>')
text.event_add('<<try-open-calltip>>', '<KeyRelease-parenleft>')
text.event_add('<<refresh-calltip>>', '<KeyRelease-parenright>')
text.event_add('<<paren-closed>>', '<KeyRelease-parenright>',
'<KeyRelease-bracketright>', '<KeyRelease-braceright>')
# Former extension bindings depends on frame.text being packed
# (called from self.ResetColorizer()).
autocomplete = self.AutoComplete(self)
text.bind("<<autocomplete>>", autocomplete.autocomplete_event)
text.bind("<<try-open-completions>>",
autocomplete.try_open_completions_event)
text.bind("<<force-open-completions>>",
autocomplete.force_open_completions_event)
text.bind("<<expand-word>>", self.AutoExpand(self).expand_word_event)
text.bind("<<format-paragraph>>",
self.FormatParagraph(self).format_paragraph_event)
parenmatch = self.ParenMatch(self)
text.bind("<<flash-paren>>", parenmatch.flash_paren_event)
text.bind("<<paren-closed>>", parenmatch.paren_closed_event)
scriptbinding = ScriptBinding(self)
text.bind("<<check-module>>", scriptbinding.check_module_event)
text.bind("<<run-module>>", scriptbinding.run_module_event)
text.bind("<<do-rstrip>>", self.RstripExtension(self).do_rstrip)
calltips = self.CallTips(self)
text.bind("<<try-open-calltip>>", calltips.try_open_calltip_event)
#refresh-calltips must come after paren-closed to work right
text.bind("<<refresh-calltip>>", calltips.refresh_calltip_event)
text.bind("<<force-open-calltip>>", calltips.force_open_calltip_event)
text.bind("<<zoom-height>>", self.ZoomHeight(self).zoom_height_event)
text.bind("<<toggle-code-context>>",
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 @@ class EditorWindow(object):
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):
......
......@@ -437,78 +437,57 @@ class IdleConfTest(unittest.TestCase):
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'),
{'<<force-open-completions>>': ['<Control-Key-space>']})
eq(conf.GetExtensionKeys('ParenMatch'),
{'<<flash-paren>>': ['<Control-Key-0>']})
key = ['<Option-Key-2>'] if sys.platform == 'darwin' else ['<Alt-Key-2>']
eq(conf.GetExtensionKeys('ZoomHeight'), {'<<zoom-height>>': key})
eq(conf.GetExtensionKeys('ZzDummy'),
{'<<z-in>>': ['<Control-Shift-KeyRelease-Insert>']})
# need option key test
## key = ['<Option-Key-2>'] if sys.platform == 'darwin' else ['<Alt-Key-2>']
## eq(conf.GetExtensionKeys('ZoomHeight'), {'<<zoom-height>>': key})
def test_get_extension_bindings(self):
conf = self.mock_config()
self.assertEqual(conf.GetExtensionBindings('NotExists'), {})
key = ['<Option-Key-2>'] if sys.platform == 'darwin' else ['<Alt-Key-2>']
#key = ['<Option-Key-2>'] if sys.platform == 'darwin' else ['<Alt-Key-2>']
expect = {'<<z-in>>': ['<Control-Shift-KeyRelease-Insert>'],
'<<z-out>>': ['<Control-Shift-KeyRelease-Delete>']}
self.assertEqual(
conf.GetExtensionBindings('ZoomHeight'), {'<<zoom-height>>': key})
conf.GetExtensionBindings('ZzDummy'), expect)
# Add non-configuarable bindings
conf.defaultCfg['extensions'].add_section('Foobar')
......@@ -542,9 +521,11 @@ class IdleConfTest(unittest.TestCase):
sys.platform = 'some-linux'
self.assertEqual(conf.GetCurrentKeySet(), conf.GetKeySet(conf.CurrentKeys()))
# This should not be the same, sicne replace <Alt- to <Option-
sys.platform = 'darwin'
self.assertNotEqual(conf.GetCurrentKeySet(), conf.GetKeySet(conf.CurrentKeys()))
# This should not be the same, since replace <Alt- to <Option-.
# Above depended on config-extensions.def having Alt keys,
# which is no longer true.
# sys.platform = 'darwin'
# self.assertNotEqual(conf.GetCurrentKeySet(), conf.GetKeySet(conf.CurrentKeys()))
# Restore platform
sys.platform = current_platform
......
......@@ -824,19 +824,20 @@ class KeysPageTest(unittest.TestCase):
d.custom_name.set('my custom keys')
d.bindingslist.delete(0, 'end')
d.bindingslist.insert(0, 'copy')
d.bindingslist.insert(1, 'expand-word')
d.bindingslist.insert(1, 'z-in')
d.bindingslist.selection_set(0)
d.bindingslist.selection_anchor(0)
# Core binding - adds to keys.
d.keybinding.set('<Key-F11>')
self.assertEqual(keyspage,
{'my custom keys': {'copy': '<Key-F11>'}})
# Not a core binding - adds to extensions.
d.bindingslist.selection_set(1)
d.bindingslist.selection_anchor(1)
d.keybinding.set('<Key-F11>')
self.assertEqual(extpage,
{'AutoExpand_cfgBindings': {'expand-word': '<Key-F11>'}})
{'ZzDummy_cfgBindings': {'z-in': '<Key-F11>'}})
def test_set_keys_type(self):
eq = self.assertEqual
......@@ -1125,13 +1126,6 @@ class GenPageTest(unittest.TestCase):
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 @@ class GenPageTest(unittest.TestCase):
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
......
......@@ -52,6 +52,11 @@ menudefs = [
('Find in Files...', '<<find-in-files>>'),
('R_eplace...', '<<replace>>'),
('Go to _Line', '<<goto-line>>'),
('S_how Completions', '<<force-open-completions>>'),
('E_xpand Word', '<<expand-word>>'),
('Show C_all Tip', '<<force-open-calltip>>'),
('Show Surrounding P_arens', '<<flash-paren>>'),
]),
('format', [
('_Indent Region', '<<indent-region>>'),
......@@ -62,9 +67,13 @@ menudefs = [
('Untabify Region', '<<untabify-region>>'),
('Toggle Tabs', '<<toggle-tabs>>'),
('New Indent Width', '<<change-indentwidth>>'),
('F_ormat Paragraph', '<<format-paragraph>>'),
('S_trip Trailing Whitespace', '<<do-rstrip>>'),
]),
('run', [
('Python Shell', '<<open-python-shell>>'),
('C_heck Module', '<<check-module>>'),
('R_un Module', '<<run-module>>'),
]),
('shell', [
('_View Last Restart', '<<view-restart>>'),
......@@ -80,7 +89,10 @@ menudefs = [
]),
('options', [
('Configure _IDLE', '<<open-config-dialog>>'),
None,
('_Code Context', '<<toggle-code-context>>'),
]),
('windows', [
('Zoom Height', '<<zoom-height>>'),
]),
('help', [
('_About IDLE', '<<about-idle>>'),
......
......@@ -77,6 +77,7 @@ class OutputWindow(EditorWindow):
def __init__(self, *args):
EditorWindow.__init__(self, *args)
self.text.bind("<<goto-file-line>>", self.goto_file_line)
self.text.unbind("<<toggle-code-context>>")
# Customize EditorWindow
def ispythonsource(self, filename):
......
"""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 @@ from idlelib.config import idleConf
class FormatParagraph:
menudefs = [
('format', [ # /s/edit/format dscherer@cmu.edu
('Format Paragraph', '<<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 @@ class FormatParagraph:
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 @@ class FormatParagraph:
text.see("insert")
return "break"
FormatParagraph.reload()
def find_paragraph(text, mark):
"""Returns the start/stop indices enclosing the paragraph that mark is in.
......
"""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", "<<flash-paren>>"),
])
]
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 = "<<parenmatch-check-restore>>"
# We want the restore event be called before the usual return and
......@@ -62,6 +50,17 @@ class ParenMatch:
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 @@ class ParenMatch:
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)
......@@ -2,12 +2,8 @@
class RstripExtension:
menudefs = [
('format', [None, ('Strip trailing whitespace', '<<do-rstrip>>'), ] ), ]
def __init__(self, editwin):
self.editwin = editwin
self.editwin.text.bind("<<do-rstrip>>", self.do_rstrip)
def do_rstrip(self, event=None):
......
"""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 @@ by Format->Untabify Region and specify the number of columns used by each tab.
class ScriptBinding:
menudefs = [
('run', [None,
('Check Module', '<<check-module>>'),
('Run Module', '<<run-module>>'), ]), ]
def __init__(self, editwin):
self.editwin = editwin
# Provide instance variables referenced by debugger
......
# Sample extension: zoom a window to maximum height
"Zoom a window to maximum height."
import re
import sys
......@@ -8,12 +8,6 @@ from idlelib import macosx
class ZoomHeight:
menudefs = [
('windows', [
('_Zoom Height', '<<zoom-height>>'),
])
]
def __init__(self, editwin):
self.editwin = editwin
......
"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-in>>'),
## ('Z out', '<<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)
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 '<<force-open-completions>>', '<<expand-word>>',
'<<force-open-calltip>>', '<<flash-paren>>', '<<format-paragraph>>',
'<<run-module>>', '<<check-module>>', and '<<zoom-height>>'. 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.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment