Class: Launcher

Inherits:
Object show all
Defined in:
lib/xiki/launcher.rb

Constant Summary collapse

CLEAR_CONSOLES =
[
"*ol",
"*output - tail of /tmp/ds_ol.notes",
"*visits - tail of /tmp/visit_log.notes",
"*console app",
]
[
"#{Xiki.dir}menu",
File.expand_path("~/menu"),
]
@@log =
File.expand_path("~/.emacs.d/menu_log.notes")
@@just_show =

Use @launcher/options/show or launch/ to enable. Look in /tmp/output.notes

false

Class Method Summary collapse

Class Method Details

.add(*args, &block) ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/xiki/launcher.rb', line 123

def self.add *args, &block
  arg = args.shift

  raise "Launcher.add called with no args and no block" if args == [nil] && block.nil?

  if arg.is_a? Regexp   # If regex, add
    @@launchers[arg] = block
  elsif arg.is_a? Proc   # If proc, add to procs
    @@launchers_procs << [arg, block]
  elsif arg.is_a?(String)
    self.add_menu arg, args[0], block
  else
    raise "Don't know how to launch this"
  end
end

.add_class_launchers(classes) ⇒ Object



972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
# File 'lib/xiki/launcher.rb', line 972

def self.add_class_launchers classes

  classes.each do |clazz|
    next if clazz =~ /\//

    # Why is this line causing an error??
    #       clazz = $el.el4r_ruby_eval(TextUtil.camel_case clazz) rescue nil
    #       method = clazz.method(:menu) rescue nil
    #       next if method.nil?

    self.add clazz do |path|
      Launcher.invoke clazz, path
    end
  end
end

.add_menu(root, hash, block) ⇒ Object



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/xiki/launcher.rb', line 139

def self.add_menu root, hash, block
  if hash.nil? && block   # If just block, just define
    return @@menus[1][root] = block
  elsif hash.is_a?(Hash) && hash[:menu_file] && block
    return @@menus[0][root] = block
  end

  # If just root, we'll use use class with that name
  if block.nil? && (hash.nil? || hash[:class])
    clazz = hash ? hash[:class] : root
    clazz.sub!(/(\w+)/) {TextUtil.snake_case $1} if hash
    self.add root do |path|
      # Make class me camel case, and change Launcher.invoke to Menu.call
      Launcher.invoke clazz, path
    end
    return
  end

  menu = hash[:menu]
  if menu
    if menu =~ /\A\/.+\.\w+\z/   # If it's a file (1 line and has extension)
      require_menu menu
      return
    elsif menu =~ /\A[\w \/-]+\z/   # If it's a menu to delegate to
      self.add root do |path|
        Menu.call menu, Tree.rootless(path)
      end
      return
    end

    self.add root do |path|   # If text of the actual menu
      # Different from Menu[...] or .drill?
      Tree.children menu, Tree.rootless(path)
    end
    return
  end

  raise "Don't know how to deal with: #{root}, #{hash}, #{block}"
end

.append_log(path) ⇒ Object



988
989
990
991
992
993
994
995
996
997
998
# File 'lib/xiki/launcher.rb', line 988

def self.append_log path
  return if View.name =~ /_log.notes$/

  path = path.sub /^[+-] /, ''   # Remove bullet
  path = "#{path}/" if path !~ /\//   # Append slash if just root without path

  return if path =~ /^(h|log|last)\//

  path = "- #{path}"
  File.open(@@log, "a") { |f| f << "#{path}\n" } rescue nil
end

.as_deleteObject



1268
1269
1270
1271
# File 'lib/xiki/launcher.rb', line 1268

def self.as_delete
  Keys.prefix = "delete"
  Launcher.launch
end

.as_openObject



1273
1274
1275
1276
# File 'lib/xiki/launcher.rb', line 1273

def self.as_open
  Keys.prefix = "open"
  Launcher.launch
end

.as_updateObject



1263
1264
1265
1266
# File 'lib/xiki/launcher.rb', line 1263

def self.as_update
  Keys.prefix = "update"
  Launcher.launch :leave_bullet=>1
end

.do_last_launch(options = {}) ⇒ Object



740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
# File 'lib/xiki/launcher.rb', line 740

def self.do_last_launch options={}
  orig = View.index

  CLEAR_CONSOLES.each do |buffer|
    View.clear buffer
  end

  prefix = Keys.prefix :clear=>true

  if prefix ==:u || options[:here]
    View.to_nth orig
  else
    Move.to_window 1
    if prefix.is_a? Fixnum
      View.line = prefix
    end
  end

  line = Line.value

  # Go to parent and collapse, if not at left margin, and buffer modified (shows we recently inserted)
  if ! Color.at_cursor.member?("color-rb-light")   #&& line !~ /^ *[+-] /  # and not a bullet
    if line =~ /^ /
      Tree.to_parent
    end
    Tree.kill_under
  end

  Launcher.launch_or_hide :blink=>true, :no_search=>true
  View.to_nth orig
end

.enter_allObject



1278
1279
1280
1281
1282
1283
# File 'lib/xiki/launcher.rb', line 1278

def self.enter_all
  return FileTree.enter_lines(/.*/) if Line.blank?

  Keys.prefix = "all"
  Launcher.launch
end

.enter_last_launchedObject



