# fuzzig Fuzzy matcher based on the matching algorithm implementation of [ms-edit](https://github.com/microsoft/edit/blob/main/src/fuzzy.rs) as the editor is MIT-Licensed, this project is also under the MIT-License. ## Install Add or update this library as a dependency in your zig project run the following command: ```sh zig fetch --save git+https://gitea.yves-biener.de/yves-biener/fuzzig ``` Afterwards add the library as a dependency to any module in your *build.zig*: ```zig const fuzzig_dependency = b.dependency("fuzzig", .{ .target = target, .optimize = optimize, }); ``` ## Usage The following snippet shows a test case to illustrate usage of `fuzzig` to run fuzzy matches: ```zig test "matching `s` on local files" { var gpa = testing.allocator; // files to fuzzy match against var files: std.ArrayList([]const u8) = .empty; defer { for (files.items) |file| gpa.free(file); files.deinit(gpa); } // fuzzy matching results (containing only the scores) var results: std.ArrayList(Result) = .empty; defer { for (results.items) |*result| result.deinit(gpa); results.deinit(gpa); } // arrange var dir = try std.fs.cwd().openDir(".", .{ .iterate = true }); defer dir.close(); var iter = try dir.walk(gpa); defer iter.deinit(); while (try iter.next()) |entry| { switch (entry.kind) { .file => { if (std.mem.startsWith(u8, entry.path, ".git/")) continue; if (std.mem.startsWith(u8, entry.path, ".zig-cache")) continue; const path = try gpa.dupe(u8, entry.path[0..entry.path.len]); try files.append(gpa, path); }, else => continue, } } try results.ensureTotalCapacity(gpa, files.items.len); // act const search = "s"; // create fuzzy score for each file entry for (0.., files.items) |idx, entry| { const result = try match(gpa, entry, search, idx) orelse continue; try results.append(gpa, result); } // sort scores by their received score descending std.sort.heap(Result, results.items, {}, greaterThan); var buf: [128]u8 = undefined; var buffer = std.fs.File.stderr().writer(&buf); var writer = &buffer.interface; defer writer.flush() catch unreachable; std.debug.lockStdErr(); defer std.debug.unlockStdErr(); // assert var scored_entries: usize = 0; var unscored_entries: usize = 0; for (results.items) |result| { if (result.score > 0) scored_entries += 1 else unscored_entries += 1; if (result.score == 0) continue; // do not print results that are unmatched const item = files.items[result.index]; var match_highlights: []u8 = try gpa.alloc(u8, item.len); defer gpa.free(match_highlights); @memset(match_highlights, ' '); // highlight what caused this search result for (result.positions.items) |pos| match_highlights[pos] = '^'; // print item and its highlighted positions // NOTE uncomment the print for the writer to show matches and their highlights of what matched // -> as the writer prints to *stderr* writing will cause the test to fail, hence it is commented out by default try writer.print("{s}\n{s}\n", .{ item, match_highlights }); } try testing.expectEqual(5, scored_entries); try testing.expectEqual(results.items.len - 5, unscored_entries); } ``` Resulting in the output of the found (and ordered) matches: ``` src/root.zig ^ LICENSE ^ .gitea/workflows/test.yaml ^ .gitea/workflows/release.yaml ^ .typos-config ^ ```