From ba344e7d655cb51262e94a2a83f4c9614c86450d Mon Sep 17 00:00:00 2001 From: Yves Biener Date: Tue, 25 Nov 2025 19:47:00 +0100 Subject: [PATCH] doc: add missing README file; lint: correct reported typo --- README.md | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/root.zig | 2 +- 2 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..003c23e --- /dev/null +++ b/README.md @@ -0,0 +1,123 @@ +# 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. + +> [!caution] +> Only builds using the zig master version are tested to work. + +## 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 + ^ +``` diff --git a/src/root.zig b/src/root.zig index 572f34e..f69bd2c 100644 --- a/src/root.zig +++ b/src/root.zig @@ -174,7 +174,7 @@ fn compute_char_score(query: u8, query_lower: u8, target_prev: ?u8, target_curr: return score; } -/// Scoring for separator characters. Slightly prefering path separators over other separators. +/// Scoring for separator characters. Slightly preferring path separators over other separators. fn score_separator_at_pos(prev: u8) u32 { return switch (prev) { '/', '\\' => 5, // prefer path separators...