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.
Files changed (252) hide show
  1. data/Gemfile +11 -0
  2. data/LICENSE +22 -0
  3. data/README.markdown +83 -0
  4. data/Rakefile +8 -0
  5. data/bin/xiki +46 -0
  6. data/etc/command/xiki_command.rb +203 -0
  7. data/etc/command/xiki_process.rb +52 -0
  8. data/etc/command/xiki_wrapper +2 -0
  9. data/etc/js/menu1.js +55 -0
  10. data/etc/js/xiki.js +259 -0
  11. data/etc/logo.png +0 -0
  12. data/etc/presentations/bootstrap.deck +5 -0
  13. data/etc/presentations/databases.deck +41 -0
  14. data/etc/presentations/diffs.deck +23 -0
  15. data/etc/presentations/documentation.deck +14 -0
  16. data/etc/presentations/effects.deck +5 -0
  17. data/etc/presentations/face.deck +297 -0
  18. data/etc/presentations/files.deck +79 -0
  19. data/etc/presentations/icons.deck +22 -0
  20. data/etc/presentations/images.deck +24 -0
  21. data/etc/presentations/key_shortcuts.deck +16 -0
  22. data/etc/presentations/macros.deck +18 -0
  23. data/etc/presentations/menu_classes.deck +44 -0
  24. data/etc/presentations/menu_directories.deck +30 -0
  25. data/etc/presentations/notes.deck +19 -0
  26. data/etc/presentations/other_languages.deck +55 -0
  27. data/etc/presentations/other_wiki_syntaxes.deck +4 -0
  28. data/etc/presentations/piano.deck +5 -0
  29. data/etc/presentations/potential/diffs.deck +38 -0
  30. data/etc/presentations/potential/evolution.deck +18 -0
  31. data/etc/presentations/potential/intro.deck +711 -0
  32. data/etc/presentations/potential/intro1.deck +711 -0
  33. data/etc/presentations/potential/intro2.deck +97 -0
  34. data/etc/presentations/potential/make_mysql_menu.deck +36 -0
  35. data/etc/presentations/potential/ruby_development.deck +17 -0
  36. data/etc/presentations/potential/ui_prototyping.deck +50 -0
  37. data/etc/presentations/potential/web_dev.deck +4 -0
  38. data/etc/presentations/potential/web_development.deck +10 -0
  39. data/etc/presentations/potential/wiki.deck +45 -0
  40. data/etc/presentations/presentations.deck +24 -0
  41. data/etc/presentations/rails_development.deck +29 -0
  42. data/etc/presentations/search_key_shortcuts.deck +37 -0
  43. data/etc/presentations/simplest_possible_ui.deck +37 -0
  44. data/etc/presentations/svg.deck +5 -0
  45. data/etc/presentations/testing.deck +28 -0
  46. data/etc/presentations/the_end.deck +13 -0
  47. data/etc/presentations/type_something_and_double_click.deck +57 -0
  48. data/etc/presentations/type_the_acronym.deck +144 -0
  49. data/etc/presentations/web_browser.deck +56 -0
  50. data/etc/presentations/xiki_command.deck +20 -0
  51. data/etc/presentations/xiki_url.deck +14 -0
  52. data/etc/shark.icns +0 -0
  53. data/etc/shark_script.icns +0 -0
  54. data/etc/snippets/html.notes +7 -0
  55. data/etc/snippets/notes.notes +20 -0
  56. data/etc/snippets/rb.notes +38 -0
  57. data/etc/templates/menu_template.menu +2 -0
  58. data/etc/templates/menu_template.rb +8 -0
  59. data/etc/templates/template.rb +5 -0
  60. data/etc/themes/Dark_Metal.notes +28 -0
  61. data/etc/themes/Orange_Path.notes +15 -0
  62. data/etc/themes/Shiny_Blue.notes +27 -0
  63. data/etc/themes/Shiny_Green.notes +27 -0
  64. data/etc/wrappers/wrapper.js +17 -0
  65. data/etc/wrappers/wrapper.py +20 -0
  66. data/etc/wrappers/wrapper.rb +25 -0
  67. data/lib/block.rb +72 -0
  68. data/lib/bookmarks.rb +352 -0
  69. data/lib/buffers.rb +170 -0
  70. data/lib/clipboard.rb +333 -0
  71. data/lib/code.rb +860 -0
  72. data/lib/code_tree.rb +476 -0
  73. data/lib/color.rb +274 -0
  74. data/lib/console.rb +557 -0
  75. data/lib/control_lock.rb +9 -0
  76. data/lib/control_tab.rb +176 -0
  77. data/lib/core_ext.rb +31 -0
  78. data/lib/cursor.rb +111 -0
  79. data/lib/deletes.rb +65 -0
  80. data/lib/diff_log.rb +297 -0
  81. data/lib/effects.rb +145 -0
  82. data/lib/environment.rb +5 -0
  83. data/lib/file_tree.rb +1875 -0
  84. data/lib/files.rb +334 -0
  85. data/lib/hide.rb +259 -0
  86. data/lib/history.rb +286 -0
  87. data/lib/image.rb +51 -0
  88. data/lib/incrementer.rb +15 -0
  89. data/lib/insert.rb +7 -0
  90. data/lib/irc.rb +22 -0
  91. data/lib/key_bindings.rb +658 -0
  92. data/lib/keys.rb +754 -0
  93. data/lib/launcher.rb +1351 -0
  94. data/lib/line.rb +429 -0
  95. data/lib/links.rb +6 -0
  96. data/lib/location.rb +175 -0
  97. data/lib/macros.rb +48 -0
  98. data/lib/man.rb +19 -0
  99. data/lib/menu.rb +708 -0
  100. data/lib/merb.rb +259 -0
  101. data/lib/message.rb +5 -0
  102. data/lib/meths.rb +56 -0
  103. data/lib/mode.rb +34 -0
  104. data/lib/move.rb +248 -0
  105. data/lib/notes.rb +1000 -0
  106. data/lib/numbers.rb +45 -0
  107. data/lib/ol.rb +203 -0
  108. data/lib/ol_helper.rb +44 -0
  109. data/lib/overlay.rb +167 -0
  110. data/lib/pause_means_space.rb +68 -0
  111. data/lib/php.rb +22 -0
  112. data/lib/projects.rb +21 -0
  113. data/lib/relinquish_exception.rb +2 -0
  114. data/lib/remote.rb +206 -0
  115. data/lib/requirer.rb +46 -0
  116. data/lib/rest_tree.rb +108 -0
  117. data/lib/ruby.rb +57 -0
  118. data/lib/ruby_console.rb +165 -0
  119. data/lib/search.rb +1572 -0
  120. data/lib/search_term.rb +40 -0
  121. data/lib/snippet.rb +68 -0
  122. data/lib/specs.rb +229 -0
  123. data/lib/styles.rb +274 -0
  124. data/lib/svn.rb +682 -0
  125. data/lib/text_util.rb +110 -0
  126. data/lib/tree.rb +1871 -0
  127. data/lib/tree_cursor.rb +87 -0
  128. data/lib/trouble_shooting.rb +27 -0
  129. data/lib/url_tree.rb +11 -0
  130. data/lib/view.rb +1474 -0
  131. data/lib/window.rb +133 -0
  132. data/lib/xiki.rb +404 -0
  133. data/menus/accounts.rb +5 -0
  134. data/menus/address_book.rb +21 -0
  135. data/menus/agenda.rb +28 -0
  136. data/menus/all.rb +5 -0
  137. data/menus/amazon.rb +16 -0
  138. data/menus/app.rb +16 -0
  139. data/menus/applescript.rb +46 -0
  140. data/menus/as.rb +15 -0
  141. data/menus/bookmarklet.rb +63 -0
  142. data/menus/bootstrap.rb +568 -0
  143. data/menus/browse.rb +13 -0
  144. data/menus/browser.rb +78 -0
  145. data/menus/cassandra_db.rb +36 -0
  146. data/menus/chmod.rb +27 -0
  147. data/menus/classes.rb +5 -0
  148. data/menus/coffee_script.rb +35 -0
  149. data/menus/computer.rb +24 -0
  150. data/menus/contacts.rb +5 -0
  151. data/menus/cookies.rb +25 -0
  152. data/menus/couch.rb +184 -0
  153. data/menus/crop.rb +45 -0
  154. data/menus/css.rb +55 -0
  155. data/menus/current.rb +5 -0
  156. data/menus/db.rb +12 -0
  157. data/menus/deck.rb +219 -0
  158. data/menus/dictionary.rb +9 -0
  159. data/menus/dir.rb +8 -0
  160. data/menus/disk.rb +5 -0
  161. data/menus/do.rb +13 -0
  162. data/menus/docs.rb +58 -0
  163. data/menus/dotsies.rb +107 -0
  164. data/menus/edited.rb +18 -0
  165. data/menus/emacs.rb +17 -0
  166. data/menus/enter.rb +13 -0
  167. data/menus/eval.rb +17 -0
  168. data/menus/executable.rb +16 -0
  169. data/menus/filter.rb +46 -0
  170. data/menus/firefox.rb +607 -0
  171. data/menus/foo.rb +30 -0
  172. data/menus/french.rb +7 -0
  173. data/menus/git.rb +185 -0
  174. data/menus/gito.rb +785 -0
  175. data/menus/google.rb +35 -0
  176. data/menus/google_images.rb +11 -0
  177. data/menus/google_patents.rb +10 -0
  178. data/menus/gutenberg.rb +13 -0
  179. data/menus/head.rb +10 -0
  180. data/menus/headings.rb +39 -0
  181. data/menus/html.rb +61 -0
  182. data/menus/icon.rb +40 -0
  183. data/menus/images.menu +2 -0
  184. data/menus/img.rb +15 -0
  185. data/menus/info.rb +9 -0
  186. data/menus/ip.rb +10 -0
  187. data/menus/iterm.rb +36 -0
  188. data/menus/itunes.rb +78 -0
  189. data/menus/javascript.rb +74 -0
  190. data/menus/layout.rb +18 -0
  191. data/menus/local_storage.rb +67 -0
  192. data/menus/ls.rb +19 -0
  193. data/menus/mac.rb +87 -0
  194. data/menus/maps.rb +18 -0
  195. data/menus/matches.rb +18 -0
  196. data/menus/memcache.rb +117 -0
  197. data/menus/mkdir.rb +23 -0
  198. data/menus/mongo.rb +83 -0
  199. data/menus/mysql.rb +294 -0
  200. data/menus/node.rb +88 -0
  201. data/menus/open.rb +19 -0
  202. data/menus/outline.rb +24 -0
  203. data/menus/piano.rb +746 -0
  204. data/menus/postgres.rb +34 -0
  205. data/menus/pre.rb +5 -0
  206. data/menus/python.rb +39 -0
  207. data/menus/rails.rb +160 -0
  208. data/menus/rake.rb +12 -0
  209. data/menus/redmine.rb +168 -0
  210. data/menus/riak_tree.rb +204 -0
  211. data/menus/rss.rb +15 -0
  212. data/menus/safari.rb +11 -0
  213. data/menus/sass.rb +15 -0
  214. data/menus/say.rb +6 -0
  215. data/menus/scale.rb +49 -0
  216. data/menus/serve.rb +78 -0
  217. data/menus/shuffle.rb +24 -0
  218. data/menus/spanish.rb +7 -0
  219. data/menus/standalone.rb +57 -0
  220. data/menus/tail.rb +41 -0
  221. data/menus/technologies.rb +19 -0
  222. data/menus/themes.rb +32 -0
  223. data/menus/thesaurus.rb +13 -0
  224. data/menus/to.rb +24 -0
  225. data/menus/twitter.rb +57 -0
  226. data/menus/wikipedia.rb +34 -0
  227. data/menus/words.rb +11 -0
  228. data/spec/code_tree_spec.rb +59 -0
  229. data/spec/diff_log_spec.rb +40 -0
  230. data/spec/file_tree_spec.rb +102 -0
  231. data/spec/keys_spec.rb +24 -0
  232. data/spec/line_spec.rb +68 -0
  233. data/spec/menu_spec.rb +50 -0
  234. data/spec/ol_spec.rb +98 -0
  235. data/spec/remote_spec.rb +43 -0
  236. data/spec/search_spec.rb +162 -0
  237. data/spec/text_util_spec.rb +119 -0
  238. data/spec/tree_cursor_spec.rb +91 -0
  239. data/spec/tree_spec.rb +955 -0
  240. data/tests/console_test.rb +11 -0
  241. data/tests/couch_db_test.rb +12 -0
  242. data/tests/diff_log_test.rb +43 -0
  243. data/tests/el_mixin.rb +16 -0
  244. data/tests/git_test.rb +95 -0
  245. data/tests/keys_test.rb +19 -0
  246. data/tests/line_test.rb +38 -0
  247. data/tests/merb_test.rb +11 -0
  248. data/tests/redmine_test.rb +50 -0
  249. data/tests/remote_test.rb +31 -0
  250. data/tests/rest_tree_test.rb +70 -0
  251. data/xiki.gemspec +37 -0
  252. metadata +332 -0
