xiki 0.5.0a
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +11 -0
- data/LICENSE +22 -0
- data/README.markdown +83 -0
- data/Rakefile +8 -0
- data/bin/xiki +46 -0
- data/etc/command/xiki_command.rb +203 -0
- data/etc/command/xiki_process.rb +52 -0
- data/etc/command/xiki_wrapper +2 -0
- data/etc/js/menu1.js +55 -0
- data/etc/js/xiki.js +259 -0
- data/etc/logo.png +0 -0
- data/etc/presentations/bootstrap.deck +5 -0
- data/etc/presentations/databases.deck +41 -0
- data/etc/presentations/diffs.deck +23 -0
- data/etc/presentations/documentation.deck +14 -0
- data/etc/presentations/effects.deck +5 -0
- data/etc/presentations/face.deck +297 -0
- data/etc/presentations/files.deck +79 -0
- data/etc/presentations/icons.deck +22 -0
- data/etc/presentations/images.deck +24 -0
- data/etc/presentations/key_shortcuts.deck +16 -0
- data/etc/presentations/macros.deck +18 -0
- data/etc/presentations/menu_classes.deck +44 -0
- data/etc/presentations/menu_directories.deck +30 -0
- data/etc/presentations/notes.deck +19 -0
- data/etc/presentations/other_languages.deck +55 -0
- data/etc/presentations/other_wiki_syntaxes.deck +4 -0
- data/etc/presentations/piano.deck +5 -0
- data/etc/presentations/potential/diffs.deck +38 -0
- data/etc/presentations/potential/evolution.deck +18 -0
- data/etc/presentations/potential/intro.deck +711 -0
- data/etc/presentations/potential/intro1.deck +711 -0
- data/etc/presentations/potential/intro2.deck +97 -0
- data/etc/presentations/potential/make_mysql_menu.deck +36 -0
- data/etc/presentations/potential/ruby_development.deck +17 -0
- data/etc/presentations/potential/ui_prototyping.deck +50 -0
- data/etc/presentations/potential/web_dev.deck +4 -0
- data/etc/presentations/potential/web_development.deck +10 -0
- data/etc/presentations/potential/wiki.deck +45 -0
- data/etc/presentations/presentations.deck +24 -0
- data/etc/presentations/rails_development.deck +29 -0
- data/etc/presentations/search_key_shortcuts.deck +37 -0
- data/etc/presentations/simplest_possible_ui.deck +37 -0
- data/etc/presentations/svg.deck +5 -0
- data/etc/presentations/testing.deck +28 -0
- data/etc/presentations/the_end.deck +13 -0
- data/etc/presentations/type_something_and_double_click.deck +57 -0
- data/etc/presentations/type_the_acronym.deck +144 -0
- data/etc/presentations/web_browser.deck +56 -0
- data/etc/presentations/xiki_command.deck +20 -0
- data/etc/presentations/xiki_url.deck +14 -0
- data/etc/shark.icns +0 -0
- data/etc/shark_script.icns +0 -0
- data/etc/snippets/html.notes +7 -0
- data/etc/snippets/notes.notes +20 -0
- data/etc/snippets/rb.notes +38 -0
- data/etc/templates/menu_template.menu +2 -0
- data/etc/templates/menu_template.rb +8 -0
- data/etc/templates/template.rb +5 -0
- data/etc/themes/Dark_Metal.notes +28 -0
- data/etc/themes/Orange_Path.notes +15 -0
- data/etc/themes/Shiny_Blue.notes +27 -0
- data/etc/themes/Shiny_Green.notes +27 -0
- data/etc/wrappers/wrapper.js +17 -0
- data/etc/wrappers/wrapper.py +20 -0
- data/etc/wrappers/wrapper.rb +25 -0
- data/lib/block.rb +72 -0
- data/lib/bookmarks.rb +352 -0
- data/lib/buffers.rb +170 -0
- data/lib/clipboard.rb +333 -0
- data/lib/code.rb +860 -0
- data/lib/code_tree.rb +476 -0
- data/lib/color.rb +274 -0
- data/lib/console.rb +557 -0
- data/lib/control_lock.rb +9 -0
- data/lib/control_tab.rb +176 -0
- data/lib/core_ext.rb +31 -0
- data/lib/cursor.rb +111 -0
- data/lib/deletes.rb +65 -0
- data/lib/diff_log.rb +297 -0
- data/lib/effects.rb +145 -0
- data/lib/environment.rb +5 -0
- data/lib/file_tree.rb +1875 -0
- data/lib/files.rb +334 -0
- data/lib/hide.rb +259 -0
- data/lib/history.rb +286 -0
- data/lib/image.rb +51 -0
- data/lib/incrementer.rb +15 -0
- data/lib/insert.rb +7 -0
- data/lib/irc.rb +22 -0
- data/lib/key_bindings.rb +658 -0
- data/lib/keys.rb +754 -0
- data/lib/launcher.rb +1351 -0
- data/lib/line.rb +429 -0
- data/lib/links.rb +6 -0
- data/lib/location.rb +175 -0
- data/lib/macros.rb +48 -0
- data/lib/man.rb +19 -0
- data/lib/menu.rb +708 -0
- data/lib/merb.rb +259 -0
- data/lib/message.rb +5 -0
- data/lib/meths.rb +56 -0
- data/lib/mode.rb +34 -0
- data/lib/move.rb +248 -0
- data/lib/notes.rb +1000 -0
- data/lib/numbers.rb +45 -0
- data/lib/ol.rb +203 -0
- data/lib/ol_helper.rb +44 -0
- data/lib/overlay.rb +167 -0
- data/lib/pause_means_space.rb +68 -0
- data/lib/php.rb +22 -0
- data/lib/projects.rb +21 -0
- data/lib/relinquish_exception.rb +2 -0
- data/lib/remote.rb +206 -0
- data/lib/requirer.rb +46 -0
- data/lib/rest_tree.rb +108 -0
- data/lib/ruby.rb +57 -0
- data/lib/ruby_console.rb +165 -0
- data/lib/search.rb +1572 -0
- data/lib/search_term.rb +40 -0
- data/lib/snippet.rb +68 -0
- data/lib/specs.rb +229 -0
- data/lib/styles.rb +274 -0
- data/lib/svn.rb +682 -0
- data/lib/text_util.rb +110 -0
- data/lib/tree.rb +1871 -0
- data/lib/tree_cursor.rb +87 -0
- data/lib/trouble_shooting.rb +27 -0
- data/lib/url_tree.rb +11 -0
- data/lib/view.rb +1474 -0
- data/lib/window.rb +133 -0
- data/lib/xiki.rb +404 -0
- data/menus/accounts.rb +5 -0
- data/menus/address_book.rb +21 -0
- data/menus/agenda.rb +28 -0
- data/menus/all.rb +5 -0
- data/menus/amazon.rb +16 -0
- data/menus/app.rb +16 -0
- data/menus/applescript.rb +46 -0
- data/menus/as.rb +15 -0
- data/menus/bookmarklet.rb +63 -0
- data/menus/bootstrap.rb +568 -0
- data/menus/browse.rb +13 -0
- data/menus/browser.rb +78 -0
- data/menus/cassandra_db.rb +36 -0
- data/menus/chmod.rb +27 -0
- data/menus/classes.rb +5 -0
- data/menus/coffee_script.rb +35 -0
- data/menus/computer.rb +24 -0
- data/menus/contacts.rb +5 -0
- data/menus/cookies.rb +25 -0
- data/menus/couch.rb +184 -0
- data/menus/crop.rb +45 -0
- data/menus/css.rb +55 -0
- data/menus/current.rb +5 -0
- data/menus/db.rb +12 -0
- data/menus/deck.rb +219 -0
- data/menus/dictionary.rb +9 -0
- data/menus/dir.rb +8 -0
- data/menus/disk.rb +5 -0
- data/menus/do.rb +13 -0
- data/menus/docs.rb +58 -0
- data/menus/dotsies.rb +107 -0
- data/menus/edited.rb +18 -0
- data/menus/emacs.rb +17 -0
- data/menus/enter.rb +13 -0
- data/menus/eval.rb +17 -0
- data/menus/executable.rb +16 -0
- data/menus/filter.rb +46 -0
- data/menus/firefox.rb +607 -0
- data/menus/foo.rb +30 -0
- data/menus/french.rb +7 -0
- data/menus/git.rb +185 -0
- data/menus/gito.rb +785 -0
- data/menus/google.rb +35 -0
- data/menus/google_images.rb +11 -0
- data/menus/google_patents.rb +10 -0
- data/menus/gutenberg.rb +13 -0
- data/menus/head.rb +10 -0
- data/menus/headings.rb +39 -0
- data/menus/html.rb +61 -0
- data/menus/icon.rb +40 -0
- data/menus/images.menu +2 -0
- data/menus/img.rb +15 -0
- data/menus/info.rb +9 -0
- data/menus/ip.rb +10 -0
- data/menus/iterm.rb +36 -0
- data/menus/itunes.rb +78 -0
- data/menus/javascript.rb +74 -0
- data/menus/layout.rb +18 -0
- data/menus/local_storage.rb +67 -0
- data/menus/ls.rb +19 -0
- data/menus/mac.rb +87 -0
- data/menus/maps.rb +18 -0
- data/menus/matches.rb +18 -0
- data/menus/memcache.rb +117 -0
- data/menus/mkdir.rb +23 -0
- data/menus/mongo.rb +83 -0
- data/menus/mysql.rb +294 -0
- data/menus/node.rb +88 -0
- data/menus/open.rb +19 -0
- data/menus/outline.rb +24 -0
- data/menus/piano.rb +746 -0
- data/menus/postgres.rb +34 -0
- data/menus/pre.rb +5 -0
- data/menus/python.rb +39 -0
- data/menus/rails.rb +160 -0
- data/menus/rake.rb +12 -0
- data/menus/redmine.rb +168 -0
- data/menus/riak_tree.rb +204 -0
- data/menus/rss.rb +15 -0
- data/menus/safari.rb +11 -0
- data/menus/sass.rb +15 -0
- data/menus/say.rb +6 -0
- data/menus/scale.rb +49 -0
- data/menus/serve.rb +78 -0
- data/menus/shuffle.rb +24 -0
- data/menus/spanish.rb +7 -0
- data/menus/standalone.rb +57 -0
- data/menus/tail.rb +41 -0
- data/menus/technologies.rb +19 -0
- data/menus/themes.rb +32 -0
- data/menus/thesaurus.rb +13 -0
- data/menus/to.rb +24 -0
- data/menus/twitter.rb +57 -0
- data/menus/wikipedia.rb +34 -0
- data/menus/words.rb +11 -0
- data/spec/code_tree_spec.rb +59 -0
- data/spec/diff_log_spec.rb +40 -0
- data/spec/file_tree_spec.rb +102 -0
- data/spec/keys_spec.rb +24 -0
- data/spec/line_spec.rb +68 -0
- data/spec/menu_spec.rb +50 -0
- data/spec/ol_spec.rb +98 -0
- data/spec/remote_spec.rb +43 -0
- data/spec/search_spec.rb +162 -0
- data/spec/text_util_spec.rb +119 -0
- data/spec/tree_cursor_spec.rb +91 -0
- data/spec/tree_spec.rb +955 -0
- data/tests/console_test.rb +11 -0
- data/tests/couch_db_test.rb +12 -0
- data/tests/diff_log_test.rb +43 -0
- data/tests/el_mixin.rb +16 -0
- data/tests/git_test.rb +95 -0
- data/tests/keys_test.rb +19 -0
- data/tests/line_test.rb +38 -0
- data/tests/merb_test.rb +11 -0
- data/tests/redmine_test.rb +50 -0
- data/tests/remote_test.rb +31 -0
- data/tests/rest_tree_test.rb +70 -0
- data/xiki.gemspec +37 -0
- metadata +332 -0
data/lib/launcher.rb
ADDED
@@ -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
|