Class: Tkri::Tab

Inherits:
TkFrame
  • Object
show all
Defined in:
lib/tkri.rb

Overview

A Tab encapsulates an @address box, where you type the topic to go to; a “Go” button; and an @info box in which to show the topic.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tk_parent, app, configuration = {}) ⇒ Tab

Returns a new instance of Tab.



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/tkri.rb', line 277

def initialize(tk_parent, app, configuration = {})
  @app = app
  super(tk_parent, configuration)

  #
  # The address bar
  #
  addressbar = TkFrame.new(self) { |ab|
    pack :side => 'top', :fill => 'x'
    TkButton.new(ab) {
      configure Settings::get_configuration('go_button')
      text 'Go'
      command { app.go }
      pack :side => 'right'
    }
  }
  @address = TkEntry.new(addressbar) {
    configure Settings::get_configuration('__base__')
    configure :width => 30
    pack :side => 'left', :expand => true, :fill => 'both'
  }

  #
  # The info box, where the main text is displayed.
  #
  _frame = self
  @info = TkText.new(self) { |t|
    configure Settings::get_configuration('__base__')
    pack :side => 'left', :fill => 'both', :expand => true
    TkScrollbar.new(_frame) { |s|
      pack :side => 'right', :fill => 'y'
      command { |*args| t.yview *args }
      t.yscrollcommand { |first,last| s.set first,last }
    }
  }

  Settings::VISUALS.each do |name, hash|
    if hash[:is_tag]
      @info.tag_configure(name, Settings::get_configuration(name))
    end
  end

  Tkri.attach_bindings @address, 'addressbox'
  Tkri.attach_bindings @info, 'info'

  @history = History.new
end

Instance Attribute Details

#topicObject (readonly)

Returns the value of attribute topic.



275
276
277
# File 'lib/tkri.rb', line 275

def topic
  @topic
end

Instance Method Details

#_highlight_word(word_or_regexp, text, tag_name) ⇒ Object



410
411
412
413
414
415
416
417
# File 'lib/tkri.rb', line 410

def _highlight_word(word_or_regexp, text, tag_name)
  pos = -1
  while pos = text.index(word_or_regexp, pos + 1)
    length = (word_or_regexp.is_a? String) ? word_or_regexp.length : $&.length
    @info.tag_add(tag_name, '1.0 + %d chars' % pos,
                            '1.0 + %d chars' % (pos + length))
  end
end

#_load_topic(topic) ⇒ Object



607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
# File 'lib/tkri.rb', line 607

def _load_topic(topic)
  @app.status = 'Loading "%s"...' % topic
  @address.delete('0', 'end')
  @address.insert('end', topic)
  focus_address
  # We need to give our GUI a chance to redraw itself, so we run the
  # time-consuming 'ri' command "in the next go".
  TkAfter.new 100, 1 do
    ri = @app.fetch_ri(topic)
    set_ansi_text(ri)
    @app.refresh_tabsbar
    @app.status = ''
    @info.focus
    set_cursor '1.0'
    yield if block_given?
  end.start
end

#fixup_topic(topic) ⇒ Object

Allow for some shortcuts when typing topics…



556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
# File 'lib/tkri.rb', line 556

def fixup_topic(topic)
  case topic
  when 'S', 's', 'string'
    'String'
  when 'A', 'a', 'array'
    'Array'
  when 'H', 'h', 'hash'
    'Hash'
  when 'File::new'
    # See qri bug at http://rubyforge.org/tracker/index.php?func=detail&aid=23504&group_id=2545&atid=9811
    'File#new'
  else
    topic
  end
end

#focus_addressObject

Moves the keyboard focus to the address box. Also, selects all the text, like modern GUIs do.



357
358
359
360
361
# File 'lib/tkri.rb', line 357

def focus_address
  @address.selection_range('0', 'end')
  @address.icursor = 'end'
  @address.focus
end

#get_previous_class(cursor) ⇒ Object

Returns the first class mentioned before the cursor.



458
459
460
461
462
# File 'lib/tkri.rb', line 458

