diff --git a/autoload/commandt.vim b/autoload/commandt.vim new file mode 100644 index 00000000..2505fe5a --- /dev/null +++ b/autoload/commandt.vim @@ -0,0 +1,193 @@ +" Copyright 2010-2014 Wincent Colaiuta. 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. + +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 + +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 => 'tabe' +endfunction + +function CommandTAcceptSelectionSplit() + ruby $command_t.accept_selection :command => 'sp' +endfunction + +function CommandTAcceptSelectionVSplit() + ruby $command_t.accept_selection :command => 'vs' +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 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/vim' + require 'command-t/controller' + require 'command-t/mru' + $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) and 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 + require 'command-t/stub' + $command_t = CommandT::Stub.new + end +EOF diff --git a/data/benchmark.yml b/data/benchmark.yml index e0d2e247..7d6cff2f 100644 --- a/data/benchmark.yml +++ b/data/benchmark.yml @@ -1,9 +1,24 @@ --- tests: - name: pathological - times: 20 + times: 200 paths: - - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + - aaaaaaaaaaaa + - aaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa queries: - a - aaa diff --git a/doc/command-t.txt b/doc/command-t.txt index 29f2ee44..11433191 100644 --- a/doc/command-t.txt +++ b/doc/command-t.txt @@ -369,18 +369,30 @@ paths which begin with the same prefix. COMMANDS *command-t-commands* *:CommandT* -|:CommandT| Brings up the Command-T file window, starting in the +|:CommandT| Brings up the Command-T file window, starting in the current working directory as returned by the|:pwd| command. *:CommandTBuffer* -|:CommandTBuffer|Brings up the Command-T buffer window. +|:CommandTBuffer| Brings up the Command-T buffer window. This works exactly like the standard file window, except that the selection is limited to files that you already have open in buffers. + *:CommandTMRU* +|:CommandTMRU| Brings up the Command-T buffer window, except that matches + are shown in MRU (most recently used) order. If you prefer to + use this over the normal buffer finder, I suggest overwriting + the standard mapping with a command like: + + :nnoremap b :CommandTMRU + + Note that Command-T only starts recording most recently used + buffers when you first use a Command-T command or mapping; + this is an optimization to improve startup time. + *:CommandTJumps* -|:CommandTJump| Brings up the Command-T jumplist window. +|:CommandTJump| Brings up the Command-T jumplist window. This works exactly like the standard file window, except that the selection is limited to files that you already have in the jumplist. Note that jumps @@ -388,13 +400,13 @@ COMMANDS *command-t-commands* documentation for more info). *:CommandTTag* -|:CommandTTag| Brings up the Command-T window tags window, which can +|:CommandTTag| Brings up the Command-T window tags window, which can be used to select from the tags, if any, returned by Vim's |taglist()| function. See Vim's |tag| documentation for general info on tags. *:CommandTFlush* -|:CommandTFlush|Instructs the plug-in to flush its path cache, causing +|:CommandTFlush| Instructs the plug-in to flush its path cache, causing the directory to be rescanned for new or deleted paths the next time the file window is shown (pressing when a match listing is visible flushes the cache immediately; this @@ -742,6 +754,19 @@ To disable flow control, add the following to your `.zshrc` or See the `stty` man page for more details. +Why doesn't the Escape key close the match listing in terminal Vim? ~ + +In some terminals such as xterm the Escape key misbehaves, so Command-T +doesn't set up a mapping for it. If you want to try using the escape key +anyway, you can add something like the following to your ~/.vimrc file: + + if &term =~ "xterm" || &term =~ "screen" + let g:CommandTCancelMap = ['', ''] + endif + +This configuration has worked for me with recent versions of Vim on multiple +platforms (OS X, CentOS etc). + TIPS *command-t-tips* @@ -863,17 +888,18 @@ Command-T is written and maintained by Wincent Colaiuta . Other contributors that have submitted patches include (in alphabetical order): - Andy Waite Mike Lundy Shlomi Fish - Anthony Panozzo Nadav Samet Steven Moazami - Daniel Hahler Nate Kane Sung Pae - Felix Tjandrawibawa Nicholas Alpi Thomas Pelletier - Gary Bernhardt Noon Silk Victor Hugo Borja - Ivan Ukhov Paul Jolly Vít Ondruch - Jeff Kreeftmeijer Pavel Sergeev Woody Peterson - Lucas de Vries Rainux Luo Yan Pritzker - Marcus Brito Roland Puntaier Yiding Jia + Andy Waite Nadav Samet Steven Moazami + Anthony Panozzo Nate Kane Sung Pae + Daniel Hahler Nicholas Alpi Thomas Pelletier + Felix Tjandrawibawa Noon Silk Ton van den Heuvel + Gary Bernhardt Paul Jolly Victor Hugo Borja + Ivan Ukhov Pavel Sergeev Vít Ondruch + Jeff Kreeftmeijer Rainux Luo Woody Peterson + Lucas de Vries Roland Puntaier Yan Pritzker + Marcus Brito Ross Lagerwall Yiding Jia Marian Schubert Scott Bronson Zak Johnson Matthew Todd Seth Fowler + Mike Lundy Shlomi Fish As this was the first Vim plug-in I had ever written I was heavily influenced by the design of the LustyExplorer plug-in by Stephen Bach, which I understand @@ -976,6 +1002,18 @@ POSSIBILITY OF SUCH DAMAGE. HISTORY *command-t-history* +1.9 (not yet released) + +- improved startup time using Vim's autload mechanism (patch from Ross + Lagerwall) +- added MRU (most-recently-used) buffer finder (patch from Ton van den Heuvel) +- fixed edge case in matching algorithm which could cause spurious matches + with queries containing repeated characters +- fixed slight positive bias in the match scoring algorithm's weighting of + matching characters based on distance from last match +- tune memoization in match scoring algorithm, yield a more than 10% speed + boost + 1.8 (31 March 2014) - taught Watchman file scanner to use the binary protocol instead of JSON, diff --git a/plugin/command-t.vim b/plugin/command-t.vim index 6a4a9113..af23e0ef 100644 --- a/plugin/command-t.vim +++ b/plugin/command-t.vim @@ -1,4 +1,3 @@ -" command-t.vim " Copyright 2010-2014 Wincent Colaiuta. All rights reserved. " " Redistribution and use in source and binary forms, with or without @@ -27,168 +26,21 @@ if exists("g:command_t_loaded") || &cp endif let g:command_t_loaded = 1 -command CommandTBuffer call CommandTShowBufferFinder() -command CommandTJump call CommandTShowJumpFinder() -command CommandTTag call CommandTShowTagFinder() -command -nargs=? -complete=dir CommandT call CommandTShowFileFinder() -command CommandTFlush call CommandTFlush() +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() +command CommandTFlush call commandt#CommandTFlush() -if !hasmapto(':CommandT') +if !hasmapto(':CommandT') && maparg('t', 'n') == '' silent! nnoremap t :CommandT endif -if !hasmapto(':CommandTBuffer') +if !hasmapto(':CommandTBuffer') && maparg('b', 'n') == '' silent! nnoremap b :CommandTBuffer endif -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 s:CommandTShowBufferFinder() - if has('ruby') - ruby $command_t.show_buffer_finder - else - call s:CommandTRubyWarning() - endif -endfunction - -function s:CommandTShowFileFinder(arg) - if has('ruby') - ruby $command_t.show_file_finder - else - call s:CommandTRubyWarning() - endif -endfunction - -function s:CommandTShowJumpFinder() - if has('ruby') - ruby $command_t.show_jump_finder - else - call s:CommandTRubyWarning() - endif -endfunction - -function s:CommandTShowTagFinder() - if has('ruby') - ruby $command_t.show_tag_finder - else - call s:CommandTRubyWarning() - endif -endfunction - -function s:CommandTFlush() - if has('ruby') - ruby $command_t.flush - else - 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 => 'tabe' -endfunction - -function CommandTAcceptSelectionSplit() - ruby $command_t.accept_selection :command => 'sp' -endfunction - -function CommandTAcceptSelectionVSplit() - ruby $command_t.accept_selection :command => 'vs' -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 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 - -ruby << EOF - # require Ruby files - begin - # prepare controller - require 'command-t/vim' - require 'command-t/controller' - $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) and 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 - require 'command-t/stub' - $command_t = CommandT::Stub.new - end -EOF diff --git a/ruby/command-t/controller.rb b/ruby/command-t/controller.rb index 3193fbec..4a28b24c 100644 --- a/ruby/command-t/controller.rb +++ b/ruby/command-t/controller.rb @@ -24,6 +24,7 @@ require 'command-t/finder/buffer_finder' require 'command-t/finder/jump_finder' require 'command-t/finder/file_finder' +require 'command-t/finder/mru_buffer_finder' require 'command-t/finder/tag_finder' require 'command-t/match_window' require 'command-t/prompt' @@ -50,6 +51,12 @@ def show_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 @@ -372,6 +379,10 @@ def buffer_finder @buffer_finder ||= CommandT::BufferFinder.new end + def mru_finder + @mru_finder ||= CommandT::MRUBufferFinder.new + end + def file_finder @file_finder ||= CommandT::FileFinder.new nil, :max_depth => get_number('g:CommandTMaxDepth'), @@ -393,4 +404,4 @@ def tag_finder :include_filenames => get_bool('g:CommandTTagIncludeFilenames') end end # class Controller -end # module commandT +end # module CommandT diff --git a/ruby/command-t/finder/mru_buffer_finder.rb b/ruby/command-t/finder/mru_buffer_finder.rb new file mode 100644 index 00000000..98f67547 --- /dev/null +++ b/ruby/command-t/finder/mru_buffer_finder.rb @@ -0,0 +1,50 @@ +# Copyright 2014 Wincent Colaiuta. 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. + +require 'command-t/ext' # CommandT::Matcher +require 'command-t/scanner/mru_buffer_scanner' +require 'command-t/finder/buffer_finder' + +module CommandT + class MRUBufferFinder < BufferFinder + # 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.stack.last && + relative_path_under_working_directory(MRU.stack.last.name) == matches.first + matches[1..-1] + [matches.first] + else + matches + end + end + + def initialize + @scanner = MRUBufferScanner.new + @matcher = Matcher.new @scanner, :always_show_dot_files => true + end + end # class MRUBufferFinder +end # CommandT diff --git a/ruby/command-t/match.c b/ruby/command-t/match.c index df7a6460..7e716e20 100644 --- a/ruby/command-t/match.c +++ b/ruby/command-t/match.c @@ -51,9 +51,10 @@ double recursive_match(matchinfo_t *m, // sharable meta-data long i, j, distance; int found; double score_for_char; + long memo_idx = haystack_idx; // do we have a memoized result we can return? - double memoized = m->memo[needle_idx * m->needle_len + haystack_idx]; + double memoized = m->memo[needle_idx * m->needle_len + memo_idx]; if (memoized != DBL_MAX) return memoized; @@ -125,7 +126,7 @@ double recursive_match(matchinfo_t *m, // sharable meta-data } score += score_for_char; - last_idx = ++haystack_idx; + last_idx = haystack_idx++; break; } } @@ -144,7 +145,7 @@ double recursive_match(matchinfo_t *m, // sharable meta-data score = score > seen_score ? score : seen_score; memoize: - m->memo[needle_idx * m->needle_len + haystack_idx] = score; + m->memo[needle_idx * m->needle_len + memo_idx] = score; return score; } diff --git a/ruby/command-t/matcher.c b/ruby/command-t/matcher.c index 1fcc844c..8f7a0369 100644 --- a/ruby/command-t/matcher.c +++ b/ruby/command-t/matcher.c @@ -132,6 +132,7 @@ 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; @@ -143,6 +144,7 @@ VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self) VALUE paths; VALUE results; VALUE scanner; + VALUE sort_option; VALUE threads_option; // process arguments: 1 mandatory, 1 optional @@ -157,6 +159,7 @@ VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self) // check optional options has for overrides limit_option = CommandT_option_from_hash("limit", options); threads_option = CommandT_option_from_hash("threads", options); + sort_option = CommandT_option_from_hash("sort", options); // get unsorted matches scanner = rb_iv_get(self, "@scanner"); @@ -175,7 +178,7 @@ VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self) #define THREAD_THRESHOLD 1000 /* avoid the overhead of threading when search space is small */ if (path_count < THREAD_THRESHOLD) thread_count = 1; - pthread_t *threads = malloc(sizeof(pthread_t) * thread_count); + threads = malloc(sizeof(pthread_t) * thread_count); if (!threads) rb_raise(rb_eNoMemError, "memory allocation failed"); #endif @@ -216,13 +219,15 @@ VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self) free(threads); #endif - 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); + 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(); diff --git a/ruby/command-t/mru.rb b/ruby/command-t/mru.rb new file mode 100644 index 00000000..fad80a27 --- /dev/null +++ b/ruby/command-t/mru.rb @@ -0,0 +1,58 @@ +# Copyright 2014 Wincent Colaiuta. 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. + +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 + + # 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 for the correct buffer number. + stack.delete_if do |b| + b.number == ::VIM::evaluate('expand("")').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 diff --git a/ruby/command-t/scanner/mru_buffer_scanner.rb b/ruby/command-t/scanner/mru_buffer_scanner.rb new file mode 100644 index 00000000..6da28556 --- /dev/null +++ b/ruby/command-t/scanner/mru_buffer_scanner.rb @@ -0,0 +1,48 @@ +# Copyright 2014 Wincent Colaiuta. 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. + +require 'command-t/vim/path_utilities' +require 'command-t/scanner/buffer_scanner' + +module CommandT + # Returns a list of all open buffers, sorted in MRU order. + class MRUBufferScanner < BufferScanner + include VIM::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 # module CommandT diff --git a/ruby/command-t/stub.rb b/ruby/command-t/stub.rb index 4628b61f..bf0bd47b 100644 --- a/ruby/command-t/stub.rb +++ b/ruby/command-t/stub.rb @@ -1,4 +1,4 @@ -# Copyright 2010-2011 Wincent Colaiuta. All rights reserved. +# Copyright 2010-2014 Wincent Colaiuta. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: @@ -25,10 +25,18 @@ module CommandT class Stub @@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#{RUBY_PATCHLEVEL}", 'For more information type: :help command-t'] - [:flush, :show_buffer_finder, :show_file_finder, :show_tag_finder].each do |method| - define_method(method.to_sym) { warn *@@load_error } + [ + :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 diff --git a/ruby/command-t/util.rb b/ruby/command-t/util.rb index 87362dfd..e9532f24 100644 --- a/ruby/command-t/util.rb +++ b/ruby/command-t/util.rb @@ -111,4 +111,4 @@ def processor_count! end end end # module Util -end # module commandT +end # module CommandT diff --git a/ruby/command-t/watchman.c b/ruby/command-t/watchman.c index ce18e9a8..e274f762 100644 --- a/ruby/command-t/watchman.c +++ b/ruby/command-t/watchman.c @@ -297,6 +297,8 @@ int64_t watchman_load_int(char **ptr, char *end) { * 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"); } @@ -310,14 +312,14 @@ VALUE watchman_load_string(char **ptr, char *end) { rb_raise(rb_eArgError, "invalid string header"); } - int64_t len = watchman_load_int(ptr, end); + 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"); } - VALUE string = rb_str_new(*ptr, len); + string = rb_str_new(*ptr, len); *ptr += len; return string; } @@ -327,11 +329,12 @@ VALUE watchman_load_string(char **ptr, char *end) { * 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"); } - double val = *(double *)*ptr; + val = *(double *)*ptr; *ptr += sizeof(double); return val; } @@ -496,10 +499,14 @@ VALUE watchman_load(char **ptr, char *end) { * 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); - long len = RSTRING_LEN(serialized); - char *ptr = RSTRING_PTR(serialized); - char *end = ptr + len; + 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) { @@ -512,21 +519,29 @@ VALUE CommandTWatchmanUtils_load(VALUE self, VALUE serialized) { // get size marker ptr += sizeof(WATCHMAN_BINARY_MARKER) - 1; - uint64_t payload_size = watchman_load_int(&ptr, end); + 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)", end - (ptr + payload_size)); + rb_raise( + rb_eArgError, + "payload size mismatch (%lu)", + (unsigned long)(end - (ptr + payload_size)) + ); } - VALUE loaded = watchman_load(&ptr, end); + loaded = watchman_load(&ptr, end); // one more sanity check if (ptr != end) { - rb_raise(rb_eArgError, "payload termination mismatch (%lu)", end - ptr); + rb_raise( + rb_eArgError, + "payload termination mismatch (%lu)", + (unsigned long)(end - ptr) + ); } return loaded; @@ -542,15 +557,17 @@ VALUE CommandTWatchmanUtils_load(VALUE self, VALUE serialized) { * (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 - uint64_t *len = (uint64_t *)(w->data + sizeof(WATCHMAN_HEADER) - sizeof(uint64_t) - 1); + len = (uint64_t *)(w->data + sizeof(WATCHMAN_HEADER) - sizeof(uint64_t) - 1); *len = w->len - sizeof(WATCHMAN_HEADER) + 1; // prepare final return value - VALUE serialized = rb_str_buf_new(w->len); + serialized = rb_str_buf_new(w->len); rb_str_buf_cat(serialized, (const char*)w->data, w->len); watchman_free(w); return serialized; @@ -582,18 +599,29 @@ void watchman_raise_system_call_error(int number) { * returns the result. */ VALUE CommandTWatchmanUtils_query(VALUE self, VALUE query, VALUE socket) { - int fileno = NUM2INT(rb_funcall(socket, rb_intern("fileno"), 0)); + 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 - int flags = fcntl(fileno, F_GETFL); + 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 - VALUE serialized = CommandTWatchmanUtils_dump(self, query); - long query_len = RSTRING_LEN(serialized); - ssize_t sent = send(fileno, RSTRING_PTR(serialized), query_len, 0); + 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) { @@ -602,8 +630,7 @@ VALUE CommandTWatchmanUtils_query(VALUE self, VALUE query, VALUE socket) { } // sniff to see how large the header is - int8_t peek[WATCHMAN_PEEK_BUFFER_SIZE]; - ssize_t received = recv(fileno, peek, WATCHMAN_SNIFF_BUFFER_SIZE, MSG_PEEK | MSG_WAITALL); + 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) { @@ -611,9 +638,12 @@ VALUE CommandTWatchmanUtils_query(VALUE self, VALUE query, VALUE socket) { } // peek at size of PDU - int8_t sizes[] = { 0, 0, 0, 1, 2, 4, 8 }; - ssize_t peek_size = sizeof(WATCHMAN_BINARY_MARKER) - 1 + sizeof(int8_t) + - sizes[peek[sizeof(WATCHMAN_BINARY_MARKER) - 1]]; + 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) { @@ -621,15 +651,19 @@ VALUE CommandTWatchmanUtils_query(VALUE self, VALUE query, VALUE socket) { } else if (received != peek_size) { rb_raise(rb_eRuntimeError, "failed to peek at PDU header"); } - int8_t *pdu_size_ptr = peek + sizeof(WATCHMAN_BINARY_MARKER) - sizeof(int8_t); - int64_t payload_size = + 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 - void *buffer = xmalloc(payload_size); + buffer = xmalloc(payload_size); if (!buffer) { - rb_raise(rb_eNoMemError, "failed to allocate %lld bytes", payload_size); + 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) { @@ -637,8 +671,8 @@ VALUE CommandTWatchmanUtils_query(VALUE self, VALUE query, VALUE socket) { } else if (received != payload_size) { rb_raise(rb_eRuntimeError, "failed to load PDU"); } - char *payload = buffer + peek_size; - VALUE loaded = watchman_load(&payload, payload + payload_size); + payload = (char *)buffer + peek_size; + loaded = watchman_load(&payload, payload + payload_size); free(buffer); return loaded; }