Swap FuzzyFinder for Command-T.

This commit is contained in:
Max Bucknell 2015-01-11 09:33:36 +00:00
parent 4cdb6dc777
commit edb217bc0f
114 changed files with 478043 additions and 9246 deletions

View file

@ -0,0 +1 @@
data/benchmark.yml export-ignore

View file

@ -0,0 +1,5 @@
.release-notes.txt
.ruby-version
command-t.recipe
/.bundle
vendor/bundle

View file

@ -0,0 +1,9 @@
[submodule "vendor/vimball"]
path = vendor/vimball
url = https://github.com/tomtom/vimball.rb.git
[submodule "vendor/vimscriptuploader"]
path = vendor/vimscriptuploader
url = https://github.com/tomtom/vimscriptuploader.rb.git
[submodule "vendor/vroom"]
path = vendor/vroom
url = https://github.com/google/vroom.git

View file

@ -0,0 +1,8 @@
Greg Hurrell <greg@hurrell.net> Greg Hurrell <greg@hurrell.net>
Greg Hurrell <greg@hurrell.net> Wincent Colaiuta <win@wincent.com>
Kevin Webster <webster.kevin@gmail.com> rabidpraxis <webster.kevin@gmail.com>
Nicolas Alpi <nicolas.alpi@gmail.com> Spyou <nicolas.alpi@gmail.com>
Noon Silk <noonsilk@gmail.com> Noon Silk <noonsilk@gmail.com>
Noon Silk <noonsilk@gmail.com> Noon Silk <superhappyfun@gmail.com>
Sung Pae <sung@metablu.com> guns <sung@metablu.com>
Sung Pae <sung@metablu.com> guns <self@sungpae.com>

View file

@ -0,0 +1 @@
--colour

View file

@ -0,0 +1,2 @@
--- {}

View file

@ -0,0 +1,7 @@
source 'https://rubygems.org'
gem 'nokogiri'
gem 'mechanize'
gem 'rake'
gem 'rr'
gem 'rspec'

View file

@ -0,0 +1,48 @@
GEM
remote: https://rubygems.org/
specs:
diff-lcs (1.2.5)
domain_name (0.5.15)
unf (>= 0.0.5, < 1.0.0)
http-cookie (1.0.2)
domain_name (~> 0.5)
mechanize (2.7.3)
domain_name (~> 0.5, >= 0.5.1)
http-cookie (~> 1.0)
mime-types (~> 2.0)
net-http-digest_auth (~> 1.1, >= 1.1.1)
net-http-persistent (~> 2.5, >= 2.5.2)
nokogiri (~> 1.4)
ntlm-http (~> 0.1, >= 0.1.1)
webrobots (>= 0.0.9, < 0.2)
mime-types (2.1)
mini_portile (0.5.2)
net-http-digest_auth (1.4)
net-http-persistent (2.9.4)
nokogiri (1.6.1)
mini_portile (~> 0.5.0)
ntlm-http (0.1.1)
rake (10.1.1)
rr (1.1.2)
rspec (2.14.1)
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
rspec-core (2.14.7)
rspec-expectations (2.14.5)
diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.14.5)
unf (0.1.3)
unf_ext
unf_ext (0.0.6)
webrobots (0.1.1)
PLATFORMS
ruby
DEPENDENCIES
mechanize
nokogiri
rake
rr
rspec

View file

@ -0,0 +1,22 @@
Copyright 2010-2014 Greg Hurrell. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,21 @@
rubyfiles := $(shell find ruby -name '*.rb')
cfiles := $(shell find ruby -name '*.c')
cheaders := $(shell find ruby -name '*.h')
depends := $(shell find ruby -name depend)
txtfiles := $(shell find doc -name '*.txt')
vimfiles := $(shell find autoload plugin -name '*.vim')
vimball: command-t.vba
command-t.recipe: $(rubyfiles) $(cfiles) $(cheaders) $(depends) $(txtfiles) $(vimfiles)
echo "$^" | perl -pe 's/ /\n/g' > $@
command-t.vba: command-t.recipe
vendor/vimball/vimball.rb -d . -b . vba $^
.PHONY: spec
spec:
rspec spec
.PHONY: clean
clean:
rm -f command-t.vba

View file

@ -0,0 +1 @@
doc/command-t.txt

View file

