Class: Tree

Inherits:
Object show all
Defined in:
lib/xiki/tree.rb,
lib/xiki/vim/tree.rb

Constant Summary collapse

@@lorem =
{
  "lorem"=>"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
  "ipsum"=>"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
  "dolor"=>"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
  "sit"=>"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
}

Class Method Summary collapse

Class Method Details

.<<(txt) ⇒ Object



934
935
936
# File 'lib/xiki/tree.rb', line 934

def self.<< txt, options={}
  self.under txt, options
end

.acronym_regexp(search) ⇒ Object



856
857
858
# File 'lib/xiki/tree.rb', line 856

def self.acronym_regexp search
  search.gsub(/([a-zA-Z])/, "[a-z]*[_.]\\1").sub(/.+?\].+?\]/,'^ +')
end

.add_closing_tags(html, l, previous) ⇒ Object



1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
# File 'lib/xiki/tree.rb', line 1814

def self.add_closing_tags html, l, previous

  if l.length <= previous.length
    left = l.length-1
    left = 0 if left < 0
    close_these = previous[left..-1]
    close_these.reverse.each_with_index do |tag, i|

      tag.sub! /^\| ?/, ''
      tag = Line.without_label :line=>tag
      next if tag !~ /(.*\w)\/$/ && tag !~ /^<([^<\n]*[\w"'])>$/
      tag = $1
      tag = tag.sub(/ \w+=.+/, '')
      next if ["img"].member? tag
      html << "  " * (previous.length - i - 1)
      html << "</#{tag}>\n"
    end
  end
  previous.replace l
end

.add_pluses_and_minuses(tree, dirs = '-', files = '+') ⇒ Object

Prepend bullets (pluses and minuses) to lines



1760
1761
1762
1763
# File 'lib/xiki/tree.rb', line 1760

def self.add_pluses_and_minuses tree, dirs='-', files='+'
  tree.gsub! /^( *)([^ \n|+-].*\/)$/, "\\1#{dirs} \\2"
  tree.gsub! /^( *)([^ \n|+-].*[^\/\n])$/, "\\1#{files} \\2"
end

.after(txt) ⇒ Object

Inserts indented underneath. Not sure why it’s calling View.under instead of Tree.under



940
941
942
943
944
# File 'lib/xiki/tree.rb', line 940

def self.after txt
  # Is anything calling this - maybe make them just call Tree.under.
  # View.under does a couple lines, then calls Tree.under.
  View.under txt, :after=>1
end

.after_children(options = {}) ⇒ Object

Go to the right side of the tree item (move to after last item that is indented lower)



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

def self.after_children options={}
  indent = Line.indent.size

  pattern = "\\(\n\n\n\\|^ \\{0,#{indent}\\}[^ \n]\\)"   # Find line with same or less indent

  # Why would I do this at all?
  #     self.minus_to_plus_maybe unless options[:no_plus]

  Line.next
  Search.forward pattern, :go_anyway=>1
  Line.to_left

  Search.backward "^."
  Line.next
end

.after_siblingsObject

Move cursor to after last sibling, crossing blank lines, but not double-blank lines. Tree.after_siblings



625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
# File 'lib/xiki/tree.rb', line 625

def self.after_siblings # options={}
  indent = Line.indent.size

  return if indent == 0

  indent -= 2

  pattern = "\\(\n\n\n\\|^ \\{0,#{indent}\\}[^ \n]\\)"   # Find line with less indent

  Search.forward pattern, :go_anyway=>1
  Line.to_left

  Search.backward "^."
  Line.next
  nil
end

.ancestors_indented(options = {}) ⇒ Object

Gets path from root to here, indenting each line by 2 spaces deeper. Tree.ancestors_indented



1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
# File 'lib/xiki/tree.rb', line 1340

def self.ancestors_indented options={}

  all = options[:just_sub_tree] ? nil : 1
  path = Tree.construct_path(:list=>1, :ignore_ol=>1, :all=>all)
  result = ""
  path.each_with_index { |o, i|
    result << "#{'  ' * i}#{o}\n"
  }

  result
end

.before_siblingsObject

Jumps to first sibling, crossing blank lines, but not double-blank lines



680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
# File 'lib/xiki/tree.rb', line 680

def self.before_siblings

  indent = Line.value[/^  ( *)/, 1]

  # For now, don't handle if at root

  regex = "\\(\n\n\n\\|^#{indent}[^\t \n]\\)"
  Search.backward regex, :go_anyway=>true

  # Move.to_next_paragraph(:no_skip=>1)
  hit_two_blanks = View.cursor == Line.right

  return Move.to_next_paragraph(:no_skip=>1) if hit_two_blanks || Line.value(2).blank?
  Line.next

  # Can't get siblings of item at left margin - undecided how to implement it" if !indent
end

.childObject

bar



1627
1628
1629
1630
1631
1632
1633
1634
# File 'lib/xiki/tree.rb', line 1627

def self.child
  following_line = Line.value 2
  # If indent is one greater, it is a child
  if Line.indent.size + 2 == Line.indent(following_line).size
    return Line.without_label(:line=>following_line)
  end
  nil
end

.children(tree = nil, target = nil, options = {}) ⇒ Object

Extracts children from tree arg and target (path) arg.

Or, if no tree passed in, delegates to Tree.children_at_cursor

Tree.children “an bn c”, “a”



1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
# File 'lib/xiki/tree.rb', line 1474

def self.children tree=nil, target=nil, options={}

  # Read in file, if tree looks like a file path
  tree = File.read(File.expand_path tree) if tree !~ /\n/ && tree =~ /^~/

  include_subitems = options[:include_subitems]   # Include sub-items for all children

  return self.children_at_cursor(tree) if tree.nil? || tree.is_a?(Hash)   # tree is actually options

  target = target.join("/") if target.is_a? Array
  target = "" if target == nil || target == "/"   # Must be at root if nil
  tree = TextUtil.unindent tree

  target.sub!(/^\//, '')
  target.sub!(/\/$/, '')

  found = nil
  result = ""

  found = -1 if target.empty?

  @@under_preexpand = false   # Will include sub-items of only some children

  self.traverse tree do |branch, path|
    blank = branch[-1].nil?

    if ! found
      target_match = Tree.target_match path, target
      next unless target_match == :shorter || target_match == :same
      found = branch.length - 1   # Found, remember indent

    else
      current_indent = branch.length - 1
      # If found and still indented one deeper
      one_deeper = current_indent == found + 1

      if one_deeper || ((include_subitems || @@under_preexpand) && current_indent > found)

        next result << "\n" if blank

        item = branch[-1]
        item.sub!(/^- /, '+ ') if item =~ /\/$/
        item.sub!(/^([<+-][<=]* )?\./, "\\1")
        next if item =~ /^[+-] \*\/$/   # Skip asterixes

        # If @@under_preexpand, add on indent
        if include_subitems || @@under_preexpand
          item = "#{'  ' * (branch.length - found - 2)}#{item}"
        end

        @@under_preexpand = false if one_deeper

        # Pre-expand if @... or doesn't end in slash
        @@under_preexpand = true if one_deeper && (item =~ /^([+-] )?@/ || item !~ /\/$/)

        result << "#{item}\n"  # Output

      else  # Otherwise, stop looking for children if indent is less

        #           # If blank line is at same level
        #           if branch.empty? && found == current_indent
        #             next result << "\n"
        #           end

        next if current_indent > found
        # No longer beneath found item
        found = nil
        @@under_preexpand = false
      end
    end

  end

  result.empty? ? nil : result
end

.children?Boolean

Returns:

  • (Boolean)


1614
1615
1616
1617
1618
# File 'lib/xiki/tree.rb', line 1614

def self.children?
  # Whether next line is more indented
  Line.indent(Line.value(2)).size >
    Line.indent.size
end

.children_at_cursor(options = {}) ⇒ Object

c



1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
# File 'lib/xiki/tree.rb', line 1562

def self.children_at_cursor options={}

  options ||= {}
  child = self.child
  return nil if child.nil?   # Return if no child


  # If :cross_blank_lines, use Tree.after_children to find end
  if options[:cross_blank_lines]
    orig = Location.new

    left = Line.left 2
    Tree.after_children
    right = Line.left
    orig.go
    # return
    return View.txt left, right
  end

  indent = Line.indent(Line.value(2)).size

  # :as_hash isn't used anywhere
  children = options[:as_hash] ? {} : []
  i = 2

  # Add each child indented the same or more
  while(Line.indent(Line.value(i)).size >= indent)
    child = Line.value(i)
    if options[:as_hash]
      match = child.match(/ *([\w -]+): (.+)/)
      if match
        k, v = match[1..2]
        children[k] = v
      else
        i += 1
        next
      end

    else
      children << child
    end

    i += 1
  end

  if options[:string]
    return children.join("\n")+"\n"
  end

  children
end

.clean_path(path) ⇒ Object



830
831
832
833
834
835
# File 'lib/xiki/tree.rb', line 830

def self.clean_path path
  path = Line.without_label :line=>path#, :leave_indent=>true
  path.sub!(/^([^|\n-]*)##.+/, "\\1")  # Ignore "##"
  path.sub!(/^([^|\n-]*)\*\*.+/, "\\1")  # Ignore "**"
  path
end

.clear_empty_dirs!(lines, options = {}) ⇒ Object

# TODO Do some checking for duplicates

  end
end


568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
# File 'lib/xiki/tree.rb', line 568

def self.clear_empty_dirs! lines, options={}
  regex = options[:quotes] ?
    /^ +[+-] [^#|]+$|^ *:\d+$/ :
    /^[^|]+\/$/

  lines = lines.split("\n") if lines.is_a?(String)

  file_indent = 0
  i = lines.length
  while( i > 0)
    i -= 1
    l = lines[i]

    l =~ /^( +)/
    spaces = $1 ? $1.length : 0
    if l =~ regex   # If thing to always keep (dir, or dir and file)
      if spaces < file_indent   # If lower than indent, decrement indent
        file_indent -= 2
      else   # Else, delete
        lines.delete_at i
      end
    else   # If file
      file_indent = spaces   # Set indent
    end
  end
  lines
end

.closest_dirObject



1452
1453
1454
1455
1456
1457
1458
1459
# File 'lib/xiki/tree.rb', line 1452

def self.closest_dir
  dir = Xiki.trunk.reverse.find{|o| FileTree.matches_root_pattern? o}

  dir = Bookmarks[dir]
  return nil if dir.nil?

  File.expand_path dir
end

.collapse(options = {}) ⇒ Object



1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
# File 'lib/xiki/tree.rb', line 1319

def self.collapse options={}
  # If at root or end of line, go to next
  Line.next if Line !~ /^ / || Line.at_right?
  CodeTree.kill_siblings

  Move.to_end -1

  Line.sub! /([ +-]*).+/, "\\1" if options[:replace_parent]

  left = View.cursor
  $el.skip_chars_forward(" \n+-")
  View.delete left, View.cursor

  Move.to_end
  left, right = View.paragraph :bounds=>true, :start_here=>true

  $el.indent_rigidly View.cursor, right, -2
end

.construct_path(options = {}) ⇒ Object

Mapped to Enter when on a FileTree buffer. Opens file cursor is on in the tree. It assumes the path to a dir is on the current line.



740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
# File 'lib/xiki/tree.rb', line 740

def self.construct_path options={}
  begin
    path = []
    orig = $el.point

    # Do until we're at a root
    line = Line.value
    clean = self.clean_path line
    while(line =~ /^ / && (options[:all] || clean !~ /^@/))
      line =~ /^  ( *)(.*)/
      spaces, item = $1, $2
      item = clean unless options[:raw]   # Removes labels, ##..., **...
      if item != ""   # If item wasn't completely cleaned away
        path.unshift item  # Add item to list
      end
      $el.search_backward_regexp "^#{spaces}[^\t \n]"

      # If ignoring Ol lines, keep searching until not on one
      if options[:ignore_ol]
        while Line =~ /^[# ]*Ol\b/
          $el.search_backward_regexp "^#{spaces}[^\t \n]"
        end
      end

      line = Line.value
      clean = self.clean_path line
    end
    # Add root of tree
    root = Line.value.sub(/^ +/, '')
    root = self.clean_path(root) unless options[:raw]
    root.slice! /^@ ?/
    path.unshift root

    last = path.length - 1
    path = path.map_with_index{|o, i|
      next o if i == last   # Don't add slash to last
      o =~ /\/$/ ? o : "#{o}/"
    } if options[:slashes]

    $el.goto_char orig
    if options[:indented]
      indentify_path path
    elsif options[:list]
      path
    else
      path.join
    end
  rescue Exception=>e
    raise ".construct_path couldn't construct the path - is this a well-formed tree\?: #{e}"
  end
end

.dir(options = {}) ⇒ Object

Returns the dir that a @menu is nested under, or says must be nested under a dir.

Tree.dir



1685
1686
1687
# File 'lib/xiki/tree.rb', line 1685

def self.dir options={}
  self.file options.merge(:require=>'dir')
end

.dir_at_spot(options = {}) ⇒ Object

Goes to spot (from as+spot) and grabs path?



1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
# File 'lib/xiki/tree.rb', line 1657

def self.dir_at_spot options={}
  orig = Location.new   # Save where we are
  Location.to_spot

  path = Tree.construct_path
  # TODO: Make it use this instead:
  #     path = Tree.construct_path :string=>1 instead

  if options[:delete]
    Effects.glow :fade_out=>1
    Tree.kill_under
    Line.delete

    # Adjust orig if in same file and above
    if orig.file_or_buffer == View.file_or_buffer && orig.line > View.line_number
      orig.line = orig.line - 1
    end
  end

  orig.go   # Go back to where we were

  Bookmarks[path]
end

.dotify!(tree, target) ⇒ Object



1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
# File 'lib/xiki/tree.rb', line 1214

def self.dotify! tree, target
  target_flat = target.join "/"

  self.traverse(tree, :no_bullets=>1) do |branch, path|

    match = self.target_match path, target_flat

    if match == :same || match == :longer
      indent = branch.length - 1

      # If last item in path has period and target doesn't
      if branch[indent] =~ /^\./ && target[indent] !~ /^\./
        # Add period to nth item in target
        target[indent].sub! /^/, '.'
      end
    end
  end

  # Optimization
    # If last path wasn't match and indent is lower than last path, we won't match

end

.enter_underObject

Insert section from a file under it in tree



1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
# File 'lib/xiki/tree.rb', line 1147

def self.enter_under
  Line.beginning
  path = Tree.construct_path  # Get path
  path.sub!(/\|.+/, '')  # Remove file
  path = Bookmarks.expand(path)

  # Cut off indent and pipe (including following space)
  Line.value =~ /(^ +)\| (.+)/
  quote_indent, line = $1, $2

  if line =~ /^> /   # If heading in a notes file
    # Go through lines in file until end of section
    matches = ""
    found_yet = false

    IO.foreach(path, *Files.encoding_binary) do |line|
      l.sub!(/[\r\n]+$/, '')
      l.gsub!("\c@", '.')   # Replace out characters that el4r can't handle
      # Swallow up until match
      if !found_yet
        found_yet = l == line
        next
      end
      # Grab rest until another pipe
      break if l =~ /^\> /

      l = " #{l}" unless l.empty?
      matches << "#{quote_indent}  |#{l}\n"
    end

    # Insert and start search
    Tree.insert_quoted_and_search matches

  else  # Otherwise, grab by indent

    # Go through lines in file until we've found it
    indent = line[/^\s*/].gsub("\t", '        ').length
    matches = ""
    found_yet = false

    IO.foreach(path, *Files.encoding_binary) do |line|
      l.sub!(/[\r\n]+$/, '')
      l.gsub!("\c@", '.')   # Replace out characters that el4r can't handle
      # Swallow up until match
      if !found_yet
        found_yet = l == line
        next
      end
      # Grab rest until not indented less

      current_indent = l[/^\s*/].gsub("\t", '        ').length

      break matches.<<("#{quote_indent}  |\n") if line.blank?

      break if current_indent <= indent

      l = " #{l}" unless l.blank?
      matches << "#{quote_indent}  |#{l}\n"
    end

    # Insert and start search
    Tree.insert_quoted_and_search matches
  end
end

.file(options = {}) ⇒ Object

Returns the dir or file that a @menu is nested under.

Tree.file Tree.file :require=>1 # Shows message if not nested under something Tree.file :require=>‘dir’ # Shows message if not nested under a dir Tree.file :require=>‘file’ # Shows message if not nested under a file



1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
# File 'lib/xiki/tree.rb', line 1695

def self.file options={}
  trunk = Xiki.trunk

  # If tree we're in is a file tree, they probably just wanted that

  return Bookmarks[trunk[-1]] if FileTree.handles? trunk[-1]
  # Just return it if we're under a dir

  dir = trunk[-2]
  dir = "/" if dir == ""   # Root dir will come across as blank

  return Bookmarks[dir].sub('//', '/') if FileTree.handles?(dir)

  # Dir wasn't found, raise message for certain options
  return File.expand_path("~/Desktop") if options[:or] == :desktop
  kind_required = options[:require]
  return nil if ! kind_required

  guessed_menu = trunk.last.split('/').first
  example = options[:example] || (kind_required == "file" ? "/tmp/file.txt" : "/tmp/dir/")

  if kind = options[:require]
    adjective = kind.is_a?(String) ? kind : "filesystem"
    raise "> This menu must be nested under a #{adjective} path, like:\n| - #{example}\n|   @#{guessed_menu}"
  end
  nil
end

.first_letter(lines) ⇒ Object



416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/xiki/tree.rb', line 416

def self.first_letter lines
  letters = {}
  lines.each_with_index do |l, i|
    found = false
    l.length.times do |j|
      #         letter = l[/^  \S+ (\w)/, 1]   # Grab 1st letter
      letter = l[j].chr
      next if letter !~ /[a-z]/i
      next if letters[letter]

      letters[letter] = [i+1, j+1]   # Set letter to index, if not there yet
      break
    end
  end

  self.highlight_tree_keys letters, Line.number

  # Get input
  Message << "type 1st letter... "
  ch, ch_raw = Keys.char
  letterized = $el.char_to_string(Keys.remove_control ch_raw).to_s
  Overlay.delete_all

  if ch_raw == 7   # C-g
    return Cursor.restore :before_file_tree
  end

  if letters[letterized]
    Cursor.restore :before_file_tree
    Line.next letters[letterized][0] - 1
    CodeTree.kill_siblings
    Launcher.launch
    return nil
  end

  # If was a valid letter but no match
  if ch =~ /^[a-z0-9]$/i
    return [ch, ch_raw]   # We didn't do anything, so continue on
  end

  Cursor.restore :before_file_tree
  $el.command_execute ch

  nil   # We handled it
end

.has_child?Boolean

Returns whether the next line is indented one lower

p Tree.has_child?

Returns:

  • (Boolean)


1883
1884
1885
1886
1887
# File 'lib/xiki/tree.rb', line 1883

def self.has_child?

  Line.indent(Line.value)+"  " == Line.indent(Line.value 2)

end

.highlight_tree_keys(letters, line) ⇒ Object



462
463
464
465
466
467
468
469
470
471
472
# File 'lib/xiki/tree.rb', line 462

def self.highlight_tree_keys letters, line

  letters.each do |k, v|
    View.line = line + v[0] - 1
    cursor = View.cursor
    Overlay.face :tree_keys, :left=>cursor-1+v[1], :right=>cursor+v[1]
  end

  View.line = line

end

.indent(txt, line = 1) ⇒ Object



890
891
892
893
# File 'lib/xiki/tree.rb', line 890

def self.indent txt, line=1
  indent = Line.indent(Line.value(line))
  txt.gsub!(/^/, "#{indent}  ")
end

.indent_size(line) ⇒ Object



884
885
886
887
888
# File 'lib/xiki/tree.rb', line 884

def self.indent_size(line)
  spaces = line[/^ +/]
  return 0 unless spaces
  spaces.size / 2
end

.insert_quoted_and_search(matches, options = {}) ⇒ Object



913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
# File 'lib/xiki/tree.rb', line 913

def self.insert_quoted_and_search matches, options={}
  # Insert matches
  Line.next
  left = $el.point
  View.insert matches, options
  right = $el.point

  $el.goto_char left
  if options[:line_found] && options[:line_found] > 0
    Line.next(options[:line_found]-1)
    Color.colorize :l
  end

  Line.to_words
  # Do a search

  return if options[:no_search]

  Tree.search(:left=>left, :right=>right)
end

.is_root?(path) ⇒ Boolean

Returns:

  • (Boolean)


824
825
826
827
828
# File 'lib/xiki/tree.rb', line 824

def self.is_root? path
  # It's the root if it's not at the left margin
  result = path !~ /^ /
  result ? true : false
end

.kill_under(options = {}) ⇒ Object



642
643
644
645
646
647
648
649
650
651
652
# File 'lib/xiki/tree.rb', line 642

def self.kill_under options={}

  # Get indent
  orig = Line.left
  left = Line.left(Keys.prefix_u? ? 1 : 2)

  self.after_children options

  View.delete(left, View.cursor)
  View.to orig
end

.last_itemObject

Grab last path item.

Tree.last_item Tree.last_item # /hey Tree.last_item # /hey/



1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
# File 'lib/xiki/tree.rb', line 1866

def self.last_item

  # If there's a slash (not counting end of line), use after last slash

  if Line.value =~ /\/.+/
    Line[/.+\/(.+)\/$/, 1] || Line[/.+\/(.+)/, 1]
  else   # else, just replace whole line minus bullet
    Line[/^[ +-]*(.+)\//, 1] || Line[/^[ +-]*(.+)/, 1]
  end

end

.leaf(path, options = {}) ⇒ Object

If foo/bar line, returns bar. If pipe-quoted returns siblings as multi-line string (assuming current line is pipe-quoted).

Tree.leaf “hey/you” Tree.leaf “| hii” # Would get siblings also if line cursor is on had siblings



483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
# File 'lib/xiki/tree.rb', line 483

def self.leaf path, options={}
  if path =~ /(?:^\||\/\|) ?(.*)/   # If has ^| or /|, grab siblings
    # We should probably verify that the current line is the same as the path too? (in case cursor moved to other quoted tree?)
    orig = $1
    # First, make sure the current line is quoted (otherwise, .siblings will be pulling from somewhere else)
    return orig if options[:dont_look] || Line.value !~ /^ *\|/

    siblings = Tree.siblings(:quotes=>1)
    siblings = siblings.map{|i| i.gsub(/^\| ?/, '')}.join("\n")  # :
    siblings << "\n" if siblings =~ /\n/
    return siblings
  end

  path.split("/")[-1]

end


4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/xiki/tree.rb', line 4

def self.menu
  "
  - api/
    > Get dir (or file) menu is nested under
    @ p Tree.file
    @ p Tree.file :require=>1   # Raise message if not nested under dir or file
    @ p Tree.file :require=>'file'   # Raise message if not nested under file
    @ p Tree.file :require=>'dir'   # Raise message if not nested under dir

    > Show siblings
    @ p Tree.siblings

    | Include all siblings (current line is usually ommited), just
    | siblings before, or just siblings after:
    @ p Tree.siblings :all=>1
    @ p Tree.siblings :after=>1
    @ p Tree.siblings :before=>1
    @ p Tree.siblings :string=>1   # Consecutive lines, quotes removed

    > Moving around
    @ Tree.to_parent   # Go to parent, regardless of blanks
    @ Tree.after_children   # Go after children of this element, crossing blank lines
    @ Tree.before_siblings   # Jumps to first sibling, crossing blank lines, but not double-blank lines
    @ Tree.after_siblings   # Go after last sibling, crossing blank lines, but not double-blank lines

    > All methods
    @ Tree.meths
  "
end

.minus_to_plusObject



730
731
732
# File 'lib/xiki/tree.rb', line 730

def self.minus_to_plus
  self.toggle_plus_and_minus if Line.matches(/^\s*- /)
end

.minus_to_plus_maybeObject



734
735
736
# File 'lib/xiki/tree.rb', line 734

def self.minus_to_plus_maybe
  self.minus_to_plus if Line.matches(/(^\s*[+-] [a-z]|\/$)/)
end

.output_and_search(block_or_string, options = {}) ⇒ Object



1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
# File 'lib/xiki/tree.rb', line 1357

def self.output_and_search block_or_string, options={}

  line = options[:line]

  if $el
    buffer_orig = View.buffer
    orig = Location.new
    orig_left = View.cursor
  end

  error_happened = nil

  self.unset_env_vars

  output =
    if block_or_string.is_a? String
      block_or_string
    else   # Must be a proc
      begin
        block_or_string.call line
      rescue Exception=>e
        message = e.message

        error_happened = true
        CodeTree.draw_exception e, Code.to_ruby(block_or_string)
      end
    end

  return if output.blank?

  if output.is_a?(String) && $el && output.strip =~ /\A<<< (.+)\/\z/
    Tree.replace_item $1
    Launcher.launch
    return true
  end

  # TODO: move some of this crap into the else block above (block_or_string is proc)


  if $el
    buffer_changed = buffer_orig != View.buffer   # Remember whether we left the buffer

    ended_up = Location.new
    orig.go   # Go back to where we were before running code
  end

  # Move what they printed over to left margin initally, in case they haven't
  output = TextUtil.unindent(output) if output =~ /\A[ \n]/
  # Remove any double linebreaks at end
  output = CodeTree.returned_to_s output

  if $el
    return View.prompt $1 if output =~ /\A\.prompt (.+)/
    return View.flash $1 if output =~ /\A\.flash (.+)/
  end

  output.sub!(/\n\n\z/, "\n")
  output = "#{output}\n" if output !~ /\n\z/

  return output if options[:just_return]


  # Add slash to end of line if not suppressed, and line isn't a quote
  line=options[:line]
  if !options[:no_slash] && ! ENV['no_slash'] && Line !~ /(^ *\||\/$)/
    Line << "/"
  end
  indent = Line.indent
  Line.to_left
  Line.next
  left = View.cursor

  output.gsub! /^./, "#{indent}  \\0"   # Add indent, except for blank lines

  View.<< output, :utf8=>1
  right = View.cursor

  orig.go   # Move cursor back  <-- why doing this?
  ended_up.go   # End up where script took us
  moved = View.cursor != orig_left

  # Move to :line_found if any
  if options[:line_found] && options[:line_found] > 0
    Line.next(options[:line_found])
    Color.colorize :l
  end

  if !error_happened && !$xiki_no_search &&!options[:no_search] && !buffer_changed && !moved
    Tree.search_appropriately left, right, output, options
  elsif ! options[:line_found]
    Line.to_beginning :down=>1
  end
  output
end

.path(options = {}) ⇒ Object



1723
1724
1725
1726
# File 'lib/xiki/tree.rb', line 1723

def self.path options={}
  path = Tree.construct_path(:all=>1, :slashes=>1)
  options[:string] ? path : path.split(/\/@ ?/)
end

.paths_to_tree(paths) ⇒ Object



1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
# File 'lib/xiki/tree.rb', line 1728

def self.paths_to_tree paths
  result = ""
  stack = []
  paths.sort.each do |path|   # For each path

    beginning_slash = path =~ /^\//
    ending_slash = path =~ /\/$/
    split = path.sub(/^\//, '').split('/')

    split[0].sub! /^/, '/' if beginning_slash   # Restore beginning slash after split
    split[-1].sub! /$/, '/' if ending_slash   # Restore beginning slash after split

    # put all slashes back first!

    # Pop from stack until path begins with stack
    while(stack.size > 0 && stack != split[0..(stack.size - 1)])
      stack.pop
    end
    indent = stack.length   # Get remainder of path after stack
    remainder = split[indent..-1]
    remainder.each do |dir|
      result << ("  " * indent) + dir
      result << "\n"
      indent += 1
    end
    stack = split
  end
  self.add_pluses_and_minuses result
  result
end

.plus_to_minusObject



722
723
724
# File 'lib/xiki/tree.rb', line 722

def self.plus_to_minus
  self.toggle_plus_and_minus if Line.matches(/^\s*\+ /)
end

.plus_to_minus_maybeObject



726
727
728
# File 'lib/xiki/tree.rb', line 726

def self.plus_to_minus_maybe
  self.plus_to_minus if Line.matches(/(^\s*[+-] [a-z]|\/$)/)
end

.quote(txt, options = {}) ⇒ Object



903
904
905
906
907
908
909
910
911
# File 'lib/xiki/tree.rb', line 903

def self.quote txt, options={}
  if options[:leave_headings]
    return TextUtil.unindent(txt).gsub(/^([^>])/, "| \\1").gsub(/^\| $/, '|')
  end

  TextUtil.unindent(txt).gsub(/^/, "| ").gsub(/^\| $/, '|')

  #     TextUtil.unindent(txt).gsub(/^([^|@>+-])/, "| \\1").gsub(/^\| $/, '|')
end

.replace_item(txt) ⇒ Object

Replace last path item with this string.

Tree.replace_item “replaces whole line” Tree.replace_item “replaces after slash” # /hey Tree.replace_item “replaces after slash” # /hey/



1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
# File 'lib/xiki/tree.rb', line 1841

def self.replace_item txt

  new_ends_in_slash = txt =~ /\/$/   # If slash at end, remember to not add another one

  # If there's a slash (not counting end of line), replace after last slash

  if Line.value =~ /\/.+/
    (! new_ends_in_slash && Line.sub!(/(.+)\/.+\/$/, "\\1/#{txt}/")) ||
      Line.sub!(/(.+)\/.+/, "\\1/#{txt}")
  else   # else, just replace whole line minus bullet
    (! new_ends_in_slash && Line.sub!(/^([ +-]*).+\//, "\\1#{txt}\/")) ||
      Line.sub!(/^([ +-]*).+/, "\\1#{txt}")
  end

  nil
end

.rest(path) ⇒ Object

Cuts off 1st item in the path.

Use instead of .leaf when you know all but the root is part of the leaf (in case there are slashes).

Tree.rest “hey/you/there”



1292
1293
1294
1295
1296
1297
1298
# File 'lib/xiki/tree.rb', line 1292

def self.rest path

  path = self.rootless path
  path = "|#{path}" unless path =~ /^(\||$)/

  self.leaf(path)
end

.restore(treea, treeb) ⇒ Object

Copy children from treeb to treea, but only for branches in treea where children were removed.



1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
# File 'lib/xiki/tree.rb', line 1302

def self.restore treea, treeb

  treea, treeb = TreeCursor.new(treea), TreeCursor.new(treeb)

  # For each leaf in A
  treea.each do
    next unless treea.at_leaf?   # We only care about leafs

    treeb.select treea.line   # Find branch in B
    next if treeb.at_leaf?   # Skip if no children children

    treea << treeb.under   # Grab them and move into A
  end

  treea.txt
end

.root(path) ⇒ Object



504
505
506
# File 'lib/xiki/tree.rb', line 504

def self.root path
  path.sub /\/.*/, ''
end

.rootless(path) ⇒ Object



500
501
502
# File 'lib/xiki/tree.rb', line 500

def self.rootless path
  path.sub /^\/?[^\/]+\/?/, ''
end

.search(options = {}) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
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
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# File 'lib/xiki/tree.rb', line 34

def self.search options={}
  return $xiki_no_search=false if $xiki_no_search

  recursive = options[:recursive]
  recursive_quotes = options[:recursive_quotes]
  left, right = options[:left], options[:right]
  if ! left
    ignore, left, right = View.block_positions "^>"
  end

  # No search if there aren't more than 3 lines
  return if((Line.number(right) - Line.number(left)) <= 1 && View.txt(left, right) !~ /\/$/) && options[:always_search].nil?

  # Make cursor blue
  Cursor.remember :before_file_tree

  Cursor.box

  error = ""

  pattern = ""
  lines = $el.buffer_substring(left, right).split "\n"

  ch = nil

  if options[:first_letter]
    ch, ch_raw = self.first_letter lines
    return if ! ch
  end

  Message << "filter... "

  ch, ch_raw = Keys.char if ch.nil?

  if ch.nil?
    return Cursor.restore(:before_file_tree)
  end

  # While chars to search for (alpha chars etc.), narrow down list...

  while ch.is_a?(String) && (ch =~ /[ -"&-),-.\/-:<?A-~]/ &&   # Be careful editing, due to ranges (_-_)
      (ch_raw < 67108912 || ch_raw > 67108921) && ch_raw != 67108909) # ||   # If not control-<number> or C--

    if ch == ' ' && pattern != ""   # If space and not already cleared out
      pattern = ''
    elsif ch_raw == 2   # C-b
      while(Line.previous == 0)
        next if FileTree.dir?   # Keep going if line is a dir
        Line.to_words
        break   # Otherwise, stop
      end

    elsif ch_raw == 6   # C-f
      while(Line.next == 0)
        next if FileTree.dir?   # Keep going if line is a dir
        Line.to_words
        break   # Otherwise, stop
      end

    else
      if ch == "\\"  # If escape, get real char
        ch = $el.char_to_string($el.read_char)
      end
      pattern << Regexp.quote(ch)

      if pattern =~ /[A-Z]$/   # If upper, remove any lower
        pattern.sub!(/^[a-z]+/, '')
      elsif pattern =~ /[a-z]$/   # If lower, remove any upper
        pattern.sub!(/^[A-Z]+/, '')
      end

      regexp = pattern

      $el.delete_region left, right

      regexp = "\\/$|#{regexp}" if recursive
      # Always keep if "- file" or "- /dir"
      regexp = "^ *:\\d|^ *[+-] [a-zA-Z0-9@:.\/]|#{regexp}" if recursive_quotes

      regexp = /#{regexp}/i
      lines_new = nil
      if pattern =~ /[A-Z]$/   # If upper, search in directory
        lines_new = search_dir_names(lines, /#{pattern}/i)
      else
        lines_new = lines.grep(regexp)
      end
      # If search not found, don't delete all
      if lines_new.size == 0
        error = "   ---------- no matches! ---------- "
        View.beep
      else
        lines = lines_new
      end

      # Remove dirs with nothing under them
      self.clear_empty_dirs! lines if recursive
      self.clear_empty_dirs!(lines, :quotes=>true) if recursive_quotes

      # Put back into buffer
      View.insert(lines.join("\n") + "\n")
      right = $el.point

      # Go to first file
      $el.goto_char left

      # Move to first file
      if recursive
        FileTree.select_next_file
      elsif recursive_quotes
        Search.forward "|\\|#"
        Line.to_beginning
      else
        Line.to_beginning
      end
    end

    message = "filter... #{pattern}#{error}"
    message << "    (space for 'and')" if pattern.present?
    Message << message
    ch, ch_raw = Keys.char

    if ch.nil?
      return Cursor.restore(:before_file_tree)
    end

  end

  Cursor.restore :before_file_tree   # Exiting, so restore cursor

  # Options during search

  # Do something based on char that exited search, like run as command...

  case ch
  when "0"
    file = self.construct_path   # Expand out ~
    # Open in OS
    $el.shell_command("open #{file}")
    #     when "\C-a"
    #       Line.to_left
  when "\C-j"
    ch = Keys.input :chars=>1
    if ch == 't'   # just_time
      self.to_parent
      self.kill_under
      FileTree.dir :date_sort=>true
    elsif ch == 's'   # just_size
      self.to_parent
      self.kill_under
      FileTree.dir :size_sort=>true
    elsif ch == 'n'   # just_name
      self.to_parent
      self.kill_under
      FileTree.dir
    elsif ch == 'a'   # just_all

      # If a quote, insert lines indented lower
      if Line.matches(/\|/)
        CodeTree.kill_siblings
        self.enter_under
      elsif FileTree.dir?  # A Dir, so do recursive search
        $el.delete_region(Line.left(2), right)
        FileTree.dir_recursive
      else   # A file, so enter lines
        $el.delete_region(Line.left(2), right)
        FileTree.enter_lines(//)  # Insert all lines
      end
    end
  when :return   # If return, just stop (like isearch)
    # Do nothing

    Keys.clear_prefix
    Launcher.launch

  when :control_return, "\C-m", :control_period #, :right   # If C-., go in but don't collapse siblings
    Keys.clear_prefix
    Launcher.launch
  when "\t"   # If tab, hide siblings and go in
    $el.delete_region(Line.left(2), right)
    Keys.clear_prefix
    Launcher.launch
  when :backspace #, :left   # Collapse tree
    self.to_parent
    self.kill_under
    self.search(:left => Line.left, :right => Line.left(2))

  when :control_slash   # Collapse tree and exit

    line = Line.txt

    # Don't kill siblings if "<<" or "<=" line

    if line =~ /^<+=? /
      Keys.clear_prefix
      Launcher.launch
      return
    end

    # If CodeTree search
    if CodeTree.handles?
      # Kill others
      View.delete(Line.left(2), right)

      if Line.without_label =~ /^\./   # If just a method
        # Back up to first . on last line
        Search.forward "\\."
        right = View.cursor
        Line.previous
        Search.forward "\\."
      else   # Else, just delete previous line
        right = View.cursor
        Line.previous
        Line.to_beginning
      end
      View.delete(View.cursor, right)
      return Launcher.launch
    end

    $el.delete_region(Line.left(2), right)  # Delete other files
    $el.delete_horizontal_space
    $el.delete_backward_char 1

    # delete -|+ if there
    if View.txt(View.cursor, Line.right) =~ /^[+-] /
      $el.delete_char 2
    end

    Launcher.launch if line =~ /\/$/   # Only launch if it can expand


  when "#"   # Show ##.../ search
    self.stop_and_insert left, right, pattern
    View.insert self.indent("- ##/", 0)
    View.to(Line.right - 1)

  when "*"   # Show **.../ search
    self.stop_and_insert left, right, pattern
    View.insert self.indent("- **/", 0)
    View.to(Line.right - 1)

  when "$"   # Insert '$ ' for command
    self.stop_and_insert left, right, pattern
    View.insert self.indent("$ ", 0)

  when "%"   # Insert '!' for command
    self.stop_and_insert left, right, pattern
    View.insert self.indent("% ", 0)

  when "-"   # Insert '-' for bullet
    self.stop_and_insert left, right, pattern
    View.insert self.indent("- ", 0)

  when "@"   # Insert '@' for menus
    self.stop_and_insert left, right, pattern
    View.insert self.indent("@", 0)

  when "+"   # Create dir
    self.stop_and_insert left, right, pattern, :dont_disable_control_lock=>true
    Line.previous
    parent = self.construct_path
    Line.next
    View.insert self.indent("", 0)
    name = Keys.input(:prompt=>'Name of dir to create: ')
    Dir.mkdir("#{parent}#{name}")
    View.insert "- #{name}/\n"
    View.insert self.indent("", 0)
    #Line.to_right

  when ">"   # Split view, then launch
    $el.delete_region(Line.left(2), right)
    Keys.clear_prefix
    View.create
    Launcher.launch

  when "\C-e"   # Also C-a
    return Line.to_right

  when "\C-a"   # Also C-a

    return Line.to_left

  when "\C-o"   # When 9 or C-o, show methods, or outline
    $el.delete_region(Line.left(2), right)   # Delete other files
    return FileTree.drill_quotes_or_enter_lines self.construct_path.sub(/\|.*/, ''), Line.=~(/^ *\|/)
  when "1".."9"
    if ch == "7" and ! View.bar?   # Open in bar
      $el.delete_region(Line.left(2), right)  # Delete other files
      View.bar
      Keys.clear_prefix
      return Launcher.launch   # Expand or open
    end
    Keys.clear_prefix
    n = ch.to_i

    # Pull whole string out
    lines = $el.buffer_substring(left, right).split "\n"
    $el.delete_region left, right
    if recursive
      filtered = []
      file_count = 0
      # Replace out lines that don't match (and aren't dirs)
      lines.each_with_index do |l, i|
        is_dir = (l =~ /\/$/)
        file_count += 1 unless is_dir
        # If dir or nth, keep
        filtered << l if (is_dir or (file_count == n))
      end

      # Remove dirs with nothing under them
      self.clear_empty_dirs! filtered

      # Put back into buffer
      View.insert(filtered.join("\n") + "\n")
      right = $el.point

      # Go to first file and go back into search
      $el.goto_char left
      FileTree.select_next_file

      # Todo: merge this and the following .search
      self.search(:recursive => true, :left => Line.left, :right => Line.left(2))
    else
      nth = lines[ch.to_i - 1]
      View.insert "#{nth}\n"
      $el.previous_line
      if options[:number_means_enter]  # If explicitly supposed to enter
        Launcher.launch
      elsif FileTree.dir?  # If a dir, go into it
        Launcher.launch
      else
        Launcher.launch
        return

        Line.to_beginning
        # Get back into search, waiting for input
        self.search(:left => Line.left, :right => Line.left(2))
      end
    end

  when "\C-s"
    $el.isearch_forward

  when "\C-r"
    $el.isearch_backward

  when ";"   # Replace parent

    #       CodeTree.kill_siblings
    Tree.collapse :replace_parent=>1
    return Launcher.launch


  # when "/"   # Append selected dir to parent dir
  # Just search for a slash


  when "="   # Drill into the file
    dir = self.construct_path  # Expand out ~
    View.open(dir)

    # Does something else above
    #     when "0"   # Drill into the file
    #       # TODO Is this ever used? - does it work?
    #       $el.delete_region(Line.left(2), right)   # Delete other files
    #       self.drill

  when "\a"   # Typed C-g
    View.beep
  when :left
    Move.backward
  when :right
    Move.forward
  when :up
    $el.previous_line
  when :down
    $el.next_line
  else
    $el.command_execute ch
  end
end

.search_appropriately(left, right, output, options = {}) ⇒ Object



1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
# File 'lib/xiki/tree.rb', line 1077

def self.search_appropriately left, right, output, options={}

  View.cursor = left unless options[:line_found]
  Line.to_words

  # Determine how to search based on output!

  options.merge! :left=>left, :right=>right

  root_indent = output[/\A */]
  if output =~ /^#{root_indent}  /   # If any indenting
    if output =~ /^  +\|/
      Search.forward "^ +\\(|\\|- ##\\)", :beginning=>true
      Line.to_beginning
      options[:recursive_quotes] = true
    else
      FileTree.select_next_file
      options[:recursive] = true
    end
    Tree.search options
  else
    Tree.search options.merge(:number_means_enter=>true)
  end

end

.search_dir_names(lines, regexp) ⇒ Object



860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
# File 'lib/xiki/tree.rb', line 860

def self.search_dir_names(lines, regexp)
  result = []
  stack = [0]
  indent = indent_size(lines[0])
  lines.each do |l|
    last_indent = indent

    indent, name = l.match(/^( *)(.+)/)[1..2]
    indent = indent_size(indent)
    if indent > last_indent
      stack << 0
    elsif indent < last_indent
      (last_indent - indent).times { stack.pop }
    end
    stack[stack.size-1] = name
    # If file, remove this line if path doesn't match
    if stack.last !~ /\/$/
      next unless stack[0..-2].join =~ regexp
    end
    result << l
  end
  result
end

.sibling_bounds(options = {}) ⇒ Object

Gets position before and after sibling bounds.

Tree.sibling_bounds # Returns: start, current line start, current line end, end Tree.sibling_bounds :cross_blank_lines=>1 # Returns: top, bottom



1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
# File 'lib/xiki/tree.rb', line 1033

def self.sibling_bounds options={}
  if options[:cross_blank_lines]   # If :cross_blank_lines, just jump to parent, then use .after_children
    orig = Location.new
    Tree.before_siblings
    left = Line.left
    Tree.after_siblings
    right = View.cursor

    orig.go
    return [left, right]
  end

  indent_size = Line.indent.size   # Get indent
  indent_less = indent_size - 1

  orig = Location.new

  right1 = Line.left   # Right side of lines before

  # Search for line indented less - parent (to get siblings after)
  found = indent_less < 0 ?
    Search.backward("^$", :go_anyway=>1) :
    Search.backward("^ \\{0,#{indent_less}\\}\\($\\|[^ \n]\\)")

  Line.next if found
  left1 = Line.left   # Left side of lines before

  orig.go

  # Search for line indented same or less (to get siblings after)
  Line.next
  Search.forward "^ \\{0,#{indent_size}\\}\\($\\|[^ \n]\\)"   # Move after original node's children, if any
  Line.to_left
  left2 = View.cursor
  # Search for line indented less
  indent_less < 0 ?
    Search.forward("^$") :
    Search.forward("^ \\{0,#{indent_less}\\}\\($\\|[^ \n]\\)")
  right2 = Line.left   # Left side of lines before
  orig.go

  [left1, right1, left2, right2]
end

.siblings(options = {}) ⇒ Object

sample chile



961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
# File 'lib/xiki/tree.rb', line 961

def self.siblings options={}
  return self.siblings(:all=>true).join("\n").gsub(/^ *\| ?/, '')+"\n" if options[:string] && ! options[:cross_blank_lines] && ! options[:before] && ! options[:after]

  if options[:cross_blank_lines]
    left1, right2 = self.sibling_bounds :cross_blank_lines=>1
    # For now, if :cross_blank_lines, assume just left1, right2
    options[:all] = 1
  else
    left1, right1, left2, right2 = self.sibling_bounds# options
  end

  # Combine and process siblings
  if options[:all] || options[:everything]
    siblings = View.txt(options.merge(:left=>left1, :right=>right2))

  elsif options[:quotes]   # Only grab contiguous quoted lines
    above = View.txt(options.merge(:left=>left1, :right=>right1))
    found = true
    above = above.split("\n").reverse.select{|o| found && o =~ /^ *\|/ or found = false}.reverse.join("\n")
    above << "\n" if above.any?

    middle = View.txt(options.merge(:left=>right1, :right=>left2))

    below = View.txt(options.merge(:left=>left2, :right=>right2))
    found = true
    below = below.split("\n").select{|o| found && o =~ /^ *\|/ or found = false}.join("\n")
    below << "\n" if below.any?

    siblings = "#{above}#{middle}#{below}"  #.strip

  elsif options[:before]
    siblings = View.txt(options.merge(:left=>left1, :right=>right1))
  elsif options[:after]
    siblings = View.txt(options.merge(:left=>left2, :right=>right2))
  else
    # By default, don't include sibling on current line
    # TODO: swap this, so it includes all by default?
    # Go through and make new :exclude_current param, and make invocations use it
    siblings = View.txt(options.merge(:left=>left1, :right=>right1)) + View.txt(options.merge(:left=>left2, :right=>right2))
  end

  if options[:everything]
    indent = siblings[/\A */]
    return siblings.gsub(/^#{indent}/, '')
  end

  siblings.gsub! /^#{Line.indent} .*\n/, ''   # Remove more indented lines (children)
  siblings.gsub! /^ +\n/, ''   # Remove blank lines
  siblings.gsub! /^ +/, ''   # Remove indents

  if options[:string]   # Must have :before or :after option also if it got here
    return siblings.gsub /^\| ?/, ''
  end

  siblings = siblings.split("\n")

  unless options[:include_label]   # Optionally remove labels
    siblings.map!{|i| Line.without_label(:line=>i)}
  end

  # Change blanks to nil
  siblings.map!{|o| o.blank? ? nil : o}

  siblings
end

.slashless(txt) ⇒ Object

Tree.slashless(“hey/you/”).should == “hey/you”



1462
1463
1464
# File 'lib/xiki/tree.rb', line 1462

def self.slashless txt
  txt.sub /\/$/, ''
end

.stop_and_insert(left, right, pattern, options = {}) ⇒ Object



596
597
598
599
600
601
602
603
604
# File 'lib/xiki/tree.rb', line 596

def self.stop_and_insert left, right, pattern, options={}
  $el.goto_char left
  # TODO: delete left if recursive - emulate what "delete" does to delete, first
  pattern == "" ?
    $el.delete_region($el.point, right) :
    Line.next
  $el.open_line 1
  ControlLock.disable unless options[:dont_disable_control_lock]
end

.subtreeObject

Returns subtree rooted at cursor.



1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
# File 'lib/xiki/tree.rb', line 1639

def self.subtree

  # Just return line if no children
  return Line.value if ! Tree.has_child?

  orig = View.cursor
  left = Line.left
  Line.next
  ignore, right = Tree.sibling_bounds :cross_blank_lines=>1
  View.cursor = orig

  txt = View.txt left, right
  txt
end

.target_match(path, target) ⇒ Object

Called by Tree.children and Tree.dotify

p Tree.target_match “a/b”, “a/b” p Tree.target_match “a/b”, “a” p Tree.target_match “a”, “a/b”



1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
# File 'lib/xiki/tree.rb', line 1245

def self.target_match path, target
  pi, ti = 0, 0

  while true
    pathi, targeti = path[pi], target[ti]

    if pathi.nil? || targeti.nil?
      return :same if pathi.nil? && targeti.nil?   # Handles a, a and a/, a/
      return :same if pathi.nil? && target[ti+1].nil? && targeti.chr == "/"   # Handles a, a/
      return :same if targeti.nil? && path[pi+1].nil? && pathi.chr == "/"   # Handles a/, a

      return :shorter if pathi && (pathi.chr == "/" || path[pi-1].chr == "/" || pi == 0)
      return :longer if targeti && (targeti.chr == "/" || target[ti-1].chr == "/" || ti == 0)
      return nil   # At end of one, but no match
    end

    # If chars equal, increment
    if pathi == targeti
      pi += 1
      next ti += 1

    # If path has /. or . at beginning, increment path
    elsif pathi.chr == "." && (path[pi-1].chr == "/" || pi == 0) && (target[ti-1].chr == "/" || ti == 0)
      next pi += 1

    # If path has /*/ or /* at end, increment path and increment target to next / or end
    elsif pathi.chr == "*" && path[pi-1].chr == "/" && (path[pi+1].nil? || path[pi+1].chr == "/")
      pi += 1
      ti = target.index("/", ti+1) || target.length
      next
    end

    break   # Not found
  end

  nil
end

.to_html(txt) ⇒ Object

Tree.to_html “p/n hin”



1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
# File 'lib/xiki/tree.rb', line 1766

def self.to_html txt
  html = ""

  txt = txt.gsub /^( *)([+-] )?(\w[\w ]*\/)(.+)/, "\\1\\3\n\\1  \\4"   # Preprocess to break foo/Bar into separate lines

  previous = []
  Tree.traverse(txt) do |l, path|

    last = l.last
    next if !last   # Blank lines

    self.add_closing_tags html, l, previous   # If lower than last, add any closing tags

    last = Line.without_label :line=>last
    if last =~ /([^*\n]+)\/$/
      tag = $1
      html.<<("  " * (l.length-1)) unless l[-2] =~ /[ +-]*pre\/$/

      next html << "<#{tag}>\n"
    end

    last.sub! /^\| ?/, ''

    if last =~ /\.\.\.$/   # If "Lorem..." or "Lorem ipsum..." etc. make progressively longer
      old_length = last.length
      last.gsub!(/\w+/){|o| @@lorem[o.downcase] || o}
      last.sub!(/\.\.\.$/, '') if last.length != old_length   # Remove ... if we replaced something
    end

    parent = l[-2]
    html.<<("  " * (l.length-1)) unless parent =~ /[ +-]*pre\/$/

    html << "#{last}\n"
  end


  self.add_closing_tags html, [], previous

  html
end

.to_parent(prefix = nil) ⇒ Object

Jumps to parent, regardless of blanks



699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
# File 'lib/xiki/tree.rb', line 699

def self.to_parent prefix=nil #, options={}

  prefix ||= Keys.prefix :clear=>true

  # U means go to previous line at margin
  if prefix == :u
    Search.backward "^[^ \t\n]"
    return
  end

  times = prefix || 1
  times.times do
    indent = Line.value[/^  ( *)/, 1]

    # If odd indent, subtract 1
    indent.slice!(/ /) if indent && indent.length % 2 == 1

    regex = "^#{indent}[^\t \n]"
    $el.search_backward_regexp regex
    Line.to_beginning :quote=>1
  end
end

.to_root(options = {}) ⇒ Object

Moves cursor to root of tree.

Tree.to_root # To last @.. line Tree.to_root :highest=>1 # All the way to highest root (left margin)



798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
# File 'lib/xiki/tree.rb', line 798

def self.to_root options={}

  Move.to_end   # In case already at left of line and root

  # Always go up at least once
  Tree.to_parent

  # Until we're at the root, keep jumping to parent
  line = Line.value

  if options[:highest]
    while(line =~ /^\s/) do
      Tree.to_parent
      line = Line.value
    end
    return
  end

  while(line =~ /^\s/ && line !~ /^ *([+-] )?@/) do
    Tree.to_parent
    line = Line.value
  end

  nil
end

.toggle_plus_and_minusObject



837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
# File 'lib/xiki/tree.rb', line 837

def self.toggle_plus_and_minus
  orig = Location.new
  l = Line.value 1, :delete => true
  case l[/^\s*([+-])/, 1]
  when '+'
    View.insert l.sub(/^(\s*)([+-]) /, "\\1- ")
    orig.go
    '+'
  when '-'
    View.insert l.sub(/^(\s*)([+-]) /, "\\1+ ")
    orig.go
    '-'
  else
    View.insert l
    orig.go
    nil
  end
end

.traverse(tree, options = {}, &block) ⇒ Object

Iterate through each line in the tree Tree.traverse(“an b”) {|o| p o}



1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
# File 'lib/xiki/tree.rb', line 1105

def self.traverse tree, options={}, &block
  branch, line_indent, indent = [], 0, 0

  tree = tree.split("\n")
  tree.each_with_index do |line, i|

    # If empty line use last non-blank line's indent
    if line.empty?
      line = nil
      last_indent = line_indent
      line_indent = tree[i+1]   # Use indent of following line
      line_indent = line_indent ? (line_indent[/^ */].length / 2) : 0
      raise "Blank lines in trees between parents and children aren't allowed.  Also 2 consecutive blank lines isn't allowed." if line_indent > 0 && line_indent > last_indent
    else
      line_indent = line[/^ */].length / 2
      line.strip!
    end

    branch[line_indent] = line

    if line_indent < indent
      branch = branch[0..line_indent]
    end

    branch_dup = branch.dup

    if options[:no_bullets]
      branch_dup.map!{|o| o ? o.sub(/^[<+-][<=]* /, '') : nil }
    end

    flattened = branch_dup.dup

    flattened.map!{|o| o ? o.sub(/^[+-] /, '') : nil } if ! options[:no_bullets]   # Might have side-effects if done twice
    flattened = flattened.join('')#.gsub(/[.:]/, '')   # Why were :'s removed??

    block.call [branch_dup, flattened]

    indent = line_indent
  end
end

.under(txt, options = {}) ⇒ Object

Inserts text indented under, and searches. Called by Tree.<<



658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
# File 'lib/xiki/tree.rb', line 658

def self.under txt, options={}

  return if txt.nil?

  txt = TextUtil.unindent(txt) if txt =~ /\A[ \n]/

  escape = options[:escape] || ''
  txt = txt.gsub!(/^/, escape)
  txt.gsub!(/^\| $/, '|')

  # Add linebreak at end if none
  txt = "#{txt}\n" unless txt =~ /\n/

  # Insert linebreak if at end of file

  txt.gsub! /^  /, '' if options[:before] || options[:after]   # Move back to left

  self.output_and_search txt, options
  nil
end

.unquote(txt) ⇒ Object



899
900
901
# File 'lib/xiki/tree.rb', line 899

def self.unquote txt
  txt.gsub(/^\| ?/, '')
end

.unquote!(txt) ⇒ Object



895
896
897
# File 'lib/xiki/tree.rb', line 895

def self.unquote! txt
  txt.replace self.unquote(txt)
end

.unset_env_varsObject

Returns self and all siblings (without children).



1353
1354
1355
# File 'lib/xiki/tree.rb', line 1353

def self.unset_env_vars
  ENV['no_slash'] = nil
end