Skip to content

Commit 189c268

Browse files
committed
Feat: Use ScrolledText widget to show tooltip
1 parent 2b4feee commit 189c268

File tree

3 files changed

+30
-56
lines changed

3 files changed

+30
-56
lines changed

Lib/idlelib/calltip.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -182,19 +182,13 @@ def get_argspec(ob):
182182
# If fob has no argument, use default callable argspec.
183183
argspec = _default_callable_argspec
184184

185-
lines = (textwrap.wrap(argspec, _MAX_COLS, subsequent_indent=_INDENT)
186-
if len(argspec) > _MAX_COLS else [argspec] if argspec else [])
185+
lines = [argspec] if argspec else []
187186

188187
# Augment lines from docstring, if any, and join to get argspec.
189188
doc = inspect.getdoc(ob)
190189
if doc:
191-
for line in doc.split('\n', _MAX_LINES)[:_MAX_LINES]:
192-
line = line.strip()
193-
if not line:
194-
break
195-
if len(line) > _MAX_COLS:
196-
line = line[: _MAX_COLS - 3] + '...'
197-
lines.append(line)
190+
for line in doc.split('\n'):
191+
lines.append(line.strip())
198192
argspec = '\n'.join(lines)
199193

200194
return argspec or _default_callable_argspec

