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.
(cherry picked from commit 97e4e0f53d)

Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu>
This commit is contained in:
Miss Islington (bot) 2020-05-29 16:13:21 -07:00 committed by GitHub
parent 805fa54676
commit 9f3f70fd0b
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 @@ def handle_yview(self, event, *args):
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 @@ def right_menu_event(self, event):
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 @@
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 @@ def test_tabwidth_8(self):
) )
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 @@ def tearDownClass(cls):
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 @@ def test_indent_and_newline_event(self):
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 @@ def test_indent_and_newline_event(self):
# 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.