Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions Lib/idlelib/calltip.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,19 +182,13 @@ 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)
for line in doc.split('\n'):
lines.append(line.strip())
argspec = '\n'.join(lines)

return argspec or _default_callable_argspec
Expand Down
30 changes: 27 additions & 3 deletions Lib/idlelib/calltip_w.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,26 @@
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

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."""

Expand Down Expand Up @@ -74,16 +82,30 @@ 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()
label_w, label_h = widget_size(self.label) # get the old version of tooltip window size
self.label.forget()

self.label = ScrolledText(self.tipwindow, wrap="word",
background="#ffffd0", foreground="black",
relief=SOLID, borderwidth=1,
font=self.anchor_widget['font'])
font=self.anchor_widget["font"])
self.label.insert("1.0", self.text)
self.label.config(state="disabled")
self.label.pack()
max_w, max_h = widget_size(self.label)

if self.label.yview()[1] == 1: # already shown entire text
self.label.vbar.forget()

self.tipwindow.geometry("%dx%d" % (min(label_w, max_w), min(label_h, max_h)))

def checkhide_event(self, event=None):
"""Handle CHECK_HIDE_EVENT: call hidetip or reschedule."""
Expand Down Expand Up @@ -156,6 +178,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."""
Expand Down
62 changes: 17 additions & 45 deletions Lib/idlelib/idle_test/test_calltip.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,24 +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...''')
Return the string obtained by replacing the leftmost non-overlapping \
occurrences of pattern in string by the replacement repl.''')

def test_signature_wrap(self):
def test_signature(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=' [...]')
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.
If you want to completely replace the main wrapping algorithm,
you\'ll probably have to override _wrap_chunks().''')
self.assertEqual(get_spec(textwrap.TextWrapper).split('\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=' [...]')''')

def test_properly_formatted(self):

Expand All @@ -127,16 +123,14 @@ def baz(s='a'*100, z='b'*100):
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):
Expand All @@ -145,29 +139,7 @@ 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) + '...'}")

@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")

# Test max lines
self.assertEqual(get_spec(bytes), '''\
bytes(iterable_of_ints) -> bytes
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''')

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%s" % ('a'*300))

def test_functions(self):
def t1(): 'doc'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Make CallTips selectable

The text display widget in the "CalltipWindow" has been changed from
"tk.Label" to "ScrolledText", and now the text in the "Calltip" window can
be selected with mouse. The display size of the "CalltipWindow" is set to
the smaller value between the size when using the "tk.Label" widget and the
default size of "tk.Text". When the displayed text exceeds the display area
of the "ScrolledText" window, showing the vertical scrollbar; otherwise,
hiding the scrollbar. Since more text can be displayed, "argspec" is no
longer truncated, and the tests related to the max lines or text truncation
have been removed from the unit tests. Contributed by Shixian Li.
Loading