]> git.r.bdr.sh - rbdr/dotfiles/blob - vim/tmp/command_t/ruby/command-t/match_window.rb
Relative line numbers for vim
[rbdr/dotfiles] / vim / tmp / command_t / ruby / command-t / match_window.rb
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 'ostruct'
25 require 'command-t/settings'
26
27 module CommandT
28 class MatchWindow
29 @@selection_marker = '> '
30 @@marker_length = @@selection_marker.length
31 @@unselected_marker = ' ' * @@marker_length
32 @@buffer = nil
33
34 def initialize options = {}
35 @prompt = options[:prompt]
36 @reverse_list = options[:match_window_reverse]
37
38 # save existing window dimensions so we can restore them later
39 @windows = []
40 (0..(::VIM::Window.count - 1)).each do |i|
41 window = OpenStruct.new :index => i, :height => ::VIM::Window[i].height
42 @windows << window
43 end
44
45 # global settings (must manually save and restore)
46 @settings = Settings.new
47 ::VIM::set_option 'timeout' # ensure mappings timeout
48 ::VIM::set_option 'timeoutlen=0' # respond immediately to mappings
49 ::VIM::set_option 'nohlsearch' # don't highlight search strings
50 ::VIM::set_option 'noinsertmode' # don't make Insert mode the default
51 ::VIM::set_option 'noshowcmd' # don't show command info on last line
52 ::VIM::set_option 'report=9999' # don't show "X lines changed" reports
53 ::VIM::set_option 'sidescroll=0' # don't sidescroll in jumps
54 ::VIM::set_option 'sidescrolloff=0' # don't sidescroll automatically
55 ::VIM::set_option 'noequalalways' # don't auto-balance window sizes
56
57 # show match window
58 split_location = options[:match_window_at_top] ? 'topleft' : 'botright'
59 if @@buffer # still have buffer from last time
60 ::VIM::command "silent! #{split_location} #{@@buffer.number}sbuffer"
61 raise "Can't re-open GoToFile buffer" unless $curbuf.number == @@buffer.number
62 $curwin.height = 1
63 else # creating match window for first time and set it up
64 split_command = "silent! #{split_location} 1split GoToFile"
65 [
66 split_command,
67 'setlocal bufhidden=unload', # unload buf when no longer displayed
68 'setlocal buftype=nofile', # buffer is not related to any file
69 'setlocal nomodifiable', # prevent manual edits
70 'setlocal noswapfile', # don't create a swapfile
71 'setlocal nowrap', # don't soft-wrap
72 'setlocal nonumber', # don't show line numbers
73 'setlocal nolist', # don't use List mode (visible tabs etc)
74 'setlocal foldcolumn=0', # don't show a fold column at side
75 'setlocal foldlevel=99', # don't fold anything
76 'setlocal nocursorline', # don't highlight line cursor is on
77 'setlocal nospell', # spell-checking off
78 'setlocal nobuflisted', # don't show up in the buffer list
79 'setlocal textwidth=0' # don't hard-wrap (break long lines)
80 ].each { |command| ::VIM::command command }
81
82 # sanity check: make sure the buffer really was created
83 raise "Can't find GoToFile buffer" unless $curbuf.name.match /GoToFile\z/
84 @@buffer = $curbuf
85 end
86
87 # syntax coloring
88 if VIM::has_syntax?
89 ::VIM::command "syntax match CommandTSelection \"^#{@@selection_marker}.\\+$\""
90 ::VIM::command 'syntax match CommandTNoEntries "^-- NO MATCHES --$"'
91 ::VIM::command 'syntax match CommandTNoEntries "^-- NO SUCH FILE OR DIRECTORY --$"'
92 ::VIM::command 'highlight link CommandTSelection Visual'
93 ::VIM::command 'highlight link CommandTNoEntries Error'
94 ::VIM::evaluate 'clearmatches()'
95
96 # hide cursor
97 @cursor_highlight = get_cursor_highlight
98 hide_cursor
99 end
100
101 # perform cleanup using an autocmd to ensure we don't get caught out
102 # by some unexpected means of dismissing or leaving the Command-T window
103 # (eg. <C-W q>, <C-W k> etc)
104 ::VIM::command 'autocmd! * <buffer>'
105 ::VIM::command 'autocmd BufLeave <buffer> ruby $command_t.leave'
106 ::VIM::command 'autocmd BufUnload <buffer> ruby $command_t.unload'
107
108 @has_focus = false
109 @selection = nil
110 @abbrev = ''
111 @window = $curwin
112 end
113
114 def close
115 # Workaround for upstream bug in Vim 7.3 on some platforms
116 #
117 # On some platforms, $curbuf.number always returns 0. One workaround is
118 # to build Vim with --disable-largefile, but as this is producing lots of
119 # support requests, implement the following fallback to the buffer name
120 # instead, at least until upstream gets fixed.
121 #
122 # For more details, see: https://wincent.com/issues/1617
123 if $curbuf.number == 0
124 # use bwipeout as bunload fails if passed the name of a hidden buffer
125 ::VIM::command 'bwipeout! GoToFile'
126 @@buffer = nil
127 else
128 ::VIM::command "bunload! #{@@buffer.number}"
129 end
130 end
131
132 def leave
133 close
134 unload
135 end
136
137 def unload
138 restore_window_dimensions
139 @settings.restore
140 @prompt.dispose
141 show_cursor
142 end
143
144 def add! char
145 @abbrev += char
146 end
147
148 def backspace!
149 @abbrev.chop!
150 end
151
152 def select_next
153 if @selection < @matches.length - 1
154 @selection += 1
155 print_match(@selection - 1) # redraw old selection (removes marker)
156 print_match(@selection) # redraw new selection (adds marker)
157 move_cursor_to_selected_line
158 else
159 # (possibly) loop or scroll
160 end
161 end
162
163 def select_prev
164 if @selection > 0
165 @selection -= 1
166 print_match(@selection + 1) # redraw old selection (removes marker)
167 print_match(@selection) # redraw new selection (adds marker)
168 move_cursor_to_selected_line
169 else
170 # (possibly) loop or scroll
171 end
172 end
173
174 def matches= matches
175 matches = matches.reverse if @reverse_list
176 if matches != @matches
177 @matches = matches
178 @selection = @reverse_list ? @matches.length - 1 : 0
179 print_matches
180 move_cursor_to_selected_line
181 end
182 end
183
184 def focus
185 unless @has_focus
186 @has_focus = true
187 if VIM::has_syntax?
188 ::VIM::command 'highlight link CommandTSelection Search'
189 end
190 end
191 end
192
193 def unfocus
194 if @has_focus
195 @has_focus = false
196 if VIM::has_syntax?
197 ::VIM::command 'highlight link CommandTSelection Visual'
198 end
199 end
200 end
201
202 def find char
203 # is this a new search or the continuation of a previous one?
204 now = Time.now
205 if @last_key_time.nil? or @last_key_time < (now - 0.5)
206 @find_string = char
207 else
208 @find_string += char
209 end
210 @last_key_time = now
211
212 # see if there's anything up ahead that matches
213 @matches.each_with_index do |match, idx|
214 if match[0, @find_string.length].casecmp(@find_string) == 0
215 old_selection = @selection
216 @selection = idx
217 print_match(old_selection) # redraw old selection (removes marker)
218 print_match(@selection) # redraw new selection (adds marker)
219 break
220 end
221 end
222 end
223
224 # Returns the currently selected item as a String.
225 def selection
226 @matches[@selection]
227 end
228
229 def print_no_such_file_or_directory
230 print_error 'NO SUCH FILE OR DIRECTORY'
231 end
232
233 private
234
235 def move_cursor_to_selected_line
236 # on some non-GUI terminals, the cursor doesn't hide properly
237 # so we move the cursor to prevent it from blinking away in the
238 # upper-left corner in a distracting fashion
239 @window.cursor = [@selection + 1, 0]
240 end
241
242 def print_error msg
243 return unless VIM::Window.select(@window)
244 unlock
245 clear
246 @window.height = 1
247 @@buffer[1] = "-- #{msg} --"
248 lock
249 end
250
251 def restore_window_dimensions
252 # sort from tallest to shortest
253 @windows.sort! { |a, b| b.height <=> a.height }
254
255 # starting with the tallest ensures that there are no constraints
256 # preventing windows on the side of vertical splits from regaining
257 # their original full size
258 @windows.each do |w|
259 # beware: window may be nil
260 window = ::VIM::Window[w.index]
261 window.height = w.height if window
262 end
263 end
264
265 def match_text_for_idx idx
266 match = truncated_match @matches[idx]
267 if idx == @selection
268 prefix = @@selection_marker
269 suffix = padding_for_selected_match match
270 else
271 prefix = @@unselected_marker
272 suffix = ''
273 end
274 prefix + match + suffix
275 end
276
277 # Print just the specified match.
278 def print_match idx
279 return unless VIM::Window.select(@window)
280 unlock
281 @@buffer[idx + 1] = match_text_for_idx idx
282 lock
283 end
284
285 # Print all matches.
286 def print_matches
287 match_count = @matches.length
288 if match_count == 0
289 print_error 'NO MATCHES'
290 else
291 return unless VIM::Window.select(@window)
292 unlock
293 clear
294 actual_lines = 1
295 @window_width = @window.width # update cached value
296 max_lines = VIM::Screen.lines - 5
297 max_lines = 1 if max_lines < 0
298 actual_lines = match_count > max_lines ? max_lines : match_count
299 @window.height = actual_lines
300 (1..actual_lines).each do |line|
301 idx = line - 1
302 if @@buffer.count >= line
303 @@buffer[line] = match_text_for_idx idx
304 else
305 @@buffer.append line - 1, match_text_for_idx(idx)
306 end
307 end
308 lock
309 end
310 end
311
312 # Prepare padding for match text (trailing spaces) so that selection
313 # highlighting extends all the way to the right edge of the window.
314 def padding_for_selected_match str
315 len = str.length
316 if len >= @window_width - @@marker_length
317 ''
318 else
319 ' ' * (@window_width - @@marker_length - len)
320 end
321 end
322
323 # Convert "really/long/path" into "really...path" based on available
324 # window width.
325 def truncated_match str
326 len = str.length
327 available_width = @window_width - @@marker_length
328 return str if len <= available_width
329 left = (available_width / 2) - 1
330 right = (available_width / 2) - 2 + (available_width % 2)
331 str[0, left] + '...' + str[-right, right]
332 end
333
334 def clear
335 # range = % (whole buffer)
336 # action = d (delete)
337 # register = _ (black hole register, don't record deleted text)
338 ::VIM::command 'silent %d _'
339 end
340
341 def get_cursor_highlight
342 # as :highlight returns nothing and only prints,
343 # must redirect its output to a variable
344 ::VIM::command 'silent redir => g:command_t_cursor_highlight'
345
346 # force 0 verbosity to ensure origin information isn't printed as well
347 ::VIM::command 'silent! 0verbose highlight Cursor'
348 ::VIM::command 'silent redir END'
349
350 # there are 3 possible formats to check for, each needing to be
351 # transformed in a certain way in order to reapply the highlight:
352 # Cursor xxx guifg=bg guibg=fg -> :hi! Cursor guifg=bg guibg=fg
353 # Cursor xxx links to SomethingElse -> :hi! link Cursor SomethingElse
354 # Cursor xxx cleared -> :hi! clear Cursor
355 highlight = ::VIM::evaluate 'g:command_t_cursor_highlight'
356 if highlight =~ /^Cursor\s+xxx\s+links to (\w+)/
357 "link Cursor #{$~[1]}"
358 elsif highlight =~ /^Cursor\s+xxx\s+cleared/
359 'clear Cursor'
360 elsif highlight =~ /Cursor\s+xxx\s+(.+)/
361 "Cursor #{$~[1]}"
362 else # likely cause E411 Cursor highlight group not found
363 nil
364 end
365 end
366
367 def hide_cursor
368 if @cursor_highlight
369 ::VIM::command 'highlight Cursor NONE'
370 end
371 end
372
373 def show_cursor
374 if @cursor_highlight
375 ::VIM::command "highlight #{@cursor_highlight}"
376 end
377 end
378
379 def lock
380 ::VIM::command 'setlocal nomodifiable'
381 end
382
383 def unlock
384 ::VIM::command 'setlocal modifiable'
385 end
386 end
387 end