From d7583af6d0f0e1c331712241c3578882bd076cb8 Mon Sep 17 00:00:00 2001 From: Shixian Li Date: Mon, 23 Feb 2026 17:12:01 +0800 Subject: [PATCH 1/3] Feat: Copy or cut current line while no selection --- Lib/idlelib/editor.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index 3128934763a1c08..1e1e871b1bde3be 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -605,13 +605,16 @@ def python_docs(self, event=None): return "break" def cut(self,event): + if not self.text.tag_ranges('sel'): + self.text.tag_add('sel', 'insert linestart', 'insert+1l linestart') + self.text.mark_set('insert', 'insert linestart') 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.tag_add('sel', 'insert linestart', 'insert+1l linestart') + self.text.mark_set('insert', 'insert linestart') self.text.event_generate("<>") return "break" From 4c9463fa395acff7f902167d7b545572bbaaf217 Mon Sep 17 00:00:00 2001 From: Shixian Li Date: Mon, 23 Feb 2026 17:29:08 +0800 Subject: [PATCH 2/3] Docs: Add news --- .../next/IDLE/2026-02-23-17-28-34.gh-issue-94521.vlr8XB.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/IDLE/2026-02-23-17-28-34.gh-issue-94521.vlr8XB.rst 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. From d6b41029aba0bf18a3fc0af41d36458ef7c41859 Mon Sep 17 00:00:00 2001 From: Shixian Li Date: Wed, 7 Oct 2026 22:33:30 +0800 Subject: [PATCH 3/3] gh-94521: Copy/cut current line without selecting it --- Doc/library/idle.rst | 4 +++ Lib/idlelib/editor.py | 16 ++++++---- Lib/idlelib/idle_test/test_editor.py | 46 ++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 6 deletions(-) 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 bc85c9e86431873..68db9ecd6c22184 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -609,16 +609,20 @@ def python_docs(self, event=None): def cut(self,event): if not self.text.tag_ranges('sel'): - self.text.tag_add('sel', 'insert linestart', 'insert+1l linestart') - self.text.mark_set('insert', 'insert linestart') - self.text.event_generate("<>") + 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"): - self.text.tag_add('sel', 'insert linestart', 'insert+1l linestart') + if not self.text.tag_ranges('sel'): self.text.mark_set('insert', 'insert linestart') - self.text.event_generate("<>") + 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 0dfe2f3c58befad..ff896f09395d28d 100644 --- a/Lib/idlelib/idle_test/test_editor.py +++ b/Lib/idlelib/idle_test/test_editor.py @@ -237,5 +237,51 @@ def test_rclick(self): pass +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)