@ -0,0 +1,179 @@
require 'yaml'
def bail_on_failure
exitstatus = $?.exitstatus
if exitstatus != 0
err "last command failed with exit status #{exitstatus}"
exit 1
end
end
def version
`git describe`.chomp
end
def rubygems_version
# RubyGems will barf if we try to pass an intermediate version number
# like "1.1b2-10-g61a374a", so no choice but to abbreviate it
`git describe --abbrev=0`.chomp
end
def yellow
"\033[33m"
end
def red
"\033[31m"
end
def clear
"\033[0m"
end
def warn(str)
puts "#{yellow}warning: #{str}#{clear}"
end
def err(str)
puts "#{red}error: #{str}#{clear}"
end
def prepare_release_notes
# extract base release notes from README.txt HISTORY section
File.open('.release-notes.txt', 'w') do |out|
lines = File.readlines('README.txt').each { |line| line.chomp! }
while line = lines.shift do
next unless line =~ /^HISTORY +\*command-t-history\*$/
break unless lines.shift == '' &&
(line = lines.shift) && line =~ /^\d\.\d/ &&
lines.shift == ''
while line = lines.shift && line != ''
out.puts line
end
break
end
out.puts ''
out.puts '# Please edit the release notes to taste.'
out.puts '# Blank lines and lines beginning with a hash will be removed.'
out.puts '# To abort, exit your editor with a non-zero exit status (:cquit in Vim).'
end
unless system "$EDITOR .release-notes.txt"
err "editor exited with non-zero exit status; aborting"
exit 1
end
filtered = read_release_notes
File.open('.release-notes.txt', 'w') do |out|
out.print filtered
end
end
def read_release_notes
File.readlines('.release-notes.txt').reject do |line|
line =~ /^(#.*|\s*)$/ # filter comment lines and blank lines
end.join
end
task :default => :spec
desc 'Print help on preparing a release'
task :help do
puts <<-END
The general release sequence is:
rake prerelease
rake gem
rake push
rake upload:all
Note: the upload task depends on the Mechanize gem; and may require a
prior `gem install mechanize`
END
end
desc 'Run specs'
task :spec do
system 'bundle exec rspec spec'
bail_on_failure
end
desc 'Create vimball archive'
task :vimball => :check_tag do
system 'make'
bail_on_failure
FileUtils.cp 'command-t.vba', "command-t-#{version}.vba"
end
desc 'Clean compiled products'
task :clean do
Dir.chdir 'ruby/command-t' do
system 'make clean' if File.exists?('Makefile')
system 'rm -f Makefile'
end
end
desc 'Clobber all generated files'
task :clobber => :clean do
system 'make clean'
end
desc 'Compile extension'
task :make do
Dir.chdir 'ruby/command-t' do
ruby 'extconf.rb'
system 'make clean'
bail_on_failure
system 'make'
bail_on_failure
end
end
desc 'Check that the current HEAD is tagged'
task :check_tag do
unless system 'git describe --exact-match HEAD 2> /dev/null'
warn 'current HEAD is not tagged'
end
end
desc 'Run checks prior to release'
task :prerelease => ['make', 'spec', :vimball, :check_tag]
namespace :upload do
desc 'Upload current vimball to Amazon S3'
task :s3 => :vimball do
sh 'aws --curl-options=--insecure put ' +
"s3.wincent.com/command-t/releases/command-t-#{version}.vba " +
"command-t-#{version}.vba"
sh 'aws --curl-options=--insecure put ' +
"s3.wincent.com/command-t/releases/command-t-#{version}.vba?acl " +
'--public'
end
desc 'Upload current vimball to www.vim.org'
task :vim => :vimball do
prepare_release_notes
sh "vendor/vimscriptuploader/vimscriptuploader.rb \
--id 3025 \
--file command-t-#{version}.vba \
--message-file .release-notes.txt \
--version #{version} \
--config ~/.vim_org.yml \
.vim_org.yml"
end
desc 'Upload current vimball everywhere'
task :all => [ :s3, :vim ]
end
desc 'Create the ruby gem package'
task :gem => :check_tag do
sh "gem build command-t.gemspec"
end
desc 'Push gem to Gemcutter ("gem push")'
task :push => :gem do
sh "gem push command-t-#{rubygems_version}.gem"
end

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2014 Vít Ondruch <v.ondruch@gmail.com> -->
<component type="addon">
<id>vim-command-t</id>
<extends>gvim.desktop</extends>
<name>command-t</name>
<summary>Provides an extremely fast, intuitive mechanism for opening files with a minimal number of keystrokes</summary>
<url type="homepage">https://wincent.com/products/command-t</url>
<metadata_license>CC0-1.0</metadata_license>
<project_license>BSD</project_license>
<updatecontact>v.ondruch@gmail.com</updatecontact>
</component>

View file

@ -0,0 +1,180 @@
" Copyright 2010-2014 Greg Hurrell. All rights reserved.
" Licensed under the terms of the BSD 2-clause license.
if exists("g:command_t_autoloaded") || &cp
finish
endif
let g:command_t_autoloaded = 1
function s:CommandTRubyWarning()
echohl WarningMsg
echo "command-t.vim requires Vim to be compiled with Ruby support"
echo "For more information type: :help command-t"
echohl none
endfunction
function commandt#CommandTShowBufferFinder()
if has('ruby')
ruby $command_t.show_buffer_finder
else
call s:CommandTRubyWarning()
endif
endfunction
function commandt#CommandTShowFileFinder(arg)
if has('ruby')
ruby $command_t.show_file_finder
else
call s:CommandTRubyWarning()
endif
endfunction
function commandt#CommandTShowJumpFinder()
if has('ruby')
ruby $command_t.show_jump_finder
else
call s:CommandTRubyWarning()
endif
endfunction
function commandt#CommandTShowMRUFinder()
if has('ruby')
ruby $command_t.show_mru_finder
else
call s:CommandTRubyWarning()
endif
endfunction
function commandt#CommandTShowTagFinder()
if has('ruby')
ruby $command_t.show_tag_finder
else
call s:CommandTRubyWarning()
endif
endfunction
function commandt#CommandTFlush()
if has('ruby')
ruby $command_t.flush
else
call s:CommandTRubyWarning()
endif
endfunction
function commandt#CommandTLoad()
if !has('ruby')
call s:CommandTRubyWarning()
endif
endfunction
if !has('ruby')
finish
endif
function CommandTListMatches()
ruby $command_t.list_matches
endfunction
function CommandTHandleKey(arg)
ruby $command_t.handle_key
endfunction
function CommandTBackspace()
ruby $command_t.backspace
endfunction
function CommandTDelete()
ruby $command_t.delete
endfunction
function CommandTAcceptSelection()
ruby $command_t.accept_selection
endfunction
function CommandTAcceptSelectionTab()
ruby $command_t.accept_selection :command => $command_t.tab_command
endfunction
function CommandTAcceptSelectionSplit()
ruby $command_t.accept_selection :command => $command_t.split_command
endfunction
function CommandTAcceptSelectionVSplit()
ruby $command_t.accept_selection :command => $command_t.vsplit_command
endfunction
function CommandTQuickfix()
ruby $command_t.quickfix
endfunction
function CommandTRefresh()
ruby $command_t.refresh
endfunction
function CommandTToggleFocus()
ruby $command_t.toggle_focus
endfunction
function CommandTCancel()
ruby $command_t.cancel
endfunction
function CommandTSelectNext()
ruby $command_t.select_next
endfunction
function CommandTSelectPrev()
ruby $command_t.select_prev
endfunction
function CommandTClear()
ruby $command_t.clear
endfunction
function CommandTClearPrevWord()
ruby $command_t.clear_prev_word
endfunction
function CommandTCursorLeft()
ruby $command_t.cursor_left
endfunction
function CommandTCursorRight()
ruby $command_t.cursor_right
endfunction
function CommandTCursorEnd()
ruby $command_t.cursor_end
endfunction
function CommandTCursorStart()
ruby $command_t.cursor_start
endfunction
" note that we only start tracking buffers from first (autoloaded) use of Command-T
augroup CommandTMRUBuffer
autocmd BufEnter * ruby CommandT::MRU.touch
autocmd BufDelete * ruby CommandT::MRU.delete
augroup END
ruby << EOF
# require Ruby files
begin
require 'command-t'
$command_t = CommandT::Controller.new
rescue LoadError
load_path_modified = false
::VIM::evaluate('&runtimepath').to_s.split(',').each do |path|
lib = "#{path}/ruby"
if !$LOAD_PATH.include?(lib) && File.exist?(lib)
$LOAD_PATH << lib
load_path_modified = true
end
end
retry if load_path_modified
# could get here if C extension was not compiled, or was compiled
# for the wrong architecture or Ruby version
$command_t = CommandT::Stub.new
end
EOF

View file

@ -0,0 +1,33 @@
#!/usr/bin/env ruby
#
# Copyright 2013-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
lib = File.expand_path('../../ruby', File.dirname(__FILE__))
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'command-t/ext'
require 'command-t/util'
require 'benchmark'
require 'ostruct'
require 'yaml'
yaml = File.expand_path('../../data/benchmark.yml', File.dirname(__FILE__))
data = YAML.load_file(yaml)
threads = CommandT::Util.processor_count
puts "Starting benchmark run (PID: #{Process.pid})"
Benchmark.bmbm do |b|
data['tests'].each do |test|
scanner = OpenStruct.new(:paths => test['paths'])
matcher = CommandT::Matcher.new(scanner)
b.report(test['name']) do
test['times'].times do
test['queries'].each do |query|
matcher.sorted_matches_for(query, :threads => threads)
end
end
end
end
end

View file

@ -0,0 +1,76 @@
#!/usr/bin/env ruby
#
# Copyright 2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
lib = File.expand_path('../../ruby', File.dirname(__FILE__))
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'command-t/ext'
require 'benchmark'
require 'json'
require 'pathname'
require 'socket'
puts "Starting benchmark run (PID: #{Process.pid})"
TEST_TIMES = 10
Benchmark.bmbm do |b|
b.report('watchman JSON') do
TEST_TIMES.times do
sockname = JSON[%x{watchman get-sockname}]['sockname']
raise unless $?.exitstatus.zero?
UNIXSocket.open(sockname) do |s|
root = Pathname.new(ENV['PWD']).realpath
s.puts JSON.generate(['watch-list'])
if !JSON[s.gets]['roots'].include?(root)
# this path isn't being watched yet; try to set up watch
s.puts JSON.generate(['watch', root])
# root_restrict_files setting may prevent Watchman from working
raise if JSON[s.gets].has_key?('error')
end
s.puts JSON.generate(['query', root, {
'expression' => ['type', 'f'],
'fields' => ['name'],
}])
paths = JSON[s.gets]
# could return error if watch is removed
raise if paths.has_key?('error')
end
end
end
b.report('watchman binary') do
TEST_TIMES.times do
sockname = CommandT::Watchman::Utils.load(
%x{watchman --output-encoding=bser get-sockname}
)['sockname']
raise unless $?.exitstatus.zero?
UNIXSocket.open(sockname) do |socket|
root = Pathname.new(ENV['PWD']).realpath.to_s
roots = CommandT::Watchman::Utils.query(['watch-list'], socket)['roots']
if !roots.include?(root)
# this path isn't being watched yet; try to set up watch
result = CommandT::Watchman::Utils.query(['watch', root], socket)
# root_restrict_files setting may prevent Watchman from working
raise if result.has_key?('error')
end
query = ['query', root, {
'expression' => ['type', 'f'],
'fields' => ['name'],
}]
paths = CommandT::Watchman::Utils.query(query, socket)
# could return error if watch is removed
raise if paths.has_key?('error')
end
end
end
end

View file

@ -0,0 +1,36 @@
Gem::Specification.new do |s|
s.name = 'command-t'
# see note in the Rakefile about how intermediate version numbers
# can break RubyGems
v = `git describe --abbrev=0`.chomp
s.version = v
s.authors = ['Greg Hurrell']
s.email = 'greg@hurrell.net'
files =
['README.txt', 'LICENSE', 'Gemfile', 'Rakefile'] +
Dir.glob('{ruby,doc,plugin}/**/*')
files = files.reject { |f| f =~ /\.(rbc|o|log|plist|dSYM)/ }
s.files = files
s.license = 'BSD'
s.require_path = 'ruby'
s.extensions = 'ruby/command-t/extconf.rb'
s.executables = []
s.has_rdoc = false
s.homepage = 'https://wincent.com/products/command-t'
s.summary = 'The Command-T plug-in for VIM.'
s.description = <<-EOS
Command-T provides a fast, intuitive mechanism for opening files with a
minimal number of keystrokes. Its full functionality is only available when
installed as a Vim plug-in, but it is also made available as a RubyGem so
that other applications can make use of its searching algorithm.
EOS
end

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
tags

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
.

View file

@ -0,0 +1 @@
.

View file

@ -0,0 +1 @@
.

View file

@ -0,0 +1 @@
.

View file

@ -0,0 +1 @@
.

View file

@ -0,0 +1 @@
.

View file

@ -0,0 +1 @@
.

View file

@ -0,0 +1,23 @@
" Copyright 2010-2014 Greg Hurrell. All rights reserved.
" Licensed under the terms of the BSD 2-clause license.
if exists("g:command_t_loaded") || &cp
finish
endif
let g:command_t_loaded = 1
command CommandTBuffer call commandt#CommandTShowBufferFinder()
command CommandTJump call commandt#CommandTShowJumpFinder()
command CommandTMRU call commandt#CommandTShowMRUFinder()
command CommandTTag call commandt#CommandTShowTagFinder()
command -nargs=? -complete=dir CommandT call commandt#CommandTShowFileFinder(<q-args>)
command CommandTFlush call commandt#CommandTFlush()
command CommandTLoad call commandt#CommandTLoad()
if !hasmapto(':CommandT<CR>') && maparg('<Leader>t', 'n') == ''
silent! nnoremap <unique> <silent> <Leader>t :CommandT<CR>
endif
if !hasmapto(':CommandTBuffer<CR>') && maparg('<Leader>b', 'n') == ''
silent! nnoremap <unique> <silent> <Leader>b :CommandTBuffer<CR>
endif

View file

@ -0,0 +1,17 @@
# Copyright 2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
autoload :Controller, 'command-t/controller'
autoload :Finder, 'command-t/finder'
autoload :MRU, 'command-t/mru'
autoload :MatchWindow, 'command-t/match_window'
autoload :PathUtilities, 'command-t/path_utilities'
autoload :Prompt, 'command-t/prompt'
autoload :SCMUtilities, 'command-t/scm_utilities'
autoload :Scanner, 'command-t/scanner'
autoload :Settings, 'command-t/settings'
autoload :Stub, 'command-t/stub'
autoload :Util, 'command-t/util'
autoload :VIM, 'command-t/vim'
end # module CommandT

View file

@ -0,0 +1,6 @@
Makefile
*.o
*.log
ext.*
!ext.c
!ext.h

View file

@ -0,0 +1,430 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
class Controller
include PathUtilities
include SCMUtilities
def initialize
@prompt = Prompt.new
end
def show_buffer_finder
@path = VIM::pwd
@active_finder = buffer_finder
show
end
def show_jump_finder
@path = VIM::pwd
@active_finder = jump_finder
show
end
def show_mru_finder
@path = VIM::pwd
@active_finder = mru_finder
show
end
def show_tag_finder
@path = VIM::pwd
@active_finder = tag_finder
show
end
def show_file_finder
# optional parameter will be desired starting directory, or ""
arg = ::VIM::evaluate('a:arg')
if arg && arg.size > 0
@path = File.expand_path(arg, VIM::pwd)
else
traverse = VIM::get_string('g:CommandTTraverseSCM') || 'file'
case traverse
when 'file'
@path = nearest_ancestor(VIM::current_file_dir, scm_markers) || VIM::pwd
when 'dir'
@path = nearest_ancestor(VIM::pwd, scm_markers) || VIM::pwd
else
@path = VIM::pwd
end
end
@active_finder = file_finder
file_finder.path = @path
show
rescue Errno::ENOENT
# probably a problem with the optional parameter
@match_window.print_no_such_file_or_directory
end
def hide
@match_window.leave
if VIM::Window.select @initial_window
if @initial_buffer.number == 0
# upstream bug: buffer number misreported as 0
# see: https://wincent.com/issues/1617
::VIM::command "silent b #{@initial_buffer.name}"
else
::VIM::command "silent b #{@initial_buffer.number}"
end
end
end
# Take current matches and stick them in the quickfix window.
def quickfix
hide
matches = @matches.map do |match|
"{ 'filename': '#{VIM::escape_for_single_quotes match}' }"
end.join(', ')
::VIM::command 'call setqflist([' + matches + '])'
::VIM::command 'cope'
end
def refresh
return unless @active_finder && @active_finder.respond_to?(:flush)
@active_finder.flush
list_matches!
end
def flush
@max_height = nil
@min_height = nil
@file_finder = nil
@tag_finder = nil
end
def handle_key
key = ::VIM::evaluate('a:arg').to_i.chr
if @focus == @prompt
@prompt.add! key
@needs_update = true
else
@match_window.find key
end
end
def backspace
if @focus == @prompt
@prompt.backspace!
@needs_update = true
end
end
def delete
if @focus == @prompt
@prompt.delete!
@needs_update = true
end
end
def accept_selection(options = {})
selection = @match_window.selection
hide
open_selection(selection, options) unless selection.nil?
end
def toggle_focus
@focus.unfocus # old focus
@focus = @focus == @prompt ? @match_window : @prompt
@focus.focus # new focus
end
def cancel
hide
end
def select_next
@match_window.select_next
end
def select_prev
@match_window.select_prev
end
def clear
@prompt.clear!
list_matches!
end
def clear_prev_word
@prompt.clear_prev_word!
list_matches!
end
def cursor_left
@prompt.cursor_left if @focus == @prompt
end
def cursor_right
@prompt.cursor_right if @focus == @prompt
end
def cursor_end
@prompt.cursor_end if @focus == @prompt
end
def cursor_start
@prompt.cursor_start if @focus == @prompt
end
def leave
@match_window.leave
end
def unload
@match_window.unload
end
def list_matches(options = {})
return unless @needs_update || options[:force]
@matches = @active_finder.sorted_matches_for(
@prompt.abbrev,
:case_sensitive => case_sensitive?,
:limit => match_limit,
:threads => CommandT::Util.processor_count
)
@match_window.matches = @matches
@needs_update = false
end
def tab_command
VIM::get_string('g:CommandTAcceptSelectionTabCommand') || 'tabe'
end
def split_command
VIM::get_string('g:CommandTAcceptSelectionSplitCommand') || 'sp'
end
def vsplit_command
VIM::get_string('g:CommandTAcceptSelectionVSplitCommand') || 'vs'
end
private
def scm_markers
markers = VIM::get_string('g:CommandTSCMDirectories')
markers = markers && markers.split(/\s*,\s*/)
markers = %w[.git .hg .svn .bzr _darcs] unless markers && markers.length
markers
end
def list_matches!
list_matches(:force => true)
end
def show
@initial_window = $curwin
@initial_buffer = $curbuf
@match_window = MatchWindow.new \
:highlight_color => VIM::get_string('g:CommandTHighlightColor'),
:match_window_at_top => VIM::get_bool('g:CommandTMatchWindowAtTop'),
:match_window_reverse => VIM::get_bool('g:CommandTMatchWindowReverse'),
:min_height => min_height,
:debounce_interval => VIM::get_number('g:CommandTInputDebounce') || 50,
:prompt => @prompt
@focus = @prompt
@prompt.focus
register_for_key_presses
set_up_autocmds
clear # clears prompt and lists matches
end
def max_height
@max_height ||= VIM::get_number('g:CommandTMaxHeight') || 0
end
def min_height
@min_height ||= begin
min_height = VIM::get_number('g:CommandTMinHeight') || 0
min_height = max_height if max_height != 0 && min_height > max_height
min_height
end
end
def case_sensitive?
if @prompt.abbrev.match(/[A-Z]/)
if VIM::exists?('g:CommandTSmartCase')
smart_case = VIM::get_bool('g:CommandTSmartCase')
else
smart_case = VIM::get_bool('&smartcase')
end
if smart_case
return true
end
end
if VIM::exists?('g:CommandTIgnoreCase')
return !VIM::get_bool('g:CommandTIgnoreCase')
end
false
end
# Backslash-escape space, \, |, %, #, "
def sanitize_path_string(str)
# for details on escaping command-line mode arguments see: :h :
# (that is, help on ":") in the Vim documentation.
str.gsub(/[ \\|%#"]/, '\\\\\0')
end
def current_buffer_visible_in_other_window
count = (0...::VIM::Window.count).to_a.inject(0) do |acc, i|
acc += 1 if ::VIM::Window[i].buffer.number == $curbuf.number
acc
end
count > 1
end
def default_open_command
if !VIM::get_bool('&modified') ||
VIM::get_bool('&hidden') ||
VIM::get_bool('&autowriteall') && !VIM::get_bool('&readonly') ||
current_buffer_visible_in_other_window
VIM::get_string('g:CommandTAcceptSelectionCommand') || 'e'
else
'sp'
end
end
def ensure_appropriate_window_selection
# normally we try to open the selection in the current window, but there
# is one exception:
#
# - we don't touch any "unlisted" buffer with buftype "nofile" (such as
# NERDTree or MiniBufExplorer); this is to avoid things like the "Not
# enough room" error which occurs when trying to open in a split in a
# shallow (potentially 1-line) buffer like MiniBufExplorer is current
#
# Other "unlisted" buffers, such as those with buftype "help" are treated
# normally.
initial = $curwin
while true do
break unless ::VIM::evaluate('&buflisted').to_i == 0 &&
::VIM::evaluate('&buftype').to_s == 'nofile'
::VIM::command 'wincmd w' # try next window
break if $curwin == initial # have already tried all
end
end
def open_selection(selection, options = {})
command = options[:command] || default_open_command
selection = File.expand_path selection, @path
selection = relative_path_under_working_directory selection
selection = sanitize_path_string selection
selection = File.join('.', selection) if selection =~ /^\+/
ensure_appropriate_window_selection
@active_finder.open_selection command, selection, options
end
def map(key, function, param = nil)
::VIM::command "noremap <silent> <buffer> #{key} " \
":call CommandT#{function}(#{param})<CR>"
end
def term
@term ||= ::VIM::evaluate('&term')
end
def register_for_key_presses
# "normal" keys (interpreted literally)
numbers = ('0'..'9').to_a.join
lowercase = ('a'..'z').to_a.join
uppercase = lowercase.upcase
punctuation = '<>`@#~!"$%&/()=+*-_.,;:?\\\'{}[] ' # and space
(numbers + lowercase + uppercase + punctuation).each_byte do |b|
map "<Char-#{b}>", 'HandleKey', b
end
# "special" keys (overridable by settings)
{
'AcceptSelection' => '<CR>',
'AcceptSelectionSplit' => ['<C-CR>', '<C-s>'],
'AcceptSelectionTab' => '<C-t>',
'AcceptSelectionVSplit' => '<C-v>',
'Backspace' => '<BS>',
'Cancel' => ['<C-c>', '<Esc>'],
'Clear' => '<C-u>',
'ClearPrevWord' => '<C-w>',
'CursorEnd' => '<C-e>',
'CursorLeft' => ['<Left>', '<C-h>'],
'CursorRight' => ['<Right>', '<C-l>'],
'CursorStart' => '<C-a>',
'Delete' => '<Del>',
'Quickfix' => '<C-q>',
'Refresh' => '<C-f>',
'SelectNext' => ['<C-n>', '<C-j>', '<Down>'],
'SelectPrev' => ['<C-p>', '<C-k>', '<Up>'],
'ToggleFocus' => '<Tab>',
}.each do |key, value|
if override = VIM::get_list_or_string("g:CommandT#{key}Map")
Array(override).each do |mapping|
map mapping, key
end
else
Array(value).each do |mapping|
unless mapping == '<Esc>' && term =~ /\A(screen|xterm|vt100)/
map mapping, key
end
end
end
end
end
def set_up_autocmds
::VIM::command 'augroup Command-T'
::VIM::command 'au!'
::VIM::command 'autocmd CursorHold <buffer> :call CommandTListMatches()'
::VIM::command 'augroup END'
end
# Returns the desired maximum number of matches, based on available vertical
# space and the g:CommandTMaxHeight option.
#
# Note the "available" space is actually a theoretical upper bound; it takes
# into account screen dimensions but not things like existing splits which
# may reduce the amount of space in practice.
def match_limit
limit = [1, VIM::Screen.lines - 5].max
limit = [limit, max_height].min if max_height > 0
limit
end
def buffer_finder
@buffer_finder ||= CommandT::Finder::BufferFinder.new
end
def mru_finder
@mru_finder ||= CommandT::Finder::MRUBufferFinder.new
end
def file_finder
@file_finder ||= CommandT::Finder::FileFinder.new nil,
:max_depth => VIM::get_number('g:CommandTMaxDepth'),
:max_files => VIM::get_number('g:CommandTMaxFiles'),
:max_caches => VIM::get_number('g:CommandTMaxCachedDirectories'),
:always_show_dot_files => VIM::get_bool('g:CommandTAlwaysShowDotFiles'),
:never_show_dot_files => VIM::get_bool('g:CommandTNeverShowDotFiles'),
:scan_dot_directories => VIM::get_bool('g:CommandTScanDotDirectories'),
:wild_ignore => VIM::get_string('g:CommandTWildIgnore'),
:scanner => VIM::get_string('g:CommandTFileScanner')
end
def jump_finder
@jump_finder ||= CommandT::Finder::JumpFinder.new
end
def tag_finder
@tag_finder ||= CommandT::Finder::TagFinder.new \
:include_filenames => VIM::get_bool('g:CommandTTagIncludeFilenames')
end
end # class Controller
end # module CommandT

View file

@ -0,0 +1,4 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
CFLAGS += -Wall -Wextra -Wno-unused-parameter

View file

@ -0,0 +1,40 @@
// Copyright 2010-2014 Greg Hurrell. All rights reserved.
// Licensed under the terms of the BSD 2-clause license.
#include "matcher.h"
#include "watchman.h"
VALUE mCommandT = 0; // module CommandT
VALUE cCommandTMatcher = 0; // class CommandT::Matcher
VALUE mCommandTWatchman = 0; // module CommandT::Watchman
VALUE mCommandTWatchmanUtils = 0; // module CommandT::Watchman::Utils
VALUE CommandT_option_from_hash(const char *option, VALUE hash)
{
VALUE key;
if (NIL_P(hash))
return Qnil;
key = ID2SYM(rb_intern(option));
if (rb_funcall(hash, rb_intern("has_key?"), 1, key) == Qtrue)
return rb_hash_aref(hash, key);
else
return Qnil;
}
void Init_ext()
{
// module CommandT
mCommandT = rb_define_module("CommandT");
// class CommandT::Matcher
cCommandTMatcher = rb_define_class_under(mCommandT, "Matcher", rb_cObject);
rb_define_method(cCommandTMatcher, "initialize", CommandTMatcher_initialize, -1);
rb_define_method(cCommandTMatcher, "sorted_matches_for", CommandTMatcher_sorted_matches_for, -1);
// module CommandT::Watchman::Utils
mCommandTWatchman = rb_define_module_under(mCommandT, "Watchman");
mCommandTWatchmanUtils = rb_define_module_under(mCommandTWatchman, "Utils");
rb_define_singleton_method(mCommandTWatchmanUtils, "load", CommandTWatchmanUtils_load, 1);
rb_define_singleton_method(mCommandTWatchmanUtils, "dump", CommandTWatchmanUtils_dump, 1);
rb_define_singleton_method(mCommandTWatchmanUtils, "query", CommandTWatchmanUtils_query, 2);
}

View file

@ -0,0 +1,17 @@
// Copyright 2010-2014 Greg Hurrell. All rights reserved.
// Licensed under the terms of the BSD 2-clause license.
#include <ruby.h>
extern VALUE mCommandT; // module CommandT
extern VALUE cCommandTMatcher; // class CommandT::Matcher
extern VALUE mCommandTWatchman; // module CommandT::Watchman
extern VALUE mCommandTWatchmanUtils; // module CommandT::Watchman::Utils
// Encapsulates common pattern of checking for an option in an optional
// options hash. The hash itself may be nil, but an exception will be
// raised if it is not nil and not a hash.
VALUE CommandT_option_from_hash(const char *option, VALUE hash);
// Debugging macro.
#define ruby_inspect(obj) rb_funcall(rb_mKernel, rb_intern("p"), 1, obj)

View file

@ -0,0 +1,47 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
begin
require 'mkmf'
rescue LoadError
puts <<-DOC.gsub(/^\s+/, '')
Unable to require "mkmf"; you may need to install Ruby development tools
(depending on your system, a "ruby-dev"/"ruby-devel" package or similar).
[exiting]
DOC
exit 1
end
def header(item)
unless find_header(item)
puts "couldn't find #{item} (required)"
exit 1
end
end
# mandatory headers
header('float.h')
header('ruby.h')
header('stdlib.h')
header('string.h')
# optional headers (for CommandT::Watchman::Utils)
if have_header('fcntl.h') &&
have_header('stdint.h') &&
have_header('sys/errno.h') &&
have_header('sys/socket.h')
RbConfig::MAKEFILE_CONFIG['DEFS'] ||= ''
RbConfig::MAKEFILE_CONFIG['DEFS'] += ' -DWATCHMAN_BUILD'
have_header('ruby/st.h') # >= 1.9; sets HAVE_RUBY_ST_H
have_header('st.h') # 1.8; sets HAVE_ST_H
end
# optional
if RbConfig::CONFIG['THREAD_MODEL'] == 'pthread'
have_library('pthread', 'pthread_create') # sets HAVE_PTHREAD_H if found
end
RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']
create_makefile('ext')

View file

@ -0,0 +1,40 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
require 'command-t/ext' # CommandT::Matcher, CommandT::Watchman::Utils
module CommandT
# Encapsulates a Scanner instance (which builds up a list of available files
# in a directory) and a Matcher instance (which selects from that list based
# on a search string).
#
# Specialized subclasses use different kinds of scanners adapted for
# different kinds of search (files, buffers).
class Finder
autoload :BufferFinder, 'command-t/finder/buffer_finder'
autoload :FileFinder, 'command-t/finder/file_finder'
autoload :JumpFinder, 'command-t/finder/jump_finder'
autoload :MRUBufferFinder, 'command-t/finder/mru_buffer_finder'
autoload :TagFinder, 'command-t/finder/tag_finder'
include PathUtilities
def initialize(path = Dir.pwd, options = {})
raise RuntimeError, 'Subclass responsibility'
end
# Options:
# :limit (integer): limit the number of returned matches
def sorted_matches_for(str, options = {})
@matcher.sorted_matches_for str, options
end
def open_selection(command, selection, options = {})
::VIM::command "silent #{command} #{selection}"
end
def path=(path)
@scanner.path = path
end
end # class Finder
end # CommandT

View file

@ -0,0 +1,13 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
class Finder
class BufferFinder < Finder
def initialize
@scanner = Scanner::BufferScanner.new
@matcher = Matcher.new @scanner, :always_show_dot_files => true
end
end # class BufferFinder
end # class Finder
end # CommandT

View file

@ -0,0 +1,29 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
class Finder
class FileFinder < Finder
def initialize(path = Dir.pwd, options = {})
case options.delete(:scanner)
when 'ruby', nil # ruby is the default
@scanner = Scanner::FileScanner::RubyFileScanner.new(path, options)
when 'find'
@scanner = Scanner::FileScanner::FindFileScanner.new(path, options)
when 'watchman'
@scanner = Scanner::FileScanner::WatchmanFileScanner.new(path, options)
when 'git'
@scanner = Scanner::FileScanner::GitFileScanner.new(path, options)
else
raise ArgumentError, "unknown scanner type '#{options[:scanner]}'"
end
@matcher = Matcher.new @scanner, options
end
def flush
@scanner.flush
end
end # class FileFinder
end # class Finder
end # module CommandT

View file

@ -0,0 +1,13 @@
# Copyright 2011-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
class Finder
class JumpFinder < Finder
def initialize
@scanner = Scanner::JumpScanner.new
@matcher = Matcher.new @scanner, :always_show_dot_files => true
end
end # class JumpFinder
end # class Finder
end # module CommandT

View file

@ -0,0 +1,28 @@
# Copyright 2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
class Finder
class MRUBufferFinder < BufferFinder
def initialize
@scanner = Scanner::MRUBufferScanner.new
@matcher = Matcher.new @scanner, :always_show_dot_files => true
end
# Override sorted_matches_for to prevent MRU ordered matches from being
# ordered alphabetically.
def sorted_matches_for(str, options = {})
matches = super(str, options.merge(:sort => false))
# take current buffer (by definition, the most recently used) and move it
# to the end of the results
if MRU.last &&
relative_path_under_working_directory(MRU.last.name) == matches.first
matches[1..-1] + [matches.first]
else
matches
end
end
end # class MRUBufferFinder
end # class Finder
end # CommandT

View file

@ -0,0 +1,26 @@
# Copyright 2011-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
class Finder
class TagFinder < Finder
def initialize(options = {})
@scanner = Scanner::TagScanner.new options
@matcher = Matcher.new @scanner, :always_show_dot_files => true
end
def open_selection(command, selection, options = {})
if @scanner.include_filenames
selection = selection[0, selection.index(':')]
end
# open the tag and center the screen on it
::VIM::command "silent! tag #{selection} | :normal zz"
end
def flush
@scanner.flush
end
end # class TagFinder
end # class Finder
end # module CommandT

View file

@ -0,0 +1,173 @@
// Copyright 2010-2014 Greg Hurrell. All rights reserved.
// Licensed under the terms of the BSD 2-clause license.
#include <float.h> /* for DBL_MAX */
#include "match.h"
#include "ext.h"
#include "ruby_compat.h"
// use a struct to make passing params during recursion easier
typedef struct {
char *haystack_p; // pointer to the path string to be searched
long haystack_len; // length of same
char *needle_p; // pointer to search string (needle)
long needle_len; // length of same
double max_score_per_char;
int always_show_dot_files; // boolean
int never_show_dot_files; // boolean
int case_sensitive; // boolean
double *memo; // memoization
} matchinfo_t;
double recursive_match(matchinfo_t *m, // sharable meta-data
long haystack_idx, // where in the path string to start
long needle_idx, // where in the needle string to start
long last_idx, // location of last matched character
double score) // cumulative score so far
{
double score_for_char;
double seen_score = 0; // remember best score seen via recursion
int found;
long i, j, distance;
long memo_idx = haystack_idx;
// do we have a memoized result we can return?
double memoized = m->memo[needle_idx * m->needle_len + memo_idx];
if (memoized != DBL_MAX)
return memoized;
// bail early if not enough room (left) in haystack for (rest of) needle
if (m->haystack_len - haystack_idx < m->needle_len - needle_idx) {
score = 0.0;
goto memoize;
}
for (i = needle_idx; i < m->needle_len; i++) {
char c = m->needle_p[i];
found = 0;
// similar to above, we'll stop iterating when we know we're too close
// to the end of the string to possibly match
for (j = haystack_idx;
j <= m->haystack_len - (m->needle_len - i);
j++, haystack_idx++) {
char d = m->haystack_p[j];
if (d == '.') {
if (j == 0 || m->haystack_p[j - 1] == '/') { // this is a dot-file
int dot_search = (i == 0 && c == '.'); // searching for a dot
if (m->never_show_dot_files || (!dot_search && !m->always_show_dot_files)) {
score = 0.0;
goto memoize;
}
}
} else if (d >= 'A' && d <= 'Z' && !m->case_sensitive) {
d += 'a' - 'A'; // add 32 to downcase
}
if (c == d) {
found = 1;
// calculate score
score_for_char = m->max_score_per_char;
distance = j - last_idx;
if (distance > 1) {
double factor = 1.0;
char last = m->haystack_p[j - 1];
char curr = m->haystack_p[j]; // case matters, so get again
if (last == '/')
factor = 0.9;
else if (last == '-' ||
last == '_' ||
last == ' ' ||
(last >= '0' && last <= '9'))
factor = 0.8;
else if (last >= 'a' && last <= 'z' &&
curr >= 'A' && curr <= 'Z')
factor = 0.8;
else if (last == '.')
factor = 0.7;
else
// if no "special" chars behind char, factor diminishes
// as distance from last matched char increases
factor = (1.0 / distance) * 0.75;
score_for_char *= factor;
}
if (++j < m->haystack_len) {
// bump cursor one char to the right and
// use recursion to try and find a better match
double sub_score = recursive_match(m, j, i, last_idx, score);
if (sub_score > seen_score)
seen_score = sub_score;
}
score += score_for_char;
last_idx = haystack_idx++;
break;
}
}
if (!found) {
score = 0.0;
goto memoize;
}
}
score = score > seen_score ? score : seen_score;
memoize:
m->memo[needle_idx * m->needle_len + memo_idx] = score;
return score;
}
void calculate_match(VALUE str,
VALUE needle,
VALUE case_sensitive,
VALUE always_show_dot_files,
VALUE never_show_dot_files,
match_t *out)
{
long i, max;
double score;
matchinfo_t m;
m.haystack_p = RSTRING_PTR(str);
m.haystack_len = RSTRING_LEN(str);
m.needle_p = RSTRING_PTR(needle);
m.needle_len = RSTRING_LEN(needle);
m.max_score_per_char = (1.0 / m.haystack_len + 1.0 / m.needle_len) / 2;
m.always_show_dot_files = always_show_dot_files == Qtrue;
m.never_show_dot_files = never_show_dot_files == Qtrue;
m.case_sensitive = case_sensitive;
// calculate score
score = 1.0;
// special case for zero-length search string
if (m.needle_len == 0) {
// filter out dot files
if (!m.always_show_dot_files) {
for (i = 0; i < m.haystack_len; i++) {
char c = m.haystack_p[i];
if (c == '.' && (i == 0 || m.haystack_p[i - 1] == '/')) {
score = 0.0;
break;
}
}
}
} else if (m.haystack_len > 0) { // normal case
// prepare for memoization
double memo[m.haystack_len * m.needle_len];
for (i = 0, max = m.haystack_len * m.needle_len; i < max; i++)
memo[i] = DBL_MAX;
m.memo = memo;
score = recursive_match(&m, 0, 0, 0, 0.0);
}
// final book-keeping
out->path = str;
out->score = score;
}

View file

@ -0,0 +1,17 @@
// Copyright 2010-2014 Greg Hurrell. All rights reserved.
// Licensed under the terms of the BSD 2-clause license.
#include <ruby.h>
// struct for representing an individual match
typedef struct {
VALUE path;
double score;
} match_t;
extern void calculate_match(VALUE str,
VALUE needle,
VALUE case_sensitive,
VALUE always_show_dot_files,
VALUE never_show_dot_files,
match_t *out);

View file

@ -0,0 +1,441 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
require 'ostruct'
module CommandT
class MatchWindow
SELECTION_MARKER = '> '
MARKER_LENGTH = SELECTION_MARKER.length
UNSELECTED_MARKER = ' ' * MARKER_LENGTH
MH_START = '<commandt>'
MH_END = '</commandt>'
@@buffer = nil
def initialize(options = {})
@highlight_color = options[:highlight_color] || 'PmenuSel'
@min_height = options[:min_height]
@prompt = options[:prompt]
@reverse_list = options[:match_window_reverse]
# save existing window dimensions so we can restore them later
@windows = (0..(::VIM::Window.count - 1)).map do |i|
OpenStruct.new(
:index => i,
:height => ::VIM::Window[i].height,
:width => ::VIM::Window[i].width
)
end
set 'timeout', true # ensure mappings timeout
set 'hlsearch', false # don't highlight search strings
set 'insertmode', false # don't make Insert mode the default
set 'showcmd', false # don't show command info on last line
set 'equalalways', false # don't auto-balance window sizes
set 'timeoutlen', 0 # respond immediately to mappings
set 'report', 9999 # don't show "X lines changed" reports
set 'scrolloff', 0 # don't scroll near buffer edges
set 'sidescroll', 0 # don't sidescroll in jumps
set 'sidescrolloff', 0 # don't sidescroll automatically
set 'updatetime', options[:debounce_interval]
# show match window
split_location = options[:match_window_at_top] ? 'topleft' : 'botright'
if @@buffer # still have buffer from last time
::VIM::command "silent! #{split_location} #{@@buffer.number}sbuffer"
raise "Can't re-open GoToFile buffer" unless $curbuf.number == @@buffer.number
$curwin.height = 1
else # creating match window for first time and set it up
::VIM::command "silent! #{split_location} 1split GoToFile"
set 'bufhidden', 'unload' # unload buf when no longer displayed
set 'buftype', 'nofile' # buffer is not related to any file
set 'modifiable', false # prevent manual edits
set 'swapfile', false # don't create a swapfile
set 'wrap', false # don't soft-wrap
set 'number', false # don't show line numbers
set 'list', false # don't use List mode (visible tabs etc)
set 'foldcolumn', 0 # don't show a fold column at side
set 'foldlevel', 99 # don't fold anything
set 'cursorline', false # don't highlight line cursor is on
set 'spell', false # spell-checking off
set 'buflisted', false # don't show up in the buffer list
set 'textwidth', 0 # don't hard-wrap (break long lines)
# don't show the color column
set 'colorcolumn', 0 if VIM::exists?('+colorcolumn')
# don't show relative line numbers
set 'relativenumber', false if VIM::exists?('+relativenumber')
# sanity check: make sure the buffer really was created
raise "Can't find GoToFile buffer" unless $curbuf.name.match /GoToFile\z/
@@buffer = $curbuf
end
# syntax coloring
if VIM::has?('syntax')
::VIM::command "syntax match CommandTSelection \"^#{SELECTION_MARKER}.\\+$\""
::VIM::command 'syntax match CommandTNoEntries "^-- NO MATCHES --$"'
::VIM::command 'syntax match CommandTNoEntries "^-- NO SUCH FILE OR DIRECTORY --$"'
set 'synmaxcol', 9999
if VIM::has?('conceal')
set 'conceallevel', 2
set 'concealcursor', 'nvic'
::VIM::command 'syntax region CommandTCharMatched ' \
"matchgroup=CommandTCharMatched start=+#{MH_START}+ " \
"matchgroup=CommandTCharMatchedEnd end=+#{MH_END}+ concealends"
::VIM::command 'highlight def CommandTCharMatched ' \
'term=bold,underline cterm=bold,underline ' \
'gui=bold,underline'
end
::VIM::command "highlight link CommandTSelection #{@highlight_color}"
::VIM::command 'highlight link CommandTNoEntries Error'
# hide cursor
@cursor_highlight = get_cursor_highlight
hide_cursor
end
# perform cleanup using an autocmd to ensure we don't get caught out
# by some unexpected means of dismissing or leaving the Command-T window
# (eg. <C-W q>, <C-W k> etc)
::VIM::command 'autocmd! * <buffer>'
::VIM::command 'autocmd BufLeave <buffer> silent! ruby $command_t.leave'
::VIM::command 'autocmd BufUnload <buffer> silent! ruby $command_t.unload'
@has_focus = false
@abbrev = ''
@window = $curwin
end
def close
# Unlisted buffers like those provided by Netrw, NERDTree and Vim's help
# don't actually appear in the buffer list; if they are the only such
# buffers present when Command-T is invoked (for example, when invoked
# immediately after starting Vim with a directory argument, like `vim .`)
# then performing the normal clean-up will yield an "E90: Cannot unload
# last buffer" error. We can work around that by doing a :quit first.
if ::VIM::Buffer.count == 0
::VIM::command 'silent quit'
end
# Workaround for upstream bug in Vim 7.3 on some platforms
#
# On some platforms, $curbuf.number always returns 0. One workaround is
# to build Vim with --disable-largefile, but as this is producing lots of
# support requests, implement the following fallback to the buffer name
# instead, at least until upstream gets fixed.
#
# For more details, see: https://wincent.com/issues/1617
if $curbuf.number == 0
# use bwipeout as bunload fails if passed the name of a hidden buffer
::VIM::command 'silent! bwipeout! GoToFile'
@@buffer = nil
else
::VIM::command "silent! bunload! #{@@buffer.number}"
end
end
def leave
close
unload
end
def unload
restore_window_dimensions
@settings.restore
@prompt.dispose
show_cursor
end
def add!(char)
@abbrev += char
end
def backspace!
@abbrev.chop!
end
def select_next
@reverse_list ? _prev : _next
end
def select_prev
@reverse_list ? _next : _prev
end
def matches=(matches)
if matches != @matches
@matches = matches
@selection = 0
print_matches
move_cursor_to_selected_line
end
end
def focus
unless @has_focus
@has_focus = true
if VIM::has?('syntax')
::VIM::command 'highlight link CommandTSelection Search'
end
end
end
def unfocus
if @has_focus
@has_focus = false
if VIM::has?('syntax')
::VIM::command "highlight link CommandTSelection #{@highlight_color}"
end
end
end
def find(char)
# is this a new search or the continuation of a previous one?
now = Time.now
if @last_key_time.nil? || @last_key_time < (now - 0.5)
@find_string = char
else
@find_string += char
end
@last_key_time = now
# see if there's anything up ahead that matches
matches = @reverse_list ? @matches.reverse : @matches
matches.each_with_index do |match, idx|
if match[0, @find_string.length].casecmp(@find_string) == 0
old_selection = @selection
@selection = @reverse_list ? matches.length - idx - 1 : idx
print_match(old_selection) # redraw old selection (removes marker)
print_match(@selection) # redraw new selection (adds marker)
break
end
end
end
# Returns the currently selected item as a String.
def selection
@matches[@selection]
end
def print_no_such_file_or_directory
print_error 'NO SUCH FILE OR DIRECTORY'
end
private
def _next
if @selection < [@window.height, @matches.length].min - 1
@selection += 1
print_match(@selection - 1) # redraw old selection (removes marker)
print_match(@selection) # redraw new selection (adds marker)
move_cursor_to_selected_line
end
end
def _prev
if @selection > 0
@selection -= 1
print_match(@selection + 1) # redraw old selection (removes marker)
print_match(@selection) # redraw new selection (adds marker)
move_cursor_to_selected_line
end
end
# Translate from a 0-indexed match index to a 1-indexed Vim line number.
# Also takes into account reversed listings.
def line(match_index)
@reverse_list ? @window.height - match_index : match_index + 1
end
def set(setting, value)
@settings ||= Settings.new
@settings.set(setting, value)
end
def move_cursor_to_selected_line
# on some non-GUI terminals, the cursor doesn't hide properly
# so we move the cursor to prevent it from blinking away in the
# upper-left corner in a distracting fashion
@window.cursor = [line(@selection), 0]
end
def print_error(msg)
return unless VIM::Window.select(@window)
unlock
clear
@window.height = [1, @min_height].min
@@buffer[1] = "-- #{msg} --"
lock
end
def restore_window_dimensions
# sort from tallest to shortest, tie-breaking on window width
@windows.sort! do |a, b|
order = b.height <=> a.height
if order.zero?
b.width <=> a.width
else
order
end
end
# starting with the tallest ensures that there are no constraints
# preventing windows on the side of vertical splits from regaining
# their original full size
@windows.each do |w|
# beware: window may be nil
if window = ::VIM::Window[w.index]
window.height = w.height
window.width = w.width
end
end
end
def match_text_for_idx(idx)
match = truncated_match @matches[idx].to_s
if idx == @selection
prefix = SELECTION_MARKER
suffix = padding_for_selected_match match
else
if VIM::has?('syntax') && VIM::has?('conceal')
match = match_with_syntax_highlight match
end
prefix = UNSELECTED_MARKER
suffix = ''
end
prefix + match + suffix
end
# Highlight matching characters within the matched string.
#
# Note that this is only approximate; it will highlight the first matching
# instances within the string, which may not actually be the instances that
# were used by the matching/scoring algorithm to determine the best score
# for the match.
#
def match_with_syntax_highlight(match)
highlight_chars = @prompt.abbrev.downcase.scan(/./mu)
match.scan(/./mu).inject([]) do |output, char|
if char.downcase == highlight_chars.first
highlight_chars.shift
output.concat [MH_START, char, MH_END]
else
output << char
end
end.join
end
# Print just the specified match.
def print_match(idx)
return unless VIM::Window.select(@window)
unlock
@@buffer[line(idx)] = match_text_for_idx idx
lock
end
def max_lines
[1, VIM::Screen.lines - 5].max
end
# Print all matches.
def print_matches
match_count = @matches.length
if match_count == 0
print_error 'NO MATCHES'
else
return unless VIM::Window.select(@window)
unlock
clear
@window_width = @window.width # update cached value
desired_lines = [match_count, @min_height].max
desired_lines = [max_lines, desired_lines].min
@window.height = desired_lines
matches = []
(0...@window.height).each do |idx|
text = match_text_for_idx(idx)
@reverse_list ? matches.unshift(text) : matches.push(text)
end
matches.each_with_index do |match, idx|
if @@buffer.count > idx
@@buffer[idx + 1] = match
else
@@buffer.append(idx, match)
end
end
lock
end
end
# Prepare padding for match text (trailing spaces) so that selection
# highlighting extends all the way to the right edge of the window.
def padding_for_selected_match(str)
len = str.length
if len >= @window_width - MARKER_LENGTH
''
else
' ' * (@window_width - MARKER_LENGTH - len)
end
end
# Convert "really/long/path" into "really...path" based on available
# window width.
def truncated_match(str)
len = str.length
available_width = @window_width - MARKER_LENGTH
return str if len <= available_width
left = (available_width / 2) - 1
right = (available_width / 2) - 2 + (available_width % 2)
str[0, left] + '...' + str[-right, right]
end
def clear
# range = % (whole buffer)
# action = d (delete)
# register = _ (black hole register, don't record deleted text)
::VIM::command 'silent %d _'
end
def get_cursor_highlight
# there are 3 possible formats to check for, each needing to be
# transformed in a certain way in order to reapply the highlight:
# Cursor xxx guifg=bg guibg=fg -> :hi! Cursor guifg=bg guibg=fg
# Cursor xxx links to SomethingElse -> :hi! link Cursor SomethingElse
# Cursor xxx cleared -> :hi! clear Cursor
highlight = VIM::capture 'silent! 0verbose highlight Cursor'
if highlight =~ /^Cursor\s+xxx\s+links to (\w+)/
"link Cursor #{$~[1]}"
elsif highlight =~ /^Cursor\s+xxx\s+cleared/
'clear Cursor'
elsif highlight =~ /Cursor\s+xxx\s+(.+)/
"Cursor #{$~[1]}"
else # likely cause E411 Cursor highlight group not found
nil
end
end
def hide_cursor
if @cursor_highlight
::VIM::command 'highlight Cursor NONE'
end
end
def show_cursor
if @cursor_highlight
::VIM::command "highlight #{@cursor_highlight}"
end
end
def lock
set 'modifiable', false
end
def unlock
set 'modifiable', true
end
end
end

View file

@ -0,0 +1,232 @@
// Copyright 2010-2014 Greg Hurrell. All rights reserved.
// Licensed under the terms of the BSD 2-clause license.
#include <stdlib.h> /* for qsort() */
#include <string.h> /* for strncmp() */
#include "matcher.h"
#include "match.h"
#include "ext.h"
#include "ruby_compat.h"
// order matters; we want this to be evaluated only after ruby.h
#ifdef HAVE_PTHREAD_H
#include <pthread.h> /* for pthread_create, pthread_join etc */
#endif
// comparison function for use with qsort
int cmp_alpha(const void *a, const void *b)
{
match_t a_match = *(match_t *)a;
match_t b_match = *(match_t *)b;
VALUE a_str = a_match.path;
VALUE b_str = b_match.path;
char *a_p = RSTRING_PTR(a_str);
long a_len = RSTRING_LEN(a_str);
char *b_p = RSTRING_PTR(b_str);
long b_len = RSTRING_LEN(b_str);
int order = 0;
if (a_len > b_len) {
order = strncmp(a_p, b_p, b_len);
if (order == 0)
order = 1; // shorter string (b) wins
} else if (a_len < b_len) {
order = strncmp(a_p, b_p, a_len);
if (order == 0)
order = -1; // shorter string (a) wins
} else {
order = strncmp(a_p, b_p, a_len);
}
return order;
}
// comparison function for use with qsort
int cmp_score(const void *a, const void *b)
{
match_t a_match = *(match_t *)a;
match_t b_match = *(match_t *)b;
if (a_match.score > b_match.score)
return -1; // a scores higher, a should appear sooner
else if (a_match.score < b_match.score)
return 1; // b scores higher, a should appear later
else
return cmp_alpha(a, b);
}
VALUE CommandTMatcher_initialize(int argc, VALUE *argv, VALUE self)
{
VALUE always_show_dot_files;
VALUE never_show_dot_files;
VALUE options;
VALUE scanner;
// process arguments: 1 mandatory, 1 optional
if (rb_scan_args(argc, argv, "11", &scanner, &options) == 1)
options = Qnil;
if (NIL_P(scanner))
rb_raise(rb_eArgError, "nil scanner");
rb_iv_set(self, "@scanner", scanner);
// check optional options hash for overrides
always_show_dot_files = CommandT_option_from_hash("always_show_dot_files", options);
never_show_dot_files = CommandT_option_from_hash("never_show_dot_files", options);
rb_iv_set(self, "@always_show_dot_files", always_show_dot_files);
rb_iv_set(self, "@never_show_dot_files", never_show_dot_files);
return Qnil;
}
typedef struct {
int thread_count;
int thread_index;
int case_sensitive;
match_t *matches;
long path_count;
VALUE paths;
VALUE abbrev;
VALUE always_show_dot_files;
VALUE never_show_dot_files;
} thread_args_t;
void *match_thread(void *thread_args)
{
long i;
thread_args_t *args = (thread_args_t *)thread_args;
for (i = args->thread_index; i < args->path_count; i += args->thread_count) {
VALUE path = RARRAY_PTR(args->paths)[i];
calculate_match(path,
args->abbrev,
args->case_sensitive,
args->always_show_dot_files,
args->never_show_dot_files,
&args->matches[i]);
}
return NULL;
}
VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self)
{
long i, limit, path_count, thread_count;
#ifdef HAVE_PTHREAD_H
long err;
pthread_t *threads;
#endif
match_t *matches;
thread_args_t *thread_args;
VALUE abbrev;
VALUE case_sensitive;
VALUE always_show_dot_files;
VALUE limit_option;
VALUE never_show_dot_files;
VALUE options;
VALUE paths;
VALUE results;
VALUE scanner;
VALUE sort_option;
VALUE threads_option;
// process arguments: 1 mandatory, 1 optional
if (rb_scan_args(argc, argv, "11", &abbrev, &options) == 1)
options = Qnil;
if (NIL_P(abbrev))
rb_raise(rb_eArgError, "nil abbrev");
// check optional options has for overrides
case_sensitive = CommandT_option_from_hash("case_sensitive", options);
limit_option = CommandT_option_from_hash("limit", options);
threads_option = CommandT_option_from_hash("threads", options);
sort_option = CommandT_option_from_hash("sort", options);
abbrev = StringValue(abbrev);
if (case_sensitive != Qtrue)
abbrev = rb_funcall(abbrev, rb_intern("downcase"), 0);
// get unsorted matches
scanner = rb_iv_get(self, "@scanner");
paths = rb_funcall(scanner, rb_intern("paths"), 0);
always_show_dot_files = rb_iv_get(self, "@always_show_dot_files");
never_show_dot_files = rb_iv_get(self, "@never_show_dot_files");
path_count = RARRAY_LEN(paths);
matches = malloc(path_count * sizeof(match_t));
if (!matches)
rb_raise(rb_eNoMemError, "memory allocation failed");
thread_count = NIL_P(threads_option) ? 1 : NUM2LONG(threads_option);
#ifdef HAVE_PTHREAD_H
#define THREAD_THRESHOLD 1000 /* avoid the overhead of threading when search space is small */
if (path_count < THREAD_THRESHOLD)
thread_count = 1;
threads = malloc(sizeof(pthread_t) * thread_count);
if (!threads)
rb_raise(rb_eNoMemError, "memory allocation failed");
#endif
thread_args = malloc(sizeof(thread_args_t) * thread_count);
if (!thread_args)
rb_raise(rb_eNoMemError, "memory allocation failed");
for (i = 0; i < thread_count; i++) {
thread_args[i].thread_count = thread_count;
thread_args[i].thread_index = i;
thread_args[i].case_sensitive = case_sensitive == Qtrue;
thread_args[i].matches = matches;
thread_args[i].path_count = path_count;
thread_args[i].paths = paths;
thread_args[i].abbrev = abbrev;
thread_args[i].always_show_dot_files = always_show_dot_files;
thread_args[i].never_show_dot_files = never_show_dot_files;
#ifdef HAVE_PTHREAD_H
if (i == thread_count - 1) {
#endif
// for the last "worker", we'll just use the main thread
(void)match_thread(&thread_args[i]);
#ifdef HAVE_PTHREAD_H
} else {
err = pthread_create(&threads[i], NULL, match_thread, (void *)&thread_args[i]);
if (err != 0)
rb_raise(rb_eSystemCallError, "pthread_create() failure (%d)", (int)err);
}
#endif
}
#ifdef HAVE_PTHREAD_H
for (i = 0; i < thread_count - 1; i++) {
err = pthread_join(threads[i], NULL);
if (err != 0)
rb_raise(rb_eSystemCallError, "pthread_join() failure (%d)", (int)err);
}
free(threads);
#endif
if (NIL_P(sort_option) || sort_option == Qtrue) {
if (RSTRING_LEN(abbrev) == 0 ||
(RSTRING_LEN(abbrev) == 1 && RSTRING_PTR(abbrev)[0] == '.'))
// alphabetic order if search string is only "" or "."
qsort(matches, path_count, sizeof(match_t), cmp_alpha);
else
// for all other non-empty search strings, sort by score
qsort(matches, path_count, sizeof(match_t), cmp_score);
}
results = rb_ary_new();
limit = NIL_P(limit_option) ? 0 : NUM2LONG(limit_option);
if (limit == 0)
limit = path_count;
for (i = 0; i < path_count && limit > 0; i++) {
if (matches[i].score > 0.0) {
rb_funcall(results, rb_intern("push"), 1, matches[i].path);
limit--;
}
}
free(matches);
return results;
}

View file

@ -0,0 +1,7 @@
// Copyright 2010-2014 Greg Hurrell. All rights reserved.
// Licensed under the terms of the BSD 2-clause license.
#include <ruby.h>
extern VALUE CommandTMatcher_initialize(int argc, VALUE *argv, VALUE self);
extern VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self);

View file

@ -0,0 +1,43 @@
# Copyright 2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
# Maintains a stack of seen buffers in MRU (most recently used) order.
module MRU
class << self
# The stack of used buffers in MRU order.
def stack
@stack ||= []
end
# The (last) most recent buffer in the stack, if any.
def last
stack.last
end
# Mark the current buffer as having been used, effectively moving it to
# the top of the stack.
def touch
return unless ::VIM::evaluate('buflisted(%d)' % $curbuf.number) == 1
return unless $curbuf.name
stack.delete $curbuf
stack.push $curbuf
end
# Mark a buffer as deleted, removing it from the stack.
def delete
# Note that $curbuf does not point to the buffer that is being deleted;
# we need to use Vim's <abuf> for the correct buffer number.
stack.delete_if do |b|
b.number == ::VIM::evaluate('expand("<abuf>")').to_i
end
end
# Returns `true` if `buffer` has been used (ie. is present in the stack).
def used?(buffer)
stack.include?(buffer)
end
end
end # module MRU
end # module CommandT

View file

@ -0,0 +1,17 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
module PathUtilities
private
def relative_path_under_working_directory(path)
# any path under the working directory will be specified as a relative
# path to improve the readability of the buffer list etc
pwd = File.expand_path(VIM::pwd) + '/'
path.index(pwd) == 0 ? path[pwd.length..-1] : path
end
end # module PathUtilities
end # module CommandT

View file

@ -0,0 +1,161 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
# Abuse the status line as a prompt.
class Prompt
attr_accessor :abbrev
def initialize
@abbrev = '' # abbreviation entered so far
@col = 0 # cursor position
@has_focus = false
end
# Erase whatever is displayed in the prompt line,
# effectively disposing of the prompt
def dispose
::VIM::command 'echo'
::VIM::command 'redraw'
end
# Clear any entered text.
def clear!
@abbrev = ''
@col = 0
redraw
end
# Remove word before cursor
def clear_prev_word!
suffix_length = @abbrev.length - @col
@abbrev.match(
%r{
(.*?) # prefix
\w*\s* # word to clear
(.{#{suffix_length}}) # suffix
\z
}x
)
@abbrev = $~[1] + $~[2]
@col = @abbrev.length - suffix_length
redraw
end
# Insert a character at (before) the current cursor position.
def add!(char)
left, cursor, right = abbrev_segments
@abbrev = left + char + cursor + right
@col += 1
redraw
end
# Delete a character to the left of the current cursor position.
def backspace!
if @col > 0
left, cursor, right = abbrev_segments
@abbrev = left.chop! + cursor + right
@col -= 1
redraw
end
end
# Delete a character at the current cursor position.
def delete!
if @col < @abbrev.length
left, cursor, right = abbrev_segments
@abbrev = left + right
redraw
end
end
def cursor_left
if @col > 0
@col -= 1
redraw
end
end
def cursor_right
if @col < @abbrev.length
@col += 1
redraw
end
end
def cursor_end
if @col < @abbrev.length
@col = @abbrev.length
redraw
end
end
def cursor_start
if @col != 0
@col = 0
redraw
end
end
def focus
unless @has_focus
@has_focus = true
redraw
end
end
def unfocus
if @has_focus
@has_focus = false
redraw
end
end
private
def redraw
if @has_focus
prompt_highlight = 'Comment'
normal_highlight = 'None'
cursor_highlight = 'Underlined'
else
prompt_highlight = 'NonText'
normal_highlight = 'NonText'
cursor_highlight = 'NonText'
end
left, cursor, right = abbrev_segments
components = [prompt_highlight, '>>', 'None', ' ']
components += [normal_highlight, left] unless left.empty?
components += [cursor_highlight, cursor] unless cursor.empty?
components += [normal_highlight, right] unless right.empty?
components += [cursor_highlight, ' '] if cursor.empty?
set_status *components
end
# Returns the @abbrev string divided up into three sections, any of
# which may actually be zero width, depending on the location of the
# cursor:
# - left segment (to left of cursor)
# - cursor segment (character at cursor)
# - right segment (to right of cursor)
def abbrev_segments
left = @abbrev[0, @col]
cursor = @abbrev[@col, 1]
right = @abbrev[(@col + 1)..-1] || ''
[left, cursor, right]
end
def set_status(*args)
# see ':help :echo' for why forcing a redraw here helps
# prevent the status line from getting inadvertantly cleared
# after our echo commands
::VIM::command 'redraw'
while (highlight = args.shift) && (text = args.shift)
text = VIM::escape_for_single_quotes text
::VIM::command "echohl #{highlight}"
::VIM::command "echon '#{text}'"
end
::VIM::command 'echohl None'
end
end # class Prompt
end # module CommandT

View file

@ -0,0 +1,29 @@
// Copyright 2010-2014 Greg Hurrell. All rights reserved.
// Licensed under the terms of the BSD 2-clause license.
#include <ruby.h>
// for compatibility with older versions of Ruby which don't declare RSTRING_PTR
#ifndef RSTRING_PTR
#define RSTRING_PTR(s) (RSTRING(s)->ptr)
#endif
// for compatibility with older versions of Ruby which don't declare RSTRING_LEN
#ifndef RSTRING_LEN
#define RSTRING_LEN(s) (RSTRING(s)->len)
#endif
// for compatibility with older versions of Ruby which don't declare RARRAY_PTR
#ifndef RARRAY_PTR
#define RARRAY_PTR(a) (RARRAY(a)->ptr)
#endif
// for compatibility with older versions of Ruby which don't declare RARRAY_LEN
#ifndef RARRAY_LEN
#define RARRAY_LEN(a) (RARRAY(a)->len)
#endif
// for compatibility with older versions of Ruby which don't declare RFLOAT_VALUE
#ifndef RFLOAT_VALUE
#define RFLOAT_VALUE(f) (RFLOAT(f)->value)
#endif

View file

@ -0,0 +1,12 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
class Scanner
autoload :BufferScanner, 'command-t/scanner/buffer_scanner'
autoload :FileScanner, 'command-t/scanner/file_scanner'
autoload :JumpScanner, 'command-t/scanner/jump_scanner'
autoload :MRUBufferScanner, 'command-t/scanner/mru_buffer_scanner'
autoload :TagScanner, 'command-t/scanner/tag_scanner'
end # class Scanner
end # module CommandT

View file

@ -0,0 +1,20 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
class Scanner
# Returns a list of all open buffers.
class BufferScanner < Scanner
include PathUtilities
def paths
(0..(::VIM::Buffer.count - 1)).map do |n|
buffer = ::VIM::Buffer[n]
if buffer.name # beware, may be nil
relative_path_under_working_directory buffer.name
end
end.compact
end
end # class BufferScanner
end # class Scanner
end # module CommandT

View file

@ -0,0 +1,93 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
class Scanner
# Reads the current directory recursively for the paths to all regular files.
#
# This is an abstract superclass; the real work is done by subclasses which
# obtain file listings via different strategies (for examples, see the
# RubyFileScanner and FindFileScanner subclasses).
class FileScanner < Scanner
# Errors
autoload :FileLimitExceeded, 'command-t/scanner/file_scanner/file_limit_exceeded'
# Subclasses
autoload :FindFileScanner, 'command-t/scanner/file_scanner/find_file_scanner'
autoload :GitFileScanner, 'command-t/scanner/file_scanner/git_file_scanner'
autoload :RubyFileScanner, 'command-t/scanner/file_scanner/ruby_file_scanner'
autoload :WatchmanFileScanner, 'command-t/scanner/file_scanner/watchman_file_scanner'
attr_accessor :path
def initialize(path = Dir.pwd, options = {})
@paths = {}
@paths_keys = []
@path = path
@max_depth = options[:max_depth] || 15
@max_files = options[:max_files] || 30_000
@max_caches = options[:max_caches] || 1
@scan_dot_directories = options[:scan_dot_directories] || false
@wild_ignore = options[:wild_ignore]
@base_wild_ignore = wild_ignore
end
def paths
@paths[@path] ||= begin
ensure_cache_under_limit
@prefix_len = @path.chomp('/').length + 1
set_wild_ignore { paths! }
end
end
def flush
@paths = {}
end
private
def wild_ignore
VIM::exists?('&wildignore') && ::VIM::evaluate('&wildignore').to_s
end
def paths!
raise RuntimeError, 'Subclass responsibility'
end
def ensure_cache_under_limit
# Ruby 1.8 doesn't have an ordered hash, so use a separate stack to
# track and expire the oldest entry in the cache
if @max_caches > 0 && @paths_keys.length >= @max_caches
@paths.delete @paths_keys.shift
end
@paths_keys << @path
end
def path_excluded?(path, prefix_len = @prefix_len)
if apply_wild_ignore?
# first strip common prefix (@path) from path to match VIM's behavior
path = path[prefix_len..-1]
path = VIM::escape_for_single_quotes path
::VIM::evaluate("empty(expand(fnameescape('#{path}')))").to_i == 1
end
end
def has_custom_wild_ignore?
@wild_ignore && !@wild_ignore.empty?
end
# Used to skip expensive calls to `expand()` when there is no applicable
# wildignore.
def apply_wild_ignore?
has_custom_wild_ignore? || @base_wild_ignore
end
def set_wild_ignore(&block)
::VIM::command("set wildignore=#{@wild_ignore}") if has_custom_wild_ignore?
yield
ensure
::VIM::command("set wildignore=#{@base_wild_ignore}") if has_custom_wild_ignore?
end
end # class FileScanner
end # class Scanner
end # module CommandT

View file

@ -0,0 +1,10 @@
# Copyright 2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
class Scanner
class FileScanner
class FileLimitExceeded < ::RuntimeError; end
end # class FileScanner
end # class Scanner
end # module Command-T

View file

@ -0,0 +1,50 @@
# Copyright 2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
require 'open3'
module CommandT
class Scanner
class FileScanner
# A FileScanner which shells out to the `find` executable in order to scan.
class FindFileScanner < FileScanner
include PathUtilities
def paths!
# temporarily set field separator to NUL byte; this setting is
# respected by both `readlines` and `chomp!` below, and makes it easier
# to parse the output of `find -print0`
separator = $/
$/ = "\x00"
unless @scan_dot_directories
dot_directory_filter = [
'-not', '-path', "#{@path}/.*/*", # top-level dot dir
'-and', '-not', '-path', "#{@path}/*/.*/*" # lower-level dot dir
]
end
paths = []
Open3.popen3(*([
'find', '-L', # follow symlinks
@path, # anchor search here
'-maxdepth', @max_depth.to_s, # limit depth of DFS
'-type', 'f', # only show regular files (not dirs etc)
dot_directory_filter, # possibly skip out dot directories
'-print0' # NUL-terminate results
].flatten.compact)) do |stdin, stdout, stderr|
counter = 1
stdout.readlines.each do |line|
next if path_excluded?(line.chomp!)
paths << line[@prefix_len..-1]
break if (counter += 1) > @max_files
end
end
paths
ensure
$/ = separator
end
end # class FindFileScanner
end # class FileScanner
end # class Scanner
end # module CommandT

View file

@ -0,0 +1,34 @@
# Copyright 2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
class Scanner
class FileScanner
# Uses git ls-files to scan for files
class GitFileScanner < FindFileScanner
def paths!
Dir.chdir(@path) do
stdin, stdout, stderr = Open3.popen3(*[
'git',
'ls-files',
'--exclude-standard',
@path
])
all_files = stdout.readlines.
map { |path| path.chomp }.
reject { |path| path_excluded?(path, 0) }.
take(@max_files).
to_a
# will fall back to find if not a git repository or there's an error
stderr.gets ? super : all_files
end
rescue Errno::ENOENT => e
# git executable not present and executable
super
end
end # class GitFileScanner
end # class FileScanner
end # class Scanner
end # module CommandT

View file

@ -0,0 +1,55 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
class Scanner
class FileScanner
# Pure Ruby implementation of a file scanner.
class RubyFileScanner < FileScanner
def paths!
accumulator = []
@depth = 0
@files = 0
add_paths_for_directory(@path, accumulator)
accumulator
rescue FileLimitExceeded
accumulator
end
private
def looped_symlink?(path)
if File.symlink?(path)
target = File.expand_path(File.readlink(path), File.dirname(path))
target.include?(@path) || @path.include?(target)
end
end
def add_paths_for_directory(dir, accumulator)
Dir.foreach(dir) do |entry|
next if ['.', '..'].include?(entry)
path = File.join(dir, entry)
unless path_excluded?(path)
if File.file?(path)
@files += 1
raise FileLimitExceeded if @files > @max_files
accumulator << path[@prefix_len..-1]
elsif File.directory?(path)
next if @depth >= @max_depth
next if (entry.match(/\A\./) && !@scan_dot_directories)
next if looped_symlink?(path)
@depth += 1
add_paths_for_directory(path, accumulator)
@depth -= 1
end
end
end
rescue Errno::EACCES
# skip over directories for which we don't have access
rescue ArgumentError
# skip over bad file names
end
end # class RubyFileScanner
end # class FileScanner
end # class Scanner
end # module CommandT

View file

@ -0,0 +1,55 @@
# Copyright 2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
require 'pathname'
require 'socket'
module CommandT
class Scanner
class FileScanner
# A FileScanner which delegates the heavy lifting to Watchman
# (https://github.com/facebook/watchman); useful for very large hierarchies.
#
# Inherits from FindFileScanner so that it can fall back to it in the event
# that Watchman isn't available or able to fulfil the request.
class WatchmanFileScanner < FindFileScanner
# Exception raised when Watchman is unavailable or unable to process the
# requested path.
class WatchmanUnavailable < RuntimeError; end
def paths!
sockname = Watchman::Utils.load(
%x{watchman --output-encoding=bser get-sockname}
)['sockname']
raise WatchmanUnavailable unless $?.exitstatus.zero?
UNIXSocket.open(sockname) do |socket|
root = Pathname.new(@path).realpath.to_s
roots = Watchman::Utils.query(['watch-list'], socket)['roots']
if !roots.include?(root)
# this path isn't being watched yet; try to set up watch
result = Watchman::Utils.query(['watch', root], socket)
# root_restrict_files setting may prevent Watchman from working
raise WatchmanUnavailable if result.has_key?('error')
end
query = ['query', root, {
'expression' => ['type', 'f'],
'fields' => ['name'],
}]
paths = Watchman::Utils.query(query, socket)
# could return error if watch is removed
raise WatchmanUnavailable if paths.has_key?('error')
paths['files']
end
end
rescue Errno::ENOENT, WatchmanUnavailable
# watchman executable not present, or unable to fulfil request
super
end # class WatchmanFileScanner
end # class FileScanner
end # class Scanner
end # module CommandT

View file

@ -0,0 +1,32 @@
# Copyright 2011-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
class Scanner
# Returns a list of files in the jumplist.
class JumpScanner < Scanner
include PathUtilities
def paths
jumps_with_filename = jumps.lines.select do |line|
line_contains_filename?(line)
end
filenames = jumps_with_filename[1..-2].map do |line|
relative_path_under_working_directory line.split[3]
end
filenames.sort.uniq
end
private
def line_contains_filename?(line)
line.split.count > 3
end
def jumps
VIM::capture 'silent jumps'
end
end # class JumpScanner
end # class Scanner
end # module CommandT

View file

@ -0,0 +1,27 @@
# Copyright 2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
class Scanner
# Returns a list of all open buffers, sorted in MRU order.
class MRUBufferScanner < BufferScanner
include PathUtilities
def paths
# Collect all buffers that have not been used yet.
unused_buffers = (0..(::VIM::Buffer.count - 1)).map do |n|
buffer = ::VIM::Buffer[n]
buffer if buffer.name && !MRU.used?(buffer)
end
# Combine all most recently used buffers and all unused buffers, and
# return all listed buffer paths.
(unused_buffers + MRU.stack).map do |buffer|
if buffer && buffer.name
relative_path_under_working_directory buffer.name
end
end.compact.reverse
end
end # class MRUBufferScanner
end # class Scanner
end # module CommandT

View file

@ -0,0 +1,33 @@
# Copyright 2011-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
class Scanner
class TagScanner < Scanner
attr_reader :include_filenames
def initialize(options = {})
@include_filenames = options[:include_filenames] || false
@cached_tags = nil
end
def paths
@cached_tags ||= taglist.map do |tag|
path = tag['name']
path << ":#{tag['filename']}" if @include_filenames
path
end.uniq.sort
end
def flush
@cached_tags = nil
end
private
def taglist
::VIM::evaluate 'taglist(".")'
end
end # class TagScanner
end # class Scanner
end # module CommandT

View file

@ -0,0 +1,22 @@
# Copyright 2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
module SCMUtilities
private
def nearest_ancestor(starting_directory, markers)
path = File.expand_path(starting_directory)
while !markers.
map { |dir| File.join(path, dir) }.
map { |dir| File.exist?(dir) }.
any?
next_path = File.expand_path(File.join(path, '..'))
return nil if next_path == path
path = next_path
end
path
end
end # module SCMUtilities
end # module CommandT

View file

@ -0,0 +1,97 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
# Convenience class for saving and restoring global settings.
class Settings
# Settings which apply globally and so must be manually saved and restored
GLOBAL_SETTINGS = %w[
equalalways
hlsearch
insertmode
report
showcmd
scrolloff
sidescroll
sidescrolloff
timeout
timeoutlen
updatetime
]
# Settings which can be made locally to the Command-T buffer or window
LOCAL_SETTINGS = %w[
bufhidden
buflisted
buftype
colorcolumn
concealcursor
conceallevel
cursorline
foldcolumn
foldlevel
list
modifiable
number
relativenumber
spell
swapfile
synmaxcol
textwidth
wrap
]
KNOWN_SETTINGS = GLOBAL_SETTINGS + LOCAL_SETTINGS
def initialize
@settings = []
end
def set(setting, value)
raise "Unknown setting #{setting}" unless KNOWN_SETTINGS.include?(setting)
case value
when TrueClass, FalseClass
@settings.push([setting, VIM::get_bool("&#{setting}")]) if global?(setting)
set_bool setting, value
when Numeric
@settings.push([setting, VIM::get_number("&#{setting}")]) if global?(setting)
set_number setting, value
when String
@settings.push([setting, VIM::get_string("&#{setting}")]) if global?(setting)
set_string setting, value
end
end
def restore
@settings.each do |setting, value|
case value
when TrueClass, FalseClass
set_bool setting, value
when Numeric
set_number setting, value
when String
set_string setting, value
end
end
end
private
def global?(setting)
GLOBAL_SETTINGS.include?(setting)
end
def set_bool(setting, value)
command = global?(setting) ? 'set' : 'setlocal'
setting = value ? setting : "no#{setting}"
::VIM::command "#{command} #{setting}"
end
def set_number(setting, value)
command = global?(setting) ? 'set' : 'setlocal'
::VIM::command "#{command} #{setting}=#{value}"
end
alias set_string set_number
end # class Settings
end # module CommandT

View file

@ -0,0 +1,31 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
class Stub
@@patch_level = defined?(RUBY_PATCHLEVEL) ? RUBY_PATCHLEVEL : '[unknown]'
@@load_error = ['command-t.vim could not load the C extension',
'Please see INSTALLATION and TROUBLE-SHOOTING in the help',
"Vim Ruby version: #{RUBY_VERSION}-p#{@@patch_level}",
'For more information type: :help command-t']
[
:flush,
:show_buffer_finder,
:show_file_finder,
:show_jump_finder,
:show_mru_finder,
:show_tag_finder
].each do |method|
define_method(method) { warn *@@load_error }
end
private
def warn(*msg)
::VIM::command 'echohl WarningMsg'
msg.each { |m| ::VIM::command "echo '#{m}'" }
::VIM::command 'echohl none'
end
end # class Stub
end # module CommandT

View file

@ -0,0 +1,94 @@
# Copyright 2013-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
require 'rbconfig'
module CommandT
module Util
class << self
def processor_count
@processor_count ||= begin
count = processor_count!
count = 1 if count < 1 # sanity check
count = 32 if count > 32 # sanity check
count
end
end
private
# This method derived from:
#
# https://github.com/grosser/parallel/blob/d11e4a3c8c1a/lib/parallel.rb
#
# Number of processors seen by the OS and used for process scheduling.
#
# * AIX: /usr/sbin/pmcycles (AIX 5+), /usr/sbin/lsdev
# * BSD: /sbin/sysctl
# * Cygwin: /proc/cpuinfo
# * Darwin: /usr/bin/hwprefs, /usr/sbin/sysctl
# * HP-UX: /usr/sbin/ioscan
# * IRIX: /usr/sbin/sysconf
# * Linux: /proc/cpuinfo
# * Minix 3+: /proc/cpuinfo
# * Solaris: /usr/sbin/psrinfo
# * Tru64 UNIX: /usr/sbin/psrinfo
# * UnixWare: /usr/sbin/psrinfo
#
# Copyright (C) 2013 Michael Grosser <michael@grosser.it>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
def processor_count!
os_name = RbConfig::CONFIG['target_os']
if os_name =~ /mingw|mswin/
require 'win32ole'
result = WIN32OLE.connect('winmgmts://').ExecQuery(
'select NumberOfLogicalProcessors from Win32_Processor')
result.to_enum.collect(&:NumberOfLogicalProcessors).reduce(:+)
elsif File.readable?('/proc/cpuinfo')
IO.read('/proc/cpuinfo').scan(/^processor/).size
elsif File.executable?('/usr/bin/hwprefs')
IO.popen(%w[/usr/bin/hwprefs thread_count]).read.to_i
elsif File.executable?('/usr/sbin/psrinfo')
IO.popen('/usr/sbin/psrinfo').read.scan(/^.*on-*line/).size
elsif File.executable?('/usr/sbin/ioscan')
IO.popen(%w[/usr/sbin/ioscan -kC processor]) do |out|
out.read.scan(/^.*processor/).size
end
elsif File.executable?('/usr/sbin/pmcycles')
IO.popen(%w[/usr/sbin/pmcycles -m]).read.count("\n")
elsif File.executable?('/usr/sbin/lsdev')
IO.popen(%w[/usr/sbin/lsdev -Cc processor -S 1]).read.count("\n")
elsif File.executable?('/usr/sbin/sysconf') && os_name =~ /irix/i
IO.popen(%w[/usr/sbin/sysconf NPROC_ONLN]).read.to_i
elsif File.executable?('/usr/sbin/sysctl')
IO.popen(%w[/usr/sbin/sysctl -n hw.ncpu]).read.to_i
elsif File.executable?('/sbin/sysctl')
IO.popen(%w[/sbin/sysctl -n hw.ncpu]).read.to_i
else # unknown platform
1
end
rescue
1
end
end
end # module Util
end # module CommandT

View file

@ -0,0 +1,71 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
module VIM
autoload :Screen, 'command-t/vim/screen'
autoload :Window, 'command-t/vim/window'
class << self
# Check for the existence of a feature such as "conceal" or "syntax".
def has?(feature)
::VIM::evaluate(%{has("#{feature}")}).to_i != 0
end
# Check for the presence of a setting such as:
#
# - g:CommandTSmartCase (plug-in setting)
# - &wildignore (Vim setting)
# - +cursorcolumn (Vim setting, that works)
#
def exists?(str)
::VIM::evaluate(%{exists("#{str}")}).to_i != 0
end
def get_number(name)
exists?(name) ? ::VIM::evaluate("#{name}").to_i : nil
end
def get_bool(name)
exists?(name) ? ::VIM::evaluate("#{name}").to_i != 0 : nil
end
def get_string(name)
exists?(name) ? ::VIM::evaluate("#{name}").to_s : nil
end
# expect a string or a list of strings
def get_list_or_string(name)
return nil unless exists?(name)
list_or_string = ::VIM::evaluate("#{name}")
if list_or_string.kind_of?(Array)
list_or_string.map { |item| item.to_s }
else
list_or_string.to_s
end
end
def pwd
::VIM::evaluate 'getcwd()'
end
def current_file_dir
::VIM::evaluate 'expand("%:p:h")'
end
# Execute cmd, capturing the output into a variable and returning it.
def capture(cmd)
::VIM::command 'silent redir => g:command_t_captured_output'
::VIM::command cmd
::VIM::command 'silent redir END'
::VIM::evaluate 'g:command_t_captured_output'
end
# Escape a string for safe inclusion in a Vim single-quoted string
# (single quotes escaped by doubling, everything else is literal)
def escape_for_single_quotes(str)
str.gsub "'", "''"
end
end
end # module VIM
end # module CommandT

View file

@ -0,0 +1,14 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
module VIM
module Screen
class << self
def lines
::VIM::evaluate('&lines').to_i
end
end
end # module Screen
end # module VIM
end # module CommandT

View file

@ -0,0 +1,20 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
module CommandT
module VIM
module Window
class << self
def select(window)
return true if $curwin == window
initial = $curwin
while true do
::VIM::command 'wincmd w' # cycle through windows
return true if $curwin == window # have selected desired window
return false if $curwin == initial # have already looped through all
end
end
end
end # module Window
end # module VIM
end # module CommandT

View file

@ -0,0 +1,660 @@
// Copyright 2014 Greg Hurrell. All rights reserved.
// Licensed under the terms of the BSD 2-clause license.
#include "watchman.h"
#ifdef WATCHMAN_BUILD
#if defined(HAVE_RUBY_ST_H)
#include <ruby/st.h>
#elif defined(HAVE_ST_H)
#include <st.h>
#else
#error no st.h header found
#endif
#include <stdint.h> /* for uint8_t */
#include <fcntl.h> /* for fcntl() */
#include <sys/errno.h> /* for errno */
#include <sys/socket.h> /* for recv(), MSG_PEEK */
typedef struct {
uint8_t *data; // payload
size_t cap; // total capacity
size_t len; // current length
} watchman_t;
// Forward declarations:
VALUE watchman_load(char **ptr, char *end);
void watchman_dump(watchman_t *w, VALUE serializable);
#define WATCHMAN_DEFAULT_STORAGE 4096
#define WATCHMAN_BINARY_MARKER "\x00\x01"
#define WATCHMAN_ARRAY_MARKER 0x00
#define WATCHMAN_HASH_MARKER 0x01
#define WATCHMAN_STRING_MARKER 0x02
#define WATCHMAN_INT8_MARKER 0x03
#define WATCHMAN_INT16_MARKER 0x04
#define WATCHMAN_INT32_MARKER 0x05
#define WATCHMAN_INT64_MARKER 0x06
#define WATCHMAN_FLOAT_MARKER 0x07
#define WATCHMAN_TRUE 0x08
#define WATCHMAN_FALSE 0x09
#define WATCHMAN_NIL 0x0a
#define WATCHMAN_TEMPLATE_MARKER 0x0b
#define WATCHMAN_SKIP_MARKER 0x0c
#define WATCHMAN_HEADER \
WATCHMAN_BINARY_MARKER \
"\x06" \
"\x00\x00\x00\x00\x00\x00\x00\x00"
static const char watchman_array_marker = WATCHMAN_ARRAY_MARKER;
static const char watchman_hash_marker = WATCHMAN_HASH_MARKER;
static const char watchman_string_marker = WATCHMAN_STRING_MARKER;
static const char watchman_true = WATCHMAN_TRUE;
static const char watchman_false = WATCHMAN_FALSE;
static const char watchman_nil = WATCHMAN_NIL;
/**
* Appends `len` bytes, starting at `data`, to the watchman_t struct `w`
*
* Will attempt to reallocate the underlying storage if it is not sufficient.
*/
void watchman_append(watchman_t *w, const char *data, size_t len) {
if (w->len + len > w->cap) {
w->cap += w->len + WATCHMAN_DEFAULT_STORAGE;
REALLOC_N(w->data, uint8_t, w->cap);
}
memcpy(w->data + w->len, data, len);
w->len += len;
}
/**
* Allocate a new watchman_t struct
*
* The struct has a small amount of extra capacity preallocated, and a blank
* header that can be filled in later to describe the PDU.
*/
watchman_t *watchman_init() {
watchman_t *w = ALLOC(watchman_t);
w->cap = WATCHMAN_DEFAULT_STORAGE;
w->len = 0;
w->data = ALLOC_N(uint8_t, WATCHMAN_DEFAULT_STORAGE);
watchman_append(w, WATCHMAN_HEADER, sizeof(WATCHMAN_HEADER) - 1);
return w;
}
/**
* Free a watchman_t struct `w` that was previously allocated with
* `watchman_init`
*/
void watchman_free(watchman_t *w) {
xfree(w->data);
xfree(w);
}
/**
* Encodes and appends the integer `num` to `w`
*/
void watchman_dump_int(watchman_t *w, int64_t num) {
char encoded[1 + sizeof(int64_t)];
if (num == (int8_t)num) {
encoded[0] = WATCHMAN_INT8_MARKER;
encoded[1] = (int8_t)num;
watchman_append(w, encoded, 1 + sizeof(int8_t));
} else if (num == (int16_t)num) {
encoded[0] = WATCHMAN_INT16_MARKER;
*(int16_t *)(encoded + 1) = (int16_t)num;
watchman_append(w, encoded, 1 + sizeof(int16_t));
} else if (num == (int32_t)num) {
encoded[0] = WATCHMAN_INT32_MARKER;
*(int32_t *)(encoded + 1) = (int32_t)num;
watchman_append(w, encoded, 1 + sizeof(int32_t));
} else {
encoded[0] = WATCHMAN_INT64_MARKER;
*(int64_t *)(encoded + 1) = (int64_t)num;
watchman_append(w, encoded, 1 + sizeof(int64_t));
}
}
/**
* Encodes and appends the string `string` to `w`
*/
void watchman_dump_string(watchman_t *w, VALUE string) {
watchman_append(w, &watchman_string_marker, sizeof(watchman_string_marker));
watchman_dump_int(w, RSTRING_LEN(string));
watchman_append(w, RSTRING_PTR(string), RSTRING_LEN(string));
}
/**
* Encodes and appends the double `num` to `w`
*/
void watchman_dump_double(watchman_t *w, double num) {
char encoded[1 + sizeof(double)];
encoded[0] = WATCHMAN_FLOAT_MARKER;
*(double *)(encoded + 1) = num;
watchman_append(w, encoded, sizeof(encoded));
}
/**
* Encodes and appends the array `array` to `w`
*/
void watchman_dump_array(watchman_t *w, VALUE array) {
long i;
watchman_append(w, &watchman_array_marker, sizeof(watchman_array_marker));
watchman_dump_int(w, RARRAY_LEN(array));
for (i = 0; i < RARRAY_LEN(array); i++) {
watchman_dump(w, rb_ary_entry(array, i));
}
}
/**
* Helper method that encodes and appends a key/value pair (`key`, `value`) from
* a hash to the watchman_t struct passed in via `data`
*/
int watchman_dump_hash_iterator(VALUE key, VALUE value, VALUE data) {
watchman_t *w = (watchman_t *)data;
watchman_dump_string(w, StringValue(key));
watchman_dump(w, value);
return ST_CONTINUE;
}
/**
* Encodes and appends the hash `hash` to `w`
*/
void watchman_dump_hash(watchman_t *w, VALUE hash) {
watchman_append(w, &watchman_hash_marker, sizeof(watchman_hash_marker));
watchman_dump_int(w, RHASH_SIZE(hash));
rb_hash_foreach(hash, watchman_dump_hash_iterator, (VALUE)w);
}
/**
* Encodes and appends the serialized Ruby object `serializable` to `w`
*
* Examples of serializable objects include arrays, hashes, strings, numbers
* (integers, floats), booleans, and nil.
*/
void watchman_dump(watchman_t *w, VALUE serializable) {
switch (TYPE(serializable)) {
case T_ARRAY:
return watchman_dump_array(w, serializable);
case T_HASH:
return watchman_dump_hash(w, serializable);
case T_STRING:
return watchman_dump_string(w, serializable);
case T_FIXNUM: // up to 63 bits
return watchman_dump_int(w, FIX2LONG(serializable));
case T_BIGNUM:
return watchman_dump_int(w, NUM2LL(serializable));
case T_FLOAT:
return watchman_dump_double(w, NUM2DBL(serializable));
case T_TRUE:
return watchman_append(w, &watchman_true, sizeof(watchman_true));
case T_FALSE:
return watchman_append(w, &watchman_false, sizeof(watchman_false));
case T_NIL:
return watchman_append(w, &watchman_nil, sizeof(watchman_nil));
default:
rb_raise(rb_eTypeError, "unsupported type");
}
}
/**
* Extract and return the int encoded at `ptr`
*
* Moves `ptr` past the extracted int.
*
* Will raise an ArgumentError if extracting the int would take us beyond the
* end of the buffer indicated by `end`, or if there is no int encoded at `ptr`.
*
* @returns The extracted int
*/
int64_t watchman_load_int(char **ptr, char *end) {
char *val_ptr = *ptr + sizeof(int8_t);
int64_t val = 0;
if (val_ptr >= end) {
rb_raise(rb_eArgError, "insufficient int storage");
}
switch (*ptr[0]) {
case WATCHMAN_INT8_MARKER:
if (val_ptr + sizeof(int8_t) > end) {
rb_raise(rb_eArgError, "overrun extracting int8_t");
}
val = *(int8_t *)val_ptr;
*ptr = val_ptr + sizeof(int8_t);
break;
case WATCHMAN_INT16_MARKER:
if (val_ptr + sizeof(int16_t) > end) {
rb_raise(rb_eArgError, "overrun extracting int16_t");
}
val = *(int16_t *)val_ptr;
*ptr = val_ptr + sizeof(int16_t);
break;
case WATCHMAN_INT32_MARKER:
if (val_ptr + sizeof(int32_t) > end) {
rb_raise(rb_eArgError, "overrun extracting int32_t");
}
val = *(int32_t *)val_ptr;
*ptr = val_ptr + sizeof(int32_t);
break;
case WATCHMAN_INT64_MARKER:
if (val_ptr + sizeof(int64_t) > end) {
rb_raise(rb_eArgError, "overrun extracting int64_t");
}
val = *(int64_t *)val_ptr;
*ptr = val_ptr + sizeof(int64_t);
break;
default:
rb_raise(rb_eArgError, "bad integer marker 0x%02x", (unsigned int)*ptr[0]);
break;
}
return val;
}
/**
* Reads and returns a string encoded in the Watchman binary protocol format,
* starting at `ptr` and finishing at or before `end`
*/
VALUE watchman_load_string(char **ptr, char *end) {
int64_t len;
VALUE string;
if (*ptr >= end) {
rb_raise(rb_eArgError, "unexpected end of input");
}
if (*ptr[0] != WATCHMAN_STRING_MARKER) {
rb_raise(rb_eArgError, "not a number");
}
*ptr += sizeof(int8_t);
if (*ptr >= end) {
rb_raise(rb_eArgError, "invalid string header");
}
len = watchman_load_int(ptr, end);
if (len == 0) { // special case for zero-length strings
return rb_str_new2("");
} else if (*ptr + len > end) {
rb_raise(rb_eArgError, "insufficient string storage");
}
string = rb_str_new(*ptr, len);
*ptr += len;
return string;
}
/**
* Reads and returns a double encoded in the Watchman binary protocol format,
* starting at `ptr` and finishing at or before `end`
*/
double watchman_load_double(char **ptr, char *end) {
double val;
*ptr += sizeof(int8_t); // caller has already verified the marker
if (*ptr + sizeof(double) > end) {
rb_raise(rb_eArgError, "insufficient double storage");
}
val = *(double *)*ptr;
*ptr += sizeof(double);
return val;
}
/**
* Helper method which returns length of the array encoded in the Watchman
* binary protocol format, starting at `ptr` and finishing at or before `end`
*/
int64_t watchman_load_array_header(char **ptr, char *end) {
if (*ptr >= end) {
rb_raise(rb_eArgError, "unexpected end of input");
}
// verify and consume marker
if (*ptr[0] != WATCHMAN_ARRAY_MARKER) {
rb_raise(rb_eArgError, "not an array");
}
*ptr += sizeof(int8_t);
// expect a count
if (*ptr + sizeof(int8_t) * 2 > end) {
rb_raise(rb_eArgError, "incomplete array header");
}
return watchman_load_int(ptr, end);
}
/**
* Reads and returns an array encoded in the Watchman binary protocol format,
* starting at `ptr` and finishing at or before `end`
*/
VALUE watchman_load_array(char **ptr, char *end) {
int64_t count, i;
VALUE array;
count = watchman_load_array_header(ptr, end);
array = rb_ary_new2(count);
for (i = 0; i < count; i++) {
rb_ary_push(array, watchman_load(ptr, end));
}
return array;
}
/**
* Reads and returns a hash encoded in the Watchman binary protocol format,
* starting at `ptr` and finishing at or before `end`
*/
VALUE watchman_load_hash(char **ptr, char *end) {
int64_t count, i;
VALUE hash, key, value;
*ptr += sizeof(int8_t); // caller has already verified the marker
// expect a count
if (*ptr + sizeof(int8_t) * 2 > end) {
rb_raise(rb_eArgError, "incomplete hash header");
}
count = watchman_load_int(ptr, end);
hash = rb_hash_new();
for (i = 0; i < count; i++) {
key = watchman_load_string(ptr, end);
value = watchman_load(ptr, end);
rb_hash_aset(hash, key, value);
}
return hash;
}
/**
* Reads and returns a templated array encoded in the Watchman binary protocol
* format, starting at `ptr` and finishing at or before `end`
*
* Templated arrays are arrays of hashes which have repetitive key information
* pulled out into a separate "headers" prefix.
*
* @see https://github.com/facebook/watchman/blob/master/BSER.markdown
*/
VALUE watchman_load_template(char **ptr, char *end) {
int64_t header_items_count, i, row_count;
VALUE array, hash, header, key, value;
*ptr += sizeof(int8_t); // caller has already verified the marker
// process template header array
header_items_count = watchman_load_array_header(ptr, end);
header = rb_ary_new2(header_items_count);
for (i = 0; i < header_items_count; i++) {
rb_ary_push(header, watchman_load_string(ptr, end));
}
// process row items
row_count = watchman_load_int(ptr, end);
array = rb_ary_new2(header_items_count);
while (row_count--) {
hash = rb_hash_new();
for (i = 0; i < header_items_count; i++) {
if (*ptr >= end) {
rb_raise(rb_eArgError, "unexpected end of input");
}
if (*ptr[0] == WATCHMAN_SKIP_MARKER) {
*ptr += sizeof(uint8_t);
} else {
value = watchman_load(ptr, end);
key = rb_ary_entry(header, i);
rb_hash_aset(hash, key, value);
}
}
rb_ary_push(array, hash);
}
return array;
}
/**
* Reads and returns an object encoded in the Watchman binary protocol format,
* starting at `ptr` and finishing at or before `end`
*/
VALUE watchman_load(char **ptr, char *end) {
if (*ptr >= end) {
rb_raise(rb_eArgError, "unexpected end of input");
}
switch (*ptr[0]) {
case WATCHMAN_ARRAY_MARKER:
return watchman_load_array(ptr, end);
case WATCHMAN_HASH_MARKER:
return watchman_load_hash(ptr, end);
case WATCHMAN_STRING_MARKER:
return watchman_load_string(ptr, end);
case WATCHMAN_INT8_MARKER:
case WATCHMAN_INT16_MARKER:
case WATCHMAN_INT32_MARKER:
case WATCHMAN_INT64_MARKER:
return LL2NUM(watchman_load_int(ptr, end));
case WATCHMAN_FLOAT_MARKER:
return rb_float_new(watchman_load_double(ptr, end));
case WATCHMAN_TRUE:
*ptr += 1;
return Qtrue;
case WATCHMAN_FALSE:
*ptr += 1;
return Qfalse;
case WATCHMAN_NIL:
*ptr += 1;
return Qnil;
case WATCHMAN_TEMPLATE_MARKER:
return watchman_load_template(ptr, end);
default:
rb_raise(rb_eTypeError, "unsupported type");
}
return Qnil; // keep the compiler happy
}
/**
* CommandT::Watchman::Utils.load(serialized)
*
* Converts the binary object, `serialized`, from the Watchman binary protocol
* format into a normal Ruby object.
*/
VALUE CommandTWatchmanUtils_load(VALUE self, VALUE serialized) {
char *ptr, *end;
long len;
uint64_t payload_size;
VALUE loaded;
serialized = StringValue(serialized);
len = RSTRING_LEN(serialized);
ptr = RSTRING_PTR(serialized);
end = ptr + len;
// expect at least the binary marker and a int8_t length counter
if ((size_t)len < sizeof(WATCHMAN_BINARY_MARKER) - 1 + sizeof(int8_t) * 2) {
rb_raise(rb_eArgError, "undersized header");
}
if (memcmp(ptr, WATCHMAN_BINARY_MARKER, sizeof(WATCHMAN_BINARY_MARKER) - 1)) {
rb_raise(rb_eArgError, "missing binary marker");
}
// get size marker
ptr += sizeof(WATCHMAN_BINARY_MARKER) - 1;
payload_size = watchman_load_int(&ptr, end);
if (!payload_size) {
rb_raise(rb_eArgError, "empty payload");
}
// sanity check length
if (ptr + payload_size != end) {
rb_raise(
rb_eArgError,
"payload size mismatch (%lu)",
(unsigned long)(end - (ptr + payload_size))
);
}
loaded = watchman_load(&ptr, end);
// one more sanity check
if (ptr != end) {
rb_raise(
rb_eArgError,
"payload termination mismatch (%lu)",
(unsigned long)(end - ptr)
);
}
return loaded;
}
/**
* CommandT::Watchman::Utils.dump(serializable)
*
* Converts the Ruby object, `serializable`, into a binary string in the
* Watchman binary protocol format.
*
* Examples of serializable objects include arrays, hashes, strings, numbers
* (integers, floats), booleans, and nil.
*/
VALUE CommandTWatchmanUtils_dump(VALUE self, VALUE serializable) {
uint64_t *len;
VALUE serialized;
watchman_t *w = watchman_init();
watchman_dump(w, serializable);
// update header with final length information
len = (uint64_t *)(w->data + sizeof(WATCHMAN_HEADER) - sizeof(uint64_t) - 1);
*len = w->len - sizeof(WATCHMAN_HEADER) + 1;
// prepare final return value
serialized = rb_str_buf_new(w->len);
rb_str_buf_cat(serialized, (const char*)w->data, w->len);
watchman_free(w);
return serialized;
}
/**
* Helper method for raising a SystemCallError wrapping a lower-level error code
* coming from the `errno` global variable.
*/
void watchman_raise_system_call_error(int number) {
VALUE error = INT2FIX(number);
rb_exc_raise(rb_class_new_instance(1, &error, rb_eSystemCallError));
}
// How far we have to look to figure out the size of the PDU header
#define WATCHMAN_SNIFF_BUFFER_SIZE sizeof(WATCHMAN_BINARY_MARKER) - 1 + sizeof(int8_t)
// How far we have to peek, at most, to figure out the size of the PDU itself
#define WATCHMAN_PEEK_BUFFER_SIZE \
sizeof(WATCHMAN_BINARY_MARKER) - 1 + \
sizeof(WATCHMAN_INT64_MARKER) + \
sizeof(int64_t)
/**
* CommandT::Watchman::Utils.query(query, socket)
*
* Converts `query`, a Watchman query comprising Ruby objects, into the Watchman
* binary protocol format, transmits it over socket, and unserializes and
* returns the result.
*/
VALUE CommandTWatchmanUtils_query(VALUE self, VALUE query, VALUE socket) {
char *payload;
int fileno, flags;
int8_t peek[WATCHMAN_PEEK_BUFFER_SIZE];
int8_t sizes[] = { 0, 0, 0, 1, 2, 4, 8 };
int8_t sizes_idx;
int8_t *pdu_size_ptr;
int64_t payload_size;
long query_len;
ssize_t peek_size, sent, received;
void *buffer;
VALUE loaded, serialized;
fileno = NUM2INT(rb_funcall(socket, rb_intern("fileno"), 0));
// do blocking I/O to simplify the following logic
flags = fcntl(fileno, F_GETFL);
if (fcntl(fileno, F_SETFL, flags & ~O_NONBLOCK) == -1) {
rb_raise(rb_eRuntimeError, "unable to clear O_NONBLOCK flag");
}
// send the message
serialized = CommandTWatchmanUtils_dump(self, query);
query_len = RSTRING_LEN(serialized);
sent = send(fileno, RSTRING_PTR(serialized), query_len, 0);
if (sent == -1) {
watchman_raise_system_call_error(errno);
} else if (sent != query_len) {
rb_raise(rb_eRuntimeError, "expected to send %ld bytes but sent %ld",
query_len, sent);
}
// sniff to see how large the header is
received = recv(fileno, peek, WATCHMAN_SNIFF_BUFFER_SIZE, MSG_PEEK | MSG_WAITALL);
if (received == -1) {
watchman_raise_system_call_error(errno);
} else if (received != WATCHMAN_SNIFF_BUFFER_SIZE) {
rb_raise(rb_eRuntimeError, "failed to sniff PDU header");
}
// peek at size of PDU
sizes_idx = peek[sizeof(WATCHMAN_BINARY_MARKER) - 1];
if (sizes_idx < WATCHMAN_INT8_MARKER || sizes_idx > WATCHMAN_INT64_MARKER) {
rb_raise(rb_eRuntimeError, "bad PDU size marker");
}
peek_size = sizeof(WATCHMAN_BINARY_MARKER) - 1 + sizeof(int8_t) +
sizes[sizes_idx];
received = recv(fileno, peek, peek_size, MSG_PEEK);
if (received == -1) {
watchman_raise_system_call_error(errno);
} else if (received != peek_size) {
rb_raise(rb_eRuntimeError, "failed to peek at PDU header");
}
pdu_size_ptr = peek + sizeof(WATCHMAN_BINARY_MARKER) - sizeof(int8_t);
payload_size =
peek_size +
watchman_load_int((char **)&pdu_size_ptr, (char *)peek + peek_size);
// actually read the PDU
buffer = xmalloc(payload_size);
if (!buffer) {
rb_raise(
rb_eNoMemError,
"failed to allocate %lld bytes",
(long long int)payload_size
);
}
received = recv(fileno, buffer, payload_size, MSG_WAITALL);
if (received == -1) {
watchman_raise_system_call_error(errno);
} else if (received != payload_size) {
rb_raise(rb_eRuntimeError, "failed to load PDU");
}
payload = (char *)buffer + peek_size;
loaded = watchman_load(&payload, payload + payload_size);
free(buffer);
return loaded;
}
#else /* don't build Watchman utils; supply stubs only*/
VALUE CommandTWatchmanUtils_load(VALUE self, VALUE serialized) {
rb_raise(rb_eRuntimeError, "unsupported operation");
}
VALUE CommandTWatchmanUtils_dump(VALUE self, VALUE serializable) {
rb_raise(rb_eRuntimeError, "unsupported operation");
}
VALUE CommandTWatchmanUtils_query(VALUE self, VALUE query, VALUE socket) {
rb_raise(rb_eRuntimeError, "unsupported operation");
}
#endif

View file

@ -0,0 +1,32 @@
// Copyright 2014 Greg Hurrell. All rights reserved.
// Licensed under the terms of the BSD 2-clause license.
#include <ruby.h>
/**
* @module CommandT::Watchman::Utils
*
* Methods for working with the Watchman binary protocol
*
* @see https://github.com/facebook/watchman/blob/master/BSER.markdown
*/
/**
* Convert an object serialized using the Watchman binary protocol[0] into an
* unpacked Ruby object
*/
extern VALUE CommandTWatchmanUtils_load(VALUE self, VALUE serialized);
/**
* Serialize a Ruby object into the Watchman binary protocol format
*/
extern VALUE CommandTWatchmanUtils_dump(VALUE self, VALUE serializable);
/**
* Issue `query` to the Watchman instance listening on `socket` (a `UNIXSocket`
* instance) and return the result
*
* The query is serialized following the Watchman binary protocol and the
* result is converted to native Ruby objects before returning to the caller.
*/
extern VALUE CommandTWatchmanUtils_query(VALUE self, VALUE query, VALUE socket);

View file

@ -0,0 +1,88 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
require 'spec_helper'
describe CommandT::Controller do
describe 'accept selection' do
let(:controller) { CommandT::Controller.new }
before do
check_ruby_1_9_2
stub_finder
stub_match_window 'path/to/selection'
stub_prompt
stub_vim '/working/directory'
end
def set_string(name, value)
stub(::VIM).evaluate(%{exists("#{name}")}).returns(1)
stub(::VIM).evaluate(name).returns(value)
end
it 'opens relative paths inside the working directory' do
stub(::VIM).evaluate('a:arg').returns('')
set_string('g:CommandTTraverseSCM', 'pwd')
controller.show_file_finder
mock(::VIM).command('silent e path/to/selection')
controller.accept_selection
end
it 'opens absolute paths outside the working directory' do
stub(::VIM).evaluate('a:arg').returns('../outside')
controller.show_file_finder
mock(::VIM).command('silent e /working/outside/path/to/selection')
controller.accept_selection
end
it 'does not get confused by common directory prefixes' do
stub(::VIM).evaluate('a:arg').returns('../directory-oops')
controller.show_file_finder
mock(::VIM).command('silent e /working/directory-oops/path/to/selection')
controller.accept_selection
end
end
def check_ruby_1_9_2
if RUBY_VERSION =~ /\A1\.9\.2/
pending 'broken in Ruby 1.9.2 (see https://gist.github.com/455547)'
end
end
def stub_finder(sorted_matches=[])
finder = CommandT::Finder::FileFinder.new
stub(finder).path = anything
stub(finder).sorted_matches_for(anything, anything).returns(sorted_matches)
stub(CommandT::Finder::FileFinder).new.returns(finder)
end
def stub_match_window(selection)
match_window = Object.new
stub(match_window).matches = anything
stub(match_window).leave
stub(match_window).selection.returns(selection)
stub(CommandT::MatchWindow).new.returns(match_window)
end
def stub_prompt(abbrev='')
prompt = Object.new
stub(prompt).focus
stub(prompt).clear!
stub(prompt).abbrev.returns(abbrev)
stub(CommandT::Prompt).new.returns(prompt)
end
def stub_vim(working_directory)
stub($curbuf).number.returns('0')
stub(::VIM).command(/noremap/)
stub(::VIM).command('silent b 0')
stub(::VIM).command(/augroup/)
stub(::VIM).command('au!')
stub(::VIM).command(/autocmd/)
stub(::VIM).evaluate(/exists\(.+\)/).returns('0')
stub(::VIM).evaluate('getcwd()').returns(working_directory)
stub(::VIM).evaluate('&buflisted').returns('1')
stub(::VIM).evaluate('&lines').returns('80')
stub(::VIM).evaluate('&term').returns('vt100')
end
end

View file

@ -0,0 +1,55 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
require 'spec_helper'
describe CommandT::Finder::BufferFinder do
before do
@paths = %w(.git/config .vim/notes .vimrc baz foo/beta)
any_instance_of(CommandT::Scanner::BufferScanner, :paths => @paths)
@finder = CommandT::Finder::BufferFinder.new
end
describe 'sorted_matches_for method' do
it 'returns an empty array when no matches' do
@finder.sorted_matches_for('kung foo fighting').should == []
end
it 'returns all files when query string is empty' do
@finder.sorted_matches_for('').should == @paths
end
it 'returns files in alphabetical order when query string is empty' do
results = @finder.sorted_matches_for('')
results.should == results.sort
end
it 'returns matching files in score order' do
@finder.sorted_matches_for('ba').should == %w(baz foo/beta)
@finder.sorted_matches_for('a').should == %w(baz foo/beta)
end
it 'returns matching dot files even when search term does not include a dot' do
@finder.sorted_matches_for('i').should include('.vimrc')
end
it 'returns matching files inside dot directories even when search term does not include a dot' do
@finder.sorted_matches_for('i').should include('.vim/notes')
end
it "does not use the Vim expand() function to consult the 'wildignore' setting" do
do_not_allow(::VIM).evaluate
@finder.sorted_matches_for('i')
end
it 'obeys the :limit option for empty search strings' do
@finder.sorted_matches_for('', :limit => 1).
should == %w(.git/config)
end
it 'obeys the :limit option for non-empty search strings' do
@finder.sorted_matches_for('i', :limit => 2).
should == %w(.vimrc .vim/notes)
end
end
end

View file

@ -0,0 +1,56 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
require 'spec_helper'
describe CommandT::Finder::FileFinder do
before :all do
@finder = CommandT::Finder::FileFinder.new File.join(File.dirname(__FILE__), '..',
'..', '..', 'fixtures')
@all_fixtures = %w(
bar/abc
bar/xyz
baz
bing
foo/alpha/t1
foo/alpha/t2
foo/beta
)
end
before do
stub(::VIM).evaluate(/expand/) { 0 }
end
describe 'sorted_matches_for method' do
it 'returns an empty array when no matches' do
@finder.sorted_matches_for('kung foo fighting').should == []
end
it 'returns all files when query string is empty' do
@finder.sorted_matches_for('').should == @all_fixtures
end
it 'returns files in alphabetical order when query string is empty' do
results = @finder.sorted_matches_for('')
results.should == results.sort
end
it 'returns matching files in score order' do
@finder.sorted_matches_for('ba').
should == %w(baz bar/abc bar/xyz foo/beta)
@finder.sorted_matches_for('a').
should == %w(baz bar/abc bar/xyz foo/alpha/t1 foo/alpha/t2 foo/beta)
end
it 'obeys the :limit option for empty search strings' do
@finder.sorted_matches_for('', :limit => 2).
should == %w(bar/abc bar/xyz)
end
it 'obeys the :limit option for non-empty search strings' do
@finder.sorted_matches_for('a', :limit => 3).
should == %w(baz bar/abc bar/xyz)
end
end
end

View file

@ -0,0 +1,218 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
require 'spec_helper'
require 'ostruct'
require 'command-t/ext' # CommandT::Matcher
describe CommandT::Matcher do
def matcher(*paths)
scanner = OpenStruct.new(:paths => paths)
CommandT::Matcher.new(scanner)
end
describe 'initialization' do
it 'raises an ArgumentError if passed nil' do
expect { CommandT::Matcher.new(nil) }.to raise_error(ArgumentError)
end
end
describe '#sorted_matches_for' do
def ordered_matches(paths, query)
matcher(*paths).sorted_matches_for(query)
end
it 'raises an ArgumentError if passed nil' do
expect { matcher.sorted_matches_for(nil) }.to raise_error(ArgumentError)
end
it 'returns empty array when source array empty' do
matcher.sorted_matches_for('foo').should == []
matcher.sorted_matches_for('').should == []
end
it 'returns empty array when no matches' do
matcher = matcher(*%w[foo/bar foo/baz bing])
matcher.sorted_matches_for('xyz').should == []
end
it 'returns matching paths' do
matcher = matcher(*%w[foo/bar foo/baz bing])
matches = matcher.sorted_matches_for('z')
matches.map { |m| m.to_s }.should == ['foo/baz']
matches = matcher.sorted_matches_for('bg')
matches.map { |m| m.to_s }.should == ['bing']
end
it 'performs case-insensitive matching' do
matches = matcher('Foo').sorted_matches_for('f')
matches.map { |m| m.to_s }.should == ['Foo']
end
it 'considers the empty string to match everything' do
matches = matcher('foo').sorted_matches_for('')
matches.map { |m| m.to_s }.should == ['foo']
end
it 'does not consider mere substrings of the query string to be a match' do
matcher('foo').sorted_matches_for('foo...').should == []
end
it 'prioritizes shorter paths over longer ones' do
ordered_matches(%w[
articles_controller_spec.rb
article.rb
], 'art').should == %w[
article.rb
articles_controller_spec.rb
]
end
it 'prioritizes matches after "/"' do
ordered_matches(%w[fooobar foo/bar], 'b').should == %w[foo/bar fooobar]
# note that / beats _
ordered_matches(%w[foo_bar foo/bar], 'b').should == %w[foo/bar foo_bar]
# / also beats -
ordered_matches(%w[foo-bar foo/bar], 'b').should == %w[foo/bar foo-bar]
# and numbers
ordered_matches(%w[foo9bar foo/bar], 'b').should == %w[foo/bar foo9bar]
# and periods
ordered_matches(%w[foo.bar foo/bar], 'b').should == %w[foo/bar foo.bar]
# and spaces
ordered_matches(['foo bar', 'foo/bar'], 'b').should == ['foo/bar', 'foo bar']
end
it 'prioritizes matches after "-"' do
ordered_matches(%w[fooobar foo-bar], 'b').should == %w[foo-bar fooobar]
# - also beats .
ordered_matches(%w[foo.bar foo-bar], 'b').should == %w[foo-bar foo.bar]
end
it 'prioritizes matches after "_"' do
ordered_matches(%w[fooobar foo_bar], 'b').should == %w[foo_bar fooobar]
# _ also beats .
ordered_matches(%w[foo.bar foo_bar], 'b').should == %w[foo_bar foo.bar]
end
it 'prioritizes matches after " "' do
ordered_matches(['fooobar', 'foo bar'], 'b').should == ['foo bar', 'fooobar']
# " " also beats .
ordered_matches(['foo.bar', 'foo bar'], 'b').should == ['foo bar', 'foo.bar']
end
it 'prioritizes matches after numbers' do
ordered_matches(%w[fooobar foo9bar], 'b').should == %w[foo9bar fooobar]
# numbers also beat .
ordered_matches(%w[foo.bar foo9bar], 'b').should == %w[foo9bar foo.bar]
end
it 'prioritizes matches after periods' do
ordered_matches(%w[fooobar foo.bar], 'b').should == %w[foo.bar fooobar]
end
it 'prioritizes matching capitals following lowercase' do
ordered_matches(%w[foobar fooBar], 'b').should == %w[fooBar foobar]
end
it 'prioritizes matches earlier in the string' do
ordered_matches(%w[******b* **b*****], 'b').should == %w[**b***** ******b*]
end
it 'prioritizes matches closer to previous matches' do
ordered_matches(%w[**b***c* **bc****], 'bc').should == %w[**bc**** **b***c*]
end
it 'scores alternative matches of same path differently' do
# ie:
# app/controllers/articles_controller.rb
ordered_matches(%w[
a**/****r******/**t*c***_*on*******.**
***/***********/art*****_con*******.**
], 'artcon').should == %w[
***/***********/art*****_con*******.**
a**/****r******/**t*c***_*on*******.**
]
end
it 'provides intuitive results for "artcon" and "articles_controller"' do
ordered_matches(%w[
app/controllers/heartbeat_controller.rb
app/controllers/articles_controller.rb
], 'artcon').should == %w[
app/controllers/articles_controller.rb
app/controllers/heartbeat_controller.rb
]
end
it 'provides intuitive results for "aca" and "a/c/articles_controller"' do
ordered_matches(%w[
app/controllers/heartbeat_controller.rb
app/controllers/articles_controller.rb
], 'aca').should == %w[
app/controllers/articles_controller.rb
app/controllers/heartbeat_controller.rb
]
end
it 'provides intuitive results for "d" and "doc/command-t.txt"' do
ordered_matches(%w[
TODO
doc/command-t.txt
], 'd').should == %w[
doc/command-t.txt
TODO
]
end
it 'provides intuitive results for "do" and "doc/command-t.txt"' do
ordered_matches(%w[
TODO
doc/command-t.txt
], 'do').should == %w[
doc/command-t.txt
TODO
]
end
it "doesn't incorrectly accept repeats of the last-matched character" do
# https://github.com/wincent/Command-T/issues/82
matcher = matcher(*%w[ash/system/user/config.h])
matcher.sorted_matches_for('usercc').should == []
# simpler test case
matcher = matcher(*%w[foobar])
matcher.sorted_matches_for('fooooo').should == []
# minimal repro
matcher = matcher(*%w[ab])
matcher.sorted_matches_for('aa').should == []
end
it 'ignores dotfiles by default' do
matcher = matcher(*%w[.foo .bar])
matcher.sorted_matches_for('foo').should == []
end
it 'shows dotfiles if the query starts with a dot' do
matcher = matcher(*%w[.foo .bar])
matcher.sorted_matches_for('.fo').should == %w[.foo]
end
it "doesn't show dotfiles if the query contains a non-leading dot" do
matcher = matcher(*%w[.foo.txt .bar.txt])
matcher.sorted_matches_for('f.t').should == []
# counter-example
matcher.sorted_matches_for('.f.t').should == %w[.foo.txt]
end
end
end

View file

@ -0,0 +1,29 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
require 'spec_helper'
require 'ostruct'
describe CommandT::Scanner::BufferScanner do
def buffer(name)
b = OpenStruct.new
b.name = name
b
end
before do
@paths = %w(bar/abc bar/xyz baz bing foo/alpha/t1 foo/alpha/t2 foo/beta)
@scanner = CommandT::Scanner::BufferScanner.new
stub(@scanner).relative_path_under_working_directory(is_a(String)) { |arg| arg }
stub(::VIM::Buffer).count { 7 }
(0..6).each do |n|
stub(::VIM::Buffer)[n].returns(buffer @paths[n])
end
end
describe 'paths method' do
it 'returns a list of regular files' do
@scanner.paths.should =~ @paths
end
end
end

View file

@ -0,0 +1,65 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
require 'spec_helper'
describe CommandT::Scanner::FileScanner::RubyFileScanner do
before do
@dir = File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'fixtures')
@all_fixtures = %w(
bar/abc bar/xyz baz bing foo/alpha/t1 foo/alpha/t2 foo/beta
)
@scanner = CommandT::Scanner::FileScanner::RubyFileScanner.new(@dir)
stub(::VIM).evaluate(/exists/) { 1 }
stub(::VIM).evaluate(/expand\(.+\)/) { '0' }
stub(::VIM).evaluate(/wildignore/) { '' }
end
describe 'paths method' do
it 'returns a list of regular files' do
@scanner.paths.should =~ @all_fixtures
end
end
describe 'path= method' do
it 'allows repeated applications of scanner at different paths' do
@scanner.paths.should =~ @all_fixtures
# drill down 1 level
@scanner.path = File.join(@dir, 'foo')
@scanner.paths.should =~ %w(alpha/t1 alpha/t2 beta)
# and another
@scanner.path = File.join(@dir, 'foo', 'alpha')
@scanner.paths.should =~ %w(t1 t2)
end
end
describe "'wildignore' exclusion" do
context "when there is a 'wildignore' setting in effect" do
it "calls on VIM's expand() function for pattern filtering" do
stub(::VIM).command(/set wildignore/)
scanner =
CommandT::Scanner::FileScanner::RubyFileScanner.new @dir, :wild_ignore => '*.o'
mock(::VIM).evaluate(/expand\(.+\)/).times(10)
scanner.paths
end
end
context "when there is no 'wildignore' setting in effect" do
it "does not call VIM's expand() function" do
scanner = CommandT::Scanner::FileScanner::RubyFileScanner.new @dir
mock(::VIM).evaluate(/expand\(.+\)/).never
scanner.paths
end
end
end
describe ':max_depth option' do
it 'does not descend below "max_depth" levels' do
@scanner = CommandT::Scanner::FileScanner::RubyFileScanner.new @dir, :max_depth => 1
@scanner.paths.should =~ %w(bar/abc bar/xyz baz bing foo/beta)
end
end
end

View file

@ -0,0 +1,18 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
require 'spec_helper'
describe CommandT::Scanner::FileScanner do
before do
dir = File.join(File.dirname(__FILE__), '..', '..', '..', 'fixtures')
@scanner = CommandT::Scanner::FileScanner.new(dir)
end
describe 'flush method' do
it 'forces a rescan on next call to paths method' do
expect { @scanner.flush }.
to change { @scanner.instance_variable_get('@paths').object_id }
end
end
end

View file

@ -0,0 +1,14 @@
# Copyright 2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
require 'spec_helper'
describe CommandT::VIM do
describe '.escape_for_single_quotes' do
it 'turns doubles all single quotes' do
input = %{it's ''something''}
expected = %{it''s ''''something''''}
expect(CommandT::VIM.escape_for_single_quotes(input)).to eq(expected)
end
end
end

View file

@ -0,0 +1,416 @@
# Copyright 2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
require 'spec_helper'
require 'command-t/ext' # for CommandT::Watchman::Utils
describe CommandT::Watchman::Utils do
def binary(str)
if str.respond_to?(:force_encoding) # Ruby >= 1.9
str.force_encoding('ASCII-8BIT')
else
str
end
end
def little_endian?
byte = [0xff00].pack('s')[0]
if byte.is_a?(Fixnum) # ie. Ruby 1.8
byte.zero?
elsif byte.is_a?(String) # ie. Ruby >= 1.9
byte == "\x00"
else
raise 'unable to determine endianness'
end
end
def roundtrip(value)
described_class.load(described_class.dump(value))
end
it 'roundtrips arrays' do
value = [1, 2, ['three', false]]
expect(roundtrip(value)).to eq(value)
end
it 'roundtrips hashes' do
value = {
'foo' => 1,
'bar' => {
'baz' => 'bing',
}
}
expect(roundtrip(value)).to eq(value)
end
it 'roundtrips strings' do
expect(roundtrip('')).to eq('')
expect(roundtrip('/foo/bar/baz')).to eq('/foo/bar/baz')
end
it 'roundtrips uint8_t integers' do
expect(roundtrip(0)).to eq(0)
expect(roundtrip(1)).to eq(1)
expect(roundtrip(0xff)).to eq(0xff)
end
it 'roundtrips uint16_t integers' do
expect(roundtrip(0x1234)).to eq(0x1234)
end
it 'roundtrips uint32_t integers' do
expect(roundtrip(0x12345678)).to eq(0x12345678)
end
it 'roundtrips uint64_t integers' do
expect(roundtrip(0x12345678abcdef00)).to eq(0x12345678abcdef00)
end
it 'roundtrips floats' do
expect(roundtrip(1234.5678)).to eq(1234.5678)
end
it 'roundtrips `true` booleans' do
expect(roundtrip(true)).to be_true
end
it 'roundtrips `false` booleans' do
expect(roundtrip(false)).to be_false
end
it 'roundtrips nil' do
expect(roundtrip(nil)).to be_nil
end
describe '.load' do
it 'rejects undersized input' do
expect { described_class.load('') }.
to raise_error(ArgumentError, /undersized/i)
end
it 'rejects input without a binary marker' do
expect { described_class.load('gibberish') }.
to raise_error(ArgumentError, /missing/i)
end
it 'rejects a missing payload header' do
# binary protocol marker, but nothing else
input = binary("\x00\x01")
expect { described_class.load(input) }.
to raise_error(ArgumentError, /undersized/i)
end
it 'rejects empty payloads' do
# uint8_t size marker of zero
input = binary("\x00\x01\x03\x00")
expect { described_class.load(input) }.
to raise_error(ArgumentError, /empty/i)
end
it 'rejects unrecognized payload markers' do
# 0x10 is not a valid integer marker
input = binary("\x00\x01\x10\x00")
expect { described_class.load(input) }.
to raise_error(ArgumentError, /bad integer/i)
end
it 'rejects undersized payload markers' do
# int16_t marker, but only storage for int8_t
input = binary("\x00\x01\x04\x00")
expect { described_class.load(input) }.
to raise_error(ArgumentError, /overrun\b.+\bint16_t/i)
end
it 'loads array values' do
input = binary(
"\x00\x01\x03\x16\x00\x03\x05\x03\x01\x02\x03" \
"\x06foobar\x08\x09\x00\x03\x02\x03\x0a\x0a"
)
expect(described_class.load(input)).
to eq([1, 'foobar', true, false, [10, nil]])
end
it 'handles empty arrays' do
input = binary("\x00\x01\x03\x03\x00\x03\x00")
expect(described_class.load(input)).to eq([])
end
it 'rejects arrays with incomplete headers' do
input = binary("\x00\x01\x03\x02\x00\x03")
expect { described_class.load(input) }.
to raise_error(ArgumentError, /incomplete array header/i)
end
it 'rejects arrays with incomplete entries' do
input = binary("\x00\x01\x03\x05\x00\x03\x10\x0a\x0a")
expect { described_class.load(input) }.
to raise_error(ArgumentError, /unexpected end/i)
end
it 'loads hash values' do
input = binary(
"\x00\x01\x03\x1a\x01\x03\x02\x02\x03\x03foo\x0a" \
"\x02\x03\x03bar\x01\x03\x01\x02\x03\x03baz\x08"
)
expected = {
'foo' => nil,
'bar' => {
'baz' => true,
}
}
expect(described_class.load(input)).to eq(expected)
end
it 'handles empty hashes' do
input = binary("\x00\x01\x03\x03\x01\x03\x00")
expect(described_class.load(input)).to eq({})
end
it 'rejects hashes with incomplete headers' do
input = binary("\x00\x01\x03\x02\x01\x03")
expect { described_class.load(input) }.
to raise_error(ArgumentError, /incomplete hash header/i)
end
it 'rejects hashes with invalid keys' do
# keys must be strings; this one uses uses a number instead
input = binary("\x00\x01\x03\x05\x01\x03\x01\x03\x00")
expect { described_class.load(input) }.
to raise_error(ArgumentError, /not a number/i)
end
it 'rejects hashes with missing keys' do
input = binary("\x00\x01\x03\x03\x01\x03\x01")
expect { described_class.load(input) }.
to raise_error(ArgumentError, /unexpected end/i)
end
it 'rejects hashes with missing values' do
input = binary("\x00\x01\x03\x09\x01\x03\x01\x02\x03\x03foo")
expect { described_class.load(input) }.
to raise_error(ArgumentError, /unexpected end/i)
end
it 'loads string values' do
input = binary("\x00\x01\x03\x06\x02\x03\x03foo")
expect(described_class.load(input)).to eq('foo')
end
it 'handles empty strings' do
input = binary("\x00\x01\x03\x03\x02\x03\x00")
expect(described_class.load(input)).to eq('')
end
if String.new.respond_to?(:encoding) # ie. Ruby >= 1.9
it 'loads string values as ASCII-8BIT encoded strings' do
input = binary("\x00\x01\x03\x06\x02\x03\x03foo")
expect(described_class.load(input).encoding.to_s).to eq('ASCII-8BIT')
end
end
it 'rejects string values with incomplete headers' do
input = binary("\x00\x01\x03\x01\x02")
expect { described_class.load(input) }.
to raise_error(ArgumentError, /invalid string header/i)
end
it 'rejects string values with invalid headers' do
# expect a number indicating the string length, get a boolean instead
input = binary("\x00\x01\x03\x05\x02\x08foo")
expect { described_class.load(input) }.
to raise_error(ArgumentError, /bad integer/i)
end
it 'rejects string values with insufficient storage' do
# expect 3 bytes, get 2 instead
input = binary("\x00\x01\x03\x05\x02\x03\x03fo")
expect { described_class.load(input) }.
to raise_error(ArgumentError, /insufficient string storage/i)
end
it 'loads uint8_t values' do
input = binary("\x00\x01\x03\x02\x03\x12")
expect(described_class.load(input)).to eq(0x12)
end
it 'loads uint16_t values' do
if little_endian?
input = binary("\x00\x01\x03\x03\x04\x34\x12")
else
input = binary("\x00\x01\x03\x03\x04\x12\x34")
end
expect(described_class.load(input)).to eq(0x1234)
end
it 'loads uint32_t values' do
if little_endian?
input = binary("\x00\x01\x03\x05\x05\x78\x56\x34\x12")
else
input = binary("\x00\x01\x03\x05\x05\x12\x34\x56\x78")
end
expect(described_class.load(input)).to eq(0x12345678)
end
it 'loads int uint64_t values' do
if little_endian?
input = binary("\x00\x01\x03\x09\x06\xef\xcd\xab\x90\x78\x56\x34\x12")
else
input = binary("\x00\x01\x03\x09\x06\x12\x34\x56\x78\x90\xab\xcd\xef")
end
expect(described_class.load(input)).to eq(0x1234567890abcdef)
end
it 'rejects int markers with missing values' do
# expect an integer, but hit the end of the buffer
input = binary("\x00\x01\x03\x01\x05")
expect { described_class.load(input) }.
to raise_error(ArgumentError, /insufficient int storage/i)
end
it 'rejects double markers with insufficient storage' do
# double with 7 bytes of storage instead of the required 8 bytes
input = binary("\x00\x01\x03\x08\x07\x00\x00\x00\x00\x00\x00\x00")
expect { described_class.load(input) }.
to raise_error(ArgumentError, /insufficient double storage/i)
end
it 'loads boolean `true` values' do
input = binary("\x00\x01\x03\x01\x08")
expect(described_class.load(input)).to be_true
end
it 'loads boolean `false` values' do
input = binary("\x00\x01\x03\x01\x09")
expect(described_class.load(input)).to be_false
end
it 'loads nil' do
input = binary("\x00\x01\x03\x01\x0a")
expect(described_class.load(input)).to be_nil
end
it 'loads templates' do
# this example includes a "skip" marker
input = binary(
"\x00\x01\x03\x28\x0b\x00\x03\x02\x02\x03\x04name" \
"\x02\x03\x03age\x03\x03\x02\x03\x04fred\x03" \
"\x14\x02\x03\x04pete\x03\x1e\x0c\x03\x19"
)
expected = [
{ 'name' => 'fred', 'age' => 20 },
{ 'name' => 'pete', 'age' => 30 },
{ 'age' => 25 },
]
expect(described_class.load(input)).to eq(expected)
end
it 'handles empty templates' do
input = binary(
"\x00\x01\x03\x12\x0b\x00\x03\x02\x02" \
"\x03\x03foo\x02\x03\x03bar\x03\x00"
)
expect(described_class.load(input)).to eq([])
end
it 'rejects templates without a header array' do
input = binary("\x00\x01\x03\x01\x0b")
expect { described_class.load(input) }.
to raise_error(ArgumentError, /unexpected end/i)
end
it 'rejects templates without a row items array' do
input = binary(
"\x00\x01\x03\x10\x0b\x00\x03\x02\x02" \
"\x03\x03foo\x02\x03\x03bar"
)
expect { described_class.load(input) }.
to raise_error(ArgumentError, /insufficient/i)
end
it 'rejects templates with non-string header items' do
input = binary(
"\x00\x01\x03\x0e\x0b\x00\x03\x02\x02" \
"\x03\x03foo\x03\x03\x03\x00"
)
expect { described_class.load(input) }.
to raise_error(ArgumentError, /not a number/)
end
it 'rejects templates with a header item array count mismatch' do
input = binary(
"\x00\x01\x03\x0a\x0b\x00\x03\x02\x02" \
"\x03\x03foo"
)
expect { described_class.load(input) }.
to raise_error(ArgumentError, /unexpected end/)
end
it 'rejects templates with a row item count mismatch' do
input = binary(
"\x00\x01\x03\x25\x0b\x00\x03\x02\x02\x03\x04name" \
"\x02\x03\x03age\x03\x03\x02\x03\x04fred\x03" \
"\x14\x02\x03\x04pete\x03\x1e"
)
expect { described_class.load(input) }.
to raise_error(ArgumentError, /unexpected end/)
end
end
describe '.dump' do
let(:query) do
# this is the typical kind of query that Command-T will actually issue
['query', '/some/path', {
'expression' => ['type', 'f'],
'fields' => ['name'],
}]
end
it 'serializes' do
expect { described_class.dump(query) }.to_not raise_error
end
if String.new.respond_to?(:encoding) # ie. Ruby >= 1.9
it 'serializes to an ASCII-8BIT string' do
expect(described_class.dump(query).encoding.to_s).to eq('ASCII-8BIT')
end
end
it 'generates a correct serialization' do
# in Ruby 1.8, hashes aren't ordered, so two serializations are possible
if little_endian?
expected = [
binary(
"\x00\x01\x06\x49\x00\x00\x00\x00\x00\x00\x00\x00\x03\x03\x02\x03" \
"\x05query\x02\x03\x0a/some/path\x01\x03\x02\x02\x03\x0a" \
"expression\x00\x03\x02\x02\x03\x04type\x02\x03\x01f\x02\x03\x06" \
"fields\x00\x03\x01\x02\x03\x04name"
),
binary(
"\x00\x01\x06\x49\x00\x00\x00\x00\x00\x00\x00\x00\x03\x03\x02\x03" \
"\x05query\x02\x03\x0a/some/path\x01\x03\x02\x02\x03\x06fields" \
"\x00\x03\x01\x02\x03\x04name\x02\x03\x0aexpression\x00\x03\x02" \
"\x02\x03\x04type\x02\x03\x01f"
)
]
else
expected = [
binary(
"\x00\x01\x06\x00\x00\x00\x00\x00\x00\x00\x49\x00\x03\x03\x02\x03" \
"\x05query\x02\x03\x0a/some/path\x01\x03\x02\x02\x03\x0a" \
"expression\x00\x03\x02\x02\x03\x04type\x02\x03\x01f\x02\x03\x06" \
"fields\x00\x03\x01\x02\x03\x04name"
),
binary(
"\x00\x01\x06\x00\x00\x00\x00\x00\x00\x00\x49\x00\x03\x03\x02\x03" \
"\x05query\x02\x03\x0a/some/path\x01\x03\x02\x02\x03\x06fields" \
"\x00\x03\x01\x02\x03\x04name\x02\x03\x0aexpression\x00\x03\x02" \
"\x02\x03\x04type\x02\x03\x01f"
)
]
end
expect(expected).to include(described_class.dump(query))
end
end
end

View file

@ -0,0 +1,29 @@
# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.
if !Object.const_defined?('Bundler')
require 'rubygems'
require 'bundler'
Bundler.setup
end
require 'rspec'
lib = File.expand_path('../ruby', File.dirname(__FILE__))
unless $LOAD_PATH.include? lib
$LOAD_PATH.unshift lib
end
require 'command-t'
RSpec.configure do |config|
config.mock_framework = :rr
end
# Fake top-level VIM implementation, for stubbing.
module VIM
class << self
def evaluate(*args); end
end
class Buffer; end
end

File diff suppressed because it is too large Load diff

View file

@ -1,163 +0,0 @@
"=============================================================================
" Copyright (c) 2010 Takeshi NISHIDA
"
"=============================================================================
" LOAD GUARD {{{1
if !l9#guardScriptLoading(expand('<sfile>:p'), 0, 0, [])
finish
endif
" }}}1
"=============================================================================
" GLOBAL FUNCTIONS {{{1
"
function fuf#bookmarkdir#createHandler(base)
return a:base.concretize(copy(s:handler))
endfunction
"
function fuf#bookmarkdir#getSwitchOrder()
return g:fuf_bookmarkdir_switchOrder
endfunction
"
function fuf#bookmarkdir#getEditableDataNames()
return ['items']
endfunction
"
function fuf#bookmarkdir#renewCache()
endfunction
"
function fuf#bookmarkdir#requiresOnCommandPre()
return 0
endfunction
"
function fuf#bookmarkdir#onInit()
call fuf#defineLaunchCommand('FufBookmarkDir', s:MODE_NAME, '""', [])
command! -bang -narg=? FufBookmarkDirAdd call s:bookmark(<q-args>)
endfunction
" }}}1
"=============================================================================
" LOCAL FUNCTIONS/VARIABLES {{{1
let s:MODE_NAME = expand('<sfile>:t:r')
let s:OPEN_TYPE_DELETE = -1
"
function s:bookmark(word)
let item = {
\ 'time' : localtime(),
\ }
let item.path = l9#inputHl('Question', '[fuf] Directory to bookmark:',
\ fnamemodify(getcwd(), ':p:~'), 'dir')
if item.path !~ '\S'
call fuf#echoWarning('Canceled')
return
endif
let item.word = l9#inputHl('Question', '[fuf] Bookmark as:',
\ fnamemodify(getcwd(), ':p:~'))
if item.word !~ '\S'
call fuf#echoWarning('Canceled')
return
endif
let items = fuf#loadDataFile(s:MODE_NAME, 'items')
call insert(items, item)
call fuf#saveDataFile(s:MODE_NAME, 'items', items)
endfunction
"
function s:findItem(items, word)
for item in a:items
if item.word ==# a:word
return item
endif
endfor
return {}
endfunction
" }}}1
"=============================================================================
" s:handler {{{1
let s:handler = {}
"
function s:handler.getModeName()
return s:MODE_NAME
endfunction
"
function s:handler.getPrompt()
return fuf#formatPrompt(g:fuf_bookmarkdir_prompt, self.partialMatching, '')
endfunction
"
function s:handler.getPreviewHeight()
return 0
endfunction
"
function s:handler.isOpenable(enteredPattern)
return 1
endfunction
"
function s:handler.makePatternSet(patternBase)
return fuf#makePatternSet(a:patternBase, 's:interpretPrimaryPatternForNonPath',
\ self.partialMatching)
endfunction
"
function s:handler.makePreviewLines(word, count)
return []
endfunction
"
function s:handler.getCompleteItems(patternPrimary)
return self.items
endfunction
"
function s:handler.onOpen(word, mode)
if a:mode ==# s:OPEN_TYPE_DELETE
let items = fuf#loadDataFile(s:MODE_NAME, 'items')
call filter(items, 'v:val.word !=# a:word')
call fuf#saveDataFile(s:MODE_NAME, 'items', items)
let self.reservedMode = self.getModeName()
return
else
let item = s:findItem(fuf#loadDataFile(s:MODE_NAME, 'items'), a:word)
if !empty(item)
execute ':cd ' . fnameescape(item.path)
endif
endif
endfunction
"
function s:handler.onModeEnterPre()
endfunction
"
function s:handler.onModeEnterPost()
call fuf#defineKeyMappingInHandler(g:fuf_bookmarkdir_keyDelete,
\ 'onCr(' . s:OPEN_TYPE_DELETE . ')')
let self.items = fuf#loadDataFile(s:MODE_NAME, 'items')
call map(self.items, 'fuf#makeNonPathItem(v:val.word, strftime(g:fuf_timeFormat, v:val.time))')
call fuf#mapToSetSerialIndex(self.items, 1)
call map(self.items, 'fuf#setAbbrWithFormattedWord(v:val, 1)')
endfunction
"
function s:handler.onModeLeavePost(opened)
endfunction
" }}}1
"=============================================================================
" vim: set fdm=marker:

View file

@ -1,199 +0,0 @@
"=============================================================================
" Copyright (c) 2010 Takeshi NISHIDA
"
"=============================================================================
" LOAD GUARD {{{1
if !l9#guardScriptLoading(expand('<sfile>:p'), 0, 0, [])
finish
endif
" }}}1
"=============================================================================
" GLOBAL FUNCTIONS {{{1
"
function fuf#bookmarkfile#createHandler(base)
return a:base.concretize(copy(s:handler))
endfunction
"
function fuf#bookmarkfile#getSwitchOrder()
return g:fuf_bookmarkfile_switchOrder
endfunction
"
function fuf#bookmarkfile#getEditableDataNames()
return ['items']
endfunction
"
function fuf#bookmarkfile#renewCache()
endfunction
"
function fuf#bookmarkfile#requiresOnCommandPre()
return 0
endfunction
"
function fuf#bookmarkfile#onInit()
call fuf#defineLaunchCommand('FufBookmarkFile', s:MODE_NAME, '""', [])
command! -bang -narg=? FufBookmarkFileAdd call s:bookmarkHere(<q-args>)
command! -bang -narg=0 -range FufBookmarkFileAddAsSelectedText call s:bookmarkHere(l9#getSelectedText())
endfunction
" }}}1
"=============================================================================
" LOCAL FUNCTIONS/VARIABLES {{{1
let s:MODE_NAME = expand('<sfile>:t:r')
let s:OPEN_TYPE_DELETE = -1
" opens a:path and jumps to the line matching to a:pattern from a:lnum within
" a:range. if not found, jumps to a:lnum.
function s:jumpToBookmark(path, mode, pattern, lnum)
call fuf#openFile(a:path, a:mode, g:fuf_reuseWindow)
call cursor(s:getMatchingLineNumber(getline(1, '$'), a:pattern, a:lnum), 0)
normal! zvzz
endfunction
"
function s:getMatchingLineNumber(lines, pattern, lnumBegin)
let l = min([a:lnumBegin, len(a:lines)])
for [l0, l1] in map(range(0, g:fuf_bookmarkfile_searchRange),
\ '[l + v:val, l - v:val]')
if l0 <= len(a:lines) && a:lines[l0 - 1] =~# a:pattern
return l0
elseif l1 >= 0 && a:lines[l1 - 1] =~# a:pattern
return l1
endif
endfor
return l
endfunction
"
function s:getLinePattern(lnum)
return '\C\V\^' . escape(getline(a:lnum), '\') . '\$'
endfunction
"
function s:bookmarkHere(word)
if !empty(&buftype) || expand('%') !~ '\S'
call fuf#echoWarning('Can''t bookmark this buffer.')
return
endif
let item = {
\ 'word' : (a:word =~# '\S' ? substitute(a:word, '\n', ' ', 'g')
\ : pathshorten(expand('%:p:~')) . '|' . line('.') . '| ' . getline('.')),
\ 'path' : expand('%:p'),
\ 'lnum' : line('.'),
\ 'pattern' : s:getLinePattern(line('.')),
\ 'time' : localtime(),
\ }
let item.word = l9#inputHl('Question', '[fuf] Bookmark as:', item.word)
if item.word !~ '\S'
call fuf#echoWarning('Canceled')
return
endif
let items = fuf#loadDataFile(s:MODE_NAME, 'items')
call insert(items, item)
call fuf#saveDataFile(s:MODE_NAME, 'items', items)
endfunction
"
function s:findItem(items, word)
for item in a:items
if item.word ==# a:word
return item
endif
endfor
return {}
endfunction
" }}}1
"=============================================================================
" s:handler {{{1
let s:handler = {}
"
function s:handler.getModeName()
return s:MODE_NAME
endfunction
"
function s:handler.getPrompt()
return fuf#formatPrompt(g:fuf_bookmarkfile_prompt, self.partialMatching, '')
endfunction
"
function s:handler.getPreviewHeight()
return g:fuf_previewHeight
endfunction
"
function s:handler.isOpenable(enteredPattern)
return 1
endfunction
"
function s:handler.makePatternSet(patternBase)
return fuf#makePatternSet(a:patternBase, 's:interpretPrimaryPatternForNonPath',
\ self.partialMatching)
endfunction
"
function s:handler.makePreviewLines(word, count)
let item = s:findItem(fuf#loadDataFile(s:MODE_NAME, 'items'), a:word)
let lines = fuf#getFileLines(item.path)
if empty(lines)
return []
endif
let index = s:getMatchingLineNumber(lines, item.pattern, item.lnum) - 1
return fuf#makePreviewLinesAround(
\ lines, [index], a:count, self.getPreviewHeight())
endfunction
"
function s:handler.getCompleteItems(patternPrimary)
return self.items
endfunction
"
function s:handler.onOpen(word, mode)
if a:mode ==# s:OPEN_TYPE_DELETE
let items = fuf#loadDataFile(s:MODE_NAME, 'items')
call filter(items, 'v:val.word !=# a:word')
call fuf#saveDataFile(s:MODE_NAME, 'items', items)
let self.reservedMode = self.getModeName()
return
else
let item = s:findItem(fuf#loadDataFile(s:MODE_NAME, 'items'), a:word)
if !empty(item)
call s:jumpToBookmark(item.path, a:mode, item.pattern, item.lnum)
endif
endif
endfunction
"
function s:handler.onModeEnterPre()
endfunction
"
function s:handler.onModeEnterPost()
call fuf#defineKeyMappingInHandler(g:fuf_bookmarkfile_keyDelete,
\ 'onCr(' . s:OPEN_TYPE_DELETE . ')')
let self.items = fuf#loadDataFile(s:MODE_NAME, 'items')
call map(self.items, 'fuf#makeNonPathItem(v:val.word, strftime(g:fuf_timeFormat, v:val.time))')
call fuf#mapToSetSerialIndex(self.items, 1)
call map(self.items, 'fuf#setAbbrWithFormattedWord(v:val, 1)')
endfunction
"
function s:handler.onModeLeavePost(opened)
endfunction
" }}}1
"=============================================================================
" vim: set fdm=marker:

View file

@ -1,189 +0,0 @@
"=============================================================================
" Copyright (c) 2007-2010 Takeshi NISHIDA
"
"=============================================================================
" LOAD GUARD {{{1
if !l9#guardScriptLoading(expand('<sfile>:p'), 0, 0, [])
finish
endif
" }}}1
"=============================================================================
" GLOBAL FUNCTIONS {{{1
"
function fuf#buffer#createHandler(base)
return a:base.concretize(copy(s:handler))
endfunction
"
function fuf#buffer#getSwitchOrder()
return g:fuf_buffer_switchOrder
endfunction
"
function fuf#buffer#getEditableDataNames()
return []
endfunction
"
function fuf#buffer#renewCache()
endfunction
"
function fuf#buffer#requiresOnCommandPre()
return 0
endfunction
"
function fuf#buffer#onInit()
call fuf#defineLaunchCommand('FufBuffer', s:MODE_NAME, '""', [])
augroup fuf#buffer
autocmd!
autocmd BufEnter * call s:updateBufTimes()
autocmd BufWritePost * call s:updateBufTimes()
augroup END
endfunction
" }}}1
"=============================================================================
" LOCAL FUNCTIONS/VARIABLES {{{1
let s:MODE_NAME = expand('<sfile>:t:r')
let s:OPEN_TYPE_DELETE = -1
let s:bufTimes = {}
"
function s:updateBufTimes()
let s:bufTimes[bufnr('%')] = localtime()
endfunction
"
function s:makeItem(nr)
let fname = (empty(bufname(a:nr))
\ ? '[No Name]'
\ : fnamemodify(bufname(a:nr), ':p:~:.'))
let time = (exists('s:bufTimes[a:nr]') ? s:bufTimes[a:nr] : 0)
let item = fuf#makePathItem(fname, strftime(g:fuf_timeFormat, time), 0)
let item.index = a:nr
let item.bufNr = a:nr
let item.time = time
let item.abbrPrefix = s:getBufIndicator(a:nr) . ' '
return item
endfunction
"
function s:getBufIndicator(bufNr)
if !getbufvar(a:bufNr, '&modifiable')
return '[-]'
elseif getbufvar(a:bufNr, '&modified')
return '[+]'
elseif getbufvar(a:bufNr, '&readonly')
return '[R]'
else
return ' '
endif
endfunction
"
function s:compareTimeDescending(i1, i2)
return a:i1.time == a:i2.time ? 0 : a:i1.time > a:i2.time ? -1 : +1
endfunction
"
function s:findItem(items, word)
for item in a:items
if item.word ==# a:word
return item
endif
endfor
return {}
endfunction
" }}}1
"=============================================================================
" s:handler {{{1
let s:handler = {}
"
function s:handler.getModeName()
return s:MODE_NAME
endfunction
"
function s:handler.getPrompt()
return fuf#formatPrompt(g:fuf_buffer_prompt, self.partialMatching, '')
endfunction
"
function s:handler.getPreviewHeight()
return g:fuf_previewHeight
endfunction
"
function s:handler.isOpenable(enteredPattern)
return 1
endfunction
"
function s:handler.makePatternSet(patternBase)
return fuf#makePatternSet(a:patternBase, 's:interpretPrimaryPatternForPath',
\ self.partialMatching)
endfunction
"
function s:handler.makePreviewLines(word, count)
let item = s:findItem(self.items, a:word)
if empty(item)
return []
endif
return fuf#makePreviewLinesForFile(item.bufNr, a:count, self.getPreviewHeight())
endfunction
"
function s:handler.getCompleteItems(patternPrimary)
return self.items
endfunction
"
function s:handler.onOpen(word, mode)
" not use bufnr(a:word) in order to handle unnamed buffer
let item = s:findItem(self.items, a:word)
if empty(item)
" do nothing
elseif a:mode ==# s:OPEN_TYPE_DELETE
execute item.bufNr . 'bdelete'
let self.reservedMode = self.getModeName()
else
call fuf#openBuffer(item.bufNr, a:mode, g:fuf_reuseWindow)
endif
endfunction
"
function s:handler.onModeEnterPre()
endfunction
"
function s:handler.onModeEnterPost()
call fuf#defineKeyMappingInHandler(g:fuf_buffer_keyDelete,
\ 'onCr(' . s:OPEN_TYPE_DELETE . ')')
let self.items = range(1, bufnr('$'))
call filter(self.items, 'buflisted(v:val) && v:val != self.bufNrPrev && v:val != bufnr("%")')
call map(self.items, 's:makeItem(v:val)')
if g:fuf_buffer_mruOrder
call sort(self.items, 's:compareTimeDescending')
call fuf#mapToSetSerialIndex(self.items, 1)
endif
let self.items = fuf#mapToSetAbbrWithSnippedWordAsPath(self.items)
endfunction
"
function s:handler.onModeLeavePost(opened)
endfunction
" }}}1
"=============================================================================
" vim: set fdm=marker:

View file

@ -1,300 +0,0 @@
"=============================================================================
" Copyright (c) 2010 Takeshi NISHIDA
"
"=============================================================================
" LOAD GUARD {{{1
if !l9#guardScriptLoading(expand('<sfile>:p'), 0, 0, [])
finish
endif
" }}}1
"=============================================================================
" GLOBAL FUNCTIONS {{{1
"
function fuf#buffertag#createHandler(base)
return a:base.concretize(copy(s:handler))
endfunction
"
function fuf#buffertag#getSwitchOrder()
return g:fuf_buffertag_switchOrder
endfunction
"
function fuf#buffertag#getEditableDataNames()
return []
endfunction
"
function fuf#buffertag#renewCache()
let s:tagItemsCache = {}
let s:tagDataCache = {}
endfunction
"
function fuf#buffertag#requiresOnCommandPre()
return 0
endfunction
"
function fuf#buffertag#onInit()
call fuf#defineLaunchCommand('FufBufferTag', s:MODE_NAME, '""',
\ [['g:fuf_buffertag_forAll', 0]])
call fuf#defineLaunchCommand('FufBufferTagAll', s:MODE_NAME, '""',
\ [['g:fuf_buffertag_forAll', 1]])
call fuf#defineLaunchCommand('FufBufferTagWithCursorWord', s:MODE_NAME,
\ 'expand(''<cword>'')', [['g:fuf_buffertag_forAll', 0]])
call fuf#defineLaunchCommand('FufBufferTagAllWithCursorWord', s:MODE_NAME,
\ 'expand(''<cword>'')', [['g:fuf_buffertag_forAll', 1]])
call fuf#defineLaunchCommand('FufBufferTagWithSelectedText', s:MODE_NAME,
\ 'l9#getSelectedText()', [['g:fuf_buffertag_forAll', 0]])
call fuf#defineLaunchCommand('FufBufferTagAllWithSelectedText', s:MODE_NAME,
\ 'l9#getSelectedText()', [['g:fuf_buffertag_forAll', 1]])
call l9#defineVariableDefault('g:fuf_buffertag_forAll', 0) " private option
" the following settings originate from taglist.vim
call l9#defineVariableDefault('g:fuf_buffertag__asm' , '--language-force=asm --asm-types=dlmt')
call l9#defineVariableDefault('g:fuf_buffertag__aspperl' , '--language-force=asp --asp-types=fsv')
call l9#defineVariableDefault('g:fuf_buffertag__aspvbs' , '--language-force=asp --asp-types=fsv')
call l9#defineVariableDefault('g:fuf_buffertag__awk' , '--language-force=awk --awk-types=f')
call l9#defineVariableDefault('g:fuf_buffertag__beta' , '--language-force=beta --beta-types=fsv')
call l9#defineVariableDefault('g:fuf_buffertag__c' , '--language-force=c --c-types=dgsutvf')
call l9#defineVariableDefault('g:fuf_buffertag__cpp' , '--language-force=c++ --c++-types=nvdtcgsuf')
call l9#defineVariableDefault('g:fuf_buffertag__cs' , '--language-force=c# --c#-types=dtncEgsipm')
call l9#defineVariableDefault('g:fuf_buffertag__cobol' , '--language-force=cobol --cobol-types=dfgpPs')
call l9#defineVariableDefault('g:fuf_buffertag__eiffel' , '--language-force=eiffel --eiffel-types=cf')
call l9#defineVariableDefault('g:fuf_buffertag__erlang' , '--language-force=erlang --erlang-types=drmf')
call l9#defineVariableDefault('g:fuf_buffertag__expect' , '--language-force=tcl --tcl-types=cfp')
call l9#defineVariableDefault('g:fuf_buffertag__fortran' , '--language-force=fortran --fortran-types=pbceiklmntvfs')
call l9#defineVariableDefault('g:fuf_buffertag__html' , '--language-force=html --html-types=af')
call l9#defineVariableDefault('g:fuf_buffertag__java' , '--language-force=java --java-types=pcifm')
call l9#defineVariableDefault('g:fuf_buffertag__javascript', '--language-force=javascript --javascript-types=f')
call l9#defineVariableDefault('g:fuf_buffertag__lisp' , '--language-force=lisp --lisp-types=f')
call l9#defineVariableDefault('g:fuf_buffertag__lua' , '--language-force=lua --lua-types=f')
call l9#defineVariableDefault('g:fuf_buffertag__make' , '--language-force=make --make-types=m')
call l9#defineVariableDefault('g:fuf_buffertag__pascal' , '--language-force=pascal --pascal-types=fp')
call l9#defineVariableDefault('g:fuf_buffertag__perl' , '--language-force=perl --perl-types=clps')
call l9#defineVariableDefault('g:fuf_buffertag__php' , '--language-force=php --php-types=cdvf')
call l9#defineVariableDefault('g:fuf_buffertag__python' , '--language-force=python --python-types=cmf')
call l9#defineVariableDefault('g:fuf_buffertag__rexx' , '--language-force=rexx --rexx-types=s')
call l9#defineVariableDefault('g:fuf_buffertag__ruby' , '--language-force=ruby --ruby-types=cfFm')
call l9#defineVariableDefault('g:fuf_buffertag__scheme' , '--language-force=scheme --scheme-types=sf')
call l9#defineVariableDefault('g:fuf_buffertag__sh' , '--language-force=sh --sh-types=f')
call l9#defineVariableDefault('g:fuf_buffertag__csh' , '--language-force=sh --sh-types=f')
call l9#defineVariableDefault('g:fuf_buffertag__zsh' , '--language-force=sh --sh-types=f')
call l9#defineVariableDefault('g:fuf_buffertag__slang' , '--language-force=slang --slang-types=nf')
call l9#defineVariableDefault('g:fuf_buffertag__sml' , '--language-force=sml --sml-types=ecsrtvf')
call l9#defineVariableDefault('g:fuf_buffertag__sql' , '--language-force=sql --sql-types=cFPrstTvfp')
call l9#defineVariableDefault('g:fuf_buffertag__tcl' , '--language-force=tcl --tcl-types=cfmp')
call l9#defineVariableDefault('g:fuf_buffertag__vera' , '--language-force=vera --vera-types=cdefgmpPtTvx')
call l9#defineVariableDefault('g:fuf_buffertag__verilog' , '--language-force=verilog --verilog-types=mcPertwpvf')
call l9#defineVariableDefault('g:fuf_buffertag__vim' , '--language-force=vim --vim-types=avf')
call l9#defineVariableDefault('g:fuf_buffertag__yacc' , '--language-force=yacc --yacc-types=l')
endfunction
" }}}1
"=============================================================================
" LOCAL FUNCTIONS/VARIABLES {{{1
let s:MODE_NAME = expand('<sfile>:t:r')
"
function s:parseTagLine(line)
" tag W:\Win32\SRC7\NCSIM\NCVW32\CUBEFACE.H /^#define CUBEFACE_H$/;" macro line:4
let fields = matchlist(a:line, '\v^([^\t]+)\t(.+)\t\/\^(.+)\$\/\;\"\t(.+)\tline\:(\d+)')
if empty(fields)
return {}
endif
return {
\ 'tag' : fields[1],
\ 'fname' : fields[2],
\ 'pattern': fields[3],
\ 'kind' : fields[4],
\ 'lnum' : str2nr(fields[5]),
\ }
endfunction
"
let s:TEMP_VARIABLES_GROUP = expand('<sfile>:p')
"
function s:getFileType(bufNr)
let ft = getbufvar(a:bufNr, '&filetype')
if !empty(ft) || bufloaded(a:bufNr)
return ft
endif
let ft = getbufvar(a:bufNr, 'fuf_buffertag_filetype')
if !empty(ft)
return ft
endif
call l9#tempvariables#set(s:TEMP_VARIABLES_GROUP, '&eventignore', 'FileType')
call l9#tempvariables#set(s:TEMP_VARIABLES_GROUP, '&filetype', &filetype)
" from taglist.vim
execute 'doautocmd filetypedetect BufRead ' . bufname(a:bufNr)
let ft = &filetype
call l9#tempvariables#end(s:TEMP_VARIABLES_GROUP)
call setbufvar(a:bufNr, 'fuf_buffertag_filetype', ft)
return ft
endfunction
"
function s:makeCtagsCmd(bufNr)
let ft = s:getFileType(a:bufNr)
if !exists('g:fuf_buffertag__{ft}')
return ''
endif
"
let cmd = join([g:fuf_buffertag_ctagsPath,
\ '-f - --sort=no --excmd=pattern --fields=nKs',
\ g:fuf_buffertag__{ft},
\ shellescape(fnamemodify(bufname(a:bufNr), ':p'))])
return cmd
endfunction
"
function s:getTagItems(bufNr)
let cmd = s:makeCtagsCmd(a:bufNr)
if empty(cmd)
return []
elseif !exists('s:tagItemsCache[cmd]') ||
\ s:tagItemsCache[cmd].time < getftime(expand(bufname(a:bufNr)))
let items = split(system(cmd), "\n")
if v:shell_error
call fuf#echoError([cmd] + items)
throw "Command error"
endif
call map(items, 's:parseTagLine(v:val)')
call filter(items, '!empty(v:val)')
let s:tagItemsCache[cmd] = {
\ 'time' : localtime(),
\ 'items' : items,
\ }
endif
return s:tagItemsCache[cmd].items
endfunction
"
function s:makeItem(tag, itemMap)
let menu = fnamemodify(a:itemMap[a:tag][0].fname, ':t')
\ . ' [' . a:itemMap[a:tag][0].kind . ']'
if len(a:itemMap[a:tag]) > 1
let menu .= ' (' . len(a:itemMap[a:tag]) . ')'
endif
let item = fuf#makeNonPathItem(a:tag, menu)
return item
endfunction
"
function s:getTagData(bufNrs)
let key = join([0] + sort(copy(a:bufNrs)), "\n")
let bufNames = map(copy(a:bufNrs), 'bufname(v:val)')
if !exists('s:tagDataCache[key]') ||
\ fuf#countModifiedFiles(bufNames, s:tagDataCache[key].time) > 0
let itemMap = {}
for item in l9#concat(map(copy(a:bufNrs), 's:getTagItems(v:val)'))
if !exists('itemMap[item.tag]')
let itemMap[item.tag] = []
endif
call add(itemMap[item.tag], item)
endfor
let items = sort(keys(itemMap))
call map(items, 's:makeItem(v:val, itemMap)')
call fuf#mapToSetSerialIndex(items, 1)
call map(items, 'fuf#setAbbrWithFormattedWord(v:val, 1)')
let s:tagDataCache[key] = {
\ 'time' : localtime(),
\ 'itemMap': itemMap,
\ 'items' : items,
\ }
endif
return [s:tagDataCache[key].items, s:tagDataCache[key].itemMap]
endfunction
"
function s:jumpToTag(item, mode)
call fuf#openFile(a:item.fname, a:mode, g:fuf_reuseWindow)
call cursor(a:item.lnum, 1)
normal! zvzz
endfunction
" }}}1
"=============================================================================
" s:handler {{{1
let s:handler = {}
"
function s:handler.getModeName()
return s:MODE_NAME
endfunction
"
function s:handler.getPrompt()
return fuf#formatPrompt(g:fuf_buffertag_prompt, self.partialMatching, '')
endfunction
"
function s:handler.getPreviewHeight()
return 0
endfunction
"
function s:handler.isOpenable(enteredPattern)
return 1
endfunction
"
function s:handler.makePatternSet(patternBase)
return fuf#makePatternSet(a:patternBase, 's:interpretPrimaryPatternForNonPath',
\ self.partialMatching)
endfunction
"
function s:handler.makePreviewLines(word, count)
return []
endfunction
"
function s:handler.getCompleteItems(patternPrimary)
return self.items
endfunction
"
function s:handler.onOpen(word, mode)
if !exists('self.itemMap[a:word][0]')
call fuf#echoError('Definition not found:' . a:word)
return
elseif len(self.itemMap[a:word]) == 1
let i = 0
else
let list = map(fuf#mapToSetSerialIndex(copy(self.itemMap[a:word]), 1),
\ 'printf(" %2d: %s|%d| [%s] %s",v:val.index, fnamemodify(v:val.fname, ":~:."), v:val.lnum, v:val.kind, v:val.pattern)')
let i = inputlist(['Select a definition of "' . a:word . '":'] + list) - 1
endif
if 0 <= i && i < len(self.itemMap[a:word])
call s:jumpToTag(self.itemMap[a:word][i], a:mode)
endif
endfunction
"
function s:handler.onModeEnterPre()
endfunction
"
function s:handler.onModeEnterPost()
if g:fuf_buffertag_forAll
let bufNrs = filter(range(1, bufnr('$')), 'buflisted(v:val)')
else
let bufNrs = [self.bufNrPrev]
endif
let [self.items, self.itemMap] = s:getTagData(bufNrs)
endfunction
"
function s:handler.onModeLeavePost(opened)
endfunction
" }}}1
"=============================================================================
" vim: set fdm=marker:

