xiki 0.5.0a

Sign up to get free protection for your applications and to get access to all the features.
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,1351 @@
1
+ require 'effects'
2
+ require 'requirer'
3
+
4
+ require 'xiki'
5
+
6
+ Requirer.require_gem 'activesupport', :name2=>'active_support/ordered_hash'
7
+ Requirer.require_gem 'httparty', :optional=>1 # Not super-important
8
+ Requirer.require_gem 'haml', :optional=>1
9
+
10
+ class Launcher
11
+
12
+ CLEAR_CONSOLES = [
13
+ "*ol",
14
+ "*output - tail of /tmp/ds_ol.notes",
15
+ "*visits - tail of /tmp/visit_log.notes",
16
+ "*console app",
17
+ ]
18
+
19
+ MENU_DIRS = [
20
+ "#{Xiki.dir}menus",
21
+ File.expand_path("~/menus"),
22
+ ]
23
+
24
+ @@log = File.expand_path("~/.emacs.d/menu_log.notes")
25
+
26
+ # Use @launcher/options/show or launch/ to enable.
27
+ # Look in /tmp/output.notes
28
+ @@just_show = false
29
+
30
+ @@launchers ||= ActiveSupport::OrderedHash.new
31
+ @@launchers_procs ||= []
32
+ @@launchers_parens ||= {}
33
+ @@menus ||= [{}, {}] # [.menu files, .rb files]
34
+
35
+ def self.menus
36
+ @@menus
37
+ end
38
+
39
+ def self.menu_keys
40
+ (@@menus[0].keys + @@menus[1].keys).sort.uniq #.select do |possibility|
41
+ end
42
+
43
+ def self.menu
44
+ %`
45
+ - .setup/
46
+ > Toggle Temporarily just showing the launcher that matched
47
+ - .show or launch/
48
+ - docs/
49
+ > Summary
50
+ Launcher is the class that handles "launching" things (double-clicking on a
51
+ line, or typing Ctrl-enter).
52
+
53
+ > See
54
+ @launcher/api/
55
+ - api/
56
+ > Open menu in new buffer
57
+ @ Launcher.open "computer"
58
+
59
+ > Insert monu
60
+ @ Launcher.insert "computer" # Assumes you're on a blank line
61
+
62
+ > Invoke (used behind the scenes?)
63
+ @ p Launcher.invoke 'Computer', 'computer/ip/'
64
+ `
65
+ end
66
+
67
+ def self.show_or_launch
68
+ @@just_show = ! @@just_show
69
+ View.flash "- Will #{@@just_show ? 'just show' : 'actually launch'}!"
70
+ end
71
+
72
+ def self.last path=nil, options={}
73
+ path = path.sub /^last\/?/, '' if path
74
+
75
+ paths = IO.readlines self.log_file
76
+
77
+ # If nothing passed, just list all roots
78
+
79
+ if path.blank?
80
+ paths.map!{|o| o.sub /\/.+/, '/'} # Cut off after path
81
+ paths = paths.reverse.uniq
82
+ paths.delete "- #{options[:omit]}/\n" if options[:omit]
83
+ return paths.join
84
+ end
85
+
86
+ # Root passed, so show all matches
87
+
88
+ paths = paths.select{|o| o =~ /^- #{Notes::LABEL_REGEX}#{path}\/./}
89
+
90
+ bullet = options[:quoted] ? "|" : "-"
91
+
92
+ if options[:exclude_path]
93
+ paths.each{|o| o.sub! /^- (#{Notes::LABEL_REGEX})#{path}\//, "#{bullet} \\1"}
94
+ paths = paths.select{|o| o != "#{bullet} "}
95
+ else
96
+ paths = paths.map{|o| o.sub /^- #{Notes::LABEL_REGEX}/, '\\0@'}
97
+ end
98
+ paths = paths.reverse.uniq
99
+ paths.delete_if{|o| o == "| \n"}
100
+ paths.join
101
+ end
102
+
103
+ def self.log
104
+
105
+ lines = IO.readlines self.log_file
106
+
107
+ # If parent, narrow down to just it
108
+ trunk = Xiki.trunk
109
+ if trunk.length > 1 && trunk[-2] != "menu/history" # Show all if under this menu
110
+ lines = lines.select {|o| o.start_with? "- #{trunk[-2]}"}
111
+ end
112
+
113
+ lines.reverse.uniq.map{|o| o.sub /^- /, '<< '}.join
114
+ end
115
+
116
+ def self.log_file
117
+ @@log
118
+ end
119
+
120
+ def self.add *args, &block
121
+ arg = args.shift
122
+
123
+ raise "Launcher.add called with no args and no block" if args == [nil] && block.nil?
124
+
125
+ if arg.is_a? Regexp # If regex, add
126
+ @@launchers[arg] = block
127
+ elsif arg.is_a? Proc # If proc, add to procs
128
+ @@launchers_procs << [arg, block]
129
+ elsif arg.is_a?(String)
130
+ self.add_menu arg, args[0], block
131
+ else
132
+ raise "Don't know how to launch this"
133
+ end
134
+ end
135
+
136
+ def self.add_menu root, hash, block
137
+ if hash.nil? && block # If just block, just define
138
+ return @@menus[1][root] = block
139
+ elsif hash.is_a?(Hash) && hash[:menu_file] && block
140
+ return @@menus[0][root] = block
141
+ end
142
+
143
+ # If just root, we'll use use class with that name
144
+ if block.nil? && (hash.nil? || hash[:class])
145
+ clazz = hash ? hash[:class] : root
146
+ clazz.sub!(/(\w+)/) {TextUtil.snake_case $1} if hash
147
+ self.add root do |path|
148
+ # Make class me camel case, and change Launcher.invoke to Menu.call
149
+ Launcher.invoke clazz, path
150
+ end
151
+ return
152
+ end
153
+
154
+ menu = hash[:menu]
155
+ if menu
156
+ if menu =~ /\A\/.+\.\w+\z/ # If it's a file (1 line and has extension)
157
+ require_menu menu
158
+ return
159
+ elsif menu =~ /\A[\w \/-]+\z/ # If it's a menu to delegate to
160
+ self.add root do |path|
161
+ Menu.call menu, Tree.rootless(path)
162
+ end
163
+ return
164
+ end
165
+
166
+ self.add root do |path| # If text of the actual menu
167
+ # Different from Menu[...] or .drill?
168
+ Tree.children menu, Tree.rootless(path)
169
+ end
170
+ return
171
+ end
172
+
173
+ raise "Don't know how to deal with: #{root}, #{hash}, #{block}"
174
+ end
175
+
176
+ def self.launch_or_hide options={}
177
+ # If no prefixes and children exist, delete under
178
+ if ! Keys.prefix and ! Line.blank? and Tree.children?
179
+ Tree.minus_to_plus
180
+ Tree.kill_under
181
+ return
182
+ end
183
+
184
+ # Else, launch
185
+ self.launch options
186
+ end
187
+
188
+ def self.hide
189
+ Tree.kill_under
190
+ end
191
+
192
+ # Call the appropriate launcher if we find one, passing it line
193
+ def self.launch options={}
194
+
195
+ # Add linebreak at end if at end of file and none
196
+ Line.<<("\n", :dont_move=>1) if Line.right == View.bottom
197
+
198
+ Tree.plus_to_minus unless options[:leave_bullet]
199
+
200
+ Line.sub! /^\.$/, './'
201
+ Line.sub! /^~$/, '~/'
202
+
203
+ # Maybe don't blink when in $__small_menu_box!"
204
+ Effects.blink(:what=>:line) if options[:blink]
205
+ line = options[:line] || Line.value # Get paren from line
206
+ label = Line.label(line)
207
+
208
+ if line =~ /^ *@$/
209
+ matches = Launcher.menu_keys
210
+ Tree.<< matches.sort.map{|o| "<< #{o.sub '_', ' '}/"}.join("\n"), :no_slash=>1
211
+ return
212
+ end
213
+
214
+ # Special hooks for specific files and modes
215
+ return if self.file_and_mode_hooks
216
+
217
+ $xiki_no_search = options[:no_search] # If :no_search, disable search
218
+
219
+ is_root = false
220
+
221
+ if line =~ /^( *)[+-] [^\n\(]+?\) (.+)/ # Split off label, if there
222
+ line = $1 + $2
223
+ end
224
+ if line =~ /^( *)[+-] (.+)/ # Split off bullet, if there
225
+ line = $1 + $2
226
+ end
227
+ if line =~ /^ *@ ?(.+)/ # Split off @ and indent if @ exists
228
+ is_root = true
229
+ line = $1
230
+ end
231
+
232
+ # Special case to turn launchers back on
233
+ return self.show_or_launch if line == "launcher/setup/show or launch/"
234
+
235
+ @@launchers.each do |regex, block| # Try each potential regex match
236
+ # If we found a match, launch it
237
+ if line =~ regex
238
+ group = $1
239
+
240
+ # Run it
241
+ if @@just_show
242
+ Ol << "- regex: #{regex.to_s}\n- group: #{group}"
243
+ else
244
+
245
+ begin
246
+ block.call line
247
+ rescue RelinquishException
248
+ next # They didn't want to handle it, keep going
249
+ rescue Exception=>e
250
+ Tree.<< CodeTree.draw_exception(e, block.to_ruby), :no_slash=>true
251
+ end
252
+
253
+ end
254
+ $xiki_no_search = false
255
+ return true
256
+ end
257
+ end
258
+
259
+ # If current line is indented and not passed recursively yet, try again, passing tree
260
+
261
+ if Line.value =~ /^ / && ! options[:line] && !is_root # If indented, call .launch recursively
262
+
263
+ # Use Xiki.branch here? (breaks up by @'s)
264
+
265
+ # merge together (spaces if no slashes) and pass that to launch
266
+
267
+ list = Tree.construct_path :list=>true, :ignore_ol=>1 # Get path to pass to procs, to help them decide
268
+
269
+ found = list.index{|o| o =~ /^@/} and list = list[found..-1] # Remove before @... node if any
270
+ merged = list.map{|o| o.sub /\/$/, ''}.join('/')
271
+ merged << "/" if list[-1] =~ /\/$/
272
+
273
+ # Recursively call again with full path
274
+ return self.launch options.merge(:line=>merged)
275
+
276
+ # What was this doing, did we mean to only pass on :no_search??
277
+ # return self.launch options.slice(:no_search).merge(:line=>merged)
278
+ end
279
+
280
+ if self.launch_by_proc # Try procs (currently all trees)
281
+ return $xiki_no_search = false
282
+ end
283
+
284
+ # If nothing found so far, don't do anything if...
285
+ if line =~ /^\|/
286
+ View.beep
287
+ return View.message "Don't know what to do with this line"
288
+ end
289
+
290
+ # See if it matches path launcher
291
+
292
+ self.set_env_vars line
293
+
294
+ result = self.try_menu_launchers line, options
295
+ self.unset_env_vars
296
+ return if result
297
+
298
+ if line =~ /^([\w -]*)$/ || line =~ /^([\w -]*)\.\.\.\/?$/
299
+
300
+ # if line =~ /^([\w -]*)(\.\.\.)?\/?$/
301
+ # TODO just check for exact match in dir, and load it if no launcher yet!
302
+
303
+ root = $1
304
+ root.gsub!(/[ -]/, '_') if root
305
+ matches = self.menu_keys.select do |possibility|
306
+ possibility =~ /^#{root}/
307
+ end
308
+ if matches.any?
309
+ if matches.length == 1
310
+ match = matches[0].gsub '_', ' '
311
+ Line.sub! /^([ @+-]*).*/, "\\1#{match}"
312
+ Launcher.launch
313
+ return
314
+ end
315
+
316
+ Line.sub! /\b$/, "..."
317
+
318
+ View.under matches.sort.map{|o| "<< #{o.sub '_', ' '}/"}.join("\n")
319
+ return
320
+ end
321
+ end
322
+
323
+ # If just root line, load any unloaded launchers this completes and relaunch
324
+
325
+ # Failed attempt to not auto-complete if slash
326
+ # It's tough because we still want to load!
327
+ # Don't do if ends with slash? - does this mean it won't load unloaded?
328
+
329
+ if line =~ /^([\w -]+)\/?$/ && ! options[:recursed]
330
+ root = $1
331
+ root.gsub!(/[ -]/, '_') if root
332
+
333
+ ["~/menus", Bookmarks["$x/menus"]].each do |dir|
334
+
335
+ matches = Dir[File.expand_path("#{dir}/#{root}*")]
336
+
337
+ if matches.any?
338
+ matches.sort.each do |file|
339
+ iroot = file[/\/(\w+)\./, 1]
340
+ next if @@menus[0][root] || @@menus[1][root] # Skip if already loaded
341
+ require_menu(file) # if File.exists? file
342
+ end
343
+ return self.launch :recursed=>1 # options.slice(:no_search).merge(:line=>merged)
344
+ end
345
+
346
+ end
347
+ end
348
+
349
+ if root = line[/^[\w -]+/]
350
+ Xiki.dont_search
351
+ # Maybe make the following print out optionally, via a 'help_last' block?
352
+ Tree << "
353
+ | There's no \"#{root}\" menu yet. Create it? You can start by adding items
354
+ | right here, or you can create a class.
355
+ <= @menu/create/here/
356
+ <= @menu/create/class/
357
+ <= @menu/install/gem/
358
+ "
359
+ else
360
+ View.flash "- No launcher matched!"
361
+ end
362
+ $xiki_no_search = false
363
+ end
364
+
365
+ def self.try_menu_launchers line, options={}
366
+
367
+ # If there's a /@ in the path, cut it off
368
+ line.sub! /.+\/@/, ''
369
+
370
+ root_orig = root = line[/^[\w -]+/] # Grab thing to match
371
+ root = TextUtil.snake_case root if root
372
+
373
+ self.append_log line
374
+ trunk = Xiki.trunk
375
+
376
+ # If menu nested under dir or file, chdir first
377
+
378
+ orig_pwd = nil
379
+ if trunk.size > 1 && closest_dir = Tree.closest_dir
380
+ orig_pwd = Dir.pwd # Where ruby pwd was before
381
+
382
+ if root == "mkdir"
383
+ Dir.chdir "/tmp/"
384
+ elsif File.directory?(closest_dir) || is_file = File.file?(closest_dir) # If dir path
385
+ closest_dir = File.dirname closest_dir if is_file
386
+
387
+ Dir.chdir closest_dir
388
+
389
+ # If file, make path only have dir
390
+ # remove file
391
+
392
+ else # If doesn't exist
393
+ View.beep "- Dir doesn't exist: #{closest_dir}"
394
+ return true
395
+ end
396
+ end
397
+
398
+ # If there is a matching .menu, use it
399
+
400
+ out = nil
401
+ if block_dot_menu = @@menus[0][root]
402
+
403
+ if @@just_show
404
+ Ol.line "Maps to .menu file, for menu: #{root}\n - #{block_dot_menu}\n - #{block_dot_menu.to_ruby}"
405
+ View.flash "- Showed launcher in $o", :times=>4
406
+ return true # To make it stop trying to run it
407
+ end
408
+
409
+ begin
410
+ out = Tree.output_and_search block_dot_menu, :line=>line #, :dir=>file_path
411
+ ensure
412
+ Dir.chdir orig_pwd if orig_pwd
413
+ end
414
+
415
+ # If .menu file matched but had no output, and no other block to delegate to, say we handled it so it will stop looking
416
+
417
+ if ! out
418
+ require_menu File.expand_path("~/menus/#{root}.rb"), :ok_if_not_found=>1
419
+ if ! @@menus[1][root]
420
+ Tree << "
421
+ | This menu item does nothing yet. You can update the .menu file to
422
+ | give it children or create a class to give it dynamic behavior:
423
+ <= @menu/create/class/
424
+ "
425
+ return true
426
+ end
427
+ end
428
+ return true if out # Output means we handled it, otherwise continue on and try class
429
+ end
430
+
431
+ # If there is a matching .rb for the menu, use it
432
+
433
+ if block_other = @@menus[1][root] # If class menu
434
+
435
+ if @@just_show
436
+ Ol.line << "Maps to class or other block, for menu: #{root}\n - #{block_other}\n - #{block_other.to_ruby}"
437
+ View.flash "- Showed launcher in $o", :times=>4
438
+ return true # To make it stop trying to run it
439
+ end
440
+
441
+ begin
442
+ Tree.output_and_search block_other, options.merge(:line=>line) #, :dir=>file_path
443
+ ensure
444
+ Dir.chdir orig_pwd if orig_pwd
445
+ end
446
+
447
+ return true
448
+ end
449
+
450
+ # If uppercase, try invoking on in-memory class
451
+ if root_orig =~ /^[A-Z]/
452
+
453
+ if @@just_show
454
+ Ol.line << "Maps to in-memory class for: #{root}"
455
+ View.flash "- Showed launcher in $o", :times=>4
456
+ return true # To make it stop trying to run it
457
+ end
458
+
459
+ begin
460
+
461
+ lam = lambda do |path|
462
+ Launcher.invoke root_orig, path
463
+ end
464
+
465
+ # Launcher.invoke__
466
+ # do |path|
467
+ # # Make class me camel case, and change Launcher.invoke to Menu.call
468
+ # end
469
+
470
+ Tree.output_and_search lam, options.merge(:line=>line) #, :dir=>file_path
471
+ ensure
472
+ Dir.chdir orig_pwd if orig_pwd
473
+ end
474
+
475
+ return true
476
+ end
477
+
478
+ # Pull into other function?
479
+ # re-use code that calls class wrapper
480
+
481
+ false # No match, keep looking
482
+ end
483
+
484
+ def self.launch_by_proc list=nil
485
+ list = Tree.construct_path(:list=>true) # Get path to pass to procs, to help them decide
486
+
487
+ # Try each proc
488
+ @@launchers_procs.each do |launcher| # For each potential match
489
+ condition_proc, block = launcher
490
+ if found = condition_proc.call(list) # If we found a match, launch it
491
+ if @@just_show
492
+ Ol << condition_proc.to_ruby
493
+ else
494
+ block.call list[found..-1]
495
+ end
496
+ return true
497
+ end
498
+ end
499
+ return false
500
+ end
501
+
502
+ def self.init_default_launchers
503
+
504
+ self.add(/^\$ /) do |l| # $ shell command inline (sync)
505
+ Console.launch :sync=>true
506
+ end
507
+
508
+ self.add /^%( |$)/ do # % shell command (async)
509
+ Console.launch_async
510
+ end
511
+
512
+ self.add /^&( |$)/ do # % shell command in iterm
513
+ Console.launch_async :iterm=>1
514
+ end
515
+
516
+ # %\n | multiline\n | commands
517
+ Launcher.add /^\%\// do # For % with nested quoted lines
518
+ path = Tree.construct_path :list=>1
519
+
520
+ next if path[-1] !~ /^\| /
521
+
522
+ txt = Tree.siblings :string=>1
523
+
524
+ orig = Location.new
525
+ Console.to_shell_buffer
526
+ View.to_bottom
527
+ Console.enter txt
528
+ orig.go
529
+ end
530
+
531
+ self.add(/^(http|file).?:\/\/.+/) do |path|
532
+ Launcher.append_log "- http/#{path}"
533
+
534
+ prefix = Keys.prefix
535
+ Keys.clear_prefix
536
+
537
+ url = path[/(http|file).?:\/\/.+/]
538
+ if prefix == "all"
539
+ txt = RestTree.request("GET", url)
540
+ txt = Tree.quote(txt) if txt =~ /\A<\w/
541
+ Tree.under txt, :no_slash=>1
542
+ next
543
+ end
544
+ url.gsub! '%', '%25'
545
+ url.gsub! '"', '%22'
546
+ prefix == :u ? $el.browse_url(url) : Firefox.url(url)
547
+ end
548
+
549
+ self.add(/^\$[^ #*!\/]+$/) do |line| # Bookmark
550
+ View.open Line.without_indent(line)
551
+ end
552
+
553
+ self.add(/^(p )?[A-Z][A-Za-z]+\.(\/|$)/) do |line|
554
+ line.sub! /^p /, ''
555
+ Code.launch_dot_at_end line
556
+ end
557
+
558
+ self.add(/^p /) do |line|
559
+ CodeTree.run line
560
+ end
561
+
562
+ self.add(/^ *pp /) do |line|
563
+ CodeTree.run line
564
+ end
565
+
566
+ self.add(/^ *puts /) do |line|
567
+ CodeTree.run line
568
+ end
569
+
570
+ self.add(/^ *print\(/) do |line|
571
+ Javascript.launch
572
+ end
573
+
574
+ self.add(/^ *$/) do |line| # Empty line
575
+ View.beep
576
+ View.message "There was nothing on this line to launch."
577
+ end
578
+
579
+ self.add(/^\*$/) do |line| # *... buffer
580
+ Line.sub! /.+/, "all"
581
+
582
+ Launcher.launch
583
+ end
584
+
585
+ self.add(/^\*./) do |line| # *... buffer
586
+ name = Line.without_label.sub(/\*/, '')
587
+ View.to_after_bar
588
+ View.to_buffer name
589
+ end
590
+
591
+ # Must have at least 2 slashes!
592
+ self.add(/^[^\|@:]+\/\w+\/[\/\w\-]+\.\w+:\d+/) do |line| # Stack traces, etc
593
+ # Match again (necessary)
594
+ line =~ /([$\/.\w\-]+):(\d+)/
595
+ path, line = $1, $2
596
+
597
+ # If relative dir, prepend current dir
598
+ if path =~ /^\w/
599
+ path = "#{View.dir}/#{path}"
600
+ path.sub! "//", "/" # View.dir sometimes ends with slash
601
+ end
602
+
603
+ View.open path
604
+ View.to_line line.to_i
605
+ end
606
+
607
+ # Xiki protocol to server
608
+ self.add(/^[a-z-]{2,}\.(com|net|org|loc|in|edu|gov|uk)(\/|$)/) do |line| # **.../: Tree grep in dir
609
+ self.web_menu line
610
+ end
611
+
612
+ self.add(/^localhost:?\d*(\/|$)/) do |line|
613
+ self.web_menu line
614
+ end
615
+
616
+ self.add(/^ *(Ol\.line|Ol << )/) do
617
+ View.layout_output :called_by_launch=>1
618
+ end
619
+
620
+ # Example code in method comments
621
+ # /tmp/foo.rb
622
+ # class Foo
623
+ # # Control-enter to run this line
624
+ # # Foo.bar
625
+ # def self.bar
626
+ Launcher.add /^class (\w+)\/\#.+/ do |path|
627
+ # Remove comment and run
628
+ txt = Line.value.sub /^ +# /, ''
629
+ result = Code.eval(txt)
630
+ next Tree.<<(CodeTree.draw_exception(result[2], txt), :no_search=>1) if result[2]
631
+ next Tree.<< result[0].to_s, :no_slash=>1 if result[0] # Returned value
632
+ Tree.<< result[1].to_s, :no_slash=>1 if result[1].any? # Stdout
633
+ end
634
+
635
+ Launcher.add /^class (\w+)\/def self.menu\/(.+)/ do |path|
636
+ clazz, path = path.match(/^class (\w+)\/def self.menu\/(.+)/)[1..2]
637
+
638
+ path = "#{TextUtil.snake_case clazz}/#{path}".gsub("/.", '/')
639
+
640
+ Tree << Menu[path]
641
+ end
642
+
643
+ Launcher.add /^ +<+@ .+/ do
644
+ Menu.root_collapser_launcher
645
+ end
646
+
647
+ Launcher.add /^ +<+ .+/ do
648
+ Menu.collapser_launcher
649
+ end
650
+
651
+ Launcher.add /^ +<+= .+/ do
652
+ Menu.replacer_launcher
653
+ end
654
+
655
+ Launcher.add /^[a-z]+\+[a-z+]+\/?$/ do |path|
656
+ Tree << %`
657
+ | If you were told to "type #{path}", it is meant that you should
658
+ | "type the acronym" while holding down control. This means Meaning
659
+ | you should type:
660
+ |
661
+ | #{Keys.human_readable(path)}
662
+ `
663
+ end
664
+
665
+ # Menu launchers
666
+
667
+ Launcher.add "log" do # |path|
668
+ Launcher.log# Tree.rootless(path)
669
+ end
670
+
671
+ Launcher.add "last" do |path|
672
+ Launcher.last path
673
+ end
674
+
675
+ # ...Tree classes
676
+
677
+ # RestTree
678
+ condition_proc = proc {|list| RestTree.handles? list}
679
+ Launcher.add condition_proc do |list|
680
+ RestTree.launch :path=>list
681
+ end
682
+
683
+ # FileTree
684
+ condition_proc = proc {|list| FileTree.handles? list}
685
+ Launcher.add condition_proc do |list|
686
+ FileTree.launch list
687
+ end
688
+
689
+ # CodeTree
690
+ condition_proc = proc {|list| CodeTree.handles? list}
691
+ Launcher.add condition_proc do |list|
692
+ CodeTree.launch :path=>list
693
+ end
694
+
695
+ # UrlTree
696
+ condition_proc = proc {|list| UrlTree.handles? list}
697
+ Launcher.add condition_proc do |list|
698
+ UrlTree.launch :path=>list
699
+ end
700
+ end
701
+
702
+ def self.file_and_mode_hooks
703
+ if View.mode == :dired_mode
704
+ filename = $el.dired_get_filename
705
+ # If dir, open tree
706
+ if File.directory?(filename)
707
+ FileTree.ls :dir=>filename
708
+ else # If file, do full file search?
709
+ History.open_current :all => true, :paths => [filename]
710
+ end
711
+ return true
712
+ end
713
+ if View.name =~ /^\*ol/ # If in an ol output log file
714
+ OlHelper.launch
715
+ Effects.blink(:what=>:line)
716
+ return true
717
+ end
718
+ return false
719
+ end
720
+
721
+ def self.do_last_launch options={}
722
+ orig = View.index
723
+
724
+ CLEAR_CONSOLES.each do |buffer|
725
+ View.clear buffer
726
+ end
727
+
728
+ prefix = Keys.prefix :clear=>true
729
+
730
+ if prefix ==:u || options[:here]
731
+ View.to_nth orig
732
+ else
733
+ Move.to_window 1
734
+ end
735
+
736
+ line = Line.value
737
+
738
+ # Go to parent and collapse, if not at left margin, and buffer modified (shows we recently inserted)
739
+ if ! Color.at_cursor.member?("color-rb-light") #&& line !~ /^ *[+-] / # and not a bullet
740
+ if line =~ /^ /
741
+ Tree.to_parent
742
+ end
743
+ Tree.kill_under
744
+ end
745
+
746
+ Launcher.launch_or_hide :blink=>true, :no_search=>true
747
+ View.to_nth orig
748
+ end
749
+
750
+ # Used any more? - should be replaced by menu log - delete this
751
+ def self.urls
752
+ txt = File.read File.expand_path("~/.emacs.d/url_log.notes")
753
+ txt = txt.split("\n").reverse.uniq.join("\n")
754
+ end
755
+
756
+ def self.enter_last_launched
757
+ Launcher.insert self.last_launched_menu
758
+ end
759
+
760
+ def self.last_launched_menu
761
+ bm = Keys.input(:timed => true, :prompt => "bookmark to show launches for (* for all): ")
762
+
763
+ menu =
764
+ if bm == "8" || bm == " "
765
+ "- search/launched/"
766
+ elsif bm == "."
767
+ "- Search.launched '#{View.file}'/"
768
+ elsif bm == "3"
769
+ "- Search.launched '#'/"
770
+ elsif bm == ";" || bm == ":" || bm == "-"
771
+ "- Search.launched ':'/"
772
+ else
773
+ "- search/launched/$#{bm}/"
774
+ end
775
+ end
776
+
777
+ def self.invoke clazz, path, options={}
778
+
779
+ default_method = "menu"
780
+ # If dot, extract it as method
781
+ if clazz =~ /\./
782
+ clazz, default_method = clazz.match(/(.+)\.(.+)/)[1..2]
783
+ end
784
+
785
+ if clazz.is_a? String
786
+ # Require it to be camel case (because .invoke will be Menu.call "Class"
787
+ # if lower case, will assume Menu.call "path"
788
+ camel = TextUtil.camel_case clazz
789
+ clazz = $el.instance_eval(camel, __FILE__, __LINE__) rescue nil
790
+
791
+ elsif clazz.is_a? Class
792
+ camel = clazz.to_s
793
+ end
794
+
795
+ snake = TextUtil.snake_case camel
796
+
797
+ raise "No class '#{clazz || camel}' found in launcher" if clazz.nil?
798
+
799
+ # reload 'path_to_class'
800
+ Menu.load_if_changed File.expand_path("~/menus/#{snake}.rb")
801
+
802
+ args = path.is_a?(Array) ?
803
+ path : Menu.split(path, :rootless=>1)
804
+
805
+ # Call .menu_before if there...
806
+
807
+ method = clazz.method("menu_before") rescue nil
808
+
809
+ self.set_env_vars path
810
+
811
+ if method
812
+ code = "#{camel}.menu_before *#{args.inspect}"
813
+ returned, out, exception = Code.eval code
814
+
815
+ return CodeTree.draw_exception exception, code if exception
816
+ if returned
817
+
818
+ # TODO: call .unset_env_vars before this and other below places we return
819
+
820
+ returned = returned.unindent if returned =~ /\A[ \n]/
821
+ return returned
822
+ end
823
+ end
824
+
825
+ menu_arity = nil
826
+ txt = options[:tree]
827
+
828
+ # Call .menu with no args to get child menus or route to other method...
829
+
830
+ if txt.nil?
831
+ method = clazz.method(default_method) rescue nil
832
+ if method && method.arity == 0
833
+ menu_arity = 0
834
+ code = "#{camel}.#{default_method}"
835
+ returned, out, exception = Code.eval code
836
+ return CodeTree.draw_exception exception, code if exception
837
+ txt = CodeTree.returned_to_s returned # Convert from array into string, etc.
838
+ end
839
+ end
840
+
841
+ # Error if no menu method or file
842
+ if method.nil? && txt.nil? && ! args.find{|o| o =~ /^\./}
843
+
844
+ cmethods = clazz.methods - Class.methods
845
+ return cmethods.sort.map{|o| ".#{o}/"}
846
+ end
847
+
848
+
849
+ # If got routable menu text, use it to route (get children or dotify)...
850
+
851
+ if txt
852
+ txt = txt.unindent if txt =~ /\A[ \n]/
853
+ raise "#{code} returned nil, but is supposed to return something when it takes no arguments" if txt.nil?
854
+
855
+ tree = txt
856
+
857
+ txt = Tree.children tree, args
858
+
859
+ if txt && txt != "- */\n"
860
+ # Pass in output of menu as either:
861
+ # ENV['output']
862
+ # 1st parameter: .menu_after output, *args
863
+ return self.invoke_menu_after clazz, txt, args
864
+ end
865
+
866
+ # Copy dots onto args, so last dotted one will be used as action
867
+
868
+ Tree.dotify! tree, args
869
+
870
+ # TODO: when to invoke this?
871
+ # Maybe invoke even if there was no .menu method
872
+ # Is that happening now?
873
+
874
+ # If .menu_hidden exists, dotify based on its output as well...
875
+ method = clazz.method("menu_hidden") rescue nil
876
+ if method
877
+ returned, out, exception = Code.eval "#{camel}.menu_hidden"
878
+
879
+ if returned && returned.is_a?(String)
880
+ returned = returned.unindent
881
+ Tree.dotify! returned, args
882
+ end
883
+ end
884
+
885
+ end
886
+ # Else, continue on to run it based on routified path
887
+
888
+
889
+ # TODO: Maybe extract this out into .dotified_to_ruby ?
890
+
891
+ # Figure out which ones are actions
892
+
893
+ # Last .dotted one is the action, and non-dotted are variables to pass
894
+ actions, variables = args.partition{|o| o =~ /^\./ }
895
+ action = actions.last || ".#{default_method}"
896
+ action.gsub! /[ -]/, '_'
897
+ action.gsub! /[^\w.]/, ''
898
+
899
+ # Call .menu_after if appropriate...
900
+
901
+ if action == ".menu" && txt == nil && menu_arity == 0
902
+ return self.invoke_menu_after clazz, txt, args
903
+ end
904
+
905
+ args = variables.map{|o| "\"#{CodeTree.escape o}\""}.join(", ")
906
+
907
+ # TODO: use adapter here, so we can call .js file?
908
+
909
+ # TODO .menu_after: Check for arity - if mismatch, don't call, but go straight to .menu_after!
910
+ # We could probably not worry about this for now?
911
+
912
+ code = "#{camel}#{action.downcase} #{args}".strip
913
+
914
+ txt, out, exception = Code.eval code
915
+ txt = CodeTree.returned_to_s(txt) # Convert from array into string, etc.
916
+ self.unset_env_vars
917
+
918
+ txt = txt.unindent if txt =~ /\A[ \n]/
919
+
920
+ return CodeTree.draw_exception exception, code if exception
921
+
922
+ txt = self.invoke_menu_after clazz, txt, args
923
+
924
+ self.unset_env_vars
925
+
926
+ txt
927
+ end
928
+
929
+ def self.invoke_menu_after clazz, txt, args
930
+ camel = clazz.to_s
931
+ method = clazz.method("menu_after") rescue nil
932
+ return txt if method.nil?
933
+
934
+ code = "#{camel}.menu_after #{txt.inspect}, *#{args.inspect}"
935
+ returned, out, exception = Code.eval code
936
+
937
+ return CodeTree.draw_exception exception, code if exception
938
+ if returned
939
+
940
+ # TODO: call .unset_env_vars before this and other below places we return
941
+
942
+ returned = returned.unindent if returned =~ /\A[ \n]/
943
+ return returned
944
+ end
945
+
946
+ txt # Otherwise, just return output!"
947
+
948
+ end
949
+
950
+ def self.add_class_launchers classes
951
+ classes.each do |clazz|
952
+ next if clazz =~ /\//
953
+
954
+ # Why is this line causing an error??
955
+ # clazz = $el.el4r_ruby_eval(TextUtil.camel_case clazz) rescue nil
956
+ # method = clazz.method(:menu) rescue nil
957
+ # next if method.nil?
958
+
959
+ self.add clazz do |path|
960
+ Launcher.invoke clazz, path
961
+ end
962
+ end
963
+ end
964
+
965
+ def self.append_log path
966
+ return if View.name =~ /_log.notes$/
967
+
968
+ path = path.sub /^[+-] /, '' # Remove bullet
969
+ path = "#{path}/" if path !~ /\// # Append slash if just root without path
970
+
971
+ return if path =~ /^(h|log|last)\//
972
+
973
+ path = "- #{path}"
974
+ File.open(@@log, "a") { |f| f << "#{path}\n" } rescue nil
975
+ end
976
+
977
+ #
978
+ # Insert menu right here and launch it
979
+ #
980
+ # Launcher.open "computer"
981
+ #
982
+ def self.insert txt, options={}
983
+ View.insert txt
984
+ $el.open_line(1)
985
+ Launcher.launch options
986
+ end
987
+
988
+ def self.show menu, options={}
989
+ self.open menu, options.merge(:no_launch=>1)
990
+ end
991
+
992
+ #
993
+ # Open new buffer and launch the menu in it
994
+ #
995
+ # Launcher.open "computer"
996
+ #
997
+ def self.open menu, options={}
998
+ return self.insert(menu, options) if options[:inline]
999
+
1000
+ $el.sit_for 0.25 if options[:delay] || options[:first_letter] # Delay slightly, (avoid flicking screen when they type command quickly)
1001
+
1002
+ View.to_after_bar if View.in_bar? && !options[:bar_is_fine]
1003
+
1004
+ dir = View.dir
1005
+
1006
+ # For buffer name, handle multi-line strings
1007
+ buffer = menu.sub(/.+\n[ -]*/m, '').gsub(/[.,]/, '')
1008
+ buffer = "@" + buffer.sub(/^[+-] /, '')
1009
+ View.to_buffer buffer, :dir=>dir
1010
+
1011
+ View.clear
1012
+ Notes.mode
1013
+ View.wrap :off
1014
+
1015
+ txt = menu
1016
+
1017
+ if txt.blank?
1018
+ return View.insert("\n", :dont_move=>1)
1019
+ end
1020
+
1021
+ dir = options[:dir] and txt = "- #{dir.sub /\/$/, ''}/\n - #{txt}"
1022
+
1023
+ View << txt
1024
+
1025
+ $el.open_line 1
1026
+
1027
+ if options[:choices]
1028
+ View.to_highest
1029
+ Tree.search
1030
+ return
1031
+ end
1032
+
1033
+ if options[:no_launch]
1034
+ View.to_highest
1035
+ return
1036
+ end
1037
+
1038
+ Launcher.launch options
1039
+ end
1040
+
1041
+ def self.method_missing *args, &block
1042
+
1043
+ arg = args.shift
1044
+
1045
+ if block.nil?
1046
+ if args == [] # Trying to call menu with no args
1047
+ return Menu.call arg.to_s
1048
+ end
1049
+ if args.length == 1 && args[0].is_a?(String) # Trying to call menu with args / path?
1050
+ return
1051
+ end
1052
+ end
1053
+
1054
+ raise "Menu.#{arg} called with no block and no args" if args == [] && block.nil?
1055
+ self.add arg.to_s, args[0], &block
1056
+ end
1057
+
1058
+ def self.wrapper path
1059
+
1060
+ # If starts with bookmark, expand as file (not dir)
1061
+
1062
+ path = Bookmarks.expand path, :file_ok=>1
1063
+
1064
+ # TODO: make generic
1065
+ # TODO: load all the adapters and construct the "rb|js" part of the regex
1066
+ match = path.match(/(.+\/)(\w+)\.(rb|js|coffee|py|notes|menu|haml)\/(.*)/)
1067
+ if match
1068
+ dir, file, extension, path = match[1..4]
1069
+ # TODO: instead, call Launcher.invoke JsAdapter(dir, path), path
1070
+ self.send "wrapper_#{extension}", dir, "#{file}.#{extension}", path
1071
+ return true # Indicate we handled it
1072
+ end
1073
+
1074
+ # For matches to filename instead of extensions?
1075
+ match = path.match(/(.+\/)(Rakefile)\/(.*)/)
1076
+ if match
1077
+ dir, file, path = match[1..4]
1078
+ # TODO: instead, call Launcher.invoke JsAdapter(dir, path), path
1079
+ self.send "wrapper_#{file.downcase}", dir, file, path
1080
+ return true # Indicate we handled it
1081
+ end
1082
+
1083
+ return false
1084
+
1085
+ end
1086
+
1087
+ def self.wrapper_rb dir, file, path
1088
+ output = Console.run "ruby #{Xiki.dir}/etc/wrappers/wrapper.rb #{file} \"#{path}\"", :sync=>1, :dir=>dir
1089
+
1090
+ # Sensible thing for now is to just do literal output
1091
+ # output = Tree.children output, path if path !~ /^\./
1092
+
1093
+ # How to know when to do children?!
1094
+ # Because it called .menu, and menu had no args
1095
+ # Make it set env var?
1096
+
1097
+ # output = Tree.children output, path
1098
+
1099
+ Tree << output
1100
+ end
1101
+
1102
+ def self.wrapper_js dir, file, path
1103
+ output = Console.run "node #{Xiki.dir}etc/wrappers/wrapper.js \"#{dir}#{file}\" \"#{path}\"", :sync=>1, :dir=>dir
1104
+ output = Tree.children output, path
1105
+ Tree << output
1106
+ end
1107
+
1108
+ def self.wrapper_coffee dir, file, path
1109
+ txt = CoffeeScript.to_js("#{dir}#{file}")
1110
+ tmp_file = "/tmp/tmp.js"
1111
+ File.open(tmp_file, "w") { |f| f << txt }
1112
+
1113
+ output = Console.run "node #{Xiki.dir}etc/wrappers/wrapper.js \"#{tmp_file}\" \"#{path}\"", :sync=>1, :dir=>dir
1114
+ output = Tree.children output, path
1115
+ Tree << output
1116
+ end
1117
+
1118
+ def self.wrapper_notes dir, file, path
1119
+ if match = path.match(/^(\| .+)(\| .*)/)
1120
+ heading, content = match[1..2]
1121
+ # [nil, nil])[1..2]
1122
+ else
1123
+ heading, content = [path, nil]
1124
+ end
1125
+
1126
+ heading = nil if heading.blank?
1127
+
1128
+ # heading, content = (path.match(/^(\| .+)(\| .*)/) || [nil, nil])[1..2]
1129
+
1130
+ dir = "#{dir}/" if dir !~ /\/$/
1131
+ output = Notes.drill "#{dir}#{file}", heading, content
1132
+ Tree << output
1133
+ end
1134
+
1135
+ def self.wrapper_menu dir, file, path
1136
+ heading, content = (path.match(/^(\| .+)(\| .*)?/) || [nil, nil])[1..2]
1137
+
1138
+ # output = Menu.drill "#{dir}/#{file}", heading, content
1139
+
1140
+ # output = Tree.children File.read(file), Tree.rootless(path)
1141
+ output = Tree.children File.read("#{Bookmarks[dir]}/#{file}"), path
1142
+
1143
+ Tree << output
1144
+ end
1145
+
1146
+ def self.wrapper_py dir, file, path
1147
+ output = Console.run "python #{Xiki.dir}etc/wrappers/wrapper.py \"#{dir}#{file}\" \"#{path}\"", :sync=>1, :dir=>dir
1148
+ output = Tree.children output, path if path !~ /^\./
1149
+ Tree << output
1150
+ end
1151
+
1152
+ def self.wrapper_haml dir, file, path
1153
+
1154
+ engine = Haml::Engine.new(File.read "#{dir}#{file}")
1155
+
1156
+ foos = ["foo1", "foo2", "foo3"]
1157
+ o = Object.new
1158
+ o.instance_eval do
1159
+ @foo = "Foooo"
1160
+ @foos = foos
1161
+ end
1162
+
1163
+ txt = engine.render(o, "foo"=>"Fooooooo", "foos"=>foos)
1164
+
1165
+ Tree << Tree.quote(txt)
1166
+ end
1167
+
1168
+ def self.wrapper_rakefile dir, file, path
1169
+
1170
+ # If just file passed, show all tasks
1171
+
1172
+ if path.blank?
1173
+ txt = Console.sync "rake -T", :dir=>dir
1174
+
1175
+ txt = txt.scan(/^rake (.+?) *#/).flatten
1176
+
1177
+ Tree << txt.map{|o| "- #{o}/\n"}.join
1178
+ return
1179
+ end
1180
+
1181
+ # Task name passed, so run it
1182
+
1183
+ path.sub! /\/$/, ''
1184
+ Console.run "rake #{path}", :dir=>dir
1185
+ nil
1186
+
1187
+ end
1188
+
1189
+ def self.reload_menu_dirs
1190
+ Ol.stack
1191
+ MENU_DIRS.each do |dir|
1192
+ next unless File.directory? dir
1193
+
1194
+ Files.in_dir(dir).each do |f|
1195
+ next if f !~ /^[a-z].*\..*[a-z]$/ || f =~ /__/
1196
+ path = "#{dir}/#{f}"
1197
+ stem = f[/[^.]*/]
1198
+ self.add stem, :menu=>path
1199
+ end
1200
+ end
1201
+ "- reloaded!"
1202
+ end
1203
+
1204
+ #
1205
+ # Launches "menu/item", first prompting for name. Used by search+like_menu
1206
+ # and other places.
1207
+ #
1208
+ # If matches substring, shows the possible matches and does an isearch.
1209
+ #
1210
+ # Menu.like_menu "htm"
1211
+ #
1212
+ def self.like_menu item, options={}
1213
+
1214
+ # return
1215
+ return if item.nil?
1216
+
1217
+ menu = Keys.input :timed=>true, :prompt=>"Enter menu to pass '#{item}' to (space if menu): "
1218
+
1219
+ return self.open(item, options) if menu == " " # Space means text is the menu
1220
+
1221
+ matches = self.menu_keys.select do |possibility|
1222
+ possibility =~ /^#{menu}/
1223
+ end
1224
+
1225
+ if matches.length == 1
1226
+ return self.open("- #{matches[0]}/#{item}", options)
1227
+ end
1228
+
1229
+ self.open(matches.map{|o| "- #{o}/#{item}\n"}.join(''), options.merge(:choices=>1))
1230
+ right = View.cursor
1231
+ Move.to_previous_paragraph
1232
+ # return
1233
+ Tree.search :left=>View.cursor, :right=>right
1234
+ end
1235
+
1236
+ def self.search_like_menu
1237
+ txt = Search.stop
1238
+ self.like_menu txt
1239
+ end
1240
+
1241
+ def self.as_update
1242
+ Keys.prefix = "update"
1243
+ Launcher.launch :leave_bullet=>1
1244
+ end
1245
+
1246
+ def self.as_delete
1247
+ Keys.prefix = "delete"
1248
+ Launcher.launch
1249
+ end
1250
+
1251
+ def self.as_open
1252
+ Keys.prefix = "open"
1253
+ Launcher.launch
1254
+ end
1255
+
1256
+ def self.enter_all
1257
+ return FileTree.enter_lines(/.*/) if Line.blank?
1258
+
1259
+ Keys.prefix = "all"
1260
+ Launcher.launch
1261
+ end
1262
+
1263
+ def self.enter_outline
1264
+ return FileTree.enter_lines if Line.blank? # Prompts for bookmark
1265
+
1266
+ # If there's a numeric prefix, add it
1267
+ Keys.add_prefix "outline"
1268
+ Launcher.launch
1269
+ end
1270
+
1271
+ def self.set_env_vars path
1272
+
1273
+ return if ! $el
1274
+
1275
+ # TODO: I guess they'll need to be set from somewhere else as well?
1276
+
1277
+ ENV['prefix'] = Keys.prefix.to_s
1278
+
1279
+ args = path.is_a?(Array) ?
1280
+ path : Menu.split(path, :rootless=>1)
1281
+
1282
+ # ?? If any has ^|, then make sure current line has slash
1283
+
1284
+ quoted = args.find{|o| o =~ /^\|( |$)/}
1285
+
1286
+ if ! quoted
1287
+ return ENV['txt'] = args[-1]
1288
+ end
1289
+
1290
+ # Quoted lines
1291
+
1292
+ txt = Tree.leaf("|") # Cheat to make it grab quoted
1293
+ ENV['txt'] = txt.length > 1_000_000 ? "*too long to put into env var*" : txt
1294
+ end
1295
+
1296
+ def self.web_menu line
1297
+ Line << "/" unless Line =~ /\/$/
1298
+ url = "http://#{line}"
1299
+ url.sub! /\.\w+/, "\\0/xiki"
1300
+ url.gsub! ' ', '+'
1301
+
1302
+ begin
1303
+ response = HTTParty.get(url)
1304
+ Tree << response.body
1305
+ rescue Exception=>e
1306
+ Tree << "- couldn't connect!"
1307
+ end
1308
+ end
1309
+
1310
+ def self.unset_env_vars
1311
+ ENV['prefix'] = nil
1312
+ ENV['txt'] = nil
1313
+ end
1314
+ end
1315
+
1316
+ def require_menu file, options={}
1317
+ if ! options[:ok_if_not_found]
1318
+ raise "File Not Found" if !File.exists?(file)
1319
+ end
1320
+
1321
+ stem = file[/(\w+)\./, 1]
1322
+
1323
+ # As .menu...
1324
+
1325
+ if file =~ /\.menu$/ || options[:force_as] == "menu"
1326
+ Launcher.add stem, :menu_file=>1 do |path|
1327
+ next View.flash("- Xiki couldn't find: #{file}", :times=>5) if ! File.exists?(file)
1328
+ Tree.children File.read(file), Tree.rootless(path)
1329
+ end
1330
+ return
1331
+ end
1332
+
1333
+ # As class, so require and add launcher...
1334
+
1335
+ result = :not_found
1336
+ begin
1337
+ result = Menu.load_if_changed file
1338
+ rescue LoadError => e
1339
+ gem_name = Requirer.extract_gem_from_exception e.to_s
1340
+ Requirer.show "The file #{file} wants to use the '#{gem_name}' gem.\n% sudo gem install #{gem_name}\n\n"
1341
+ rescue Exception=>e
1342
+ txt = CodeTree.draw_exception e
1343
+ Requirer.show "The file #{file} had this exception:\n#{txt}\n\n"
1344
+ end
1345
+
1346
+ return if result == :not_found
1347
+
1348
+ Launcher.add stem if ! Launcher.menus[1][stem]
1349
+ end
1350
+
1351
+ Launcher.init_default_launchers