Skip to content

Commit a4b366e

Browse files
committed
Almost-working tooltip positioning.
I still need to figure out why the tooltip alternates between displaying in the middle of the screen and at the right position.
1 parent 4c820e1 commit a4b366e

File tree

1 file changed

+64
-6
lines changed

1 file changed

+64
-6
lines changed

bpython/urwid.py

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,36 @@ def format_tokens(tokensource):
114114

115115
class BPythonEdit(urwid.Edit):
116116

117-
"""Customized editor *very* tightly interwoven with URWIDRepl."""
117+
"""Customized editor *very* tightly interwoven with URWIDRepl.
118+
119+
Changes include:
120+
121+
- The edit text supports markup, not just the caption.
122+
This works by calling set_edit_markup from the change event
123+
as well as whenever markup changes while text does not.
124+
125+
- The widget can be made readonly, which currently just means
126+
it is no longer selectable and stops drawing the cursor.
127+
128+
This is currently a one-way operation, but that is just because
129+
I only need and test the readwrite->readonly transition.
130+
"""
118131

119132
def __init__(self, *args, **kwargs):
120133
self._bpy_text = ''
121134
self._bpy_attr = []
122135
self._bpy_selectable = True
123136
urwid.Edit.__init__(self, *args, **kwargs)
124137

138+
def make_readonly(self):
139+
self._bpy_selectable = False
140+
# This is necessary to prevent the listbox we are in getting
141+
# fresh cursor coords of None from get_cursor_coords
142+
# immediately after we go readonly and then getting a cached
143+
# canvas that still has the cursor set. It spots that
144+
# inconsistency and raises.
145+
self._invalidate()
146+
125147
def set_edit_markup(self, markup):
126148
"""Call this when markup changes but the underlying text does not.
127149
@@ -144,6 +166,14 @@ def get_cursor_coords(self, *args, **kwargs):
144166
return None
145167
return urwid.Edit.get_cursor_coords(self, *args, **kwargs)
146168

169+
def render(self, size, focus=False):
170+
# XXX I do not want to have to do this, but listbox gets confused
171+
# if I do not (getting None out of get_cursor_coords because
172+
# we just became unselectable, then having this render a cursor)
173+
if not self._bpy_selectable:
174+
focus = False
175+
return urwid.Edit.render(self, size, focus=focus)
176+
147177
def get_pref_col(self, size):
148178
# Need to make this deal with us being nonselectable
149179
if not self._bpy_selectable:
@@ -183,12 +213,15 @@ def render(self, size, focus=False):
183213

184214
class URWIDRepl(repl.Repl):
185215

186-
def __init__(self, main_loop, listbox, listwalker, tooltiptext,
187-
interpreter, statusbar, config):
216+
# XXX this is getting silly, need to split this up somehow
217+
def __init__(self, main_loop, frame, listbox, listwalker, overlay,
218+
tooltiptext, interpreter, statusbar, config):
188219
repl.Repl.__init__(self, interpreter, config)
189220
self.main_loop = main_loop
221+
self.frame = frame
190222
self.listbox = listbox
191223
self.listwalker = listwalker
224+
self.overlay = overlay
192225
self.tooltiptext = tooltiptext
193226
self.edits = []
194227
self.edit = None
@@ -251,8 +284,9 @@ def _populate_completion(self, main_loop, user_data):
251284
if self.argspec:
252285
text = '%s\n\n%r' % (text, self.argspec)
253286
self.tooltiptext.set_text(text)
287+
self.frame.body = self.overlay
254288
else:
255-
self.tooltiptext.set_text('NOPE')
289+
self.frame.body = self.listbox
256290

257291
def reprint_line(self, lineno, tokens):
258292
edit = self.edits[-len(self.buffer) + lineno - 1]
@@ -283,19 +317,43 @@ def prompt(self, more):
283317
self.edits.append(self.edit)
284318
self.listwalker.append(self.edit)
285319
self.listbox.set_focus(len(self.listwalker) - 1)
320+
# Hide the tooltip
321+
self.frame.body = self.listbox
286322

287323
def on_input_change(self, edit, text):
288324
tokens = self.tokenize(text, False)
289325
edit.set_edit_markup(list(format_tokens(tokens)))
290326
# If we call this synchronously the get_edit_text() in repl.cw
291327
# still returns the old text...
292328
self.main_loop.set_alarm_in(0, self._populate_completion)
329+
self._reposition_tooltip()
330+
331+
def _reposition_tooltip(self):
332+
# Reposition the tooltip based on cursor position.
333+
screen_cols, screen_rows = self.main_loop.screen.get_cols_rows()
334+
# XXX this should use self.listbox.get_cursor_coords
335+
# but that doesn't exist (urwid oversight)
336+
offset, inset = self.listbox.get_focus_offset_inset(
337+
(screen_cols, screen_rows))
338+
rel_x, rel_y = self.edit.get_cursor_coords((screen_cols,))
339+
y = offset + rel_y
340+
if y < 0:
341+
# Cursor off the screen (no clue if this can happen).
342+
# Just clamp to 0.
343+
y = 0
344+
# XXX not sure if these overlay attributes are meant to be public...
345+
if y * 2 < screen_rows:
346+
self.overlay.valign_type = 'fixed top'
347+
self.overlay.valign_amount = y + 1
348+
else:
349+
self.overlay.valign_type = 'fixed bottom'
350+
self.overlay.valign_amount = screen_rows - y - 1
293351

294352
def handle_input(self, event):
295353
if event == 'enter':
296354
inp = self.edit.get_edit_text()
297355
self.history.append(inp)
298-
self.edit._bpy_selectable = False
356+
self.edit.make_readonly()
299357
# XXX what is this s_hist thing?
300358
self.stdout_hist += inp + '\n'
301359
self.edit = None
@@ -374,7 +432,7 @@ def main(args=None, locals_=None, banner=None):
374432
loop = urwid.MainLoop(frame, palette, event_loop=event_loop)
375433

376434
# TODO: hook up idle callbacks somewhere.
377-
myrepl = URWIDRepl(loop, listbox, listwalker, tooltiptext,
435+
myrepl = URWIDRepl(loop, frame, listbox, listwalker, overlay, tooltiptext,
378436
interpreter, statusbar, config)
379437

380438
# XXX HACK: circular dependency between the event loop and repl.

0 commit comments

Comments
 (0)