View file

@ -1,137 +0,0 @@
"=============================================================================
" Copyright (c) 2007-2010 Takeshi NISHIDA
"
"=============================================================================
" LOAD GUARD {{{1
if !l9#guardScriptLoading(expand('<sfile>:p'), 0, 0, [])
finish
endif
" }}}1
"=============================================================================
" GLOBAL FUNCTIONS {{{1
"
function fuf#callbackfile#createHandler(base)
return a:base.concretize(copy(s:handler))
endfunction
"
function fuf#callbackfile#getSwitchOrder()
return -1
endfunction
"
function fuf#callbackfile#getEditableDataNames()
return []
endfunction
"
function fuf#callbackfile#renewCache()
let s:cache = {}
endfunction
"
function fuf#callbackfile#requiresOnCommandPre()
return 0
endfunction
"
function fuf#callbackfile#onInit()
endfunction
"
function fuf#callbackfile#launch(initialPattern, partialMatching, prompt, exclude, listener)
let s:prompt = (empty(a:prompt) ? '>' : a:prompt)
let s:exclude = a:exclude
let s:listener = a:listener
call fuf#launch(s:MODE_NAME, a:initialPattern, a:partialMatching)
endfunction
" }}}1
"=============================================================================
" LOCAL FUNCTIONS/VARIABLES {{{1
let s:MODE_NAME = expand('<sfile>:t:r')
"
function s:enumItems(dir)
let key = getcwd() . g:fuf_ignoreCase . s:exclude . "\n" . a:dir
if !exists('s:cache[key]')
let s:cache[key] = fuf#enumExpandedDirsEntries(a:dir, s:exclude)
if isdirectory(a:dir)
call insert(s:cache[key], fuf#makePathItem(a:dir . '.', '', 0))
endif
call fuf#mapToSetSerialIndex(s:cache[key], 1)
call fuf#mapToSetAbbrWithSnippedWordAsPath(s:cache[key])
endif
return s:cache[key]
endfunction
" }}}1
"=============================================================================
" s:handler {{{1
let s:handler = {}
"
function s:handler.getModeName()
return s:MODE_NAME
endfunction
"
function s:handler.getPrompt()
return fuf#formatPrompt(s:prompt, self.partialMatching, '')
endfunction
"
function s:handler.getPreviewHeight()
return g:fuf_previewHeight
endfunction
"
function s:handler.isOpenable(enteredPattern)
return a:enteredPattern =~# '[^/\\]$'
endfunction
"
function s:handler.makePatternSet(patternBase)
return fuf#makePatternSet(a:patternBase, 's:interpretPrimaryPatternForPathTail',
\ self.partialMatching)
endfunction
"
function s:handler.makePreviewLines(word, count)
return fuf#makePreviewLinesForFile(a:word, a:count, self.getPreviewHeight())
endfunction
"
function s:handler.getCompleteItems(patternPrimary)
let items = copy(s:enumItems(fuf#splitPath(a:patternPrimary).head))
return filter(items, 'bufnr("^" . v:val.word . "$") != self.bufNrPrev')
endfunction
"
function s:handler.onOpen(word, mode)
call s:listener.onComplete(a:word, a:mode)
endfunction
"
function s:handler.onModeEnterPre()
endfunction
"
function s:handler.onModeEnterPost()
endfunction
"
function s:handler.onModeLeavePost(opened)
if !a:opened && exists('s:listener.onAbort()')
call s:listener.onAbort()
endif
endfunction
" }}}1
"=============================================================================
" vim: set fdm=marker:

View file

@ -1,139 +0,0 @@
"=============================================================================
" Copyright (c) 2007-2010 Takeshi NISHIDA
"
"=============================================================================
" LOAD GUARD {{{1
if !l9#guardScriptLoading(expand('<sfile>:p'), 0, 0, [])
finish
endif
" }}}1
"=============================================================================
" GLOBAL FUNCTIONS {{{1
"
function fuf#callbackitem#createHandler(base)
return a:base.concretize(copy(s:handler))
endfunction
"
function fuf#callbackitem#getSwitchOrder()
return -1
endfunction
"
function fuf#callbackitem#getEditableDataNames()
return []
endfunction
"
function fuf#callbackitem#renewCache()
endfunction
"
function fuf#callbackitem#requiresOnCommandPre()
return 0
endfunction
"
function fuf#callbackitem#onInit()
endfunction
"
function fuf#callbackitem#launch(initialPattern, partialMatching, prompt, listener, items, forPath)
let s:prompt = (empty(a:prompt) ? '>' : a:prompt)
let s:listener = a:listener
let s:forPath = a:forPath
let s:items = copy(a:items)
if s:forPath
call map(s:items, 'fuf#makePathItem(v:val, "", 1)')
call fuf#mapToSetSerialIndex(s:items, 1)
call fuf#mapToSetAbbrWithSnippedWordAsPath(s:items)
else
call map(s:items, 'fuf#makeNonPathItem(v:val, "")')
call fuf#mapToSetSerialIndex(s:items, 1)
call map(s:items, 'fuf#setAbbrWithFormattedWord(v:val, 1)')
endif
call fuf#launch(s:MODE_NAME, a:initialPattern, a:partialMatching)
endfunction
" }}}1
"=============================================================================
" LOCAL FUNCTIONS/VARIABLES {{{1
let s:MODE_NAME = expand('<sfile>:t:r')
" }}}1
"=============================================================================
" s:handler {{{1
let s:handler = {}
"
function s:handler.getModeName()
return s:MODE_NAME
endfunction
"
function s:handler.getPrompt()
return fuf#formatPrompt(s:prompt, self.partialMatching, '')
endfunction
"
function s:handler.getPreviewHeight()
if s:forPath
return g:fuf_previewHeight
endif
return 0
endfunction
"
function s:handler.isOpenable(enteredPattern)
return 1
endfunction
"
function s:handler.makePatternSet(patternBase)
let parser = (s:forPath
\ ? 's:interpretPrimaryPatternForPath'
\ : 's:interpretPrimaryPatternForNonPath')
return fuf#makePatternSet(a:patternBase, parser, self.partialMatching)
endfunction
"
function s:handler.makePreviewLines(word, count)
if s:forPath
return fuf#makePreviewLinesForFile(a:word, a:count, self.getPreviewHeight())
endif
return []
endfunction
"
function s:handler.getCompleteItems(patternPrimary)
return s:items
endfunction
"
function s:handler.onOpen(word, mode)
call s:listener.onComplete(a:word, a:mode)
endfunction
"
function s:handler.onModeEnterPre()
endfunction
"
function s:handler.onModeEnterPost()
endfunction
"
function s:handler.onModeLeavePost(opened)
if !a:opened && exists('s:listener.onAbort()')
call s:listener.onAbort()
endif
endfunction
" }}}1
"=============================================================================
" vim: set fdm=marker:

View file

@ -1,172 +0,0 @@
"=============================================================================
" Copyright (c) 2007-2010 Takeshi NISHIDA
"
"=============================================================================
" LOAD GUARD {{{1
if !l9#guardScriptLoading(expand('<sfile>:p'), 0, 0, [])
finish
endif
" }}}1
"=============================================================================
" GLOBAL FUNCTIONS {{{1
"
function fuf#changelist#createHandler(base)
return a:base.concretize(copy(s:handler))
endfunction
"
function fuf#changelist#getSwitchOrder()
return g:fuf_changelist_switchOrder
endfunction
"
function fuf#changelist#getEditableDataNames()
return []
endfunction
"
function fuf#changelist#renewCache()
endfunction
"
function fuf#changelist#requiresOnCommandPre()
return 0
endfunction
"
function fuf#changelist#onInit()
call fuf#defineLaunchCommand('FufChangeList', s:MODE_NAME, '""', [])
endfunction
" }}}1
"=============================================================================
" LOCAL FUNCTIONS/VARIABLES {{{1
let s:MODE_NAME = expand('<sfile>:t:r')
"
function s:getChangesLines()
redir => result
:silent changes
redir END
return split(result, "\n")
endfunction
"
function s:parseChangesLine(line)
" return matchlist(a:line, '^\(.\)\s\+\(\d\+\)\s\(.*\)$')
let elements = matchlist(a:line, '\v^(.)\s*(\d+)\s+(\d+)\s+(\d+)\s*(.*)$')
if empty(elements)
return {}
endif
return {
\ 'prefix': elements[1],
\ 'count' : elements[2],
\ 'lnum' : elements[3],
\ 'text' : printf('|%d:%d|%s', elements[3], elements[4], elements[5]),
\ }
endfunction
"
function s:makeItem(line)
let parsed = s:parseChangesLine(a:line)
if empty(parsed)
return {}
endif
let item = fuf#makeNonPathItem(parsed.text, '')
let item.abbrPrefix = parsed.prefix
let item.lnum = parsed.lnum
return item
endfunction
" }}}1
"=============================================================================
" s:handler {{{1
let s:handler = {}
"
function s:handler.getModeName()
return s:MODE_NAME
endfunction
"
function s:handler.getPrompt()
return fuf#formatPrompt(g:fuf_changelist_prompt, self.partialMatching, '')
endfunction
"
function s:handler.getPreviewHeight()
return g:fuf_previewHeight
endfunction
"
function s:handler.isOpenable(enteredPattern)
return 1
endfunction
"
function s:handler.makePatternSet(patternBase)
return fuf#makePatternSet(a:patternBase, 's:interpretPrimaryPatternForNonPath',
\ self.partialMatching)
endfunction
"
function s:handler.makePreviewLines(word, count)
let items = filter(copy(self.items), 'v:val.word ==# a:word')
if empty(items)
return []
endif
let lines = fuf#getFileLines(self.bufNrPrev)
return fuf#makePreviewLinesAround(
\ lines, [items[0].lnum - 1], a:count, self.getPreviewHeight())
endfunction
"
function s:handler.getCompleteItems(patternPrimary)
return self.items
endfunction
"
function s:handler.onOpen(word, mode)
call fuf#prejump(a:mode)
let older = 0
for line in reverse(s:getChangesLines())
if stridx(line, '>') == 0
let older = 1
endif
let parsed = s:parseChangesLine(line)
if !empty(parsed) && parsed.text ==# a:word
if parsed.count != 0
execute 'normal! ' . parsed.count . (older ? 'g;' : 'g,') . 'zvzz'
endif
break
endif
endfor
endfunction
"
function s:handler.onModeEnterPre()
let self.items = s:getChangesLines()
endfunction
"
function s:handler.onModeEnterPost()
call map(self.items, 's:makeItem(v:val)')
call filter(self.items, '!empty(v:val)')
call reverse(self.items)
call fuf#mapToSetSerialIndex(self.items, 1)
call map(self.items, 'fuf#setAbbrWithFormattedWord(v:val, 1)')
endfunction
"
function s:handler.onModeLeavePost(opened)
endfunction
" }}}1
"=============================================================================
" vim: set fdm=marker:

