bpo-39885: Make IDLE context menu cut and copy work again (GH-18951)

Leave selection when right click within.  This exception to clearing selections when right-clicking was omitted from the previous commit, 4ca060d.  I did not realize that this completely disabled the context menu entries, and  I should have merged a minimal fix immediately.  An automated test should follow.
This commit is contained in:
Terry Jan Reedy 2020-05-29 18:54:14 -04:00 committed by GitHub
parent 8bd216dfed
commit 97e4e0f53d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 52 additions and 16 deletions

View File

@ -12,8 +12,9 @@ when fetching a calltip.
bpo-27115: For 'Go to Line', use a Query entry box subclass with bpo-27115: For 'Go to Line', use a Query entry box subclass with
IDLE standard behavior and improved error checking. IDLE standard behavior and improved error checking.
bpo-39885: Since clicking to get an IDLE context menu moves the bpo-39885: When a context menu is invoked by right-clicking outside
cursor, any text selection should be and now is cleared. of a selection, clear the selection and move the cursor. Cut and
Copy require that the click be within the selection.
bpo-39852: Edit "Go to line" now clears any selection, preventing bpo-39852: Edit "Go to line" now clears any selection, preventing
accidental deletion. It also updates Ln and Col on the status bar. accidental deletion. It also updates Ln and Col on the status bar.

View File

@ -499,15 +499,23 @@ class EditorWindow(object):
rmenu = None rmenu = None
def right_menu_event(self, event): def right_menu_event(self, event):
self.text.tag_remove("sel", "1.0", "end") text = self.text
self.text.mark_set("insert", "@%d,%d" % (event.x, event.y)) newdex = text.index(f'@{event.x},{event.y}')
try:
in_selection = (text.compare('sel.first', '<=', newdex) and
text.compare(newdex, '<=', 'sel.last'))
except TclError:
in_selection = False
if not in_selection:
text.tag_remove("sel", "1.0", "end")
text.mark_set("insert", newdex)
if not self.rmenu: if not self.rmenu:
self.make_rmenu() self.make_rmenu()
rmenu = self.rmenu rmenu = self.rmenu
self.event = event self.event = event
iswin = sys.platform[:3] == 'win' iswin = sys.platform[:3] == 'win'
if iswin: if iswin:
self.text.config(cursor="arrow") text.config(cursor="arrow")
for item in self.rmenu_specs: for item in self.rmenu_specs:
try: try:
@ -520,7 +528,6 @@ class EditorWindow(object):
state = getattr(self, verify_state)() state = getattr(self, verify_state)()
rmenu.entryconfigure(label, state=state) rmenu.entryconfigure(label, state=state)
rmenu.tk_popup(event.x_root, event.y_root) rmenu.tk_popup(event.x_root, event.y_root)
if iswin: if iswin:
self.text.config(cursor="ibeam") self.text.config(cursor="ibeam")

View File

@ -5,6 +5,7 @@ import unittest
from collections import namedtuple from collections import namedtuple
from test.support import requires from test.support import requires
from tkinter import Tk from tkinter import Tk
from idlelib.idle_test.mock_idle import Func
Editor = editor.EditorWindow Editor = editor.EditorWindow
@ -92,6 +93,12 @@ class TestGetLineIndent(unittest.TestCase):
) )
def insert(text, string):
text.delete('1.0', 'end')
text.insert('end', string)
text.update() # Force update for colorizer to finish.
class IndentAndNewlineTest(unittest.TestCase): class IndentAndNewlineTest(unittest.TestCase):
@classmethod @classmethod
@ -113,13 +120,6 @@ class IndentAndNewlineTest(unittest.TestCase):
cls.root.destroy() cls.root.destroy()
del cls.root del cls.root
def insert(self, text):
t = self.window.text
t.delete('1.0', 'end')
t.insert('end', text)
# Force update for colorizer to finish.
t.update()
def test_indent_and_newline_event(self): def test_indent_and_newline_event(self):
eq = self.assertEqual eq = self.assertEqual
w = self.window w = self.window
@ -170,13 +170,13 @@ class IndentAndNewlineTest(unittest.TestCase):
w.prompt_last_line = '' w.prompt_last_line = ''
for test in tests: for test in tests:
with self.subTest(label=test.label): with self.subTest(label=test.label):
self.insert(test.text) insert(text, test.text)
text.mark_set('insert', test.mark) text.mark_set('insert', test.mark)
nl(event=None) nl(event=None)
eq(get('1.0', 'end'), test.expected) eq(get('1.0', 'end'), test.expected)
# Selected text. # Selected text.
self.insert(' def f1(self, a, b):\n return a + b') insert(text, ' def f1(self, a, b):\n return a + b')
text.tag_add('sel', '1.17', '1.end') text.tag_add('sel', '1.17', '1.end')
nl(None) nl(None)
# Deletes selected text before adding new line. # Deletes selected text before adding new line.
@ -184,11 +184,37 @@ class IndentAndNewlineTest(unittest.TestCase):
# Preserves the whitespace in shell prompt. # Preserves the whitespace in shell prompt.
w.prompt_last_line = '>>> ' w.prompt_last_line = '>>> '
self.insert('>>> \t\ta =') insert(text, '>>> \t\ta =')
text.mark_set('insert', '1.5') text.mark_set('insert', '1.5')
nl(None) nl(None)
eq(get('1.0', 'end'), '>>> \na =\n') eq(get('1.0', 'end'), '>>> \na =\n')
class RMenuTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.window = Editor(root=cls.root)
@classmethod
def tearDownClass(cls):
cls.window._close()
del cls.window
cls.root.update_idletasks()
for id in cls.root.tk.call('after', 'info'):
cls.root.after_cancel(id)
cls.root.destroy()
del cls.root
class DummyRMenu:
def tk_popup(x, y): pass
def test_rclick(self):
pass
if __name__ == '__main__': if __name__ == '__main__':
unittest.main(verbosity=2) unittest.main(verbosity=2)

View File

@ -0,0 +1,2 @@
Make context menu Cut and Copy work again when right-clicking within a
selection.