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:
parent
8bd216dfed
commit
97e4e0f53d
|
@ -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.
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Make context menu Cut and Copy work again when right-clicking within a
|
||||||
|
selection.
|
Loading…
Reference in New Issue