778
779
780
# File 'lib/xiki/launcher.rb', line 778

def self.enter_last_launched
  Launcher.insert self.last_launched_menu
end

.enter_outlineObject



1285
1286
1287
1288
1289
1290
1291
# File 'lib/xiki/launcher.rb', line 1285

def self.enter_outline
  return FileTree.enter_lines if Line.blank?   # Prompts for bookmark

  # If there's a numeric prefix, add it
  Keys.add_prefix "outline"
  Launcher.launch
end

.file_and_mode_hooksObject



721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
# File 'lib/xiki/launcher.rb', line 721

def self.file_and_mode_hooks
  if View.mode == :dired_mode
    filename = $el.dired_get_filename
    # If dir, open tree
    if File.directory?(filename)
      FileTree.ls :dir=>filename
    else   # If file, do full file search?
      History.open_current :all => true, :paths => [filename]
    end
    return true
  end
  if View.name =~ /^\*ol/   # If in an ol output log file
    OlHelper.launch
    Effects.blink(:what=>:line)
    return true
  end
  return false
end

.hideObject



191
192
193
# File 'lib/xiki/launcher.rb', line 191

def self.hide
  Tree.kill_under
end

.init_default_launchersObject



510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
# File 'lib/xiki/launcher.rb', line 510

def self.init_default_launchers

  self.add(/^\$ /) do |l|   # $ shell command inline (sync)
    Console.launch :sync=>true
  end

  self.add /^%( |$)/ do   # % shell command (async)
    Console.launch_async
  end

  self.add /^&( |$)/ do   # % shell command in iterm
    Console.launch_async :iterm=>1
  end

  # %\n  | multiline\n  | commands
  Launcher.add /^\%\// do   # For % with nested quoted lines
    path = Tree.construct_path :list=>1

    next if path[-1] !~ /^\| /

    txt = Tree.siblings :string=>1

    orig = Location.new
    Console.to_shell_buffer
    View.to_bottom
    Console.enter txt
    orig.go
  end

  self.add(/^(http|file).?:\/\/.+/) do |path|
    Launcher.append_log "- http/#{path}"

    prefix = Keys.prefix
    Keys.clear_prefix

    url = path[/(http|file).?:\/\/.+/]
    if prefix == "all"
      txt = RestTree.request("GET", url)
      txt = Tree.quote(txt) if txt =~ /\A<\w/
      Tree.under Tree.quote(txt), :no_slash=>1
      next
    end
    url.gsub! '%', '%25'
    url.gsub! '"', '%22'
    prefix == :u ? $el.browse_url(url) : Firefox.url(url)
  end

  self.add(/^\$[^ #*!\/]+$/) do |line|   # Bookmark
    View.open Line.without_indent(line)
  end

  self.add(/^(p )?[A-Z][A-Za-z]+\.(\/|$)/) do |line|
    line.sub! /^p /, ''
    Code.launch_dot_at_end line
  end

  self.add(/^p /) do |line|
    CodeTree.run line
  end

  self.add(/^ *pp /) do |line|
    CodeTree.run line
  end

  self.add(/^ *puts /) do |line|
    CodeTree.run line
  end

  self.add(/^ *print\(/) do |line|
    Javascript.launch
  end

  self.add(/^ *$/) do |line|  # Empty line
    View.beep
    View.message "There was nothing on this line to launch."
  end

  self.add(/^\*$/) do |line|  # *... buffer
    Line.sub! /.+/, "all"

    Launcher.launch
  end

  self.add(/^\*./) do |line|  # *... buffer
    name = Line.without_label.sub(/\*/, '')
    View.to_after_bar
    View.to_buffer name
  end

  # Must have at least 2 slashes!
  self.add(/^[^\|@:]+\/\w+\/[\/\w\-]+\.\w+:\d+/) do |line|  # Stack traces, etc
    # Match again (necessary)
    line =~ /([$\/.\w\-]+):(\d+)/
    path, line = $1, $2

    if path =~ /^(\w.*)/ || path =~ /^\.\/(.+)/

      path = $1

      local_path = "#{View.dir}/#{path}".sub "//", "/"
      xiki_path = "#{Xiki.dir}/#{path}".sub "//", "/"
      if File.exists? local_path
        path = local_path
      elsif File.exist? xiki_path
        path = xiki_path
      end

    else
      return ".flash - File doesn't exist!" if ! File.exists? path
    end

    View.open path
    View.to_line line.to_i
  end

  # Xiki protocol to server
  self.add(/^[a-z-]{2,}\.(com|net|org|loc|in|edu|gov|uk)(\/|$)/) do |line|  # **.../: Tree grep in dir
    self.web_menu line
  end

  self.add(/^localhost:?\d*(\/|$)/) do |line|
    self.web_menu line
  end

  self.add(/^ *(Ol\.line|Ol << )/) do
    View.layout_output :called_by_launch=>1
  end

  # Example code in method comments
  #   /tmp/foo.rb
  #     class Foo
  #       # Control-enter to run this line
  #       # Foo.bar
  #       def self.bar
  Launcher.add /^class (\w+)\/\#.+/ do |path|
    # Remove comment and run
    txt = Line.value.sub /^ +# /, ''
    result = Code.eval(txt)
    next Tree.<<(CodeTree.draw_exception(result[2], txt), :no_search=>1) if result[2]
    next Tree.<< result[0].to_s, :no_slash=>1 if result[0]   # Returned value
    Tree.<< result[1].to_s, :no_slash=>1 if result[1].any?   # Stdout
  end

  Launcher.add /^class (\w+)\/def self.menu\/(.+)/ do |path|
    clazz, path = path.match(/^class (\w+)\/def self.menu\/(.+)/)[1..2]

    path = "#{TextUtil.snake_case clazz}/#{path}".gsub("/.", '/')

    Tree << Menu[path]
  end

  Launcher.add /^  +<+@ .+/ do
    Menu.root_collapser_launcher
  end

  Launcher.add /^  +<+ .+/ do
    Menu.collapser_launcher
  end

  Launcher.add /^  +<+= .+/ do
    Menu.replacer_launcher
  end

  Launcher.add /^[a-z]+\+[a-z+]+\/?$/ do |path|
    txt = %`
      | If you were told to "type #{path}", it is meant that you should
      | "type the acronym" while holding down control. This means
      | you should type:
      |
      |   #{Keys.human_readable(path)}
      `
    Tree.<< txt, :no_slash=>1
  end

  # Menu launchers

  Launcher.add "log" do # |path|
    Launcher.log# Tree.rootless(path)
  end

  Launcher.add "last" do |path|
    Launcher.last path
  end

  # ...Tree classes

  # RestTree
  condition_proc = proc {|list| RestTree.handles? list}
  Launcher.add condition_proc do |list|
    RestTree.launch :path=>list
  end

  # FileTree
  condition_proc = proc {|list| FileTree.handles? list}
  Launcher.add condition_proc do |list|
    FileTree.launch list
  end

  # CodeTree
  condition_proc = proc {|list| CodeTree.handles? list}
  Launcher.add condition_proc do |list|
    CodeTree.launch :path=>list
  end

  # UrlTree
  condition_proc = proc {|list| UrlTree.handles? list}
  Launcher.add condition_proc do |list|
    UrlTree.launch :path=>list
  end
end

.insert(txt, options = {}) ⇒ Object

Insert menu right here and launch it

Launcher.open “computer”



1005
1006
1007
1008
1009
# File 'lib/xiki/launcher.rb', line 1005

def self.insert txt, options={}
  View.insert txt
  $el.open_line(1)
  Launcher.launch options
end

.invoke(clazz, path, options = {}) ⇒ Object



799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
# File 'lib/xiki/launcher.rb', line 799

def self.invoke clazz, path, options={}

  default_method = "menu"
  # If dot, extract it as method
  if clazz =~ /\./
    clazz, default_method = clazz.match(/(.+)\.(.+)/)[1..2]
  end

  if clazz.is_a? String
    # Require it to be camel case (because .invoke will be Menu.call "Class"
    # if lower case, will assume Menu.call "path"
    camel = TextUtil.camel_case clazz
    clazz = $el.instance_eval(camel, __FILE__, __LINE__) rescue nil

  elsif clazz.is_a? Class
    camel = clazz.to_s
  end

  snake = TextUtil.snake_case camel

  raise "No class '#{clazz || camel}' found in launcher" if clazz.nil?

  # reload 'path_to_class'
  Menu.load_if_changed File.expand_path("~/menu/#{snake}.rb")

  args = path.is_a?(Array) ?
    path : Menu.split(path, :rootless=>1)

  # Call .menu_before if there...

  method = clazz.method("menu_before") rescue nil

  self.set_env_vars path

  if method
    code = "#{camel}.menu_before *#{args.inspect}"
    returned, out, exception = Code.eval code

    return CodeTree.draw_exception exception, code if exception
    if returned

      # TODO: call .unset_env_vars before this and other below places we return

      returned = returned.unindent if returned =~ /\A[ \n]/
      return returned
    end
  end

  menu_arity = nil
  txt = options[:tree]

  # Call .menu with no args to get child menus or route to other method...

  if txt.nil?
    method = clazz.method(default_method) rescue nil
    if method && method.arity == 0
      menu_arity = 0
      code = "#{camel}.#{default_method}"
      returned, out, exception = Code.eval code
      return CodeTree.draw_exception exception, code if exception
      txt = CodeTree.returned_to_s returned   # Convert from array into string, etc.
    end
  end

  # Error if no menu method or file
  if method.nil? && txt.nil? && ! args.find{|o| o =~ /^\./}

    cmethods = clazz.methods - Class.methods
    return cmethods.sort.map{|o| ".#{o}/"}
  end


  # If got routable menu text, use it to route (get children or dotify)...

  if txt
    txt = txt.unindent if txt =~ /\A[ \n]/
    raise "#{code} returned nil, but is supposed to return something when it takes no arguments" if txt.nil?

    tree = txt

    txt = Tree.children tree, args

    if txt && txt != "- */\n"
      # Pass in output of menu as either:
        # ENV['output']
        # 1st parameter: .menu_after output, *args
      return self.invoke_menu_after clazz, txt, args
    end

    # Copy dots onto args, so last dotted one will be used as action

    Tree.dotify! tree, args

    # TODO: when to invoke this?
      # Maybe invoke even if there was no .menu method
        # Is that happening now?

    # If .menu_hidden exists, dotify based on its output as well...
    method = clazz.method("menu_hidden") rescue nil
    if method
      returned, out, exception = Code.eval "#{camel}.menu_hidden"

      if returned && returned.is_a?(String)
        returned = returned.unindent
        Tree.dotify! returned, args
      end
    end

  end
  # Else, continue on to run it based on routified path


  # TODO: Maybe extract this out into .dotified_to_ruby ?

  # Figure out which ones are actions

  # Last .dotted one is the action, and non-dotted are variables to pass
  actions, variables = args.partition{|o| o =~ /^\./ }
  action = actions.last || ".#{default_method}"
  action.gsub! /[ -]/, '_'
  action.gsub! /[^\w.]/, ''

  # Call .menu_after if appropriate...

  if action == ".menu" && txt == nil && menu_arity == 0
    return self.invoke_menu_after clazz, txt, args
  end

  args = variables.map{|o| "\"#{CodeTree.escape o}\""}.join(", ")

  # TODO: use adapter here, so we can call .js file?

  # TODO .menu_after: Check for arity - if mismatch, don't call, but go straight to .menu_after!
  # We could probably not worry about this for now?

  code = "#{camel}#{action.downcase} #{args}".strip

  txt, out, exception = Code.eval code
  txt = CodeTree.returned_to_s(txt)   # Convert from array into string, etc.
  self.unset_env_vars

  txt = txt.unindent if txt =~ /\A[ \n]/

  return CodeTree.draw_exception exception, code if exception

  txt = self.invoke_menu_after clazz, txt, args

  self.unset_env_vars

  txt
end

.invoke_menu_after(clazz, txt, args) ⇒ Object



951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
# File 'lib/xiki/launcher.rb', line 951

def self.invoke_menu_after clazz, txt, args
  camel = clazz.to_s
  method = clazz.method("menu_after") rescue nil
  return txt if method.nil?

  code = "#{camel}.menu_after #{txt.inspect}, *#{args.inspect}"
  returned, out, exception = Code.eval code

  return CodeTree.draw_exception exception, code if exception
  if returned

    # TODO: call .unset_env_vars before this and other below places we return

    returned = returned.unindent if returned =~ /\A[ \n]/
    return returned
  end

  txt   # Otherwise, just return output!"

end

.last(path = nil, options = {}) ⇒ Object



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/xiki/launcher.rb', line 75

def self.last path=nil, options={}
  path = path.sub /^last\/?/, '' if path

  paths = IO.readlines self.log_file

  # If nothing passed, just list all roots

  if path.blank?
    paths.map!{|o| o.sub /\/.+/, '/'}   # Cut off after path
    paths = paths.reverse.uniq
    paths.delete "- #{options[:omit]}/\n" if options[:omit]
    return paths.join
  end

  # Root passed, so show all matches

  paths = paths.select{|o| o =~ /^- #{Notes::LABEL_REGEX}#{path}\/./}

  bullet = options[:quoted] ? "|" : "-"

  if options[:exclude_path]
    paths.each{|o| o.sub! /^- (#{Notes::LABEL_REGEX})#{path}\//, "#{bullet} \\1"}
    paths = paths.select{|o| o != "#{bullet} "}
  else
    paths = paths.map{|o| o.sub /^- #{Notes::LABEL_REGEX}/, '\\0@'}
  end
  paths = paths.reverse.uniq
  paths.delete_if{|o| o == "| \n"}
  paths.join
end

.last_launched_menuObject



782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
# File 'lib/xiki/launcher.rb', line 782

def self.last_launched_menu
  bm = Keys.input(:timed => true, :prompt => "bookmark to show launches for (* for all): ")

  menu =
    if bm == "8" || bm == " "
      "- search/launched/"
    elsif bm == "."
      "- Search.launched '#{View.file}'/"
    elsif bm == "3"
      "- Search.launched '#'/"
    elsif bm == ";" || bm == ":" || bm == "-"
      "- Search.launched ':'/"
    else
      "- search/launched/$#{bm}/"
    end
end

.launch(options = {}) ⇒ Object

Call the appropriate launcher if we find one, passing it line



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/xiki/launcher.rb', line 196

def self.launch options={}

  # Add linebreak at end if at end of file and none
  Line.<<("\n", :dont_move=>1) if Line.right == View.bottom

  Tree.plus_to_minus unless options[:leave_bullet]

  Line.sub! /^\.$/, './'
  Line.sub! /^~$/, '~/'

  # Maybe don't blink when in $__small_menu_box!"
  Effects.blink(:what=>:line) if options[:blink]
  line = options[:line] || Line.value   # Get paren from line
  label = Line.label(line)

  if line =~ /^ *@$/
    matches = Launcher.menu_keys
    Tree.<< matches.sort.map{|o| "<< #{o.gsub '_', ' '}/"}.join("\n"), :no_slash=>1
    return
  end

  # Special hooks for specific files and modes
  return if self.file_and_mode_hooks

  $xiki_no_search = options[:no_search]   # If :no_search, disable search

  is_root = false

  if line =~ /^( *)[+-] [^\n\(]+?\) (.+)/   # Split off label, if there
    line = $1 + $2
  end
  if line =~ /^( *)[+-] (.+)/   # Split off bullet, if there
    line = $1 + $2
  end
  if line =~ /^ *@ ?(.+)/   # Split off @ and indent if @ exists
    is_root = true
    line = $1
  end

  # Special case to turn launchers back on
  return self.show_or_launch if line == "launcher/setup/show or launch/"

  # Try each potential regex match...

  @@launchers.each do |regex, block|
    # If we found a match, launch it
    if line =~ regex
      group = $1

      # Run it
      if @@just_show
        Ol << "- regex: #{regex.to_s}\n- group: #{group}"
      else

        begin
          block.call line
        rescue RelinquishException
          next   # They didn't want to handle it, keep going
        rescue Exception=>e
          # Show error and sourche of block
          Tree.<< CodeTree.draw_exception(e, block.to_source), :no_slash=>true
        end

      end
      $xiki_no_search = false
      return true
    end
  end

  # If current line is indented and not passed recursively yet, try again, passing tree...

  if Line.value =~ /^ / && ! options[:line] && !is_root   # If indented, call .launch recursively

    # Use Xiki.branch here? (breaks up by @'s)

    # merge together (spaces if no slashes) and pass that to launch

    list = Tree.construct_path :list=>true, :ignore_ol=>1   # Get path to pass to procs, to help them decide

    found = list.index{|o| o =~ /^@/} and list = list[found..-1]   # Remove before @... node if any
    merged = list.map{|o| o.sub /\/$/, ''}.join('/')
    merged << "/" if list[-1] =~ /\/$/

    # Recursively call again with full path
    return self.launch options.merge(:line=>merged)

    # What was this doing, did we mean to only pass on :no_search??
    #       return self.launch options.slice(:no_search).merge(:line=>merged)
  end

  if self.launch_by_proc   # Try procs (currently all trees)
    return $xiki_no_search = false
  end

  # If nothing found so far, don't do anything if...
  if line =~ /^\|/
    View.beep
    return View.message "Don't know what to do with this line"
  end

  # See if it's a menu...

  self.set_env_vars line

  result = self.try_menu_launchers line, options
  self.unset_env_vars
  return if result

  # Do "autocomplete" - show all menus that start with this...

  if line =~ /^([\w -]*)$/ || line =~ /^([\w -]*)\.\.\.\/?$/

    #     if line =~ /^([\w -]*)(\.\.\.)?\/?$/
    # TODO just check for exact match in dir, and load it if no launcher yet!

    root = $1
    root.gsub!(/[ -]/, '_') if root
    matches = self.menu_keys.select do |possibility|
      possibility =~ /^#{root}/
    end
    if matches.any?
      if matches.length == 1
        match = matches[0].gsub '_', ' '
        Line.sub! /^([ @+-]*).*/, "\\1#{match}"
        Launcher.launch
        return
      end

      Line.sub! /\b$/, "..."

      View.under matches.sort.map{|o| "<< #{o.gsub '_', ' '}/"}.join("\n")
      return
    end
  end

  # If just root line, load any unloaded launchers this completes and relaunch...

  # Failed attempt to not auto-complete if slash
    # It's tough because we still want to load!
  # Don't do if ends with slash? - does this mean it won't load unloaded?

  if line =~ /^([\w -]+)\/?$/ && ! options[:recursed]
    root = $1
    root.gsub!(/[ -]/, '_') if root

    ["~/menu", Bookmarks["$x/menu"]].each do |dir|

      matches = Dir[File.expand_path("#{dir}/#{root}*")]

      if matches.any?
        matches.sort.each do |file|
          iroot = file[/\/(\w+)\./, 1]
          next if @@menus[0][root] || @@menus[1][root]   # Skip if already loaded
          require_menu(file)  # if File.exists? file
        end
        return self.launch :recursed=>1   # options.slice(:no_search).merge(:line=>merged)
      end

    end
  end

  if root = line[/^[\w -]+/]

    Xiki.dont_search
    # Maybe make the following print out optionally, via a 'help_last' block?
    Tree << "
      | There's no \"#{root}\" menu yet. Create it? You can start by adding items
      | right here, or you can create a class.
      <= @menu/create/here/
      <= @menu/create/class/
      <= @menu/install/gem/
      "
  else
    View.flash "- No launcher matched!"
  end
  $xiki_no_search = false
end

.launch_by_proc(list = nil) ⇒ Object



492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
# File 'lib/xiki/launcher.rb', line 492

def self.launch_by_proc list=nil
  list = Tree.construct_path(:list=>true)   # Get path to pass to procs, to help them decide

  # Try each proc
  @@launchers_procs.each do |launcher|   # For each potential match
    condition_proc, block = launcher
    if found = condition_proc.call(list)   # If we found a match, launch it
      if @@just_show
        Ol << condition_proc.to_source
      else
        block.call list[found..-1]
      end
      return true
    end
  end
  return false
end

.launch_or_hide(options = {}) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
# File 'lib/xiki/launcher.rb', line 179

def self.launch_or_hide options={}
  # If no prefixes and children exist, delete under
  if ! Keys.prefix and ! Line.blank? and Tree.children?
    Tree.minus_to_plus
    Tree.kill_under
    return
  end

  # Else, launch
  self.launch options
end

.like_menu(item, options = {}) ⇒ Object

Launches “menu/item”, first prompting for name. Used by search+like_menu and other places.

If matches substring, shows the possible matches and does an isearch.

Menu.like_menu “htm”



1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
# File 'lib/xiki/launcher.rb', line 1234

def self.like_menu item, options={}

  # return
  return if item.nil?

  menu = Keys.input :timed=>true, :prompt=>"Enter menu to pass '#{item}' to (space if menu): "

  return self.open(item, options) if menu == " "   # Space means text is the menu

  matches = self.menu_keys.select do |possibility|
    possibility =~ /^#{menu}/
  end

  if matches.length == 1
    return self.open("- #{matches[0]}/#{item}", options)
  end

  self.open(matches.map{|o| "- #{o}/#{item}\n"}.join(''), options.merge(:choices=>1))
  right = View.cursor
  Move.to_previous_paragraph
  # return
  Tree.search :left=>View.cursor, :right=>right
end

.logObject



106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/xiki/launcher.rb', line 106

def self.log

  lines = IO.readlines self.log_file

  # If parent, narrow down to just it
  trunk = Xiki.trunk
  if trunk.length > 1 && trunk[-2] != "menu/history"   # Show all if under this menu
    lines = lines.select {|o| o.start_with? "- #{trunk[-2]}"}
  end

  lines.reverse.uniq.map{|o| o.sub /^- /, '<< '}.join
end

.log_fileObject



119
120
121
# File 'lib/xiki/launcher.rb', line 119

def self.log_file
  @@log
end


46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/xiki/launcher.rb', line 46

def self.menu
  %`
  - .setup/
    > Toggle Temporarily just showing the launcher that matched
    - .show or launch/
  - docs/
    > Summary
    Launcher is the class that handles "launching" things (double-clicking on a
    line, or typing Ctrl-enter).

    > See
    @launcher/api/
  - api/
    > Open menu in new buffer
    @ Launcher.open "computer"

    > Insert monu
    @ Launcher.insert "computer"   # Assumes you're on a blank line

    > Invoke (used behind the scenes?)
    @ p Launcher.invoke 'Computer', 'computer/ip/'
  `
end


42
43
44
# File 'lib/xiki/launcher.rb', line 42

def self.menu_keys
  (@@menus[0].keys + @@menus[1].keys).sort.uniq #.select do |possibility|
end


38
39
40
# File 'lib/xiki/launcher.rb', line 38

def self.menus
  @@menus
end

.method_missing(*args, &block) ⇒ Object



1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
# File 'lib/xiki/launcher.rb', line 1064

def self.method_missing *args, &block

  arg = args.shift

  if block.nil?
    if args == []   # Trying to call menu with no args
      return Menu.call arg.to_s
    end
    if args.length == 1 && args[0].is_a?(String)   # Trying to call menu with args / path?
      return
    end
  end

  raise "Menu.#{arg} called with no block and no args" if args == [] && block.nil?
  self.add arg.to_s, args[0], &block
end

.open(menu, options = {}) ⇒ Object

Open new buffer and launch the menu in it

Launcher.open “ip”



1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
# File 'lib/xiki/launcher.rb', line 1020

def self.open menu, options={}
  return self.insert(menu, options) if options[:inline]

  $el.sit_for 0.25 if options[:delay] || options[:first_letter]   # Delay slightly, (avoid flicking screen when they type command quickly)

  View.to_after_bar if View.in_bar? && !options[:bar_is_fine]

  dir = View.dir

  # For buffer name, handle multi-line strings
  buffer = menu.sub(/.+\n[ -]*/m, '').gsub(/[.,]/, '')
  buffer = "@" + buffer.sub(/^[+-] /, '')
  View.to_buffer buffer, :dir=>dir

  View.clear
  Notes.mode
  View.wrap :off

  txt = menu

  if txt.blank?
    return View.insert("\n", :dont_move=>1)
  end

  dir = options[:dir] and txt = "- #{dir.sub /\/$/, ''}/\n  - #{txt}"

  View << txt

  $el.open_line 1

  if options[:choices]
    View.to_highest
    Tree.search
    return
  end

  if options[:no_launch]
    View.to_highest
    return
  end

  Launcher.launch options
end

.reload_menu_dirsObject



1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
# File 'lib/xiki/launcher.rb', line 1213

def self.reload_menu_dirs
  MENU_DIRS.each do |dir|
    next unless File.directory? dir
    Files.in_dir(dir).each do |f|
      next if f !~ /^[a-z].*\..*[a-z]$/ || f =~ /__/
      path = "#{dir}/#{f}"
      stem = f[/[^.]*/]
      self.add stem, :menu=>path
    end
  end
  "- reloaded!"
end

.search_like_menuObject



1258
1259
1260
1261
# File 'lib/xiki/launcher.rb', line 1258

def self.search_like_menu
  txt = Search.stop
  self.like_menu txt
end

.set_env_vars(path) ⇒ Object



1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
# File 'lib/xiki/launcher.rb', line 1293

def self.set_env_vars path

  return if ! $el

  # TODO: I guess they'll need to be set from somewhere else as well?

  ENV['prefix'] = Keys.prefix.to_s

  args = path.is_a?(Array) ?
    path : Menu.split(path, :rootless=>1)

  # ?? If any has ^|, then make sure current line has slash

  quoted = args.find{|o| o =~ /^\|( |$)/}

  if ! quoted
    return ENV['txt'] = args[-1]
  end

  # Quoted lines

  txt = Tree.leaf("|")   # Cheat to make it grab quoted
  ENV['txt'] = txt.length > 1_000_000 ? "*too long to put into env var*" : txt
end

.show(menu, options = {}) ⇒ Object



1011
1012
1013
# File 'lib/xiki/launcher.rb', line 1011

def self.show menu, options={}
  self.open menu, options.merge(:no_launch=>1)
end

.show_or_launchObject



70
71
72
73
# File 'lib/xiki/launcher.rb', line 70

def self.show_or_launch
  @@just_show = ! @@just_show
  View.flash "- Will #{@@just_show ? 'just show' : 'actually launch'}!"
end

.try_menu_launchers(line, options = {}) ⇒ Object



374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
# File 'lib/xiki/launcher.rb', line 374

def self.try_menu_launchers line, options={}
  # If there's a /@ in the path, cut it off
  line.sub! /.+\/@/, ''

  root_orig = root = line[/^[\w -]+/]   # Grab thing to match
  root = TextUtil.snake_case root if root

  self.append_log line
  trunk = Xiki.trunk

  # If menu nested under dir or file, chdir first

  orig_pwd = nil
  if trunk.size > 1 && closest_dir = Tree.closest_dir
    orig_pwd = Dir.pwd   # Where ruby pwd was before

    if root == "mkdir"
      Dir.chdir "/tmp/"
    elsif File.directory?(closest_dir) || is_file = File.file?(closest_dir)   # If dir path
      closest_dir = File.dirname closest_dir if is_file

      Dir.chdir closest_dir

      # If file, make path only have dir
      # remove file

    else   # If doesn't exist
      Tree.<< "> Dir doesn't exist.  Create it?\n@mkdir/\n"
      return true
    end
  end

  # If there is a matching .menu, use it

  out = nil
  if block_dot_menu = @@menus[0][root]

    if @@just_show
      Ol.line "Maps to .menu file, for menu: #{root}\n - #{block_dot_menu}\n - #{block_dot_menu.to_source}"
      View.flash "- Showed launcher in $o", :times=>4
      return true   # To make it stop trying to run it
    end

    begin
      out = Tree.output_and_search block_dot_menu, :line=>line  #, :dir=>file_path
    ensure
      Dir.chdir orig_pwd if orig_pwd
    end

    # If .menu file matched but had no output, and no other block to delegate to, say we handled it so it will stop looking

    if ! out
      require_menu File.expand_path("~/menu/#{root}.rb"), :ok_if_not_found=>1
      if ! @@menus[1][root]
        Tree << "
          | This menu item does nothing yet.  You can update the .menu file to
          | give it children or create a class to give it dynamic behavior:
          <= @menu/create/class/
          "
        return true
      end
    end
    return true if out   # Output means we handled it, otherwise continue on and try class
  end

  # If there is a matching .rb for the menu, use it

  if block_other = @@menus[1][root]   # If class menu

    if @@just_show
      Ol.line << "Maps to class or other block, for menu: #{root}\n - #{block_other}\n - #{block_other.to_source}"
      View.flash "- Showed launcher in $o", :times=>4
      return true   # To make it stop trying to run it
    end

    begin
      Tree.output_and_search block_other, options.merge(:line=>line)  #, :dir=>file_path
    ensure
      Dir.chdir orig_pwd if orig_pwd
    end

    return true
  end

  # If uppercase, try invoking on in-memory class
  if root_orig =~ /^[A-Z]/

    if @@just_show
      Ol.line << "Maps to in-memory class for: #{root}"
      View.flash "- Showed launcher in $o", :times=>4
      return true   # To make it stop trying to run it
    end

    begin

      lam = lambda do |path|
        Launcher.invoke root_orig, path
      end

      #         Launcher.invoke__
      #         do |path|
      #           # Make class me camel case, and change Launcher.invoke to Menu.call
      #         end

      Tree.output_and_search lam, options.merge(:line=>line)  #, :dir=>file_path
    ensure
      Dir.chdir orig_pwd if orig_pwd
    end

    return true
  end

  # Pull into other function?
    # re-use code that calls class wrapper

  false   # No match, keep looking
end

.unset_env_varsObject



1332
1333
1334
1335
# File 'lib/xiki/launcher.rb', line 1332

def self.unset_env_vars
  ENV['prefix'] = nil
  ENV['txt'] = nil
end

.urlsObject

Used any more? - should be replaced by menu log - delete this



773
774
775
776
# File 'lib/xiki/launcher.rb', line 773

def self.urls
  txt = File.read File.expand_path("~/.emacs.d/url_log.notes")
  txt = txt.split("\n").reverse.uniq.join("\n")
end

.web_menu(line) ⇒ Object



1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
# File 'lib/xiki/launcher.rb', line 1318

def self.web_menu line
  Line << "/" unless Line =~ /\/$/
  url = "http://#{line}"
  url.sub! /\.\w+/, "\\0/xiki"
  url.gsub! ' ', '+'

  begin
    response = HTTParty.get(url)
    Tree << response.body
  rescue Exception=>e
    Tree << "- couldn't connect!"
  end
end

.wrapper(path) ⇒ Object



1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
# File 'lib/xiki/launcher.rb', line 1081

def self.wrapper path

  # If starts with bookmark, expand as file (not dir)

  path = Bookmarks.expand path, :file_ok=>1

  # TODO: make generic
  # TODO: load all the adapters and construct the "rb|js" part of the regex

  # Don't match if it's quoted (after a pipe)
  match = path.match(/^([^|]+\/)(\w+)\.(rb|js|coffee|py|notes|menu|haml)\/(.*)/)
  if match
    dir, file, extension, path = match[1..4]
    # TODO: instead, call Launcher.invoke JsAdapter(dir, path), path
    self.send "wrapper_#{extension}", dir, "#{file}.#{extension}", path
    return true   # Indicate we handled it
  end

  # For matches to filename instead of extensions?
  match = path.match(/^([^|]+\/)(Rakefile)\/(.*)/)
  if match
    dir, file, path = match[1..4]
    # TODO: instead, call Launcher.invoke JsAdapter(dir, path), path
    self.send "wrapper_#{file.downcase}", dir, file, path
    return true   # Indicate we handled it
  end

  return false

end

.wrapper_coffee(dir, file, path) ⇒ Object



1133
1134
1135
1136
1137
1138
1139
1140
1141
# File 'lib/xiki/launcher.rb', line 1133

def self.wrapper_coffee dir, file, path
  txt = CoffeeScript.to_js("#{dir}#{file}")
  tmp_file = "/tmp/tmp.js"
  File.open(tmp_file, "w") { |f| f << txt }

  output = Console.run "node #{Xiki.dir}etc/wrappers/wrapper.js \"#{tmp_file}\" \"#{path}\"", :sync=>1, :dir=>dir
  output = Tree.children output, path
  Tree << output
end

.wrapper_haml(dir, file, path) ⇒ Object



1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
# File 'lib/xiki/launcher.rb', line 1177

def self.wrapper_haml dir, file, path

  engine = Haml::Engine.new(File.read "#{dir}#{file}")

  foos = ["foo1", "foo2", "foo3"]
  o = Object.new
  o.instance_eval do
    @foo = "Foooo"
    @foos = foos
  end

  txt = engine.render(o, "foo"=>"Fooooooo", "foos"=>foos)

  Tree << Tree.quote(txt)
end

.wrapper_js(dir, file, path) ⇒ Object



1127
1128
1129
1130
1131
# File 'lib/xiki/launcher.rb', line 1127

def self.wrapper_js dir, file, path
  output = Console.run "node #{Xiki.dir}etc/wrappers/wrapper.js \"#{dir}#{file}\" \"#{path}\"", :sync=>1, :dir=>dir
  output = Tree.children output, path
  Tree << output
end

.wrapper_menu(dir, file, path) ⇒ Object



1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
# File 'lib/xiki/launcher.rb', line 1160

def self.wrapper_menu dir, file, path
  heading, content = (path.match(/^(\| .+)(\| .*)?/) || [nil, nil])[1..2]

  #     output = Menu.drill "#{dir}/#{file}", heading, content

  #     output = Tree.children File.read(file), Tree.rootless(path)
  output = Tree.children File.read("#{Bookmarks[dir]}/#{file}"), path

  Tree << output
end

.wrapper_notes(dir, file, path) ⇒ Object



1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
# File 'lib/xiki/launcher.rb', line 1143

def self.wrapper_notes dir, file, path
  if match = path.match(/^(\| .+)(\| .*)/)
    heading, content = match[1..2]
    # [nil, nil])[1..2]
  else
    heading, content = [path, nil]
  end

  heading = nil if heading.blank?

  #     heading, content = (path.match(/^(\| .+)(\| .*)/) || [nil, nil])[1..2]

  dir = "#{dir}/" if dir !~ /\/$/
  output = Notes.drill "#{dir}#{file}", heading, content
  Tree << output
end

.wrapper_py(dir, file, path) ⇒ Object



1171
1172
1173
1174
1175
# File 'lib/xiki/launcher.rb', line 1171

def self.wrapper_py dir, file, path
  output = Console.run "python #{Xiki.dir}etc/wrappers/wrapper.py \"#{dir}#{file}\" \"#{path}\"", :sync=>1, :dir=>dir
  output = Tree.children output, path if path !~ /^\./
  Tree << output
end

.wrapper_rakefile(dir, file, path) ⇒ Object



1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
# File 'lib/xiki/launcher.rb', line 1193

def self.wrapper_rakefile dir, file, path

  # If just file passed, show all tasks

  if path.blank?
    txt = Console.sync "rake -T", :dir=>dir

    txt = txt.scan(/^rake (.+?) *#/).flatten

    Tree << txt.map{|o| "- #{o}/\n"}.join
    return
  end

  # Task name passed, so run it

  path.sub! /\/$/, ''
  Console.run "rake #{path}", :dir=>dir
  nil
end

.wrapper_rb(dir, file, path) ⇒ Object



1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
# File 'lib/xiki/launcher.rb', line 1112

def self.wrapper_rb dir, file, path
  output = Console.run "ruby #{Xiki.dir}/etc/wrappers/wrapper.rb #{file} \"#{path}\"", :sync=>1, :dir=>dir

  # Sensible thing for now is to just do literal output
  #     output = Tree.children output, path if path !~ /^\./

  # How to know when to do children?!
    # Because it called .menu, and menu had no args
      # Make it set env var?

  #     output = Tree.children output, path

  Tree << output
end