command-t 3.0.2 → 4.0
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.
- checksums.yaml +4 -4
- data/README.md +8 -2
- data/doc/command-t.txt +312 -147
- data/ruby/command-t.rb +13 -12
- data/ruby/command-t/controller.rb +86 -15
- data/ruby/command-t/depend +4 -0
- data/ruby/command-t/ext.h +9 -2
- data/ruby/command-t/extconf.rb +2 -2
- data/ruby/command-t/finder.rb +6 -2
- data/ruby/command-t/finder/buffer_finder.rb +3 -3
- data/ruby/command-t/finder/command_finder.rb +23 -0
- data/ruby/command-t/finder/file_finder.rb +3 -3
- data/ruby/command-t/finder/help_finder.rb +25 -0
- data/ruby/command-t/finder/history_finder.rb +27 -0
- data/ruby/command-t/finder/jump_finder.rb +3 -3
- data/ruby/command-t/finder/line_finder.rb +23 -0
- data/ruby/command-t/finder/mru_buffer_finder.rb +3 -3
- data/ruby/command-t/finder/tag_finder.rb +3 -3
- data/ruby/command-t/heap.c +146 -0
- data/ruby/command-t/heap.h +22 -0
- data/ruby/command-t/match.c +183 -116
- data/ruby/command-t/match.h +16 -10
- data/ruby/command-t/match_window.rb +10 -1
- data/ruby/command-t/matcher.c +203 -63
- data/ruby/command-t/metadata/fallback.rb +2 -2
- data/ruby/command-t/mru.rb +2 -2
- data/ruby/command-t/path_utilities.rb +2 -2
- data/ruby/command-t/progress_reporter.rb +38 -0
- data/ruby/command-t/prompt.rb +4 -4
- data/ruby/command-t/scanner.rb +22 -2
- data/ruby/command-t/scanner/buffer_scanner.rb +3 -3
- data/ruby/command-t/scanner/command_scanner.rb +33 -0
- data/ruby/command-t/scanner/file_scanner.rb +30 -6
- data/ruby/command-t/scanner/file_scanner/find_file_scanner.rb +12 -7
- data/ruby/command-t/scanner/file_scanner/git_file_scanner.rb +11 -8
- data/ruby/command-t/scanner/file_scanner/ruby_file_scanner.rb +7 -4
- data/ruby/command-t/scanner/file_scanner/watchman_file_scanner.rb +13 -5
- data/ruby/command-t/scanner/help_scanner.rb +40 -0
- data/ruby/command-t/scanner/history_scanner.rb +24 -0
- data/ruby/command-t/scanner/jump_scanner.rb +3 -3
- data/ruby/command-t/scanner/line_scanner.rb +45 -0
- data/ruby/command-t/scanner/mru_buffer_scanner.rb +3 -3
- data/ruby/command-t/scanner/tag_scanner.rb +3 -3
- data/ruby/command-t/scm_utilities.rb +2 -2
- data/ruby/command-t/settings.rb +2 -2
- data/ruby/command-t/stub.rb +7 -2
- data/ruby/command-t/util.rb +2 -2
- data/ruby/command-t/vim.rb +27 -2
- data/ruby/command-t/vim/screen.rb +3 -3
- data/ruby/command-t/vim/window.rb +3 -3
- data/ruby/command-t/watchman.c +1 -1
- metadata +13 -2
@@ -0,0 +1,22 @@
|
|
1
|
+
// Copyright 2016-present Greg Hurrell. All rights reserved.
|
2
|
+
// Licensed under the terms of the BSD 2-clause license.
|
3
|
+
|
4
|
+
/**
|
5
|
+
* A fixed size min-heap implementation.
|
6
|
+
*/
|
7
|
+
|
8
|
+
typedef int (*heap_compare_entries)(const void *a, const void *b);
|
9
|
+
|
10
|
+
typedef struct {
|
11
|
+
long count;
|
12
|
+
long capacity;
|
13
|
+
void **entries;
|
14
|
+
heap_compare_entries comparator;
|
15
|
+
} heap_t;
|
16
|
+
|
17
|
+
#define HEAP_PEEK(heap) (heap->entries[0])
|
18
|
+
|
19
|
+
heap_t *heap_new(long capacity, heap_compare_entries comparator);
|
20
|
+
void heap_free(heap_t *heap);
|
21
|
+
void heap_insert(heap_t *heap, void *value);
|
22
|
+
void *heap_extract(heap_t *heap);
|
data/ruby/command-t/match.c
CHANGED
@@ -6,171 +6,238 @@
|
|
6
6
|
#include "ext.h"
|
7
7
|
#include "ruby_compat.h"
|
8
8
|
|
9
|
-
|
9
|
+
#define UNSET_SCORE FLT_MAX
|
10
|
+
|
11
|
+
// Use a struct to make passing params during recursion easier.
|
10
12
|
typedef struct {
|
11
|
-
char *haystack_p; //
|
12
|
-
long haystack_len; //
|
13
|
-
char *needle_p; //
|
14
|
-
long needle_len; //
|
15
|
-
|
16
|
-
|
17
|
-
int
|
18
|
-
int
|
19
|
-
int
|
20
|
-
|
13
|
+
char *haystack_p; // Pointer to the path string to be searched.
|
14
|
+
long haystack_len; // Length of same.
|
15
|
+
char *needle_p; // Pointer to search string (needle).
|
16
|
+
long needle_len; // Length of same.
|
17
|
+
long *rightmost_match_p; // Rightmost match for each char in needle.
|
18
|
+
float max_score_per_char;
|
19
|
+
int always_show_dot_files; // Boolean.
|
20
|
+
int never_show_dot_files; // Boolean.
|
21
|
+
int case_sensitive; // Boolean.
|
22
|
+
int recurse; // Boolean.
|
23
|
+
float *memo; // Memoization.
|
21
24
|
} matchinfo_t;
|
22
25
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
//
|
36
|
-
double memoized = m->memo[needle_idx * m->needle_len + memo_idx];
|
37
|
-
if (memoized != DBL_MAX)
|
38
|
-
return memoized;
|
39
|
-
|
40
|
-
// bail early if not enough room (left) in haystack for (rest of) needle
|
41
|
-
if (m->haystack_len - haystack_idx < m->needle_len - needle_idx) {
|
42
|
-
score = 0.0;
|
43
|
-
goto memoize;
|
44
|
-
}
|
45
|
-
|
26
|
+
float recursive_match(
|
27
|
+
matchinfo_t *m, // Sharable meta-data.
|
28
|
+
long haystack_idx, // Where in the path string to start.
|
29
|
+
long needle_idx, // Where in the needle string to start.
|
30
|
+
long last_idx, // Location of last matched character.
|
31
|
+
float score // Cumulative score so far.
|
32
|
+
) {
|
33
|
+
long distance, i, j;
|
34
|
+
float *memoized = NULL;
|
35
|
+
float score_for_char;
|
36
|
+
float seen_score = 0;
|
37
|
+
|
38
|
+
// Iterate over needle.
|
46
39
|
for (i = needle_idx; i < m->needle_len; i++) {
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
40
|
+
// Iterate over (valid range of) haystack.
|
41
|
+
for (j = haystack_idx; j <= m->rightmost_match_p[i]; j++) {
|
42
|
+
char c, d;
|
43
|
+
|
44
|
+
// Do we have a memoized result we can return?
|
45
|
+
memoized = &m->memo[j * m->needle_len + i];
|
46
|
+
if (*memoized != UNSET_SCORE) {
|
47
|
+
return *memoized > seen_score ? *memoized : seen_score;
|
48
|
+
}
|
49
|
+
c = m->needle_p[i];
|
50
|
+
d = m->haystack_p[j];
|
56
51
|
if (d == '.') {
|
57
|
-
if (j == 0 || m->haystack_p[j - 1] == '/') { //
|
58
|
-
int dot_search =
|
59
|
-
if (
|
60
|
-
|
61
|
-
|
52
|
+
if (j == 0 || m->haystack_p[j - 1] == '/') { // This is a dot-file.
|
53
|
+
int dot_search = c == '.'; // Searching for a dot.
|
54
|
+
if (
|
55
|
+
m->never_show_dot_files ||
|
56
|
+
(!dot_search && !m->always_show_dot_files)
|
57
|
+
) {
|
58
|
+
return *memoized = 0.0;
|
62
59
|
}
|
63
60
|
}
|
64
61
|
} else if (d >= 'A' && d <= 'Z' && !m->case_sensitive) {
|
65
|
-
d += 'a' - 'A'; //
|
62
|
+
d += 'a' - 'A'; // Add 32 to downcase.
|
66
63
|
}
|
67
64
|
|
68
65
|
if (c == d) {
|
69
|
-
|
70
|
-
|
71
|
-
// calculate score
|
66
|
+
// Calculate score.
|
67
|
+
float sub_score = 0;
|
72
68
|
score_for_char = m->max_score_per_char;
|
73
69
|
distance = j - last_idx;
|
74
70
|
|
75
71
|
if (distance > 1) {
|
76
|
-
|
72
|
+
float factor = 1.0;
|
77
73
|
char last = m->haystack_p[j - 1];
|
78
|
-
char curr = m->haystack_p[j]; //
|
79
|
-
if (last == '/')
|
74
|
+
char curr = m->haystack_p[j]; // Case matters, so get again.
|
75
|
+
if (last == '/') {
|
80
76
|
factor = 0.9;
|
81
|
-
else if (
|
82
|
-
|
83
|
-
|
84
|
-
|
77
|
+
} else if (
|
78
|
+
last == '-' ||
|
79
|
+
last == '_' ||
|
80
|
+
last == ' ' ||
|
81
|
+
(last >= '0' && last <= '9')
|
82
|
+
) {
|
85
83
|
factor = 0.8;
|
86
|
-
else if (
|
87
|
-
|
84
|
+
} else if (
|
85
|
+
last >= 'a' && last <= 'z' &&
|
86
|
+
curr >= 'A' && curr <= 'Z'
|
87
|
+
) {
|
88
88
|
factor = 0.8;
|
89
|
-
else if (last == '.')
|
89
|
+
} else if (last == '.') {
|
90
90
|
factor = 0.7;
|
91
|
-
else
|
92
|
-
//
|
93
|
-
// as distance from last matched char increases
|
91
|
+
} else {
|
92
|
+
// If no "special" chars behind char, factor diminishes
|
93
|
+
// as distance from last matched char increases.
|
94
94
|
factor = (1.0 / distance) * 0.75;
|
95
|
+
}
|
95
96
|
score_for_char *= factor;
|
96
97
|
}
|
97
98
|
|
98
|
-
if (
|
99
|
-
|
100
|
-
|
101
|
-
double sub_score = recursive_match(m, j, i, last_idx, score);
|
102
|
-
if (sub_score > seen_score)
|
99
|
+
if (j < m->rightmost_match_p[i] && m->recurse) {
|
100
|
+
sub_score = recursive_match(m, j + 1, i, last_idx, score);
|
101
|
+
if (sub_score > seen_score) {
|
103
102
|
seen_score = sub_score;
|
103
|
+
}
|
104
104
|
}
|
105
|
-
|
105
|
+
last_idx = j;
|
106
|
+
haystack_idx = last_idx + 1;
|
106
107
|
score += score_for_char;
|
107
|
-
|
108
|
-
|
108
|
+
*memoized = seen_score > score ? seen_score : score;
|
109
|
+
if (i == m->needle_len - 1) {
|
110
|
+
// Whole string matched.
|
111
|
+
return *memoized;
|
112
|
+
}
|
113
|
+
if (!m->recurse) {
|
114
|
+
break;
|
115
|
+
}
|
109
116
|
}
|
110
117
|
}
|
111
|
-
if (!found) {
|
112
|
-
score = 0.0;
|
113
|
-
goto memoize;
|
114
|
-
}
|
115
118
|
}
|
116
|
-
|
117
|
-
score = score > seen_score ? score : seen_score;
|
118
|
-
|
119
|
-
memoize:
|
120
|
-
m->memo[needle_idx * m->needle_len + memo_idx] = score;
|
121
|
-
return score;
|
119
|
+
return *memoized = score;
|
122
120
|
}
|
123
121
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
long
|
133
|
-
|
122
|
+
float calculate_match(
|
123
|
+
VALUE haystack,
|
124
|
+
VALUE needle,
|
125
|
+
VALUE case_sensitive,
|
126
|
+
VALUE always_show_dot_files,
|
127
|
+
VALUE never_show_dot_files,
|
128
|
+
VALUE recurse,
|
129
|
+
long needle_bitmask,
|
130
|
+
long *haystack_bitmask
|
131
|
+
) {
|
134
132
|
matchinfo_t m;
|
135
|
-
|
136
|
-
|
133
|
+
long i;
|
134
|
+
float score = 1.0;
|
135
|
+
int compute_bitmasks = *haystack_bitmask == UNSET_BITMASK;
|
136
|
+
m.haystack_p = RSTRING_PTR(haystack);
|
137
|
+
m.haystack_len = RSTRING_LEN(haystack);
|
137
138
|
m.needle_p = RSTRING_PTR(needle);
|
138
139
|
m.needle_len = RSTRING_LEN(needle);
|
140
|
+
m.rightmost_match_p = NULL;
|
139
141
|
m.max_score_per_char = (1.0 / m.haystack_len + 1.0 / m.needle_len) / 2;
|
140
142
|
m.always_show_dot_files = always_show_dot_files == Qtrue;
|
141
143
|
m.never_show_dot_files = never_show_dot_files == Qtrue;
|
142
144
|
m.case_sensitive = (int)case_sensitive;
|
143
145
|
m.recurse = recurse == Qtrue;
|
144
146
|
|
145
|
-
//
|
146
|
-
score = 1.0;
|
147
|
-
|
148
|
-
// special case for zero-length search string
|
147
|
+
// Special case for zero-length search string.
|
149
148
|
if (m.needle_len == 0) {
|
150
|
-
|
151
|
-
|
152
|
-
if (!m.always_show_dot_files) {
|
149
|
+
// Filter out dot files.
|
150
|
+
if (m.never_show_dot_files || !m.always_show_dot_files) {
|
153
151
|
for (i = 0; i < m.haystack_len; i++) {
|
154
152
|
char c = m.haystack_p[i];
|
155
|
-
|
156
153
|
if (c == '.' && (i == 0 || m.haystack_p[i - 1] == '/')) {
|
157
|
-
|
158
|
-
break;
|
154
|
+
return 0.0;
|
159
155
|
}
|
160
156
|
}
|
161
157
|
}
|
162
|
-
} else
|
158
|
+
} else {
|
159
|
+
long haystack_limit;
|
160
|
+
long memo_size;
|
161
|
+
long needle_idx;
|
162
|
+
long mask;
|
163
|
+
long rightmost_match_p[m.needle_len];
|
164
|
+
|
165
|
+
if (*haystack_bitmask != UNSET_BITMASK) {
|
166
|
+
if ((needle_bitmask & *haystack_bitmask) != needle_bitmask) {
|
167
|
+
return 0.0;
|
168
|
+
}
|
169
|
+
}
|
163
170
|
|
164
|
-
//
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
m.
|
171
|
+
// Pre-scan string:
|
172
|
+
// - Bail if it can't match at all.
|
173
|
+
// - Record rightmost match for each character (prune search space).
|
174
|
+
// - Record bitmask for haystack to speed up future searches.
|
175
|
+
m.rightmost_match_p = rightmost_match_p;
|
176
|
+
needle_idx = m.needle_len - 1;
|
177
|
+
mask = 0;
|
178
|
+
for (i = m.haystack_len - 1; i >= 0; i--) {
|
179
|
+
char c = m.haystack_p[i];
|
180
|
+
char lower = c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c;
|
181
|
+
if (!m.case_sensitive) {
|
182
|
+
c = lower;
|
183
|
+
}
|
184
|
+
if (compute_bitmasks) {
|
185
|
+
mask |= (1 << (lower - 'a'));
|
186
|
+
}
|
169
187
|
|
170
|
-
|
171
|
-
|
188
|
+
if (needle_idx >= 0) {
|
189
|
+
char d = m.needle_p[needle_idx];
|
190
|
+
if (c == d) {
|
191
|
+
rightmost_match_p[needle_idx] = i;
|
192
|
+
needle_idx--;
|
193
|
+
}
|
194
|
+
}
|
195
|
+
}
|
196
|
+
if (compute_bitmasks) {
|
197
|
+
*haystack_bitmask = mask;
|
198
|
+
}
|
199
|
+
if (needle_idx != -1) {
|
200
|
+
return 0.0;
|
201
|
+
}
|
172
202
|
|
173
|
-
|
174
|
-
|
175
|
-
|
203
|
+
// Prepare for memoization.
|
204
|
+
haystack_limit = rightmost_match_p[m.needle_len - 1] + 1;
|
205
|
+
memo_size = m.needle_len * haystack_limit;
|
206
|
+
{
|
207
|
+
float memo[memo_size];
|
208
|
+
for (i = 0; i < memo_size; i++) {
|
209
|
+
memo[i] = UNSET_SCORE;
|
210
|
+
}
|
211
|
+
m.memo = memo;
|
212
|
+
score = recursive_match(&m, 0, 0, 0, 0.0);
|
213
|
+
|
214
|
+
#ifdef DEBUG
|
215
|
+
fprintf(stdout, " ");
|
216
|
+
for (i = 0; i < m.needle_len; i++) {
|
217
|
+
fprintf(stdout, " %c ", m.needle_p[i]);
|
218
|
+
}
|
219
|
+
fprintf(stdout, "\n");
|
220
|
+
for (i = 0; i < memo_size; i++) {
|
221
|
+
char formatted[8];
|
222
|
+
if (i % m.needle_len == 0) {
|
223
|
+
long haystack_idx = i / m.needle_len;
|
224
|
+
fprintf(stdout, "%c: ", m.haystack_p[haystack_idx]);
|
225
|
+
}
|
226
|
+
if (memo[i] == UNSET_SCORE) {
|
227
|
+
snprintf(formatted, sizeof(formatted), " - ");
|
228
|
+
} else {
|
229
|
+
snprintf(formatted, sizeof(formatted), " %-.4f", memo[i]);
|
230
|
+
}
|
231
|
+
fprintf(stdout, "%s", formatted);
|
232
|
+
if ((i + 1) % m.needle_len == 0) {
|
233
|
+
fprintf(stdout, "\n");
|
234
|
+
} else {
|
235
|
+
fprintf(stdout, " ");
|
236
|
+
}
|
237
|
+
}
|
238
|
+
fprintf(stdout, "Final score: %f\n\n", score);
|
239
|
+
#endif
|
240
|
+
}
|
241
|
+
}
|
242
|
+
return score;
|
176
243
|
}
|
data/ruby/command-t/match.h
CHANGED
@@ -3,16 +3,22 @@
|
|
3
3
|
|
4
4
|
#include <ruby.h>
|
5
5
|
|
6
|
-
|
6
|
+
#define UNSET_BITMASK (-1)
|
7
|
+
|
8
|
+
// Struct for representing an individual match.
|
7
9
|
typedef struct {
|
8
|
-
VALUE
|
9
|
-
|
10
|
+
VALUE path;
|
11
|
+
long bitmask;
|
12
|
+
float score;
|
10
13
|
} match_t;
|
11
14
|
|
12
|
-
extern
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
extern float calculate_match(
|
16
|
+
VALUE str,
|
17
|
+
VALUE needle,
|
18
|
+
VALUE case_sensitive,
|
19
|
+
VALUE always_show_dot_files,
|
20
|
+
VALUE never_show_dot_files,
|
21
|
+
VALUE recurse,
|
22
|
+
long needle_bitmask,
|
23
|
+
long *haystack_bitmask
|
24
|
+
);
|
@@ -15,6 +15,7 @@ module CommandT
|
|
15
15
|
Highlight = Struct.new(:highlight, :bang)
|
16
16
|
|
17
17
|
def initialize(options = {})
|
18
|
+
@encoding = options[:encoding]
|
18
19
|
@highlight_color = options[:highlight_color] || 'PmenuSel'
|
19
20
|
@min_height = options[:min_height]
|
20
21
|
@prompt = options[:prompt]
|
@@ -35,7 +36,10 @@ module CommandT
|
|
35
36
|
set 'scrolloff', 0 # don't scroll near buffer edges
|
36
37
|
set 'sidescroll', 0 # don't sidescroll in jumps
|
37
38
|
set 'sidescrolloff', 0 # don't sidescroll automatically
|
38
|
-
|
39
|
+
|
40
|
+
if options[:debounce_interval] > 0
|
41
|
+
set 'updatetime', options[:debounce_interval]
|
42
|
+
end
|
39
43
|
|
40
44
|
# Save existing window views so we can restore them later.
|
41
45
|
current_window = ::VIM::evaluate('winnr()')
|
@@ -396,6 +400,11 @@ module CommandT
|
|
396
400
|
#
|
397
401
|
def match_with_syntax_highlight(match)
|
398
402
|
highlight_chars = @prompt.abbrev.downcase.scan(/./mu)
|
403
|
+
if @encoding &&
|
404
|
+
match.respond_to?(:force_encoding) &&
|
405
|
+
match.encoding != @encoding
|
406
|
+
match = match.force_encoding(@encoding)
|
407
|
+
end
|
399
408
|
match.scan(/./mu).inject([]) do |output, char|
|
400
409
|
if char.downcase == highlight_chars.first
|
401
410
|
highlight_chars.shift
|
data/ruby/command-t/matcher.c
CHANGED
@@ -3,8 +3,9 @@
|
|
3
3
|
|
4
4
|
#include <stdlib.h> /* for qsort() */
|
5
5
|
#include <string.h> /* for strncmp() */
|
6
|
-
#include "matcher.h"
|
7
6
|
#include "match.h"
|
7
|
+
#include "matcher.h"
|
8
|
+
#include "heap.h"
|
8
9
|
#include "ext.h"
|
9
10
|
#include "ruby_compat.h"
|
10
11
|
|
@@ -13,7 +14,7 @@
|
|
13
14
|
#include <pthread.h> /* for pthread_create, pthread_join etc */
|
14
15
|
#endif
|
15
16
|
|
16
|
-
//
|
17
|
+
// Comparison function for use with qsort.
|
17
18
|
int cmp_alpha(const void *a, const void *b)
|
18
19
|
{
|
19
20
|
match_t a_match = *(match_t *)a;
|
@@ -29,11 +30,11 @@ int cmp_alpha(const void *a, const void *b)
|
|
29
30
|
if (a_len > b_len) {
|
30
31
|
order = strncmp(a_p, b_p, b_len);
|
31
32
|
if (order == 0)
|
32
|
-
order = 1; // shorter string (b) wins
|
33
|
+
order = 1; // shorter string (b) wins.
|
33
34
|
} else if (a_len < b_len) {
|
34
35
|
order = strncmp(a_p, b_p, a_len);
|
35
36
|
if (order == 0)
|
36
|
-
order = -1; // shorter string (a) wins
|
37
|
+
order = -1; // shorter string (a) wins.
|
37
38
|
} else {
|
38
39
|
order = strncmp(a_p, b_p, a_len);
|
39
40
|
}
|
@@ -41,16 +42,16 @@ int cmp_alpha(const void *a, const void *b)
|
|
41
42
|
return order;
|
42
43
|
}
|
43
44
|
|
44
|
-
//
|
45
|
+
// Comparison function for use with qsort.
|
45
46
|
int cmp_score(const void *a, const void *b)
|
46
47
|
{
|
47
48
|
match_t a_match = *(match_t *)a;
|
48
49
|
match_t b_match = *(match_t *)b;
|
49
50
|
|
50
51
|
if (a_match.score > b_match.score)
|
51
|
-
return -1; // a scores higher, a should appear sooner
|
52
|
+
return -1; // a scores higher, a should appear sooner.
|
52
53
|
else if (a_match.score < b_match.score)
|
53
|
-
return 1; // b scores higher, a should appear later
|
54
|
+
return 1; // b scores higher, a should appear later.
|
54
55
|
else
|
55
56
|
return cmp_alpha(a, b);
|
56
57
|
}
|
@@ -62,7 +63,7 @@ VALUE CommandTMatcher_initialize(int argc, VALUE *argv, VALUE self)
|
|
62
63
|
VALUE options;
|
63
64
|
VALUE scanner;
|
64
65
|
|
65
|
-
//
|
66
|
+
// Process arguments: 1 mandatory, 1 optional.
|
66
67
|
if (rb_scan_args(argc, argv, "11", &scanner, &options) == 1)
|
67
68
|
options = Qnil;
|
68
69
|
if (NIL_P(scanner))
|
@@ -70,7 +71,7 @@ VALUE CommandTMatcher_initialize(int argc, VALUE *argv, VALUE self)
|
|
70
71
|
|
71
72
|
rb_iv_set(self, "@scanner", scanner);
|
72
73
|
|
73
|
-
//
|
74
|
+
// Check optional options hash for overrides.
|
74
75
|
always_show_dot_files = CommandT_option_from_hash("always_show_dot_files", options);
|
75
76
|
never_show_dot_files = CommandT_option_from_hash("never_show_dot_files", options);
|
76
77
|
|
@@ -84,94 +85,192 @@ typedef struct {
|
|
84
85
|
long thread_count;
|
85
86
|
long thread_index;
|
86
87
|
long case_sensitive;
|
88
|
+
long limit;
|
87
89
|
match_t *matches;
|
88
90
|
long path_count;
|
89
|
-
VALUE
|
90
|
-
VALUE
|
91
|
+
VALUE haystacks;
|
92
|
+
VALUE needle;
|
91
93
|
VALUE always_show_dot_files;
|
92
94
|
VALUE never_show_dot_files;
|
93
95
|
VALUE recurse;
|
96
|
+
long needle_bitmask;
|
94
97
|
} thread_args_t;
|
95
98
|
|
96
99
|
void *match_thread(void *thread_args)
|
97
100
|
{
|
98
101
|
long i;
|
102
|
+
float score;
|
103
|
+
heap_t *heap = NULL;
|
99
104
|
thread_args_t *args = (thread_args_t *)thread_args;
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
105
|
+
|
106
|
+
if (args->limit) {
|
107
|
+
// Reserve one extra slot so that we can do an insert-then-extract even
|
108
|
+
// when "full" (effectively allows use of min-heap to maintain a
|
109
|
+
// top-"limit" list of items).
|
110
|
+
heap = heap_new(args->limit + 1, cmp_score);
|
111
|
+
}
|
112
|
+
|
113
|
+
for (
|
114
|
+
i = args->thread_index;
|
115
|
+
i < args->path_count;
|
116
|
+
i += args->thread_count
|
117
|
+
) {
|
118
|
+
args->matches[i].path = RARRAY_PTR(args->haystacks)[i];
|
119
|
+
if (args->needle_bitmask == UNSET_BITMASK) {
|
120
|
+
args->matches[i].bitmask = UNSET_BITMASK;
|
121
|
+
}
|
122
|
+
args->matches[i].score = calculate_match(
|
123
|
+
args->matches[i].path,
|
124
|
+
args->needle,
|
125
|
+
args->case_sensitive,
|
126
|
+
args->always_show_dot_files,
|
127
|
+
args->never_show_dot_files,
|
128
|
+
args->recurse,
|
129
|
+
args->needle_bitmask,
|
130
|
+
&args->matches[i].bitmask
|
131
|
+
);
|
132
|
+
if (heap) {
|
133
|
+
if (heap->count == args->limit) {
|
134
|
+
score = ((match_t *)HEAP_PEEK(heap))->score;
|
135
|
+
if (args->matches[i].score >= score) {
|
136
|
+
heap_insert(heap, &args->matches[i]);
|
137
|
+
(void)heap_extract(heap);
|
138
|
+
}
|
139
|
+
} else {
|
140
|
+
heap_insert(heap, &args->matches[i]);
|
141
|
+
}
|
142
|
+
}
|
109
143
|
}
|
110
144
|
|
111
|
-
return
|
145
|
+
return heap;
|
146
|
+
}
|
147
|
+
|
148
|
+
long calculate_bitmask(VALUE string) {
|
149
|
+
char *str = RSTRING_PTR(string);
|
150
|
+
long len = RSTRING_LEN(string);
|
151
|
+
long i;
|
152
|
+
long mask = 0;
|
153
|
+
for (i = 0; i < len; i++) {
|
154
|
+
if (str[i] >= 'a' && str[i] <= 'z') {
|
155
|
+
mask |= (1 << (str[i] - 'a'));
|
156
|
+
} else if (str[i] >= 'A' && str[i] <= 'Z') {
|
157
|
+
mask |= (1 << (str[i] - 'A'));
|
158
|
+
}
|
159
|
+
}
|
160
|
+
return mask;
|
112
161
|
}
|
113
162
|
|
114
163
|
VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self)
|
115
164
|
{
|
116
|
-
long i, limit, path_count, thread_count;
|
165
|
+
long i, j, limit, path_count, thread_count;
|
117
166
|
#ifdef HAVE_PTHREAD_H
|
118
167
|
long err;
|
119
168
|
pthread_t *threads;
|
120
169
|
#endif
|
170
|
+
long needle_bitmask = UNSET_BITMASK;
|
171
|
+
long heap_matches_count;
|
172
|
+
int use_heap;
|
173
|
+
int sort;
|
121
174
|
match_t *matches;
|
175
|
+
match_t *heap_matches = NULL;
|
176
|
+
heap_t *heap;
|
122
177
|
thread_args_t *thread_args;
|
123
|
-
VALUE abbrev;
|
124
|
-
VALUE case_sensitive;
|
125
178
|
VALUE always_show_dot_files;
|
179
|
+
VALUE case_sensitive;
|
180
|
+
VALUE recurse;
|
181
|
+
VALUE ignore_spaces;
|
126
182
|
VALUE limit_option;
|
183
|
+
VALUE needle;
|
127
184
|
VALUE never_show_dot_files;
|
128
|
-
VALUE
|
185
|
+
VALUE new_paths_object_id;
|
129
186
|
VALUE options;
|
130
187
|
VALUE paths;
|
131
|
-
VALUE
|
188
|
+
VALUE paths_object_id;
|
132
189
|
VALUE results;
|
133
190
|
VALUE scanner;
|
134
191
|
VALUE sort_option;
|
135
192
|
VALUE threads_option;
|
193
|
+
VALUE wrapped_matches;
|
136
194
|
|
137
|
-
//
|
138
|
-
if (rb_scan_args(argc, argv, "11", &
|
195
|
+
// Process arguments: 1 mandatory, 1 optional.
|
196
|
+
if (rb_scan_args(argc, argv, "11", &needle, &options) == 1)
|
139
197
|
options = Qnil;
|
140
|
-
if (NIL_P(
|
141
|
-
rb_raise(rb_eArgError, "nil
|
198
|
+
if (NIL_P(needle))
|
199
|
+
rb_raise(rb_eArgError, "nil needle");
|
142
200
|
|
143
|
-
//
|
201
|
+
// Check optional options hash for overrides.
|
144
202
|
case_sensitive = CommandT_option_from_hash("case_sensitive", options);
|
145
203
|
limit_option = CommandT_option_from_hash("limit", options);
|
146
204
|
threads_option = CommandT_option_from_hash("threads", options);
|
147
205
|
sort_option = CommandT_option_from_hash("sort", options);
|
148
206
|
ignore_spaces = CommandT_option_from_hash("ignore_spaces", options);
|
207
|
+
always_show_dot_files = rb_iv_get(self, "@always_show_dot_files");
|
208
|
+
never_show_dot_files = rb_iv_get(self, "@never_show_dot_files");
|
149
209
|
recurse = CommandT_option_from_hash("recurse", options);
|
150
210
|
|
151
|
-
|
211
|
+
limit = NIL_P(limit_option) ? 15 : NUM2LONG(limit_option);
|
212
|
+
sort = NIL_P(sort_option) || sort_option == Qtrue;
|
213
|
+
use_heap = limit && sort;
|
214
|
+
heap_matches_count = 0;
|
215
|
+
|
216
|
+
needle = StringValue(needle);
|
152
217
|
if (case_sensitive != Qtrue)
|
153
|
-
|
218
|
+
needle = rb_funcall(needle, rb_intern("downcase"), 0);
|
154
219
|
|
155
220
|
if (ignore_spaces == Qtrue)
|
156
|
-
|
221
|
+
needle = rb_funcall(needle, rb_intern("delete"), 1, rb_str_new2(" "));
|
157
222
|
|
158
|
-
//
|
223
|
+
// Get unsorted matches.
|
159
224
|
scanner = rb_iv_get(self, "@scanner");
|
160
225
|
paths = rb_funcall(scanner, rb_intern("paths"), 0);
|
161
|
-
always_show_dot_files = rb_iv_get(self, "@always_show_dot_files");
|
162
|
-
never_show_dot_files = rb_iv_get(self, "@never_show_dot_files");
|
163
|
-
|
164
226
|
path_count = RARRAY_LEN(paths);
|
165
|
-
|
166
|
-
|
167
|
-
|
227
|
+
|
228
|
+
// Cached C data, not visible to Ruby layer.
|
229
|
+
paths_object_id = rb_ivar_get(self, rb_intern("paths_object_id"));
|
230
|
+
new_paths_object_id = rb_funcall(paths, rb_intern("object_id"), 0);
|
231
|
+
rb_ivar_set(self, rb_intern("paths_object_id"), new_paths_object_id);
|
232
|
+
if (
|
233
|
+
NIL_P(paths_object_id) ||
|
234
|
+
NUM2LONG(new_paths_object_id) != NUM2LONG(paths_object_id)
|
235
|
+
) {
|
236
|
+
// `paths` changed, need to replace matches array.
|
237
|
+
paths_object_id = new_paths_object_id;
|
238
|
+
matches = malloc(path_count * sizeof(match_t));
|
239
|
+
if (!matches) {
|
240
|
+
rb_raise(rb_eNoMemError, "memory allocation failed");
|
241
|
+
}
|
242
|
+
wrapped_matches = Data_Wrap_Struct(
|
243
|
+
rb_cObject,
|
244
|
+
0,
|
245
|
+
free,
|
246
|
+
matches
|
247
|
+
);
|
248
|
+
rb_ivar_set(self, rb_intern("matches"), wrapped_matches);
|
249
|
+
} else {
|
250
|
+
// Get existing array.
|
251
|
+
Data_Get_Struct(
|
252
|
+
rb_ivar_get(self, rb_intern("matches")),
|
253
|
+
match_t,
|
254
|
+
matches
|
255
|
+
);
|
256
|
+
|
257
|
+
// Will compare against previously computed haystack bitmasks.
|
258
|
+
needle_bitmask = calculate_bitmask(needle);
|
259
|
+
}
|
168
260
|
|
169
261
|
thread_count = NIL_P(threads_option) ? 1 : NUM2LONG(threads_option);
|
262
|
+
if (use_heap) {
|
263
|
+
heap_matches = malloc(thread_count * limit * sizeof(match_t));
|
264
|
+
if (!heap_matches) {
|
265
|
+
rb_raise(rb_eNoMemError, "memory allocation failed");
|
266
|
+
}
|
267
|
+
}
|
170
268
|
|
171
269
|
#ifdef HAVE_PTHREAD_H
|
172
270
|
#define THREAD_THRESHOLD 1000 /* avoid the overhead of threading when search space is small */
|
173
|
-
if (path_count < THREAD_THRESHOLD)
|
271
|
+
if (path_count < THREAD_THRESHOLD) {
|
174
272
|
thread_count = 1;
|
273
|
+
}
|
175
274
|
threads = malloc(sizeof(pthread_t) * thread_count);
|
176
275
|
if (!threads)
|
177
276
|
rb_raise(rb_eNoMemError, "memory allocation failed");
|
@@ -185,58 +284,99 @@ VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self)
|
|
185
284
|
thread_args[i].thread_index = i;
|
186
285
|
thread_args[i].case_sensitive = case_sensitive == Qtrue;
|
187
286
|
thread_args[i].matches = matches;
|
287
|
+
thread_args[i].limit = use_heap ? limit : 0;
|
188
288
|
thread_args[i].path_count = path_count;
|
189
|
-
thread_args[i].
|
190
|
-
thread_args[i].
|
289
|
+
thread_args[i].haystacks = paths;
|
290
|
+
thread_args[i].needle = needle;
|
191
291
|
thread_args[i].always_show_dot_files = always_show_dot_files;
|
192
292
|
thread_args[i].never_show_dot_files = never_show_dot_files;
|
193
293
|
thread_args[i].recurse = recurse;
|
294
|
+
thread_args[i].needle_bitmask = needle_bitmask;
|
194
295
|
|
195
296
|
#ifdef HAVE_PTHREAD_H
|
196
297
|
if (i == thread_count - 1) {
|
197
298
|
#endif
|
198
|
-
//
|
199
|
-
|
299
|
+
// For the last "worker", we'll just use the main thread.
|
300
|
+
heap = match_thread(&thread_args[i]);
|
301
|
+
if (heap) {
|
302
|
+
for (j = 0; j < heap->count; j++) {
|
303
|
+
heap_matches[heap_matches_count++] = *(match_t *)heap->entries[j];
|
304
|
+
}
|
305
|
+
heap_free(heap);
|
306
|
+
}
|
200
307
|
#ifdef HAVE_PTHREAD_H
|
201
308
|
} else {
|
202
309
|
err = pthread_create(&threads[i], NULL, match_thread, (void *)&thread_args[i]);
|
203
|
-
if (err != 0)
|
310
|
+
if (err != 0) {
|
204
311
|
rb_raise(rb_eSystemCallError, "pthread_create() failure (%d)", (int)err);
|
312
|
+
}
|
205
313
|
}
|
206
314
|
#endif
|
207
315
|
}
|
208
316
|
|
209
317
|
#ifdef HAVE_PTHREAD_H
|
210
318
|
for (i = 0; i < thread_count - 1; i++) {
|
211
|
-
err = pthread_join(threads[i],
|
212
|
-
if (err != 0)
|
319
|
+
err = pthread_join(threads[i], (void **)&heap);
|
320
|
+
if (err != 0) {
|
213
321
|
rb_raise(rb_eSystemCallError, "pthread_join() failure (%d)", (int)err);
|
322
|
+
}
|
323
|
+
if (heap) {
|
324
|
+
for (j = 0; j < heap->count; j++) {
|
325
|
+
heap_matches[heap_matches_count++] = *(match_t *)heap->entries[j];
|
326
|
+
}
|
327
|
+
heap_free(heap);
|
328
|
+
}
|
214
329
|
}
|
215
330
|
free(threads);
|
216
331
|
#endif
|
217
332
|
|
218
|
-
if (
|
219
|
-
if (
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
//
|
225
|
-
|
333
|
+
if (sort) {
|
334
|
+
if (
|
335
|
+
RSTRING_LEN(needle) == 0 ||
|
336
|
+
(RSTRING_LEN(needle) == 1 && RSTRING_PTR(needle)[0] == '.')
|
337
|
+
) {
|
338
|
+
// Alphabetic order if search string is only "" or "."
|
339
|
+
// TODO: make those semantics fully apply to heap case as well
|
340
|
+
// (they don't because the heap itself calls cmp_score, which means
|
341
|
+
// that the items which stay in the top [limit] may (will) be
|
342
|
+
// different).
|
343
|
+
qsort(
|
344
|
+
use_heap ? heap_matches : matches,
|
345
|
+
use_heap ? heap_matches_count : path_count,
|
346
|
+
sizeof(match_t),
|
347
|
+
cmp_alpha
|
348
|
+
);
|
349
|
+
} else {
|
350
|
+
qsort(
|
351
|
+
use_heap ? heap_matches : matches,
|
352
|
+
use_heap ? heap_matches_count : path_count,
|
353
|
+
sizeof(match_t),
|
354
|
+
cmp_score
|
355
|
+
);
|
356
|
+
}
|
226
357
|
}
|
227
358
|
|
228
359
|
results = rb_ary_new();
|
229
|
-
|
230
|
-
limit = NIL_P(limit_option) ? 0 : NUM2LONG(limit_option);
|
231
360
|
if (limit == 0)
|
232
361
|
limit = path_count;
|
233
|
-
for (
|
234
|
-
|
235
|
-
|
362
|
+
for (
|
363
|
+
i = 0;
|
364
|
+
i < (use_heap ? heap_matches_count : path_count) && limit > 0;
|
365
|
+
i++
|
366
|
+
) {
|
367
|
+
if ((use_heap ? heap_matches : matches)[i].score > 0.0) {
|
368
|
+
rb_funcall(
|
369
|
+
results,
|
370
|
+
rb_intern("push"),
|
371
|
+
1,
|
372
|
+
(use_heap ? heap_matches : matches)[i].path
|
373
|
+
);
|
236
374
|
limit--;
|
237
375
|
}
|
238
376
|
}
|
239
377
|
|
240
|
-
|
378
|
+
if (use_heap) {
|
379
|
+
free(heap_matches);
|
380
|
+
}
|
241
381
|
return results;
|
242
382
|
}
|