Lib/idlelib/calltip_w.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
Used by calltip.py.
55
"""
66
from tkinter import Label, LEFT, SOLID, TclError
7+
from tkinter.scrolledtext import ScrolledText
78

89
from idlelib.tooltip import TooltipBase
910

1011
HIDE_EVENT = "<<calltipwindow-hide>>"
11-
HIDE_SEQUENCES = ("<Key-Escape>", "<FocusOut>")
12+
HIDE_SEQUENCES = ("<Key-Escape>",)
1213
CHECKHIDE_EVENT = "<<calltipwindow-checkhide>>"
1314
CHECKHIDE_SEQUENCES = ("<KeyRelease>", "<ButtonRelease>")
1415
CHECKHIDE_TIME = 100 # milliseconds
@@ -74,17 +75,22 @@ def showtip(self, text, parenleft, parenright):
7475
int, self.anchor_widget.index(parenleft).split("."))
7576

7677
super().showtip()
78+
self.tipwindow.wm_attributes('-topmost', 1)
7779

7880
self._bind_events()
7981

8082
def showcontents(self):
8183
"""Create the call-tip widget."""
82-
self.label = Label(self.tipwindow, text=self.text, justify=LEFT,
84+
self.label = ScrolledText(self.tipwindow, wrap="word",
8385
background="#ffffd0", foreground="black",
8486
relief=SOLID, borderwidth=1,
8587
font=self.anchor_widget['font'])
88+
self.label.insert('1.0', self.text)
89+
self.label.config(state='disabled')
8690
self.label.pack()
8791

92+
self.tipwindow.geometry('%dx%d' % (400, 120))
93+
8894
def checkhide_event(self, event=None):
8995
"""Handle CHECK_HIDE_EVENT: call hidetip or reschedule."""
9096
if not self.tipwindow:
@@ -156,6 +162,8 @@ def _bind_events(self):
156162
self.hide_event)
157163
for seq in HIDE_SEQUENCES:
158164
self.anchor_widget.event_add(HIDE_EVENT, seq)
165+
if self.tipwindow:
166+
self.tipwindow.bind("<Key-Escape>", self.hide_event)
159167

160168
def _unbind_events(self):
161169
"""Unbind event handlers."""

Lib/idlelib/idle_test/test_calltip.py

Lines changed: 17 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -93,24 +93,20 @@ class SB: __call__ = None
9393
non-overlapping occurrences of the pattern in string by the
9494
replacement repl. repl can be either a string or a callable;
9595
if a string, backslash escapes in it are processed. If it is
96-
a callable, it's passed the Match object and must return''')
96+
a callable, it's passed the Match object and must return
97+
a replacement string to be used.''')
9798
tiptest(p.sub, '''\
9899
(repl, string, count=0)
99-
Return the string obtained by replacing the leftmost \
100-
non-overlapping occurrences o...''')
100+
Return the string obtained by replacing the leftmost non-overlapping \
101+
occurrences of pattern in string by the replacement repl.''')
101102

102-
def test_signature_wrap(self):
103+
def test_signature(self):
103104
if textwrap.TextWrapper.__doc__ is not None:
104-
self.assertEqual(get_spec(textwrap.TextWrapper), '''\
105-
(width=70, initial_indent='', subsequent_indent='', expand_tabs=True,
106-
replace_whitespace=True, fix_sentence_endings=False, break_long_words=True,
107-
drop_whitespace=True, break_on_hyphens=True, tabsize=8, *, max_lines=None,
108-
placeholder=' [...]')
109-
Object for wrapping/filling text. The public interface consists of
110-
the wrap() and fill() methods; the other methods are just there for
111-
subclasses to override in order to tweak the default behaviour.
112-
If you want to completely replace the main wrapping algorithm,
113-
you\'ll probably have to override _wrap_chunks().''')
105+
self.assertEqual(get_spec(textwrap.TextWrapper).split('\n')[0], '''\
106+
(width=70, initial_indent='', subsequent_indent='', expand_tabs=True, \
107+
replace_whitespace=True, fix_sentence_endings=False, break_long_words=True, \
108+
drop_whitespace=True, break_on_hyphens=True, tabsize=8, *, max_lines=None, \
109+
placeholder=' [...]')''')
114110

115111
def test_properly_formatted(self):
116112

@@ -127,16 +123,14 @@ def baz(s='a'*100, z='b'*100):
127123
indent = calltip._INDENT
128124

129125
sfoo = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
130-
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\
131-
"aaaaaaaaaa')"
126+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')"
132127
sbar = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
133-
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\
134-
"aaaaaaaaaa')\nHello Guido"
128+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')"\
129+
"\nHello Guido"
135130
sbaz = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
136-
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\
137-
"aaaaaaaaaa', z='bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"\
138-
"bbbbbbbbbbbbbbbbb\n" + indent + "bbbbbbbbbbbbbbbbbbbbbb"\
139-
"bbbbbbbbbbbbbbbbbbbbbb')"
131+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',"\
132+
" z='bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"\
133+
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')"
140134

141135
for func,doc in [(foo, sfoo), (bar, sbar), (baz, sbaz)]:
142136
with self.subTest(func=func, doc=doc):
@@ -145,29 +139,7 @@ def baz(s='a'*100, z='b'*100):
145139
def test_docline_truncation(self):
146140
def f(): pass
147141
f.__doc__ = 'a'*300
148-
self.assertEqual(get_spec(f), f"()\n{'a'*(calltip._MAX_COLS-3) + '...'}")
149-
150-
@unittest.skipIf(MISSING_C_DOCSTRINGS,
151-
"Signature information for builtins requires docstrings")
152-
def test_multiline_docstring(self):
153-
# Test fewer lines than max.
154-
self.assertEqual(get_spec(range),
155-
"range(stop) -> range object\n"
156-
"range(start, stop[, step]) -> range object")
157-
158-
# Test max lines
159-
self.assertEqual(get_spec(bytes), '''\
160-
bytes(iterable_of_ints) -> bytes
161-
bytes(string, encoding[, errors]) -> bytes
162-
bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer
163-
bytes(int) -> bytes object of size given by the parameter initialized with null bytes
164-
bytes() -> empty bytes object''')
165-
166-
def test_multiline_docstring_2(self):
167-
# Test more than max lines
168-
def f(): pass
169-
f.__doc__ = 'a\n' * 15
170-
self.assertEqual(get_spec(f), '()' + '\na' * calltip._MAX_LINES)
142+
self.assertEqual(get_spec(f), "()\n%s" % ('a'*300))
171143

172144
def test_functions(self):
173145
def t1(): 'doc'

0 commit comments

Comments
 (0)