| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- # 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.
- require 'command-t/finder/buffer_finder'
- require 'command-t/finder/jump_finder'
- require 'command-t/finder/file_finder'
- require 'command-t/finder/tag_finder'
- require 'command-t/match_window'
- require 'command-t/prompt'
- require 'command-t/vim/path_utilities'
- require 'command-t/util'
- module CommandT
- class Controller
- include VIM::PathUtilities
- 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_tag_finder
- @path = VIM::pwd
- @active_finder = tag_finder
- show
- end
- def show_file_finder
- # optional parameter will be desired starting directory, or ""
- @path = File.expand_path(::VIM::evaluate('a:arg'), VIM::pwd)
- @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.close
- 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 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,
- :limit => match_limit,
- :threads => CommandT::Util.processor_count
- )
- @match_window.matches = @matches
- @needs_update = false
- end
- private
- def list_matches!
- list_matches(:force => true)
- end
- def show
- @initial_window = $curwin
- @initial_buffer = $curbuf
- @match_window = MatchWindow.new \
- :highlight_color => get_string('g:CommandTHighlightColor'),
- :match_window_at_top => get_bool('g:CommandTMatchWindowAtTop'),
- :match_window_reverse => get_bool('g:CommandTMatchWindowReverse'),
- :min_height => min_height,
- :debounce_interval => 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 ||= get_number('g:CommandTMaxHeight', 0)
- end
- def min_height
- @min_height ||= begin
- min_height = get_number('g:CommandTMinHeight', 0)
- min_height = max_height if max_height != 0 && min_height > max_height
- min_height
- end
- end
- def get_number(name, default = nil)
- VIM::exists?(name) ? ::VIM::evaluate("#{name}").to_i : default
- end
- def get_bool(name)
- VIM::exists?(name) ? ::VIM::evaluate("#{name}").to_i != 0 : nil
- end
- def get_string(name)
- VIM::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 VIM::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
- # 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 default_open_command
- if !get_bool('&hidden') && get_bool('&modified')
- 'sp'
- else
- 'e'
- 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>',
- '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 = 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.
- def match_limit
- limit = VIM::Screen.lines - 5
- limit = 1 if limit < 0
- limit = [limit, max_height].min if max_height > 0
- limit
- end
- def buffer_finder
- @buffer_finder ||= CommandT::BufferFinder.new
- end
- def file_finder
- @file_finder ||= CommandT::FileFinder.new nil,
- :max_depth => get_number('g:CommandTMaxDepth'),
- :max_files => get_number('g:CommandTMaxFiles'),
- :max_caches => get_number('g:CommandTMaxCachedDirectories'),
- :always_show_dot_files => get_bool('g:CommandTAlwaysShowDotFiles'),
- :never_show_dot_files => get_bool('g:CommandTNeverShowDotFiles'),
- :scan_dot_directories => get_bool('g:CommandTScanDotDirectories'),
- :wild_ignore => get_string('g:CommandTWildIgnore'),
- :scanner => get_string('g:CommandTFileScanner')
- end
- def jump_finder
- @jump_finder ||= CommandT::JumpFinder.new
- end
- def tag_finder
- @tag_finder ||= CommandT::TagFinder.new \
- :include_filenames => get_bool('g:CommandTTagIncludeFilenames')
- end
- end # class Controller
- end # module commandT
|