mirror of
https://github.com/python/cpython.git
synced 2026-10-28 10:25:05 +08:00
On Windows, split the command line as the Python executable does instead of using the POSIX rules, so that backslashes are not escape characters. Display previous arguments quoted and joined, not as a Tcl list.
175 lines
6.3 KiB
Python
175 lines
6.3 KiB
Python
"""
|
|
Idlelib objects with no external idlelib dependencies
|
|
which are needed in more than one idlelib module.
|
|
|
|
They are included here because
|
|
a) they don't particularly belong elsewhere; or
|
|
b) because inclusion here simplifies the idlelib dependency graph.
|
|
|
|
TODO:
|
|
* Python versions (editor and help_about),
|
|
* tk version and patchlevel (pyshell, help_about, maxos?, editor?),
|
|
* std streams (pyshell, run),
|
|
* warning stuff (pyshell, run).
|
|
"""
|
|
import re
|
|
import sys
|
|
|
|
# .pyw is for Windows; .pyi is for typing stub files.
|
|
# The extension order is needed for iomenu open/save dialogs.
|
|
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 = root.tk_scaling() # tkinter method new in 3.16
|
|
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
|
|
if sys.platform == 'win32': # pragma: no cover
|
|
def fix_win_hidpi(): # Called in pyshell and turtledemo.
|
|
try:
|
|
import ctypes
|
|
PROCESS_SYSTEM_DPI_AWARE = 1 # Int required.
|
|
ctypes.OleDLL('shcore').SetProcessDpiAwareness(PROCESS_SYSTEM_DPI_AWARE)
|
|
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,
|
|
'<<Paste>>',
|
|
'catch {%W delete sel.first sel.last}\n' +
|
|
root.bind_class(cls, '<<Paste>>'))
|
|
|
|
|
|
# Mouse wheel handling.
|
|
|
|
def x11_buttons(widget):
|
|
"""Return whether Tk reports wheel rotations to widget as button events.
|
|
|
|
On X11, Tk 8.6 and older report a mouse wheel rotation as a
|
|
<Button-4> or <Button-5> event. Tk 8.7 and newer report it as a
|
|
<MouseWheel> event, as Tk always did on Windows and macOS. Which of
|
|
the two a widget gets depends on its windowing system, which is a
|
|
property of its display, so a widget is needed, not just the version.
|
|
"""
|
|
from tkinter import TkVersion
|
|
return TkVersion < 8.7 and widget._windowingsystem == 'x11'
|
|
|
|
|
|
def bind_wheel(widget, func): # Called in editor and tree.
|
|
"Bind func to the events that Tk sends widget for a wheel rotation."
|
|
if x11_buttons(widget):
|
|
widget.bind('<Button-4>', func)
|
|
widget.bind('<Button-5>', func)
|
|
else:
|
|
widget.bind('<MouseWheel>', func)
|
|
|
|
|
|
def wheel_event(event, widget=None):
|
|
"""Handle a scrollwheel event by scrolling 5 lines.
|
|
|
|
For a <MouseWheel> event, event.delta is 120*n on Windows and X11,
|
|
and -1*n on macOS, where n can be > 1 if one scrolls fast. Flicking
|
|
the wheel generates up to maybe 20 events with n up to 10 or more.
|
|
Macs use wheel down (delta = 1*n) to scroll up, so positive delta
|
|
means to scroll up on all systems.
|
|
|
|
A <Button-4> or <Button-5> event (see x11_buttons) says up or down
|
|
by its number, and has no delta; a wheel event has no number.
|
|
|
|
The widget parameter is needed so tree label bindings can pass the
|
|
underlying canvas. If tree is replaced by ttk.Treeview, it can go.
|
|
|
|
This function depends on widget.yview to not be overridden by
|
|
a subclass.
|
|
"""
|
|
up = event.num == 4 if event.num in (4, 5) else event.delta > 0
|
|
lines = -5 if up else 5
|
|
widget = event.widget if widget is None else widget
|
|
widget.yview('scroll', lines, 'units')
|
|
return 'break'
|
|
|
|
|
|
_cli_token_re = re.compile(r"""
|
|
(?P<backslashes>\\*)(?P<quotes>"+)
|
|
| (?P<literal>\\+|[^ \t"\\]+) # backslashes not followed by a quote
|
|
| (?P<space>[ \t]+)
|
|
""", re.VERBOSE)
|
|
|
|
|
|
def _split_windows(cli_string):
|
|
"""Split a command line into arguments as the C runtime does.
|
|
|
|
See https://learn.microsoft.com/cpp/c-language/parsing-c-command-line-arguments
|
|
"""
|
|
args = []
|
|
arg = None # None when not in an argument.
|
|
quoted = False
|
|
for m in _cli_token_re.finditer(cli_string):
|
|
match m.lastgroup:
|
|
case 'space' if not quoted:
|
|
if arg is not None:
|
|
args.append(arg)
|
|
arg = None
|
|
case 'space' | 'literal':
|
|
arg = (arg or '') + m[0]
|
|
case _: # Backslashes followed by quotes.
|
|
count = len(m['backslashes'])
|
|
escaped = count % 2 # Odd backslashes escape a quote.
|
|
bare = len(m['quotes']) - escaped
|
|
# In a quoted part every two quotes give a literal quote;
|
|
# if not quoted, the first quote opens a quoted part.
|
|
literal = escaped + ((bare + quoted - 1) // 2 if bare else 0)
|
|
arg = (arg or '') + '\\' * (count // 2) + '"' * literal
|
|
quoted ^= bare % 2
|
|
if arg is not None:
|
|
args.append(arg)
|
|
return args
|
|
|
|
|
|
def split_cli_args(cli_string): # Called in query.
|
|
"Split a command line as the Python executable does (gh-93016)."
|
|
if sys.platform == 'win32':
|
|
return _split_windows(cli_string)
|
|
import shlex
|
|
return shlex.split(cli_string)
|
|
|
|
|
|
def join_cli_args(cli_args): # Called in query.
|
|
"Join arguments into a command line which split_cli_args() splits back."
|
|
if sys.platform == 'win32':
|
|
import subprocess
|
|
return subprocess.list2cmdline(cli_args)
|
|
import shlex
|
|
return shlex.join(cli_args)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
from unittest import main
|
|
main('idlelib.idle_test.test_util', verbosity=2)
|