command-t-standalone 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,29 @@
1
+ // Copyright 2010 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
+ #include <ruby.h>
25
+
26
+ extern VALUE CommandTMatch_initialize(int argc, VALUE *argv, VALUE self);
27
+ extern VALUE CommandTMatch_matches(VALUE self);
28
+ extern VALUE CommandTMatch_score(VALUE self);
29
+ extern VALUE CommandTMatch_to_s(VALUE self);
@@ -0,0 +1,445 @@
1
+ # Copyright 2010-2012 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
+ MH_START = '<commandt>'
33
+ MH_END = '</commandt>'
34
+ @@buffer = nil
35
+
36
+ def initialize options = {}
37
+ @prompt = options[:prompt]
38
+ @reverse_list = options[:match_window_reverse]
39
+ @min_height = options[:min_height]
40
+
41
+ # save existing window dimensions so we can restore them later
42
+ @windows = []
43
+ (0..(::VIM::Window.count - 1)).each do |i|
44
+ @windows << OpenStruct.new(:index => i,
45
+ :height => ::VIM::Window[i].height,
46
+ :width => ::VIM::Window[i].width)
47
+ end
48
+
49
+ # global settings (must manually save and restore)
50
+ @settings = Settings.new
51
+ ::VIM::set_option 'timeout' # ensure mappings timeout
52
+ ::VIM::set_option 'timeoutlen=0' # respond immediately to mappings
53
+ ::VIM::set_option 'nohlsearch' # don't highlight search strings
54
+ ::VIM::set_option 'noinsertmode' # don't make Insert mode the default
55
+ ::VIM::set_option 'noshowcmd' # don't show command info on last line
56
+ ::VIM::set_option 'report=9999' # don't show "X lines changed" reports
57
+ ::VIM::set_option 'sidescroll=0' # don't sidescroll in jumps
58
+ ::VIM::set_option 'sidescrolloff=0' # don't sidescroll automatically
59
+ ::VIM::set_option 'noequalalways' # don't auto-balance window sizes
60
+
61
+ # show match window
62
+ split_location = options[:match_window_at_top] ? 'topleft' : 'botright'
63
+ if @@buffer # still have buffer from last time
64
+ ::VIM::command "silent! #{split_location} #{@@buffer.number}sbuffer"
65
+ raise "Can't re-open GoToFile buffer" unless $curbuf.number == @@buffer.number
66
+ $curwin.height = 1
67
+ else # creating match window for first time and set it up
68
+ split_command = "silent! #{split_location} 1split GoToFile"
69
+ [
70
+ split_command,
71
+ 'setlocal bufhidden=unload', # unload buf when no longer displayed
72
+ 'setlocal buftype=nofile', # buffer is not related to any file
73
+ 'setlocal nomodifiable', # prevent manual edits
74
+ 'setlocal noswapfile', # don't create a swapfile
75
+ 'setlocal nowrap', # don't soft-wrap
76
+ 'setlocal nonumber', # don't show line numbers
77
+ 'setlocal nolist', # don't use List mode (visible tabs etc)
78
+ 'setlocal foldcolumn=0', # don't show a fold column at side
79
+ 'setlocal foldlevel=99', # don't fold anything
80
+ 'setlocal nocursorline', # don't highlight line cursor is on
81
+ 'setlocal nospell', # spell-checking off
82
+ 'setlocal nobuflisted', # don't show up in the buffer list
83
+ 'setlocal textwidth=0' # don't hard-wrap (break long lines)
84
+ ].each { |command| ::VIM::command command }
85
+
86
+ # don't show the color column
87
+ ::VIM::command 'setlocal colorcolumn=0' if VIM::exists?('+colorcolumn')
88
+
89
+ # don't show relative line numbers
90
+ ::VIM::command 'setlocal norelativenumber' if VIM::exists?('+relativenumber')
91
+
92
+ # sanity check: make sure the buffer really was created
93
+ raise "Can't find GoToFile buffer" unless $curbuf.name.match /GoToFile\z/
94
+ @@buffer = $curbuf
95
+ end
96
+
97
+ # syntax coloring
98
+ if VIM::has_syntax?
99
+ ::VIM::command "syntax match CommandTSelection \"^#{SELECTION_MARKER}.\\+$\""
100
+ ::VIM::command 'syntax match CommandTNoEntries "^-- NO MATCHES --$"'
101
+ ::VIM::command 'syntax match CommandTNoEntries "^-- NO SUCH FILE OR DIRECTORY --$"'
102
+ ::VIM::command 'setlocal synmaxcol=9999'
103
+
104
+ if VIM::has_conceal?
105
+ ::VIM::command 'setlocal conceallevel=2'
106
+ ::VIM::command 'setlocal concealcursor=nvic'
107
+ ::VIM::command 'syntax region CommandTCharMatched ' \
108
+ "matchgroup=CommandTCharMatched start=+#{MH_START}+ " \
109
+ "matchgroup=CommandTCharMatchedEnd end=+#{MH_END}+ concealends"
110
+ ::VIM::command 'highlight def CommandTCharMatched ' \
111
+ 'term=bold,underline cterm=bold,underline ' \
112
+ 'gui=bold,underline'
113
+ end
114
+
115
+ ::VIM::command 'highlight link CommandTSelection Visual'
116
+ ::VIM::command 'highlight link CommandTNoEntries Error'
117
+ ::VIM::evaluate 'clearmatches()'
118
+
119
+ # hide cursor
120
+ @cursor_highlight = get_cursor_highlight
121
+ hide_cursor
122
+ end
123
+
124
+ # perform cleanup using an autocmd to ensure we don't get caught out
125
+ # by some unexpected means of dismissing or leaving the Command-T window
126
+ # (eg. <C-W q>, <C-W k> etc)
127
+ ::VIM::command 'autocmd! * <buffer>'
128
+ ::VIM::command 'autocmd BufLeave <buffer> silent! ruby $command_t.leave'
129
+ ::VIM::command 'autocmd BufUnload <buffer> silent! ruby $command_t.unload'
130
+
131
+ @has_focus = false
132
+ @selection = nil
133
+ @abbrev = ''
134
+ @window = $curwin
135
+ end
136
+
137
+ def close
138
+ # Unlisted buffers like those provided by Netrw, NERDTree and Vim's help
139
+ # don't actually appear in the buffer list; if they are the only such
140
+ # buffers present when Command-T is invoked (for example, when invoked
141
+ # immediately after starting Vim with a directory argument, like `vim .`)
142
+ # then performing the normal clean-up will yield an "E90: Cannot unload
143
+ # last buffer" error. We can work around that by doing a :quit first.
144
+ if ::VIM::Buffer.count == 0
145
+ ::VIM::command 'silent quit'
146
+ end
147
+
148
+ # Workaround for upstream bug in Vim 7.3 on some platforms
149
+ #
150
+ # On some platforms, $curbuf.number always returns 0. One workaround is
151
+ # to build Vim with --disable-largefile, but as this is producing lots of
152
+ # support requests, implement the following fallback to the buffer name
153
+ # instead, at least until upstream gets fixed.
154
+ #
155
+ # For more details, see: https://wincent.com/issues/1617
156
+ if $curbuf.number == 0
157
+ # use bwipeout as bunload fails if passed the name of a hidden buffer
158
+ ::VIM::command 'silent! bwipeout! GoToFile'
159
+ @@buffer = nil
160
+ else
161
+ ::VIM::command "silent! bunload! #{@@buffer.number}"
162
+ end
163
+ end
164
+
165
+ def leave
166
+ close
167
+ unload
168
+ end
169
+
170
+ def unload
171
+ restore_window_dimensions
172
+ @settings.restore
173
+ @prompt.dispose
174
+ show_cursor
175
+ end
176
+
177
+ def add! char
178
+ @abbrev += char
179
+ end
180
+
181
+ def backspace!
182
+ @abbrev.chop!
183
+ end
184
+
185
+ def select_next
186
+ if @selection < @matches.length - 1
187
+ @selection += 1
188
+ print_match(@selection - 1) # redraw old selection (removes marker)
189
+ print_match(@selection) # redraw new selection (adds marker)
190
+ move_cursor_to_selected_line
191
+ else
192
+ # (possibly) loop or scroll
193
+ end
194
+ end
195
+
196
+ def select_prev
197
+ if @selection > 0
198
+ @selection -= 1
199
+ print_match(@selection + 1) # redraw old selection (removes marker)
200
+ print_match(@selection) # redraw new selection (adds marker)
201
+ move_cursor_to_selected_line
202
+ else
203
+ # (possibly) loop or scroll
204
+ end
205
+ end
206
+
207
+ def matches= matches
208
+ matches = matches.reverse if @reverse_list
209
+ if matches != @matches
210
+ @matches = matches
211
+ @selection = @reverse_list ? @matches.length - 1 : 0
212
+ print_matches
213
+ move_cursor_to_selected_line
214
+ end
215
+ end
216
+
217
+ def focus
218
+ unless @has_focus
219
+ @has_focus = true
220
+ if VIM::has_syntax?
221
+ ::VIM::command 'highlight link CommandTSelection Search'
222
+ end
223
+ end
224
+ end
225
+
226
+ def unfocus
227
+ if @has_focus
228
+ @has_focus = false
229
+ if VIM::has_syntax?
230
+ ::VIM::command 'highlight link CommandTSelection Visual'
231
+ end
232
+ end
233
+ end
234
+
235
+ def find char
236
+ # is this a new search or the continuation of a previous one?
237
+ now = Time.now
238
+ if @last_key_time.nil? or @last_key_time < (now - 0.5)
239
+ @find_string = char
240
+ else
241
+ @find_string += char
242
+ end
243
+ @last_key_time = now
244
+
245
+ # see if there's anything up ahead that matches
246
+ @matches.each_with_index do |match, idx|
247
+ if match[0, @find_string.length].casecmp(@find_string) == 0
248
+ old_selection = @selection
249
+ @selection = idx
250
+ print_match(old_selection) # redraw old selection (removes marker)
251
+ print_match(@selection) # redraw new selection (adds marker)
252
+ break
253
+ end
254
+ end
255
+ end
256
+
257
+ # Returns the currently selected item as a String.
258
+ def selection
259
+ @matches[@selection]
260
+ end
261
+
262
+ def print_no_such_file_or_directory
263
+ print_error 'NO SUCH FILE OR DIRECTORY'
264
+ end
265
+
266
+ private
267
+
268
+ def move_cursor_to_selected_line
269
+ # on some non-GUI terminals, the cursor doesn't hide properly
270
+ # so we move the cursor to prevent it from blinking away in the
271
+ # upper-left corner in a distracting fashion
272
+ @window.cursor = [@selection + 1, 0]
273
+ end
274
+
275
+ def print_error msg
276
+ return unless VIM::Window.select(@window)
277
+ unlock
278
+ clear
279
+ @window.height = @min_height > 0 ? @min_height : 1
280
+ @@buffer[1] = "-- #{msg} --"
281
+ lock
282
+ end
283
+
284
+ def restore_window_dimensions
285
+ # sort from tallest to shortest, tie-breaking on window width
286
+ @windows.sort! do |a, b|
287
+ order = b.height <=> a.height
288
+ if order.zero?
289
+ b.width <=> a.width
290
+ else
291
+ order
292
+ end
293
+ end
294
+
295
+ # starting with the tallest ensures that there are no constraints
296
+ # preventing windows on the side of vertical splits from regaining
297
+ # their original full size
298
+ @windows.each do |w|
299
+ # beware: window may be nil
300
+ if window = ::VIM::Window[w.index]
301
+ window.height = w.height
302
+ window.width = w.width
303
+ end
304
+ end
305
+ end
306
+
307
+ def match_text_for_idx idx
308
+ match = truncated_match @matches[idx].to_s
309
+ if idx == @selection
310
+ prefix = SELECTION_MARKER
311
+ suffix = padding_for_selected_match match
312
+ else
313
+ if VIM::has_syntax? && VIM::has_conceal?
314
+ match = match_with_syntax_highlight match
315
+ end
316
+ prefix = UNSELECTED_MARKER
317
+ suffix = ''
318
+ end
319
+ prefix + match + suffix
320
+ end
321
+
322
+ # Highlight matching characters within the matched string.
323
+ #
324
+ # Note that this is only approximate; it will highlight the first matching
325
+ # instances within the string, which may not actually be the instances that
326
+ # were used by the matching/scoring algorithm to determine the best score
327
+ # for the match.
328
+ #
329
+ def match_with_syntax_highlight match
330
+ highlight_chars = @prompt.abbrev.downcase.chars.to_a
331
+ match.chars.inject([]) do |output, char|
332
+ if char.downcase == highlight_chars.first
333
+ highlight_chars.shift
334
+ output.concat [MH_START, char, MH_END]
335
+ else
336
+ output << char
337
+ end
338
+ end.join
339
+ end
340
+
341
+ # Print just the specified match.
342
+ def print_match idx
343
+ return unless VIM::Window.select(@window)
344
+ unlock
345
+ @@buffer[idx + 1] = match_text_for_idx idx
346
+ lock
347
+ end
348
+
349
+ # Print all matches.
350
+ def print_matches
351
+ match_count = @matches.length
352
+ if match_count == 0
353
+ print_error 'NO MATCHES'
354
+ else
355
+ return unless VIM::Window.select(@window)
356
+ unlock
357
+ clear
358
+ actual_lines = 1
359
+ @window_width = @window.width # update cached value
360
+ max_lines = VIM::Screen.lines - 5
361
+ max_lines = 1 if max_lines < 0
362
+ actual_lines = match_count < @min_height ? @min_height : match_count
363
+ actual_lines = max_lines if actual_lines > max_lines
364
+ @window.height = actual_lines
365
+ (1..actual_lines).each do |line|
366
+ idx = line - 1
367
+ if @@buffer.count >= line
368
+ @@buffer[line] = match_text_for_idx idx
369
+ else
370
+ @@buffer.append line - 1, match_text_for_idx(idx)
371
+ end
372
+ end
373
+ lock
374
+ end
375
+ end
376
+
377
+ # Prepare padding for match text (trailing spaces) so that selection
378
+ # highlighting extends all the way to the right edge of the window.
379
+ def padding_for_selected_match str
380
+ len = str.length
381
+ if len >= @window_width - MARKER_LENGTH
382
+ ''
383
+ else
384
+ ' ' * (@window_width - MARKER_LENGTH - len)
385
+ end
386
+ end
387
+
388
+ # Convert "really/long/path" into "really...path" based on available
389
+ # window width.
390
+ def truncated_match str
391
+ len = str.length
392
+ available_width = @window_width - MARKER_LENGTH
393
+ return str if len <= available_width
394
+ left = (available_width / 2) - 1
395
+ right = (available_width / 2) - 2 + (available_width % 2)
396
+ str[0, left] + '...' + str[-right, right]
397
+ end
398
+
399
+ def clear
400
+ # range = % (whole buffer)
401
+ # action = d (delete)
402
+ # register = _ (black hole register, don't record deleted text)
403
+ ::VIM::command 'silent %d _'
404
+ end
405
+
406
+ def get_cursor_highlight
407
+ # there are 3 possible formats to check for, each needing to be
408
+ # transformed in a certain way in order to reapply the highlight:
409
+ # Cursor xxx guifg=bg guibg=fg -> :hi! Cursor guifg=bg guibg=fg
410
+ # Cursor xxx links to SomethingElse -> :hi! link Cursor SomethingElse
411
+ # Cursor xxx cleared -> :hi! clear Cursor
412
+ highlight = VIM::capture 'silent! 0verbose highlight Cursor'
413
+
414
+ if highlight =~ /^Cursor\s+xxx\s+links to (\w+)/
415
+ "link Cursor #{$~[1]}"
416
+ elsif highlight =~ /^Cursor\s+xxx\s+cleared/
417
+ 'clear Cursor'
418
+ elsif highlight =~ /Cursor\s+xxx\s+(.+)/
419
+ "Cursor #{$~[1]}"
420
+ else # likely cause E411 Cursor highlight group not found
421
+ nil
422
+ end
423
+ end
424
+
425
+ def hide_cursor
426
+ if @cursor_highlight
427
+ ::VIM::command 'highlight Cursor NONE'
428
+ end
429
+ end
430
+
431
+ def show_cursor
432
+ if @cursor_highlight
433
+ ::VIM::command "highlight #{@cursor_highlight}"
434
+ end
435
+ end
436
+
437
+ def lock
438
+ ::VIM::command 'setlocal nomodifiable'
439
+ end
440
+
441
+ def unlock
442
+ ::VIM::command 'setlocal modifiable'
443
+ end
444
+ end
445
+ end