@@ -114,14 +114,36 @@ def format_tokens(tokensource):
114114
115115class 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
184214class 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