]>
Commit | Line | Data |
---|---|---|
0d23b6e5 BB |
1 | # Copyright 2010-2011 Wincent Colaiuta. All rights reserved. |
2 | # | |
3 | # Redistribution and use in source and binary forms, with or without | |
4 | # modification, are permitted provided that the following conditions are met: | |
5 | # | |
6 | # 1. Redistributions of source code must retain the above copyright notice, | |
7 | # this list of conditions and the following disclaimer. | |
8 | # 2. Redistributions in binary form must reproduce the above copyright notice, | |
9 | # this list of conditions and the following disclaimer in the documentation | |
10 | # and/or other materials provided with the distribution. | |
11 | # | |
12 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
13 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
14 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
15 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE | |
16 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
17 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
18 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
19 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
20 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
21 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
22 | # POSSIBILITY OF SUCH DAMAGE. | |
23 | ||
24 | require 'command-t/finder/buffer_finder' | |
25 | require 'command-t/finder/file_finder' | |
26 | require 'command-t/match_window' | |
27 | require 'command-t/prompt' | |
28 | require 'command-t/vim/path_utilities' | |
29 | ||
30 | module CommandT | |
31 | class Controller | |
32 | include VIM::PathUtilities | |
33 | ||
34 | def initialize | |
35 | @prompt = Prompt.new | |
36 | @buffer_finder = CommandT::BufferFinder.new | |
37 | set_up_file_finder | |
38 | set_up_max_height | |
39 | end | |
40 | ||
41 | def show_buffer_finder | |
42 | @path = VIM::pwd | |
43 | @active_finder = @buffer_finder | |
44 | show | |
45 | end | |
46 | ||
47 | def show_file_finder | |
48 | # optional parameter will be desired starting directory, or "" | |
49 | @path = File.expand_path(::VIM::evaluate('a:arg'), VIM::pwd) | |
50 | @file_finder.path = @path | |
51 | @active_finder = @file_finder | |
52 | show | |
53 | rescue Errno::ENOENT | |
54 | # probably a problem with the optional parameter | |
55 | @match_window.print_no_such_file_or_directory | |
56 | end | |
57 | ||
58 | def hide | |
59 | @match_window.close | |
60 | if VIM::Window.select @initial_window | |
61 | if @initial_buffer.number == 0 | |
62 | # upstream bug: buffer number misreported as 0 | |
63 | # see: https://wincent.com/issues/1617 | |
64 | ::VIM::command "silent b #{@initial_buffer.name}" | |
65 | else | |
66 | ::VIM::command "silent b #{@initial_buffer.number}" | |
67 | end | |
68 | end | |
69 | end | |
70 | ||
71 | def flush | |
72 | set_up_max_height | |
73 | set_up_file_finder | |
74 | end | |
75 | ||
76 | def handle_key | |
77 | key = ::VIM::evaluate('a:arg').to_i.chr | |
78 | if @focus == @prompt | |
79 | @prompt.add! key | |
80 | list_matches | |
81 | else | |
82 | @match_window.find key | |
83 | end | |
84 | end | |
85 | ||
86 | def backspace | |
87 | if @focus == @prompt | |
88 | @prompt.backspace! | |
89 | list_matches | |
90 | end | |
91 | end | |
92 | ||
93 | def delete | |
94 | if @focus == @prompt | |
95 | @prompt.delete! | |
96 | list_matches | |
97 | end | |
98 | end | |
99 | ||
100 | def accept_selection options = {} | |
101 | selection = @match_window.selection | |
102 | hide | |
103 | open_selection(selection, options) unless selection.nil? | |
104 | end | |
105 | ||
106 | def toggle_focus | |
107 | @focus.unfocus # old focus | |
108 | @focus = @focus == @prompt ? @match_window : @prompt | |
109 | @focus.focus # new focus | |
110 | end | |
111 | ||
112 | def cancel | |
113 | hide | |
114 | end | |
115 | ||
116 | def select_next | |
117 | @match_window.select_next | |
118 | end | |
119 | ||
120 | def select_prev | |
121 | @match_window.select_prev | |
122 | end | |
123 | ||
124 | def clear | |
125 | @prompt.clear! | |
126 | list_matches | |
127 | end | |
128 | ||
129 | def cursor_left | |
130 | @prompt.cursor_left if @focus == @prompt | |
131 | end | |
132 | ||
133 | def cursor_right | |
134 | @prompt.cursor_right if @focus == @prompt | |
135 | end | |
136 | ||
137 | def cursor_end | |
138 | @prompt.cursor_end if @focus == @prompt | |
139 | end | |
140 | ||
141 | def cursor_start | |
142 | @prompt.cursor_start if @focus == @prompt | |
143 | end | |
144 | ||
145 | def leave | |
146 | @match_window.leave | |
147 | end | |
148 | ||
149 | def unload | |
150 | @match_window.unload | |
151 | end | |
152 | ||
153 | private | |
154 | ||
155 | def show | |
156 | @initial_window = $curwin | |
157 | @initial_buffer = $curbuf | |
158 | @match_window = MatchWindow.new \ | |
159 | :prompt => @prompt, | |
160 | :match_window_at_top => get_bool('g:CommandTMatchWindowAtTop'), | |
161 | :match_window_reverse => get_bool('g:CommandTMatchWindowReverse') | |
162 | @focus = @prompt | |
163 | @prompt.focus | |
164 | register_for_key_presses | |
165 | clear # clears prompt and lists matches | |
166 | end | |
167 | ||
168 | def set_up_max_height | |
169 | @max_height = get_number('g:CommandTMaxHeight') || 0 | |
170 | end | |
171 | ||
172 | def set_up_file_finder | |
173 | @file_finder = CommandT::FileFinder.new nil, | |
174 | :max_files => get_number('g:CommandTMaxFiles'), | |
175 | :max_depth => get_number('g:CommandTMaxDepth'), | |
176 | :always_show_dot_files => get_bool('g:CommandTAlwaysShowDotFiles'), | |
177 | :never_show_dot_files => get_bool('g:CommandTNeverShowDotFiles'), | |
178 | :scan_dot_directories => get_bool('g:CommandTScanDotDirectories') | |
179 | end | |
180 | ||
181 | def exists? name | |
182 | ::VIM::evaluate("exists(\"#{name}\")").to_i != 0 | |
183 | end | |
184 | ||
185 | def get_number name | |
186 | exists?(name) ? ::VIM::evaluate("#{name}").to_i : nil | |
187 | end | |
188 | ||
189 | def get_bool name | |
190 | exists?(name) ? ::VIM::evaluate("#{name}").to_i != 0 : nil | |
191 | end | |
192 | ||
193 | def get_string name | |
194 | exists?(name) ? ::VIM::evaluate("#{name}").to_s : nil | |
195 | end | |
196 | ||
197 | # expect a string or a list of strings | |
198 | def get_list_or_string name | |
199 | return nil unless exists?(name) | |
200 | list_or_string = ::VIM::evaluate("#{name}") | |
201 | if list_or_string.kind_of?(Array) | |
202 | list_or_string.map { |item| item.to_s } | |
203 | else | |
204 | list_or_string.to_s | |
205 | end | |
206 | end | |
207 | ||
208 | # Backslash-escape space, \, |, %, #, " | |
209 | def sanitize_path_string str | |
210 | # for details on escaping command-line mode arguments see: :h : | |
211 | # (that is, help on ":") in the Vim documentation. | |
212 | str.gsub(/[ \\|%#"]/, '\\\\\0') | |
213 | end | |
214 | ||
215 | def default_open_command | |
216 | if !get_bool('&hidden') && get_bool('&modified') | |
217 | 'sp' | |
218 | else | |
219 | 'e' | |
220 | end | |
221 | end | |
222 | ||
223 | def ensure_appropriate_window_selection | |
224 | # normally we try to open the selection in the current window, but there | |
225 | # is one exception: | |
226 | # | |
227 | # - we don't touch any "unlisted" buffer with buftype "nofile" (such as | |
228 | # NERDTree or MiniBufExplorer); this is to avoid things like the "Not | |
229 | # enough room" error which occurs when trying to open in a split in a | |
230 | # shallow (potentially 1-line) buffer like MiniBufExplorer is current | |
231 | # | |
232 | # Other "unlisted" buffers, such as those with buftype "help" are treated | |
233 | # normally. | |
234 | initial = $curwin | |
235 | while true do | |
236 | break unless ::VIM::evaluate('&buflisted').to_i == 0 && | |
237 | ::VIM::evaluate('&buftype').to_s == 'nofile' | |
238 | ::VIM::command 'wincmd w' # try next window | |
239 | break if $curwin == initial # have already tried all | |
240 | end | |
241 | end | |
242 | ||
243 | def open_selection selection, options = {} | |
244 | command = options[:command] || default_open_command | |
245 | selection = File.expand_path selection, @path | |
246 | selection = relative_path_under_working_directory selection | |
247 | selection = sanitize_path_string selection | |
248 | ensure_appropriate_window_selection | |
249 | ::VIM::command "silent #{command} #{selection}" | |
250 | end | |
251 | ||
252 | def map key, function, param = nil | |
253 | ::VIM::command "noremap <silent> <buffer> #{key} " \ | |
254 | ":call CommandT#{function}(#{param})<CR>" | |
255 | end | |
256 | ||
257 | def xterm? | |
258 | !!(::VIM::evaluate('&term') =~ /\Axterm/) | |
259 | end | |
260 | ||
261 | def vt100? | |
262 | !!(::VIM::evaluate('&term') =~ /\Avt100/) | |
263 | end | |
264 | ||
265 | def register_for_key_presses | |
266 | # "normal" keys (interpreted literally) | |
267 | numbers = ('0'..'9').to_a.join | |
268 | lowercase = ('a'..'z').to_a.join | |
269 | uppercase = lowercase.upcase | |
270 | punctuation = '<>`@#~!"$%&/()=+*-_.,;:?\\\'{}[] ' # and space | |
271 | (numbers + lowercase + uppercase + punctuation).each_byte do |b| | |
272 | map "<Char-#{b}>", 'HandleKey', b | |
273 | end | |
274 | ||
275 | # "special" keys (overridable by settings) | |
276 | { 'Backspace' => '<BS>', | |
277 | 'Delete' => '<Del>', | |
278 | 'AcceptSelection' => '<CR>', | |
279 | 'AcceptSelectionSplit' => ['<C-CR>', '<C-s>'], | |
280 | 'AcceptSelectionTab' => '<C-t>', | |
281 | 'AcceptSelectionVSplit' => '<C-v>', | |
282 | 'ToggleFocus' => '<Tab>', | |
283 | 'Cancel' => ['<C-c>', '<Esc>'], | |
284 | 'SelectNext' => ['<C-n>', '<C-j>', '<Down>'], | |
285 | 'SelectPrev' => ['<C-p>', '<C-k>', '<Up>'], | |
286 | 'Clear' => '<C-u>', | |
287 | 'CursorLeft' => ['<Left>', '<C-h>'], | |
288 | 'CursorRight' => ['<Right>', '<C-l>'], | |
289 | 'CursorEnd' => '<C-e>', | |
290 | 'CursorStart' => '<C-a>' }.each do |key, value| | |
291 | if override = get_list_or_string("g:CommandT#{key}Map") | |
292 | [override].flatten.each do |mapping| | |
293 | map mapping, key | |
294 | end | |
295 | else | |
296 | [value].flatten.each do |mapping| | |
297 | map mapping, key unless mapping == '<Esc>' && (xterm? || vt100?) | |
298 | end | |
299 | end | |
300 | end | |
301 | end | |
302 | ||
303 | # Returns the desired maximum number of matches, based on available | |
304 | # vertical space and the g:CommandTMaxHeight option. | |
305 | def match_limit | |
306 | limit = VIM::Screen.lines - 5 | |
307 | limit = 1 if limit < 0 | |
308 | limit = [limit, @max_height].min if @max_height > 0 | |
309 | limit | |
310 | end | |
311 | ||
312 | def list_matches | |
313 | matches = @active_finder.sorted_matches_for @prompt.abbrev, :limit => match_limit | |
314 | @match_window.matches = matches | |
315 | end | |
316 | end # class Controller | |
317 | end # module commandT |