Ejemplo n.º 1
0
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;
}
Ejemplo n.º 2
0
VALUE CommandTMatcher_initialize(int argc, VALUE *argv, VALUE self)
{
    // process arguments: 1 mandatory, 1 optional
    VALUE scanner, options;
    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
    VALUE always_show_dot_files = CommandT_option_from_hash("always_show_dot_files", options);
    if (always_show_dot_files != Qtrue)
        always_show_dot_files = Qfalse;
    VALUE never_show_dot_files = CommandT_option_from_hash("never_show_dot_files", options);
    if (never_show_dot_files != Qtrue)
        never_show_dot_files = Qfalse;
    VALUE case_sensitive = CommandT_option_from_hash("case_sensitive", options);
    if (case_sensitive != Qtrue)
        case_sensitive = Qfalse;
    rb_iv_set(self, "@always_show_dot_files", always_show_dot_files);
    rb_iv_set(self, "@never_show_dot_files", never_show_dot_files);
    rb_iv_set(self, "@case_sensitive", case_sensitive);
    return Qnil;
}
Ejemplo n.º 3
0
Archivo: match.c Proyecto: 0xmtn/mtnvim
// Match.new needle, string, options = {}
VALUE CommandTMatch_initialize(int argc, VALUE *argv, VALUE self)
{
    // process arguments: 2 mandatory, 1 optional
    VALUE str, needle, options;
    if (rb_scan_args(argc, argv, "21", &str, &needle, &options) == 2)
        options = Qnil;
    str    = StringValue(str);
    needle = StringValue(needle); // already downcased by caller

    // check optional options hash for overrides
    VALUE always_show_dot_files = CommandT_option_from_hash("always_show_dot_files", options);
    VALUE never_show_dot_files = CommandT_option_from_hash("never_show_dot_files", options);

    matchinfo_t m;
    m.haystack_p            = RSTRING_PTR(str);
    m.haystack_len          = RSTRING_LEN(str);
    m.needle_p              = RSTRING_PTR(needle);
    m.needle_len            = RSTRING_LEN(needle);
    m.max_score_per_char    = (1.0 / m.haystack_len + 1.0 / m.needle_len) / 2;
    m.dot_file              = 0;
    m.always_show_dot_files = always_show_dot_files == Qtrue;
    m.never_show_dot_files  = never_show_dot_files == Qtrue;

    // calculate score
    double score = 1.0;

    // special case for zero-length search string
    if (m.needle_len == 0) {

        // filter out dot files
        if (!m.always_show_dot_files) {
            for (long i = 0; i < m.haystack_len; i++) {
                char c = m.haystack_p[i];

                if (c == '.' && (i == 0 || m.haystack_p[i - 1] == '/')) {
                    score = 0.0;
                    break;
                }
            }
        }
    } else if (m.haystack_len > 0) { // normal case

        // prepare for memoization
        double memo[m.haystack_len * m.needle_len];
        for (long i = 0, max = m.haystack_len * m.needle_len; i < max; i++)
            memo[i] = DBL_MAX;
        m.memo = memo;

        score = recursive_match(&m, 0, 0, 0, 0.0);
    }

    // clean-up and final book-keeping
    rb_iv_set(self, "@score", rb_float_new(score));
    rb_iv_set(self, "@str", str);
    return Qnil;
}
Ejemplo n.º 4
0
VALUE CommandTMatcher_sorted_matches_for(VALUE self, VALUE abbrev, VALUE options)
{
    // process optional options hash
    VALUE limit_option = CommandT_option_from_hash("limit", options);

    // get unsorted matches
    VALUE matches = CommandTMatcher_matches_for(self, abbrev);

    abbrev = StringValue(abbrev);
    if (RSTRING_LEN(abbrev) == 0 ||
        (RSTRING_LEN(abbrev) == 1 && RSTRING_PTR(abbrev)[0] == '.'))
        // alphabetic order if search string is only "" or "."
        qsort(RARRAY_PTR(matches), RARRAY_LEN(matches), sizeof(VALUE), comp_alpha);
    else
        // for all other non-empty search strings, sort by score
        qsort(RARRAY_PTR(matches), RARRAY_LEN(matches), sizeof(VALUE), comp_score);

    // apply optional limit option
    long limit = NIL_P(limit_option) ? 0 : NUM2LONG(limit_option);
    if (limit == 0 || RARRAY_LEN(matches) < limit)
        limit = RARRAY_LEN(matches);

    // will return an array of strings, not an array of Match objects
    for (long i = 0; i < limit; i++)
    {
        VALUE str = rb_funcall(RARRAY_PTR(matches)[i], rb_intern("to_s"), 0);
        RARRAY_PTR(matches)[i] = str;
    }

    // trim off any items beyond the limit
    if (limit < RARRAY_LEN(matches))
        (void)rb_funcall(matches, rb_intern("slice!"), 2, LONG2NUM(limit),
            LONG2NUM(RARRAY_LEN(matches) - limit));
    return matches;
}
Ejemplo n.º 5
0
VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self)
{
    // process arguments: 1 mandatory, 1 optional
    VALUE abbrev, options;

    if (rb_scan_args(argc, argv, "11", &abbrev, &options) == 1)
        options = Qnil;
    if (NIL_P(abbrev))
        rb_raise(rb_eArgError, "nil abbrev");

    abbrev = StringValue(abbrev);
    abbrev = rb_funcall(abbrev, rb_intern("downcase"), 0);

    // check optional options has for overrides
    VALUE limit_option = CommandT_option_from_hash("limit", options);

    // get unsorted matches
    VALUE scanner = rb_iv_get(self, "@scanner");
    VALUE paths = rb_funcall(scanner, rb_intern("paths"), 0);
    VALUE always_show_dot_files = rb_iv_get(self, "@always_show_dot_files");
    VALUE never_show_dot_files = rb_iv_get(self, "@never_show_dot_files");

    long path_count = RARRAY_LEN(paths);
    match_t *matches = malloc(path_count * sizeof(match_t));
    if (!matches)
        rb_raise(rb_eNoMemError, "memory allocation failed");

    int err;
    int thread_count = 1;

#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;
    else
        thread_count = PROCESSOR_COUNT; // passed in as preprocessor macro
    pthread_t *threads = malloc(sizeof(pthread_t) * thread_count);
    if (!threads)
        rb_raise(rb_eNoMemError, "memory allocation failed");
#endif

    thread_args_t *thread_args = malloc(sizeof(thread_args_t) * thread_count);
    if (!thread_args)
        rb_raise(rb_eNoMemError, "memory allocation failed");
    for (int i = 0; i < thread_count; i++) {
        thread_args[i].thread_count = thread_count;
        thread_args[i].thread_index = i;
        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)", err);
        }
