Compare commits

...

3 Commits

Author SHA1 Message Date
Terry Jan Reedy a087a97438
[3.8] bpo-32631: IDLE: Enable zzdummy example extension module (GH-14491)
Make menu items work with formatter, add docstrings, add 100% tests.

Co-authored-by: Cheryl Sabella <cheryl.sabella@gmail.com>
Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu>
(cherry picked from commit e40e2a2cc9)
2021-01-05 04:18:02 -05:00
Miss Islington (bot) 3b6dcb8630
Fix broken NEWS markup (GH-24110)
(cherry picked from commit cde988e893)

Co-authored-by: Brandt Bucher <brandtbucher@gmail.com>
2021-01-05 00:18:43 -08:00
Miss Islington (bot) d05b470d6a
bpo-42508: Keep IDLE running on macOS (GH-23577) (#23670)
Remove obsolete workaround that prevented running files with
shortcuts when using new universal2 installers built on macOS 11.
Ignore buggy 2nd run_module_event call.
(cherry picked from commit 57e5113610)

Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu>
2021-01-05 03:09:30 -05:00
9 changed files with 244 additions and 53 deletions

View File

@ -3,6 +3,13 @@ Released on 2020-12-??
======================================
bpo-32631: Finish zzdummy example extension module: make menu entries
work; add docstrings and tests with 100% coverage.
bpo-42508: Keep IDLE running on macOS. Remove obsolete workaround
that prevented running files with shortcuts when using new universal2
installers built on macOS 11.
bpo-42426: Fix reporting offset of the RE error in searchengine.
bpo-42416: Get docstrings for IDLE calltips more often

View File

@ -2316,7 +2316,15 @@ display when Code Context is turned on for an editor window.
Shell Preferences: Auto-Squeeze Min. Lines is the minimum number of lines
of output to automatically "squeeze".
'''
''',
'Extensions': '''
ZzDummy: This extension is provided as an example for how to create and
use an extension. Enable indicates whether the extension is active or
not; likewise enable_editor and enable_shell indicate which windows it
will be active on. For this extension, z-text is the text that will be
inserted at or removed from the beginning of the lines of selected text,
or the current line if no selection.
''',
}

View File