View file

@ -1,199 +0,0 @@
"=============================================================================
" Copyright (c) 2007-2010 Takeshi NISHIDA
"
"=============================================================================
" LOAD GUARD {{{1
if !l9#guardScriptLoading(expand('<sfile>:p'), 0, 0, [])
finish
endif
" }}}1
"=============================================================================
" GLOBAL FUNCTIONS {{{1
"
function fuf#coveragefile#createHandler(base)
return a:base.concretize(copy(s:handler))
endfunction
"
function fuf#coveragefile#getSwitchOrder()
return g:fuf_coveragefile_switchOrder
endfunction
"
function fuf#coveragefile#getEditableDataNames()
return ['coverages']
endfunction
"
function fuf#coveragefile#renewCache()
let s:cache = {}
endfunction
"
function fuf#coveragefile#requiresOnCommandPre()
return 0
endfunction
"
function fuf#coveragefile#onInit()
call fuf#defineLaunchCommand('FufCoverageFile', s:MODE_NAME, '""', [])
call l9#defineVariableDefault('g:fuf_coveragefile_name', '') " private option
command! -bang -narg=0 FufCoverageFileRegister call s:registerCoverage()
command! -bang -narg=? FufCoverageFileChange call s:changeCoverage(<q-args>)
endfunction
" }}}1
"=============================================================================
" LOCAL FUNCTIONS/VARIABLES {{{1
let s:MODE_NAME = expand('<sfile>:t:r')
"
function s:enumItems()
let key = join([getcwd(), g:fuf_ignoreCase, g:fuf_coveragefile_exclude,
\ g:fuf_coveragefile_globPatterns], "\n")
if !exists('s:cache[key]')
let s:cache[key] = l9#concat(map(copy(g:fuf_coveragefile_globPatterns),
\ 'fuf#glob(v:val)'))
call filter(s:cache[key], 'filereadable(v:val)') " filter out directories
call map(s:cache[key], 'fuf#makePathItem(fnamemodify(v:val, ":~:."), "", 0)')
if len(g:fuf_coveragefile_exclude)
call filter(s:cache[key], 'v:val.word !~ g:fuf_coveragefile_exclude')
endif
call fuf#mapToSetSerialIndex(s:cache[key], 1)
call fuf#mapToSetAbbrWithSnippedWordAsPath(s:cache[key])
endif
return s:cache[key]
endfunction
"
function s:registerCoverage()
let patterns = []
while 1
let pattern = l9#inputHl('Question', '[fuf] Glob pattern for coverage (<Esc> and end):',
\ '', 'file')
if pattern !~ '\S'
break
endif
call add(patterns, pattern)
endwhile
if empty(patterns)
call fuf#echoWarning('Canceled')
return
endif
echo '[fuf] patterns: ' . string(patterns)
let name = l9#inputHl('Question', '[fuf] Coverage name:')
if name !~ '\S'
call fuf#echoWarning('Canceled')
return
endif
let coverages = fuf#loadDataFile(s:MODE_NAME, 'coverages')
call insert(coverages, {'name': name, 'patterns': patterns})
call fuf#saveDataFile(s:MODE_NAME, 'coverages', coverages)
endfunction
"
function s:createChangeCoverageListener()
let listener = {}
function listener.onComplete(name, method)
call s:changeCoverage(a:name)
endfunction
return listener
endfunction
"
function s:changeCoverage(name)
let coverages = fuf#loadDataFile(s:MODE_NAME, 'coverages')
if a:name !~ '\S'
let names = map(copy(coverages), 'v:val.name')
call fuf#callbackitem#launch('', 0, '>Coverage>', s:createChangeCoverageListener(), names, 0)
return
else
let name = a:name
endif
call filter(coverages, 'v:val.name ==# name')
if empty(coverages)
call fuf#echoError('Coverage not found: ' . name)
return
endif
call fuf#setOneTimeVariables(
\ ['g:fuf_coveragefile_globPatterns', coverages[0].patterns],
\ ['g:fuf_coveragefile_name' , a:name]
\ )
FufCoverageFile
endfunction
" }}}1
"=============================================================================
" s:handler {{{1
let s:handler = {}
"
function s:handler.getModeName()
return s:MODE_NAME
endfunction
"
function s:handler.getPrompt()
let nameString = (empty(g:fuf_coveragefile_name) ? ''
\ : '[' . g:fuf_coveragefile_name . ']')
return fuf#formatPrompt(g:fuf_coveragefile_prompt, self.partialMatching,
\ nameString)
endfunction
"
function s:handler.getPreviewHeight()
return g:fuf_previewHeight
endfunction
"
function s:handler.isOpenable(enteredPattern)
return 1
endfunction
"
function s:handler.makePatternSet(patternBase)
return fuf#makePatternSet(a:patternBase, 's:interpretPrimaryPatternForPath',
\ self.partialMatching)
endfunction
"
function s:handler.makePreviewLines(word, count)
return fuf#makePreviewLinesForFile(a:word, a:count, self.getPreviewHeight())
endfunction
"
function s:handler.getCompleteItems(patternPrimary)
return self.items
endfunction
"
function s:handler.onOpen(word, mode)
call fuf#openFile(a:word, a:mode, g:fuf_reuseWindow)
endfunction
"
function s:handler.onModeEnterPre()
endfunction
"
function s:handler.onModeEnterPost()
" NOTE: Comparing filenames is faster than bufnr('^' . fname . '$')
let bufNamePrev = fnamemodify(bufname(self.bufNrPrev), ':~:.')
let self.items = copy(s:enumItems())
call filter(self.items, 'v:val.word !=# bufNamePrev')
endfunction
"
function s:handler.onModeLeavePost(opened)
endfunction
" }}}1
"=============================================================================
" vim: set fdm=marker:

