command-t 1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +5 -0
- data/LICENSE +22 -0
- data/README.txt +779 -0
- data/Rakefile +217 -0
- data/doc/README.txt +779 -0
- data/doc/command-t.txt +736 -0
- data/plugin/command-t.vim +164 -0
- data/ruby/command-t/Makefile +181 -0
- data/ruby/command-t/controller.rb +317 -0
- data/ruby/command-t/depend +24 -0
- data/ruby/command-t/ext.c +65 -0
- data/ruby/command-t/ext.h +36 -0
- data/ruby/command-t/extconf.rb +32 -0
- data/ruby/command-t/finder.rb +52 -0
- data/ruby/command-t/finder/buffer_finder.rb +35 -0
- data/ruby/command-t/finder/file_finder.rb +35 -0
- data/ruby/command-t/match.c +189 -0
- data/ruby/command-t/match.h +29 -0
- data/ruby/command-t/match_window.rb +377 -0
- data/ruby/command-t/matcher.c +164 -0
- data/ruby/command-t/matcher.h +30 -0
- data/ruby/command-t/prompt.rb +165 -0
- data/ruby/command-t/ruby_compat.h +49 -0
- data/ruby/command-t/scanner.rb +28 -0
- data/ruby/command-t/scanner/buffer_scanner.rb +42 -0
- data/ruby/command-t/scanner/file_scanner.rb +94 -0
- data/ruby/command-t/settings.rb +77 -0
- data/ruby/command-t/stub.rb +46 -0
- data/ruby/command-t/vim.rb +43 -0
- data/ruby/command-t/vim/path_utilities.rb +40 -0
- data/ruby/command-t/vim/screen.rb +32 -0
- data/ruby/command-t/vim/window.rb +38 -0
- metadata +97 -0
@@ -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,377 @@
|
|
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
|
+
else
|
158
|
+
# (possibly) loop or scroll
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def select_prev
|
163
|
+
if @selection > 0
|
164
|
+
@selection -= 1
|
165
|
+
print_match(@selection + 1) # redraw old selection (removes marker)
|
166
|
+
print_match(@selection) # redraw new selection (adds marker)
|
167
|
+
else
|
168
|
+
# (possibly) loop or scroll
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def matches= matches
|
173
|
+
matches = matches.reverse if @reverse_list
|
174
|
+
if matches != @matches
|
175
|
+
@matches = matches
|
176
|
+
@selection = @reverse_list ? @matches.length - 1 : 0
|
177
|
+
print_matches
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def focus
|
182
|
+
unless @has_focus
|
183
|
+
@has_focus = true
|
184
|
+
if VIM::has_syntax?
|
185
|
+
::VIM::command 'highlight link CommandTSelection Search'
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def unfocus
|
191
|
+
if @has_focus
|
192
|
+
@has_focus = false
|
193
|
+
if VIM::has_syntax?
|
194
|
+
::VIM::command 'highlight link CommandTSelection Visual'
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def find char
|
200
|
+
# is this a new search or the continuation of a previous one?
|
201
|
+
now = Time.now
|
202
|
+
if @last_key_time.nil? or @last_key_time < (now - 0.5)
|
203
|
+
@find_string = char
|
204
|
+
else
|
205
|
+
@find_string += char
|
206
|
+
end
|
207
|
+
@last_key_time = now
|
208
|
+
|
209
|
+
# see if there's anything up ahead that matches
|
210
|
+
@matches.each_with_index do |match, idx|
|
211
|
+
if match[0, @find_string.length].casecmp(@find_string) == 0
|
212
|
+
old_selection = @selection
|
213
|
+
@selection = idx
|
214
|
+
print_match(old_selection) # redraw old selection (removes marker)
|
215
|
+
print_match(@selection) # redraw new selection (adds marker)
|
216
|
+
break
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# Returns the currently selected item as a String.
|
222
|
+
def selection
|
223
|
+
@matches[@selection]
|
224
|
+
end
|
225
|
+
|
226
|
+
def print_no_such_file_or_directory
|
227
|
+
print_error 'NO SUCH FILE OR DIRECTORY'
|
228
|
+
end
|
229
|
+
|
230
|
+
private
|
231
|
+
|
232
|
+
def print_error msg
|
233
|
+
return unless VIM::Window.select(@window)
|
234
|
+
unlock
|
235
|
+
clear
|
236
|
+
@window.height = 1
|
237
|
+
@@buffer[1] = "-- #{msg} --"
|
238
|
+
lock
|
239
|
+
end
|
240
|
+
|
241
|
+
def restore_window_dimensions
|
242
|
+
# sort from tallest to shortest
|
243
|
+
@windows.sort! { |a, b| b.height <=> a.height }
|
244
|
+
|
245
|
+
# starting with the tallest ensures that there are no constraints
|
246
|
+
# preventing windows on the side of vertical splits from regaining
|
247
|
+
# their original full size
|
248
|
+
@windows.each do |w|
|
249
|
+
# beware: window may be nil
|
250
|
+
window = ::VIM::Window[w.index]
|
251
|
+
window.height = w.height if window
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def match_text_for_idx idx
|
256
|
+
match = truncated_match @matches[idx]
|
257
|
+
if idx == @selection
|
258
|
+
prefix = @@selection_marker
|
259
|
+
suffix = padding_for_selected_match match
|
260
|
+
else
|
261
|
+
prefix = @@unselected_marker
|
262
|
+
suffix = ''
|
263
|
+
end
|
264
|
+
prefix + match + suffix
|
265
|
+
end
|
266
|
+
|
267
|
+
# Print just the specified match.
|
268
|
+
def print_match idx
|
269
|
+
return unless VIM::Window.select(@window)
|
270
|
+
unlock
|
271
|
+
@@buffer[idx + 1] = match_text_for_idx idx
|
272
|
+
lock
|
273
|
+
end
|
274
|
+
|
275
|
+
# Print all matches.
|
276
|
+
def print_matches
|
277
|
+
match_count = @matches.length
|
278
|
+
if match_count == 0
|
279
|
+
print_error 'NO MATCHES'
|
280
|
+
else
|
281
|
+
return unless VIM::Window.select(@window)
|
282
|
+
unlock
|
283
|
+
clear
|
284
|
+
actual_lines = 1
|
285
|
+
@window_width = @window.width # update cached value
|
286
|
+
max_lines = VIM::Screen.lines - 5
|
287
|
+
max_lines = 1 if max_lines < 0
|
288
|
+
actual_lines = match_count > max_lines ? max_lines : match_count
|
289
|
+
@window.height = actual_lines
|
290
|
+
(1..actual_lines).each do |line|
|
291
|
+
idx = line - 1
|
292
|
+
if @@buffer.count >= line
|
293
|
+
@@buffer[line] = match_text_for_idx idx
|
294
|
+
else
|
295
|
+
@@buffer.append line - 1, match_text_for_idx(idx)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
lock
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
# Prepare padding for match text (trailing spaces) so that selection
|
303
|
+
# highlighting extends all the way to the right edge of the window.
|
304
|
+
def padding_for_selected_match str
|
305
|
+
len = str.length
|
306
|
+
if len >= @window_width - @@marker_length
|
307
|
+
''
|
308
|
+
else
|
309
|
+
' ' * (@window_width - @@marker_length - len)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
# Convert "really/long/path" into "really...path" based on available
|
314
|
+
# window width.
|
315
|
+
def truncated_match str
|
316
|
+
len = str.length
|
317
|
+
available_width = @window_width - @@marker_length
|
318
|
+
return str if len <= available_width
|
319
|
+
left = (available_width / 2) - 1
|
320
|
+
right = (available_width / 2) - 2 + (available_width % 2)
|
321
|
+
str[0, left] + '...' + str[-right, right]
|
322
|
+
end
|
323
|
+
|
324
|
+
def clear
|
325
|
+
# range = % (whole buffer)
|
326
|
+
# action = d (delete)
|
327
|
+
# register = _ (black hole register, don't record deleted text)
|
328
|
+
::VIM::command 'silent %d _'
|
329
|
+
end
|
330
|
+
|
331
|
+
def get_cursor_highlight
|
332
|
+
# as :highlight returns nothing and only prints,
|
333
|
+
# must redirect its output to a variable
|
334
|
+
::VIM::command 'silent redir => g:command_t_cursor_highlight'
|
335
|
+
|
336
|
+
# force 0 verbosity to ensure origin information isn't printed as well
|
337
|
+
::VIM::command 'silent! 0verbose highlight Cursor'
|
338
|
+
::VIM::command 'silent redir END'
|
339
|
+
|
340
|
+
# there are 3 possible formats to check for, each needing to be
|
341
|
+
# transformed in a certain way in order to reapply the highlight:
|
342
|
+
# Cursor xxx guifg=bg guibg=fg -> :hi! Cursor guifg=bg guibg=fg
|
343
|
+
# Cursor xxx links to SomethingElse -> :hi! link Cursor SomethingElse
|
344
|
+
# Cursor xxx cleared -> :hi! clear Cursor
|
345
|
+
highlight = ::VIM::evaluate 'g:command_t_cursor_highlight'
|
346
|
+
if highlight =~ /^Cursor\s+xxx\s+links to (\w+)/
|
347
|
+
"link Cursor #{$~[1]}"
|
348
|
+
elsif highlight =~ /^Cursor\s+xxx\s+cleared/
|
349
|
+
'clear Cursor'
|
350
|
+
elsif highlight =~ /Cursor\s+xxx\s+(.+)/
|
351
|
+
"Cursor #{$~[1]}"
|
352
|
+
else # likely cause E411 Cursor highlight group not found
|
353
|
+
nil
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
def hide_cursor
|
358
|
+
if @cursor_highlight
|
359
|
+
::VIM::command 'highlight Cursor NONE'
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
def show_cursor
|
364
|
+
if @cursor_highlight
|
365
|
+
::VIM::command "highlight #{@cursor_highlight}"
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
def lock
|
370
|
+
::VIM::command 'setlocal nomodifiable'
|
371
|
+
end
|
372
|
+
|
373
|
+
def unlock
|
374
|
+
::VIM::command 'setlocal modifiable'
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
@@ -0,0 +1,164 @@
|
|
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 <stdlib.h> /* for qsort() */
|
25
|
+
#include <string.h> /* for strcmp() */
|
26
|
+
#include "matcher.h"
|
27
|
+
#include "ext.h"
|
28
|
+
#include "ruby_compat.h"
|
29
|
+
|
30
|
+
// comparison function for use with qsort
|
31
|
+
int comp_alpha(const void *a, const void *b)
|
32
|
+
{
|
33
|
+
VALUE a_val = *(VALUE *)a;
|
34
|
+
VALUE b_val = *(VALUE *)b;
|
35
|
+
ID to_s = rb_intern("to_s");
|
36
|
+
|
37
|
+
VALUE a_str = rb_funcall(a_val, to_s, 0);
|
38
|
+
VALUE b_str = rb_funcall(b_val, to_s, 0);
|
39
|
+
char *a_p = RSTRING_PTR(a_str);
|
40
|
+
long a_len = RSTRING_LEN(a_str);
|
41
|
+
char *b_p = RSTRING_PTR(b_str);
|
42
|
+
long b_len = RSTRING_LEN(b_str);
|
43
|
+
int order = 0;
|
44
|
+
if (a_len > b_len)
|
45
|
+
{
|
46
|
+
order = strncmp(a_p, b_p, b_len);
|
47
|
+
if (order == 0)
|
48
|
+
order = 1; // shorter string (b) wins
|
49
|
+
}
|
50
|
+
else if (a_len < b_len)
|
51
|
+
{
|
52
|
+
order = strncmp(a_p, b_p, a_len);
|
53
|
+
if (order == 0)
|
54
|
+
order = -1; // shorter string (a) wins
|
55
|
+
}
|
56
|
+
else
|
57
|
+
order = strncmp(a_p, b_p, a_len);
|
58
|
+
return order;
|
59
|
+
}
|
60
|
+
|
61
|
+
// comparison function for use with qsort
|
62
|
+
int comp_score(const void *a, const void *b)
|
63
|
+
{
|
64
|
+
VALUE a_val = *(VALUE *)a;
|
65
|
+
VALUE b_val = *(VALUE *)b;
|
66
|
+
ID score = rb_intern("score");
|
67
|
+
double a_score = RFLOAT_VALUE(rb_funcall(a_val, score, 0));
|
68
|
+
double b_score = RFLOAT_VALUE(rb_funcall(b_val, score, 0));
|
69
|
+
if (a_score > b_score)
|
70
|
+
return -1; // a scores higher, a should appear sooner
|
71
|
+
else if (a_score < b_score)
|
72
|
+
return 1; // b scores higher, a should appear later
|
73
|
+
else
|
74
|
+
return comp_alpha(a, b);
|
75
|
+
}
|
76
|
+
|
77
|
+
VALUE CommandTMatcher_initialize(int argc, VALUE *argv, VALUE self)
|
78
|
+
{
|
79
|
+
// process arguments: 1 mandatory, 1 optional
|
80
|
+
VALUE scanner, options;
|
81
|
+
if (rb_scan_args(argc, argv, "11", &scanner, &options) == 1)
|
82
|
+
options = Qnil;
|
83
|
+
if (NIL_P(scanner))
|
84
|
+
rb_raise(rb_eArgError, "nil scanner");
|
85
|
+
rb_iv_set(self, "@scanner", scanner);
|
86
|
+
|
87
|
+
// check optional options hash for overrides
|
88
|
+
VALUE always_show_dot_files = CommandT_option_from_hash("always_show_dot_files", options);
|
89
|
+
if (always_show_dot_files != Qtrue)
|
90
|
+
always_show_dot_files = Qfalse;
|
91
|
+
VALUE never_show_dot_files = CommandT_option_from_hash("never_show_dot_files", options);
|
92
|
+
if (never_show_dot_files != Qtrue)
|
93
|
+
never_show_dot_files = Qfalse;
|
94
|
+
rb_iv_set(self, "@always_show_dot_files", always_show_dot_files);
|
95
|
+
rb_iv_set(self, "@never_show_dot_files", never_show_dot_files);
|
96
|
+
return Qnil;
|
97
|
+
}
|
98
|
+
|
99
|
+
VALUE CommandTMatcher_sorted_matches_for(VALUE self, VALUE abbrev, VALUE options)
|
100
|
+
{
|
101
|
+
// process optional options hash
|
102
|
+
VALUE limit_option = CommandT_option_from_hash("limit", options);
|
103
|
+
|
104
|
+
// get unsorted matches
|
105
|
+
VALUE matches = CommandTMatcher_matches_for(self, abbrev);
|
106
|
+
|
107
|
+
abbrev = StringValue(abbrev);
|
108
|
+
if (RSTRING_LEN(abbrev) == 0 ||
|
109
|
+
(RSTRING_LEN(abbrev) == 1 && RSTRING_PTR(abbrev)[0] == '.'))
|
110
|
+
// alphabetic order if search string is only "" or "."
|
111
|
+
qsort(RARRAY_PTR(matches), RARRAY_LEN(matches), sizeof(VALUE), comp_alpha);
|
112
|
+
else
|
113
|
+
// for all other non-empty search strings, sort by score
|
114
|
+
qsort(RARRAY_PTR(matches), RARRAY_LEN(matches), sizeof(VALUE), comp_score);
|
115
|
+
|
116
|
+
// apply optional limit option
|
117
|
+
long limit = NIL_P(limit_option) ? 0 : NUM2LONG(limit_option);
|
118
|
+
if (limit == 0 || RARRAY_LEN(matches) < limit)
|
119
|
+
limit = RARRAY_LEN(matches);
|
120
|
+
|
121
|
+
// will return an array of strings, not an array of Match objects
|
122
|
+
for (long i = 0; i < limit; i++)
|
123
|
+
{
|
124
|
+
VALUE str = rb_funcall(RARRAY_PTR(matches)[i], rb_intern("to_s"), 0);
|
125
|
+
RARRAY_PTR(matches)[i] = str;
|
126
|
+
}
|
127
|
+
|
128
|
+
// trim off any items beyond the limit
|
129
|
+
if (limit < RARRAY_LEN(matches))
|
130
|
+
(void)rb_funcall(matches, rb_intern("slice!"), 2, LONG2NUM(limit),
|
131
|
+
LONG2NUM(RARRAY_LEN(matches) - limit));
|
132
|
+
return matches;
|
133
|
+
}
|
134
|
+
|
135
|
+
VALUE CommandTMatcher_matches_for(VALUE self, VALUE abbrev)
|
136
|
+
{
|
137
|
+
if (NIL_P(abbrev))
|
138
|
+
rb_raise(rb_eArgError, "nil abbrev");
|
139
|
+
VALUE matches = rb_ary_new();
|
140
|
+
VALUE scanner = rb_iv_get(self, "@scanner");
|
141
|
+
VALUE always_show_dot_files = rb_iv_get(self, "@always_show_dot_files");
|
142
|
+
VALUE never_show_dot_files = rb_iv_get(self, "@never_show_dot_files");
|
143
|
+
VALUE options = Qnil;
|
144
|
+
if (always_show_dot_files == Qtrue)
|
145
|
+
{
|
146
|
+
options = rb_hash_new();
|
147
|
+
rb_hash_aset(options, ID2SYM(rb_intern("always_show_dot_files")), always_show_dot_files);
|
148
|
+
}
|
149
|
+
else if (never_show_dot_files == Qtrue)
|
150
|
+
{
|
151
|
+
options = rb_hash_new();
|
152
|
+
rb_hash_aset(options, ID2SYM(rb_intern("never_show_dot_files")), never_show_dot_files);
|
153
|
+
}
|
154
|
+
abbrev = rb_funcall(abbrev, rb_intern("downcase"), 0);
|
155
|
+
VALUE paths = rb_funcall(scanner, rb_intern("paths"), 0);
|
156
|
+
for (long i = 0, max = RARRAY_LEN(paths); i < max; i++)
|
157
|
+
{
|
158
|
+
VALUE path = RARRAY_PTR(paths)[i];
|
159
|
+
VALUE match = rb_funcall(cCommandTMatch, rb_intern("new"), 3, path, abbrev, options);
|
160
|
+
if (rb_funcall(match, rb_intern("matches?"), 0) == Qtrue)
|
161
|
+
rb_funcall(matches, rb_intern("push"), 1, match);
|
162
|
+
}
|
163
|
+
return matches;
|
164
|
+
}
|