@@ -114,14 +114,36 @@ def format_tokens(tokensource):
114
114
115
115
class BPythonEdit (urwid .Edit ):
116
116
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
+ """
118
131
119
132
def __init__ (self , * args , ** kwargs ):
120
133
self ._bpy_text = ''
121
134
self ._bpy_attr = []
122
135
self ._bpy_selectable = True
123
136
urwid .Edit .__init__ (self , * args , ** kwargs )
124
137
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
+
125
147
def set_edit_markup (self , markup ):
126
148
"""Call this when markup changes but the underlying text does not.
127
149
@@ -144,6 +166,14 @@ def get_cursor_coords(self, *args, **kwargs):
144
166
return None
145
167
return urwid .Edit .get_cursor_coords (self , * args , ** kwargs )
146
168
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
+
147
177
def get_pref_col (self , size ):
148
178
# Need to make this deal with us being nonselectable
149
179
if not self ._bpy_selectable :
@@ -183,12 +213,15 @@ def render(self, size, focus=False):
183
213
184
214
class URWIDRepl (repl .Repl ):
185
215
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 ):
188
219
repl .Repl .__init__ (self , interpreter , config )
189
220
self .main_loop = main_loop
221
+ self .frame = frame
190
222
self .listbox = listbox
191
223
self .listwalker = listwalker
224
+ self .overlay = overlay
192
225
self .tooltiptext = tooltiptext
193
226
self .edits = []
194
227
self .edit = None
@@ -251,8 +284,9 @@ def _populate_completion(self, main_loop, user_data):
251
284
if self .argspec :
252
285
text = '%s\n \n %r' % (text , self .argspec )
253
286
self .tooltiptext .set_text (text )
287
+ self .frame .body = self .overlay
254
288
else :
255
- self .tooltiptext . set_text ( 'NOPE' )
289
+ self .frame . body = self . listbox
256
290
257
291
def reprint_line (self , lineno , tokens ):
258
292
edit = self .edits [- len (self .buffer ) + lineno - 1 ]
@@ -283,19 +317,43 @@ def prompt(self, more):
283
317
self .edits .append (self .edit )
284
318
self .listwalker .append (self .edit )
285
319
self .listbox .set_focus (len (self .listwalker ) - 1 )
320
+ # Hide the tooltip
321
+ self .frame .body = self .listbox
286
322
287
323
def on_input_change (self , edit , text ):
288
324
tokens = self .tokenize (text , False )
289
325
edit .set_edit_markup (list (format_tokens (tokens )))
290
326
# If we call this synchronously the get_edit_text() in repl.cw
291
327
# still returns the old text...
292
328
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
293
351
294
352
def handle_input (self , event ):
295
353
if event == 'enter' :
296
354
inp = self .edit .get_edit_text ()
297
355
self .history .append (inp )
298
- self .edit ._bpy_selectable = False
356
+ self .edit .make_readonly ()
299
357
# XXX what is this s_hist thing?
300
358
self .stdout_hist += inp + '\n '
301
359
self .edit = None
@@ -374,7 +432,7 @@ def main(args=None, locals_=None, banner=None):
374
432
loop = urwid .MainLoop (frame , palette , event_loop = event_loop )
375
433
376
434
# TODO: hook up idle callbacks somewhere.
377
- myrepl = URWIDRepl (loop , listbox , listwalker , tooltiptext ,
435
+ myrepl = URWIDRepl (loop , frame , listbox , listwalker , overlay , tooltiptext ,
378
436
interpreter , statusbar , config )
379
437
380
438
# XXX HACK: circular dependency between the event loop and repl.
0 commit comments