# Copyright 2010-2011 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/file_finder' require 'command-t/match_window' require 'command-t/prompt' require 'command-t/vim/path_utilities' module CommandT class Controller include VIM::PathUtilities def initialize @prompt = Prompt.new @buffer_finder = CommandT::BufferFinder.new set_up_file_finder set_up_max_height end def show_buffer_finder @path = VIM::pwd @active_finder = @buffer_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) @file_finder.path = @path @active_finder = @file_finder 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 def flush set_up_max_height set_up_file_finder end def handle_key key = ::VIM::evaluate('a:arg').to_i.chr if @focus == @prompt @prompt.add! key list_matches else @match_window.find key end end def backspace if @focus == @prompt @prompt.backspace! list_matches end end def delete if @focus == @prompt @prompt.delete! list_matches 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 private def show @initial_window = $curwin @initial_buffer = $curbuf @match_window = MatchWindow.new \ :prompt => @prompt, :match_window_at_top => get_bool('g:CommandTMatchWindowAtTop'), :match_window_reverse => get_bool('g:CommandTMatchWindowReverse') @focus = @prompt @prompt.focus register_for_key_presses clear # clears prompt and lists matches end def set_up_max_height @max_height = get_number('g:CommandTMaxHeight') || 0 end def set_up_file_finder @file_finder = CommandT::FileFinder.new nil, :max_files => get_number('g:CommandTMaxFiles'), :max_depth => get_number('g:CommandTMaxDepth'), :always_show_dot_files => get_bool('g:CommandTAlwaysShowDotFiles'), :never_show_dot_files => get_bool('g:CommandTNeverShowDotFiles'), :scan_dot_directories => get_bool('g:CommandTScanDotDirectories') end def exists? name ::VIM::evaluate("exists(\"#{name}\")").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 # 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 ensure_appropriate_window_selection ::VIM::command "silent #{command} #{selection}" end def map key, function, param = nil ::VIM::command "noremap #{key} " \ ":call CommandT#{function}(#{param})" end def xterm? !!(::VIM::evaluate('&term') =~ /\Axterm/) end def vt100? !!(::VIM::evaluate('&term') =~ /\Avt100/) 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 "", 'HandleKey', b end # "special" keys (overridable by settings) { 'Backspace' => '', 'Delete' => '', 'AcceptSelection' => '', 'AcceptSelectionSplit' => ['', ''], 'AcceptSelectionTab' => '', 'AcceptSelectionVSplit' => '', 'ToggleFocus' => '', 'Cancel' => ['', ''], 'SelectNext' => ['', '', ''], 'SelectPrev' => ['', '', ''], 'Clear' => '', 'CursorLeft' => ['', ''], 'CursorRight' => ['', ''], 'CursorEnd' => '', 'CursorStart' => '' }.each do |key, value| if override = get_list_or_string("g:CommandT#{key}Map") [override].flatten.each do |mapping| map mapping, key end else [value].flatten.each do |mapping| map mapping, key unless mapping == '' && (xterm? || vt100?) end end 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 list_matches matches = @active_finder.sorted_matches_for @prompt.abbrev, :limit => match_limit @match_window.matches = matches end end # class Controller end # module commandT