mirror of
https://github.com/python/cpython.git
synced 2026-05-04 09:31:02 +00:00
Merge 17599c706c into 3efd2f4db6
This commit is contained in:
commit
feff24cded
4 changed files with 64 additions and 34 deletions
|
|
@ -182,19 +182,12 @@ def get_argspec(ob):
|
|||
# If fob has no argument, use default callable argspec.
|
||||
argspec = _default_callable_argspec
|
||||
|
||||
lines = (textwrap.wrap(argspec, _MAX_COLS, subsequent_indent=_INDENT)
|
||||
if len(argspec) > _MAX_COLS else [argspec] if argspec else [])
|
||||
lines = [argspec] if argspec else []
|
||||
|
||||
# Augment lines from docstring, if any, and join to get argspec.
|
||||
doc = inspect.getdoc(ob)
|
||||
if doc:
|
||||
for line in doc.split('\n', _MAX_LINES)[:_MAX_LINES]:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
break
|
||||
if len(line) > _MAX_COLS:
|
||||
line = line[: _MAX_COLS - 3] + '...'
|
||||
lines.append(line)
|
||||
lines.extend(map(str.strip, doc.split('\n')))
|
||||
argspec = '\n'.join(lines)
|
||||
|
||||
return argspec or _default_callable_argspec
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@
|
|||
Used by calltip.py.
|
||||
"""
|
||||
from tkinter import Label, LEFT, SOLID, TclError
|
||||
from tkinter.scrolledtext import ScrolledText
|
||||
|
||||
from idlelib.tooltip import TooltipBase
|
||||
|
||||
HIDE_EVENT = "<<calltipwindow-hide>>"
|
||||
HIDE_SEQUENCES = ("<Key-Escape>", "<FocusOut>")
|
||||
HIDE_SEQUENCES = ("<Key-Escape>",)
|
||||
CHECKHIDE_EVENT = "<<calltipwindow-checkhide>>"
|
||||
CHECKHIDE_SEQUENCES = ("<KeyRelease>", "<ButtonRelease>")
|
||||
CHECKHIDE_TIME = 100 # milliseconds
|
||||
|
|
@ -16,6 +17,13 @@
|
|||
MARK_RIGHT = "calltipwindowregion_right"
|
||||
|
||||
|
||||
def _widget_size(widget):
|
||||
widget.update()
|
||||
width = widget.winfo_width()
|
||||
height = widget.winfo_height()
|
||||
return width, height
|
||||
|
||||
|
||||
class CalltipWindow(TooltipBase):
|
||||
"""A call-tip widget for tkinter text widgets."""
|
||||
|
||||
|
|
@ -74,16 +82,31 @@ def showtip(self, text, parenleft, parenright):
|
|||
int, self.anchor_widget.index(parenleft).split("."))
|
||||
|
||||
super().showtip()
|
||||
self.tipwindow.wm_attributes("-topmost", 1)
|
||||
|
||||
self._bind_events()
|
||||
|
||||
def showcontents(self):
|
||||
"""Create the call-tip widget."""
|
||||
self.label = Label(self.tipwindow, text=self.text, justify=LEFT,
|
||||
self.label = Label(self.tipwindow, text=self.text, font=self.anchor_widget['font'])
|
||||
self.label.pack()
|
||||
old_w, old_h = _widget_size(self.label)
|
||||
self.label.forget()
|
||||
|
||||
self.label = ScrolledText(self.tipwindow, wrap="word",
|
||||
background="#ffffd0", foreground="black",
|
||||
relief=SOLID, borderwidth=1,
|
||||
font=self.anchor_widget['font'])
|
||||
self.label.insert("1.0", self.text)
|
||||
self.label.config(state="disabled")
|
||||
self.label.pack()
|
||||
new_w, new_h = _widget_size(self.label)
|
||||
|
||||
if self.label.yview()[1] == 1: # already shown entire text
|
||||
self.label.vbar.forget()
|
||||
|
||||
w, h = min(old_w, new_w), min(old_h, new_h)
|
||||
self.tipwindow.geometry("%dx%d" % (w, h))
|
||||
|
||||
def checkhide_event(self, event=None):
|
||||
"""Handle CHECK_HIDE_EVENT: call hidetip or reschedule."""
|
||||
|
|
@ -156,6 +179,8 @@ def _bind_events(self):
|
|||
self.hide_event)
|
||||
for seq in HIDE_SEQUENCES:
|
||||
self.anchor_widget.event_add(HIDE_EVENT, seq)
|
||||
if self.tipwindow:
|
||||
self.tipwindow.bind("<Key-Escape>", self.hide_event)
|
||||
|
||||
def _unbind_events(self):
|
||||
"""Unbind event handlers."""
|
||||
|
|
|
|||
|
|
@ -93,19 +93,20 @@ class SB: __call__ = None
|
|||
non-overlapping occurrences of the pattern in string by the
|
||||
replacement repl. repl can be either a string or a callable;
|
||||
if a string, backslash escapes in it are processed. If it is
|
||||
a callable, it's passed the Match object and must return''')
|
||||
a callable, it's passed the Match object and must return
|
||||
a replacement string to be used.''')
|
||||
tiptest(p.sub, '''\
|
||||
(repl, string, count=0)
|
||||
Return the string obtained by replacing the leftmost \
|
||||
non-overlapping occurrences o...''')
|
||||
non-overlapping occurrences of pattern in string by the replacement repl.''')
|
||||
|
||||
def test_signature_wrap(self):
|
||||
if textwrap.TextWrapper.__doc__ is not None:
|
||||
self.assertEqual(get_spec(textwrap.TextWrapper), '''\
|
||||
(width=70, initial_indent='', subsequent_indent='', expand_tabs=True,
|
||||
replace_whitespace=True, fix_sentence_endings=False, break_long_words=True,
|
||||
drop_whitespace=True, break_on_hyphens=True, tabsize=8, *, max_lines=None,
|
||||
placeholder=' [...]')
|
||||
self.assertEqual(get_spec(textwrap.TextWrapper).split('\n\n')[0], '''\
|
||||
(width=70, initial_indent='', subsequent_indent='', expand_tabs=True, \
|
||||
replace_whitespace=True, fix_sentence_endings=False, break_long_words=True, \
|
||||
drop_whitespace=True, break_on_hyphens=True, tabsize=8, *, max_lines=None, \
|
||||
placeholder=' [...]')
|
||||
Object for wrapping/filling text. The public interface consists of
|
||||
the wrap() and fill() methods; the other methods are just there for
|
||||
subclasses to override in order to tweak the default behaviour.
|
||||
|
|
@ -124,19 +125,15 @@ def bar(s='a'*100):
|
|||
def baz(s='a'*100, z='b'*100):
|
||||
pass
|
||||
|
||||
indent = calltip._INDENT
|
||||
|
||||
sfoo = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\
|
||||
"aaaaaaaaaa')"
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')"
|
||||
sbar = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\
|
||||
"aaaaaaaaaa')\nHello Guido"
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')"\
|
||||
"\nHello Guido"
|
||||
sbaz = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\
|
||||
"aaaaaaaaaa', z='bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"\
|
||||
"bbbbbbbbbbbbbbbbb\n" + indent + "bbbbbbbbbbbbbbbbbbbbbb"\
|
||||
"bbbbbbbbbbbbbbbbbbbbbb')"
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',"\
|
||||
" z='bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"\
|
||||
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')"
|
||||
|
||||
for func,doc in [(foo, sfoo), (bar, sbar), (baz, sbaz)]:
|
||||
with self.subTest(func=func, doc=doc):
|
||||
|
|
@ -145,15 +142,21 @@ def baz(s='a'*100, z='b'*100):
|
|||
def test_docline_truncation(self):
|
||||
def f(): pass
|
||||
f.__doc__ = 'a'*300
|
||||
self.assertEqual(get_spec(f), f"()\n{'a'*(calltip._MAX_COLS-3) + '...'}")
|
||||
self.assertEqual(get_spec(f), f"()\n{f.__doc__}")
|
||||
|
||||
@unittest.skipIf(MISSING_C_DOCSTRINGS,
|
||||
"Signature information for builtins requires docstrings")
|
||||
def test_multiline_docstring(self):
|
||||
# Test fewer lines than max.
|
||||
self.assertEqual(get_spec(range),
|
||||
"range(stop) -> range object\n"
|
||||
"range(start, stop[, step]) -> range object")
|
||||
self.assertEqual(get_spec(range), '''\
|
||||
range(stop) -> range object
|
||||
range(start, stop[, step]) -> range object
|
||||
|
||||
Return an object that produces a sequence of integers from start (inclusive)
|
||||
to stop (exclusive) by step. range(i, j) produces i, i+1, i+2, ..., j-1.
|
||||
start defaults to 0, and stop is omitted! range(4) produces 0, 1, 2, 3.
|
||||
These are exactly the valid indices for a list of 4 elements.
|
||||
When step is given, it specifies the increment (or decrement).''')
|
||||
|
||||
# Test max lines
|
||||
self.assertEqual(get_spec(bytes), '''\
|
||||
|
|
@ -161,13 +164,19 @@ def test_multiline_docstring(self):
|
|||
bytes(string, encoding[, errors]) -> bytes
|
||||
bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer
|
||||
bytes(int) -> bytes object of size given by the parameter initialized with null bytes
|
||||
bytes() -> empty bytes object''')
|
||||
bytes() -> empty bytes object
|
||||
|
||||
Construct an immutable array of bytes from:
|
||||
- an iterable yielding integers in range(256)
|
||||
- a text string encoded using the specified encoding
|
||||
- any object implementing the buffer API.
|
||||
- an integer''')
|
||||
|
||||
def test_multiline_docstring_2(self):
|
||||
# Test more than max lines
|
||||
def f(): pass
|
||||
f.__doc__ = 'a\n' * 15
|
||||
self.assertEqual(get_spec(f), '()' + '\na' * calltip._MAX_LINES)
|
||||
self.assertEqual(get_spec(f), '()\n' + f.__doc__[:-1])
|
||||
|
||||
def test_functions(self):
|
||||
def t1(): 'doc'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
"Calltip" windows now support text selection, scrolling and
|
||||
avoid truncating their content (in particular, docstrings
|
||||
are shown in full). Patch by Shixian Li.
|
||||
Loading…
Add table
Add a link
Reference in a new issue