Skip to content

Commit ce6a886

Browse files
authored
Merge branch 'main' into pyrepl-osc-escape-sequences
2 parents 995f364 + 9777e8a commit ce6a886

24 files changed

Lines changed: 504 additions & 159 deletions

‎Doc/library/pathlib.rst‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,11 @@ we also call *flavours*:
131131
>>> PurePath(Path('foo'), Path('bar'))
132132
PurePosixPath('foo/bar')
133133

134-
When *pathsegments* is empty, the current directory is assumed::
134+
When *pathsegments* is empty or consists only of empty strings,
135+
the current directory is assumed::
135136

136-
>>> PurePath()
137-
PurePosixPath('.')
137+
>>> PurePath(), PurePath('')
138+
(PurePosixPath('.'), PurePosixPath('.'))
138139

139140
If a segment is an absolute path, all previous segments are ignored
140141
(like :func:`os.path.join`)::
@@ -1040,15 +1041,17 @@ Querying file type and status
10401041

10411042
.. method:: Path.exists(*, follow_symlinks=True)
10421043

1043-
Return ``True`` if the path points to an existing file or directory.
1044-
``False`` will be returned if the path is invalid, inaccessible or missing.
1044+
Return ``True`` if the path points to an existing file or directory and
1045+
``False`` if the path is invalid, inaccessible or missing.
10451046
Use :meth:`Path.stat` to distinguish between these cases.
10461047

10471048
This method normally follows symlinks; to check if a symlink exists, add
10481049
the argument ``follow_symlinks=False``.
10491050

10501051
::
10511052

1053+
>>> Path('').exists() # The current directory.
1054+
True
10521055
>>> Path('.').exists()
10531056
True
10541057
>>> Path('setup.py').exists()

‎Include/internal/pycore_object.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -957,7 +957,8 @@ extern int _PyObject_IsInstanceDictEmpty(PyObject *);
957957

958958
// Export for 'math' shared extension
959959
PyAPI_FUNC(PyObject*) _PyObject_LookupSpecial(PyObject *, PyObject *);
960-
PyAPI_FUNC(int) _PyObject_LookupSpecialMethod(PyObject *attr, _PyStackRef *method_and_self);
960+
PyAPI_FUNC(int) _PyObject_LookupSpecialMethod(PyObject *attr, _PyStackRef *method,
961+
_PyStackRef *self);
961962

962963
// Calls the method named `attr` on `self`, but does not set an exception if
963964
// the attribute does not exist.

