dotvim/.vim/bundle/command-t/ruby/command-t/matcher.c
2015-07-02 10:45:40 +02:00

233 lines
7.3 KiB
C

// Copyright 2010-2014 Greg Hurrell. All rights reserved.
// Licensed under the terms of the BSD 2-clause license.
#include <stdlib.h> /* for qsort() */
#include <string.h> /* for strncmp() */
#include "matcher.h"
#include "match.h"
#include "ext.h"
#include "ruby_compat.h"
// order matters; we want this to be evaluated only after ruby.h
#ifdef HAVE_PTHREAD_H
#include <pthread.h> /* for pthread_create, pthread_join etc */
#endif
// comparison function for use with qsort
int cmp_alpha(const void *a, const void *b)
{
match_t a_match = *(match_t *)a;
match_t b_match = *(match_t *)b;
VALUE a_str = a_match.path;
VALUE b_str = b_match.path;
char *a_p = RSTRING_PTR(a_str);
long a_len = RSTRING_LEN(a_str);
char *b_p = RSTRING_PTR(b_str);
long b_len = RSTRING_LEN(b_str);
int order = 0;
if (a_len > b_len) {
order = strncmp(a_p, b_p, b_len);
if (order == 0)
order = 1; // shorter string (b) wins
} else if (a_len < b_len) {
order = strncmp(a_p, b_p, a_len);
if (order == 0)
order = -1; // shorter string (a) wins
} else {
order = strncmp(a_p, b_p, a_len);
}
return order;
}
// comparison function for use with qsort
int cmp_score(const void *a, const void *b)
{
match_t a_match = *(match_t *)a;
match_t b_match = *(match_t *)b;
if (a_match.score > b_match.score)
return -1; // a scores higher, a should appear sooner
else if (a_match.score < b_match.score)
return 1; // b scores higher, a should appear later
else
return cmp_alpha(a, b);
}
VALUE CommandTMatcher_initialize(int argc, VALUE *argv, VALUE self)
{
VALUE always_show_dot_files;
VALUE never_show_dot_files;
VALUE options;
VALUE scanner;
// process arguments: 1 mandatory, 1 optional
if (rb_scan_args(argc, argv, "11", &scanner, &options) == 1)
options = Qnil;
if (NIL_P(scanner))
rb_raise(rb_eArgError, "nil scanner");
rb_iv_set(self, "@scanner", scanner);
// check optional options hash for overrides
always_show_dot_files = CommandT_option_from_hash("always_show_dot_files", options);
never_show_dot_files = CommandT_option_from_hash("never_show_dot_files", options);
rb_iv_set(self, "@always_show_dot_files", always_show_dot_files);
rb_iv_set(self, "@never_show_dot_files", never_show_dot_files);
return Qnil;
}
typedef struct {
int thread_count;
int thread_index;
int case_sensitive;
match_t *matches;
long path_count;
VALUE paths;
VALUE abbrev;
VALUE always_show_dot_files;
VALUE never_show_dot_files;
} thread_args_t;
void *match_thread(void *thread_args)
{
long i;
thread_args_t *args = (thread_args_t *)thread_args;
for (i = args->thread_index; i < args->path_count; i += args->thread_count) {
VALUE path = RARRAY_PTR(args->paths)[i];
calculate_match(path,
args->abbrev,
args->case_sensitive,
args->always_show_dot_files,
args->never_show_dot_files,
&args->matches[i]);
}
return NULL;
}
VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self)
{
long i, limit, path_count, thread_count;
#ifdef HAVE_PTHREAD_H
long err;
pthread_t *threads;
#endif
match_t *matches;
thread_args_t *thread_args;
VALUE abbrev;
VALUE case_sensitive;
VALUE always_show_dot_files;
VALUE limit_option;
VALUE never_show_dot_files;
VALUE options;
VALUE paths;
VALUE results;
VALUE scanner;
VALUE sort_option;
VALUE threads_option;
// process arguments: 1 mandatory, 1 optional
if (rb_scan_args(argc, argv, "11", &abbrev, &options) == 1)
options = Qnil;
if (NIL_P(abbrev))
rb_raise(rb_eArgError, "nil abbrev");
// check optional options has for overrides
case_sensitive = CommandT_option_from_hash("case_sensitive", options);
limit_option = CommandT_option_from_hash("limit", options);
threads_option = CommandT_option_from_hash("threads", options);
sort_option = CommandT_option_from_hash("sort", options);
abbrev = StringValue(abbrev);
if (case_sensitive != Qtrue)
abbrev = rb_funcall(abbrev, rb_intern("downcase"), 0);
// get unsorted matches
scanner = rb_iv_get(self, "@scanner");
paths = rb_funcall(scanner, rb_intern("paths"), 0);
always_show_dot_files = rb_iv_get(self, "@always_show_dot_files");
never_show_dot_files = rb_iv_get(self, "@never_show_dot_files");
path_count = RARRAY_LEN(paths);
matches = malloc(path_count * sizeof(match_t));
if (!matches)
rb_raise(rb_eNoMemError, "memory allocation failed");
thread_count = NIL_P(threads_option) ? 1 : NUM2LONG(threads_option);
#ifdef HAVE_PTHREAD_H
#define THREAD_THRESHOLD 1000 /* avoid the overhead of threading when search space is small */
if (path_count < THREAD_THRESHOLD)
thread_count = 1;
threads = malloc(sizeof(pthread_t) * thread_count);
if (!threads)
rb_raise(rb_eNoMemError, "memory allocation failed");
#endif
thread_args = malloc(sizeof(thread_args_t) * thread_count);
if (!thread_args)
rb_raise(rb_eNoMemError, "memory allocation failed");
for (i = 0; i < thread_count; i++) {
thread_args[i].thread_count = thread_count;
thread_args[i].thread_index = i;
thread_args[i].case_sensitive = case_sensitive == Qtrue;
thread_args[i].matches = matches;
thread_args[i].path_count = path_count;
thread_args[i].paths = paths;
thread_args[i].abbrev = abbrev;
thread_args[i].always_show_dot_files = always_show_dot_files;
thread_args[i].never_show_dot_files = never_show_dot_files;
#ifdef HAVE_PTHREAD_H
if (i == thread_count - 1) {
#endif
// for the last "worker", we'll just use the main thread
(void)match_thread(&thread_args[i]);
#ifdef HAVE_PTHREAD_H
} else {
err = pthread_create(&threads[i], NULL, match_thread, (void *)&thread_args[i]);
if (err != 0)
rb_raise(rb_eSystemCallError, "pthread_create() failure (%d)", (int)err);
}
#endif
}
#ifdef HAVE_PTHREAD_H
for (i = 0; i < thread_count - 1; i++) {
err = pthread_join(threads[i], NULL);
if (err != 0)
rb_raise(rb_eSystemCallError, "pthread_join() failure (%d)", (int)err);
}
free(threads);
#endif
if (NIL_P(sort_option) || sort_option == Qtrue) {
if (RSTRING_LEN(abbrev) == 0 ||
(RSTRING_LEN(abbrev) == 1 && RSTRING_PTR(abbrev)[0] == '.'))
// alphabetic order if search string is only "" or "."
qsort(matches, path_count, sizeof(match_t), cmp_alpha);
else
// for all other non-empty search strings, sort by score
qsort(matches, path_count, sizeof(match_t), cmp_score);
}
results = rb_ary_new();
limit = NIL_P(limit_option) ? 0 : NUM2LONG(limit_option);
if (limit == 0)
limit = path_count;
for (i = 0; i < path_count && limit > 0; i++) {
if (matches[i].score > 0.0) {
rb_funcall(results, rb_intern("push"), 1, matches[i].path);
limit--;
}
}
free(matches);
return results;
}