View file

@ -1,132 +0,0 @@
"=============================================================================
" Copyright (c) 2007-2010 Takeshi NISHIDA
"
"=============================================================================
" LOAD GUARD {{{1
if !l9#guardScriptLoading(expand('<sfile>:p'), 0, 0, [])
finish
endif
" }}}1
"=============================================================================
" GLOBAL FUNCTIONS {{{1
"
function fuf#dir#createHandler(base)
return a:base.concretize(copy(s:handler))
endfunction
"
function fuf#dir#getSwitchOrder()
return g:fuf_dir_switchOrder
endfunction
"
function fuf#dir#getEditableDataNames()
return []
endfunction
"
function fuf#dir#renewCache()
let s:cache = {}
endfunction
"
function fuf#dir#requiresOnCommandPre()
return 0
endfunction
"
function fuf#dir#onInit()
call fuf#defineLaunchCommand('FufDir' , s:MODE_NAME, '""', [])
call fuf#defineLaunchCommand('FufDirWithFullCwd' , s:MODE_NAME, 'fnamemodify(getcwd(), '':p'')', [])
call fuf#defineLaunchCommand('FufDirWithCurrentBufferDir', s:MODE_NAME, 'expand(''%:~:.'')[:-1-len(expand(''%:~:.:t''))]', [])
endfunction
" }}}1
"=============================================================================
" LOCAL FUNCTIONS/VARIABLES {{{1
let s:MODE_NAME = expand('<sfile>:t:r')
"
function s:enumItems(dir)
let key = getcwd() . g:fuf_ignoreCase . g:fuf_dir_exclude . "\n" . a:dir
if !exists('s:cache[key]')
let s:cache[key] = fuf#enumExpandedDirsEntries(a:dir, g:fuf_dir_exclude)
call filter(s:cache[key], 'v:val.word =~# ''[/\\]$''')
if isdirectory(a:dir)
call insert(s:cache[key], fuf#makePathItem(a:dir . '.', '', 0))
endif
call fuf#mapToSetSerialIndex(s:cache[key], 1)
call fuf#mapToSetAbbrWithSnippedWordAsPath(s:cache[key])
endif
return s:cache[key]
endfunction
" }}}1
"=============================================================================
" s:handler {{{1
let s:handler = {}
"
function s:handler.getModeName()
return s:MODE_NAME
endfunction
"
function s:handler.getPrompt()
return fuf#formatPrompt(g:fuf_dir_prompt, self.partialMatching, '')
endfunction
"
function s:handler.getPreviewHeight()
return g:fuf_previewHeight
endfunction
"
function s:handler.isOpenable(enteredPattern)
return a:enteredPattern =~# '[^/\\]$'
endfunction
"
function s:handler.makePatternSet(patternBase)
return fuf#makePatternSet(a:patternBase, 's:interpretPrimaryPatternForPathTail',
\ self.partialMatching)
endfunction
"
function s:handler.makePreviewLines(word, count)
return fuf#makePreviewLinesAround(
\ fuf#glob(fnamemodify(a:word, ':p') . '*'),
\ [], a:count, self.getPreviewHeight())
return
endfunction
"
function s:handler.getCompleteItems(patternPrimary)
return s:enumItems(fuf#splitPath(a:patternPrimary).head)
endfunction
"
function s:handler.onOpen(word, mode)
execute ':cd ' . fnameescape(a:word)
endfunction
"
function s:handler.onModeEnterPre()
endfunction
"
function s:handler.onModeEnterPost()
endfunction
"
function s:handler.onModeLeavePost(opened)
endfunction
" }}}1
"=============================================================================
" vim: set fdm=marker:

View file

@ -1,139 +0,0 @@
"=============================================================================
" Copyright (c) 2007-2010 Takeshi NISHIDA
"
"=============================================================================
" LOAD GUARD {{{1
if !l9#guardScriptLoading(expand('<sfile>:p'), 0, 0, [])
finish
endif
" }}}1
"=============================================================================
" GLOBAL FUNCTIONS {{{1
"
function fuf#file#createHandler(base)
return a:base.concretize(copy(s:handler))
endfunction
"
function fuf#file#getSwitchOrder()
return g:fuf_file_switchOrder
endfunction
"
function fuf#file#getEditableDataNames()
return []
endfunction
"
function fuf#file#renewCache()
let s:cache = {}
endfunction
"
function fuf#file#requiresOnCommandPre()
return 0
endfunction
"
function fuf#file#onInit()
call fuf#defineLaunchCommand('FufFile' , s:MODE_NAME, '""', [])
call fuf#defineLaunchCommand('FufFileWithFullCwd' , s:MODE_NAME, 'fnamemodify(getcwd(), '':p'')', [])
call fuf#defineLaunchCommand('FufFileWithCurrentBufferDir', s:MODE_NAME, 'expand(''%:~:.'')[:-1-len(expand(''%:~:.:t''))]', [])
endfunction
" }}}1
"=============================================================================
" LOCAL FUNCTIONS/VARIABLES {{{1
let s:MODE_NAME = expand('<sfile>:t:r')
"
function s:enumItems(dir)
let key = join([getcwd(), g:fuf_ignoreCase, g:fuf_file_exclude, a:dir], "\n")
if !exists('s:cache[key]')
let s:cache[key] = fuf#enumExpandedDirsEntries(a:dir, g:fuf_file_exclude)
call fuf#mapToSetSerialIndex(s:cache[key], 1)
call fuf#mapToSetAbbrWithSnippedWordAsPath(s:cache[key])
endif
return s:cache[key]
endfunction
"
function s:enumNonCurrentItems(dir, bufNrPrev, cache)
let key = a:dir . 'AVOIDING EMPTY KEY'
if !exists('a:cache[key]')
" NOTE: Comparing filenames is faster than bufnr('^' . fname . '$')
let bufNamePrev = bufname(a:bufNrPrev)
let a:cache[key] =
\ filter(copy(s:enumItems(a:dir)), 'v:val.word !=# bufNamePrev')
endif
return a:cache[key]
endfunction
" }}}1
"=============================================================================
" s:handler {{{1
let s:handler = {}
"
function s:handler.getModeName()
return s:MODE_NAME
endfunction
"
function s:handler.getPrompt()
return fuf#formatPrompt(g:fuf_file_prompt, self.partialMatching, '')
endfunction
"
function s:handler.getPreviewHeight()
return g:fuf_previewHeight
endfunction
"
function s:handler.isOpenable(enteredPattern)
return a:enteredPattern =~# '[^/\\]$'
endfunction
"
function s:handler.makePatternSet(patternBase)
return fuf#makePatternSet(a:patternBase, 's:interpretPrimaryPatternForPathTail',
\ self.partialMatching)
endfunction
"
function s:handler.makePreviewLines(word, count)
return fuf#makePreviewLinesForFile(a:word, a:count, self.getPreviewHeight())
endfunction
"
function s:handler.getCompleteItems(patternPrimary)
return s:enumNonCurrentItems(
\ fuf#splitPath(a:patternPrimary).head, self.bufNrPrev, self.cache)
endfunction
"
function s:handler.onOpen(word, mode)
call fuf#openFile(a:word, a:mode, g:fuf_reuseWindow)
endfunction
"
function s:handler.onModeEnterPre()
endfunction
"
function s:handler.onModeEnterPost()
let self.cache = {}
endfunction
"
function s:handler.onModeLeavePost(opened)
endfunction
" }}}1
"=============================================================================
" vim: set fdm=marker:

View file

@ -1,123 +0,0 @@
"=============================================================================
" Copyright (c) 2007-2010 Takeshi NISHIDA
"
"=============================================================================
" LOAD GUARD {{{1
if !l9#guardScriptLoading(expand('<sfile>:p'), 0, 0, [])
finish
endif
" }}}1
"=============================================================================
" GLOBAL FUNCTIONS {{{1
"
function fuf#givencmd#createHandler(base)
return a:base.concretize(copy(s:handler))
endfunction
"
function fuf#givencmd#getSwitchOrder()
return -1
endfunction
"
function fuf#givencmd#getEditableDataNames()
return []
endfunction
"
function fuf#givencmd#renewCache()
endfunction
"
function fuf#givencmd#requiresOnCommandPre()
return 0
endfunction
"
function fuf#givencmd#onInit()
endfunction
"
function fuf#givencmd#launch(initialPattern, partialMatching, prompt, items)
let s:prompt = (empty(a:prompt) ? '>' : a:prompt)
let s:items = copy(a:items)
call map(s:items, 'fuf#makeNonPathItem(v:val, "")')
call fuf#mapToSetSerialIndex(s:items, 1)
call map(s:items, 'fuf#setAbbrWithFormattedWord(v:val, 1)')
call fuf#launch(s:MODE_NAME, a:initialPattern, a:partialMatching)
endfunction
" }}}1
"=============================================================================
" LOCAL FUNCTIONS/VARIABLES {{{1
let s:MODE_NAME = expand('<sfile>:t:r')
" }}}1
"=============================================================================
" s:handler {{{1
let s:handler = {}
"
function s:handler.getModeName()
return s:MODE_NAME
endfunction
"
function s:handler.getPrompt()
return fuf#formatPrompt(s:prompt, self.partialMatching, '')
endfunction
"
function s:handler.getPreviewHeight()
return 0
endfunction
"
function s:handler.isOpenable(enteredPattern)
return 1
endfunction
"
function s:handler.makePatternSet(patternBase)
return fuf#makePatternSet(a:patternBase, 's:interpretPrimaryPatternForNonPath',
\ self.partialMatching)
endfunction
"
function s:handler.makePreviewLines(word, count)
return []
endfunction
"
function s:handler.getCompleteItems(patternPrimary)
return s:items
endfunction
"
function s:handler.onOpen(word, mode)
if a:word[0] =~# '[:/?]'
call histadd(a:word[0], a:word[1:])
endif
call feedkeys(a:word . "\<CR>", 'n')
endfunction
"
function s:handler.onModeEnterPre()
endfunction
"
function s:handler.onModeEnterPost()
endfunction
"
function s:handler.onModeLeavePost(opened)
endfunction
" }}}1
"=============================================================================
" vim: set fdm=marker:

