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,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