‎Include/internal/pycore_unicodeobject.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ extern int _PyUnicodeWriter_FormatV(
244244

245245
/* --- iconv Codec -------------------------------------------------------- */
246246

247-
#ifdef HAVE_ICONV
247+
#ifdef _Py_HAVE_ICONV
248248
extern PyObject* _PyUnicode_DecodeIconv(
249249
const char *encoding, /* iconv encoding name */
250250
const char *string, /* encoded string */

‎Lib/idlelib/configdialog.py‎

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,8 +1146,8 @@ def create_page_keys(self):
11461146
selected keyset. The keybindings are loaded in load_keys_list()
11471147
and are pairs of (event, [keys]) where keys can be a list
11481148
of one or more key combinations to bind to the same event.
1149-
Mouse button 1 click invokes on_bindingslist_select(), which
1150-
allows button_new_keys to be clicked.
1149+
Mouse button 1 click or Up or Down key invokes
1150+
on_bindingslist_select(), which allows button_new_keys to be clicked.
11511151
11521152
So, an item is selected in listbindings, which activates
11531153
button_new_keys, and clicking button_new_keys calls function
@@ -1221,9 +1221,12 @@ def create_page_keys(self):
12211221
scroll_target_y = Scrollbar(frame_target)
12221222
scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
12231223
self.bindingslist = Listbox(
1224-
frame_target, takefocus=FALSE, exportselection=FALSE)
1224+
frame_target, takefocus=True, exportselection=FALSE)
12251225
self.bindingslist.bind('<ButtonRelease-1>',
12261226
self.on_bindingslist_select)
1227+
self.bindingslist.bind('<KeyRelease-Up>', self.on_bindingslist_select)
1228+
self.bindingslist.bind('<KeyRelease-Down>',
1229+
self.on_bindingslist_select)
12271230
scroll_target_y['command'] = self.bindingslist.yview
12281231
scroll_target_x['command'] = self.bindingslist.xview
12291232
self.bindingslist['yscrollcommand'] = scroll_target_y.set
@@ -1427,7 +1430,14 @@ def save_as_new_key_set(self):
14271430
self.create_new_key_set(new_keys_name)
14281431

14291432
def on_bindingslist_select(self, event):
1430-
"Activate button to assign new keys to selected action."
1433+
"""Activate button to assign new keys to selected action.
1434+
1435+
Event can result from either mouse click or Up or Down key.
1436+
The keys move the selection, but not the anchor used by
1437+
get_new_keys and var_changed_keybinding.
1438+
"""
1439+
if event.type.name == 'KeyRelease':
1440+
self.bindingslist.selection_anchor(ACTIVE)
14311441
self.button_new_keys.state(('!disabled',))
14321442

14331443
def create_new_key_set(self, new_key_set_name):
@@ -1465,9 +1475,8 @@ def load_keys_list(self, keyset_name):
14651475
14661476
An action/key binding can be selected to change the key binding.
14671477
"""
1468-
reselect = False
1478+
list_index = 0
14691479
if self.bindingslist.curselection():
1470-
reselect = True
14711480
list_index = self.bindingslist.index(ANCHOR)
14721481
keyset = idleConf.GetKeySet(keyset_name)
14731482
# 'set' is dict mapping virtual event to list of key events.
@@ -1482,10 +1491,11 @@ def load_keys_list(self, keyset_name):
14821491
if bind_name in changes['keys'][keyset_name]:
14831492
key = changes['keys'][keyset_name][bind_name]
14841493
self.bindingslist.insert(END, bind_name+' - '+key)
1485-
if reselect:
1486-
self.bindingslist.see(list_index)
1487-
self.bindingslist.select_set(list_index)
1488-
self.bindingslist.select_anchor(list_index)
1494+
self.bindingslist.see(list_index)
1495+
self.bindingslist.select_set(list_index)
1496+
self.bindingslist.select_anchor(list_index)
1497+
self.bindingslist.activate(list_index)
1498+
self.button_new_keys.state(('!disabled',))
14891499

14901500
@staticmethod
14911501
def save_new_key_set(keyset_name, keyset):
@@ -2124,6 +2134,8 @@ def create_frame_help(self):
21242134
scroll_helplist['command'] = self.helplist.yview
21252135
self.helplist['yscrollcommand'] = scroll_helplist.set
21262136
self.helplist.bind('<ButtonRelease-1>', self.help_source_selected)
2137+
self.helplist.bind('<KeyRelease-Up>', self.help_source_selected)
2138+
self.helplist.bind('<KeyRelease-Down>', self.help_source_selected)
21272139

21282140
frame_buttons = Frame(self)
21292141
self.button_helplist_edit = Button(
@@ -2146,7 +2158,14 @@ def create_frame_help(self):
21462158
self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
21472159

21482160
def help_source_selected(self, event):
2149-
"Handle event for selecting additional help."
2161+
"""Handle event for selecting additional help.
2162+
2163+
Event can result from either mouse click or Up or Down key.
2164+
The keys move the selection, but not the anchor used by
2165+
helplist_item_edit and helplist_item_remove.
2166+
"""
2167+
if event.type.name == 'KeyRelease':
2168+
self.helplist.selection_anchor(ACTIVE)
21502169
self.set_add_delete_state()
21512170

21522171
def set_add_delete_state(self):

‎Lib/idlelib/idle_test/test_configdialog.py‎

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
import unittest
1010
from unittest import mock
1111
from idlelib.idle_test.mock_idle import Func
12-
from tkinter import (Tk, StringVar, IntVar, BooleanVar, DISABLED, NORMAL)
12+
from tkinter import (Tk, StringVar, IntVar, BooleanVar, DISABLED, NORMAL,
13+
EventType)
14+
from types import SimpleNamespace
1315
from idlelib import config
1416
from idlelib.configdialog import idleConf, changes, tracers
1517

@@ -1060,6 +1062,14 @@ def test_on_bindingslist_select(self):
10601062
self.assertEqual(b.get('anchor'), 'find')
10611063
self.assertNotIn('disabled', d.button_new_keys.state())
10621064

1065+
# gh-75234: Up and Down keys move the active item, but not the
1066+
# anchor; the handler moves the anchor.
1067+
d.button_new_keys.state(('disabled',))
1068+
b.activate(0)
1069+
d.on_bindingslist_select(SimpleNamespace(type=EventType.KeyRelease))
1070+
self.assertEqual(b.get('anchor'), 'copy')
1071+
self.assertNotIn('disabled', d.button_new_keys.state())
1072+
10631073
def test_create_new_key_set_and_save_new_key_set(self):
10641074
eq = self.assertEqual
10651075
d = self.page
@@ -1110,11 +1120,14 @@ def test_load_keys_list(self):
11101120
'force-open-completions - <Control-Key-space>',
11111121
'spam - <Shift-Key-a>')
11121122

1113-
# No current selection.
1123+
# No current selection: select the first item.
1124+
d.button_new_keys.state(('disabled',))
11141125
d.load_keys_list('my keys')
11151126
eq(b.get(0, 'end'), expected)
1116-
eq(b.get('anchor'), '')
1117-
eq(b.curselection(), ())
1127+
eq(b.get('anchor'), 'copy - <Control-Key-c> <Control-Key-C>')
1128+
eq(b.curselection(), (0, ))
1129+
eq(b.index('active'), 0)
1130+
self.assertNotIn('disabled', d.button_new_keys.state())
11181131

11191132
# Check selection.
11201133
b.selection_set(1)
@@ -1584,6 +1597,26 @@ def test_helplist_item_remove(self):
15841597
eq(fr.user_helplist, [])
15851598
self.assertTrue(fr.upc.called == fr.set.called == 1)
15861599

1600+
def test_helplist_item_remove_keyboard_selection(self):
1601+
# gh-75234: Up and Down keys move the active item, but not the
1602+
# anchor; the handler moves the anchor.
1603+
eq = self.assertEqual
1604+
fr = self.frame
1605+
fr.helplist.delete(0, 'end')
1606+
fr.helplist.insert('end', 'name1', 'name2')
1607+
fr.helplist.selection_anchor(0)
1608+
fr.helplist.selection_set(1)
1609+
fr.helplist.activate(1)
1610+
fr.user_helplist.clear()
1611+
fr.user_helplist.extend([('name1', 'file1'), ('name2', 'file2')])
1612+
fr.set.called = fr.upc.called = 0
1613+
1614+
fr.help_source_selected(SimpleNamespace(type=EventType.KeyRelease))
1615+
eq(fr.helplist.get('anchor'), 'name2')
1616+
fr.helplist_item_remove()
1617+
eq(fr.helplist.get(0, 'end'), ('name1',))
1618+
eq(fr.user_helplist, [('name1', 'file1')])
1619+
15871620
def test_update_help_changes(self):
15881621
fr = self.frame
15891622
self.addCleanup(setattr, fr, 'update_help_changes', Func()) # Re-mask method.

‎Lib/test/test_external_inspection.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -447,14 +447,15 @@ def test_long_task_name_is_truncated(self):
447447
async def main():
448448
asyncio.create_task(asyncio.sleep(10_000), name="x" * 300)
449449
await asyncio.sleep(0)
450-
return [
450+
names = [
451451
task.task_name
452452
for info in RemoteUnwinder(os.getpid()).get_all_awaited_by()
453453
for task in info.awaited_by
454454
]
455+
return asyncio.current_task().get_name(), names
455456

456-
names = asyncio.run(main())
457-
self.assertIn("Task-1", names)
457+
main_name, names = asyncio.run(main())
458+
self.assertIn(main_name, names)
458459
self.assertEqual([len(n) for n in names if n.startswith("x")], [255])
459460

460461
@skip_if_not_supported

‎Lib/test/test_pyrepl/test_pyrepl.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2431,6 +2431,8 @@ def test_no_newline(self):
24312431
env = os.environ.copy()
24322432
env.pop("PYTHON_BASIC_REPL", "")
24332433
env["PYTHON_BASIC_REPL"] = "1"
2434+
# Ensure user's .inputrc doesn't interfere with basic REPL output
2435+
env["INPUTRC"] = "/dev/null"
24342436

24352437
commands = "print('Something pretty long', end='')\nexit()\n"
24362438
expected_output_sequence = "Something pretty long>>> exit()"

‎Lib/test/test_shutil.py‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -552,11 +552,7 @@ def raiser(fn, *args, **kwargs):
552552
os.lstat = orig_lstat
553553

554554
def test_rmtree_uses_safe_fd_version_if_available(self):
555-
_use_fd_functions = ({os.open, os.stat, os.unlink, os.rmdir} <=
556-
os.supports_dir_fd and
557-
os.listdir in os.supports_fd and
558-
os.stat in os.supports_follow_symlinks)
559-
if _use_fd_functions:
555+
if shutil._use_fd_functions:
560556
self.assertTrue(shutil.rmtree.avoids_symlink_attacks)
561557
tmp_dir = self.mkdtemp()
562558
d = os.path.join(tmp_dir, 'a')
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix editing help sources and key bindings in the IDLE Settings dialog after
2+
selecting them with the keyboard.

‎Modules/_codecsmodule.c‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ _codecs_code_page_decode_impl(PyObject *module, int codepage,
642642

643643
#endif /* MS_WINDOWS */
644644

645-
#ifdef HAVE_ICONV
645+
#ifdef _Py_HAVE_ICONV
646646

647647
/*[clinic input]
648648
_codecs.iconv_decode
@@ -665,7 +665,7 @@ _codecs_iconv_decode_impl(PyObject *module, const char *encoding,
665665
return codec_tuple(decoded, consumed);
666666
}
667667

668-
#endif /* HAVE_ICONV */
668+
#endif /* _Py_HAVE_ICONV */
669669

670670
/* --- Encoder ------------------------------------------------------------ */
671671

@@ -977,7 +977,7 @@ _codecs_code_page_encode_impl(PyObject *module, int code_page, PyObject *str,
977977

978978
#endif /* MS_WINDOWS */
979979

980-
#ifdef HAVE_ICONV
980+
#ifdef _Py_HAVE_ICONV
981981

982982
/*[clinic input]
983983
_codecs.iconv_encode
@@ -996,7 +996,7 @@ _codecs_iconv_encode_impl(PyObject *module, const char *encoding,
996996
PyUnicode_GET_LENGTH(str));
997997
}
998998

999-
#endif /* HAVE_ICONV */
999+
#endif /* _Py_HAVE_ICONV */
10001000

10011001
/* --- Error handler registry --------------------------------------------- */
10021002

0 commit comments

Comments
 (0)