
12 changed files with 701 additions and 2 deletions
@ -0,0 +1,16 @@ |
|||
BasedOnStyle: Google |
|||
AllowShortBlocksOnASingleLine: false |
|||
AllowShortFunctionsOnASingleLine: None |
|||
AllowShortIfStatementsOnASingleLine: false |
|||
AllowShortLoopsOnASingleLine: false |
|||
BreakBeforeBraces: Allman |
|||
BreakBeforeTernaryOperators: false |
|||
ColumnLimit: 100 |
|||
ConstructorInitializerIndentWidth: 4 |
|||
ContinuationIndentWidth: 4 |
|||
IndentWidth: 4 |
|||
Standard: Cpp03 |
|||
TabWidth: 4 |
|||
UseTab: ForIndentation |
|||
DerivePointerAlignment: false |
|||
PointerAlignment: Left |
@ -0,0 +1,5 @@ |
|||
SubDir . ; |
|||
|
|||
Main macoyFuzzy$(SUFSHR) : fuzzy.c |
|||
utils.c |
|||
macoyFuzzy.c ; |
@ -0,0 +1,42 @@ |
|||
# Building Tests: |
|||
# jam clean |
|||
# jam -j4 -q |
|||
|
|||
# (if building the same target repeatedly, don't clean, if that wasn't obvious) |
|||
|
|||
## |
|||
## Compiler |
|||
## |
|||
|
|||
CC = gcc ; |
|||
LINK = gcc ; |
|||
LINKFLAGS = -shared ; |
|||
|
|||
if $(UNIX) { SUFSHR = .so ; } |
|||
else if $(NT) { SUFSHR = .dll ; } |
|||
|
|||
## |
|||
## Compiler arguments |
|||
## |
|||
|
|||
# Arguments used on all projects, regardless of any variables |
|||
CCFLAGS = -ggdb3 -Wall -fPIC ; |
|||
|
|||
KEEPOBJS = true ; # This doesn't actually fix anything, though it seems like it should |
|||
NOARSCAN = true ; # This actually fixes the problem |
|||
#AR = ar rUu ; # I was thinking maybe the AR command was wrong (always outputting deterministically) |
|||
# It doesn't seem like this is the problem though |
|||
AR = ar cr ; |
|||
|
|||
OPTIM = -O0 ; |
|||
|
|||
HDRS = . ../../3rdParty/repositories/emacs/src ; |
|||
|
|||
|
|||
# Some helpful Jam commands |
|||
# -q : stop on failed target |
|||
# -jN : use N cores |
|||
# -sVAR=VAL : Set VAR to VAL. Note that setting WINDOWS=false is the same as setting UNREAL=true, |
|||
# frustratingly |
|||
# -dx : print commands being used |
|||
# -n : don't actually run commands |
@ -1,2 +0,0 @@ |
|||
# emacs-fuzzy-module |
|||
An emacs module for doing fuzzy filtering of a list |
@ -0,0 +1,19 @@ |
|||
* emacs-fuzzy-module |
|||
An emacs module for doing fuzzy filtering of a list. |
|||
|
|||
I created this because I was encountering performance problems with ELisp-based solutions like flx-ido. I figure native module performance should be much better. |
|||
|
|||
* Installation |
|||
1. To build, make sure you have Jam installed: |
|||
: sudo apt install jam |
|||
2. Open Jamrules and add the path to your ~emacs/src~ to the ~HDRS~ variable. Ensure there is a space between the ~;~ and your path |
|||
3. Run ~jam~ in emacs-fuzzy-module directory |
|||
4. Open macoyFuzzy.el and make the ~module-load~ path reference ~macoyFuzzy.so~ (wherever you built it) |
|||
5. Evaluate macoyFuzzy.el |
|||
6. Add the following to your .emacs: |
|||
#+BEGIN_SRC emacs-lisp |
|||
(require 'macoy-fuzzy-ido) |
|||
(ido-mode 1) |
|||
(ido-everywhere 1) |
|||
(macoy-fuzzy-ido-mode 1) |
|||
#+END_SRC |
@ -0,0 +1,189 @@ |
|||
#include "fuzzy.h" |
|||
|
|||
#include <ctype.h> |
|||
#include <stdlib.h> |
|||
#include <string.h> |
|||
|
|||
static bool fuzzy_match_recursive(const char* pattern, const char* str, int* outScore, |
|||
const char* strBegin, unsigned char const* srcMatches, |
|||
unsigned char* newMatches, int maxMatches, int nextMatch, |
|||
int* recursionCount, int recursionLimit); |
|||
|
|||
// Public interface
|
|||
bool fuzzy_match_simple(char const* pattern, char const* str) |
|||
{ |
|||
while (*pattern != '\0' && *str != '\0') |
|||
{ |
|||
if (tolower(*pattern) == tolower(*str)) |
|||
++pattern; |
|||
++str; |
|||
} |
|||
|
|||
return *pattern == '\0' ? true : false; |
|||
} |
|||
|
|||
bool fuzzy_match(char const* pattern, char const* str, int* outScore) |
|||
{ |
|||
unsigned char matches[256]; |
|||
return fuzzy_match_with_matches(pattern, str, outScore, matches, sizeof(matches)); |
|||
} |
|||
|
|||
bool fuzzy_match_with_matches(char const* pattern, char const* str, int* outScore, |
|||
unsigned char* matches, int maxMatches) |
|||
{ |
|||
int recursionCount = 0; |
|||
int recursionLimit = 10; |
|||
|
|||
return fuzzy_match_recursive(pattern, str, outScore, str, NULL, matches, maxMatches, 0, |
|||
&recursionCount, recursionLimit); |
|||
} |
|||
|
|||
// Private implementation
|
|||
static bool fuzzy_match_recursive(const char* pattern, const char* str, int* outScore, |
|||
const char* strBegin, unsigned char const* srcMatches, |
|||
unsigned char* matches, int maxMatches, int nextMatch, |
|||
int* recursionCount, int recursionLimit) |
|||
{ |
|||
// Count recursions
|
|||
++(*recursionCount); |
|||
if (*recursionCount >= recursionLimit) |
|||
return false; |
|||
|
|||
// Detect end of strings
|
|||
if (*pattern == '\0' || *str == '\0') |
|||
return false; |
|||
|
|||
// Recursion params
|
|||
bool recursiveMatch = false; |
|||
unsigned char bestRecursiveMatches[256]; |
|||
int bestRecursiveScore = 0; |
|||
|
|||
// Loop through pattern and str looking for a match
|
|||
bool first_match = true; |
|||
while (*pattern != '\0' && *str != '\0') |
|||
{ |
|||
// Found match
|
|||
if (tolower(*pattern) == tolower(*str)) |
|||
{ |
|||
// Supplied matches buffer was too short
|
|||
if (nextMatch >= maxMatches) |
|||
return false; |
|||
|
|||
// "Copy-on-Write" srcMatches into matches
|
|||
if (first_match && srcMatches) |
|||
{ |
|||
memcpy(matches, srcMatches, nextMatch); |
|||
first_match = false; |
|||
} |
|||
|
|||
// Recursive call that "skips" this match
|
|||
unsigned char recursiveMatches[256]; |
|||
int recursiveScore; |
|||
if (fuzzy_match_recursive(pattern, str + 1, &recursiveScore, strBegin, matches, |
|||
recursiveMatches, sizeof(recursiveMatches), nextMatch, |
|||
recursionCount, recursionLimit)) |
|||
{ |
|||
// Pick best recursive score
|
|||
if (!recursiveMatch || recursiveScore > bestRecursiveScore) |
|||
{ |
|||
memcpy(bestRecursiveMatches, recursiveMatches, 256); |
|||
bestRecursiveScore = recursiveScore; |
|||
} |
|||
recursiveMatch = true; |
|||
} |
|||
|
|||
// Advance
|
|||
matches[nextMatch++] = (unsigned char)(str - strBegin); |
|||
++pattern; |
|||
} |
|||
++str; |
|||
} |
|||
|
|||
// Determine if full pattern was matched
|
|||
bool matched = *pattern == '\0' ? true : false; |
|||
|
|||
// Calculate score
|
|||
if (matched) |
|||
{ |
|||
const int sequential_bonus = 15; // bonus for adjacent matches
|
|||
const int separator_bonus = 30; // bonus if match occurs after a separator
|
|||
const int camel_bonus = 30; // bonus if match is uppercase and prev is lower
|
|||
const int first_letter_bonus = 15; // bonus if the first letter is matched
|
|||
|
|||
// penalty applied for every letter in str before the first match
|
|||
const int leading_letter_penalty = -5; |
|||
const int max_leading_letter_penalty = -15; // maximum penalty for leading letters
|
|||
const int unmatched_letter_penalty = -1; // penalty for every letter that doesn't matter
|
|||
|
|||
// Iterate str to end
|
|||
while (*str != '\0') |
|||
++str; |
|||
|
|||
// Initialize score
|
|||
*outScore = 100; |
|||
|
|||
// Apply leading letter penalty
|
|||
int penalty = leading_letter_penalty * matches[0]; |
|||
if (penalty < max_leading_letter_penalty) |
|||
penalty = max_leading_letter_penalty; |
|||
*outScore += penalty; |
|||
|
|||
// Apply unmatched penalty
|
|||
int unmatched = (int)(str - strBegin) - nextMatch; |
|||
*outScore += unmatched_letter_penalty * unmatched; |
|||
|
|||
// Apply ordering bonuses
|
|||
for (int i = 0; i < nextMatch; ++i) |
|||
{ |
|||
unsigned char currIdx = matches[i]; |
|||
|
|||
if (i > 0) |
|||
{ |
|||
unsigned char prevIdx = matches[i - 1]; |
|||
|
|||
// Sequential
|
|||
if (currIdx == (prevIdx + 1)) |
|||
*outScore += sequential_bonus; |
|||
} |
|||
|
|||
// Check for bonuses based on neighbor character value
|
|||
if (currIdx > 0) |
|||
{ |
|||
// Camel case
|
|||
char neighbor = strBegin[currIdx - 1]; |
|||
char curr = strBegin[currIdx]; |
|||
if (islower(neighbor) && isupper(curr)) |
|||
*outScore += camel_bonus; |
|||
|
|||
// Separator
|
|||
bool neighborSeparator = neighbor == '_' || neighbor == ' '; |
|||
if (neighborSeparator) |
|||
*outScore += separator_bonus; |
|||
} |
|||
else |
|||
{ |
|||
// First letter
|
|||
*outScore += first_letter_bonus; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Return best result
|
|||
if (recursiveMatch && (!matched || bestRecursiveScore > *outScore)) |
|||
{ |
|||
// Recursive score is better than "this"
|
|||
memcpy(matches, bestRecursiveMatches, maxMatches); |
|||
*outScore = bestRecursiveScore; |
|||
return true; |
|||
} |
|||
else if (matched) |
|||
{ |
|||
// "this" score is better than recursive
|
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
// no match
|
|||
return false; |
|||
} |
|||
} |
@ -0,0 +1,40 @@ |
|||
#pragma once |
|||
// LICENSE
|
|||
//
|
|||
// This software is dual-licensed to the public domain and under the following
|
|||
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
|||
// publish, and distribute this file as you see fit.
|
|||
//
|
|||
// VERSION
|
|||
// 0.2.0 (2017-02-18) Scored matches perform exhaustive search for best score
|
|||
// 0.1.0 (2016-03-28) Initial release
|
|||
//
|
|||
// AUTHOR
|
|||
// Forrest Smith
|
|||
//
|
|||
// NOTES
|
|||
// Compiling
|
|||
// You MUST add '#define FTS_FUZZY_MATCH_IMPLEMENTATION' before including this header in ONE
|
|||
// source file to create implementation.
|
|||
//
|
|||
// fuzzy_match_simple(...)
|
|||
// Returns true if each character in pattern is found sequentially within str
|
|||
//
|
|||
// fuzzy_match(...)
|
|||
// Returns true if pattern is found AND calculates a score.
|
|||
// Performs exhaustive search via recursion to find all possible matches and match with highest
|
|||
// score.
|
|||
// Scores values have no intrinsic meaning. Possible score range is not normalized and varies
|
|||
// with pattern.
|
|||
// Recursion is limited internally (default=10) to prevent degenerate cases (pattern="aaaaaa"
|
|||
// str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
|||
// Uses uint8_t for match indices. Therefore patterns are limited to 256 characters.
|
|||
// Score system should be tuned for YOUR use case. Words, sentences, file names, or method names
|
|||
// all prefer different tuning.
|
|||
|
|||
#include <stdbool.h> |
|||
|
|||
bool fuzzy_match_simple(char const* pattern, char const* str); |
|||
bool fuzzy_match(char const* pattern, char const* str, int* outScore); |
|||
bool fuzzy_match_with_matches(char const* pattern, char const* str, int* outScore, |
|||
unsigned char* matches, int maxMatches); |
@ -0,0 +1,203 @@ |
|||
#include <emacs-module.h> |
|||
|
|||
#include <stdlib.h> |
|||
|
|||
#include "fuzzy.h" |
|||
#include "utils.h" |
|||
|
|||
int plugin_is_GPL_compatible; |
|||
|
|||
// Note that for now this will affect the quality of the results if e.g. the best result is actually
|
|||
// match #2049. I'll have to make this good eventually
|
|||
#define MAX_MATCHES 2048 |
|||
|
|||
typedef struct MacoyFuzzyMatch |
|||
{ |
|||
emacs_value string; |
|||
int score; |
|||
} MacoyFuzzyMatch; |
|||
|
|||
// Return the value of the given element
|
|||
typedef int (*quicksort_GetValueFunc)(void* value); |
|||
// Sorts in place (modifies array)
|
|||
// Modified from https://rosettacode.org/wiki/Sorting_algorithms/Quicksort#C (GNU FDL license)
|
|||
void quicksort(void** array, int length, quicksort_GetValueFunc getValue) |
|||
{ |
|||
if (length < 2) |
|||
return; |
|||
|
|||
int pivot = getValue(array[length / 2]); |
|||
|
|||
int i, j; |
|||
for (i = 0, j = length - 1;; i++, j--) |
|||
{ |
|||
while (getValue(array[i]) < pivot) |
|||
i++; |
|||
while (getValue(array[j]) > pivot) |
|||
j--; |
|||
|
|||
if (i >= j) |
|||
break; |
|||
|
|||
void* temp = array[i]; |
|||
array[i] = array[j]; |
|||
array[j] = temp; |
|||
} |
|||
|
|||
quicksort(array, i, getValue); |
|||
quicksort(array + i, length - i, getValue); |
|||
} |
|||
|
|||
// Sorts in place (modifies array)
|
|||
// Modified from https://rosettacode.org/wiki/Sorting_algorithms/Quicksort#C (GNU FDL license)
|
|||
void quicksortReverse(void** array, int length, quicksort_GetValueFunc getValue) |
|||
{ |
|||
if (length < 2) |
|||
return; |
|||
|
|||
int pivot = getValue(array[length / 2]); |
|||
|
|||
int i, j; |
|||
for (i = 0, j = length - 1;; i++, j--) |
|||
{ |
|||
while (getValue(array[i]) > pivot) |
|||
i++; |
|||
while (getValue(array[j]) < pivot) |
|||
j--; |
|||
|
|||
if (i >= j) |
|||
break; |
|||
|
|||
void* temp = array[i]; |
|||
array[i] = array[j]; |
|||
array[j] = temp; |
|||
} |
|||
|
|||
quicksort(array, i, getValue); |
|||
quicksort(array + i, length - i, getValue); |
|||
} |
|||
|
|||
int getFuzzyMatchValue(void* match) |
|||
{ |
|||
return ((MacoyFuzzyMatch*)match)->score; |
|||
} |
|||
|
|||
// Make sure to free the returned array
|
|||
MacoyFuzzyMatch** sortFuzzyMatches(MacoyFuzzyMatch matches[], ptrdiff_t numMatches) |
|||
{ |
|||
MacoyFuzzyMatch** sortedMatches = malloc(sizeof(MacoyFuzzyMatch*) * numMatches); |
|||
for (int i = 0; i < numMatches; ++i) |
|||
sortedMatches[i] = &matches[i]; |
|||
|
|||
quicksortReverse((void**)sortedMatches, numMatches, getFuzzyMatchValue); |
|||
return sortedMatches; |
|||
} |
|||
|
|||
emacs_value makeListFromFuzzyMatches(emacs_env* env, MacoyFuzzyMatch** matches, ptrdiff_t numElems) |
|||
{ |
|||
emacs_value* values = malloc(sizeof(emacs_value) * numElems); |
|||
for (int i = 0; i < numElems; ++i) |
|||
values[i] = matches[i]->string; |
|||
|
|||
emacs_value listObject = env->funcall(env, env->intern(env, "list"), numElems, values); |
|||
free(values); |
|||
return listObject; |
|||
} |
|||
|
|||
// Takes a query string and a vector of strings and returns a list of strings matching the query
|
|||
// TODO: Make the values cached instead of having to copy the strings over every time!
|
|||
static emacs_value FmacoyFuzzyFilterVector_fun(emacs_env* env, ptrdiff_t nargs, emacs_value args[], |
|||
void* data) |
|||
{ |
|||
// Get the string arguments
|
|||
char* queryBuffer = NULL; |
|||
size_t queryBufferSize = 0; |
|||
copy_string_contents(env, args[0], &queryBuffer, &queryBufferSize); |
|||
|
|||
// TODO: Make this resizeable or come up with a way to toss out bad scores?
|
|||
MacoyFuzzyMatch matches[MAX_MATCHES]; |
|||
int numMatches = 0; |
|||
|
|||
for (int i = 0; i < env->vec_size(env, args[1]); ++i) |
|||
{ |
|||
emacs_value currentString = env->vec_get(env, args[1], i); |
|||
|
|||
char* stringToCheckBuffer = NULL; |
|||
size_t stringToCheckBufferSize = 0; |
|||
copy_string_contents(env, currentString, &stringToCheckBuffer, &stringToCheckBufferSize); |
|||
|
|||
int score = 0; |
|||
bool isMatch = fuzzy_match(queryBuffer, stringToCheckBuffer, &score); |
|||
|
|||
free(stringToCheckBuffer); |
|||
|
|||
// The string didn't match at all; we won't include it in our results
|
|||
if (!isMatch) |
|||
continue; |
|||
|
|||
// Add the value to our matches
|
|||
if (numMatches + 1 < MAX_MATCHES) |
|||
{ |
|||
MacoyFuzzyMatch* currentMatch = &matches[numMatches++]; |
|||
currentMatch->string = currentString; |
|||
currentMatch->score = score; |
|||
} |
|||
// Reached max number of matches
|
|||
else |
|||
break; |
|||
} |
|||
|
|||
free(queryBuffer); |
|||
|
|||
if (numMatches) |
|||
{ |
|||
MacoyFuzzyMatch** sortedMatches = sortFuzzyMatches(matches, numMatches); |
|||
|
|||
emacs_value matchesList = makeListFromFuzzyMatches(env, sortedMatches, numMatches); |
|||
free(sortedMatches); |
|||
return matchesList; |
|||
} |
|||
else |
|||
{ |
|||
emacs_value emptyList[] = {}; |
|||
return env->funcall(env, env->intern(env, "list"), 0, emptyList); |
|||
} |
|||
} |
|||
|
|||
static emacs_value FmacoyFuzzyScore_fun(emacs_env* env, ptrdiff_t nargs, emacs_value args[], |
|||
void* data) |
|||
{ |
|||
// Get the string arguments
|
|||
char* queryBuffer = NULL; |
|||
size_t queryBufferSize = 0; |
|||
char* stringToCheckBuffer = NULL; |
|||
size_t stringToCheckBufferSize = 0; |
|||
copy_string_contents(env, args[0], &queryBuffer, &queryBufferSize); |
|||
copy_string_contents(env, args[1], &stringToCheckBuffer, &stringToCheckBufferSize); |
|||
|
|||
int outScore = 0; |
|||
fuzzy_match(queryBuffer, stringToCheckBuffer, &outScore); |
|||
|
|||
free(queryBuffer); |
|||
free(stringToCheckBuffer); |
|||
|
|||
return env->make_integer(env, outScore); |
|||
} |
|||
|
|||
int emacs_module_init(struct emacs_runtime* ert) |
|||
{ |
|||
emacs_env* env = ert->get_environment(ert); |
|||
bind_function(env, "macoy-filter-list-fuzzy", |
|||
env->make_function(env, 2, 2, FmacoyFuzzyFilterVector_fun, |
|||
"Filter vector items by query and sort by score.", NULL)); |
|||
bind_function(env, "macoy-filter-fuzzy-score", |
|||
env->make_function( |
|||
env, 2, 2, FmacoyFuzzyScore_fun, |
|||
"(query, string). Returns the score of the string based on query", NULL)); |
|||
provide(env, "macoy-fuzzy"); |
|||
return 0; |
|||
} |
|||
|
|||
/* Integration
|
|||
Follow flx-ido defadvice for ido to replace (esp ido-set-matches-1) |
|||
*/ |
@ -0,0 +1,81 @@ |
|||
;;; macoyFuzzy.el --- fuzzy module integration for ido |
|||
|
|||
;; Copyright © 2018 Macoy Madson |
|||
|
|||
;; Author: Macoy Madson |
|||
;; Maintainer: Macoy Madson |
|||
;; Description: Module fuzzy search integration for ido |
|||
;; Version: 0.1 |
|||
;; URL: https://github.com/makuto/emacs-fuzzy-modulel |
|||
|
|||
;; This file is NOT part of GNU Emacs. |
|||
|
|||
;;; License |
|||
|
|||
;; This program is free software; you can redistribute it and/or |
|||
;; modify it under the terms of the GNU General Public License as |
|||
;; published by the Free Software Foundation; either version 3, or |
|||
;; (at your option) any later version. |
|||
;; |
|||
;; This program is distributed in the hope that it will be useful, |
|||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|||
;; General Public License for more details. |
|||
;; |
|||
;; You should have received a copy of the GNU General Public License |
|||
;; along with this program; see the file COPYING. If not, write to |
|||
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth |
|||
;; Floor, Boston, MA 02110-1301, USA. |
|||
|
|||
;;; Commentary: |
|||
|
|||
;; This package provides a native alternative to `ido-mode''s |
|||
;; built-in flex matching. |
|||
|
|||
;;; Acknowledgments |
|||
|
|||
;; Forrest Smith wrote the fuzzy matching algorithm. |
|||
;; flx-ido.el is what I looked at to know how to integrate my module into ido. |
|||
|
|||
;;; Installation: |
|||
|
|||
;; You must have compiled your Emacs with --with-modules for it to support modules. |
|||
;; Add the following code to your init file: |
|||
;; |
|||
;; (require 'macoy-fuzzy-ido) |
|||
;; (ido-mode 1) |
|||
;; (ido-everywhere 1) |
|||
;; (macoy-fuzzy-ido-mode 1) |
|||
|
|||
;;; Code: |
|||
|
|||
;; TODO: Make relative |
|||
(module-load "/home/macoy/Development/code/repositories/emacs-fuzzy-module/macoyFuzzy.so") |
|||
(require 'macoy-fuzzy) |
|||
|
|||
(define-minor-mode macoy-fuzzy-ido-mode |
|||
"Toggle Macoy fuzzy mode" |
|||
:init-value nil |
|||
:lighter "MacoyFuzzy" |
|||
:group 'ido |
|||
:global t) |
|||
|
|||
(defun macoy-filter-list-fuzzy-ido (query items) |
|||
(if (zerop (length query)) |
|||
items |
|||
(macoy-filter-list-fuzzy query (vconcat original-items)) |
|||
) |
|||
) |
|||
|
|||
(defadvice ido-set-matches-1 (around flx-ido-set-matches-1 activate compile) |
|||
"Choose between the regular ido-set-matches-1 and macoy-fuzzy-ido-match" |
|||
(if (not macoy-fuzzy-ido-mode) |
|||
ad-do-it |
|||
(let* ((query ido-text) |
|||
(original-items (ad-get-arg 0))) |
|||
(setq ad-return-value (macoy-filter-list-fuzzy-ido query original-items))) |
|||
)) |
|||
|
|||
(provide 'macoy-fuzzy-ido) |
|||
|
|||
;;; macoyFuzzy.el ends here |
@ -0,0 +1,36 @@ |
|||
;; Init |
|||
(module-load |
|||
"/home/macoy/Development/code/3rdParty/repositories/emacs/modules/macoyFuzzy/macoyFuzzy.so") |
|||
(require 'macoy-fuzzy) |
|||
|
|||
;; Score |
|||
(message "%s" (macoy-filter-fuzzy-score "test" "blah-tsdfj-sfdasjes-st")) |
|||
|
|||
;; Filter |
|||
(message "%s" (macoy-filter-list-fuzzy "test" ["blah-tsdfj-sfdasjes-st" "test" "nomatch" "whew"])) |
|||
|
|||
;; Mode |
|||
(define-minor-mode macoy-fuzzy-ido-mode |
|||
"Toggle Macoy fuzzy mode" |
|||
:init-value nil |
|||
:lighter "MacoyFuzzy" |
|||
:group 'ido |
|||
:global t) |
|||
|
|||
(defun macoy-filter-list-fuzzy-ido (query items) |
|||
(if (zerop (length query)) |
|||
items |
|||
(macoy-filter-list-fuzzy query (vconcat original-items)) |
|||
) |
|||
) |
|||
|
|||
(defadvice ido-set-matches-1 (around flx-ido-set-matches-1 activate compile) |
|||
"Choose between the regular ido-set-matches-1 and macoy-fuzzy-ido-match" |
|||
(if (not macoy-fuzzy-ido-mode) |
|||
ad-do-it |
|||
(let* ((query ido-text) |
|||
(original-items (ad-get-arg 0))) |
|||
(setq ad-return-value (macoy-filter-list-fuzzy-ido query original-items))) |
|||
)) |
|||
|
|||
(macoy-fuzzy-ido-mode 1) |
@ -0,0 +1,56 @@ |
|||
#include "utils.h" |
|||
|
|||
#include <assert.h> |
|||
#include <stdbool.h> |
|||
#include <stddef.h> |
|||
#include <stdint.h> |
|||
#include <stdlib.h> |
|||
|
|||
#include <emacs-module.h> |
|||
|
|||
// From https://phst.github.io/emacs-modules#introduction
|
|||
bool copy_string_contents(emacs_env* env, emacs_value value, char** buffer, size_t* size) |
|||
{ |
|||
ptrdiff_t buffer_size; |
|||
if (!env->copy_string_contents(env, value, NULL, &buffer_size)) |
|||
return false; |
|||
assert(env->non_local_exit_check(env) == emacs_funcall_exit_return); |
|||
assert(buffer_size > 0); |
|||
*buffer = malloc((size_t)buffer_size); |
|||
if (*buffer == NULL) |
|||
{ |
|||
env->non_local_exit_signal(env, env->intern(env, "memory-full"), env->intern(env, "nil")); |
|||
return false; |
|||
} |
|||
ptrdiff_t old_buffer_size = buffer_size; |
|||
if (!env->copy_string_contents(env, value, *buffer, &buffer_size)) |
|||
{ |
|||
free(*buffer); |
|||
*buffer = NULL; |
|||
return false; |
|||
} |
|||
assert(env->non_local_exit_check(env) == emacs_funcall_exit_return); |
|||
assert(buffer_size == old_buffer_size); |
|||
*size = (size_t)(buffer_size - 1); |
|||
return true; |
|||
} |
|||
|
|||
/* Bind NAME to FUN. */ |
|||
void bind_function(emacs_env* env, const char* name, emacs_value Sfun) |
|||
{ |
|||
emacs_value Qfset = env->intern(env, "fset"); |
|||
emacs_value Qsym = env->intern(env, name); |
|||
emacs_value args[] = {Qsym, Sfun}; |
|||
|
|||
env->funcall(env, Qfset, 2, args); |
|||
} |
|||
|
|||
/* Provide FEATURE to Emacs. */ |
|||
void provide(emacs_env* env, const char* feature) |
|||
{ |
|||
emacs_value Qfeat = env->intern(env, feature); |
|||
emacs_value Qprovide = env->intern(env, "provide"); |
|||
emacs_value args[] = {Qfeat}; |
|||
|
|||
env->funcall(env, Qprovide, 1, args); |
|||
} |
@ -0,0 +1,14 @@ |
|||
#pragma once |
|||
|
|||
#include <stdbool.h> |
|||
|
|||
#include <emacs-module.h> |
|||
|
|||
// Free() the buffer once you're done with it
|
|||
bool copy_string_contents(emacs_env* env, emacs_value value, char** buffer, size_t* size); |
|||
|
|||
/* Bind NAME to FUN. */ |
|||
void bind_function(emacs_env* env, const char* name, emacs_value Sfun); |
|||
|
|||
/* Provide FEATURE to Emacs. */ |
|||
void provide(emacs_env* env, const char* feature); |
Loading…
Reference in new issue