View file

@ -1,123 +0,0 @@
"=============================================================================
" Copyright (c) 2007-2010 Takeshi NISHIDA
"
"=============================================================================
" LOAD GUARD {{{1
if !l9#guardScriptLoading(expand('<sfile>:p'), 0, 0, [])
finish
endif
" }}}1
"=============================================================================
" GLOBAL FUNCTIONS {{{1
"
function fuf#givendir#createHandler(base)
return a:base.concretize(copy(s:handler))
endfunction
"
function fuf#givendir#getSwitchOrder()
return -1
endfunction
"
function fuf#givendir#getEditableDataNames()
return []
endfunction
"
function fuf#givendir#renewCache()
endfunction
"
function fuf#givendir#requiresOnCommandPre()
return 0
endfunction
"
function fuf#givendir#onInit()
endfunction
"
function fuf#givendir#launch(initialPattern, partialMatching, prompt, items)
let s:prompt = (empty(a:prompt) ? '>' : a:prompt)
let s:items = map(copy(a:items), 'substitute(v:val, ''[/\\]\?$'', "", "")')
let s:items = map(s:items, 'fuf#makePathItem(v:val, "", 0)')
call fuf#mapToSetSerialIndex(s:items, 1)
call fuf#mapToSetAbbrWithSnippedWordAsPath(s:items)
call fuf#launch(s:MODE_NAME, a:initialPattern, a:partialMatching)
endfunction
" }}}1
"=============================================================================
" LOCAL FUNCTIONS/VARIABLES {{{1
let s:MODE_NAME = expand('<sfile>:t:r')
" }}}1
"=============================================================================
" s:handler {{{1
let s:handler = {}
"
function s:handler.getModeName()
return s:MODE_NAME
endfunction
"
function s:handler.getPrompt()
return fuf#formatPrompt(s:prompt, self.partialMatching, '')
endfunction
"
function s:handler.getPreviewHeight()
return g:fuf_previewHeight
endfunction
"
function s:handler.isOpenable(enteredPattern)
return 1
endfunction
"
function s:handler.makePatternSet(patternBase)
return fuf#makePatternSet(a:patternBase, 's:interpretPrimaryPatternForPath',
\ self.partialMatching)
endfunction
"
function s:handler.makePreviewLines(word, count)
return fuf#makePreviewLinesAround(
\ fuf#glob(fnamemodify(a:word, ':p') . '*'),
\ [], a:count, self.getPreviewHeight())
return
endfunction
"
function s:handler.getCompleteItems(patternPrimary)
return s:items
endfunction
"
function s:handler.onOpen(word, mode)
execute ':cd ' . fnameescape(a:word)
endfunction
"
function s:handler.onModeEnterPre()
endfunction
"
function s:handler.onModeEnterPost()
endfunction
"
function s:handler.onModeLeavePost(opened)
endfunction
" }}}1
"=============================================================================
" vim: set fdm=marker:

View file

@ -1,121 +0,0 @@
"=============================================================================
" Copyright (c) 2007-2010 Takeshi NISHIDA
"
"=============================================================================
" LOAD GUARD {{{1
if !l9#guardScriptLoading(expand('<sfile>:p'), 0, 0, [])
finish
endif
" }}}1
"=============================================================================
" GLOBAL FUNCTIONS {{{1
"
function fuf#givenfile#createHandler(base)
return a:base.concretize(copy(s:handler))
endfunction
"
function fuf#givenfile#getSwitchOrder()
return -1
endfunction
"
function fuf#givenfile#getEditableDataNames()
return []
endfunction
"
function fuf#givenfile#renewCache()
endfunction
"
function fuf#givenfile#requiresOnCommandPre()
return 0
endfunction
"
function fuf#givenfile#onInit()
endfunction
"
function fuf#givenfile#launch(initialPattern, partialMatching, prompt, items)
let s:prompt = (empty(a:prompt) ? '>' : a:prompt)
let s:items = map(copy(a:items), 'fuf#makePathItem(v:val, "", 0)')
call fuf#mapToSetSerialIndex(s:items, 1)
call map(s:items, 'fuf#setAbbrWithFormattedWord(v:val, 1)')
call fuf#launch(s:MODE_NAME, a:initialPattern, a:partialMatching)
endfunction
" }}}1
"=============================================================================
" LOCAL FUNCTIONS/VARIABLES {{{1
let s:MODE_NAME = expand('<sfile>:t:r')
" }}}1
"=============================================================================
" s:handler {{{1
let s:handler = {}
"
function s:handler.getModeName()
return s:MODE_NAME
endfunction
"
function s:handler.getPrompt()
return fuf#formatPrompt(s:prompt, self.partialMatching, '')
endfunction
"
function s:handler.getPreviewHeight()
return g:fuf_previewHeight
endfunction
"
function s:handler.isOpenable(enteredPattern)
return 1
endfunction
"
function s:handler.makePatternSet(patternBase)
return fuf#makePatternSet(a:patternBase, 's:interpretPrimaryPatternForPath',
\ self.partialMatching)
endfunction
"
function s:handler.makePreviewLines(word, count)
return fuf#makePreviewLinesForFile(a:word, a:count, self.getPreviewHeight())
endfunction
"
function s:handler.getCompleteItems(patternPrimary)
return s:items
endfunction
"
function s:handler.onOpen(word, mode)
call fuf#openFile(a:word, a:mode, g:fuf_reuseWindow)
endfunction
"
function s:handler.onModeEnterPre()
endfunction
"
function s:handler.onModeEnterPost()
endfunction
"
function s:handler.onModeLeavePost(opened)
endfunction
" }}}1
"=============================================================================
" vim: set fdm=marker:

