diff --git a/Doc/library/idle.rst b/Doc/library/idle.rst index c7c30e5300c2a4a..27aaad64726a975 100644 --- a/Doc/library/idle.rst +++ b/Doc/library/idle.rst @@ -125,9 +125,11 @@ Select All Cut Copy selection into the system-wide clipboard; then delete the selection. + If there is no selection, cut the current line instead. Copy Copy selection into the system-wide clipboard. + If there is no selection, copy the current line instead. Paste Insert contents of the system-wide clipboard into the current window. @@ -371,9 +373,11 @@ Context menus have the standard clipboard functions also on the Edit menu. Cut Copy selection into the system-wide clipboard; then delete the selection. + If there is no selection, cut the current line instead. Copy Copy selection into the system-wide clipboard. + If there is no selection, copy the current line instead. Paste Insert contents of the system-wide clipboard into the current window. diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index 95ae8f450fa73d7..0249ce5cbf75295 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -610,14 +610,21 @@ def python_docs(self, event=None): return "break" def cut(self,event): - self.text.event_generate("<>") + if not self.text.tag_ranges('sel'): + self.copy(event) + self.text.delete('insert', 'insert+1l') + else: + self.text.event_generate("<>") return "break" def copy(self,event): - if not self.text.tag_ranges("sel"): - # There is no selection, so do nothing and maybe interrupt. - return None - self.text.event_generate("<>") + if not self.text.tag_ranges('sel'): + self.text.mark_set('insert', 'insert linestart') + line = self.text.get('insert', 'insert+1l') + self.text.clipboard_clear() + self.text.clipboard_append(line) + else: + self.text.event_generate("<>") return "break" def paste(self,event): diff --git a/Lib/idlelib/idle_test/test_editor.py b/Lib/idlelib/idle_test/test_editor.py index 78fe68b24921d81..8e53475a28cfabd 100644 --- a/Lib/idlelib/idle_test/test_editor.py +++ b/Lib/idlelib/idle_test/test_editor.py @@ -401,5 +401,51 @@ def test_ignore_choice_clears_mtime(self): stub.set_saved.assert_not_called() +class CopyCutLineTest(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.root.destroy() + del cls.root + + def test_copy_no_selection(self): + eq = self.assertEqual + text = self.window.text + + insert(text, 'hello\nworld') + text.mark_set('insert', '1.2') + self.window.copy(event=None) + + # Copy current line without being selected + eq(text.clipboard_get(), 'hello\n') + eq(text.tag_ranges('sel'), ()) + + # Cursor move to start of the line + eq(text.index('insert'), '1.0') + + def test_cut_no_selection(self): + eq = self.assertEqual + text = self.window.text + + insert(text, 'hello\nworld') + text.mark_set('insert', '1.2') + self.window.cut(event=None) + + # Copy current line and remove it + eq(text.clipboard_get(), 'hello\n') + eq(text.get('1.0', 'end'), 'world\n') + eq(text.tag_ranges('sel'), ()) + + # Cursor move to start of the line + eq(text.index('insert'), '1.0') + + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/Misc/NEWS.d/next/IDLE/2026-02-23-17-28-34.gh-issue-94521.vlr8XB.rst b/Misc/NEWS.d/next/IDLE/2026-02-23-17-28-34.gh-issue-94521.vlr8XB.rst new file mode 100644 index 000000000000000..1d46b3a1a2ca949 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2026-02-23-17-28-34.gh-issue-94521.vlr8XB.rst @@ -0,0 +1 @@ +Copy or cut current line while no selection. Patch by Shixian Li.