@@ -0,0 +1,145 @@
1
+ require 'styles'
2
+
3
+ # Makes visual things happen
4
+ class Effects
5
+
6
+ def self.menu
7
+ "
8
+ > Do cool-looking things to text
9
+ - api/
10
+ | Try these out by double-clicking on them.
11
+ - Glow/
12
+ @Effects.glow
13
+ @Effects.glow :times=>6
14
+ @Effects.glow :what=>:paragraph
15
+ @Effects.glow :what=>:block
16
+ @Effects.glow :what=>[1, 100]
17
+
18
+ - Colors/
19
+ @Effects.glow :color=>:fire
20
+ @Effects.glow :color=>:water
21
+ @Effects.glow :color=>:forest
22
+ @Effects.glow :color=>:rainbow
23
+ @Effects.glow :color=>:fat_rainbow
24
+ - Fade in and out/
25
+ @Effects.glow :fade_in=>1
26
+ @Effects.glow :fade_out=>1
27
+ - Blink/
28
+ | Makes line blink orange. Using a longer time since the blink happens
29
+ | anyway.
30
+
31
+ @Effects.blink :time=>1
32
+ - Some View methods that use effects/
33
+ @View.prompt
34
+ @View.flash
35
+ @View.flash 'Saved!'
36
+ - docs/
37
+ > Keys
38
+ | do+line+effects: make line blink
39
+ | up+do+line+effects: make line blink rainbow color
40
+
41
+ > See
42
+ << themes/
43
+ "
44
+ end
45
+
46
+ def self.glow options={}
47
+
48
+ what = options[:what]
49
+ if what.is_a? Array
50
+ left, right = what
51
+ elsif what == :block
52
+ ignore, left, right = View.block_positions "^[|>]"
53
+ elsif what == :paragraph
54
+ left, right = View.paragraph :bounds=>1
55
+ else
56
+ left, right = Line.left, Line.right
57
+ end
58
+
59
+
60
+ # Set :times to 1 if no args and fade out
61
+ times = 1 if ! options[:times] && (options[:fade_out] || options[:fade_in])
62
+
63
+ times ||= options[:times] || 3
64
+
65
+ over = $el.make_overlay left, right
66
+
67
+ faces =
68
+ if options[:color] == :fire
69
+ ['font-lock-doc-face', 'font-lock-function-name-face', 'html-helper-server-script-face', 'font-lock-variable-name-face', 'speedbar-tag-face', 'speedbar-tag-face', 'speedbar-tag-face']
70
+ elsif options[:color] == :forest
71
+ ['dired-mark', 'dired-mark', 'widget-documentation', 'widget-documentation', 'widget-documentation', 'bookmark-menu-heading', 'bookmark-menu-heading']
72
+ elsif options[:color] == :water
73
+ ['font-lock-constant-face', 'font-lock-constant-face', 'escape-glyph', 'escape-glyph', 'font-lock-builtin-face', 'font-lock-builtin-face', 'font-lock-builtin-face']
74
+ elsif options[:color] == :rainbow
75
+ ['blue', 'red', 'orange', 'orange', 'green', 'green', 'yellow']
76
+ elsif options[:color] == :fat_rainbow
77
+ ['notes-yellow', 'notes-yellow', 'notes-green', 'notes-green', 'notes-blue', 'notes-red', 'notes-red']
78
+ else
79
+ ['fade1', 'fade2', 'fade3', 'fade4', 'fade5', 'fade6', 'fade7']
80
+ end
81
+
82
+ up = [6, 5, 4, 3, 2]
83
+ down = [2, 3, 4, 5, 6]
84
+
85
+ sequence =
86
+ if options[:fade_out]; [1] + (down + [7]) * times
87
+ elsif options[:fade_in]; [7] + (up + [1]) * times
88
+ elsif options[:reverse]; [7] + (up + [1] + down + [7]) * times
89
+ else; [1] + (down + [7] + up + [1]) * times
90
+ end
91
+
92
+ sequence.each do |i|
93
+ $el.overlay_put over, :face, (faces[i-1] || faces[0])
94
+ $el.sit_for 0.02
95
+ end
96
+
97
+ $el.delete_overlay over
98
+ end
99
+
100
+ # Sample usages:
101
+ def self.blink options={}
102
+ what = options[:what]
103
+ what ||= :line
104
+ case what
105
+ when :all
106
+ left, right = View.top, View.bottom
107
+ when :region
108
+ left, right = View.range
109
+ when :line
110
+ left, right = Line.bounds
111
+ right += 1
112
+ when :sexp
113
+ left, right = bounds_of_thing_at_point(:sexp).to_a
114
+ return unless left
115
+ end
116
+
117
+ left = options[:left] if options[:left]
118
+ right = options[:right] if options[:right]
119
+
120
+ time = options[:time] || 0.04
121
+ over2 = $el.make_overlay(left, right)
122
+ $el.overlay_put over2, :face, :color_rb_glow2
123
+ $el.sit_for time
124
+ $el.delete_overlay over2
125
+ end
126
+
127
+ # Define font
128
+ def self.define_styles
129
+ Styles.define :color_rb_glow1, :bg => "fec"
130
+ Styles.define :color_rb_glow2, :bg => "f90"
131
+
132
+ Styles.define :red, :fg => "f00"
133
+ Styles.define :orange, :fg => "f80"
134
+ Styles.define :yellow, :fg => "ff0"
135
+ Styles.define :green, :fg => "0f0"
136
+ Styles.define :blue, :fg => "00f"
137
+ Styles.define :indigo, :fg => "408"
138
+ Styles.define :violet, :fg => "82e"
139
+
140
+ Styles.define :purple, :fg => "808"
141
+ Styles.define :cyan, :fg => "f0f"
142
+ end
143
+
144
+ end
145
+ Effects.define_styles
@@ -0,0 +1,5 @@
1
+ class Environment
2
+ def self.linux?
3
+ $el.elvar.system_type.to_s[/linux/]
4
+ end
5
+ end
@@ -0,0 +1,1875 @@
1
+ require 'styles'
2
+ require 'line'
3
+ require 'view'
4
+ require 'net/http'
5
+ require 'uri'
6
+ require 'cursor'
7
+
8
+ # Draws a tree from a dir structure and lets you incrementally search in the tree.
9
+ # Usage (user):
10
+ # - Type C-x C-t
11
+ # - A tree will be drawn in a new buffer
12
+ # - Representing the directory structure in the current dir
13
+ # - Type Enter to open a file
14
+ # - Navigate within the buffer
15
+ # - Then press Enter when you're on the file you wish to open
16
+ # Usage (api):
17
+ # - Call this to draw a tree from the current directory
18
+ # - FileTree.ls
19
+ class FileTree
20
+
21
+ @@one_view_in_bar_by_default = false
22
+
23
+ def self.menu
24
+ %`
25
+ - docs/
26
+ | > Summary
27
+ | Lets you navigate your filesystem as a tree.
28
+ |
29
+ | Type a file path on a line and double-click on it. Here's an example
30
+ | (the "@" isn't required when there are no spaces at the beginning of the
31
+ | line).
32
+ |
33
+ @/
34
+ |
35
+ - overview of keys/
36
+ | When you launch a path, the cursor turns blue and you go into a temporary
37
+ | search. Here are some keys you can type:
38
+ |
39
+ | - Letters: search and filter lines
40
+ |
41
+ | - Return: stops searching and launches (expands file or dir)
42
+ | - Tab: like return but hides others
43
+ | - ;: like return but collapses path
44
+ |
45
+ | - C-g: stops searching
46
+ |
47
+ - api/
48
+ | Turn paths into a tree
49
+ @ puts Tree.paths_to_tree $el.elvar.recentf_list.to_a[0..2]
50
+ `
51
+ end
52
+
53
+ # TODO
54
+ # - Make search handle trees with multiple roots
55
+
56
+ # Call this method from your init.rb to use the default key shortcuts.
57
+ def self.keys
58
+ Keys.XT { FileTree.ls }
59
+ end
60
+
61
+ def initialize
62
+ @res = ""
63
+ @list = []
64
+ @file_regex = nil
65
+ end
66
+ attr :res
67
+ attr :list
68
+ attr_accessor :file_regex
69
+
70
+ # Change dirs into spaces, etc
71
+ def clean path, indent=0
72
+ path = path.gsub(/.+?\//, " ")
73
+ indent == 0 ?
74
+ path :
75
+ path.sub(/^#{indent}/, '')
76
+ end
77
+
78
+ # Recursively draws out tree
79
+ def traverse path
80
+ entries = Dir.glob("#{View.expand_path(path)}/*", File::FNM_DOTMATCH).
81
+ select {|i| i !~ /\/\.(\.*|svn|git)$/}. # Exclude some dirs (exclude entensions here too?)
82
+ sort
83
+
84
+ # Process dirs
85
+ entries.each{ |f|
86
+ next unless FileTest.directory?(f)
87
+ cleaned = clean f
88
+ @res += "#{cleaned.sub(/(^ *)/, "\\1- ")}/\n"
89
+ traverse f
90
+ }
91
+
92
+ # Process files
93
+ entries.each{ |f|
94
+ next unless FileTest.file?(f)
95
+ cleaned = clean f
96
+ @res += "#{cleaned.sub(/(^ *)/, "\\1+ ")}\n"
97
+ }
98
+
99
+ end
100
+
101
+ def self.grep dir, regex, options={}
102
+
103
+ raw = options[:raw]
104
+
105
+ # Expand out bookmark (if there is one)
106
+ dir = Bookmarks.expand(dir)
107
+ dir = Bookmarks.dir_only dir # Cut off file (if there is one)
108
+ dir.sub!(/\/$/, '') # Remove slash from end
109
+
110
+ # Turn regex into a regex, if a string
111
+ regex = Regexp.new(regex, Regexp::IGNORECASE) if regex.is_a? String
112
+
113
+ unless raw
114
+ View.bar if options[:bar]
115
+ View.to_buffer "*tree grep"; View.dir = dir
116
+ View.clear; Notes.mode
117
+ end
118
+
119
+ @@indent_count = dir.count('/') - 1
120
+ @@indent = " " * @@indent_count
121
+ t = self.new
122
+
123
+ files = options[:files]
124
+ if files
125
+ files = Regexp.new(files, Regexp::IGNORECASE) if files.is_a? String
126
+ t.file_regex = files
127
+ end
128
+ t.list << "- #{dir}/"
129
+ t.grep_inner dir, regex, :first_time
130
+
131
+ list = t.list
132
+ Tree.clear_empty_dirs! list
133
+ if raw
134
+ return list
135
+ end
136
+ if list.size == 0
137
+ View.insert "> Note\n- No Results Found!\n"
138
+ else
139
+ View.insert(list.join("\n") + "\n")
140
+ end
141
+
142
+ View.to_top
143
+ $el.highlight_regexp(regex, :ls_quote_highlight) if regex
144
+ $el.re_search_forward "|"
145
+ end
146
+
147
+ def self.grep_with_hashes path, regex, prepend='##'
148
+ Search.append_log path, "- #{prepend}#{regex}/"
149
+
150
+ View.to_buffer "*tree grep"
151
+ View.dir = Files.dir_of(path)
152
+ View.clear; Notes.mode
153
+
154
+ if File.directory?(path)
155
+ View << "- #{File.expand_path path}/\n - #{prepend}#{regex}/\n"
156
+ else
157
+ dir, name = path.match(/(.+\/)(.+)/)[1..2]
158
+ View << "- #{File.expand_path dir}/\n - #{name}\n - #{prepend}#{regex}/\n"
159
+ end
160
+
161
+ View.to_bottom; Line.previous
162
+ self.launch
163
+ end
164
+
165
+ def self.grep_one_file(f, regex, indent)
166
+ result = []
167
+ IO.foreach(f) do |line|
168
+ line.gsub!(/[\r\n\c@]+/, '')
169
+ if regex
170
+ next unless line =~ regex
171
+ end
172
+ result << "#{indent}| #{line}"
173
+ end
174
+ result
175
+ end
176
+
177
+ def self.outline_search(f, regex, indent)
178
+ result = []
179
+ current_line = Search.outline_goto_once # If search_outline, we want to put cursor on that line when done
180
+ line_found, matches_count, i = nil, 0, 0
181
+ IO.foreach(f) do |line|
182
+ i+=1
183
+ line.gsub!(/[\r\n\c@]+/, '')
184
+
185
+ if current_line && line_found.nil?
186
+ line_found = matches_count if i == current_line
187
+ end
188
+
189
+ if regex
190
+ next unless line =~ regex
191
+ end
192
+ result << "#{indent}| #{line}"
193
+ matches_count+=1
194
+ end
195
+ Search.outline_goto_once = line_found
196
+ result
197
+ end
198
+
199
+ def self.skip
200
+ @skip || {}
201
+ end
202
+
203
+ def grep_inner path, regex, first_time=nil
204
+
205
+ path.sub!(/\/$/, '')
206
+ entries = Dir["#{path}/*"].entries.sort
207
+
208
+ entries = entries.select{|o| o !~ /\/(vendor|log)$/} # Exclude some dirs (why doing it here?
209
+
210
+ # Exclude some file extensions and dirs
211
+ # Make these be in config! pass along as instance var
212
+ # TMP:::: Hard-code skipping image/ dirs (for now) !!!!!!!!!!!!!
213
+ entries = entries.select{|o| o !~ /\/(images|twilio_sound)$/}
214
+
215
+ if first_time and skip = FileTree.skip[path]
216
+ entries = entries.select{|o| ! skip.member? o[/.+\/(.+)/, 1]}
217
+ end
218
+
219
+ # Process dirs
220
+ entries.each{ |f|
221
+ next unless FileTest.directory?(f)
222
+ cleaned = clean(f, @@indent)
223
+ @list << "#{cleaned.sub(/(^ *)/, "\\1- ")}/"
224
+ grep_inner f, regex
225
+ }
226
+
227
+ indent = nil
228
+
229
+ # Process files
230
+ entries.each do |f|
231
+ next unless FileTest.file?(f)
232
+
233
+ # If matching filename, skip if no match
234
+ if file_regex
235
+ stem = f[/[^\/]+$/]
236
+ next unless stem =~ file_regex
237
+ end
238
+
239
+ indent = " " * (f.count('/') - @@indent_count) unless indent
240
+
241
+ if regex
242
+ result = FileTree.grep_one_file(f, regex, indent) # Search in file contents
243
+
244
+ if result.size > 0 # Add if any files were found
245
+ @list << clean(f, @@indent).sub(/(^ *)/, "\\1- ")
246
+ @list += result
247
+ end
248
+ else
249
+ @list << clean(f, @@indent).sub(/(^ *)/, "\\1+ ")
250
+ end
251
+ end
252
+
253
+ end
254
+
255
+ # Does ls in current buffer, without making any modifications to the environment
256
+ def self.ls_here dir
257
+ t = self.new
258
+ t.traverse View.dir
259
+ View.insert "- #{dir}\n"
260
+
261
+ result_indent = t.res[/^ */] # Get indent of first line
262
+ View.insert t.res.gsub(/^#{result_indent}/, " ")
263
+ end
264
+
265
+
266
+ def self.define_styles
267
+
268
+ return if ! $el
269
+
270
+ if Styles.inverse # Bullets
271
+ Styles.define :ls_bullet,
272
+ :face => 'courier', :size => "+2", # Mac
273
+ :fg => "dd7700", :bold => true
274
+ else
275
+ Styles.define :ls_bullet,
276
+ :face => 'courier', :size => "+2", # Mac
277
+ :fg => "ff7700", :bold => true
278
+ end
279
+
280
+
281
+ if Styles.inverse
282
+ Styles.define :quote_heading, :fg=>"fff", :size=>"0", :face=>"arial", :bold=>false
283
+ Styles.define :quote_heading2, :fg=>"fff", :size=>"-2", :face=>"arial", :bold=>false
284
+ Styles.define :quote_heading_pipe, :fg=>"333", :size=>"0", :face => "verdana", :bold=>true
285
+ Styles.define :quote_heading_bracket, :fg=>"4c4c4c", :size=>"-2", :face => "Monaco", :bold=>true
286
+ Styles.define :quote_heading_small, :fg=>"fff", :size=>"-2", :face => "arial black", :bold=>true
287
+
288
+ Styles.define :diff_line_number, :bold => true, :size => "-2", :fg => "444444"
289
+ Styles.define :diff_red, :bg => "400", :fg => "ee3333", :size => "-1"
290
+ Styles.define :diff_green, :bg => "130", :fg => "44dd33", :size => "-1"
291
+ Styles.define :diff_small, :fg => "222", :size => "-11"
292
+
293
+ Styles.tree_keys :fg=>"#fff", :underline=>nil
294
+
295
+ # dir/
296
+ Styles.define :ls_dir, :fg => "888", :face => "verdana", :size => "-1", :bold => true
297
+ # Styles.define :ls_dir, :fg => "bbb", :face => "verdana", :size => "-2", :bold => true
298
+
299
+ else
300
+ Styles.define :quote_heading, :fg=>"444", :size=>"0", :face=>"arial", :bold=>false
301
+ Styles.define :quote_heading2, :fg=>"aaa", :size=>"-2", :face=>"arial", :bold=>false
302
+ Styles.define :quote_heading_pipe, :fg=>"bbb", :size=>"0", :face => "verdana", :bold=>true
303
+ Styles.define :quote_heading_bracket, :fg=>"bbb", :size=>"-2", :face => "Monaco", :bold=>true
304
+ Styles.define :quote_heading_small, :fg=>"fff", :size=>"-2", :face => "arial black", :bold=>true
305
+
306
+ Styles.define :diff_line_number, :bold => true, :size => "-2", :fg => "ccc"
307
+ Styles.define :diff_red, :bg => "ffdddd", :fg => "cc4444"
308
+ Styles.define :diff_green, :bg => "ddffcc", :fg => "337744"
309
+ Styles.define :diff_small, :fg => "ddd", :size => "-11"
310
+
311
+ Styles.tree_keys :fg=>"#ff0", :underline=>1
312
+
313
+ # dir/
314
+ Styles.define :ls_dir, :fg => "777", :face => "verdana", :size => "-1", :bold => true
315
+
316
+ end
317
+
318
+ # ##search/
319
+ Styles.define :ls_search,
320
+ :fg => "ff7700",
321
+ :face => "verdana",
322
+ :size => "-2",
323
+ :bold => true
324
+
325
+ if Styles.inverse # | Quoted text
326
+ Styles.define :ls_quote,
327
+ :size => "-1",
328
+ :fg => "aaa"
329
+ else
330
+ Styles.define :ls_quote,
331
+ :size => "-1",
332
+ :fg => "777"
333
+ end
334
+
335
+ # 001| Quoted text lines
336
+ Styles.define :ls_quote_line_number,
337
+ :size => "-4",
338
+ :fg => "eee"
339
+
340
+ # Highlight in search
341
+ Styles.define :ls_quote_highlight,
342
+ :size => "-1",
343
+ :bg => "ffff44",
344
+ :fg => "666666"
345
+ end
346
+
347
+ def self.apply_styles
348
+ $el.el4r_lisp_eval "(setq font-lock-defaults '(nil t))"
349
+
350
+ # Must go before quotes - if it goes after, it supercedes them
351
+ Styles.apply("\\(~\\)\\(.+?\\)\\(~\\)", nil, :quote_heading_bracket, :notes_label, :quote_heading_bracket)
352
+
353
+ Styles.apply("https?://[a-zA-Z0-9\/.~_:-]+", :notes_link) # blue-ify url's
354
+
355
+ # - bullets
356
+ Styles.apply("^[ \t]*\\([+=-]\\)\\( \\)", nil, :ls_bullet, :variable)
357
+
358
+ # With numbers
359
+ Styles.apply("^ +\\(:[0-9]+\\)\\(|.*\n\\)", nil, :ls_quote_line_number, :ls_quote)
360
+
361
+ # Path-like lines and parts of lines (make gray)
362
+
363
+ # Single "@" at beginning
364
+ Styles.apply("^[ <+=@-]*\\(@\\)", nil, :ls_dir) # @
365
+
366
+ Styles.apply("^[ \t]*\\(<+=?@?\\)\\( \\)", nil, :ls_bullet, :variable)
367
+
368
+ # Remove later?
369
+ Styles.apply("^[ <+=@-]*\\([^|\n]+/\\)$", nil, :ls_dir) # slash at end
370
+
371
+
372
+ # Slash after almost anything
373
+
374
+ Styles.apply("^[ <+-]*@?\\([~$#a-zA-Z0-9_,? ().:-]*[^ \n]\/\\)", nil, :ls_dir)
375
+ # Covers paths in files by themselves
376
+
377
+ Styles.apply("^[ <+-]*\\([@~$a-zA-Z0-9_,? ().:<-]*\/[@\#'$a-zA-Z0-9_,? ().:\/<-]+\/\\)", nil, :ls_dir) # Paths with multiple slashes
378
+
379
+ Styles.apply("^[ \t]*[<+-] [a-zA-Z0-9_,? ().:-]+?[:)] \\(\[.@a-zA-Z0-9 ]+\/\\)", nil, :ls_dir) # label, one word, slash
380
+ Styles.apply("^[ \t]*[<+-] [a-zA-Z0-9_,? ().:-]+?[:)] \\([.@a-zA-Z0-9 ]+\/[.@a-zA-Z0-9 \/]+\/\\)", nil, :ls_dir) # label, one word, path, slash
381
+
382
+ # Bullets
383
+ Styles.apply("^[ \t]*[+-] [^(\n]+?) \\(.+/\\)$", nil, :ls_dir) # - hey) /what/
384
+ Styles.apply("^[ \t]*[+-] [a-zA-Z0-9_,? ().:-]+?: \\(.+/\\)$", nil, :ls_dir) # - hey: /what/
385
+
386
+ # Put this one back?
387
+ # Styles.apply("^[ +-]*\\([^|\n]+/\\)$", nil, :ls_dir) # Dirs with bullets
388
+
389
+ Styles.apply('https?://[a-zA-Z0-9\/.~_:?%&=|+!-#-]+', :notes_link) # Url
390
+
391
+ # |... lines (quotes)
392
+ Styles.apply("^ *\\(|\\)\\( *\\)", nil, :quote_heading_pipe, :ls_quote)
393
+ Styles.apply("^ *\\(|\\)\\(.*\n\\)", nil, :quote_heading_pipe, :ls_quote)
394
+ Styles.apply("^ *\\(|\\)\\(.+?\\)([+-].*[-+])", nil, :quote_heading_pipe, :ls_quote) # quoted lines: beginnings of lines
395
+ Styles.apply("^ *|.*([-+].*[+-])\\(.+\\)$", nil, :ls_quote) # quoted lines: ends of lines
396
+ Styles.apply("[+-])\\(.*?\\)([+-]", nil, :ls_quote) # quoted lines: between diffs
397
+
398
+ # | >... headings
399
+ Styles.apply("^ *\\(|\\)\\( \\)\\(>\\)\\(\n\\| .*\n\\)", nil, :quote_heading_pipe, :ls_quote, :quote_heading_bracket, :quote_heading)
400
+ Styles.apply("^ *\\(|\\)\\( \\)\\(>>\\)\\(\n\\| .*\n\\)", nil, :quote_heading_pipe, :ls_quote, :quote_heading_bracket, :quote_heading_small)
401
+
402
+ # >... headings (indented)
403
+ Styles.apply("^ +\\(> ?\\)\\(\n\\|.*\n\\)", nil, :quote_heading_bracket, :quote_heading)
404
+
405
+ # >>... headings (indented)
406
+ Styles.apply("^ +\\(>>\\)\\(\n\\| .*\n\\)", nil, :quote_heading_bracket, :quote_heading2)
407
+
408
+
409
+ # |+... diffs
410
+ Styles.apply("^ +\\(:[0-9]+\\)$", nil, :ls_quote)
411
+ Styles.apply("^ *\\(|\\+.*\\)", nil, :diff_green) # whole lines
412
+ Styles.apply("^ *\\(|\-.*\\)", nil, :diff_red)
413
+ Styles.apply("^ *\\(|@@ .*\n\\)", nil, :diff_line_number)
414
+
415
+ #Styles.apply('^[ -]*\\([ a-zA-Z0-9\/_\.$-]*\\w/\\)$', nil, :ls_dir) # Most dirs
416
+ Styles.apply('^ *\\(//?\\)$', nil, :ls_dir) # /
417
+ Styles.apply('^ *\\(\./\\)$', nil, :ls_dir) # ./
418
+ end
419
+
420
+ def self.apply_styles_at_end
421
+ Styles.apply('^ *[+-] \\(##.*/\\)$', nil, :ls_search) # ##_/
422
+ Styles.apply('^ *\\([+-] \\)?\\(@f/.*/\\)$', nil, nil, :ls_search) # ##_/
423
+ Styles.apply('^ *[+-] \\(\*\*.+/\\)$', nil, :ls_search) # **_/
424
+ Styles.apply('^ *\\([+-] \\)?\\(@n/.*/\\)$', nil, nil, :ls_search) # ##_/
425
+ end
426
+
427
+ def self.handles? list=nil
428
+ begin
429
+ list ||= Tree.construct_path(:list=>true) # Use current line by default
430
+ rescue Exception=>e
431
+ return nil
432
+ end
433
+ return 0 if self.matches_root_pattern?(Line.without_label :line=>list.first)
434
+ nil
435
+ end
436
+
437
+ def self.matches_root_pattern? item
438
+ item =~ /^\/(\w|$)/ || # /... or /
439
+ item =~ /^~\// ||
440
+ item =~ /^\.\.?\// ||
441
+ item =~ /^\$\w/
442
+ end
443
+
444
+ # Open the line in the tree that the cursor is on. This is probably
445
+ # be mapped to C-. .
446
+ # TODO: remove ignore_prefix, and just use Keys.clear_prefix
447
+ def self.open options={}
448
+
449
+ # If passed just a dir, open it in new view
450
+ return self.ls :dir=>options if options.is_a? String
451
+
452
+ original_file = View.file_name
453
+
454
+ path = options[:path] || Tree.construct_path(:list=>true)
455
+ path_orig = path
456
+
457
+ if path.is_a?(Array)
458
+ # Pull off search string if exists
459
+ search_string = path.pop[/\|(.*)/, 1] if path.last =~ /^\|/
460
+ search_string.sub! /^[ +-]/, '' if search_string # Should start with space or -|+
461
+ # Discard rest of |... lines
462
+ path = path.grep(/^[^|]/).join('')
463
+ else
464
+ # Split off |... if it's there (search string)
465
+ path =~ /(.*?)-?\+?\|(.*)/
466
+ path, search_string = $1, $2 if $2
467
+ end
468
+
469
+ # Pull number off end of path if there
470
+ path =~ /(.+):(\d+)$/
471
+ path, line_number = $1, $2 if $2
472
+
473
+ path = Bookmarks.expand(path)
474
+
475
+ return Files.open_in_os(path) if Keys.prefix == 0
476
+
477
+ remote = self.is_remote?(path)
478
+ unless remote
479
+ path = File.expand_path(path)
480
+ end
481
+ # Prefix keys with specific behavior
482
+ prefix_was_u = false
483
+
484
+ prefix = Keys.prefix
485
+
486
+ case prefix
487
+ when :u # Just open file
488
+ prefix_was_u = true
489
+ Keys.clear_prefix
490
+ when "update" # Save ($ave) file
491
+ return self.save_quoted path
492
+ when "delete" # Save ($ave) file
493
+ return self.delete_file
494
+ when "all"
495
+ Keys.clear_prefix
496
+ search_string ? # If quote, enter lines under
497
+ Tree.enter_under :
498
+ self.enter_lines(//) # If file, enter all lines
499
+ return
500
+ else
501
+ if prefix =~ /\boutline\b/
502
+ prefix = Keys.prefix_n
503
+ Keys.clear_prefix
504
+ return self.drill_quotes_or_enter_lines path, search_string, :prefix=>prefix
505
+ end
506
+ end
507
+
508
+ # If numeric prefix, jump to nth window
509
+ if (! options[:ignore_prefix]) and Keys.prefix_n and Keys.prefix != 7
510
+
511
+ # If number larger than number of windows, open new one first
512
+ if Keys.prefix_n > View.list.size
513
+ View.to_nth(View.list.size - 1)
514
+ View.create
515
+ end
516
+ View.to_nth(Keys.prefix_n - 1)
517
+
518
+ end
519
+
520
+ column = View.column
521
+ if search_string
522
+ column -= (Line.value[/^.*?\|./] || '').length
523
+ column = 0 if column < 0
524
+ end
525
+
526
+ # Open or go to file
527
+
528
+ if remote
529
+ self.remote_file_contents(path) # Get text from server and insert
530
+
531
+ else # Normal file opening
532
+
533
+ # See if it exists, and store contents if not?
534
+
535
+ options[:same_view] ? View.open(path, :same_view=>true) : Location.go(path)
536
+ Effects.blink(:what=>:line) unless line_number || search_string || path =~ /\.xiki$/
537
+ end
538
+
539
+ return unless line_number || search_string
540
+
541
+ if line_number # If line number, go to it
542
+ $el.goto_line line_number.to_i
543
+ Effects.blink(:what=>:line)
544
+ elsif search_string # Else, search for |... string if it passed
545
+ Hide.reveal if View.hidden?
546
+
547
+ Move.top
548
+ # Search for exact line match
549
+ found = Search.forward "^#{$el.regexp_quote(search_string)}$"
550
+
551
+ unless found # If not found, search for substring of line, but with a break at the end
552
+ Move.top
553
+ # :beginning
554
+ found = Search.forward "#{$el.regexp_quote(search_string)}\\([^_a-zA-Z0-9\n]\\|$\\)", :beginning=>true
555
+ # found = $el.search_forward_regexp("#{$el.regexp_quote(search_string)}\\([^_a-zA-Z0-9\n]\\|$\\)", nil, true)
556
+ end
557
+ unless found # If not found, search for substring of line
558
+ Move.top
559
+ found = $el.search_forward_regexp "#{$el.regexp_quote(search_string)}", nil, true
560
+ end
561
+ unless found # If not found, search for it stripped
562
+ Move.top
563
+ found = $el.search_forward_regexp "#{$el.regexp_quote(search_string.strip)}", nil, true
564
+ end
565
+ unless found # If not found, suggest creating or show error
566
+
567
+ return if self.suggest_creating search_string
568
+
569
+ View.beep
570
+ View.message "Didn't find: \"#{search_string.strip}\"", :beep=>1
571
+ return
572
+
573
+ end
574
+
575
+ $el.beginning_of_line
576
+ $el.recenter(0) unless prefix_was_u
577
+ Effects.blink(:what=>:line)
578
+
579
+ dir, name = path.match(/(.+\/)(.+)/)[1..2]
580
+
581
+ return if original_file == "search_log.notes"
582
+
583
+ # Add to log
584
+ Search.append_log dir, "- #{name}\n | #{search_string}"
585
+ end
586
+ View.column = column
587
+
588
+ end
589
+
590
+ def self.suggest_creating search_string
591
+
592
+ if search_string =~ /^ *def self\.(.+)/ # If it's a method, suggest creating it
593
+ Code.suggest_creating_method View.file, $1
594
+ return true
595
+ end
596
+
597
+ return false # We didn't handle it
598
+ end
599
+
600
+
601
+ def self.drill_quotes_or_enter_lines path, quote, options={}
602
+ return self.enter_lines(nil, options) if ! quote # If not quote must be whole file
603
+ result = self.drill_quote path # Try showing only children with children
604
+ return Tree.<<(result) if result.any?
605
+ return Tree.enter_under # Only leafs, so show them
606
+ end
607
+
608
+ def self.save_quoted path
609
+ txt = Tree.siblings :quotes=>1
610
+
611
+ if txt[0] !~ /^\|/
612
+ # TODO: also check that it's a tree
613
+ # return View.flash("- All siblings should be quoted - (make this more flexible?)")
614
+ return self.move_or_delete_via_diffs
615
+ end
616
+
617
+ dir = File.dirname path
618
+
619
+ if ! File.exists? dir
620
+ View.flash "- Dir doesn\'t exist. Create it?"
621
+
622
+ key = Keys.input :chars=>1, :prompt=>"Create the \"#{dir}\" directory? "
623
+ return if key != "y"
624
+
625
+ `mkdir -p "#{dir}"`
626
+ end
627
+
628
+ txt = txt.map{|o| "#{o.sub /^\| ?/, ''}\n"}.join('')
629
+ DiffLog.save_diffs :patha=>path, :textb=>txt
630
+
631
+ File.open(path, "w") { |f| f << txt }
632
+
633
+ View.flash "- Saved!"
634
+ end
635
+
636
+ def self.operations_via_diffs added, deleted
637
+ tmp_path = "/tmp/saved.txt"
638
+ operations = [[], []]
639
+
640
+ file = View.file
641
+ $el.with(:with_temp_buffer) do
642
+ $el.insert_file file
643
+ # Ol << "View.txt(1, 150): #{View.txt(1, 150)}"
644
+ deleted.each do |line_number|
645
+ View.line = line_number
646
+ path = Tree.construct_path
647
+ operations[1] << path
648
+ end
649
+ end
650
+
651
+ if added.any?
652
+ $el.with(:with_temp_buffer) do
653
+ $el.insert_file tmp_path
654
+ added.each do |add|
655
+ line_number = add
656
+ View.line = line_number
657
+ path = Tree.construct_path
658
+ operations[0] << path
659
+ end
660
+ end
661
+ end
662
+ operations
663
+ end
664
+
665
+ def self.move_or_delete_via_diffs
666
+
667
+ # Save current version to temporary place, and diff
668
+ tmp_path = "/tmp/saved.txt"
669
+ $el.write_region nil, nil, tmp_path
670
+ diff = Console.sync %`diff --old-group-format="d%df %dn%c'\012'" --new-group-format="a%dF %dN%c'\012'" --unchanged-line-format="" "#{View.file}" "#{tmp_path}"`
671
+
672
+ added, deleted = DiffLog.parse_tree_diffs diff
673
+
674
+ return Tree.<<("> Error\n| You haven't changed any lines (since you last saved).", :no_slash=>1) if added.blank? && deleted.blank?
675
+
676
+ return Tree.<<("> Error\n| You\'ve deleted #{deleted.length} lines but added #{added.length} (since you last saved).\n| It\'s unclear what you\'re trying to do.", :no_slash=>1) if added.any? && added.length != deleted.length
677
+ operation = added.any? ? :moves : :deletes
678
+
679
+ operations = self.operations_via_diffs added, deleted
680
+
681
+ View.flash "- Are you sure?", :times=>1
682
+
683
+ if operation == :moves
684
+ message = "Move these files?\n"
685
+ operations[0].each_with_index do |o, i|
686
+ delete = operations[1][i]
687
+ message << "- #{delete} -> #{o}\n"
688
+ end
689
+ else
690
+ message = "Delete these files?\n"
691
+ operations[1].each do |o|
692
+ message << "- #{o}\n"
693
+ end
694
+ end
695
+
696
+ choice = Keys.input :prompt=>"#{message}", :chars=>1
697
+
698
+ return if choice !~ /[ym]/i
699
+
700
+ if operation == :moves
701
+ operations[0].each_with_index do |o, i|
702
+ delete = operations[1][i]
703
+ command = "mv \"#{delete}\" \"#{o}\""
704
+ Console.sync command, :dir=>"/tmp/"
705
+ end
706
+ else
707
+ operations[1].each do |o|
708
+ command = "rm \"#{o}\""
709
+ Console.sync command, :dir=>"/tmp/"
710
+ end
711
+ end
712
+
713
+ View.flash "- done!", :times=>1
714
+
715
+ nil
716
+ end
717
+
718
+ def self.drill_quote path
719
+ parent = Line.value
720
+ parent.sub! /^ *\| /, ''
721
+
722
+ found, indent, candidate, result = false, 0, nil, ""
723
+ IO.foreach(path) do |line|
724
+ line = line[/.*/]
725
+
726
+ if ! found
727
+ if line == parent
728
+ found = true
729
+ indent = (line[/^ */].length / 2) + 1
730
+ end
731
+ next
732
+ end
733
+
734
+ # Found
735
+
736
+ # Skip if blank
737
+ next if line.blank?
738
+
739
+ current_indent = line[/^ */].length / 2
740
+
741
+ # If indented exactly 2 under, add it
742
+ if current_indent == indent
743
+ candidate = "| #{line}\n"
744
+
745
+ # If child of candidate, append candidate if one needs appending
746
+ elsif current_indent == indent + 1
747
+ next if candidate.nil?
748
+ result << candidate
749
+ candidate = nil
750
+ # If indented less, stop
751
+ elsif current_indent < indent
752
+ break
753
+ end
754
+ end
755
+
756
+ result
757
+ end
758
+
759
+ # Goes through files in reverse order and deletes empty dirs
760
+ def self.select_next_file
761
+ $el.search_forward_regexp /[^\/]$/
762
+ $el.beginning_of_line
763
+ $el.skip_chars_forward " "
764
+ end
765
+
766
+ def self.select_previous_file
767
+ $el.search_backward_regexp /[^\/]$/
768
+ $el.beginning_of_line
769
+ $el.skip_chars_forward "\t"
770
+ end
771
+
772
+ # Draw tree, use dir of bookmark from user input
773
+ def self.ls options={}
774
+ dir = options[:dir]
775
+ # If no dir, do tree in current dir
776
+ dir ||= $el.elvar.default_directory
777
+
778
+ line = Line.value
779
+
780
+ Line << "\n" if line =~ /^>/ # If it's on a >... line, insert linebreak
781
+
782
+ # If to be in bar, go to the tree file in the bar
783
+ if options[:open_in_bar]
784
+ self.open_in_bar :ignore_prefix # Open tree
785
+ # If not on a blank line, go to the top
786
+ View.to_top unless line =~ /^$/
787
+ # If on line, add another blank
788
+ unless line =~ /^$/
789
+ View.insert "\n\n"; backward_char 2
790
+ end
791
+ end
792
+ dir = View.expand_path(dir) # Expand out ~
793
+ # If file, back up to dir
794
+ dir = Bookmarks.dir_only dir unless File.directory?(dir)
795
+
796
+ # If dir or recursive
797
+ if File.directory?(dir) || options[:recursive]
798
+ dir.sub!(/([^\/])$/, "\\1/") # Add slash on end if not there
799
+ end
800
+
801
+ name = dir[/.+\/(.+)\//, 1]
802
+
803
+ # Don't open new dir if not appropriate
804
+ if options[:open_in_bar] || options[:here]
805
+ # Don't upen buffer
806
+ else
807
+ View.to_buffer "*tree #{name}"
808
+ View.clear
809
+ View.dir = Bookmarks.dir_only dir
810
+ Notes.mode
811
+ $el.use_local_map $el.elvar.notes_mode_map
812
+ end
813
+
814
+ # If recursive
815
+ if options[:recursive]
816
+ left = $el.point
817
+ self.ls_here dir # Draw actual tree
818
+ right = $el.point
819
+ $el.goto_char left
820
+ self.select_next_file
821
+ # Start incremental search
822
+ Tree.search(:recursive => true, :left => left, :right => right)
823
+ #self.draw(dir)
824
+ else
825
+ # Insert with linebreak if file
826
+ if File.file?(dir)
827
+ #insert dir
828
+ View.insert self.filename_to_next_line(dir) # Add linebreak before filename
829
+ #insert dir.sub(/(.+)\//, "\\1/\n ") # Add linebreak before filename
830
+ open_line 1
831
+ #Tree.search
832
+ Line.to_words
833
+ Tree.search(:left => Line.left, :right => Line.left(2))
834
+ return
835
+ end
836
+
837
+ bullet = "+ "
838
+
839
+ bullet = "" if line =~ /^[ @]+$/
840
+ bullet = "@" if line =~ /^ +$/
841
+
842
+ View.insert "#{bullet}#{dir}\n"
843
+ $el.previous_line
844
+ self.dir options # Draw actual tree
845
+ end
846
+ end
847
+
848
+
849
+ def self.open_in_bar options={}
850
+ prefix = Keys.prefix :clear=>1
851
+
852
+ # If numeric prefix, open nth thing in tree
853
+ if prefix and prefix != :u and !(options[:ignore_prefix])
854
+ View.flash "- Don't know what this was supposed to do!"
855
+
856
+ # Remember original view
857
+ # start = $el.selected_window
858
+ # Open tree (ignoring prefix)
859
+ # self.open_in_bar# :ignore_prefix=>true
860
+ # Find nth file in tree
861
+ # View.to_highest
862
+
863
+ # prefix.times do
864
+ # re_search_forward "^ +[a-zA-Z0-9_.-]+$"
865
+ # end
866
+ # Go to next line if comment
867
+
868
+ # Line.next if Line.next_matches(/^ *\|/)
869
+ # Line.to_beginning
870
+ # self.open :ignore_prefix=>true
871
+ return
872
+ end
873
+
874
+ unless View.bar? # If already open, just go there
875
+ View.bar
876
+ end
877
+ View.to_nth 0
878
+ $el.find_file Bookmarks.expand("$t")
879
+
880
+ only_one_view_in_bar = prefix == :u
881
+ only_one_view_in_bar = ! only_one_view_in_bar if @@one_view_in_bar_by_default
882
+
883
+ unless only_one_view_in_bar # Unless u prefix, open $tl as well (under bar)
884
+
885
+ # If 2nd view isn't at left margin, open 2nd view
886
+ if View.left_edge(View.list[1]) != 0
887
+ View.create
888
+ end
889
+
890
+ View.to_nth 1
891
+
892
+ # If 2nd view isn't $f, open it
893
+ if $el.buffer_file_name( $el.window_buffer( View.list[1] ) ) != Bookmarks["$f"]
894
+ $el.find_file Bookmarks["$f"]
895
+ end
896
+
897
+ View.to_nth 0
898
+
899
+ end
900
+ end
901
+
902
+ # Creates tree snippet of text in file
903
+ def self.snippet options={}
904
+ txt = options[:txt] || View.selection
905
+ file = options[:file] || View.file
906
+
907
+ # Remove linebreak from end
908
+ txt = txt.sub(/\n\z/, "")
909
+ if file
910
+ txt = "#{File.dirname(file)}/\n - #{File.basename(file)}\n" +
911
+ txt.gsub(/^/, " | ").
912
+ gsub(/^ \| $/, " |") # Remove trailing spaces on blank lines
913
+ "#{txt}\n"
914
+ else
915
+ # "- From #{buffer_name}:\n" + txt.gsub(/^/, " #")
916
+ "- From #{View.name}:\n" + txt.gsub(/^/, " | ")
917
+ end
918
+ end
919
+
920
+ # Recursively display dir in tree # Insert dir contents at point (usually in existing tree)
921
+ def self.dir options={}
922
+ return Files.open_in_os(Tree.construct_path) if Keys.prefix == 0
923
+
924
+ Tree.plus_to_minus_maybe
925
+ Line.to_left
926
+ prefix = Keys.prefix
927
+ if prefix == 8 || prefix == "all"
928
+ self.dir_recursive
929
+ elsif prefix == "delete"
930
+ return self.delete_file
931
+ else
932
+ self.dir_one_level options
933
+ end
934
+ end
935
+
936
+ # Insert all files in dirs within a dir, and allow user to
937
+ # incremental search therein.
938
+ def self.dir_recursive
939
+ $el.beginning_of_line
940
+ line = Line.value
941
+ left = $el.point
942
+ indent = line[/^ */] + " " # Get indent
943
+ dir = Tree.construct_path
944
+
945
+ Line.next
946
+
947
+ # Get tree
948
+ t = self.new
949
+ dir.sub!(/\/$/, '')
950
+ t.traverse Bookmarks.expand(dir)
951
+
952
+ # Adjust indent
953
+ result_indent = t.res[/^ */] # Get indent of first line
954
+ View.insert t.res.gsub(/^#{result_indent}/, indent)
955
+
956
+ right = $el.point
957
+
958
+ $el.goto_char left
959
+ # isearch_forward
960
+ self.select_next_file
961
+ Tree.search(:recursive => true, :left => left, :right => right)
962
+ end
963
+
964
+ def self.dir_one_level options={}
965
+
966
+ Line.to_left
967
+ line = Line.value
968
+ indent = line[/^ */] + " " # Get indent
969
+
970
+ dir = Bookmarks.expand(Tree.construct_path)
971
+
972
+ remote = self.is_remote?(dir)
973
+ unless remote
974
+ # If C-2, just open in dired
975
+ if Keys.prefix == 2
976
+ # TODO: Open in 1st window
977
+ View.to_after_bar
978
+ $el.find_file dir
979
+ return
980
+ end
981
+ end
982
+
983
+ dirs, files = self.files_in_dir(dir, options) # Get dirs and files in it
984
+
985
+ if files.empty? && dirs.empty?
986
+ if ! File.exists? dir # If doesn't exist, show message
987
+ return Tree << "
988
+ > Directory '#{dir}' doesn't exist. Create it?
989
+ - @mkdir/
990
+ "
991
+ end
992
+ View.flash "- Directory is empty"#, :times=>2
993
+ Move.to_end
994
+ Notes.enter_junior
995
+ View << "- "
996
+ return
997
+ end
998
+
999
+ # Change path to proper indent
1000
+ dirs.collect!{|i| i.sub(/.*\/(.+)/, "#{indent}+ \\1/")}
1001
+
1002
+ # Change path to proper indent
1003
+ files.collect!{|i| i.sub(/.*\/(.+)/, "#{indent}+ \\1")}
1004
+
1005
+ Line.next
1006
+ left = $el.point
1007
+
1008
+ # Move .notes files to top
1009
+ files = files.select{|i| i =~ /\.notes$/} + files.select{|i| i !~ /\.notes$/}
1010
+
1011
+ both = options[:date_sort] || options[:size_sort] ?
1012
+ files + dirs : dirs + files
1013
+ View.insert(both.join("\n") + "\n")
1014
+ right = $el.point
1015
+ $el.goto_char left
1016
+
1017
+ Line.to_beginning
1018
+
1019
+ if options[:focus]
1020
+ Search.forward "^ +\\+ #{options[:focus]}$"
1021
+ Line.to_beginning
1022
+ Color.colorize :l
1023
+ end
1024
+
1025
+ Tree.search(:left=>left, :right=>right, :always_search=>true)
1026
+
1027
+ end
1028
+
1029
+ # def self.enter_snippet
1030
+ # start = selected_window
1031
+ # snippet = self.snippet
1032
+ # self.open_in_bar
1033
+
1034
+ # loc = Location.new
1035
+ # Line.to_left
1036
+ # #orig = $el.point
1037
+
1038
+ # # If at indented line
1039
+ # while(Line.matches(/^ /))
1040
+ # Line.previous
1041
+ # end
1042
+
1043
+ # # Check to see if it's the same file
1044
+ # if snippet[/.+\n.+\n/] == buffer_substring($el.point, Line.left(3))
1045
+ # Line.next 2
1046
+ # View.insert "#{snippet.sub(/.+\n.+\n/, '')}\n"
1047
+ # # Check to see if it's just in same dir
1048
+ # elsif snippet[/.+\n/] == buffer_substring(point, Line.left(2))
1049
+ # Line.next
1050
+ # View.insert "#{snippet.sub(/.+\n/, '')}\n"
1051
+ # else
1052
+ # View.insert "#{snippet}\n"
1053
+ # end
1054
+
1055
+ # loc.go
1056
+ # #goto_char orig
1057
+
1058
+ # select_window(start)
1059
+ # end
1060
+
1061
+ # Enter tree of selection at the spot (saved by AS).
1062
+ def self.enter_at_spot
1063
+ snippet = self.snippet
1064
+ Location.jump("0")
1065
+ View.insert snippet
1066
+ Location.save("0") # So any more will be entered after
1067
+ end
1068
+
1069
+ # Enter what's in clipboard with | to on the left margin, with appropriate indent
1070
+ def self.enter_quote txt=nil
1071
+
1072
+ prefix = Keys.prefix :clear=>1
1073
+
1074
+ # Skip forward if on heading
1075
+ Line.to_left
1076
+ if Line[/^\|/]
1077
+ Move.to_end
1078
+ $el.newline
1079
+ end
1080
+
1081
+ txt ||= Clipboard.get(0, :add_linebreak=>1)
1082
+
1083
+ dir = Tree.construct_path rescue nil
1084
+
1085
+ if self.dir? && self.handles? # If current line is path
1086
+ Tree.plus_to_minus_maybe
1087
+ indent = Line.indent
1088
+ dir = Tree.construct_path
1089
+ t = Clipboard.get("=")
1090
+ t = t.gsub(/^#{dir}/, '')
1091
+ if t.sub!(/\A\n/, '') # If no dir left, indent one over
1092
+ t.gsub!(/^ /, '')
1093
+ # t = t.gsub(/^ /, '')
1094
+ end
1095
+ Tree.add_pluses_and_minuses t, '-', '-'
1096
+ Line.next
1097
+ View.insert "#{t}\n".gsub(/^/, "#{indent} ")
1098
+ return
1099
+ end
1100
+
1101
+ # If empty line, just enter tree...
1102
+
1103
+ if Line.blank?
1104
+
1105
+ if prefix == :u || txt =~ /\A +[-+]?\|[-+ ]/ # If C-u or whole thing is quoted already, unquote
1106
+ txt = txt.grep(/\|/).join()
1107
+ return $el.insert(txt.gsub(/^ *[-+]?\|([-+ ]|$)/, "")) # Remove | ..., |+...., |<blank>, etc
1108
+ end
1109
+
1110
+ start = $el.point
1111
+ txt = Clipboard.get("=")
1112
+ indent = prefix || 0 # Indent prefix spaces, or 2
1113
+ txt = txt.gsub(/^/, " " * indent)
1114
+
1115
+ View.insert txt
1116
+ $el.set_mark(Line.left(2))
1117
+ $el.goto_char start
1118
+ return
1119
+ end
1120
+
1121
+ # Line has content, so indent under...
1122
+
1123
+ txt = txt.unindent # Unindent
1124
+ # TODO: don't unindent if up+?
1125
+
1126
+ indent = Line.indent # Get current indent
1127
+ on_quoted_line = Line.matches /^ +\|/
1128
+ Line.next
1129
+
1130
+ # Indent one level further unless on comment already
1131
+ unless on_quoted_line
1132
+ indent = "#{indent} "
1133
+ end
1134
+
1135
+ indent += " " * Keys.prefix_or_0 # If numeric prefix, add to indent
1136
+ txt = txt.sub /\n+\z/, '' # Remove last \n
1137
+
1138
+ # Quote unless already quoted
1139
+ quote = txt =~ /^\|/ ? '' : "| "
1140
+
1141
+ txt = txt.gsub /^/, "#{indent}#{quote}"
1142
+ txt = txt.gsub /^( *\|) $/, "\\1" # Remove blank lines
1143
+
1144
+ View.insert "#{txt}\n"
1145
+ end
1146
+
1147
+ def self.enter_as_search
1148
+ indent = Line.indent
1149
+ Move.to_end
1150
+ View.insert "\n#{indent} - ###{Clipboard["0"]}/"
1151
+ Launcher.launch
1152
+ end
1153
+
1154
+ # Remove the following lines indented more than the current one
1155
+
1156
+ # Expand if dir, or open if file
1157
+ def self.launch options={}
1158
+
1159
+ #Tree.plus_to_minus_maybe
1160
+ line = Line.value
1161
+
1162
+ indent = Line.indent
1163
+ list = nil
1164
+ path = Tree.construct_path # Get path
1165
+
1166
+ without_label = Line.without_label
1167
+ if Launcher.wrapper path # run files followed by slash as menus
1168
+ # Do nothing if it returned true
1169
+ elsif without_label =~ /^ *\$ / # $ shell command inline (sync)
1170
+ Console.launch :sync=>true
1171
+ elsif without_label =~ /^ *%( |$)/ # % shell command (async)
1172
+ Console.launch_async
1173
+ elsif without_label =~ /^ *&( |$)/ # % shell command in iterm
1174
+ Console.launch_async :iterm=>1
1175
+ elsif line =~ /^[^|\n]* (\*\*|##)/ # *foo or ## means do grep
1176
+ self.grep_syntax indent
1177
+ elsif self.dir? # if has slash - foo/ is a dir (if no | before)
1178
+
1179
+ # If is bookmark, delegate to wrapper / driller__
1180
+ # if without_label =~ /@?\$/
1181
+
1182
+ # # Use this instead
1183
+
1184
+ # Ol << "delegate to wrapper!"
1185
+ # return
1186
+ # end
1187
+ # Ol << "line: #{line.inspect}"
1188
+
1189
+ self.dir
1190
+ else
1191
+ self.open
1192
+ end
1193
+ end
1194
+
1195
+ # Grabs matching lines in file and starts hide search
1196
+ def self.outline_pattern extension=nil, options={}
1197
+ extension ||= View.extension
1198
+
1199
+ if extension == "rb"
1200
+ "^\s*(def|class|module|it|describe|create_table|context) "
1201
+ elsif extension == "rake"
1202
+ "^\\s*(task|def|class) "
1203
+ elsif extension == "js"
1204
+ "(^ *(function)| = function\\()"
1205
+ elsif extension =~ /^notes|deck$/
1206
+ "^[\\|>]( |$)"
1207
+ else
1208
+ "^[^ \\t\\n]"
1209
+ end
1210
+ end
1211
+
1212
+ #
1213
+ # Called by to+outline and enter+outline, (others?).
1214
+ #
1215
+ def self.enter_lines pattern=nil, options={}
1216
+ $xiki_no_search = false
1217
+
1218
+ # If prefix is 6, delegate to Git to enter methods by date
1219
+ if options[:prefix] == 6
1220
+ txt = Git.methods_by_date(Tree.path[-1]).join("\n")
1221
+ Tree.<< Tree.quote(txt), :no_slash=>1
1222
+ return
1223
+ end
1224
+
1225
+ # If dir, delegate to C-. (they meant to just open it)
1226
+ return Launcher.launch if self.dir?
1227
+
1228
+ Tree.plus_to_minus
1229
+
1230
+ if Line.blank? # If blank line, get bookmark and enter into current file
1231
+
1232
+ bm = Keys.input(:timed => true, :prompt => "Enter bookmark to show outline for: ")
1233
+ path = Bookmarks.expand(bm, :just_bookmark => true)
1234
+ path = File.expand_path(path)
1235
+ #Files.directory? Bookmarks.expand("h", :just_bookmark=>true)
1236
+
1237
+ # If it's a dir, delegate to Open Tree
1238
+ if path =~ /\/$/
1239
+ self.ls :here=>true, :dir => path
1240
+ return
1241
+ end
1242
+
1243
+ View.insert "- " + self.filename_to_next_line(path)
1244
+ $el.open_line 1
1245
+ end
1246
+ line = options[:path] || Line.value
1247
+ extension = line[/\.(\w+)$/, 1]
1248
+ if pattern.nil?
1249
+ return self.enter_lines(/#{self.outline_pattern extension}/, options)
1250
+ end
1251
+ Line.to_left
1252
+ path ||= options[:path] || Tree.construct_path # Get path
1253
+ path = Bookmarks.expand(path)
1254
+ indent = Line.indent # get indent
1255
+
1256
+ # If image, insert it
1257
+ return self.enter_image path if extension =~ /^(jpg|jpeg|png|gif)$/i
1258
+
1259
+ # Get matches from file
1260
+ matches = ""
1261
+ indent_more = options[:path] ? '' : ' '
1262
+
1263
+ if path =~ /^\/\w+@/
1264
+ contents = Remote.file_contents path
1265
+ Tree.under contents, :escape=>'| ', :no_slash=>1
1266
+ return
1267
+ end
1268
+
1269
+ # Adjust so it finds if we're on the line
1270
+ current_line = options[:current_line] + 1 if options[:current_line]
1271
+
1272
+ line_found, matches_count, i = nil, 0, 0
1273
+
1274
+ if ! File.exists? path
1275
+ self.file_not_found_from_template(path)
1276
+ return
1277
+ end
1278
+
1279
+ IO.foreach(path) do |line|
1280
+ i+=1
1281
+ line.sub!(/[\r\n]+$/, '')
1282
+
1283
+ if current_line && line_found.nil?
1284
+ line_found = matches_count if i == current_line
1285
+ end
1286
+
1287
+ next unless line =~ pattern
1288
+ line = line == "" ? "" : " #{line}"
1289
+ line.sub! /^ > $/, ' >'
1290
+ matches << "#{indent}#{indent_more}|#{line}\n"
1291
+
1292
+ matches_count+=1
1293
+ end
1294
+
1295
+ Tree.insert_quoted_and_search matches, :line_found=>line_found
1296
+ end
1297
+
1298
+ def self.enter_image image
1299
+ tmp_dir = "/tmp/insert_image"
1300
+ Dir.mkdir tmp_dir if ! File.exists? tmp_dir
1301
+
1302
+ width, height = Console.sync("identify \"#{image}\"").match(/(\d+)x(\d+)/)[1..2]
1303
+ max = 300
1304
+ if width.to_i > max || height.to_i > max
1305
+ dest = tmp_dir+"/"+File.basename(image).sub(".", "_#{max}.")
1306
+ Console.sync %`convert "#{image}" -resize #{max}x#{max} "#{dest}"`, :dir=>"/tmp"
1307
+ else
1308
+ dest = image
1309
+ end
1310
+
1311
+ Image.>> dest
1312
+
1313
+ Line.previous
1314
+ Line.to_beginning
1315
+
1316
+ nil
1317
+ end
1318
+
1319
+
1320
+ def self.file_not_found_from_template path
1321
+
1322
+ if path =~ /\/menus\/(.+)\.rb$/ # "
1323
+ name = $1
1324
+ View.flash "- File doesn't exist, start with this...", :times=>4
1325
+ Xiki.dont_search
1326
+ txt = File.read "#{Xiki.dir}/etc/templates/menu_template.rb"
1327
+ txt.gsub!(/\{\{(.+?)\}\}/) { eval $1 } # Expand {{these}}
1328
+ return Tree.<< txt, :escape=>"| ", :no_slash=>1
1329
+
1330
+ elsif path =~ /^#{File.expand_path("~/menus")}\/(.+)\.menu$/ # "
1331
+ View.flash "- File doesn't exist, start with this...", :times=>4
1332
+ Xiki.dont_search
1333
+ txt = File.read "#{Xiki.dir}/etc/templates/menu_template.menu"
1334
+ return Tree.<< txt, :escape=>"| ", :no_slash=>1
1335
+ end
1336
+
1337
+ name, extension = path.match(/([^\/]+)\.(.+)$/)[1..2] rescue [nil, nil]
1338
+
1339
+ [File.expand_path("~/xiki_stuff/templates/"), Bookmarks["$x/etc/templates"]].each do |dir|
1340
+ file = "#{dir}/template.#{extension}"
1341
+ next unless File.exists? file
1342
+ View.flash "- File doesn't exist, start with this...", :times=>4
1343
+ txt = File.read file
1344
+ txt.gsub!(/\{\{(.+?)\}\}/) { eval $1 } # Expand {{these}}
1345
+ return Tree.<< txt, :escape=>"| ", :no_slash=>1
1346
+ end
1347
+
1348
+ # If no templates matched
1349
+ View.flash "- File doesn't exist", :times=>1
1350
+ Tree.<< "|", :no_slash=>1
1351
+ Move.to_end
1352
+ View << ' '
1353
+
1354
+ end
1355
+
1356
+ # Mapped to shortcuts that displays the trees
1357
+ def self.tree options={}
1358
+ $xiki_no_search = false
1359
+ bm = Keys.input(:timed=>true, :prompt=>"Enter bookmark to show tree of: ")
1360
+ options.merge!(:focus=>View.file_name) if bm == "."
1361
+ dir = Keys.bookmark_as_path(:bm=>bm)
1362
+
1363
+ return if dir == :bookmark_doesnt_exist
1364
+
1365
+ dir = "/" if dir == :slash
1366
+
1367
+ dir = Bookmarks.dir_only(dir) if options[:recursive]
1368
+ options.merge!(:dir=>dir)
1369
+
1370
+ # If U prefix, open in bar
1371
+ if Keys.prefix_u?
1372
+ self.ls options.merge(:open_in_bar=>true)
1373
+ # Otherwise, open in new buffer
1374
+ else
1375
+ self.ls options
1376
+ end
1377
+ end
1378
+
1379
+ def self.parent
1380
+ return nil unless Line[/^ /]
1381
+
1382
+ orig = View.cursor # Store original location
1383
+
1384
+ Tree.to_parent
1385
+ parent = Line.without_label
1386
+
1387
+ View.cursor = orig
1388
+ parent
1389
+ end
1390
+
1391
+ # Returns a regexp to match the acronym
1392
+ def self.is_remote? path
1393
+ return path =~ /^\/\w+@/
1394
+ end
1395
+
1396
+ # Returns the files in the dir
1397
+ def self.files_in_dir dir, options={}
1398
+ if self.is_remote?(dir)
1399
+ self.remote_files_in_dir(dir)
1400
+ else
1401
+
1402
+ all = Dir.glob("#{dir}*", File::FNM_DOTMATCH).
1403
+ select {|i| i !~ /\/\.(\.*|svn|git)$/}. # Exclude some dirs (exclude entensions here too?)
1404
+ select {|i| i !~ /\/\.#/}.sort
1405
+
1406
+ if options[:date_sort]
1407
+ all = all.sort{|a,b| File.mtime(b) <=> File.mtime(a)}
1408
+ elsif options[:size_sort]
1409
+ all = all.sort{|a,b| File.size(b) <=> File.size(a)}
1410
+ end
1411
+
1412
+ dirs = all.select{|i| FileTest.directory?(i)}#.sort
1413
+ files = all.select{|i| FileTest.file?(i)}#.sort
1414
+ [dirs, files]
1415
+ end
1416
+ end
1417
+
1418
+ def self.remote_file_contents file
1419
+ path, file = file.match(/(.+\/)(.+)/)[1..2]
1420
+ Remote.dir path, file # Delegate to Remote.dir
1421
+ end
1422
+
1423
+ def self.remote_files_in_dir dir
1424
+ res = Remote.dir(dir)
1425
+ res.map!{|i| "#{dir}#{i}"}
1426
+ [res.select{|i| i =~ /\/$/}.map{|i| i.sub(/\/$/, '')}, res.select{|i| i !~ /\/$/}]
1427
+ end
1428
+
1429
+ def self.url_from_path verb, path
1430
+ server, path = path.match(/([\w.-]+)(\/.*)/)[1..2]
1431
+ "http://#{server}:7433/#{verb}#{path}"
1432
+ end
1433
+
1434
+ def self.indentify_path path
1435
+ result = ""
1436
+ indent = ""
1437
+ path.each do |i|
1438
+ result << "#{indent}#{i}\n"
1439
+ indent += " "
1440
+ end
1441
+ result
1442
+ end
1443
+
1444
+ def self.tree_to_paths tree
1445
+ # TODO: implement
1446
+ end
1447
+
1448
+ def self.filename_to_next_line path
1449
+ path.sub(/(.+)\//, "\\1/\n - ") # Add linebreak before filename
1450
+ end
1451
+
1452
+ # If cursor on a tree, returns it, otherwise return path of current file
1453
+ def self.tree_path_or_this_file dir_only=false
1454
+
1455
+ # If in tree, use that dir
1456
+ path = self.handles? && Keys.prefix != :u ?
1457
+ Tree.construct_path :
1458
+ View.file
1459
+
1460
+ # path = Files.just_dir(path) if dir_only
1461
+ if dir_only
1462
+ path = path =~ /\/$/ ? path : File.dirname(path)+"/"
1463
+ end
1464
+ path
1465
+ end
1466
+
1467
+ def self.do_create_dir
1468
+
1469
+ path = self.tree_path_or_this_file :dir_only
1470
+
1471
+ `mkdir -p "#{path}"`
1472
+ View.flash "- Created: #{path}"
1473
+ end
1474
+
1475
+ # Indent txt to be one level lower than current line
1476
+ def self.one_view_in_bar_by_default= to
1477
+ @@one_view_in_bar_by_default = to
1478
+ end
1479
+
1480
+ def self.copy_path options={}
1481
+ Effects.blink :what=>:line
1482
+
1483
+ # Return dir of view's file if at left margin, U, or not ^[|@-]
1484
+ if Line !~ /^ +[|@+-]/ || Keys.prefix_u
1485
+ path = View.file
1486
+ else
1487
+ path = Xiki.trunk.last # Grab from wiki tree
1488
+
1489
+ path.sub! /\/$/, '' if Line.value !~ /\/$/ # If current line doesn't have trailing slash, remove it
1490
+ end
1491
+
1492
+ View.flash "- copied: #{path}", :times=>2
1493
+ return Clipboard["0"] = path
1494
+ end
1495
+
1496
+ # Adds extra line if we're at the end of the file.
1497
+ # If there's no linebreak, it causes weird errors.
1498
+ def self.extra_line_if_end_of_file
1499
+ if Line.right == View.bottom
1500
+ Line.to_right
1501
+ $el.open_line(1)
1502
+ end
1503
+ end
1504
+
1505
+ def self.add_slash_maybe dir
1506
+ dir =~ /\/$/ ? dir : "#{dir}/"
1507
+ end
1508
+
1509
+ def self.grep_syntax indent
1510
+ Tree.plus_to_minus_maybe
1511
+ dir = Tree.construct_path
1512
+ raw = Tree.construct_path(:raw => true, :list => true)
1513
+
1514
+ files = contents = nil
1515
+ if raw.join('') =~ /\*\*(.+)##(.+)/ # *foo means search in files
1516
+ files, contents = $1, $2.sub!(/\/$/, '')
1517
+ elsif raw.last =~ /\*\*(.+)/ # *foo means search in files
1518
+ files = $1
1519
+ elsif raw.last =~ /##(.+)/ # ##foo means search in filenames
1520
+ contents = $1.sub!(/\/$/, '')
1521
+ if raw[-2] =~ /\*\*(.+)/ # If prev is **, use it
1522
+ files = $1
1523
+ elsif raw[-2] =~ /(.+[^\/])$/ # If prev line is file, just show matches
1524
+ contents = Regexp.new(contents, Regexp::IGNORECASE)
1525
+ list = self.outline_search(Bookmarks.expand(dir), contents, " ")
1526
+ end
1527
+ end
1528
+ files.sub!(/\/$/, '') if files
1529
+ options = {:raw => true}
1530
+ options.merge!({:files => files}) if files
1531
+
1532
+ unless list # If not already gotten
1533
+ Search.append_log dir, "- ###{contents}/"
1534
+ list = self.grep dir, contents, options
1535
+ list.shift # Pull off first dir, so they'll be relative
1536
+ end
1537
+ Line.to_next
1538
+ left = $el.point
1539
+ tree = list.join("\n") + "\n"
1540
+ View.insert tree.gsub(/^/, indent)
1541
+ right = $el.point
1542
+ $el.goto_char left
1543
+ if Line.matches(/^\s*$/) # Do nothing
1544
+ elsif Line.matches(/^\s+\|/)
1545
+ if Search.outline_goto_once # If we want to go back to a line
1546
+ Line.next Search.outline_goto_once
1547
+ Search.outline_goto_once = nil
1548
+ end
1549
+
1550
+ Tree.search :left=>left, :right=>right
1551
+ elsif contents # If we searched for something (and it wasn't just the dir)
1552
+ Move.to_quote
1553
+ Tree.search :left=>left, :right=>right, :recursive_quotes=>true
1554
+ else
1555
+ Move.to_junior
1556
+ Tree.search :left=>left, :right=>right, :recursive=>true
1557
+ end
1558
+ end
1559
+
1560
+ def self.move_dir_to_junior
1561
+ Keys.prefix_times.times do
1562
+ orig = Location.new
1563
+ indent = Line.indent
1564
+ txt = View.paragraph(:start_here => true, :delete => true)
1565
+ View.insert self.move_dir_to_junior_internal(txt, Keys.prefix_u?)
1566
+
1567
+ orig.go
1568
+
1569
+ end
1570
+ end
1571
+
1572
+ def self.move_dir_to_junior_internal txt, prefix_u=nil
1573
+
1574
+ first, rest = txt.match(/(.+?\n)(.+)/m)[1..2]
1575
+ indent = first[/ */]
1576
+
1577
+ # Split off any text not under branch (of first line)
1578
+ # Text indented the same as the first line (and after)
1579
+ rest_and_siblings = rest.match(/(.*?)^(#{indent}[^ \n].*)/m)
1580
+ rest, siblings = rest_and_siblings ? rest_and_siblings[1..2] : [rest, ""]
1581
+
1582
+ # What does this do??
1583
+ if prefix_u
1584
+ rest.sub! /^\s*[+-] /, ''
1585
+ rest.gsub! /^ /, ''
1586
+ "#{first}#{rest}#{siblings}"
1587
+ else
1588
+
1589
+ first.sub! /(.+\/)(.+\/)$/, "\\1\n#{indent} - \\2"
1590
+ rest.gsub! /^/, " "
1591
+ "#{first}#{rest}#{siblings}"
1592
+ end
1593
+ end
1594
+
1595
+ # Returns whether line is a dir (ends with "/")
1596
+ def self.dir? txt=nil
1597
+ txt ||= Line.value
1598
+ txt =~ /^[^,|\n]*\/$/
1599
+ end
1600
+
1601
+ def self.delete_file
1602
+
1603
+ # in_file_being_deleted = Line !~ /^[ +-]/ || Keys.prefix_u
1604
+
1605
+ # If not on tree, must want to delete this file
1606
+ # if in_file_being_deleted
1607
+ # dest_path = View.file
1608
+ # else
1609
+ dest_path = Tree.construct_path
1610
+ # end
1611
+
1612
+ dest_path = View.expand_path dest_path
1613
+
1614
+ return View.flash("- File doesn't exist: #{dest_path}", :times=>5) if ! File.exists?(dest_path)
1615
+
1616
+ View.flash "- Delete file for sure?"
1617
+ answer = Keys.input :chars=>1, :prompt=>"For sure delete?: #{dest_path}" #"
1618
+
1619
+ return unless answer =~ /y/i
1620
+
1621
+ executable = File.directory?(dest_path) ? "rm -r" : "rm"
1622
+ command = "#{executable} \"#{dest_path}\""
1623
+
1624
+ result = Console.run command, :sync=>true
1625
+ if (result||"").any?
1626
+ View.beep
1627
+ View.message "#{result}"
1628
+ return
1629
+ end
1630
+
1631
+ # return View.kill if in_file_being_deleted
1632
+
1633
+ Tree.kill_under
1634
+ Effects.glow :fade_out=>1
1635
+ Line.delete
1636
+ Line.to_beginning
1637
+ end
1638
+
1639
+ def self.move_latest_screenshot_to dest_path, dest_dir
1640
+ desktop = Bookmarks['$dt']
1641
+
1642
+ latest_screenshot = `ls -t #{desktop} "Screen shot*"`[/Screen shot .*/]
1643
+
1644
+ return View.message("No screenshot found.") if latest_screenshot.empty?
1645
+
1646
+ command = "mv \"#{desktop}#{latest_screenshot}\" \"#{dest_path}\""
1647
+
1648
+ result = Console.run command, :sync=>true
1649
+ View.message result
1650
+ end
1651
+
1652
+ def self.move_to
1653
+ self.copy_to :move=>1
1654
+ end
1655
+
1656
+ def self.copy_current_file_to options={}
1657
+
1658
+ return View.beep '- Save the file first!' if View.modified?
1659
+
1660
+ # Go from current file to bookmark
1661
+ source_path = View.file
1662
+ View.flash "- to which bookmark?"
1663
+ verb = options[:move] ? "Move" : "Copy"
1664
+ dest_path = Keys.bookmark_as_path :prompt=>"#{verb} current file to which bookmark? "
1665
+ return if ! dest_path.is_a? String
1666
+ # dest_path = File.dirname dest_path
1667
+
1668
+ dest_path = Bookmarks[dest_path]
1669
+
1670
+ executable = options[:move] ? "mv" : "cp -r"
1671
+
1672
+ command = "#{executable} \"#{source_path}\" \"#{dest_path}\""
1673
+
1674
+ result = Console.run command, :sync=>true
1675
+
1676
+ return View.beep result if (result||"").any? # If output isn't as expected, beep and show error
1677
+
1678
+ if options[:move]
1679
+ View.kill
1680
+ View.open "#{dest_path}#{File.basename source_path}"
1681
+ verb = "Moved"
1682
+ else
1683
+ verb = "Copied"
1684
+ end
1685
+
1686
+ View.flash "- #{verb} to: #{dest_path}"
1687
+ end
1688
+
1689
+ def self.copy_to options={}
1690
+ prefix = options[:prefix] || Keys.prefix
1691
+ Keys.clear
1692
+
1693
+ # Error if not in a file tree
1694
+
1695
+ return self.copy_current_file_to options if ! FileTree.handles?
1696
+
1697
+ dest_path = Tree.construct_path
1698
+
1699
+ dest_dir = dest_path.sub(/(.+\/).*/, "\\1")
1700
+
1701
+ if prefix == 2
1702
+ self.move_latest_screenshot_to dest_path, dest_dir
1703
+ return View.flash "- Moved the latest screenshot to here!"
1704
+ end
1705
+
1706
+ arg = options[:move] ? {:delete=>1} : {}
1707
+ source_path = Tree.dir_at_spot arg
1708
+
1709
+ stem = File.basename source_path # Cut of path of source
1710
+ indent = Line.indent
1711
+
1712
+ dest_is_dir = dest_path =~ /\/$/
1713
+ source_is_dir = source_path =~ /\/$/
1714
+
1715
+ if dest_is_dir # If dest is a dir, insert junior
1716
+ Line.next
1717
+ indent << " "
1718
+ dest_stem = stem
1719
+
1720
+ else # If file, use as dest
1721
+ dest_stem = Line.without_label
1722
+ end
1723
+ executable = options[:move] ? "mv" : "cp -r"
1724
+
1725
+ # Add ".1" if prefix is 1
1726
+ if prefix == 1
1727
+ dest_stem << '.1'
1728
+ end
1729
+
1730
+ command_dest_stem = dest_stem
1731
+
1732
+ if source_path =~ /\/$/ # If source is dir
1733
+ source_path.sub! /\/$/, '' # Remove slash, so it copies dir, not contents
1734
+ command_dest_stem = "" # put into, not replace
1735
+ end
1736
+
1737
+ command = "#{executable} \"#{source_path}\" \"#{dest_dir}#{command_dest_stem}\""
1738
+
1739
+ result = Console.run command, :sync=>true
1740
+
1741
+ if (result||"").any? # If output isn't as expected, beep and show error
1742
+ View.beep
1743
+ View.message "#{result}"
1744
+ return
1745
+ end
1746
+
1747
+ dest_stem << "/" if source_is_dir
1748
+
1749
+ Line.to_left
1750
+ if dest_is_dir || prefix == 1 # If it's a file, we didn't delete it
1751
+ Line.next if prefix == 1
1752
+ View.insert "#{indent}+ #{dest_stem}\n", :dont_move=>true
1753
+ Line.to_beginning
1754
+ Effects.glow :fade_in=>1
1755
+ Line.previous if prefix == 1
1756
+ end
1757
+ end
1758
+
1759
+ def self.rename_file
1760
+ # If dired mode, use wdired
1761
+ return $el.wdired_change_to_wdired_mode if $el.elvar.major_mode.to_s == "dired-mode"
1762
+
1763
+ column = View.column
1764
+
1765
+ if ! Line[/^ *[+-] /] # Error if not indented and ^/- /
1766
+ View.beep
1767
+ return View.message "TODO: implement renaming current file?"
1768
+ end
1769
+ source_path = Tree.construct_path
1770
+ is_dir = source_path =~ /\/$/
1771
+ source_path.sub! /\/$/, ''
1772
+
1773
+ new_name = Keys.input(
1774
+ :prompt=>"Rename #{source_path} to what?: ",
1775
+ :initial_input=>(Keys.prefix_u? ? File.basename(source_path) : "")
1776
+ )
1777
+
1778
+ dest_path = "#{source_path.sub(/(.+\/).+/, "\\1#{new_name}")}"
1779
+
1780
+ command = "mv \"#{source_path}\" \"#{dest_path}\""
1781
+
1782
+ Console.run command, :sync=>true
1783
+
1784
+ indent = Line.indent
1785
+ Effects.glow :fade_out=>1
1786
+ Line.delete
1787
+ View.insert((is_dir ? "#{indent}- #{new_name}/\n" : "#{indent}+ #{new_name}\n"), :dont_move=>true)
1788
+
1789
+ View.column = column
1790
+ Effects.glow :fade_in=>1
1791
+ end
1792
+
1793
+ def self.open_as_upper where=false
1794
+ orig_u = Keys.prefix_u
1795
+ view = View.current
1796
+ path = Tree.construct_path(:list=>true)
1797
+
1798
+ if where == :lowest
1799
+ Move.to_window 9
1800
+ else
1801
+ View.to_upper
1802
+ end
1803
+ was_at_top = View.start == $el.point
1804
+ $el.split_window_vertically
1805
+
1806
+ View.next if where # :lowest or :second
1807
+
1808
+ if was_at_top
1809
+ where ? View.previous : View.next
1810
+ $el.recenter 0
1811
+ where ? View.next : View.previous
1812
+ end
1813
+
1814
+ self.open :path=>path, :same_view=>true
1815
+ View.to_window(view) if orig_u
1816
+ end
1817
+
1818
+ def self.to_outline
1819
+ prefix = Keys.prefix :clear=>true
1820
+ args = [View.txt, View.line] if prefix == :u || prefix == :-
1821
+
1822
+ current_line = Line.number
1823
+
1824
+ path = View.file
1825
+ View.to_buffer("*tree outline")
1826
+ View.clear; Notes.mode
1827
+ if path
1828
+ dir, file = path.match(/(.+\/)(.+)/)[1..2]
1829
+ txt = "- #{dir}\n - #{file}\n"
1830
+ View.insert txt
1831
+ View.to_top
1832
+ self.select_next_file
1833
+ else
1834
+ View.insert "- buffer/\n"
1835
+ View.to_top
1836
+ end
1837
+
1838
+ case prefix
1839
+ when 2 # Prompt to add @... menu under file
1840
+ Move.to_end
1841
+ Xiki.insert_menu
1842
+ when 4 # Get ready to run $... shell command on file
1843
+ $el.delete_char(1)
1844
+ View << "$ "
1845
+ ControlLock.disable
1846
+ when 6 # List methods by date (use 'git blame')
1847
+ self.enter_lines nil, :prefix=>6
1848
+ when 7
1849
+ Move.to_end
1850
+ View << "\n @info"
1851
+ Launcher.launch
1852
+ when 8
1853
+ Launcher.enter_all
1854
+ when 0
1855
+ # Just show path if 0+...
1856
+ when :- # Just show # foo... comments...
1857
+ self.enter_lines(/(^ *def |(^| )# .+\.\.\.$)/, :current_line=>current_line)
1858
+ when 6 # Just show # foo... comments...
1859
+ self.enter_lines(/(^ *def |self\.)/, :current_line=>current_line)
1860
+ when :u
1861
+ txt, line = Search.deep_outline *args
1862
+ Tree.<< txt, :line_found=>line, :escape=>'| ', :no_slash=>1
1863
+ else
1864
+ self.enter_lines nil, :current_line=>current_line
1865
+ end
1866
+
1867
+ end
1868
+
1869
+ def self.skip_dirs dir, skip_these
1870
+ dir = Bookmarks[dir].sub /\/$/, ''
1871
+ (@skip ||= {})[dir] = skip_these
1872
+ end
1873
+
1874
+ end
1875
+ FileTree.define_styles