#endif
    }

#ifdef HAVE_PTHREAD_H
    for (int i = 0; i < thread_count - 1; i++) {
        err = pthread_join(threads[i], NULL);
        if (err != 0)
            rb_raise(rb_eSystemCallError, "pthread_join() failure (%d)", err);
    }
    free(threads);
#endif

    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);

    VALUE results = rb_ary_new();

    long limit = NIL_P(limit_option) ? 0 : NUM2LONG(limit_option);
    if (limit == 0)
        limit = path_count;
    for (long 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;
}
Ejemplo n.º 6
0
VALUE CommandTMatcher_sorted_matches_for(int argc, VALUE *argv, VALUE self)
{
    long i, j, limit, path_count, thread_count;
#ifdef HAVE_PTHREAD_H
    long err;
    pthread_t *threads;
#endif
    long needle_bitmask = UNSET_BITMASK;
    long heap_matches_count;
    int use_heap;
    int sort;
    match_t *matches;
    match_t *heap_matches = NULL;
    heap_t *heap;
    thread_args_t *thread_args;
    VALUE always_show_dot_files;
    VALUE case_sensitive;
    VALUE recurse;
    VALUE ignore_spaces;
    VALUE limit_option;
    VALUE last_needle;
    VALUE needle;
    VALUE never_show_dot_files;
    VALUE new_paths_object_id;
    VALUE options;
    VALUE paths;
    VALUE paths_object_id;
    VALUE results;
    VALUE scanner;
    VALUE sort_option;
    VALUE threads_option;
    VALUE wrapped_matches;

    // Process arguments: 1 mandatory, 1 optional.
    if (rb_scan_args(argc, argv, "11", &needle, &options) == 1) {
        options = Qnil;
    }
    if (NIL_P(needle)) {
        rb_raise(rb_eArgError, "nil needle");
    }

    // Check optional options hash 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);
    ignore_spaces = CommandT_option_from_hash("ignore_spaces", options);
    always_show_dot_files = rb_iv_get(self, "@always_show_dot_files");
    never_show_dot_files = rb_iv_get(self, "@never_show_dot_files");
    recurse = CommandT_option_from_hash("recurse", options);

    limit = NIL_P(limit_option) ? 15 : NUM2LONG(limit_option);
    sort = NIL_P(sort_option) || sort_option == Qtrue;
    use_heap = limit && sort;
    heap_matches_count = 0;

    needle = StringValue(needle);
    if (case_sensitive != Qtrue) {
        needle = rb_funcall(needle, rb_intern("downcase"), 0);
    }

    if (ignore_spaces == Qtrue) {
        needle = rb_funcall(needle, rb_intern("delete"), 1, rb_str_new2(" "));
    }

    // Get unsorted matches.
    scanner = rb_iv_get(self, "@scanner");
    paths = rb_funcall(scanner, rb_intern("paths"), 0);
    path_count = RARRAY_LEN(paths);

    // Cached C data, not visible to Ruby layer.
    paths_object_id = rb_ivar_get(self, rb_intern("paths_object_id"));
    new_paths_object_id = rb_funcall(paths, rb_intern("object_id"), 0);
    rb_ivar_set(self, rb_intern("paths_object_id"), new_paths_object_id);
    last_needle = rb_ivar_get(self, rb_intern("last_needle"));
    if (
        NIL_P(paths_object_id) ||
        rb_equal(new_paths_object_id, paths_object_id) != Qtrue
    ) {
        // `paths` changed, need to replace matches array.
        paths_object_id = new_paths_object_id;
        matches = malloc(path_count * sizeof(match_t));
        if (!matches) {
            rb_raise(rb_eNoMemError, "memory allocation failed");
        }
        wrapped_matches = Data_Wrap_Struct(
            rb_cObject,
            0,
            free,
            matches
        );
        rb_ivar_set(self, rb_intern("matches"), wrapped_matches);
        last_needle = Qnil;
    } else {
        // Get existing array.
        Data_Get_Struct(
            rb_ivar_get(self, rb_intern("matches")),
            match_t,
            matches
        );

        // Will compare against previously computed haystack bitmasks.
        needle_bitmask = calculate_bitmask(needle);

        // Check whether current search extends previous search; if so, we can
        // skip all the non-matches from last time without looking at them.
        if (rb_funcall(needle, rb_intern("start_with?"), 1, last_needle) != Qtrue) {
            last_needle = Qnil;
        }
    }

    thread_count = NIL_P(threads_option) ? 1 : NUM2LONG(threads_option);
    if (use_heap) {
        heap_matches = malloc(thread_count * limit * sizeof(match_t));
        if (!heap_matches) {
            rb_raise(rb_eNoMemError, "memory allocation failed");
        }
    }

#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].limit = use_heap ? limit : 0;
        thread_args[i].path_count = path_count;
        thread_args[i].haystacks = paths;
        thread_args[i].needle = needle;
        thread_args[i].last_needle = last_needle;
        thread_args[i].always_show_dot_files = always_show_dot_files;
        thread_args[i].never_show_dot_files = never_show_dot_files;
        thread_args[i].recurse = recurse;
        thread_args[i].needle_bitmask = needle_bitmask;

#ifdef HAVE_PTHREAD_H
        if (i == thread_count - 1) {
#endif
            // For the last "worker", we'll just use the main thread.
            heap = match_thread(&thread_args[i]);
            if (heap) {
                for (j = 0; j < heap->count; j++) {
                    heap_matches[heap_matches_count++] = *(match_t *)heap->entries[j];
                }
                heap_free(heap);
            }
#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], (void **)&heap);
        if (err != 0) {
            rb_raise(rb_eSystemCallError, "pthread_join() failure (%d)", (int)err);
        }
        if (heap) {
            for (j = 0; j < heap->count; j++) {
                heap_matches[heap_matches_count++] = *(match_t *)heap->entries[j];
            }
            heap_free(heap);
        }
    }
    free(threads);
#endif

    if (sort) {
        if (
            RSTRING_LEN(needle) == 0 ||
            (RSTRING_LEN(needle) == 1 && RSTRING_PTR(needle)[0] == '.')
        ) {
            // Alphabetic order if search string is only "" or "."
            // TODO: make those semantics fully apply to heap case as well
            // (they don't because the heap itself calls cmp_score, which means
            // that the items which stay in the top [limit] may (will) be
            // different).
            qsort(
                use_heap ? heap_matches : matches,
                use_heap ? heap_matches_count : path_count,
                sizeof(match_t),
                cmp_alpha
            );
        } else {
            qsort(
                use_heap ? heap_matches : matches,
                use_heap ? heap_matches_count : path_count,
                sizeof(match_t),
                cmp_score
            );
        }
    }

    results = rb_ary_new();
    if (limit == 0) {
        limit = path_count;
    }
    for (
        i = 0;
        i < (use_heap ? heap_matches_count : path_count) && limit > 0;
        i++
    ) {
        if ((use_heap ? heap_matches : matches)[i].score > 0.0) {
            rb_funcall(
                results,
                rb_intern("push"),
                1,
                (use_heap ? heap_matches : matches)[i].path
            );
            limit--;
        }
    }

    if (use_heap) {
        free(heap_matches);
    }

    // Save this state to potentially speed subsequent searches.
    rb_ivar_set(self, rb_intern("last_needle"), needle);
    return results;
}