@ -28,8 +28,8 @@ variables:
(There are a few more, but they are rarely useful.)
The extension class must not directly bind Window Manager (e.g. X) events.
Rather, it must define one or more virtual events, e.g. <<zoom-height>>, and
corresponding methods, e.g. zoom_height_event(). The virtual events will be
Rather, it must define one or more virtual events, e.g. <<z-in>>, and
corresponding methods, e.g. z_in_event(). The virtual events will be
bound to the corresponding methods, and Window Manager events can then be bound
to the virtual events. (This indirection is done so that the key bindings can
easily be changed, and so that other sources of virtual events can exist, such
@ -54,21 +54,21 @@ 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:
Here is a partial example from zzdummy.py:
class ZoomHeight:
class ZzDummy:
menudefs = [
('edit', [
None, # Separator
('_Zoom Height', '<<zoom-height>>'),
('format', [
('Z in', '<<z-in>>'),
('Z out', '<<z-out>>'),
] )
]
def __init__(self, editwin):
self.editwin = editwin
def zoom_height_event(self, event):
def z_in_event(self, event=None):
"...Do what you want here..."
The final piece of the puzzle is the file "config-extensions.def", which is

View File

@ -0,0 +1,152 @@
"Test zzdummy, coverage 100%."
from idlelib import zzdummy
import unittest
from test.support import requires
from tkinter import Tk, Text
from unittest import mock
from idlelib import config
from idlelib import editor
from idlelib import format
usercfg = zzdummy.idleConf.userCfg
testcfg = {
'main': config.IdleUserConfParser(''),
'highlight': config.IdleUserConfParser(''),
'keys': config.IdleUserConfParser(''),
'extensions': config.IdleUserConfParser(''),
}
code_sample = """\
class C1():
# Class comment.
def __init__(self, a, b):
self.a = a
self.b = b
"""
class DummyEditwin:
get_selection_indices = editor.EditorWindow.get_selection_indices
def __init__(self, root, text):
self.root = root
self.top = root
self.text = text
self.fregion = format.FormatRegion(self)
self.text.undo_block_start = mock.Mock()
self.text.undo_block_stop = mock.Mock()
class ZZDummyTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
root = cls.root = Tk()
root.withdraw()
text = cls.text = Text(cls.root)
cls.editor = DummyEditwin(root, text)
zzdummy.idleConf.userCfg = testcfg
@classmethod
def tearDownClass(cls):
zzdummy.idleConf.userCfg = usercfg
del cls.editor, cls.text
cls.root.update_idletasks()
for id in cls.root.tk.call('after', 'info'):
cls.root.after_cancel(id) # Need for EditorWindow.
cls.root.destroy()
del cls.root
def setUp(self):
text = self.text
text.insert('1.0', code_sample)
text.undo_block_start.reset_mock()
text.undo_block_stop.reset_mock()
zz = self.zz = zzdummy.ZzDummy(self.editor)
zzdummy.ZzDummy.ztext = '# ignore #'
def tearDown(self):
self.text.delete('1.0', 'end')
del self.zz
def checklines(self, text, value):
# Verify that there are lines being checked.
end_line = int(float(text.index('end')))
# Check each line for the starting text.
actual = []
for line in range(1, end_line):
txt = text.get(f'{line}.0', f'{line}.end')
actual.append(txt.startswith(value))
return actual
def test_init(self):
zz = self.zz
self.assertEqual(zz.editwin, self.editor)
self.assertEqual(zz.text, self.editor.text)
def test_reload(self):
self.assertEqual(self.zz.ztext, '# ignore #')
testcfg['extensions'].SetOption('ZzDummy', 'z-text', 'spam')
zzdummy.ZzDummy.reload()
self.assertEqual(self.zz.ztext, 'spam')
def test_z_in_event(self):
eq = self.assertEqual
zz = self.zz
text = zz.text
eq(self.zz.ztext, '# ignore #')
# No lines have the leading text.
expected = [False, False, False, False, False, False, False]
actual = self.checklines(text, zz.ztext)
eq(expected, actual)
text.tag_add('sel', '2.0', '4.end')
eq(zz.z_in_event(), 'break')
expected = [False, True, True, True, False, False, False]
actual = self.checklines(text, zz.ztext)
eq(expected, actual)
text.undo_block_start.assert_called_once()
text.undo_block_stop.assert_called_once()
def test_z_out_event(self):
eq = self.assertEqual
zz = self.zz
text = zz.text
eq(self.zz.ztext, '# ignore #')
# Prepend text.
text.tag_add('sel', '2.0', '5.end')
zz.z_in_event()
text.undo_block_start.reset_mock()
text.undo_block_stop.reset_mock()
# Select a few lines to remove text.
text.tag_remove('sel', '1.0', 'end')
text.tag_add('sel', '3.0', '4.end')
eq(zz.z_out_event(), 'break')
expected = [False, True, False, False, True, False, False]
actual = self.checklines(text, zz.ztext)
eq(expected, actual)
text.undo_block_start.assert_called_once()
text.undo_block_stop.assert_called_once()
def test_roundtrip(self):
# Insert and remove to all code should give back original text.
zz = self.zz
text = zz.text
text.tag_add('sel', '1.0', 'end-1c')
zz.z_in_event()
zz.z_out_event()
self.assertEqual(text.get('1.0', 'end-1c'), code_sample)
if __name__ == '__main__':
unittest.main(verbosity=2)

View File

@ -11,6 +11,7 @@ TODO: Specify command line arguments in a dialog box.
"""
import os
import tabnanny
import time
import tokenize
import tkinter.messagebox as tkMessageBox
@ -42,9 +43,7 @@ class ScriptBinding:
self.root = self.editwin.root
# cli_args is list of strings that extends sys.argv
self.cli_args = []
if macosx.isCocoaTk():
self.editwin.text_frame.bind('<<run-module-event-2>>', self._run_module_event)
self.perf = 0.0 # Workaround for macOS 11 Uni2; see bpo-42508.
def check_module_event(self, event):
if isinstance(self.editwin, outwin.OutputWindow):
@ -107,24 +106,10 @@ class ScriptBinding:
finally:
shell.set_warning_stream(saved_stream)
def run_module_event(self, event):
if macosx.isCocoaTk():
# 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_custom_event(self, event):
return self._run_module_event(event, customize=True)
return self.run_module_event(event, customize=True)
def _run_module_event(self, event, *, customize=False):
def run_module_event(self, event, *, customize=False):
"""Run the module after setting up the environment.
First check the syntax. Next get customization. If OK, make
@ -133,6 +118,8 @@ class ScriptBinding:
module being executed and also add that directory to its
sys.path if not already included.
"""
if macosx.isCocoaTk() and (time.perf_counter() - self.perf < .05):
return 'break'
if isinstance(self.editwin, outwin.OutputWindow):
self.editwin.text.bell()
return 'break'
@ -218,6 +205,7 @@ class ScriptBinding:
# XXX This should really be a function of EditorWindow...
tkMessageBox.showerror(title, message, parent=self.editwin.text)
self.editwin.text.focus_set()
self.perf = time.perf_counter()
if __name__ == "__main__":

View File

@ -1,42 +1,73 @@
"Example extension, also used for testing."
"""Example extension, also used for testing.
See extend.txt for more details on creating an extension.
See config-extension.def for configuring an extension.
"""
from idlelib.config import idleConf
from functools import wraps
ztext = idleConf.GetOption('extensions', 'ZzDummy', 'z-text')
def format_selection(format_line):
"Apply a formatting function to all of the selected lines."
@wraps(format_line)
def apply(self, event=None):
head, tail, chars, lines = self.formatter.get_region()
for pos in range(len(lines) - 1):
line = lines[pos]
lines[pos] = format_line(self, line)
self.formatter.set_region(head, tail, chars, lines)
return 'break'
return apply
class ZzDummy:
"""Prepend or remove initial text from selected lines."""
## menudefs = [
## ('format', [
## ('Z in', '<<z-in>>'),
## ('Z out', '<<z-out>>'),
## ] )
## ]
# Extend the format menu.
menudefs = [
('format', [
('Z in', '<<z-in>>'),
('Z out', '<<z-out>>'),
] )
]
def __init__(self, editwin):
"Initialize the settings for this extension."
self.editwin = editwin
self.text = editwin.text
z_in = False
self.formatter = editwin.fregion
@classmethod
def reload(cls):
"Load class variables from config."
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"
@format_selection
def z_in_event(self, line):
"""Insert text at the beginning of each selected line.
This is bound to the <<z-in>> virtual event when the extensions
are loaded.
"""
return f'{self.ztext}{line}'
@format_selection
def z_out_event(self, line):
"""Remove specific text from the beginning of each selected line.
This is bound to the <<z-out>> virtual event when the extensions
are loaded.
"""
zlength = 0 if not line.startswith(self.ztext) else len(self.ztext)
return line[zlength:]
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)
if __name__ == "__main__":
import unittest
unittest.main('idlelib.idle_test.test_zzdummy', verbosity=2, exit=False)

View File

@ -0,0 +1,2 @@
Finish zzdummy example extension module: make menu entries work;
add docstrings and tests with 100% coverage.

View File

@ -0,0 +1,3 @@
Keep IDLE running on macOS. Remove obsolete workaround that prevented
running files with shortcuts when using new universal2 installers built
on macOS 11.

View File

@ -1 +1 @@
In :mod:`sqlite3`, fix `CheckTraceCallbackContent` for SQLite pre 3.7.15.
In :mod:`sqlite3`, fix ``CheckTraceCallbackContent`` for SQLite pre 3.7.15.