From acf8278987589681720d23474b149dc7b5e9dc89 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Wed, 1 Jul 2026 00:58:39 -0400 Subject: [PATCH] gh-152728: IDLE - move 3 toplevel fix_xyz functions to idlelb.util (GH-152729) IDLE - move 3 toplevel fix_xyz functions to idlelb.util Move idlelib functions run.fix_scaling, editor.fixwordbreaks (as fix_word_breaks). All are used in at least 3 modules. (cherry picked from commit 53ca52ddb0c2725a1c34e4eb2245339cd3dcaa84) Co-authored-by: Terry Jan Reedy --- Lib/idlelib/News3.txt | 3 ++ Lib/idlelib/editor.py | 17 +++------- Lib/idlelib/filelist.py | 6 ++-- Lib/idlelib/idle_test/test_editmenu.py | 4 +-- Lib/idlelib/idle_test/test_sidebar.py | 7 ++-- Lib/idlelib/pyshell.py | 22 ++++--------- Lib/idlelib/run.py | 13 ++------ Lib/idlelib/util.py | 33 +++++++++++++++++++ ...-07-01-00-15-58.gh-issue-152728.yxIhMN.rst | 2 ++ 9 files changed, 58 insertions(+), 49 deletions(-) create mode 100644 Misc/NEWS.d/next/IDLE/2026-07-01-00-15-58.gh-issue-152728.yxIhMN.rst diff --git a/Lib/idlelib/News3.txt b/Lib/idlelib/News3.txt index 97becb858fea33..dc18b6e07eb364 100644 --- a/Lib/idlelib/News3.txt +++ b/Lib/idlelib/News3.txt @@ -4,6 +4,9 @@ Released on 2026-10-01 ========================= +gh-152728: Move functions run.fix_scaling, editor.fixwordbreaks (as fix_word_breaks) +and pyshell.fix_x11_paste to module util. Patch by Terry J. Reedy. + gh-85320: IDLE now reads and writes its configuration files and the breakpoints file using UTF-8 instead of the locale encoding. Files with non-ASCII characters and non-UTF-8 encoding may need diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index 239bf5af470567..a040d791bdeb52 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -1704,19 +1704,10 @@ def get_accelerator(keydefs, eventname): return s -def fixwordbreaks(root): - # On Windows, tcl/tk breaks 'words' only on spaces, as in Command Prompt. - # We want Motif style everywhere. See #21474, msg218992 and followup. - tk = root.tk - tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded - tk.call('set', 'tcl_wordchars', r'\w') - tk.call('set', 'tcl_nonwordchars', r'\W') - - -def _editor_window(parent): # htest # - # error if close master window first - timer event, after script - root = parent - fixwordbreaks(root) +def _editor_window(root): # htest # + # Error if close master window first - timer event, after script + from util import fix_word_breaks + fix_word_breaks(root) if sys.argv[1:]: filename = sys.argv[1] else: diff --git a/Lib/idlelib/filelist.py b/Lib/idlelib/filelist.py index e27e5d32a0ff63..384908f911f515 100644 --- a/Lib/idlelib/filelist.py +++ b/Lib/idlelib/filelist.py @@ -112,12 +112,12 @@ def canonize(self, filename): def _test(): # TODO check and convert to htest + # Maybe redundant with test_filelist.FileListTest.test_new_empty. from tkinter import Tk - from idlelib.editor import fixwordbreaks - from idlelib.run import fix_scaling + from idlelib.util import fix_scaling, fix_word_breaks root = Tk() fix_scaling(root) - fixwordbreaks(root) + fix_word_breaks(root) root.withdraw() flist = FileList(root) flist.new() diff --git a/Lib/idlelib/idle_test/test_editmenu.py b/Lib/idlelib/idle_test/test_editmenu.py index 17478473a3d1b2..0d1eb9054918da 100644 --- a/Lib/idlelib/idle_test/test_editmenu.py +++ b/Lib/idlelib/idle_test/test_editmenu.py @@ -7,7 +7,7 @@ import tkinter as tk from tkinter import ttk import unittest -from idlelib import pyshell +from idlelib.util import fix_x11_paste class PasteTest(unittest.TestCase): '''Test pasting into widgets that allow pasting. @@ -18,7 +18,7 @@ class PasteTest(unittest.TestCase): def setUpClass(cls): cls.root = root = tk.Tk() cls.root.withdraw() - pyshell.fix_x11_paste(root) + fix_x11_paste(root) cls.text = tk.Text(root) cls.entry = tk.Entry(root) cls.tentry = ttk.Entry(root) diff --git a/Lib/idlelib/idle_test/test_sidebar.py b/Lib/idlelib/idle_test/test_sidebar.py index 4157a4b4dcdd2a..dc3431d8d8ef88 100644 --- a/Lib/idlelib/idle_test/test_sidebar.py +++ b/Lib/idlelib/idle_test/test_sidebar.py @@ -11,11 +11,10 @@ from idlelib.idle_test.tkinter_testing_utils import run_in_tk_mainloop from idlelib.delegator import Delegator -from idlelib.editor import fixwordbreaks from idlelib.percolator import Percolator import idlelib.pyshell -from idlelib.pyshell import fix_x11_paste, PyShell, PyShellFileList -from idlelib.run import fix_scaling +from idlelib.pyshell import PyShell, PyShellFileList +from idlelib.util import fix_scaling, fix_word_breaks, fix_x11_paste import idlelib.sidebar from idlelib.sidebar import get_end_linenumber, get_lineno @@ -403,7 +402,7 @@ def setUpClass(cls): root.withdraw() fix_scaling(root) - fixwordbreaks(root) + fix_word_breaks(root) fix_x11_paste(root) cls.flist = flist = PyShellFileList(root) diff --git a/Lib/idlelib/pyshell.py b/Lib/idlelib/pyshell.py index b1662491935e4a..439f98170292b0 100755 --- a/Lib/idlelib/pyshell.py +++ b/Lib/idlelib/pyshell.py @@ -36,13 +36,14 @@ from idlelib.delegator import Delegator from idlelib import debugger from idlelib import debugger_r -from idlelib.editor import EditorWindow, fixwordbreaks +from idlelib.editor import EditorWindow from idlelib.filelist import FileList from idlelib.outwin import OutputWindow from idlelib import replace from idlelib import rpc from idlelib.run import idle_formatwarning, StdInputFile, StdOutputFile from idlelib.undo import UndoDelegator +from idlelib.util import fix_word_breaks # Default for testing; defaults to True in main() for running. use_subprocess = False @@ -881,9 +882,9 @@ def __init__(self, flist=None): if ms[2][0] != "shell": ms.insert(2, ("shell", "She_ll")) self.interp = ModifiedInterpreter(self) - if flist is None: + if flist is None: # TODO possible? root and flist in main. root = Tk() - fixwordbreaks(root) + fix_word_breaks(root) root.withdraw() flist = PyShellFileList(root) @@ -1452,17 +1453,6 @@ def on_squeezed_expand(self, index, text, tags): self.shell_sidebar.update_sidebar() -def fix_x11_paste(root): - "Make paste replace selection on x11. See issue #5124." - if root._windowingsystem == 'x11': - for cls in 'Text', 'Entry', 'Spinbox': - root.bind_class( - cls, - '<>', - 'catch {%W delete sel.first sel.last}\n' + - root.bind_class(cls, '<>')) - - usage_msg = """\ USAGE: idle [-deins] [-t title] [file]* @@ -1522,6 +1512,7 @@ def main(): from platform import system from idlelib import testing # bool value from idlelib import macosx + from idlelib.util import fix_scaling, fix_x11_paste global flist, root, use_subprocess @@ -1607,7 +1598,6 @@ def main(): NoDefaultRoot() root = Tk(className="Idle") root.withdraw() - from idlelib.run import fix_scaling fix_scaling(root) # set application icon @@ -1629,7 +1619,7 @@ def main(): root.wm_iconphoto(True, *icons) # start editor and/or shell windows: - fixwordbreaks(root) + fix_word_breaks(root) fix_x11_paste(root) flist = PyShellFileList(root) macosx.setupApp(root, flist) diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index e1c40fee8f4805..f5564ccf90f7f4 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -25,6 +25,7 @@ from idlelib import iomenu # encoding from idlelib import rpc # multiple objects from idlelib import stackviewer # StackTreeItem +from idlelib import util # fix_scaling import __main__ import tkinter # Use tcl and, if startup fails, messagebox. @@ -216,7 +217,7 @@ def show_socket_error(err, address): import tkinter from tkinter.messagebox import showerror root = tkinter.Tk() - fix_scaling(root) + util.fix_scaling(root) root.withdraw() showerror( "Subprocess Connection Error", @@ -389,16 +390,6 @@ def exit(): sys.exit(0) -def fix_scaling(root): - """Scale fonts on HiDPI displays.""" - import tkinter.font - scaling = float(root.tk.call('tk', 'scaling')) - if scaling > 1.4: - for name in tkinter.font.names(root): - font = tkinter.font.Font(root=root, name=name, exists=True) - size = int(font['size']) - if size < 0: - font['size'] = round(-0.75*size) def fixdoc(fun, text): diff --git a/Lib/idlelib/util.py b/Lib/idlelib/util.py index e05604ab4853f6..bf88c905e1d177 100644 --- a/Lib/idlelib/util.py +++ b/Lib/idlelib/util.py @@ -19,6 +19,20 @@ py_extensions = ('.py', '.pyw', '.pyi') +# fix_x functions seem only needed once per process. + +def fix_scaling(root): # Called in filelist _test, pyshell, and run. + """Scale fonts on HiDPI displays, once per process.""" + import tkinter.font + scaling = float(root.tk.call('tk', 'scaling')) + if scaling > 1.4: + for name in tkinter.font.names(root): + font = tkinter.font.Font(root=root, name=name, exists=True) + size = int(font['size']) + if size < 0: + font['size'] = round(-0.75*size) + + # Fix for HiDPI screens on Windows. CALL BEFORE ANY TK OPERATIONS! # URL for arguments for the ...Awareness call below. # https://msdn.microsoft.com/en-us/library/windows/desktop/dn280512(v=vs.85).aspx @@ -31,6 +45,25 @@ def fix_win_hidpi(): # Called in pyshell and turtledemo. except (ImportError, AttributeError, OSError): pass +def fix_word_breaks(root): # Called in editor htest, filelist _test, pyshell. + # On Windows, tcl/tk breaks 'words' only on spaces, as in Command Prompt. + # We want Motif style everywhere. See #21474, msg218992 and followup. + tk = root.tk + tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded + tk.call('set', 'tcl_wordchars', r'\w') + tk.call('set', 'tcl_nonwordchars', r'\W') + + +def fix_x11_paste(root): + "Make paste replace selection on x11. See issue #5124." + if root._windowingsystem == 'x11': + for cls in 'Text', 'Entry', 'Spinbox': + root.bind_class( + cls, + '<>', + 'catch {%W delete sel.first sel.last}\n' + + root.bind_class(cls, '<>')) + if __name__ == '__main__': from unittest import main diff --git a/Misc/NEWS.d/next/IDLE/2026-07-01-00-15-58.gh-issue-152728.yxIhMN.rst b/Misc/NEWS.d/next/IDLE/2026-07-01-00-15-58.gh-issue-152728.yxIhMN.rst new file mode 100644 index 00000000000000..d73512a615c189 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2026-07-01-00-15-58.gh-issue-152728.yxIhMN.rst @@ -0,0 +1,2 @@ +Move functions run.fix_scaling, editor.fixwordbreaks (as fix_word_breaks) +and pyshell.fix_x11_paste to idlelib.util.