def get_previous_class cursor
  # The class (or module) is followed by a '('
  ret = @info.rsearch_with_length(/\s\S+\(/, cursor)
  return ret[0].empty? ? nil : ret[2][1..-2] # skip the first char (a space) and the last (a parentheses)
end

#get_previous_header(cursor) ⇒ Object

Returns the section (the header) the cursor is in.



450
451
452
453
454
455
# File 'lib/tkri.rb', line 450

def get_previous_header cursor
  ret = @info.rsearch_with_length(/[\r\n]\w[^\r\n]*/, cursor)
  if !ret[0].empty? and @info.compare(cursor, '>=', ret[0])
    return ret[2].strip
  end
end

#get_selectionObject

It seems RubyTk doesn’t support the the getSelected method for Text widgets. So here’s a method of our own to get the selection.



347
348
349
350
351
352
353
# File 'lib/tkri.rb', line 347

def get_selection
  begin
    @info.get('sel.first', 'sel.last')
  rescue
    ''
  end
end

#get_word(position) ⇒ Object

Get the “topic” at a certain postion.

The ‘position’ paramter is an expression that can be, e.g., “insert” for the current caret position; or “@x,y” for the mouse poisition.



468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
# File 'lib/tkri.rb', line 468

def get_word(position)
  line = @info.get(position + ' linestart', position + ' lineend')
  pos  = @info.get(position + ' linestart', position).length

  line = ' ' + line + '  '
  pos += 1
  
  if line[pos,1] == ' '
    # If the user clicks a space between words, or after end of line, abort.
    return nil
  end
  
  a = pos
  a -= 1 while line[a-1,1] !~ /[ (]/
  z = pos
  z += 1 while line[z+1,1] !~ /[ ()]/
  word = line[a..z]

  # Get rid of English punctuation.
  word.gsub!(/[,.:;]$/, '')

  # Get rid of italic, bold, and code markup.
  if word =~ /^(_|\*|\+).*\1$/
    word = word[1...-1]
    a += 1
  end

  a -= 1 # Undo the `line = ' ' + line` we did previously.
  @info.tag_add('keyword', '%s linestart + %d chars' % [ position, a ],
                           '%s linestart + %d chars' % [ position, a+word.length ])
  word.strip!

  return nil if word.empty?
  return nil if word =~ /^-+$/ # A special case: a line of '-----'

  if word =~ /^#/
    # Sometimes there's just "#method".
    word = @topic + word
  end

  case get_previous_header(position)
  when 'Instance methods:'
    word = topic + '#' + word
  when 'Class methods:'
    word = topic + '::' + word
  when 'Includes:'
    word = get_previous_class(position) + '#' + word if not word =~ /^[A-Z]/
  end

  return word
end

#go(topic = nil) ⇒ Object

Navigates to some topic.



573
574
575
576
577
578
579
580
581
582
583
# File 'lib/tkri.rb', line 573

def go(topic=nil)
  topic = (topic || @address.get).strip
  return if topic.empty?
  @topic = fixup_topic(topic)
  # First, save the cursor position in the current history entry.
  store_in_history
  # Next, add a new entry to the history.
  @history.add HistoryEntry.new(@topic, nil, nil)
  # Finally, load the topic.
  _load_topic @topic
end

#go_caret_word(newtab = false) ⇒ Object

Navigate to the topic mentioned under the caret.



439
440
441
# File 'lib/tkri.rb', line 439

def go_caret_word(newtab=false)
  go_word('insert', newtab)
end

#go_word(position, newtab = false) ⇒ Object



443
444
445
446
447
# File 'lib/tkri.rb', line 443

def go_word(position, newtab=false)
  if (word = get_word(position))
    @app.go word, newtab
  end
end

#go_xy_word(x, y, newtab = false) ⇒ Object

Navigate to the topic mentioned under the mouse cursor (given by x,y coordinates)



428
429
430
431
432
433
434
435
436
# File 'lib/tkri.rb', line 428

def go_xy_word(x, y, newtab=false)
  if not newtab and not @info.tag_ranges('sel').empty?
    # We don't want to prohibit selecting text, so we don't trigger
    # navigation if some text is selected. (Remember, this method is called
    # upon releasing the mouse button.)
    return
  end
  go_word('@' + x.to_s + ',' + y.to_s, newtab)
end

#hideObject



657
658
659
# File 'lib/tkri.rb', line 657

def hide
  pack_forget
end

#highlight_word(word) ⇒ Object

Highlights a word in the text. Used by the search methods.



404
405
406
407
408
# File 'lib/tkri.rb', line 404

def highlight_word(word)
  return if word.empty?
  @info.tag_remove('search', '1.0', 'end')
  _highlight_word(word.downcase, @info.get('1.0', 'end').downcase, 'search')
end

#interactive_focus_address(e) ⇒ Object



363
364
365
# File 'lib/tkri.rb', line 363

def interactive_focus_address e
  focus_address
end

#interactive_go_up(e) ⇒ Object

Go “up”. That is, if we’re browsing a method, go to the class.



420
421
422
423
424
# File 'lib/tkri.rb', line 420

def interactive_go_up e
  if topic and topic =~ /(.*)(::|#|\.)/
    @app.go $1
  end
end

#interactive_goto_topic_in_addressbox(e) ⇒ Object



325
326
327
# File 'lib/tkri.rb', line 325

def interactive_goto_topic_in_addressbox e
  go
end

#interactive_goto_topic_under_caret_or_selected(e) ⇒ Object



337
338
339
340
341
342
343
# File 'lib/tkri.rb', line 337

def interactive_goto_topic_under_caret_or_selected e
  if get_selection.length > 0
    go get_selection
  else
    go_caret_word()
  end
end

#interactive_goto_topic_under_mouse(e) ⇒ Object



329
330
331
# File 'lib/tkri.rb', line 329

def interactive_goto_topic_under_mouse e
  go_xy_word(e.x, e.y)
end

#interactive_goto_topic_under_mouse_in_new_tab(e) ⇒ Object



333
334
335
# File 'lib/tkri.rb', line 333

def interactive_goto_topic_under_mouse_in_new_tab e
  go_xy_word(e.x, e.y, true)
end

#interactive_history_back(e) ⇒ Object

Navigate to the previous topic in history.



626
627
628
629
630
631
632
# File 'lib/tkri.rb', line 626

def interactive_history_back e
  if not @history.at_beginning
    store_in_history
    @history.back
    restore_from_history
  end
end

#interactive_history_forward(e) ⇒ Object

Navigate to the next topic in history.



635
636
637
638
639
640
641
# File 'lib/tkri.rb', line 635

def interactive_history_forward e
  if not @history.at_end
    store_in_history
    @history.foreward
    restore_from_history
  end
end

#new?Boolean

Returns:

  • (Boolean)


649
650
651
# File 'lib/tkri.rb', line 649

def new?
  not @topic
end

#restore_from_historyObject

Call this method after moving fack and forth in the history. This method restores the topic and cursor position recorded in the current history entry.



597
598
599
600
601
602
603
604
605
# File 'lib/tkri.rb', line 597

def restore_from_history
  if current = @history.current
    @topic = current.topic
    _load_topic(topic) do
      @info.yview_moveto current.yview
      set_cursor current.cursor
    end
  end
end

#search_next_word(word) ⇒ Object

Finds the next occurrence of a word.



368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
# File 'lib/tkri.rb', line 368

def search_next_word(word)
  @info.focus
  highlight_word word
  cursor = @info.index('insert')
  pos = @info.search_with_length(Regexp.new(Regexp::quote(word), Regexp::IGNORECASE), cursor + ' 1 chars')[0]
  if pos.empty?
    @app.status = 'Cannot find "%s"' % word
  else
    set_cursor(pos)
    if @info.compare(cursor, '>=', pos)
      @app.status = 'Continuing search at top'
    else
      @app.status = ''
    end
  end
end

#search_prev_word(word) ⇒ Object

Finds the previous occurrence of a word.



386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
# File 'lib/tkri.rb', line 386

def search_prev_word(word)
  @info.focus
  highlight_word word
  cursor = @info.index('insert')
  pos = @info.rsearch_with_length(Regexp.new(Regexp::quote(word), Regexp::IGNORECASE), cursor)[0]
  if pos.empty?
    @app.status = 'Cannot find "%s"' % word
  else
    set_cursor(pos)
    if @info.compare(cursor, '<=', pos)
      @app.status = 'Continuing search at bottom'
    else
      @app.status = ''
    end
  end
end

#set_ansi_text(text) ⇒ Object

Sets the text of the @info box, converting ANSI escape sequences to Tk tags.



522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
# File 'lib/tkri.rb', line 522

def set_ansi_text(text)
  text = text.dup
  ansi_tags = {
    # The following possibilities were taken from /usr/lib/ruby/1.8/rdoc/ri/ri_formatter.rb
    '1'    => 'bold',
    '33'   => 'italic',
    '36'   => 'code',
    '4;32' => 'header2',
    '32'   => 'header3',
  }
  ranges = []
  while text =~ /\x1b\[([\d;]+)m ([^\x1b]*) \x1b\[0?m/x
    start      = $`.length
    length     = $2.length
    raw_length = $&.length
    text[start, raw_length] = $2
    ranges << { :start => start, :length => length, :tag => ansi_tags[$1] }
  end

  @info.delete('1.0', 'end')
  @info.insert('end', text)

  ranges.each do |range|
    if range[:tag]
      @info.tag_add(range[:tag], '1.0 + %d chars' %  range[:start],
                                 '1.0 + %d chars' % (range[:start] + range[:length]))
    end
  end
  # Hide any remaining sequences. This may happen because our previous regexp
  # (or any regexp) can't handle nested sequences.
  _highlight_word(/\x1b\[([\d;]*)m/, text, 'hidden')
end

#set_cursor(pos) ⇒ Object

Sets @info’s caret position. Scroll the view if needed.



644
645
646
647
# File 'lib/tkri.rb', line 644

def set_cursor(pos)
  @info.mark_set('insert', pos)
  @info.see(pos)
end

#showObject



653
654
655
# File 'lib/tkri.rb', line 653

def show
  pack :fill => 'both', :expand => true
end

#store_in_historyObject

Call this method before switching to another topic. This method saves the cursor position in the history.



587
588
589
590
591
592
# File 'lib/tkri.rb', line 587

def store_in_history
  if current = @history.current
    current.cursor = @info.index('insert')
    current.yview = @info.yview[0]
  end
end