xiki 0.5.0a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +11 -0
- data/LICENSE +22 -0
- data/README.markdown +83 -0
- data/Rakefile +8 -0
- data/bin/xiki +46 -0
- data/etc/command/xiki_command.rb +203 -0
- data/etc/command/xiki_process.rb +52 -0
- data/etc/command/xiki_wrapper +2 -0
- data/etc/js/menu1.js +55 -0
- data/etc/js/xiki.js +259 -0
- data/etc/logo.png +0 -0
- data/etc/presentations/bootstrap.deck +5 -0
- data/etc/presentations/databases.deck +41 -0
- data/etc/presentations/diffs.deck +23 -0
- data/etc/presentations/documentation.deck +14 -0
- data/etc/presentations/effects.deck +5 -0
- data/etc/presentations/face.deck +297 -0
- data/etc/presentations/files.deck +79 -0
- data/etc/presentations/icons.deck +22 -0
- data/etc/presentations/images.deck +24 -0
- data/etc/presentations/key_shortcuts.deck +16 -0
- data/etc/presentations/macros.deck +18 -0
- data/etc/presentations/menu_classes.deck +44 -0
- data/etc/presentations/menu_directories.deck +30 -0
- data/etc/presentations/notes.deck +19 -0
- data/etc/presentations/other_languages.deck +55 -0
- data/etc/presentations/other_wiki_syntaxes.deck +4 -0
- data/etc/presentations/piano.deck +5 -0
- data/etc/presentations/potential/diffs.deck +38 -0
- data/etc/presentations/potential/evolution.deck +18 -0
- data/etc/presentations/potential/intro.deck +711 -0
- data/etc/presentations/potential/intro1.deck +711 -0
- data/etc/presentations/potential/intro2.deck +97 -0
- data/etc/presentations/potential/make_mysql_menu.deck +36 -0
- data/etc/presentations/potential/ruby_development.deck +17 -0
- data/etc/presentations/potential/ui_prototyping.deck +50 -0
- data/etc/presentations/potential/web_dev.deck +4 -0
- data/etc/presentations/potential/web_development.deck +10 -0
- data/etc/presentations/potential/wiki.deck +45 -0
- data/etc/presentations/presentations.deck +24 -0
- data/etc/presentations/rails_development.deck +29 -0
- data/etc/presentations/search_key_shortcuts.deck +37 -0
- data/etc/presentations/simplest_possible_ui.deck +37 -0
- data/etc/presentations/svg.deck +5 -0
- data/etc/presentations/testing.deck +28 -0
- data/etc/presentations/the_end.deck +13 -0
- data/etc/presentations/type_something_and_double_click.deck +57 -0
- data/etc/presentations/type_the_acronym.deck +144 -0
- data/etc/presentations/web_browser.deck +56 -0
- data/etc/presentations/xiki_command.deck +20 -0
- data/etc/presentations/xiki_url.deck +14 -0
- data/etc/shark.icns +0 -0
- data/etc/shark_script.icns +0 -0
- data/etc/snippets/html.notes +7 -0
- data/etc/snippets/notes.notes +20 -0
- data/etc/snippets/rb.notes +38 -0
- data/etc/templates/menu_template.menu +2 -0
- data/etc/templates/menu_template.rb +8 -0
- data/etc/templates/template.rb +5 -0
- data/etc/themes/Dark_Metal.notes +28 -0
- data/etc/themes/Orange_Path.notes +15 -0
- data/etc/themes/Shiny_Blue.notes +27 -0
- data/etc/themes/Shiny_Green.notes +27 -0
- data/etc/wrappers/wrapper.js +17 -0
- data/etc/wrappers/wrapper.py +20 -0
- data/etc/wrappers/wrapper.rb +25 -0
- data/lib/block.rb +72 -0
- data/lib/bookmarks.rb +352 -0
- data/lib/buffers.rb +170 -0
- data/lib/clipboard.rb +333 -0
- data/lib/code.rb +860 -0
- data/lib/code_tree.rb +476 -0
- data/lib/color.rb +274 -0
- data/lib/console.rb +557 -0
- data/lib/control_lock.rb +9 -0
- data/lib/control_tab.rb +176 -0
- data/lib/core_ext.rb +31 -0
- data/lib/cursor.rb +111 -0
- data/lib/deletes.rb +65 -0
- data/lib/diff_log.rb +297 -0
- data/lib/effects.rb +145 -0
- data/lib/environment.rb +5 -0
- data/lib/file_tree.rb +1875 -0
- data/lib/files.rb +334 -0
- data/lib/hide.rb +259 -0
- data/lib/history.rb +286 -0
- data/lib/image.rb +51 -0
- data/lib/incrementer.rb +15 -0
- data/lib/insert.rb +7 -0
- data/lib/irc.rb +22 -0
- data/lib/key_bindings.rb +658 -0
- data/lib/keys.rb +754 -0
- data/lib/launcher.rb +1351 -0
- data/lib/line.rb +429 -0
- data/lib/links.rb +6 -0
- data/lib/location.rb +175 -0
- data/lib/macros.rb +48 -0
- data/lib/man.rb +19 -0
- data/lib/menu.rb +708 -0
- data/lib/merb.rb +259 -0
- data/lib/message.rb +5 -0
- data/lib/meths.rb +56 -0
- data/lib/mode.rb +34 -0
- data/lib/move.rb +248 -0
- data/lib/notes.rb +1000 -0
- data/lib/numbers.rb +45 -0
- data/lib/ol.rb +203 -0
- data/lib/ol_helper.rb +44 -0
- data/lib/overlay.rb +167 -0
- data/lib/pause_means_space.rb +68 -0
- data/lib/php.rb +22 -0
- data/lib/projects.rb +21 -0
- data/lib/relinquish_exception.rb +2 -0
- data/lib/remote.rb +206 -0
- data/lib/requirer.rb +46 -0
- data/lib/rest_tree.rb +108 -0
- data/lib/ruby.rb +57 -0
- data/lib/ruby_console.rb +165 -0
- data/lib/search.rb +1572 -0
- data/lib/search_term.rb +40 -0
- data/lib/snippet.rb +68 -0
- data/lib/specs.rb +229 -0
- data/lib/styles.rb +274 -0
- data/lib/svn.rb +682 -0
- data/lib/text_util.rb +110 -0
- data/lib/tree.rb +1871 -0
- data/lib/tree_cursor.rb +87 -0
- data/lib/trouble_shooting.rb +27 -0
- data/lib/url_tree.rb +11 -0
- data/lib/view.rb +1474 -0
- data/lib/window.rb +133 -0
- data/lib/xiki.rb +404 -0
- data/menus/accounts.rb +5 -0
- data/menus/address_book.rb +21 -0
- data/menus/agenda.rb +28 -0
- data/menus/all.rb +5 -0
- data/menus/amazon.rb +16 -0
- data/menus/app.rb +16 -0
- data/menus/applescript.rb +46 -0
- data/menus/as.rb +15 -0
- data/menus/bookmarklet.rb +63 -0
- data/menus/bootstrap.rb +568 -0
- data/menus/browse.rb +13 -0
- data/menus/browser.rb +78 -0
- data/menus/cassandra_db.rb +36 -0
- data/menus/chmod.rb +27 -0
- data/menus/classes.rb +5 -0
- data/menus/coffee_script.rb +35 -0
- data/menus/computer.rb +24 -0
- data/menus/contacts.rb +5 -0
- data/menus/cookies.rb +25 -0
- data/menus/couch.rb +184 -0
- data/menus/crop.rb +45 -0
- data/menus/css.rb +55 -0
- data/menus/current.rb +5 -0
- data/menus/db.rb +12 -0
- data/menus/deck.rb +219 -0
- data/menus/dictionary.rb +9 -0
- data/menus/dir.rb +8 -0
- data/menus/disk.rb +5 -0
- data/menus/do.rb +13 -0
- data/menus/docs.rb +58 -0
- data/menus/dotsies.rb +107 -0
- data/menus/edited.rb +18 -0
- data/menus/emacs.rb +17 -0
- data/menus/enter.rb +13 -0
- data/menus/eval.rb +17 -0
- data/menus/executable.rb +16 -0
- data/menus/filter.rb +46 -0
- data/menus/firefox.rb +607 -0
- data/menus/foo.rb +30 -0
- data/menus/french.rb +7 -0
- data/menus/git.rb +185 -0
- data/menus/gito.rb +785 -0
- data/menus/google.rb +35 -0
- data/menus/google_images.rb +11 -0
- data/menus/google_patents.rb +10 -0
- data/menus/gutenberg.rb +13 -0
- data/menus/head.rb +10 -0
- data/menus/headings.rb +39 -0
- data/menus/html.rb +61 -0
- data/menus/icon.rb +40 -0
- data/menus/images.menu +2 -0
- data/menus/img.rb +15 -0
- data/menus/info.rb +9 -0
- data/menus/ip.rb +10 -0
- data/menus/iterm.rb +36 -0
- data/menus/itunes.rb +78 -0
- data/menus/javascript.rb +74 -0
- data/menus/layout.rb +18 -0
- data/menus/local_storage.rb +67 -0
- data/menus/ls.rb +19 -0
- data/menus/mac.rb +87 -0
- data/menus/maps.rb +18 -0
- data/menus/matches.rb +18 -0
- data/menus/memcache.rb +117 -0
- data/menus/mkdir.rb +23 -0
- data/menus/mongo.rb +83 -0
- data/menus/mysql.rb +294 -0
- data/menus/node.rb +88 -0
- data/menus/open.rb +19 -0
- data/menus/outline.rb +24 -0
- data/menus/piano.rb +746 -0
- data/menus/postgres.rb +34 -0
- data/menus/pre.rb +5 -0
- data/menus/python.rb +39 -0
- data/menus/rails.rb +160 -0
- data/menus/rake.rb +12 -0
- data/menus/redmine.rb +168 -0
- data/menus/riak_tree.rb +204 -0
- data/menus/rss.rb +15 -0
- data/menus/safari.rb +11 -0
- data/menus/sass.rb +15 -0
- data/menus/say.rb +6 -0
- data/menus/scale.rb +49 -0
- data/menus/serve.rb +78 -0
- data/menus/shuffle.rb +24 -0
- data/menus/spanish.rb +7 -0
- data/menus/standalone.rb +57 -0
- data/menus/tail.rb +41 -0
- data/menus/technologies.rb +19 -0
- data/menus/themes.rb +32 -0
- data/menus/thesaurus.rb +13 -0
- data/menus/to.rb +24 -0
- data/menus/twitter.rb +57 -0
- data/menus/wikipedia.rb +34 -0
- data/menus/words.rb +11 -0
- data/spec/code_tree_spec.rb +59 -0
- data/spec/diff_log_spec.rb +40 -0
- data/spec/file_tree_spec.rb +102 -0
- data/spec/keys_spec.rb +24 -0
- data/spec/line_spec.rb +68 -0
- data/spec/menu_spec.rb +50 -0
- data/spec/ol_spec.rb +98 -0
- data/spec/remote_spec.rb +43 -0
- data/spec/search_spec.rb +162 -0
- data/spec/text_util_spec.rb +119 -0
- data/spec/tree_cursor_spec.rb +91 -0
- data/spec/tree_spec.rb +955 -0
- data/tests/console_test.rb +11 -0
- data/tests/couch_db_test.rb +12 -0
- data/tests/diff_log_test.rb +43 -0
- data/tests/el_mixin.rb +16 -0
- data/tests/git_test.rb +95 -0
- data/tests/keys_test.rb +19 -0
- data/tests/line_test.rb +38 -0
- data/tests/merb_test.rb +11 -0
- data/tests/redmine_test.rb +50 -0
- data/tests/remote_test.rb +31 -0
- data/tests/rest_tree_test.rb +70 -0
- data/xiki.gemspec +37 -0
- metadata +332 -0
data/lib/text_util.rb
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
require 'line'
|
|
2
|
+
require 'ol'
|
|
3
|
+
|
|
4
|
+
class TextUtil
|
|
5
|
+
|
|
6
|
+
def self.menu
|
|
7
|
+
%`
|
|
8
|
+
> Change case
|
|
9
|
+
@ TextUtil.camel_case "hey you"
|
|
10
|
+
@ TextUtil.hyphen_case "hey you"
|
|
11
|
+
@ TextUtil.snake_case "hey you"
|
|
12
|
+
@ TextUtil.title_case "hey you"
|
|
13
|
+
|
|
14
|
+
> Modify vars in-place
|
|
15
|
+
You can also use bang versions, like:
|
|
16
|
+
@ s = "hey you"; TextUtil.camel_case! s; p s
|
|
17
|
+
|
|
18
|
+
> Unindent
|
|
19
|
+
@ TextUtil.unindent "hey you"
|
|
20
|
+
`
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.case_choices
|
|
24
|
+
[
|
|
25
|
+
['upper', lambda {|o| o.upcase}],
|
|
26
|
+
['lower', lambda {|o| o.downcase}],
|
|
27
|
+
['title', lambda {|o| TextUtil.title_case(o)}],
|
|
28
|
+
['camel', lambda {|o| TextUtil.camel_case(o)}],
|
|
29
|
+
['snake', lambda {|o| TextUtil.snake_case(o)}],
|
|
30
|
+
['plus', lambda {|o| TextUtil.plus_case(o)}],
|
|
31
|
+
['hyphen', lambda {|o| TextUtil.hyphen_case(o)}],
|
|
32
|
+
]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.unindent txt, options={}
|
|
36
|
+
txt = txt.sub(/\A\n/, '')# if ! options[:no_strip] # Delete initial blank line if there
|
|
37
|
+
|
|
38
|
+
txt.gsub!(/^\s+/) { |t| t.gsub("\t", ' ') } # Untab indent
|
|
39
|
+
|
|
40
|
+
# If 1st line has no indent and 2nd line has indent (at least 3 spaces)
|
|
41
|
+
if txt !~ /\A / and txt =~ /\A.+\n( +)/
|
|
42
|
+
indent = $1 # Indent left by 2nd indent
|
|
43
|
+
return txt.gsub(/^#{indent}/, '')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
old_indent = Line.indent(txt) # Get indent of first line
|
|
47
|
+
|
|
48
|
+
txt.gsub!(/^#{old_indent}/, '') # Delete current indent
|
|
49
|
+
"#{txt.strip}\n"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# TextUtil.snake_case("hi there") # -> hi_there
|
|
53
|
+
def self.snake_case s
|
|
54
|
+
s.gsub(/[ -]/, '_').
|
|
55
|
+
gsub(/([a-z0-9])([A-Z])/) {"#{$1}_#{$2}"}.downcase.
|
|
56
|
+
gsub(/[^\w]/, "").
|
|
57
|
+
gsub(/__+/, "_")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# TextUtil.plus_case("hi there") # -> hi+there
|
|
61
|
+
def self.plus_case s
|
|
62
|
+
self.snake_case(s).gsub('_', '+')
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.snake_case! s
|
|
66
|
+
s.replace self.snake_case(s)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# TextUtil.hyphen_case("hi there") # -> hi-there
|
|
70
|
+
def self.hyphen_case s
|
|
71
|
+
s.gsub(/[ _]/, '-').
|
|
72
|
+
gsub(/([a-z])([A-Z0-9])/) {"#{$1}-#{$2}"}.downcase.
|
|
73
|
+
gsub(/--+/, "-")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# TextUtil.camel_case("hi there") # -> HiThere
|
|
77
|
+
def self.camel_case s
|
|
78
|
+
# If it's all capitals, make subsequent copitals lowercase
|
|
79
|
+
if s =~ /^[A-Z_-]+$/
|
|
80
|
+
s = s.gsub(/([A-Z][A-Z]+)/) {"#{$1.capitalize}"}
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
s.gsub(/[ -]/, '_').
|
|
84
|
+
gsub(/_([a-z]+)/) {"#{$1.capitalize}"}.
|
|
85
|
+
sub(/(.)/) {$1.upcase}.
|
|
86
|
+
gsub("_", "").
|
|
87
|
+
gsub(/[^\w]/, "")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def self.camel_case! s
|
|
91
|
+
s.replace self.camel_case(s)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# TextUtil.title_case("hi there") # -> Hi There
|
|
95
|
+
def self.title_case s, options={}
|
|
96
|
+
s = s.gsub(/[ -]/, '_').
|
|
97
|
+
gsub(/([a-z])([A-Z0-9])/) {"#{$1}_#{$2}"}.downcase.
|
|
98
|
+
gsub(/([a-z]+)/) {"#{$1.capitalize}"}.
|
|
99
|
+
gsub(/__*/, " ")
|
|
100
|
+
|
|
101
|
+
s.gsub! " ", "_" if options[:underscores]
|
|
102
|
+
|
|
103
|
+
s
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def self.title_case! s
|
|
107
|
+
s.replace self.title_case(s)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
end
|
data/lib/tree.rb
ADDED
|
@@ -0,0 +1,1871 @@
|
|
|
1
|
+
require 'tree_cursor'
|
|
2
|
+
|
|
3
|
+
class Tree
|
|
4
|
+
def self.menu
|
|
5
|
+
"
|
|
6
|
+
- api/
|
|
7
|
+
> Get dir (or file) menu is nested under
|
|
8
|
+
@ p Tree.file
|
|
9
|
+
@ p Tree.file :require=>1 # Raise message if not nested under dir or file
|
|
10
|
+
@ p Tree.file :require=>'file' # Raise message if not nested under file
|
|
11
|
+
@ p Tree.file :require=>'dir' # Raise message if not nested under dir
|
|
12
|
+
|
|
13
|
+
> Show siblings
|
|
14
|
+
@ p Tree.siblings
|
|
15
|
+
|
|
16
|
+
| Include all siblings (current line is usually ommited), just
|
|
17
|
+
| siblings before, or just siblings after:
|
|
18
|
+
@ p Tree.siblings :all=>1
|
|
19
|
+
@ p Tree.siblings :after=>1
|
|
20
|
+
@ p Tree.siblings :before=>1
|
|
21
|
+
@ p Tree.siblings :string=>1 # Consecutive lines, quotes removed
|
|
22
|
+
|
|
23
|
+
> Moving around
|
|
24
|
+
@ Tree.to_parent # Go to parent, regardless of blanks
|
|
25
|
+
@ Tree.after_children # Go after children of this element, crossing blank lines
|
|
26
|
+
@ Tree.before_siblings # Jumps to first sibling, crossing blank lines, but not double-blank lines
|
|
27
|
+
@ Tree.after_siblings # Go after last sibling, crossing blank lines, but not double-blank lines
|
|
28
|
+
|
|
29
|
+
> All methods
|
|
30
|
+
@ Tree.meths
|
|
31
|
+
"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.search options={}
|
|
35
|
+
return $xiki_no_search=false if $xiki_no_search
|
|
36
|
+
|
|
37
|
+
recursive = options[:recursive]
|
|
38
|
+
recursive_quotes = options[:recursive_quotes]
|
|
39
|
+
left, right = options[:left], options[:right]
|
|
40
|
+
if ! left
|
|
41
|
+
ignore, left, right = View.block_positions "^>"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# No search if there aren't more than 3 lines
|
|
45
|
+
return if((Line.number(right) - Line.number(left)) <= 1 && View.txt(left, right) !~ /\/$/) && options[:always_search].nil?
|
|
46
|
+
|
|
47
|
+
# Make cursor blue
|
|
48
|
+
Cursor.remember :before_file_tree
|
|
49
|
+
Cursor.blue
|
|
50
|
+
error = ""
|
|
51
|
+
|
|
52
|
+
pattern = ""
|
|
53
|
+
lines = $el.buffer_substring(left, right).split "\n"
|
|
54
|
+
|
|
55
|
+
ch = nil
|
|
56
|
+
|
|
57
|
+
if options[:first_letter]
|
|
58
|
+
ch, ch_raw = self.first_letter lines
|
|
59
|
+
return if ! ch
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
Message << "filter... "
|
|
63
|
+
|
|
64
|
+
ch, ch_raw = Keys.char if ch.nil?
|
|
65
|
+
|
|
66
|
+
if ch.nil?
|
|
67
|
+
return Cursor.restore(:before_file_tree)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# While chars to search for (alpha chars etc.), narrow down list...
|
|
71
|
+
|
|
72
|
+
while (ch =~ /[ -"&-),-.\/-:<?A-~]/ && # Be careful editing, due to ranges (_-_)
|
|
73
|
+
(ch_raw < 67108912 || ch_raw > 67108921) && ch_raw != 67108909) || # If not control-<number> or C--
|
|
74
|
+
(recursive && ch_raw == 2 || ch_raw == 6) ||
|
|
75
|
+
ch == :up || ch == :down
|
|
76
|
+
|
|
77
|
+
if ch == ' ' && pattern != "" # If space and not already cleared out
|
|
78
|
+
pattern = ''
|
|
79
|
+
elsif ch_raw == 2 # C-b
|
|
80
|
+
while(Line.previous == 0)
|
|
81
|
+
next if FileTree.dir? # Keep going if line is a dir
|
|
82
|
+
Line.to_words
|
|
83
|
+
break # Otherwise, stop
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
elsif ch_raw == 6 # C-f
|
|
87
|
+
while(Line.next == 0)
|
|
88
|
+
next if FileTree.dir? # Keep going if line is a dir
|
|
89
|
+
Line.to_words
|
|
90
|
+
break # Otherwise, stop
|
|
91
|
+
end
|
|
92
|
+
elsif ch == :up
|
|
93
|
+
Line.previous
|
|
94
|
+
Line.to_words
|
|
95
|
+
elsif ch == :down
|
|
96
|
+
Line.next
|
|
97
|
+
Line.to_words
|
|
98
|
+
|
|
99
|
+
else
|
|
100
|
+
if ch == "\\" # If escape, get real char
|
|
101
|
+
ch = $el.char_to_string($el.read_char)
|
|
102
|
+
end
|
|
103
|
+
pattern << Regexp.quote(ch)
|
|
104
|
+
|
|
105
|
+
if pattern =~ /[A-Z]$/ # If upper, remove any lower
|
|
106
|
+
pattern.sub!(/^[a-z]+/, '')
|
|
107
|
+
elsif pattern =~ /[a-z]$/ # If lower, remove any upper
|
|
108
|
+
pattern.sub!(/^[A-Z]+/, '')
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
regexp = pattern
|
|
112
|
+
|
|
113
|
+
$el.delete_region left, right
|
|
114
|
+
|
|
115
|
+
regexp = "\\/$|#{regexp}" if recursive
|
|
116
|
+
# Always keep if "- file" or "- /dir"
|
|
117
|
+
regexp = "^ *:\\d|^ *[+-] [a-zA-Z0-9@:.\/]|#{regexp}" if recursive_quotes
|
|
118
|
+
|
|
119
|
+
regexp = /#{regexp}/i
|
|
120
|
+
lines_new = nil
|
|
121
|
+
if pattern =~ /[A-Z]$/ # If upper, search in directory
|
|
122
|
+
lines_new = search_dir_names(lines, /#{pattern}/i)
|
|
123
|
+
else
|
|
124
|
+
lines_new = lines.grep(regexp)
|
|
125
|
+
end
|
|
126
|
+
# If search not found, don't delete all
|
|
127
|
+
if lines_new.size == 0
|
|
128
|
+
error = " ---------- no matches! ---------- "
|
|
129
|
+
View.beep
|
|
130
|
+
else
|
|
131
|
+
lines = lines_new
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Remove dirs with nothing under them
|
|
135
|
+
self.clear_empty_dirs! lines if recursive
|
|
136
|
+
self.clear_empty_dirs!(lines, :quotes=>true) if recursive_quotes
|
|
137
|
+
|
|
138
|
+
# Put back into buffer
|
|
139
|
+
View.insert(lines.join("\n") + "\n")
|
|
140
|
+
right = $el.point
|
|
141
|
+
|
|
142
|
+
# Go to first file
|
|
143
|
+
$el.goto_char left
|
|
144
|
+
|
|
145
|
+
# Move to first file
|
|
146
|
+
if recursive
|
|
147
|
+
FileTree.select_next_file
|
|
148
|
+
elsif recursive_quotes
|
|
149
|
+
Search.forward "|\\|#"
|
|
150
|
+
Line.to_beginning
|
|
151
|
+
else
|
|
152
|
+
Line.to_beginning
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
message = "filter... #{pattern}#{error}"
|
|
158
|
+
message << " (space for 'and')" if pattern.present?
|
|
159
|
+
Message << message
|
|
160
|
+
ch, ch_raw = Keys.char
|
|
161
|
+
if ch.nil?
|
|
162
|
+
return Cursor.restore(:before_file_tree)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
Cursor.restore :before_file_tree # Exiting, so restore cursor
|
|
168
|
+
|
|
169
|
+
# Options during search
|
|
170
|
+
|
|
171
|
+
# Do something based on char that exited search, like run as command...
|
|
172
|
+
|
|
173
|
+
case ch
|
|
174
|
+
when "0"
|
|
175
|
+
file = self.construct_path # Expand out ~
|
|
176
|
+
# Open in OS
|
|
177
|
+
$el.shell_command("open #{file}")
|
|
178
|
+
# when "\C-a"
|
|
179
|
+
# Line.to_left
|
|
180
|
+
when "\C-j"
|
|
181
|
+
ch = Keys.input :chars=>1
|
|
182
|
+
if ch == 't' # just_time
|
|
183
|
+
self.to_parent
|
|
184
|
+
self.kill_under
|
|
185
|
+
FileTree.dir :date_sort=>true
|
|
186
|
+
elsif ch == 's' # just_size
|
|
187
|
+
self.to_parent
|
|
188
|
+
self.kill_under
|
|
189
|
+
FileTree.dir :size_sort=>true
|
|
190
|
+
elsif ch == 'n' # just_name
|
|
191
|
+
self.to_parent
|
|
192
|
+
self.kill_under
|
|
193
|
+
FileTree.dir
|
|
194
|
+
end
|
|
195
|
+
when :return # If return, just stop (like isearch)
|
|
196
|
+
# Do nothing
|
|
197
|
+
|
|
198
|
+
Keys.clear_prefix
|
|
199
|
+
Launcher.launch
|
|
200
|
+
|
|
201
|
+
# when :control_return, :return, "\C-m", :control_period, :right # If C-., go in but don't collapse siblings
|
|
202
|
+
when :control_return, "\C-m", :control_period, :right # If C-., go in but don't collapse siblings
|
|
203
|
+
Keys.clear_prefix
|
|
204
|
+
Launcher.launch
|
|
205
|
+
when "\t" # If tab, hide siblings and go in
|
|
206
|
+
$el.delete_region(Line.left(2), right)
|
|
207
|
+
Keys.clear_prefix
|
|
208
|
+
Launcher.launch
|
|
209
|
+
when :backspace, :left # Collapse tree
|
|
210
|
+
self.to_parent
|
|
211
|
+
self.kill_under
|
|
212
|
+
self.search(:left => Line.left, :right => Line.left(2))
|
|
213
|
+
|
|
214
|
+
when :control_slash # Collapse tree and exit
|
|
215
|
+
|
|
216
|
+
line = Line.txt
|
|
217
|
+
|
|
218
|
+
# Don't kill siblings if "<<" or "<=" line
|
|
219
|
+
|
|
220
|
+
if line =~ /^<+=? /
|
|
221
|
+
Keys.clear_prefix
|
|
222
|
+
Launcher.launch
|
|
223
|
+
return
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# If CodeTree search
|
|
227
|
+
if CodeTree.handles?
|
|
228
|
+
# Kill others
|
|
229
|
+
View.delete(Line.left(2), right)
|
|
230
|
+
|
|
231
|
+
if Line.without_label =~ /^\./ # If just a method
|
|
232
|
+
# Back up to first . on last line
|
|
233
|
+
Search.forward "\\."
|
|
234
|
+
right = View.cursor
|
|
235
|
+
Line.previous
|
|
236
|
+
Search.forward "\\."
|
|
237
|
+
else # Else, just delete previous line
|
|
238
|
+
right = View.cursor
|
|
239
|
+
Line.previous
|
|
240
|
+
Line.to_beginning
|
|
241
|
+
end
|
|
242
|
+
View.delete(View.cursor, right)
|
|
243
|
+
return Launcher.launch
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
$el.delete_region(Line.left(2), right) # Delete other files
|
|
247
|
+
$el.delete_horizontal_space
|
|
248
|
+
$el.delete_backward_char 1
|
|
249
|
+
|
|
250
|
+
# delete -|+ if there
|
|
251
|
+
if View.txt(View.cursor, Line.right) =~ /^[+-] /
|
|
252
|
+
$el.delete_char 2
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
Launcher.launch if line =~ /\/$/ # Only launch if it can expand
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
when "#" # Show ##.../ search
|
|
259
|
+
self.stop_and_insert left, right, pattern
|
|
260
|
+
View.insert self.indent("- ##/", 0)
|
|
261
|
+
View.to(Line.right - 1)
|
|
262
|
+
|
|
263
|
+
when "*" # Show **.../ search
|
|
264
|
+
self.stop_and_insert left, right, pattern
|
|
265
|
+
View.insert self.indent("- **/", 0)
|
|
266
|
+
View.to(Line.right - 1)
|
|
267
|
+
|
|
268
|
+
when "$" # Insert '$ ' for command
|
|
269
|
+
self.stop_and_insert left, right, pattern
|
|
270
|
+
View.insert self.indent("$ ", 0)
|
|
271
|
+
|
|
272
|
+
when "%" # Insert '!' for command
|
|
273
|
+
self.stop_and_insert left, right, pattern
|
|
274
|
+
View.insert self.indent("% ", 0)
|
|
275
|
+
|
|
276
|
+
when "-" # Insert '-' for bullet
|
|
277
|
+
self.stop_and_insert left, right, pattern
|
|
278
|
+
View.insert self.indent("- ", 0)
|
|
279
|
+
|
|
280
|
+
when "@" # Insert '@' for menus
|
|
281
|
+
self.stop_and_insert left, right, pattern
|
|
282
|
+
View.insert self.indent("@", 0)
|
|
283
|
+
|
|
284
|
+
when "+" # Create dir
|
|
285
|
+
self.stop_and_insert left, right, pattern, :dont_disable_control_lock=>true
|
|
286
|
+
Line.previous
|
|
287
|
+
parent = self.construct_path
|
|
288
|
+
Line.next
|
|
289
|
+
View.insert self.indent("", 0)
|
|
290
|
+
name = Keys.input(:prompt=>'Name of dir to create: ')
|
|
291
|
+
Dir.mkdir("#{parent}#{name}")
|
|
292
|
+
View.insert "- #{name}/\n"
|
|
293
|
+
View.insert self.indent("", 0)
|
|
294
|
+
#Line.to_right
|
|
295
|
+
|
|
296
|
+
when ">" # Split view, then launch
|
|
297
|
+
$el.delete_region(Line.left(2), right)
|
|
298
|
+
Keys.clear_prefix
|
|
299
|
+
View.create
|
|
300
|
+
Launcher.launch
|
|
301
|
+
|
|
302
|
+
when "\C-a" # Also C-a
|
|
303
|
+
# If a quote, insert lines indented lower
|
|
304
|
+
if Line.matches(/\|/)
|
|
305
|
+
CodeTree.kill_siblings
|
|
306
|
+
self.enter_under
|
|
307
|
+
elsif FileTree.dir? # A Dir, so do recursive search
|
|
308
|
+
$el.delete_region(Line.left(2), right)
|
|
309
|
+
FileTree.dir_recursive
|
|
310
|
+
else # A file, so enter lines
|
|
311
|
+
$el.delete_region(Line.left(2), right)
|
|
312
|
+
FileTree.enter_lines(//) # Insert all lines
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
when "\C-o" # When 9 or C-o, show methods, or outline
|
|
316
|
+
$el.delete_region(Line.left(2), right) # Delete other files
|
|
317
|
+
return FileTree.drill_quotes_or_enter_lines self.construct_path.sub(/\|.*/, ''), Line.=~(/^ *\|/)
|
|
318
|
+
when "1".."9"
|
|
319
|
+
if ch == "7" and ! View.bar? # Open in bar
|
|
320
|
+
$el.delete_region(Line.left(2), right) # Delete other files
|
|
321
|
+
View.bar
|
|
322
|
+
Keys.clear_prefix
|
|
323
|
+
# Expand or open
|
|
324
|
+
Launcher.launch
|
|
325
|
+
return
|
|
326
|
+
end
|
|
327
|
+
Keys.clear_prefix
|
|
328
|
+
n = ch.to_i
|
|
329
|
+
|
|
330
|
+
# Pull whole string out
|
|
331
|
+
lines = $el.buffer_substring(left, right).split "\n"
|
|
332
|
+
$el.delete_region left, right
|
|
333
|
+
if recursive
|
|
334
|
+
filtered = []
|
|
335
|
+
file_count = 0
|
|
336
|
+
# Replace out lines that don't match (and aren't dirs)
|
|
337
|
+
lines.each_with_index do |l, i|
|
|
338
|
+
is_dir = (l =~ /\/$/)
|
|
339
|
+
file_count += 1 unless is_dir
|
|
340
|
+
# If dir or nth, keep
|
|
341
|
+
filtered << l if (is_dir or (file_count == n))
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# Remove dirs with nothing under them
|
|
345
|
+
self.clear_empty_dirs! filtered
|
|
346
|
+
|
|
347
|
+
# Put back into buffer
|
|
348
|
+
View.insert(filtered.join("\n") + "\n")
|
|
349
|
+
right = $el.point
|
|
350
|
+
|
|
351
|
+
# Go to first file and go back into search
|
|
352
|
+
$el.goto_char left
|
|
353
|
+
FileTree.select_next_file
|
|
354
|
+
|
|
355
|
+
# Todo: merge this and the following .search
|
|
356
|
+
self.search(:recursive => true, :left => Line.left, :right => Line.left(2))
|
|
357
|
+
else
|
|
358
|
+
nth = lines[ch.to_i - 1]
|
|
359
|
+
View.insert "#{nth}\n"
|
|
360
|
+
$el.previous_line
|
|
361
|
+
if options[:number_means_enter] # If explicitly supposed to enter
|
|
362
|
+
Launcher.launch
|
|
363
|
+
elsif FileTree.dir? # If a dir, go into it
|
|
364
|
+
Launcher.launch
|
|
365
|
+
else
|
|
366
|
+
Launcher.launch
|
|
367
|
+
return
|
|
368
|
+
|
|
369
|
+
Line.to_beginning
|
|
370
|
+
# Get back into search, waiting for input
|
|
371
|
+
self.search(:left => Line.left, :right => Line.left(2))
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
when "\C-s"
|
|
376
|
+
$el.isearch_forward
|
|
377
|
+
|
|
378
|
+
when "\C-r"
|
|
379
|
+
$el.isearch_backward
|
|
380
|
+
|
|
381
|
+
when ";" # Replace parent
|
|
382
|
+
|
|
383
|
+
# CodeTree.kill_siblings
|
|
384
|
+
Tree.collapse :replace_parent=>1
|
|
385
|
+
return Launcher.launch
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
# when "/" # Append selected dir to parent dir
|
|
389
|
+
# Just search for a slash
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
when "=" # Drill into the file
|
|
393
|
+
dir = self.construct_path # Expand out ~
|
|
394
|
+
View.open(dir)
|
|
395
|
+
|
|
396
|
+
# Does something else above
|
|
397
|
+
# when "0" # Drill into the file
|
|
398
|
+
# # TODO Is this ever used? - does it work?
|
|
399
|
+
# $el.delete_region(Line.left(2), right) # Delete other files
|
|
400
|
+
# self.drill
|
|
401
|
+
|
|
402
|
+
when "\a" # Typed C-g
|
|
403
|
+
View.beep
|
|
404
|
+
else
|
|
405
|
+
$el.command_execute ch
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def self.first_letter lines
|
|
411
|
+
letters = {}
|
|
412
|
+
lines.each_with_index do |l, i|
|
|
413
|
+
found = false
|
|
414
|
+
l.length.times do |j|
|
|
415
|
+
# letter = l[/^ \S+ (\w)/, 1] # Grab 1st letter
|
|
416
|
+
letter = l[j].chr
|
|
417
|
+
next if letter !~ /[a-z]/i
|
|
418
|
+
next if letters[letter]
|
|
419
|
+
|
|
420
|
+
letters[letter] = [i+1, j+1] # Set letter to index, if not there yet
|
|
421
|
+
break
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
self.highlight_tree_keys letters, Line.number
|
|
426
|
+
|
|
427
|
+
# Get input
|
|
428
|
+
Message << "type 1st letter... "
|
|
429
|
+
ch, ch_raw = Keys.char
|
|
430
|
+
letterized = $el.char_to_string(Keys.remove_control ch_raw).to_s
|
|
431
|
+
Overlay.delete_all
|
|
432
|
+
|
|
433
|
+
if ch_raw == 7 # C-g
|
|
434
|
+
return Cursor.restore :before_file_tree
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
if letters[letterized]
|
|
438
|
+
Cursor.restore :before_file_tree
|
|
439
|
+
Line.next letters[letterized][0] - 1
|
|
440
|
+
CodeTree.kill_siblings
|
|
441
|
+
Launcher.launch
|
|
442
|
+
return nil
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
# If was a valid letter but no match
|
|
446
|
+
if ch =~ /^[a-z0-9]$/i
|
|
447
|
+
return [ch, ch_raw] # We didn't do anything, so continue on
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
Cursor.restore :before_file_tree
|
|
451
|
+
$el.command_execute ch
|
|
452
|
+
|
|
453
|
+
nil # We handled it
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def self.highlight_tree_keys letters, line
|
|
457
|
+
|
|
458
|
+
letters.each do |k, v|
|
|
459
|
+
View.line = line + v[0] - 1
|
|
460
|
+
cursor = View.cursor
|
|
461
|
+
Overlay.face :tree_keys, :left=>cursor-1+v[1], :right=>cursor+v[1]
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
View.line = line
|
|
465
|
+
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
#
|
|
470
|
+
# If foo/bar line, returns bar.
|
|
471
|
+
# If pipe-quoted returns siblings as multi-line string (assuming current
|
|
472
|
+
# line is pipe-quoted).
|
|
473
|
+
#
|
|
474
|
+
# Tree.leaf "hey/you"
|
|
475
|
+
# Tree.leaf "| hii" # Would get siblings also if line cursor is on had siblings
|
|
476
|
+
#
|
|
477
|
+
def self.leaf path, options={}
|
|
478
|
+
if path =~ /(?:^\||\/\|) ?(.*)/ # If has ^| or /|, grab siblings
|
|
479
|
+
# We should probably verify that the current line is the same as the path too? (in case cursor moved to other quoted tree?)
|
|
480
|
+
orig = $1
|
|
481
|
+
# First, make sure the current line is quoted (otherwise, .siblings will be pulling from somewhere else)
|
|
482
|
+
return orig if options[:dont_look] || Line.value !~ /^ *\|/
|
|
483
|
+
|
|
484
|
+
siblings = Tree.siblings(:quotes=>1)
|
|
485
|
+
siblings = siblings.map{|i| i.gsub(/^\| ?/, '')}.join("\n") # :
|
|
486
|
+
siblings << "\n" if siblings =~ /\n/
|
|
487
|
+
return siblings
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
path.split("/")[-1]
|
|
491
|
+
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
def self.rootless path
|
|
495
|
+
path.sub /^\/?[^\/]+\/?/, ''
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
def self.root path
|
|
499
|
+
path.sub /\/.*/, ''
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
#
|
|
504
|
+
# Not used - apparently surplanted during Tree.search by
|
|
505
|
+
# the methods C-a and C-o call.
|
|
506
|
+
#
|
|
507
|
+
# def self.drill
|
|
508
|
+
# Line.to_left
|
|
509
|
+
# # Get indent between | and tabs
|
|
510
|
+
# match = Line.value.match(/^( *)\|( *)(.+)/)
|
|
511
|
+
# # Get content
|
|
512
|
+
# path = Bookmarks.expand(construct_path) # Get path
|
|
513
|
+
# matches = ""
|
|
514
|
+
# if match # It's a quoted line
|
|
515
|
+
# indent, indent_after_bar, rest = match[1,3]
|
|
516
|
+
# indent_after_bar.gsub!("\t", " ")
|
|
517
|
+
# #"#{indent_after_bar}#{rest}"
|
|
518
|
+
# # Go through and find line
|
|
519
|
+
# found = nil
|
|
520
|
+
# IO.foreach(path[/(.+?)\|/, 1]) do |line|
|
|
521
|
+
# line.sub!(/[\r\n]+$/, '')
|
|
522
|
+
# # If not found yet, check for line
|
|
523
|
+
# if !found && line == "#{indent_after_bar}#{rest}"
|
|
524
|
+
# found = true
|
|
525
|
+
# next
|
|
526
|
+
# end
|
|
527
|
+
# next unless found
|
|
528
|
+
|
|
529
|
+
# # Exit if indented at same level (unless blank or comment)
|
|
530
|
+
# if((Line.indent(line).length <= indent_after_bar.length) &&
|
|
531
|
+
# (! (line =~ /^\s*$/)) &&
|
|
532
|
+
# (! (line =~ /^\s*#/))
|
|
533
|
+
# )
|
|
534
|
+
# break
|
|
535
|
+
# end
|
|
536
|
+
|
|
537
|
+
# # Skip unless indented 2 later
|
|
538
|
+
# next unless Line.indent(line).length == 2 + indent_after_bar.length
|
|
539
|
+
|
|
540
|
+
# matches << "#{indent}| #{line}\n"
|
|
541
|
+
# end
|
|
542
|
+
# self.insert_quoted_and_search matches
|
|
543
|
+
# else # It's a file
|
|
544
|
+
# indent = Line.value[/^ */]
|
|
545
|
+
# this_was_used = last_was_used = false
|
|
546
|
+
# IO.foreach(path) do |line| # Print lines with no indent
|
|
547
|
+
# last_was_used = this_was_used
|
|
548
|
+
# this_was_used = false
|
|
549
|
+
# line.sub!(/[\r\n]+$/, '')
|
|
550
|
+
# next if line =~ /^ +/ # Skip non top-level lines
|
|
551
|
+
# next if line =~ /^$/ and ! last_was_used # Skip blank lines, unless following top-level
|
|
552
|
+
# matches << "#{indent} | #{line}\n"
|
|
553
|
+
# this_was_used = true
|
|
554
|
+
# end
|
|
555
|
+
# self.insert_quoted_and_search matches
|
|
556
|
+
# # TODO Search in result
|
|
557
|
+
|
|
558
|
+
# # TODO Do some checking for duplicates
|
|
559
|
+
# end
|
|
560
|
+
# end
|
|
561
|
+
|
|
562
|
+
def self.clear_empty_dirs! lines, options={}
|
|
563
|
+
regex = options[:quotes] ?
|
|
564
|
+
/^ +[+-] [^#|]+$|^ *:\d+$/ :
|
|
565
|
+
/^[^|]+\/$/
|
|
566
|
+
|
|
567
|
+
lines = lines.split("\n") if lines.is_a?(String)
|
|
568
|
+
|
|
569
|
+
file_indent = 0
|
|
570
|
+
i = lines.length
|
|
571
|
+
while( i > 0)
|
|
572
|
+
i -= 1
|
|
573
|
+
l = lines[i]
|
|
574
|
+
|
|
575
|
+
l =~ /^( +)/
|
|
576
|
+
spaces = $1 ? $1.length : 0
|
|
577
|
+
if l =~ regex # If thing to always keep (dir, or dir and file)
|
|
578
|
+
if spaces < file_indent # If lower than indent, decrement indent
|
|
579
|
+
file_indent -= 2
|
|
580
|
+
else # Else, delete
|
|
581
|
+
lines.delete_at i
|
|
582
|
+
end
|
|
583
|
+
else # If file
|
|
584
|
+
file_indent = spaces # Set indent
|
|
585
|
+
end
|
|
586
|
+
end
|
|
587
|
+
lines
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
def self.stop_and_insert left, right, pattern, options={}
|
|
591
|
+
$el.goto_char left
|
|
592
|
+
# TODO: delete left if recursive - emulate what "delete" does to delete, first
|
|
593
|
+
pattern == "" ?
|
|
594
|
+
$el.delete_region($el.point, right) :
|
|
595
|
+
Line.next
|
|
596
|
+
$el.open_line 1
|
|
597
|
+
ControlLock.disable unless options[:dont_disable_control_lock]
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
# Go to the right side of the tree item (move to after last item that is indented lower)
|
|
601
|
+
def self.after_children options={}
|
|
602
|
+
indent = Line.indent.size
|
|
603
|
+
|
|
604
|
+
pattern = "\\(\n\n\n\\|^ \\{0,#{indent}\\}[^ \n]\\)" # Find line with same or less indent
|
|
605
|
+
|
|
606
|
+
# Why would I do this at all?
|
|
607
|
+
# self.minus_to_plus_maybe unless options[:no_plus]
|
|
608
|
+
|
|
609
|
+
Line.next
|
|
610
|
+
Search.forward pattern, :go_anyway=>1
|
|
611
|
+
Line.to_left
|
|
612
|
+
|
|
613
|
+
Search.backward "^."
|
|
614
|
+
Line.next
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
# Move cursor to after last sibling, crossing blank lines, but not double-blank lines.
|
|
618
|
+
# Tree.after_siblings
|
|
619
|
+
def self.after_siblings # options={}
|
|
620
|
+
indent = Line.indent.size
|
|
621
|
+
|
|
622
|
+
return if indent == 0
|
|
623
|
+
|
|
624
|
+
indent -= 2
|
|
625
|
+
|
|
626
|
+
pattern = "\\(\n\n\n\\|^ \\{0,#{indent}\\}[^ \n]\\)" # Find line with less indent
|
|
627
|
+
|
|
628
|
+
Search.forward pattern, :go_anyway=>1
|
|
629
|
+
Line.to_left
|
|
630
|
+
|
|
631
|
+
Search.backward "^."
|
|
632
|
+
Line.next
|
|
633
|
+
nil
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
def self.kill_under options={}
|
|
637
|
+
|
|
638
|
+
# Get indent
|
|
639
|
+
orig = Line.left
|
|
640
|
+
left = Line.left(Keys.prefix_u? ? 1 : 2)
|
|
641
|
+
|
|
642
|
+
self.after_children options
|
|
643
|
+
|
|
644
|
+
View.delete(left, View.cursor)
|
|
645
|
+
View.to orig
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
#
|
|
649
|
+
# Inserts text indented under, and searches.
|
|
650
|
+
# Called by Tree.<<
|
|
651
|
+
#
|
|
652
|
+
def self.under txt, options={}
|
|
653
|
+
|
|
654
|
+
return if txt.nil?
|
|
655
|
+
|
|
656
|
+
txt = TextUtil.unindent(txt) if txt =~ /\A[ \n]/
|
|
657
|
+
|
|
658
|
+
escape = options[:escape] || ''
|
|
659
|
+
txt = txt.gsub!(/^/, escape)
|
|
660
|
+
txt.gsub!(/^\| $/, '|')
|
|
661
|
+
|
|
662
|
+
# Add linebreak at end if none
|
|
663
|
+
txt = "#{txt}\n" unless txt =~ /\n/
|
|
664
|
+
|
|
665
|
+
# Insert linebreak if at end of file
|
|
666
|
+
|
|
667
|
+
txt.gsub! /^ /, '' if options[:before] || options[:after] # Move back to left
|
|
668
|
+
|
|
669
|
+
self.output_and_search txt, options
|
|
670
|
+
nil
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
# Jumps to first sibling, crossing blank lines, but not double-blank lines
|
|
674
|
+
def self.before_siblings
|
|
675
|
+
|
|
676
|
+
indent = Line.value[/^ ( *)/, 1]
|
|
677
|
+
|
|
678
|
+
# For now, don't handle if at root
|
|
679
|
+
|
|
680
|
+
regex = "\\(\n\n\n\\|^#{indent}[^\t \n]\\)"
|
|
681
|
+
Search.backward regex, :go_anyway=>true
|
|
682
|
+
|
|
683
|
+
# Move.to_next_paragraph(:no_skip=>1)
|
|
684
|
+
hit_two_blanks = View.cursor == Line.right
|
|
685
|
+
|
|
686
|
+
return Move.to_next_paragraph(:no_skip=>1) if hit_two_blanks || Line.value(2).blank?
|
|
687
|
+
Line.next
|
|
688
|
+
|
|
689
|
+
# Can't get siblings of item at left margin - undecided how to implement it" if !indent
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
# Jumps to parent, regardless of blanks
|
|
693
|
+
def self.to_parent prefix=nil #, options={}
|
|
694
|
+
|
|
695
|
+
prefix ||= Keys.prefix :clear=>true
|
|
696
|
+
|
|
697
|
+
# U means go to previous line at margin
|
|
698
|
+
if prefix == :u
|
|
699
|
+
Search.backward "^[^ \t\n]"
|
|
700
|
+
return
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
times = prefix || 1
|
|
704
|
+
times.times do
|
|
705
|
+
indent = Line.value[/^ ( *)/, 1]
|
|
706
|
+
|
|
707
|
+
# If odd indent, subtract 1
|
|
708
|
+
indent.slice!(/ /) if indent && indent.length % 2 == 1
|
|
709
|
+
|
|
710
|
+
regex = "^#{indent}[^\t \n]"
|
|
711
|
+
$el.search_backward_regexp regex
|
|
712
|
+
Line.to_beginning :quote=>1
|
|
713
|
+
end
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
def self.plus_to_minus
|
|
717
|
+
self.toggle_plus_and_minus if Line.matches(/^\s*\+ /)
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
def self.plus_to_minus_maybe
|
|
721
|
+
self.plus_to_minus if Line.matches(/(^\s*[+-] [a-z]|\/$)/)
|
|
722
|
+
end
|
|
723
|
+
|
|
724
|
+
def self.minus_to_plus
|
|
725
|
+
self.toggle_plus_and_minus if Line.matches(/^\s*- /)
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
def self.minus_to_plus_maybe
|
|
729
|
+
self.minus_to_plus if Line.matches(/(^\s*[+-] [a-z]|\/$)/)
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
# Mapped to Enter when on a FileTree buffer. Opens file cursor is on in the tree.
|
|
733
|
+
# It assumes the path to a dir is on the current line.
|
|
734
|
+
def self.construct_path options={}
|
|
735
|
+
begin
|
|
736
|
+
path = []
|
|
737
|
+
orig = $el.point
|
|
738
|
+
|
|
739
|
+
# Do until we're at a root
|
|
740
|
+
line = Line.value
|
|
741
|
+
clean = self.clean_path line
|
|
742
|
+
while(line =~ /^ / && (options[:all] || clean !~ /^@/))
|
|
743
|
+
line =~ /^ ( *)(.*)/
|
|
744
|
+
spaces, item = $1, $2
|
|
745
|
+
item = clean unless options[:raw] # Removes labels, ##..., **...
|
|
746
|
+
if item != "" # If item wasn't completely cleaned away
|
|
747
|
+
path.unshift item # Add item to list
|
|
748
|
+
end
|
|
749
|
+
$el.search_backward_regexp "^#{spaces}[^\t \n]"
|
|
750
|
+
|
|
751
|
+
# If ignoring Ol lines, keep searching until not on one
|
|
752
|
+
if options[:ignore_ol]
|
|
753
|
+
while Line =~ /^[# ]*Ol\b/
|
|
754
|
+
$el.search_backward_regexp "^#{spaces}[^\t \n]"
|
|
755
|
+
end
|
|
756
|
+
end
|
|
757
|
+
|
|
758
|
+
line = Line.value
|
|
759
|
+
clean = self.clean_path line
|
|
760
|
+
end
|
|
761
|
+
# Add root of tree
|
|
762
|
+
root = Line.value.sub(/^ +/, '')
|
|
763
|
+
root = self.clean_path(root) unless options[:raw]
|
|
764
|
+
root.slice! /^@ ?/
|
|
765
|
+
path.unshift root
|
|
766
|
+
|
|
767
|
+
last = path.length - 1
|
|
768
|
+
path = path.map_with_index{|o, i|
|
|
769
|
+
next o if i == last # Don't add slash to last
|
|
770
|
+
o =~ /\/$/ ? o : "#{o}/"
|
|
771
|
+
} if options[:slashes]
|
|
772
|
+
|
|
773
|
+
$el.goto_char orig
|
|
774
|
+
if options[:indented]
|
|
775
|
+
indentify_path path
|
|
776
|
+
elsif options[:list]
|
|
777
|
+
path
|
|
778
|
+
else
|
|
779
|
+
path.join
|
|
780
|
+
end
|
|
781
|
+
rescue Exception=>e
|
|
782
|
+
raise ".construct_path couldn't construct the path - is this a well-formed tree\?: #{e}"
|
|
783
|
+
end
|
|
784
|
+
end
|
|
785
|
+
|
|
786
|
+
#
|
|
787
|
+
# Moves cursor to root of tree.
|
|
788
|
+
#
|
|
789
|
+
# Tree.to_root # To last @.. line
|
|
790
|
+
# Tree.to_root :highest=>1 # All the way to highest root (left margin)
|
|
791
|
+
#
|
|
792
|
+
def self.to_root options={}
|
|
793
|
+
|
|
794
|
+
Move.to_end # In case already at left of line and root
|
|
795
|
+
|
|
796
|
+
# Always go up at least once
|
|
797
|
+
Tree.to_parent
|
|
798
|
+
|
|
799
|
+
# Until we're at the root, keep jumping to parent
|
|
800
|
+
line = Line.value
|
|
801
|
+
|
|
802
|
+
if options[:highest]
|
|
803
|
+
while(line =~ /^\s/) do
|
|
804
|
+
Tree.to_parent
|
|
805
|
+
line = Line.value
|
|
806
|
+
end
|
|
807
|
+
return
|
|
808
|
+
end
|
|
809
|
+
|
|
810
|
+
while(line =~ /^\s/ && line !~ /^ *([+-] )?@/) do
|
|
811
|
+
Tree.to_parent
|
|
812
|
+
line = Line.value
|
|
813
|
+
end
|
|
814
|
+
|
|
815
|
+
nil
|
|
816
|
+
end
|
|
817
|
+
|
|
818
|
+
def self.is_root? path
|
|
819
|
+
# It's the root if it's not at the left margin
|
|
820
|
+
result = path !~ /^ /
|
|
821
|
+
result ? true : false
|
|
822
|
+
end
|
|
823
|
+
|
|
824
|
+
def self.clean_path path
|
|
825
|
+
path = Line.without_label :line=>path#, :leave_indent=>true
|
|
826
|
+
path.sub!(/^([^|\n-]*)##.+/, "\\1") # Ignore "##"
|
|
827
|
+
path.sub!(/^([^|\n-]*)\*\*.+/, "\\1") # Ignore "**"
|
|
828
|
+
path
|
|
829
|
+
end
|
|
830
|
+
|
|
831
|
+
def self.toggle_plus_and_minus
|
|
832
|
+
orig = Location.new
|
|
833
|
+
l = Line.value 1, :delete => true
|
|
834
|
+
case l[/^\s*([+-])/, 1]
|
|
835
|
+
when '+'
|
|
836
|
+
View.insert l.sub(/^(\s*)([+-]) /, "\\1- ")
|
|
837
|
+
orig.go
|
|
838
|
+
'+'
|
|
839
|
+
when '-'
|
|
840
|
+
View.insert l.sub(/^(\s*)([+-]) /, "\\1+ ")
|
|
841
|
+
orig.go
|
|
842
|
+
'-'
|
|
843
|
+
else
|
|
844
|
+
View.insert l
|
|
845
|
+
orig.go
|
|
846
|
+
nil
|
|
847
|
+
end
|
|
848
|
+
end
|
|
849
|
+
|
|
850
|
+
def self.acronym_regexp search
|
|
851
|
+
search.gsub(/([a-zA-Z])/, "[a-z]*[_.]\\1").sub(/.+?\].+?\]/,'^ +')
|
|
852
|
+
end
|
|
853
|
+
|
|
854
|
+
def self.search_dir_names(lines, regexp)
|
|
855
|
+
result = []
|
|
856
|
+
stack = [0]
|
|
857
|
+
indent = indent_size(lines[0])
|
|
858
|
+
lines.each do |l|
|
|
859
|
+
last_indent = indent
|
|
860
|
+
|
|
861
|
+
indent, name = l.match(/^( *)(.+)/)[1..2]
|
|
862
|
+
indent = indent_size(indent)
|
|
863
|
+
if indent > last_indent
|
|
864
|
+
stack << 0
|
|
865
|
+
elsif indent < last_indent
|
|
866
|
+
(last_indent - indent).times { stack.pop }
|
|
867
|
+
end
|
|
868
|
+
stack[stack.size-1] = name
|
|
869
|
+
# If file, remove this line if path doesn't match
|
|
870
|
+
if stack.last !~ /\/$/
|
|
871
|
+
next unless stack[0..-2].join =~ regexp
|
|
872
|
+
end
|
|
873
|
+
result << l
|
|
874
|
+
end
|
|
875
|
+
result
|
|
876
|
+
end
|
|
877
|
+
|
|
878
|
+
def self.indent_size(line)
|
|
879
|
+
spaces = line[/^ +/]
|
|
880
|
+
return 0 unless spaces
|
|
881
|
+
spaces.size / 2
|
|
882
|
+
end
|
|
883
|
+
|
|
884
|
+
def self.indent txt, line=1
|
|
885
|
+
indent = Line.indent(Line.value(line))
|
|
886
|
+
txt.gsub!(/^/, "#{indent} ")
|
|
887
|
+
end
|
|
888
|
+
|
|
889
|
+
def self.unquote! txt
|
|
890
|
+
txt.replace self.unquote(txt)
|
|
891
|
+
end
|
|
892
|
+
|
|
893
|
+
def self.unquote txt
|
|
894
|
+
txt.gsub(/^\| ?/, '')
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
def self.quote txt, options={}
|
|
898
|
+
if options[:leave_headings]
|
|
899
|
+
return TextUtil.unindent(txt).gsub(/^([^>])/, "| \\1").gsub(/^\| $/, '|')
|
|
900
|
+
end
|
|
901
|
+
|
|
902
|
+
TextUtil.unindent(txt).gsub(/^/, "| ").gsub(/^\| $/, '|')
|
|
903
|
+
|
|
904
|
+
# TextUtil.unindent(txt).gsub(/^([^|@>+-])/, "| \\1").gsub(/^\| $/, '|')
|
|
905
|
+
end
|
|
906
|
+
|
|
907
|
+
def self.insert_quoted_and_search matches, options={}
|
|
908
|
+
# Insert matches
|
|
909
|
+
Line.next
|
|
910
|
+
left = $el.point
|
|
911
|
+
View.insert matches, options
|
|
912
|
+
right = $el.point
|
|
913
|
+
|
|
914
|
+
$el.goto_char left
|
|
915
|
+
if options[:line_found] && options[:line_found] > 0
|
|
916
|
+
Line.next(options[:line_found]-1)
|
|
917
|
+
Color.colorize :l
|
|
918
|
+
end
|
|
919
|
+
|
|
920
|
+
Line.to_words
|
|
921
|
+
# Do a search
|
|
922
|
+
|
|
923
|
+
return if options[:no_search]
|
|
924
|
+
|
|
925
|
+
Tree.search(:left=>left, :right=>right)
|
|
926
|
+
end
|
|
927
|
+
|
|
928
|
+
def self.<< txt, options={}
|
|
929
|
+
self.under txt, options
|
|
930
|
+
end
|
|
931
|
+
|
|
932
|
+
# Inserts indented underneath.
|
|
933
|
+
# Not sure why it's calling View.under instead of Tree.under
|
|
934
|
+
def self.after txt
|
|
935
|
+
# Is anything calling this - maybe make them just call Tree.under.
|
|
936
|
+
# View.under does a couple lines, then calls Tree.under.
|
|
937
|
+
View.under txt, :after=>1
|
|
938
|
+
end
|
|
939
|
+
|
|
940
|
+
#
|
|
941
|
+
# Returns group of lines close to current line that are indented at the same level.
|
|
942
|
+
# The bounds are determined by any lines indented *less* than the current line (including
|
|
943
|
+
# blank lines). In this context, lines having only spaces are not considered blank.
|
|
944
|
+
# Any lines indented *more* than the current line won't affect the bounds, but will be
|
|
945
|
+
# filtered out.
|
|
946
|
+
#
|
|
947
|
+
# p Tree.siblings # Doesn't include current line
|
|
948
|
+
# p Tree.siblings :string=>1 # As string, not list
|
|
949
|
+
# p Tree.siblings :all=>1 # Includes current line
|
|
950
|
+
# p Tree.siblings :everything=>1 # Includes current line and all children (returns string)
|
|
951
|
+
# p Tree.siblings :cross_blank_lines=>1 # Includes current line, and crosses single blank lines (returns string)
|
|
952
|
+
#
|
|
953
|
+
# sample chile
|
|
954
|
+
#
|
|
955
|
+
def self.siblings options={}
|
|
956
|
+
return self.siblings(:all=>true).join("\n").gsub(/^ *\| ?/, '')+"\n" if options[:string] && ! options[:cross_blank_lines] && ! options[:before] && ! options[:after]
|
|
957
|
+
|
|
958
|
+
if options[:cross_blank_lines]
|
|
959
|
+
left1, right2 = self.sibling_bounds :cross_blank_lines=>1
|
|
960
|
+
# For now, if :cross_blank_lines, assume just left1, right2
|
|
961
|
+
options[:all] = 1
|
|
962
|
+
else
|
|
963
|
+
left1, right1, left2, right2 = self.sibling_bounds# options
|
|
964
|
+
end
|
|
965
|
+
|
|
966
|
+
# Combine and process siblings
|
|
967
|
+
if options[:all] || options[:everything]
|
|
968
|
+
siblings = View.txt(options.merge(:left=>left1, :right=>right2))
|
|
969
|
+
|
|
970
|
+
elsif options[:quotes] # Only grab contiguous quoted lines
|
|
971
|
+
above = View.txt(options.merge(:left=>left1, :right=>right1))
|
|
972
|
+
found = true
|
|
973
|
+
above = above.split("\n").reverse.select{|o| found && o =~ /^ *\|/ or found = false}.reverse.join("\n")
|
|
974
|
+
above << "\n" if above.any?
|
|
975
|
+
|
|
976
|
+
middle = View.txt(options.merge(:left=>right1, :right=>left2))
|
|
977
|
+
|
|
978
|
+
below = View.txt(options.merge(:left=>left2, :right=>right2))
|
|
979
|
+
found = true
|
|
980
|
+
below = below.split("\n").select{|o| found && o =~ /^ *\|/ or found = false}.join("\n")
|
|
981
|
+
below << "\n" if below.any?
|
|
982
|
+
|
|
983
|
+
siblings = "#{above}#{middle}#{below}" #.strip
|
|
984
|
+
|
|
985
|
+
elsif options[:before]
|
|
986
|
+
siblings = View.txt(options.merge(:left=>left1, :right=>right1))
|
|
987
|
+
elsif options[:after]
|
|
988
|
+
siblings = View.txt(options.merge(:left=>left2, :right=>right2))
|
|
989
|
+
else
|
|
990
|
+
# By default, don't include sibling on current line
|
|
991
|
+
# TODO: swap this, so it includes all by default?
|
|
992
|
+
# Go through and make new :exclude_current param, and make invocations use it
|
|
993
|
+
siblings = View.txt(options.merge(:left=>left1, :right=>right1)) + View.txt(options.merge(:left=>left2, :right=>right2))
|
|
994
|
+
end
|
|
995
|
+
|
|
996
|
+
if options[:everything]
|
|
997
|
+
indent = siblings[/\A */]
|
|
998
|
+
return siblings.gsub(/^#{indent}/, '')
|
|
999
|
+
end
|
|
1000
|
+
|
|
1001
|
+
siblings.gsub! /^#{Line.indent} .*\n/, '' # Remove more indented lines (children)
|
|
1002
|
+
siblings.gsub! /^ +\n/, '' # Remove blank lines
|
|
1003
|
+
siblings.gsub! /^ +/, '' # Remove indents
|
|
1004
|
+
|
|
1005
|
+
if options[:string] # Must have :before or :after option also if it got here
|
|
1006
|
+
return siblings.gsub /^\| ?/, ''
|
|
1007
|
+
end
|
|
1008
|
+
|
|
1009
|
+
siblings = siblings.split("\n")
|
|
1010
|
+
|
|
1011
|
+
unless options[:include_label] # Optionally remove labels
|
|
1012
|
+
siblings.map!{|i| Line.without_label(:line=>i)}
|
|
1013
|
+
end
|
|
1014
|
+
|
|
1015
|
+
# Change blanks to nil
|
|
1016
|
+
siblings.map!{|o| o.blank? ? nil : o}
|
|
1017
|
+
|
|
1018
|
+
siblings
|
|
1019
|
+
end
|
|
1020
|
+
|
|
1021
|
+
def self.sibling_bounds options={}
|
|
1022
|
+
if options[:cross_blank_lines] # If :cross_blank_lines, just jump to parent, then use .after_children
|
|
1023
|
+
orig = Location.new
|
|
1024
|
+
Tree.before_siblings
|
|
1025
|
+
left = Line.left
|
|
1026
|
+
Tree.after_siblings
|
|
1027
|
+
right = View.cursor
|
|
1028
|
+
|
|
1029
|
+
orig.go
|
|
1030
|
+
return [left, right]
|
|
1031
|
+
end
|
|
1032
|
+
|
|
1033
|
+
indent_size = Line.indent.size # Get indent
|
|
1034
|
+
indent_less = indent_size - 1
|
|
1035
|
+
|
|
1036
|
+
orig = Location.new
|
|
1037
|
+
|
|
1038
|
+
right1 = Line.left # Right side of lines before
|
|
1039
|
+
|
|
1040
|
+
# Search for line indented less - parent (to get siblings after)
|
|
1041
|
+
found = indent_less < 0 ?
|
|
1042
|
+
Search.backward("^$", :go_anyway=>1) :
|
|
1043
|
+
Search.backward("^ \\{0,#{indent_less}\\}\\($\\|[^ \n]\\)")
|
|
1044
|
+
|
|
1045
|
+
Line.next if found
|
|
1046
|
+
left1 = Line.left # Left side of lines before
|
|
1047
|
+
|
|
1048
|
+
orig.go
|
|
1049
|
+
|
|
1050
|
+
# Search for line indented same or less (to get siblings after)
|
|
1051
|
+
Line.next
|
|
1052
|
+
Search.forward "^ \\{0,#{indent_size}\\}\\($\\|[^ \n]\\)" # Move after original node's children, if any
|
|
1053
|
+
Line.to_left
|
|
1054
|
+
left2 = View.cursor
|
|
1055
|
+
# Search for line indented less
|
|
1056
|
+
indent_less < 0 ?
|
|
1057
|
+
Search.forward("^$") :
|
|
1058
|
+
Search.forward("^ \\{0,#{indent_less}\\}\\($\\|[^ \n]\\)")
|
|
1059
|
+
right2 = Line.left # Left side of lines before
|
|
1060
|
+
orig.go
|
|
1061
|
+
|
|
1062
|
+
[left1, right1, left2, right2]
|
|
1063
|
+
end
|
|
1064
|
+
|
|
1065
|
+
def self.search_appropriately left, right, output, options={}
|
|
1066
|
+
|
|
1067
|
+
View.cursor = left unless options[:line_found]
|
|
1068
|
+
Line.to_words
|
|
1069
|
+
|
|
1070
|
+
# Determine how to search based on output!
|
|
1071
|
+
|
|
1072
|
+
options.merge! :left=>left, :right=>right
|
|
1073
|
+
|
|
1074
|
+
root_indent = output[/\A */]
|
|
1075
|
+
if output =~ /^#{root_indent} / # If any indenting
|
|
1076
|
+
if output =~ /^ +\|/
|
|
1077
|
+
Search.forward "^ +\\(|\\|- ##\\)", :beginning=>true
|
|
1078
|
+
Line.to_beginning
|
|
1079
|
+
options[:recursive_quotes] = true
|
|
1080
|
+
else
|
|
1081
|
+
FileTree.select_next_file
|
|
1082
|
+
options[:recursive] = true
|
|
1083
|
+
end
|
|
1084
|
+
Tree.search options
|
|
1085
|
+
else
|
|
1086
|
+
Tree.search options.merge(:number_means_enter=>true)
|
|
1087
|
+
end
|
|
1088
|
+
|
|
1089
|
+
end
|
|
1090
|
+
|
|
1091
|
+
# Iterate through each line in the tree
|
|
1092
|
+
# Tree.traverse("a\n b") {|o| p o}
|
|
1093
|
+
def self.traverse tree, options={}, &block
|
|
1094
|
+
branch, line_indent, indent = [], 0, 0
|
|
1095
|
+
|
|
1096
|
+
tree = tree.split("\n")
|
|
1097
|
+
tree.each_with_index do |line, i|
|
|
1098
|
+
|
|
1099
|
+
# If empty line use last non-blank line's indent
|
|
1100
|
+
if line.empty?
|
|
1101
|
+
line = nil
|
|
1102
|
+
last_indent = line_indent
|
|
1103
|
+
line_indent = tree[i+1] # Use indent of following line
|
|
1104
|
+
line_indent = line_indent ? (line_indent[/^ */].length / 2) : 0
|
|
1105
|
+
raise "Blank lines in trees between parents and children aren't allowed." if line_indent > 0 && line_indent > last_indent
|
|
1106
|
+
else
|
|
1107
|
+
line_indent = line[/^ */].length / 2
|
|
1108
|
+
line.strip!
|
|
1109
|
+
end
|
|
1110
|
+
|
|
1111
|
+
branch[line_indent] = line
|
|
1112
|
+
|
|
1113
|
+
if line_indent < indent
|
|
1114
|
+
branch = branch[0..line_indent]
|
|
1115
|
+
end
|
|
1116
|
+
|
|
1117
|
+
branch_dup = branch.dup
|
|
1118
|
+
|
|
1119
|
+
if options[:no_bullets]
|
|
1120
|
+
branch_dup.map!{|o| o ? o.sub(/^[<+-][<=]* /, '') : nil }
|
|
1121
|
+
end
|
|
1122
|
+
|
|
1123
|
+
flattened = branch_dup.dup
|
|
1124
|
+
|
|
1125
|
+
flattened.map!{|o| o ? o.sub(/^[+-] /, '') : nil } if ! options[:no_bullets] # Might have side-effects if done twice
|
|
1126
|
+
flattened = flattened.join('')#.gsub(/[.:]/, '') # Why were :'s removed??
|
|
1127
|
+
|
|
1128
|
+
block.call [branch_dup, flattened]
|
|
1129
|
+
|
|
1130
|
+
indent = line_indent
|
|
1131
|
+
end
|
|
1132
|
+
end
|
|
1133
|
+
|
|
1134
|
+
# Insert section from a file under it in tree
|
|
1135
|
+
def self.enter_under
|
|
1136
|
+
Line.beginning
|
|
1137
|
+
path = Tree.construct_path # Get path
|
|
1138
|
+
path.sub!(/\|.+/, '') # Remove file
|
|
1139
|
+
path = Bookmarks.expand(path)
|
|
1140
|
+
|
|
1141
|
+
# Cut off indent and pipe (including following space)
|
|
1142
|
+
Line.value =~ /(^ +)\| (.+)/
|
|
1143
|
+
quote_indent, line = $1, $2
|
|
1144
|
+
|
|
1145
|
+
if line =~ /^> / # If heading in a notes file
|
|
1146
|
+
# Go through lines in file until end of section
|
|
1147
|
+
matches = ""
|
|
1148
|
+
found_yet = false
|
|
1149
|
+
IO.foreach(path) do |l|
|
|
1150
|
+
l.sub!(/[\r\n]+$/, '')
|
|
1151
|
+
l.gsub!("\c@", '.') # Replace out characters that el4r can't handle
|
|
1152
|
+
# Swallow up until match
|
|
1153
|
+
if !found_yet
|
|
1154
|
+
found_yet = l == line
|
|
1155
|
+
next
|
|
1156
|
+
end
|
|
1157
|
+
# Grab rest until another pipe
|
|
1158
|
+
break if l =~ /^\> /
|
|
1159
|
+
|
|
1160
|
+
l = " #{l}" unless l.empty?
|
|
1161
|
+
matches << "#{quote_indent} |#{l}\n"
|
|
1162
|
+
end
|
|
1163
|
+
|
|
1164
|
+
# Insert and start search
|
|
1165
|
+
Tree.insert_quoted_and_search matches
|
|
1166
|
+
|
|
1167
|
+
else # Otherwise, grab by indent
|
|
1168
|
+
|
|
1169
|
+
# Go through lines in file until we've found it
|
|
1170
|
+
indent = line[/^\s*/].gsub("\t", ' ').length
|
|
1171
|
+
matches = ""
|
|
1172
|
+
found_yet = false
|
|
1173
|
+
IO.foreach(path) do |l|
|
|
1174
|
+
l.sub!(/[\r\n]+$/, '')
|
|
1175
|
+
l.gsub!("\c@", '.') # Replace out characters that el4r can't handle
|
|
1176
|
+
# Swallow up until match
|
|
1177
|
+
if !found_yet
|
|
1178
|
+
found_yet = l == line
|
|
1179
|
+
next
|
|
1180
|
+
end
|
|
1181
|
+
# Grab rest until not indented less
|
|
1182
|
+
|
|
1183
|
+
current_indent = l[/^\s*/].gsub("\t", ' ').length
|
|
1184
|
+
|
|
1185
|
+
break matches.<<("#{quote_indent} |\n") if line.blank?
|
|
1186
|
+
|
|
1187
|
+
break if current_indent <= indent
|
|
1188
|
+
|
|
1189
|
+
l = " #{l}" unless l.blank?
|
|
1190
|
+
matches << "#{quote_indent} |#{l}\n"
|
|
1191
|
+
end
|
|
1192
|
+
|
|
1193
|
+
# Insert and start search
|
|
1194
|
+
Tree.insert_quoted_and_search matches
|
|
1195
|
+
end
|
|
1196
|
+
end
|
|
1197
|
+
|
|
1198
|
+
|
|
1199
|
+
|
|
1200
|
+
def self.dotify! tree, target
|
|
1201
|
+
target_flat = target.join "/"
|
|
1202
|
+
|
|
1203
|
+
self.traverse(tree, :no_bullets=>1) do |branch, path|
|
|
1204
|
+
|
|
1205
|
+
match = self.target_match path, target_flat
|
|
1206
|
+
|
|
1207
|
+
if match == :same || match == :longer
|
|
1208
|
+
indent = branch.length - 1
|
|
1209
|
+
|
|
1210
|
+
# If last item in path has period and target doesn't
|
|
1211
|
+
if branch[indent] =~ /^\./ && target[indent] !~ /^\./
|
|
1212
|
+
# Add period to nth item in target
|
|
1213
|
+
target[indent].sub! /^/, '.'
|
|
1214
|
+
end
|
|
1215
|
+
end
|
|
1216
|
+
end
|
|
1217
|
+
|
|
1218
|
+
# Optimization
|
|
1219
|
+
# If last path wasn't match and indent is lower than last path, we won't match
|
|
1220
|
+
|
|
1221
|
+
end
|
|
1222
|
+
|
|
1223
|
+
|
|
1224
|
+
#
|
|
1225
|
+
# Called by Tree.children and Tree.dotify
|
|
1226
|
+
#
|
|
1227
|
+
# p Tree.target_match "a/b", "a/b"
|
|
1228
|
+
# p Tree.target_match "a/b", "a"
|
|
1229
|
+
# p Tree.target_match "a", "a/b"
|
|
1230
|
+
#
|
|
1231
|
+
def self.target_match path, target
|
|
1232
|
+
pi, ti = 0, 0
|
|
1233
|
+
|
|
1234
|
+
while true
|
|
1235
|
+
pathi, targeti = path[pi], target[ti]
|
|
1236
|
+
|
|
1237
|
+
if pathi.nil? || targeti.nil?
|
|
1238
|
+
return :same if pathi.nil? && targeti.nil? # Handles a, a and a/, a/
|
|
1239
|
+
return :same if pathi.nil? && target[ti+1].nil? && targeti.chr == "/" # Handles a, a/
|
|
1240
|
+
return :same if targeti.nil? && path[pi+1].nil? && pathi.chr == "/" # Handles a/, a
|
|
1241
|
+
|
|
1242
|
+
return :shorter if pathi && (pathi.chr == "/" || path[pi-1].chr == "/" || pi == 0)
|
|
1243
|
+
return :longer if targeti && (targeti.chr == "/" || target[ti-1].chr == "/" || ti == 0)
|
|
1244
|
+
return nil # At end of one, but no match
|
|
1245
|
+
end
|
|
1246
|
+
|
|
1247
|
+
# If chars equal, increment
|
|
1248
|
+
if pathi == targeti
|
|
1249
|
+
pi += 1
|
|
1250
|
+
next ti += 1
|
|
1251
|
+
|
|
1252
|
+
# If path has /. or . at beginning, increment path
|
|
1253
|
+
elsif pathi.chr == "." && (path[pi-1].chr == "/" || pi == 0) && (target[ti-1].chr == "/" || ti == 0)
|
|
1254
|
+
next pi += 1
|
|
1255
|
+
|
|
1256
|
+
# If path has /*/ or /* at end, increment path and increment target to next / or end
|
|
1257
|
+
elsif pathi.chr == "*" && path[pi-1].chr == "/" && (path[pi+1].nil? || path[pi+1].chr == "/")
|
|
1258
|
+
pi += 1
|
|
1259
|
+
ti = target.index("/", ti+1) || target.length
|
|
1260
|
+
next
|
|
1261
|
+
end
|
|
1262
|
+
|
|
1263
|
+
break # Not found
|
|
1264
|
+
end
|
|
1265
|
+
|
|
1266
|
+
nil
|
|
1267
|
+
end
|
|
1268
|
+
|
|
1269
|
+
|
|
1270
|
+
#
|
|
1271
|
+
# Cuts off 1st item in the path.
|
|
1272
|
+
#
|
|
1273
|
+
# Use instead of .leaf when you know all but the root is part of the leaf
|
|
1274
|
+
# (in case there are slashes).
|
|
1275
|
+
#
|
|
1276
|
+
# Tree.rest "hey/you/there"
|
|
1277
|
+
#
|
|
1278
|
+
def self.rest path
|
|
1279
|
+
|
|
1280
|
+
path = self.rootless path
|
|
1281
|
+
path = "|#{path}" unless path =~ /^(\||$)/
|
|
1282
|
+
|
|
1283
|
+
self.leaf(path)
|
|
1284
|
+
end
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
# Copy children from treeb to treea, but only for branches in treea where children were removed.
|
|
1288
|
+
def self.restore treea, treeb
|
|
1289
|
+
|
|
1290
|
+
treea, treeb = TreeCursor.new(treea), TreeCursor.new(treeb)
|
|
1291
|
+
|
|
1292
|
+
# For each leaf in A
|
|
1293
|
+
treea.each do
|
|
1294
|
+
next unless treea.at_leaf? # We only care about leafs
|
|
1295
|
+
|
|
1296
|
+
treeb.select treea.line # Find branch in B
|
|
1297
|
+
next if treeb.at_leaf? # Skip if no children children
|
|
1298
|
+
|
|
1299
|
+
treea << treeb.under # Grab them and move into A
|
|
1300
|
+
end
|
|
1301
|
+
|
|
1302
|
+
treea.txt
|
|
1303
|
+
end
|
|
1304
|
+
|
|
1305
|
+
def self.collapse options={}
|
|
1306
|
+
# If at root or end of line, go to next
|
|
1307
|
+
Line.next if Line !~ /^ / || Line.at_right?
|
|
1308
|
+
CodeTree.kill_siblings
|
|
1309
|
+
|
|
1310
|
+
Move.to_end -1
|
|
1311
|
+
|
|
1312
|
+
Line.sub! /([ +-]*).+/, "\\1" if options[:replace_parent]
|
|
1313
|
+
|
|
1314
|
+
left = View.cursor
|
|
1315
|
+
$el.skip_chars_forward(" \n+-")
|
|
1316
|
+
View.delete left, View.cursor
|
|
1317
|
+
|
|
1318
|
+
Move.to_end
|
|
1319
|
+
left, right = View.paragraph :bounds=>true, :start_here=>true
|
|
1320
|
+
|
|
1321
|
+
$el.indent_rigidly View.cursor, right, -2
|
|
1322
|
+
end
|
|
1323
|
+
|
|
1324
|
+
# Gets path from root to here, indenting each line by 2 spaces deeper.
|
|
1325
|
+
# Tree.ancestors_indented
|
|
1326
|
+
def self.ancestors_indented options={}
|
|
1327
|
+
|
|
1328
|
+
all = options[:just_sub_tree] ? nil : 1
|
|
1329
|
+
path = Tree.construct_path(:list=>1, :ignore_ol=>1, :all=>all)
|
|
1330
|
+
result = ""
|
|
1331
|
+
path.each_with_index { |o, i|
|
|
1332
|
+
result << "#{' ' * i}#{o}\n"
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
result
|
|
1336
|
+
end
|
|
1337
|
+
|
|
1338
|
+
# Returns self and all siblings (without children).
|
|
1339
|
+
def self.unset_env_vars
|
|
1340
|
+
ENV['no_slash'] = nil
|
|
1341
|
+
end
|
|
1342
|
+
|
|
1343
|
+
def self.output_and_search block_or_string, options={}
|
|
1344
|
+
|
|
1345
|
+
line = options[:line]
|
|
1346
|
+
|
|
1347
|
+
if $el
|
|
1348
|
+
buffer_orig = View.buffer
|
|
1349
|
+
orig = Location.new
|
|
1350
|
+
orig_left = View.cursor
|
|
1351
|
+
end
|
|
1352
|
+
|
|
1353
|
+
error_happened = nil
|
|
1354
|
+
|
|
1355
|
+
self.unset_env_vars
|
|
1356
|
+
|
|
1357
|
+
output =
|
|
1358
|
+
if block_or_string.is_a? String
|
|
1359
|
+
block_or_string
|
|
1360
|
+
else # Must be a proc
|
|
1361
|
+
begin
|
|
1362
|
+
block_or_string.call line
|
|
1363
|
+
rescue Exception=>e
|
|
1364
|
+
message = e.message
|
|
1365
|
+
|
|
1366
|
+
error_happened = true
|
|
1367
|
+
CodeTree.draw_exception e, Code.to_ruby(block_or_string)
|
|
1368
|
+
end
|
|
1369
|
+
end
|
|
1370
|
+
|
|
1371
|
+
return if output.blank?
|
|
1372
|
+
|
|
1373
|
+
if output.is_a?(String) && $el && output.strip =~ /\A<<< (.+)\/\z/
|
|
1374
|
+
Tree.replace_item $1
|
|
1375
|
+
Launcher.launch
|
|
1376
|
+
return true
|
|
1377
|
+
end
|
|
1378
|
+
|
|
1379
|
+
# TODO: move some of this crap into the else block above (block_or_string is proc)
|
|
1380
|
+
|
|
1381
|
+
|
|
1382
|
+
if $el
|
|
1383
|
+
buffer_changed = buffer_orig != View.buffer # Remember whether we left the buffer
|
|
1384
|
+
|
|
1385
|
+
ended_up = Location.new
|
|
1386
|
+
orig.go # Go back to where we were before running code
|
|
1387
|
+
end
|
|
1388
|
+
|
|
1389
|
+
# Move what they printed over to left margin initally, in case they haven't
|
|
1390
|
+
output = TextUtil.unindent(output) if output =~ /\A[ \n]/
|
|
1391
|
+
# Remove any double linebreaks at end
|
|
1392
|
+
output = CodeTree.returned_to_s output
|
|
1393
|
+
|
|
1394
|
+
if $el
|
|
1395
|
+
return View.prompt $1 if output =~ /\A\.prompt (.+)/
|
|
1396
|
+
return View.flash $1 if output =~ /\A\.flash (.+)/
|
|
1397
|
+
end
|
|
1398
|
+
|
|
1399
|
+
output.sub!(/\n\n\z/, "\n")
|
|
1400
|
+
output = "#{output}\n" if output !~ /\n\z/
|
|
1401
|
+
|
|
1402
|
+
return output if options[:just_return]
|
|
1403
|
+
|
|
1404
|
+
|
|
1405
|
+
# Add slash to end of line if not suppressed, and line isn't a quote
|
|
1406
|
+
line=options[:line]
|
|
1407
|
+
if !options[:no_slash] && ! ENV['no_slash'] && Line !~ /(^ *\||\/$)/
|
|
1408
|
+
Line << "/"
|
|
1409
|
+
end
|
|
1410
|
+
indent = Line.indent
|
|
1411
|
+
Line.to_left
|
|
1412
|
+
Line.next
|
|
1413
|
+
left = View.cursor
|
|
1414
|
+
|
|
1415
|
+
output.gsub! /^./, "#{indent} \\0" # Add indent, except for blank lines
|
|
1416
|
+
|
|
1417
|
+
View.<< output, :utf8=>1
|
|
1418
|
+
right = View.cursor
|
|
1419
|
+
|
|
1420
|
+
orig.go # Move cursor back <-- why doing this?
|
|
1421
|
+
ended_up.go # End up where script took us
|
|
1422
|
+
moved = View.cursor != orig_left
|
|
1423
|
+
|
|
1424
|
+
# Move to :line_found if any
|
|
1425
|
+
if options[:line_found] && options[:line_found] > 0
|
|
1426
|
+
Line.next(options[:line_found])
|
|
1427
|
+
Color.colorize :l
|
|
1428
|
+
end
|
|
1429
|
+
|
|
1430
|
+
if !error_happened && !$xiki_no_search &&!options[:no_search] && !buffer_changed && !moved
|
|
1431
|
+
Tree.search_appropriately left, right, output, options
|
|
1432
|
+
elsif ! options[:line_found]
|
|
1433
|
+
Line.to_beginning :down=>1
|
|
1434
|
+
end
|
|
1435
|
+
output
|
|
1436
|
+
end
|
|
1437
|
+
|
|
1438
|
+
def self.closest_dir
|
|
1439
|
+
dir = Xiki.trunk.reverse.find{|o| FileTree.matches_root_pattern? o}
|
|
1440
|
+
|
|
1441
|
+
dir = Bookmarks[dir]
|
|
1442
|
+
return nil if dir.nil?
|
|
1443
|
+
|
|
1444
|
+
File.expand_path dir
|
|
1445
|
+
end
|
|
1446
|
+
|
|
1447
|
+
# Tree.slashless("hey/you/").should == "hey/you"
|
|
1448
|
+
def self.slashless txt
|
|
1449
|
+
txt.sub /\/$/, ''
|
|
1450
|
+
end
|
|
1451
|
+
|
|
1452
|
+
|
|
1453
|
+
#
|
|
1454
|
+
# Extracts children from tree arg and target (path) arg.
|
|
1455
|
+
#
|
|
1456
|
+
# Or, if no tree passed in, delegates to Tree.children_at_cursor
|
|
1457
|
+
#
|
|
1458
|
+
# Tree.children "a\n b\n c", "a"
|
|
1459
|
+
#
|
|
1460
|
+
def self.children tree=nil, target=nil, options={}
|
|
1461
|
+
include_subitems = options[:include_subitems] # Include sub-items for all children
|
|
1462
|
+
|
|
1463
|
+
return self.children_at_cursor(tree) if tree.nil? || tree.is_a?(Hash) # tree is actually options
|
|
1464
|
+
|
|
1465
|
+
target = target.join("/") if target.is_a? Array
|
|
1466
|
+
target = "" if target == nil || target == "/" # Must be at root if nil
|
|
1467
|
+
tree = TextUtil.unindent tree
|
|
1468
|
+
|
|
1469
|
+
target.sub!(/^\//, '')
|
|
1470
|
+
target.sub!(/\/$/, '')
|
|
1471
|
+
|
|
1472
|
+
found = nil
|
|
1473
|
+
result = ""
|
|
1474
|
+
|
|
1475
|
+
found = -1 if target.empty?
|
|
1476
|
+
|
|
1477
|
+
@@under_preexpand = false # Will include sub-items of only some children
|
|
1478
|
+
|
|
1479
|
+
self.traverse tree do |branch, path|
|
|
1480
|
+
blank = branch[-1].nil?
|
|
1481
|
+
|
|
1482
|
+
if ! found
|
|
1483
|
+
target_match = Tree.target_match path, target
|
|
1484
|
+
next unless target_match == :shorter || target_match == :same
|
|
1485
|
+
found = branch.length - 1 # Found, remember indent
|
|
1486
|
+
|
|
1487
|
+
else
|
|
1488
|
+
current_indent = branch.length - 1
|
|
1489
|
+
# If found and still indented one deeper
|
|
1490
|
+
one_deeper = current_indent == found + 1
|
|
1491
|
+
|
|
1492
|
+
if one_deeper || ((include_subitems || @@under_preexpand) && current_indent > found)
|
|
1493
|
+
|
|
1494
|
+
next result << "\n" if blank
|
|
1495
|
+
|
|
1496
|
+
item = branch[-1]
|
|
1497
|
+
item.sub!(/^- /, '+ ') if item =~ /\/$/
|
|
1498
|
+
item.sub!(/^([<+-][<=]* )?\./, "\\1")
|
|
1499
|
+
next if item =~ /^[+-] \*\/$/ # Skip asterixes
|
|
1500
|
+
|
|
1501
|
+
# If @@under_preexpand, add on indent
|
|
1502
|
+
if include_subitems || @@under_preexpand
|
|
1503
|
+
item = "#{' ' * (branch.length - found - 2)}#{item}"
|
|
1504
|
+
end
|
|
1505
|
+
|
|
1506
|
+
@@under_preexpand = false if one_deeper
|
|
1507
|
+
|
|
1508
|
+
# Pre-expand if @... or doesn't end in slash
|
|
1509
|
+
@@under_preexpand = true if one_deeper && (item =~ /^([+-] )?@/ || item !~ /\/$/)
|
|
1510
|
+
|
|
1511
|
+
result << "#{item}\n" # Output
|
|
1512
|
+
|
|
1513
|
+
else # Otherwise, stop looking for children if indent is less
|
|
1514
|
+
|
|
1515
|
+
# # If blank line is at same level
|
|
1516
|
+
# if branch.empty? && found == current_indent
|
|
1517
|
+
# next result << "\n"
|
|
1518
|
+
# end
|
|
1519
|
+
|
|
1520
|
+
next if current_indent > found
|
|
1521
|
+
# No longer beneath found item
|
|
1522
|
+
found = nil
|
|
1523
|
+
@@under_preexpand = false
|
|
1524
|
+
end
|
|
1525
|
+
end
|
|
1526
|
+
|
|
1527
|
+
end
|
|
1528
|
+
|
|
1529
|
+
result.empty? ? nil : result
|
|
1530
|
+
end
|
|
1531
|
+
|
|
1532
|
+
#
|
|
1533
|
+
# Returns children indented underneath.
|
|
1534
|
+
#
|
|
1535
|
+
# Tree.children.inspect
|
|
1536
|
+
# Tree.children(:string=>1).inspect
|
|
1537
|
+
# Tree.children(:cross_blank_lines=>1).inspect
|
|
1538
|
+
# a
|
|
1539
|
+
# ab
|
|
1540
|
+
# b
|
|
1541
|
+
|
|
1542
|
+
# c
|
|
1543
|
+
#
|
|
1544
|
+
def self.children_at_cursor options={}
|
|
1545
|
+
|
|
1546
|
+
options ||= {}
|
|
1547
|
+
child = self.child
|
|
1548
|
+
return nil if child.nil? # Return if no child
|
|
1549
|
+
|
|
1550
|
+
|
|
1551
|
+
# If :cross_blank_lines, use Tree.after_children to find end
|
|
1552
|
+
if options[:cross_blank_lines]
|
|
1553
|
+
orig = Location.new
|
|
1554
|
+
|
|
1555
|
+
left = Line.left 2
|
|
1556
|
+
Tree.after_children
|
|
1557
|
+
right = Line.left
|
|
1558
|
+
orig.go
|
|
1559
|
+
# return
|
|
1560
|
+
return View.txt left, right
|
|
1561
|
+
end
|
|
1562
|
+
|
|
1563
|
+
indent = Line.indent(Line.value(2)).size
|
|
1564
|
+
|
|
1565
|
+
# :as_hash isn't used anywhere
|
|
1566
|
+
children = options[:as_hash] ? {} : []
|
|
1567
|
+
i = 2
|
|
1568
|
+
|
|
1569
|
+
# Add each child indented the same or more
|
|
1570
|
+
while(Line.indent(Line.value(i)).size >= indent)
|
|
1571
|
+
child = Line.value(i)
|
|
1572
|
+
if options[:as_hash]
|
|
1573
|
+
match = child.match(/ *([\w -]+): (.+)/)
|
|
1574
|
+
if match
|
|
1575
|
+
k, v = match[1..2]
|
|
1576
|
+
children[k] = v
|
|
1577
|
+
else
|
|
1578
|
+
i += 1
|
|
1579
|
+
next
|
|
1580
|
+
end
|
|
1581
|
+
|
|
1582
|
+
else
|
|
1583
|
+
children << child
|
|
1584
|
+
end
|
|
1585
|
+
|
|
1586
|
+
i += 1
|
|
1587
|
+
end
|
|
1588
|
+
|
|
1589
|
+
if options[:string]
|
|
1590
|
+
return children.join("\n")+"\n"
|
|
1591
|
+
end
|
|
1592
|
+
|
|
1593
|
+
children
|
|
1594
|
+
end
|
|
1595
|
+
|
|
1596
|
+
def self.children?
|
|
1597
|
+
# Whether next line is more indented
|
|
1598
|
+
Line.indent(Line.value(2)).size >
|
|
1599
|
+
Line.indent.size
|
|
1600
|
+
end
|
|
1601
|
+
|
|
1602
|
+
#
|
|
1603
|
+
# Returns the child indented underneath (but not its children).
|
|
1604
|
+
#
|
|
1605
|
+
# Tree.child
|
|
1606
|
+
# foo
|
|
1607
|
+
# bar
|
|
1608
|
+
#
|
|
1609
|
+
def self.child
|
|
1610
|
+
following_line = Line.value 2
|
|
1611
|
+
# If indent is one greater, it is a child
|
|
1612
|
+
if Line.indent.size + 2 == Line.indent(following_line).size
|
|
1613
|
+
return Line.without_label(:line=>following_line)
|
|
1614
|
+
end
|
|
1615
|
+
nil
|
|
1616
|
+
end
|
|
1617
|
+
|
|
1618
|
+
#
|
|
1619
|
+
# Returns subtree rooted at cursor.
|
|
1620
|
+
#
|
|
1621
|
+
def self.subtree
|
|
1622
|
+
|
|
1623
|
+
# Just return line if no children
|
|
1624
|
+
return Line.value if ! Tree.has_child?
|
|
1625
|
+
|
|
1626
|
+
orig = View.cursor
|
|
1627
|
+
left = Line.left
|
|
1628
|
+
Line.next
|
|
1629
|
+
ignore, right = Tree.sibling_bounds :cross_blank_lines=>1
|
|
1630
|
+
View.cursor = orig
|
|
1631
|
+
|
|
1632
|
+
txt = View.txt left, right
|
|
1633
|
+
txt
|
|
1634
|
+
end
|
|
1635
|
+
|
|
1636
|
+
#
|
|
1637
|
+
# Goes to spot (from as+spot) and grabs path?
|
|
1638
|
+
#
|
|
1639
|
+
def self.dir_at_spot options={}
|
|
1640
|
+
orig = Location.new # Save where we are
|
|
1641
|
+
Location.to_spot
|
|
1642
|
+
|
|
1643
|
+
path = Tree.construct_path
|
|
1644
|
+
# TODO: Make it use this instead:
|
|
1645
|
+
# path = Tree.construct_path :string=>1 instead
|
|
1646
|
+
|
|
1647
|
+
if options[:delete]
|
|
1648
|
+
Effects.glow :fade_out=>1
|
|
1649
|
+
Tree.kill_under
|
|
1650
|
+
Line.delete
|
|
1651
|
+
|
|
1652
|
+
# Adjust orig if in same file and above
|
|
1653
|
+
if orig.file_or_buffer == View.file_or_buffer && orig.line > View.line_number
|
|
1654
|
+
orig.line = orig.line - 1
|
|
1655
|
+
end
|
|
1656
|
+
end
|
|
1657
|
+
|
|
1658
|
+
orig.go # Go back to where we were
|
|
1659
|
+
|
|
1660
|
+
Bookmarks[path]
|
|
1661
|
+
end
|
|
1662
|
+
|
|
1663
|
+
# Returns the dir that a @menu is nested under, or says
|
|
1664
|
+
# must be nested under a dir.
|
|
1665
|
+
#
|
|
1666
|
+
# Tree.dir
|
|
1667
|
+
def self.dir options={}
|
|
1668
|
+
self.file options.merge(:require=>'dir')
|
|
1669
|
+
end
|
|
1670
|
+
|
|
1671
|
+
# Returns the dir or file that a @menu is nested under.
|
|
1672
|
+
#
|
|
1673
|
+
# Tree.file
|
|
1674
|
+
# Tree.file :require=>1 # Shows message if not nested under something
|
|
1675
|
+
# Tree.file :require=>'dir' # Shows message if not nested under a dir
|
|
1676
|
+
# Tree.file :require=>'file' # Shows message if not nested under a file
|
|
1677
|
+
def self.file options={}
|
|
1678
|
+
trunk = Xiki.trunk
|
|
1679
|
+
|
|
1680
|
+
# If tree we're in is a file tree, they probably just wanted that
|
|
1681
|
+
|
|
1682
|
+
return Bookmarks[trunk[-1]] if FileTree.handles? trunk[-1]
|
|
1683
|
+
# Just return it if we're under a dir
|
|
1684
|
+
|
|
1685
|
+
dir = trunk[-2]
|
|
1686
|
+
dir = "/" if dir == "" # Root dir will come across as blank
|
|
1687
|
+
|
|
1688
|
+
return Bookmarks[dir].sub('//', '/') if FileTree.handles?(dir)
|
|
1689
|
+
|
|
1690
|
+
# Dir wasn't found, raise message for certain options
|
|
1691
|
+
return File.expand_path("~/Desktop") if options[:or] == :desktop
|
|
1692
|
+
kind_required = options[:require]
|
|
1693
|
+
return nil if ! kind_required
|
|
1694
|
+
|
|
1695
|
+
guessed_menu = trunk.last.split('/').first
|
|
1696
|
+
example = options[:example] || (kind_required == "file" ? "/tmp/file.txt" : "/tmp/dir/")
|
|
1697
|
+
|
|
1698
|
+
if kind = options[:require]
|
|
1699
|
+
adjective = kind.is_a?(String) ? kind : "filesystem"
|
|
1700
|
+
raise "> This menu must be nested under a #{adjective} path, like:\n| - #{example}\n| @#{guessed_menu}"
|
|
1701
|
+
end
|
|
1702
|
+
nil
|
|
1703
|
+
end
|
|
1704
|
+
|
|
1705
|
+
def self.path options={}
|
|
1706
|
+
path = Tree.construct_path(:all=>1, :slashes=>1)
|
|
1707
|
+
options[:string] ? path : path.split(/\/@ ?/)
|
|
1708
|
+
end
|
|
1709
|
+
|
|
1710
|
+
def self.paths_to_tree paths
|
|
1711
|
+
result = ""
|
|
1712
|
+
stack = []
|
|
1713
|
+
paths.sort.each do |path| # For each path
|
|
1714
|
+
|
|
1715
|
+
beginning_slash = path =~ /^\//
|
|
1716
|
+
ending_slash = path =~ /\/$/
|
|
1717
|
+
split = path.sub(/^\//, '').split('/')
|
|
1718
|
+
|
|
1719
|
+
split[0].sub! /^/, '/' if beginning_slash # Restore beginning slash after split
|
|
1720
|
+
split[-1].sub! /$/, '/' if ending_slash # Restore beginning slash after split
|
|
1721
|
+
|
|
1722
|
+
# put all slashes back first!
|
|
1723
|
+
|
|
1724
|
+
# Pop from stack until path begins with stack
|
|
1725
|
+
while(stack.size > 0 && stack != split[0..(stack.size - 1)])
|
|
1726
|
+
stack.pop
|
|
1727
|
+
end
|
|
1728
|
+
indent = stack.length # Get remainder of path after stack
|
|
1729
|
+
remainder = split[indent..-1]
|
|
1730
|
+
remainder.each do |dir|
|
|
1731
|
+
result << (" " * indent) + dir
|
|
1732
|
+
result << "\n"
|
|
1733
|
+
indent += 1
|
|
1734
|
+
end
|
|
1735
|
+
stack = split
|
|
1736
|
+
end
|
|
1737
|
+
self.add_pluses_and_minuses result
|
|
1738
|
+
result
|
|
1739
|
+
end
|
|
1740
|
+
|
|
1741
|
+
# Prepend bullets (pluses and minuses) to lines
|
|
1742
|
+
def self.add_pluses_and_minuses tree, dirs='-', files='+'
|
|
1743
|
+
tree.gsub! /^( *)([^ \n|+-].*\/)$/, "\\1#{dirs} \\2"
|
|
1744
|
+
tree.gsub! /^( *)([^ \n|+-].*[^\/\n])$/, "\\1#{files} \\2"
|
|
1745
|
+
end
|
|
1746
|
+
|
|
1747
|
+
# Tree.to_html "p/\n hi\n"
|
|
1748
|
+
def self.to_html txt
|
|
1749
|
+
html = ""
|
|
1750
|
+
|
|
1751
|
+
txt = txt.gsub /^( *)([+-] )?(\w[\w ]*\/)(.+)/, "\\1\\3\n\\1 \\4" # Preprocess to break foo/Bar into separate lines
|
|
1752
|
+
|
|
1753
|
+
previous = []
|
|
1754
|
+
Tree.traverse(txt) do |l, path|
|
|
1755
|
+
|
|
1756
|
+
last = l.last
|
|
1757
|
+
next if !last # Blank lines
|
|
1758
|
+
|
|
1759
|
+
self.add_closing_tags html, l, previous # If lower than last, add any closing tags
|
|
1760
|
+
|
|
1761
|
+
last = Line.without_label :line=>last
|
|
1762
|
+
if last =~ /([^*\n]+)\/$/
|
|
1763
|
+
tag = $1
|
|
1764
|
+
html.<<(" " * (l.length-1)) unless l[-2] =~ /[ +-]*pre\/$/
|
|
1765
|
+
|
|
1766
|
+
next html << "<#{tag}>\n"
|
|
1767
|
+
end
|
|
1768
|
+
|
|
1769
|
+
last.sub! /^\| ?/, ''
|
|
1770
|
+
|
|
1771
|
+
if last =~ /\.\.\.$/ # If "Lorem..." or "Lorem ipsum..." etc. make progressively longer
|
|
1772
|
+
old_length = last.length
|
|
1773
|
+
last.gsub!(/\w+/){|o| @@lorem[o.downcase] || o}
|
|
1774
|
+
last.sub!(/\.\.\.$/, '') if last.length != old_length # Remove ... if we replaced something
|
|
1775
|
+
end
|
|
1776
|
+
|
|
1777
|
+
parent = l[-2]
|
|
1778
|
+
html.<<(" " * (l.length-1)) unless parent =~ /[ +-]*pre\/$/
|
|
1779
|
+
|
|
1780
|
+
html << "#{last}\n"
|
|
1781
|
+
end
|
|
1782
|
+
|
|
1783
|
+
|
|
1784
|
+
self.add_closing_tags html, [], previous
|
|
1785
|
+
|
|
1786
|
+
html
|
|
1787
|
+
end
|
|
1788
|
+
|
|
1789
|
+
@@lorem = {
|
|
1790
|
+
"lorem"=>"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
|
1791
|
+
"ipsum"=>"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
|
|
1792
|
+
"dolor"=>"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
|
|
1793
|
+
"sit"=>"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
def self.add_closing_tags html, l, previous
|
|
1797
|
+
|
|
1798
|
+
if l.length <= previous.length
|
|
1799
|
+
left = l.length-1
|
|
1800
|
+
left = 0 if left < 0
|
|
1801
|
+
close_these = previous[left..-1]
|
|
1802
|
+
close_these.reverse.each_with_index do |tag, i|
|
|
1803
|
+
|
|
1804
|
+
tag.sub! /^\| ?/, ''
|
|
1805
|
+
tag = Line.without_label :line=>tag
|
|
1806
|
+
next if tag !~ /(.*\w)\/$/ && tag !~ /^<([^<\n]*[\w"'])>$/
|
|
1807
|
+
tag = $1
|
|
1808
|
+
tag = tag.sub(/ \w+=.+/, '')
|
|
1809
|
+
next if ["img"].member? tag
|
|
1810
|
+
html << " " * (previous.length - i - 1)
|
|
1811
|
+
html << "</#{tag}>\n"
|
|
1812
|
+
end
|
|
1813
|
+
end
|
|
1814
|
+
previous.replace l
|
|
1815
|
+
end
|
|
1816
|
+
|
|
1817
|
+
|
|
1818
|
+
# Replace last path item with this string.
|
|
1819
|
+
#
|
|
1820
|
+
# Tree.replace_item "replaces whole line"
|
|
1821
|
+
# Tree.replace_item "replaces after slash" # /hey
|
|
1822
|
+
# Tree.replace_item "replaces after slash" # /hey/
|
|
1823
|
+
def self.replace_item txt
|
|
1824
|
+
|
|
1825
|
+
new_ends_in_slash = txt =~ /\/$/ # If slash at end, remember to not add another one
|
|
1826
|
+
|
|
1827
|
+
# If there's a slash (not counting end of line), replace after last slash
|
|
1828
|
+
|
|
1829
|
+
if Line.value =~ /\/.+/
|
|
1830
|
+
(! new_ends_in_slash && Line.sub!(/(.+)\/.+\/$/, "\\1/#{txt}/")) ||
|
|
1831
|
+
Line.sub!(/(.+)\/.+/, "\\1/#{txt}")
|
|
1832
|
+
else # else, just replace whole line minus bullet
|
|
1833
|
+
(! new_ends_in_slash && Line.sub!(/^([ +-]*).+\//, "\\1#{txt}\/")) ||
|
|
1834
|
+
Line.sub!(/^([ +-]*).+/, "\\1#{txt}")
|
|
1835
|
+
end
|
|
1836
|
+
|
|
1837
|
+
nil
|
|
1838
|
+
end
|
|
1839
|
+
|
|
1840
|
+
|
|
1841
|
+
#
|
|
1842
|
+
# Grab last path item.
|
|
1843
|
+
#
|
|
1844
|
+
# Tree.last_item
|
|
1845
|
+
# Tree.last_item # /hey
|
|
1846
|
+
# Tree.last_item # /hey/
|
|
1847
|
+
#
|
|
1848
|
+
def self.last_item
|
|
1849
|
+
|
|
1850
|
+
# If there's a slash (not counting end of line), use after last slash
|
|
1851
|
+
|
|
1852
|
+
if Line.value =~ /\/.+/
|
|
1853
|
+
Line[/.+\/(.+)\/$/, 1] || Line[/.+\/(.+)/, 1]
|
|
1854
|
+
else # else, just replace whole line minus bullet
|
|
1855
|
+
Line[/^[ +-]*(.+)\//, 1] || Line[/^[ +-]*(.+)/, 1]
|
|
1856
|
+
end
|
|
1857
|
+
|
|
1858
|
+
end
|
|
1859
|
+
|
|
1860
|
+
#
|
|
1861
|
+
# Returns whether the next line is indented one lower
|
|
1862
|
+
#
|
|
1863
|
+
# p Tree.has_child?
|
|
1864
|
+
#
|
|
1865
|
+
def self.has_child?
|
|
1866
|
+
|
|
1867
|
+
Line.indent(Line.value)+" " == Line.indent(Line.value 2)
|
|
1868
|
+
|
|
1869
|
+
end
|
|
1870
|
+
|
|
1871
|
+
end
|