├── History.txt ├── lib ├── editscript │ ├── version.rb │ ├── execavailable.rb │ ├── config.rb │ └── main.rb ├── editscript.rb └── editscript.completion.bash ├── .gitignore ├── Gemfile ├── example_config.sh ├── bin └── editscript ├── test └── editscript_test.rb ├── Manifest.txt ├── .autotest ├── editscript.gemspec ├── Rakefile ├── README.md └── README.txt /History.txt: -------------------------------------------------------------------------------- 1 | === 1.0.0 / 2014-11-29 2 | 3 | * Today I became a gem 4 | -------------------------------------------------------------------------------- /lib/editscript/version.rb: -------------------------------------------------------------------------------- 1 | module EditScript 2 | VERSION = '1.0.3' 3 | end 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | html 3 | *.gem 4 | Gemfile.lock 5 | *.sublime* 6 | *.taskpaper 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'term-ansicolor', '~> 1.3.0' 3 | gem 'fuzzy_file_finder', '~> 1.0.4' 4 | gemspec 5 | -------------------------------------------------------------------------------- /example_config.sh: -------------------------------------------------------------------------------- 1 | # Suggested aliases 2 | 3 | alias eds="editscript -1" # edit first match automatically 4 | alias edsrb="editscript -o rb" 5 | alias edspy="editscript -o py" 6 | alias edsc="editscript -o h,m,c,cc" 7 | -------------------------------------------------------------------------------- /bin/editscript: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'editscript' 3 | 4 | if RUBY_VERSION.to_f > 1.9 5 | Encoding.default_external = Encoding::UTF_8 6 | Encoding.default_internal = Encoding::UTF_8 7 | end 8 | 9 | EditScript.search(ARGV) 10 | -------------------------------------------------------------------------------- /lib/editscript.rb: -------------------------------------------------------------------------------- 1 | require 'editscript/version.rb' 2 | require 'fuzzy_file_finder' 3 | require 'term/ansicolor' 4 | require 'readline' 5 | require 'shellwords' 6 | require 'editscript/execavailable.rb' 7 | require 'editscript/config.rb' 8 | require 'editscript/main.rb' 9 | -------------------------------------------------------------------------------- /test/editscript_test.rb: -------------------------------------------------------------------------------- 1 | require "minitest/autorun" 2 | require "editscript" 3 | 4 | class TestEditScript < MiniTest::Unit::TestCase 5 | def test_parsed_options_returns_true_for_valid_arguments 6 | task = EditScript.search(["-v"], '') 7 | assert_equal task, "" 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /Manifest.txt: -------------------------------------------------------------------------------- 1 | .autotest 2 | Manifest.txt 3 | History.txt 4 | README.md 5 | README.txt 6 | bin/editscript 7 | lib/editscript.completion.bash 8 | lib/editscript.rb 9 | lib/editscript/config.rb 10 | lib/editscript/execavailable.rb 11 | lib/editscript/main.rb 12 | lib/editscript/version.rb 13 | test/editscript_test.rb 14 | -------------------------------------------------------------------------------- /lib/editscript.completion.bash: -------------------------------------------------------------------------------- 1 | function _complete_editscript() 2 | { 3 | local word=${COMP_WORDS[COMP_CWORD]} 4 | if [[ -n $word ]]; then 5 | COMPREPLY=( $(editscript -s --nocolor $word | xargs basename | sed -e 's/ /\ /') ) 6 | else 7 | COMPREPLY=() 8 | fi 9 | } 10 | 11 | 12 | complete -o bashdefault -o filenames -o default -o nospace -F _complete_editscript eds editscript 2>/dev/null || \ 13 | complete -o default -o filenames -o nospace -F _complete_editscript eds editscript 14 | -------------------------------------------------------------------------------- /.autotest: -------------------------------------------------------------------------------- 1 | # -*- ruby -*- 2 | 3 | require "autotest/restart" 4 | 5 | Autotest.add_hook :initialize do |at| 6 | at.testlib = "minitest/autorun" 7 | at.add_exception "tmp" 8 | 9 | # at.extra_files << "../some/external/dependency.rb" 10 | # 11 | # at.libs << ":../some/external" 12 | # 13 | # at.add_exception "vendor" 14 | # 15 | # at.add_mapping(/dependency.rb/) do |f, _| 16 | # at.files_matching(/test_.*rb$/) 17 | # end 18 | # 19 | # %w(TestA TestB).each do |klass| 20 | # at.extra_class_map[klass] = "test/test_misc.rb" 21 | # end 22 | end 23 | 24 | # Autotest.add_hook :run_command do |at| 25 | # system "rake build" 26 | # end 27 | -------------------------------------------------------------------------------- /lib/editscript/execavailable.rb: -------------------------------------------------------------------------------- 1 | module ScriptingTools 2 | # ExecAvailable module for Array and String testing of 3 | # available system executables 4 | module ExecAvailable 5 | def self.included(base) 6 | allowed_classes = [Array, String] 7 | unless allowed_classes.include? base 8 | fail 'ExecAvailable included in an unsupported class' 9 | end 10 | super 11 | end 12 | 13 | def bins_available? 14 | bins_available(self)[0] 15 | end 16 | 17 | def missing_bins 18 | bins_available(self)[1] 19 | end 20 | 21 | private 22 | 23 | def bins_available(bins) 24 | bins = bins.split(/\s*[ ,]\s*/) if bins.class == String 25 | matched = [] 26 | missed = [] 27 | bins.each do |b| 28 | `type -t #{b}` 29 | $CHILD_STATUS.to_i == 0 ? matched.push(b) : missed.push(b) 30 | end 31 | # found == bins.length 32 | [matched.length == bins.length, missed] 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /editscript.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("../lib", __FILE__) 2 | # lib = File.expand_path('../lib', __FILE__) 3 | # $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'editscript/version' 5 | 6 | Gem::Specification.new do |s| 7 | s.name = 'editscript' 8 | s.version = EditScript::VERSION 9 | s.author = 'Brett Terpstra' 10 | s.email = 'me@brettterpstra.com' 11 | s.homepage = 'http://brettterpstra.com/' 12 | s.platform = Gem::Platform::RUBY 13 | s.summary = 'A fuzzy search and execute tool for people who write too many scripts' 14 | s.description = 'EditScript allows you to search through predefined folders or recently-accessed files for scripts with fuzzy matching in path names.' 15 | s.license = 'MIT' 16 | 17 | s.files = `git ls-files -z`.split("\x0") 18 | s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) } 19 | s.test_files = s.files.grep(%r{^(test|spec|features)/}) 20 | s.require_paths = ["lib"] 21 | s.extra_rdoc_files = ['README.md'] 22 | s.rdoc_options << '--title' << 'editscript' << '--main' << 'README.md' << '--markup' << 'markdown' << '-ri' 23 | s.bindir = 'bin' 24 | 25 | s.add_development_dependency "bundler", "~> 1.6" 26 | s.add_development_dependency 'rake', '~> 0' 27 | s.add_development_dependency 'rdoc', '~> 6.2', '>= 6.2.0' 28 | s.add_runtime_dependency 'term-ansicolor', '~> 1.3', '>= 1.3.0' 29 | s.add_runtime_dependency 'fuzzy_file_finder', '~> 1.0', '>= 1.0.4' 30 | s.add_development_dependency 'aruba', '~> 0' 31 | 32 | end 33 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/clean' 2 | require 'rubygems' 3 | require 'rubygems/package_task' 4 | require 'rdoc/task' 5 | 6 | Rake::RDocTask.new do |rd| 7 | rd.main = "README.md" 8 | rd.rdoc_files.include("README.md","lib/**/*.rb","bin/**/*") 9 | rd.title = 'editscript' 10 | rd.markup = 'markdown' 11 | end 12 | 13 | spec = eval(File.read('editscript.gemspec')) 14 | 15 | Gem::PackageTask.new(spec) do |pkg| 16 | end 17 | 18 | desc 'Install the gem in the current ruby' 19 | task :install, :all do |t, args| 20 | args.with_defaults(:all => false) 21 | if args[:all] != FalseClass 22 | sh "rvm all do gem install pkg/*.gem" 23 | sh "sudo gem install pkg/*.gem" 24 | else 25 | sh "gem install pkg/*.gem" 26 | end 27 | end 28 | 29 | desc 'Install the gem in the all rubies' 30 | task :installall do |t, args| 31 | Rake::Task[:install].invoke(true) 32 | end 33 | 34 | desc 'Development version check' 35 | task :ver do |t| 36 | system "grep VERSION lib/#{spec.name}/version.rb" 37 | end 38 | 39 | desc 'Bump incremental version number' 40 | task :bump, :type do |t, args| 41 | args.with_defaults(:type => "inc") 42 | version_file = "lib/#{spec.name}/version.rb" 43 | raise "File not found" unless File.exists? version_file 44 | content = IO.read(version_file) 45 | content.sub!(/VERSION = '(\d+)\.(\d+)\.(\d+)(\.\S+)?'/) {|m| 46 | major = $1.to_i 47 | minor = $2.to_i 48 | inc = $3.to_i 49 | pre = $4 50 | 51 | case args[:type] 52 | when /^maj/ 53 | major += 1 54 | minor = 0 55 | inc = 0 56 | when /^min/ 57 | minor += 1 58 | inc = 0 59 | else 60 | inc += 1 61 | end 62 | 63 | $stdout.puts "At version #{major}.#{minor}.#{inc}#{pre}" 64 | "VERSION = '#{major}.#{minor}.#{inc}#{pre}'" 65 | } 66 | File.open(version_file, 'w+') {|f| 67 | f.puts content 68 | } 69 | end 70 | 71 | def alias_task(tasks) 72 | tasks.each do |new_name, old_name| 73 | task new_name, [*Rake.application[old_name].arg_names] => [old_name] 74 | end 75 | end 76 | 77 | alias_task [ 78 | [:i, :install], 79 | [:ia, :installall], 80 | [:p, :package], 81 | [:c, :clobber] 82 | ] 83 | 84 | 85 | task :build => [:package] 86 | task :default => [:clobber,:rdoc,:package] 87 | task :gobig => [:default,:installall] 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EditScript 2 | 3 | Proof of concept using Fuzzy File Finder to locate a script to edit 4 | Searches a set of predefined locations for a fuzzy string 5 | e.g. "mwp" matches both "myweatherprogram" and "mowthelawnplease" 6 | on "(m)y(w)eather(p)rogram" and "(m)o(w)thelawn(p)lease" 7 | 8 | Results are ranked and a menu is displayed with the most likely 9 | match at the top. Editor to be launched and directories to search 10 | specified in CONFIG below. 11 | 12 | EditScript is designed to work with a shallow set of configured search paths. I tend to keep most of my work in ~/scripts, ~/bin, and a couple of project folders. Search paths can be defined in your shell environment with $EDITSCRIPT\_PATH. You can also set default file extension constraints with $EDITSCRIPT\_TYPES. The file finder that EditScript uses will choke on results with upwards of about 5000 matches. It's not designed for deep traversal or handling large repositories. 13 | 14 | ### Author 15 | 16 | Brett Terpstra 17 | 18 | ### Copyright 19 | 20 | Copyright (c) 2011 Brett Terpstra. Licensed under the MIT License 21 | 22 | ### License 23 | 24 | The MIT License 25 | 26 | Copyright (c) Ryan Davis, seattle.rb 27 | 28 | Permission is hereby granted, free of charge, to any person obtaining 29 | a copy of this software and associated documentation files (the 30 | 'Software'), to deal in the Software without restriction, including 31 | without limitation the rights to use, copy, modify, merge, publish, 32 | distribute, sublicense, and/or sell copies of the Software, and to 33 | permit persons to whom the Software is furnished to do so, subject to 34 | the following conditions: 35 | 36 | The above copyright notice and this permission notice shall be 37 | included in all copies or substantial portions of the Software. 38 | 39 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 40 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 41 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 42 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 43 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 44 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 45 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 46 | 47 | 48 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | = editscript 2 | 3 | == Synopsis 4 | 5 | Proof of concept using Fuzzy File Finder to locate a script to edit 6 | Searches a set of predefined locations for a fuzzy string 7 | e.g. "mwp" matches both "myweatherprogram" and "mowthelawnplease" 8 | on "(m)y(w)eather(p)rogram" and "(m)o(w)thelawn(p)lease" 9 | 10 | Results are ranked and a menu is displayed with the most likely 11 | match at the top. Editor to be launched and directories to search 12 | specified in CONFIG below. 13 | 14 | == Examples 15 | 16 | Search through configured directories for "mwp" 17 | editscript mwp 18 | 19 | == Usage 20 | 21 | editscript [options] "search string" 22 | 23 | For help use: editscript -h 24 | 25 | == Options 26 | 27 | -s, --show Show results without executing 28 | -n, --no-menu No menu interaction. Executes the highest ranked result 29 | or, with '-s', returns a plain list of results 30 | -1, --open_single Don't show the menu if there's only one result 31 | -a, --show-all Show all results, otherwise limited to top 10 32 | --scores Show match scoring with results 33 | -d, --debug Verbose debugging 34 | -h, --help help 35 | 36 | == Author 37 | 38 | Brett Terpstra 39 | 40 | == License: 41 | 42 | (The MIT License) 43 | 44 | Copyright (c) Ryan Davis, seattle.rb 45 | 46 | Permission is hereby granted, free of charge, to any person obtaining 47 | a copy of this software and associated documentation files (the 48 | 'Software'), to deal in the Software without restriction, including 49 | without limitation the rights to use, copy, modify, merge, publish, 50 | distribute, sublicense, and/or sell copies of the Software, and to 51 | permit persons to whom the Software is furnished to do so, subject to 52 | the following conditions: 53 | 54 | The above copyright notice and this permission notice shall be 55 | included in all copies or substantial portions of the Software. 56 | 57 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 58 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 59 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 60 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 61 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 62 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 63 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 64 | -------------------------------------------------------------------------------- /lib/editscript/config.rb: -------------------------------------------------------------------------------- 1 | require 'optparse' 2 | require 'optparse/time' 3 | require 'ostruct' 4 | 5 | unless OpenStruct.method_defined? :to_h 6 | class OpenStruct 7 | def to_h 8 | self.marshal_dump 9 | end 10 | end 11 | end 12 | 13 | module EditScript 14 | class Config 15 | # TODO: Add an rc file option in addition to environment variables 16 | def self.parse(args) 17 | defaults = { 18 | :editor => false, # $EDITSCRIPT_EDITOR 19 | :search_path => false, # $EDITSCRIPT_PATH 20 | :default_types => false, # $EDITSCRIPT_TYPES 21 | :options => { 22 | :ignore_only => false, # Ignore any file extension constraints 23 | :show => false, # Show results without executing 24 | :menu => true, # Don't display a menu, execute/show the highest ranked result 25 | :only => ENV['EDITSCRIPT_TYPES'] ? self.build_type_pattern(ENV['EDITSCRIPT_TYPES']) : [], # only show files matching extensions 26 | :showall => false, # Show all results, otherwise limited to top 10 27 | :limit => 10, 28 | :showscores => false, # Show match scoring with results 29 | :debug => false, # Verbose debugging 30 | :recent => false, 31 | :function_search => false, 32 | :open_single => false, 33 | :nocolor => false 34 | } 35 | } 36 | # The options specified on the command line will be collected in *options*. 37 | # We set default values here. 38 | config = OpenStruct.new({ 39 | 'editor' => defaults[:editor], 40 | 'search_path' => defaults[:search_path], 41 | 'options' => defaults[:options] 42 | }) 43 | 44 | opt_parser = OptionParser.new do |opt| 45 | 46 | opt.banner = "Usage: #{File.basename(__FILE__)} [config.options] 'search terms'" 47 | opt.separator "" 48 | opt.separator "Options:" 49 | 50 | opt.on("-v", "--version", "Display version and exit") do 51 | ver = EditScript::VERSION 52 | $stdout.puts "EditScript v#{ver}" 53 | EditScript.do_exit 54 | end 55 | 56 | opt.on("-l","--limit X","Limit to X results") do |x| 57 | lim = x.to_i 58 | config.options[:limit] = lim > 0 ? lim - 1 : lim 59 | end 60 | 61 | opt.on("-r","--recent","Use fasd and fzf to choose recently accessed files") do 62 | missing = ['fzf', 'fasd'].missing_bins 63 | if missing.empty? 64 | config.options[:recent] = true 65 | else 66 | $stdout.puts "'recent' search specified, but required binaries weren't found:" 67 | $stdout.puts missing.join(", ") 68 | $stdout.puts "You can install with homebrew: 'brew install #{missing.join(" ")}'" 69 | $stdout.puts 70 | $stdout.print "Continue with standard search? Y/n: " 71 | res = STDIN.gets 72 | if res =~ /^n/i 73 | EditScript.do_exit 74 | end 75 | end 76 | end 77 | 78 | opt.on("-f","--function","Use grep (or silver_searcher if available) to search for files containing functions and aliases matching terms") do 79 | config.options[:function_search] = true 80 | end 81 | 82 | opt.on("-1","--open_single","If there's only a single match, open it immediately") do 83 | config.options[:open_single] = true 84 | end 85 | 86 | opt.on("-s","--show","Show results without executing") do |environment| 87 | config.options[:show] = true 88 | end 89 | 90 | opt.on("--nocolor","Supress color output on --show command") do 91 | config.options[:nocolor] = true 92 | end 93 | 94 | opt.on("-o","--only [EXTENSIONS]","Only accept files with these extensions (comma-separated). To include files with no extension, use an extra comm, e.g. '-o ,' or '-o py,rb,,'") do |exts| 95 | config.options[:only] = build_type_pattern(exts) 96 | end 97 | 98 | opt.on("-O", "Ignore any file extension limits in config, environment variables, or arguments") do 99 | config.options[:ignore_only] = true 100 | end 101 | 102 | opt.on("-n","--no-menu","No menu interaction. Executes the highest ranked result or, with '-s', returns a plain list of results") do |menu| 103 | config.options[:menu] = false 104 | end 105 | 106 | opt.on("-a","--show-all","Show all results, otherwise limited to top 10") do 107 | config.options[:showall] = true 108 | end 109 | 110 | opt.on("--scores","Show match scoring with results") do 111 | config.options[:showscores] = true 112 | end 113 | 114 | opt.on("-d","--debug","Verbose debugging") do 115 | config.options[:debug] = true 116 | end 117 | 118 | opt.on("-h","--help","help") do 119 | puts opt_parser 120 | exit 121 | end 122 | end 123 | 124 | opt_parser.parse!(args) 125 | 126 | config.editor ||= ENV['EDITSCRIPT_EDITOR'] ? ENV['EDITSCRIPT_EDITOR'] : ENV['EDITOR'] || 'vim' 127 | search_path_fallback = ENV['EDITSCRIPT_PATH'] ? ENV['EDITSCRIPT_PATH'] : ENV['HOME'] 128 | config.search_path ||= search_path_fallback.split(/:/) 129 | 130 | p config if config.options[:debug] 131 | [args, config.to_h] 132 | end 133 | 134 | private 135 | 136 | def self.build_type_pattern(string) 137 | exts = string.dup 138 | patterns = [] 139 | patterns.push("^.*?\/\\.?[^\\.]+") if exts =~ /(,,|,$)/ 140 | exts.gsub!(/ *,+ */,',') 141 | exts.split(/,/).each {|e| patterns.push("\\." + e.sub(/^\./,'')) } 142 | patterns 143 | end 144 | end 145 | end 146 | -------------------------------------------------------------------------------- /lib/editscript/main.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: utf-8 3 | 4 | module EditScript 5 | 6 | String.send :include, Term::ANSIColor 7 | 8 | [Array, String].map {|c| c.send :include, ScriptingTools::ExecAvailable } 9 | 10 | def self.search(args) 11 | @status_code = 0 12 | @stty_save = `stty -g`.chomp 13 | input, config = EditScript::Config.parse(args) 14 | @options = config[:options] 15 | @editor = config[:editor] 16 | @search_paths = config[:search_path] 17 | 18 | if (input.nil? || input.empty?) && !@options[:recent] 19 | puts "No search term given. Use '#{File.basename(__FILE__)} -h' for help." 20 | self.do_exit 21 | else 22 | if @options[:recent] 23 | @search_terms = input.join(' ') 24 | elsif @options[:function_search] 25 | @search_terms = input.map {|i| Shellwords.escape(i) }.join('|') 26 | else 27 | @search_terms = input.join('/') 28 | end 29 | end 30 | 31 | result = self.run_search 32 | 33 | %x{#{@editor} "#{result}"} 34 | end 35 | 36 | def self.do_exit 37 | unless @stty_save.nil? 38 | system('stty', @stty_save) 39 | end 40 | Process.exit 41 | end 42 | 43 | private 44 | 45 | def self.run_search 46 | if @options[:recent] 47 | puts "Searching recent files for #{@search_terms}" if @options[:debug] 48 | self.recent_search 49 | end 50 | if @options[:function_search] 51 | puts "Searching for #{@search_terms} in functions and aliases" if @options[:debug] 52 | res = self.function_search 53 | else 54 | puts "Searching #{@search_terms}" if @options[:debug] 55 | res = self.fuzzy_search 56 | end 57 | if @options[:show] 58 | self.show_and_exit(res) 59 | end 60 | 61 | unless @options[:menu] # Just execute the top result 62 | return res[0][:path] 63 | else # Show a menu of results 64 | res = res[0..@options[:limit]] unless @options[:showall] # limit to top 10 results 65 | 66 | # trap('INT') { self.do_exit } 67 | 68 | self.results_menu(res,!@options[:function_search]) 69 | begin 70 | printf("Type ".green.bold + "q".cyan.bold + " to cancel, enter to edit first option".green.bold,res.length) 71 | while line = Readline.readline(": ", true) 72 | if line =~ /^[a-z]/i 73 | @status_code = 0 74 | self.do_exit 75 | end 76 | line = line == '' ? 1 : line.to_i 77 | if (line > 0 && line <= res.length) 78 | puts @options[:function_search] ? res[line - 1] : res[line - 1][:path] if @options[:debug] 79 | return @options[:function_search] ? res[line - 1] : res[line - 1][:path] 80 | else 81 | puts "Out of range" 82 | end 83 | end 84 | rescue Interrupt => e 85 | p e 86 | @status_code = 1 87 | self.do_exit 88 | end 89 | end 90 | end 91 | 92 | def self.show_and_exit(res) 93 | res.each do |match| 94 | if @options[:nocolor] 95 | printf("[%09.4f]",match[:score]) if @options[:showscores] 96 | puts match[:path] 97 | else 98 | printf("[%09.4f]".green,match[:score]) if @options[:showscores] 99 | puts match[:path].white.bold 100 | end 101 | end 102 | self.do_exit 103 | end 104 | 105 | def self.recent_search 106 | scores = @options[:showscores] ? "" : "l" 107 | list = %x{fasd -#{scores}ftR #{@search_terms}}.split(/\n/) 108 | unless @options[:only].empty? 109 | only_pattern = @options[:only].join("|") 110 | list.delete_if {|l| 111 | l !~ /(#{only_pattern})$/ 112 | } 113 | end 114 | choices = [] 115 | patterns = [] 116 | @search_paths.each {|path| 117 | if path =~ /[\*\?]/ 118 | patterns.push(path.sub(/^~/,ENV['HOME']).gsub(/\*+/,'.*').gsub(/\?/,'\S')) 119 | else 120 | patterns.push(File.expand_path(path)) 121 | end 122 | } 123 | list.each {|path| 124 | path.strip! 125 | patterns.each {|s| 126 | if path =~ /^#{s}/ 127 | choices.push(path) 128 | break 129 | end 130 | } 131 | } 132 | if choices.length == 0 133 | $stderr.puts "No recent matches found, switching to fuzzy file search".red.bold 134 | @search_terms.gsub!(/ /, '/') 135 | else 136 | if @options[:show] 137 | choices.map! {|c| c.white.bold } unless @options[:nocolor] 138 | puts choices 139 | else 140 | res = exec "#{@editor} $(echo #{Shellwords.escape(choices.join("\n"))}|fzf -m +c -1)" 141 | @status_code = res ? 1 : 0 142 | end 143 | exit 144 | end 145 | end 146 | 147 | def self.fuzzy_search 148 | paths = [] 149 | glob_res = [] 150 | glob_terms = @search_terms.gsub(/\//,'').split('').join('.*') 151 | @search_paths.each do |p| 152 | if p =~ /[^\*]\*([^\*]|$)/ 153 | Dir.glob(File.expand_path(p)).each do |m| 154 | if m =~ /#{glob_terms}/ 155 | glob_res << { :path => m, :score => 0.5 } 156 | end 157 | end 158 | elsif p =~ /\*\*/ 159 | paths.concat(Dir.glob(File.expand_path(p))) 160 | else 161 | paths.push(File.expand_path(p)) 162 | end 163 | end 164 | 165 | 166 | glob_res.delete_if { |file| 167 | %x{file "#{file[:path]}"}.chomp !~ /text/ 168 | } 169 | 170 | finder = FuzzyFileFinder.new(paths,50000) 171 | 172 | res = finder.find(@search_terms).delete_if { |file| 173 | %x{file "#{file[:path]}"}.chomp !~ /text/ 174 | } 175 | 176 | res.concat(glob_res) 177 | 178 | if res.length == 0 179 | puts "No matching files".red.bold 180 | $code = 1 181 | exit 182 | elsif res.length > 1 183 | unless @options[:only].empty? 184 | only_pattern = @options[:only].join("|") 185 | res.delete_if {|l| l[:path] !~ /(#{only_pattern})$/ } 186 | end 187 | if res.length == 0 188 | puts "No matches" 189 | $code = 1 190 | exit 191 | end 192 | res = res.sort {|a,b| 193 | a[:score] <=> b[:score] 194 | }.reverse 195 | else 196 | if @options[:open_single] 197 | res = exec %Q{#{@editor} "#{res[0][:path]}"} 198 | do_exit res ? 1 : 0 199 | end 200 | end 201 | res 202 | end 203 | 204 | def self.function_search 205 | search_glob = "{#{@search_paths.join(',')}}" 206 | if "ag".bins_available? 207 | cmd = %q{ag --nocolor -l '(^| |\.)((%s)\S+\s*\(|(def|function|alias) (%s))' %s} % [@search_terms, @search_terms, search_glob] 208 | else 209 | cmd = %q{grep --color=never -liIRE '(^| |\.)((%s)\S+\s*\(|(def|function|alias) (%s))' %s} % [@search_terms, @search_terms, search_glob] 210 | end 211 | 212 | res = %x{#{cmd}}.split(/\n/).map {|i| i.strip } 213 | 214 | if res.length == 0 215 | puts "No matching files".red.bold 216 | $code = 1 217 | exit 218 | elsif res.length > 1 219 | unless @options[:only].empty? 220 | only_pattern = @options[:only].join("|") 221 | res.delete_if {|l| 222 | l !~ /(#{only_pattern})$/ 223 | } 224 | end 225 | else 226 | if @options[:open_single] 227 | res = exec %Q{#{@editor} "#{res[0][:path]}"} 228 | do_exit res ? 1 : 0 229 | end 230 | end 231 | res 232 | end 233 | 234 | def self.results_menu(res,fuzzy=true) 235 | counter = 1 236 | puts 237 | res.each do |match| 238 | if fuzzy 239 | display = @options[:debug] ? match[:highlighted_path] : match[:path] 240 | else 241 | display = match 242 | end 243 | if @options[:showscores] && fuzzy 244 | printf("%2d ) ".cyan.bold, counter) 245 | printf("[%09.4f]".dark.white, match[:score]) 246 | printf(" %s\n".white.bold, display) 247 | else 248 | printf("%2d ) ".cyan.bold, counter) 249 | printf(" %s\n".white.bold, display) 250 | end 251 | counter += 1 252 | end 253 | puts 254 | end 255 | 256 | end 257 | --------------------------------------------------------------------------------