View file

@ -1,198 +0,0 @@
"=============================================================================
" Copyright (c) 2007-2010 Takeshi NISHIDA
"
"=============================================================================
" LOAD GUARD {{{1
if !l9#guardScriptLoading(expand('<sfile>:p'), 0, 0, [])
finish
endif
" }}}1
"=============================================================================
" GLOBAL FUNCTIONS {{{1
"
function fuf#help#createHandler(base)
return a:base.concretize(copy(s:handler))
endfunction
"
function fuf#help#getSwitchOrder()
return g:fuf_help_switchOrder
endfunction
"
function fuf#help#getEditableDataNames()
return []
endfunction
"
function fuf#help#renewCache()
let s:cache = {}
endfunction
"
function fuf#help#requiresOnCommandPre()
return 0
endfunction
"
function fuf#help#onInit()
call fuf#defineLaunchCommand('FufHelp' , s:MODE_NAME, '""', [])
call fuf#defineLaunchCommand('FufHelpWithCursorWord', s:MODE_NAME, 'expand(''<cword>'')', [])
endfunction
" }}}1
"=============================================================================
" LOCAL FUNCTIONS/VARIABLES {{{1
let s:MODE_NAME = expand('<sfile>:t:r')
"
function s:getCurrentHelpTagFiles()
let prefix = 'doc' . l9#getPathSeparator()
let tagFiles = split(globpath(&runtimepath, prefix . 'tags' ), "\n")
\ + split(globpath(&runtimepath, prefix . 'tags-??'), "\n")
return sort(map(tagFiles, 'fnamemodify(v:val, ":p")'))
endfunction
"
function s:parseHelpTagEntry(line, tagFile)
let elements = split(a:line, "\t")
if len(elements) != 3 || elements[0][0] ==# '!'
return {}
endif
let suffix = matchstr(a:tagFile, '-\zs..$')
if empty(suffix)
let suffix = '@en'
else
let suffix = '@' . suffix
endif
let dir = fnamemodify(a:tagFile, ':h') . l9#getPathSeparator()
return {
\ 'word' : elements[0] . suffix,
\ 'path' : dir . elements[1],
\ 'pattern': elements[2][1:],
\ }
endfunction
"
function s:getHelpTagEntries(tagFile)
let names = map(l9#readFile(a:tagFile), 's:parseHelpTagEntry(v:val, a:tagFile)')
return filter(names, '!empty(v:val)')
endfunction
"
function s:parseHelpTagFiles(tagFiles, key)
let cacheName = 'cache-' . l9#hash224(a:key)
let cacheTime = fuf#getDataFileTime(s:MODE_NAME, cacheName)
if cacheTime != -1 && fuf#countModifiedFiles(a:tagFiles, cacheTime) == 0
return fuf#loadDataFile(s:MODE_NAME, cacheName)
endif
let items = l9#unique(l9#concat(map(copy(a:tagFiles), 's:getHelpTagEntries(v:val)')))
let items = map(items, 'extend(v:val, fuf#makeNonPathItem(v:val.word, ""))')
call fuf#mapToSetSerialIndex(items, 1)
let items = map(items, 'fuf#setAbbrWithFormattedWord(v:val, 1)')
call fuf#saveDataFile(s:MODE_NAME, cacheName, items)
return items
endfunction
"
function s:enumHelpTags(tagFiles)
if !len(a:tagFiles)
return []
endif
let key = join([g:fuf_ignoreCase] + a:tagFiles, "\n")
if !exists('s:cache[key]') || fuf#countModifiedFiles(a:tagFiles, s:cache[key].time)
let s:cache[key] = {
\ 'time' : localtime(),
\ 'items' : s:parseHelpTagFiles(a:tagFiles, key)
\ }
endif
return s:cache[key].items
endfunction
"
function s:getMatchingIndex(lines, pattern)
if empty(a:pattern)
return -1
endif
for i in range(len(a:lines))
if stridx(a:lines[i], a:pattern) >= 0
return i
endif
endfor
return -1
endfunction
" }}}1
"=============================================================================
" s:handler {{{1
let s:handler = {}
"
function s:handler.getModeName()
return s:MODE_NAME
endfunction
"
function s:handler.getPrompt()
return fuf#formatPrompt(g:fuf_help_prompt, self.partialMatching, '')
endfunction
"
function s:handler.getPreviewHeight()
return g:fuf_previewHeight
endfunction
"
function s:handler.isOpenable(enteredPattern)
return 1
endfunction
"
function s:handler.makePatternSet(patternBase)
return fuf#makePatternSet(a:patternBase, 's:interpretPrimaryPatternForNonPath',
\ self.partialMatching)
endfunction
"
function s:handler.makePreviewLines(word, count)
let items = filter(copy(s:enumHelpTags(self.tagFiles)), 'v:val.word ==# a:word')
if empty(items)
return []
endif
let lines = fuf#getFileLines(items[0].path)
let index = s:getMatchingIndex(lines, items[0].pattern)
return [items[0].path . ':'] + fuf#makePreviewLinesAround(
\ lines, (index < 0 ? [] : [index]), a:count, self.getPreviewHeight() - 1)
endfunction
"
function s:handler.getCompleteItems(patternPrimary)
return s:enumHelpTags(self.tagFiles)
endfunction
"
function s:handler.onOpen(word, mode)
call fuf#openHelp(a:word, a:mode)
endfunction
"
function s:handler.onModeEnterPre()
let self.tagFiles = s:getCurrentHelpTagFiles()
endfunction
"
function s:handler.onModeEnterPost()
endfunction
"
function s:handler.onModeLeavePost(opened)
endfunction
" }}}1
"=============================================================================
" vim: set fdm=marker:

View file

@ -1,182 +0,0 @@
"=============================================================================
" Copyright (c) 2007-2010 Takeshi NISHIDA
"
"=============================================================================
" LOAD GUARD {{{1
if !l9#guardScriptLoading(expand('<sfile>:p'), 0, 0, [])
finish
endif
" }}}1
"=============================================================================
" GLOBAL FUNCTIONS {{{1
"
function fuf#jumplist#createHandler(base)
return a:base.concretize(copy(s:handler))
endfunction
"
function fuf#jumplist#getSwitchOrder()
return g:fuf_jumplist_switchOrder
endfunction
"
function fuf#jumplist#getEditableDataNames()
return []
endfunction
"
function fuf#jumplist#renewCache()
endfunction
"
function fuf#jumplist#requiresOnCommandPre()
return 0
endfunction
"
function fuf#jumplist#onInit()
call fuf#defineLaunchCommand('FufJumpList', s:MODE_NAME, '""', [])
endfunction
" }}}1
"=============================================================================
" LOCAL FUNCTIONS/VARIABLES {{{1
let s:MODE_NAME = expand('<sfile>:t:r')
"
function s:getJumpsLines()
redir => result
:silent jumps
redir END
return split(result, "\n")
endfunction
"
function s:parseJumpsLine(line, bufnrPrev)
"return matchlist(a:line, '^\(.\)\s\+\(\d\+\)\s\(.*\)$')
let elements = matchlist(a:line, '\v^(.)\s*(\d+)\s+(\d+)\s+(\d+)\s*(.*)$')
if empty(elements)
return {}
endif
let linePrevBuffer = join(getbufline(a:bufnrPrev, elements[3]))
if stridx(linePrevBuffer, elements[5]) >= 0
let fname = bufname(a:bufnrPrev)
let text = elements[5]
else
let fname = elements[5]
let text = join(getbufline('^' . elements[5] . '$', elements[3]))
endif
return {
\ 'prefix': elements[1],
\ 'count' : elements[2],
\ 'lnum' : elements[3],
\ 'fname' : fname,
\ 'text' : printf('%s|%d:%d|%s', fname, elements[3], elements[4], text),
\ }
endfunction
"
function s:makeItem(line, bufnrPrev)
let parsed = s:parseJumpsLine(a:line, a:bufnrPrev)
if empty(parsed)
return {}
endif
let item = fuf#makeNonPathItem(parsed.text, '')
let item.abbrPrefix = parsed.prefix
let item.lnum = parsed.lnum
let item.fname = parsed.fname
return item
endfunction
" }}}1
"=============================================================================
" s:handler {{{1
let s:handler = {}
"
function s:handler.getModeName()
return s:MODE_NAME
endfunction
"
function s:handler.getPrompt()
return fuf#formatPrompt(g:fuf_jumplist_prompt, self.partialMatching, '')
endfunction
"
function s:handler.getPreviewHeight()
return g:fuf_previewHeight
endfunction
"
function s:handler.isOpenable(enteredPattern)
return 1
endfunction
"
function s:handler.makePatternSet(patternBase)
return fuf#makePatternSet(a:patternBase, 's:interpretPrimaryPatternForNonPath',
\ self.partialMatching)
endfunction
"
function s:handler.makePreviewLines(word, count)
let items = filter(copy(self.items), 'v:val.word ==# a:word')
if empty(items)
return []
endif
let lines = fuf#getFileLines(items[0].fname)
return fuf#makePreviewLinesAround(
\ lines, [items[0].lnum - 1], a:count, self.getPreviewHeight())
endfunction
"
function s:handler.getCompleteItems(patternPrimary)
return self.items
endfunction
"
function s:handler.onOpen(word, mode)
call fuf#prejump(a:mode)
let older = 0
for line in reverse(s:getJumpsLines())
if stridx(line, '>') == 0
let older = 1
endif
let parsed = s:parseJumpsLine(line, self.bufNrPrev)
if !empty(parsed) && parsed.text ==# a:word
if parsed.count != 0
execute 'normal! ' . parsed.count . (older ? "\<C-o>" : "\<C-i>") . 'zvzz'
endif
break
endif
endfor
endfunction
"
function s:handler.onModeEnterPre()
let self.items = s:getJumpsLines()
endfunction
"
function s:handler.onModeEnterPost()
call map(self.items, 's:makeItem(v:val, self.bufNrPrev)')
call filter(self.items, '!empty(v:val)')
call reverse(self.items)
call fuf#mapToSetSerialIndex(self.items, 1)
call map(self.items, 'fuf#setAbbrWithFormattedWord(v:val, 1)')
endfunction
"
function s:handler.onModeLeavePost(opened)
endfunction
" }}}1
"=============================================================================
" vim: set fdm=marker:

View file

@ -1,135 +0,0 @@
"=============================================================================
" Copyright (c) 2007-2010 Takeshi NISHIDA
"
"=============================================================================
" LOAD GUARD {{{1
if !l9#guardScriptLoading(expand('<sfile>:p'), 0, 0, [])
finish
endif
" }}}1
"=============================================================================
" GLOBAL FUNCTIONS {{{1
"
function fuf#line#createHandler(base)
return a:base.concretize(copy(s:handler))
endfunction
"
function fuf#line#getSwitchOrder()
return g:fuf_line_switchOrder
endfunction
"
function fuf#line#getEditableDataNames()
return []
endfunction
"
function fuf#line#renewCache()
endfunction
"
function fuf#line#requiresOnCommandPre()
return 0
endfunction
"
function fuf#line#onInit()
call fuf#defineLaunchCommand('FufLine', s:MODE_NAME, '""', [])
endfunction
" }}}1
"=============================================================================
" LOCAL FUNCTIONS/VARIABLES {{{1
let s:MODE_NAME = expand('<sfile>:t:r')
let s:OPEN_TYPE_DELETE = -1
" }}}1
"=============================================================================
" s:handler {{{1
let s:handler = {}
"
function s:handler.getModeName()
return s:MODE_NAME
endfunction
"
function s:handler.getPrompt()
return fuf#formatPrompt(g:fuf_line_prompt, self.partialMatching, '')
endfunction
"
function s:handler.getPreviewHeight()
return g:fuf_previewHeight
endfunction
"
function s:handler.isOpenable(enteredPattern)
return 1
endfunction
"
function s:handler.makePatternSet(patternBase)
return fuf#makePatternSet(a:patternBase, 's:interpretPrimaryPatternForNonPath',
\ self.partialMatching)
endfunction
"
function s:handler.makePreviewLines(word, count)
let items = filter(copy(self.items), 'v:val.word ==# a:word')
if empty(items)
return []
endif
let lines = fuf#getFileLines(self.bufNrPrev)
return fuf#makePreviewLinesAround(
\ lines, [items[0].index - 1], a:count, self.getPreviewHeight())
endfunction
"
function s:handler.getCompleteItems(patternPrimary)
return self.items
endfunction
"
function s:handler.onOpen(word, mode)
call fuf#prejump(a:mode)
call filter(self.items, 'v:val.word ==# a:word')
if empty(self.items)
return
execute 'cc ' . self.items[0].index
endif
call cursor(self.items[0].index, 0)
normal! zvzz
endfunction
"
function s:handler.onModeEnterPre()
endfunction
"
function s:handler.onModeEnterPost()
let tab = repeat(' ', getbufvar(self.bufNrPrev, '&tabstop'))
let self.items = getbufline(self.bufNrPrev, 1, '$')
let lnumFormat = '%' . len(string(len(self.items) + 1)) . 'd|'
for i in range(len(self.items))
let self.items[i] = printf(lnumFormat, i + 1)
\ . substitute(self.items[i], "\t", tab, 'g')
endfor
call map(self.items, 'fuf#makeNonPathItem(v:val, "")')
call fuf#mapToSetSerialIndex(self.items, 1)
call map(self.items, 'fuf#setAbbrWithFormattedWord(v:val, 0)')
endfunction
"
function s:handler.onModeLeavePost(opened)
endfunction
" }}}1
"=============================================================================
" vim: set fdm=marker:

View file

@ -1,134 +0,0 @@
"=============================================================================
" Copyright (c) 2007-2010 Takeshi NISHIDA
"
"=============================================================================
" LOAD GUARD {{{1
if !l9#guardScriptLoading(expand('<sfile>:p'), 0, 0, [])
finish
endif
" }}}1
"=============================================================================
" GLOBAL FUNCTIONS {{{1
"
function fuf#mrucmd#createHandler(base)
return a:base.concretize(copy(s:handler))
endfunction
"
function fuf#mrucmd#getSwitchOrder()
return g:fuf_mrucmd_switchOrder
endfunction
"
function fuf#mrucmd#getEditableDataNames()
return ['items']
endfunction
"
function fuf#mrucmd#renewCache()
endfunction
"
function fuf#mrucmd#requiresOnCommandPre()
return 1
endfunction
"
function fuf#mrucmd#onInit()
call fuf#defineLaunchCommand('FufMruCmd', s:MODE_NAME, '""', [])
endfunction
"
function fuf#mrucmd#onCommandPre(cmd)
if getcmdtype() =~# '^[:/?]'
call s:updateInfo(a:cmd)
endif
endfunction
" }}}1
"=============================================================================
" LOCAL FUNCTIONS/VARIABLES {{{1
let s:MODE_NAME = expand('<sfile>:t:r')
"
function s:updateInfo(cmd)
let items = fuf#loadDataFile(s:MODE_NAME, 'items')
let items = fuf#updateMruList(
\ items, { 'word' : a:cmd, 'time' : localtime() },
\ g:fuf_mrucmd_maxItem, g:fuf_mrucmd_exclude)
call fuf#saveDataFile(s:MODE_NAME, 'items', items)
endfunction
" }}}1
"=============================================================================
" s:handler {{{1
let s:handler = {}
"
function s:handler.getModeName()
return s:MODE_NAME
endfunction
"
function s:handler.getPrompt()
return fuf#formatPrompt(g:fuf_mrucmd_prompt, self.partialMatching, '')
endfunction
"
function s:handler.getPreviewHeight()
return 0
endfunction
"
function s:handler.isOpenable(enteredPattern)
return 1
endfunction
"
function s:handler.makePatternSet(patternBase)
return fuf#makePatternSet(a:patternBase, 's:interpretPrimaryPatternForNonPath',
\ self.partialMatching)
endfunction
"
function s:handler.makePreviewLines(word, count)
return []
endfunction
"
function s:handler.getCompleteItems(patternPrimary)
return self.items
endfunction
"
function s:handler.onOpen(word, mode)
call s:updateInfo(a:word)
call histadd(a:word[0], a:word[1:])
call feedkeys(a:word . "\<CR>", 'n')
endfunction
"
function s:handler.onModeEnterPre()
endfunction
"
function s:handler.onModeEnterPost()
let self.items = fuf#loadDataFile(s:MODE_NAME, 'items')
call map(self.items, 'fuf#makeNonPathItem(v:val.word, strftime(g:fuf_timeFormat, v:val.time))')
call fuf#mapToSetSerialIndex(self.items, 1)
call map(self.items, 'fuf#setAbbrWithFormattedWord(v:val, 1)')
endfunction
"
function s:handler.onModeLeavePost(opened)
endfunction
" }}}1
"=============================================================================
" vim: set fdm=marker:

View file

@ -1,234 +0,0 @@
"=============================================================================
" Copyright (c) 2007-2010 Takeshi NISHIDA
"
"=============================================================================
" LOAD GUARD {{{1
if !l9#guardScriptLoading(expand('<sfile>:p'), 0, 0, [])
finish
endif
" }}}1
"=============================================================================
" GLOBAL FUNCTIONS {{{1
"
function fuf#mrufile#createHandler(base)
return a:base.concretize(copy(s:handler))
endfunction
"
function fuf#mrufile#getSwitchOrder()
return g:fuf_mrufile_switchOrder
endfunction
"
function fuf#mrufile#getEditableDataNames()
return ['items', 'itemdirs']
endfunction
"
function fuf#mrufile#renewCache()
let s:cache = {}
let s:aroundCache = {}
endfunction
"
function fuf#mrufile#requiresOnCommandPre()
return 0
endfunction
"
function fuf#mrufile#onInit()
call fuf#defineLaunchCommand('FufMruFile', s:MODE_NAME, '""', [])
call fuf#defineLaunchCommand('FufMruFileInCwd', s:MODE_NAME,
\ '""', [['g:fuf_mrufile_underCwd', 1]])
call l9#defineVariableDefault('g:fuf_mrufile_underCwd', 0) " private option
call l9#defineVariableDefault('g:fuf_mrufile_searchAroundLevel', -1) " private option
augroup fuf#mrufile
autocmd!
autocmd BufEnter * call s:updateData()
autocmd BufWritePost * call s:updateData()
augroup END
endfunction
" }}}1
"=============================================================================
" LOCAL FUNCTIONS/VARIABLES {{{1
let s:MODE_NAME = expand('<sfile>:t:r')
let s:OPEN_TYPE_EXPAND = -1
"
function s:updateData()
if !empty(&buftype) || !filereadable(expand('%'))
return
endif
let items = fuf#loadDataFile(s:MODE_NAME, 'items')
let items = fuf#updateMruList(
\ items, { 'word' : expand('%:p'), 'time' : localtime() },
\ g:fuf_mrufile_maxItem, g:fuf_mrufile_exclude)
call fuf#saveDataFile(s:MODE_NAME, 'items', items)
call s:removeItemFromCache(expand('%:p'))
let itemDirs = fuf#loadDataFile(s:MODE_NAME, 'itemdirs')
let itemDirs = fuf#updateMruList(
\ itemDirs, { 'word' : expand('%:p:h') },
\ g:fuf_mrufile_maxItemDir, g:fuf_mrufile_exclude)
call fuf#saveDataFile(s:MODE_NAME, 'itemdirs', itemDirs)
endfunction
"
function s:removeItemFromCache(word)
for items in values(s:cache)
if exists('items[a:word]')
unlet items[a:word]
endif
endfor
endfunction
" returns empty value if invalid item
function s:formatItemUsingCache(item)
if a:item.word !~ '\S'
return {}
endif
if !exists('s:cache[a:item.word]')
if filereadable(a:item.word)
let s:cache[a:item.word] = fuf#makePathItem(
\ fnamemodify(a:item.word, ':p:~'), strftime(g:fuf_timeFormat, a:item.time), 0)
else
let s:cache[a:item.word] = {}
endif
endif
return s:cache[a:item.word]
endfunction
"
function s:expandSearchDir(dir, level)
let dirs = [a:dir]
let dirPrev = a:dir
for i in range(a:level)
let dirPrev = l9#concatPaths([dirPrev, '*'])
call add(dirs, dirPrev)
endfor
let dirPrev = a:dir
for i in range(a:level)
let dirPrevPrev = dirPrev
let dirPrev = fnamemodify(dirPrev, ':h')
if dirPrevPrev ==# dirPrev
break
endif
call add(dirs, dirPrev)
endfor
return dirs
endfunction
"
function s:listAroundFiles(dir)
if !exists('s:aroundCache[a:dir]')
let s:aroundCache[a:dir] = [a:dir] +
\ fuf#glob(l9#concatPaths([a:dir, '*' ])) +
\ fuf#glob(l9#concatPaths([a:dir, '.*']))
call filter(s:aroundCache[a:dir], 'filereadable(v:val)')
call map(s:aroundCache[a:dir], 'fuf#makePathItem(fnamemodify(v:val, ":~"), "", 0)')
if len(g:fuf_mrufile_exclude)
call filter(s:aroundCache[a:dir], 'v:val.word !~ g:fuf_mrufile_exclude')
endif
endif
return s:aroundCache[a:dir]
endfunction
" }}}1
"=============================================================================
" s:handler {{{1
let s:handler = {}
"
function s:handler.getModeName()
return s:MODE_NAME
endfunction
"
function s:handler.getPrompt()
let cwdString = (g:fuf_mrufile_underCwd ? '[CWD]' : '')
let levelString = (g:fuf_mrufile_searchAroundLevel < 0 ? ''
\ : '[Around:' . g:fuf_mrufile_searchAroundLevel . ']')
return fuf#formatPrompt(g:fuf_mrufile_prompt, self.partialMatching, cwdString . levelString)
endfunction
"
function s:handler.getPreviewHeight()
return g:fuf_previewHeight
endfunction
"
function s:handler.isOpenable(enteredPattern)
return 1
endfunction
"
function s:handler.makePatternSet(patternBase)
return fuf#makePatternSet(a:patternBase, 's:interpretPrimaryPatternForPath',
\ self.partialMatching)
endfunction
"
function s:handler.makePreviewLines(word, count)
return fuf#makePreviewLinesForFile(a:word, a:count, self.getPreviewHeight())
endfunction
"
function s:handler.getCompleteItems(patternPrimary)
return self.items
endfunction
"
function s:handler.onOpen(word, mode)
if a:mode ==# s:OPEN_TYPE_EXPAND
let nextLevel = (self.searchAroundLevel < 0 ? 0 : self.searchAroundLevel + 1)
call fuf#setOneTimeVariables(['g:fuf_mrufile_searchAroundLevel', nextLevel])
let self.reservedMode = self.getModeName()
return
else
call fuf#openFile(a:word, a:mode, g:fuf_reuseWindow)
endif
endfunction
"
function s:handler.onModeEnterPre()
endfunction
"
function s:handler.onModeEnterPost()
let self.searchAroundLevel = g:fuf_mrufile_searchAroundLevel
call fuf#defineKeyMappingInHandler(g:fuf_mrufile_keyExpand,
\ 'onCr(' . s:OPEN_TYPE_EXPAND . ')')
if self.searchAroundLevel < 0
let self.items = fuf#loadDataFile(s:MODE_NAME, 'items')
call map(self.items, 's:formatItemUsingCache(v:val)')
else
let self.items = fuf#loadDataFile(s:MODE_NAME, 'itemdirs')
call map(self.items, 's:expandSearchDir(v:val.word, g:fuf_mrufile_searchAroundLevel)')
let self.items = l9#concat(self.items)
let self.items = l9#unique(self.items)
call map(self.items, 's:listAroundFiles(v:val)')
let self.items = l9#concat(self.items)
endif
" NOTE: Comparing filenames is faster than bufnr('^' . fname . '$')
let bufNamePrev = fnamemodify(bufname(self.bufNrPrev), ':p:~')
call filter(self.items, '!empty(v:val) && v:val.word !=# bufNamePrev')
if g:fuf_mrufile_underCwd
let cwd = fnamemodify(getcwd(), ':p:~')
call filter(self.items, 'stridx(v:val.word, cwd) == 0')
endif
call fuf#mapToSetSerialIndex(self.items, 1)
call fuf#mapToSetAbbrWithSnippedWordAsPath(self.items)
endfunction
"
function s:handler.onModeLeavePost(opened)
endfunction
" }}}1
"=============================================================================
" vim: set fdm=marker:

View file

@ -1,154 +0,0 @@
"=============================================================================
" Copyright (c) 2007-2010 Takeshi NISHIDA
"
"=============================================================================
" LOAD GUARD {{{1
if !l9#guardScriptLoading(expand('<sfile>:p'), 0, 0, [])
finish
endif
" }}}1
"=============================================================================
" GLOBAL FUNCTIONS {{{1
"
function fuf#quickfix#createHandler(base)
return a:base.concretize(copy(s:handler))
endfunction
"
function fuf#quickfix#getSwitchOrder()
return g:fuf_quickfix_switchOrder
endfunction
"
function fuf#quickfix#getEditableDataNames()
return []
endfunction
"
function fuf#quickfix#renewCache()
endfunction
"
function fuf#quickfix#requiresOnCommandPre()
return 0
endfunction
"
function fuf#quickfix#onInit()
call fuf#defineLaunchCommand('FufQuickfix', s:MODE_NAME, '""', [])
endfunction
" }}}1
"=============================================================================
" LOCAL FUNCTIONS/VARIABLES {{{1
let s:MODE_NAME = expand('<sfile>:t:r')
"
function s:getJumpsLines()
redir => result
:silent jumps
redir END
return split(result, "\n")
endfunction
"
function s:parseJumpsLine(line)
return matchlist(a:line, '^\(.\)\s\+\(\d\+\)\s\(.*\)$')
endfunction
"
function s:makeItem(qfItem)
if !a:qfItem.valid
return {}
endif
let item = fuf#makeNonPathItem(
\ printf('%s|%d:%d|%s', bufname(a:qfItem.bufnr), a:qfItem.lnum,
\ a:qfItem.col, matchstr(a:qfItem.text, '\s*\zs.*\S'))
\ , '')
let item.bufnr = a:qfItem.bufnr
let item.lnum = a:qfItem.lnum
return item
endfunction
" }}}1
"=============================================================================
" s:handler {{{1
let s:handler = {}
"
function s:handler.getModeName()
return s:MODE_NAME
endfunction
"
function s:handler.getPrompt()
return fuf#formatPrompt(g:fuf_quickfix_prompt, self.partialMatching, '')
endfunction
"
function s:handler.getPreviewHeight()
return g:fuf_previewHeight
endfunction
"
function s:handler.isOpenable(enteredPattern)
return 1
endfunction
"
function s:handler.makePatternSet(patternBase)
return fuf#makePatternSet(a:patternBase, 's:interpretPrimaryPatternForNonPath',
\ self.partialMatching)
endfunction
"
function s:handler.makePreviewLines(word, count)
let items = filter(copy(self.items), 'v:val.word ==# a:word')
if empty(items)
return []
endif
let lines = fuf#getFileLines(items[0].bufnr)
return fuf#makePreviewLinesAround(
\ lines, [items[0].lnum - 1], a:count, self.getPreviewHeight())
endfunction
"
function s:handler.getCompleteItems(patternPrimary)
return self.items
endfunction
"
function s:handler.onOpen(word, mode)
call fuf#prejump(a:mode)
call filter(self.items, 'v:val.word ==# a:word')
if !empty(self.items)
execute 'cc ' . self.items[0].index
endif
endfunction
"
function s:handler.onModeEnterPre()
endfunction
"
function s:handler.onModeEnterPost()
let self.items = getqflist()
call map(self.items, 's:makeItem(v:val)')
call fuf#mapToSetSerialIndex(self.items, 1)
call filter(self.items, 'exists("v:val.word")')
call map(self.items, 'fuf#setAbbrWithFormattedWord(v:val, 1)')
endfunction
"
function s:handler.onModeLeavePost(opened)
endfunction
" }}}1
"=============================================================================
" vim: set fdm=marker:

Some files were not shown because too many files have changed in this diff Show more