Hugo Machet: 1 Add tab completion for files 2 files changed, 96 insertions(+), 0 deletions(-)
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~leon_plickat/nfm/patches/34590/mbox | git am -3Learn more about email & git
Implements: https://todo.sr.ht/~leon_plickat/nfm/17 --- v4 -> v5 Rebase and fixes with latest master src/InputBuffer.zig | 9 +++++ src/nfm.zig | 87 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/src/InputBuffer.zig b/src/InputBuffer.zig index d7d053d60810..06c31c72d32b 100644 --- a/src/InputBuffer.zig +++ b/src/InputBuffer.zig @@ -56,6 +56,15 @@ pub fn moveCursor(self: *Self, comptime direction: Direction, amount: usize) voi } } +pub fn getCursorToken(self: *Self) []u21 { + var i: usize = self.cursor; + while (i > 0) { + if (isSpace(self.buffer.items[i - 1])) break; + i -= 1; + } + return self.buffer.items[i..self.cursor]; +} + pub fn delete(self: *Self, comptime direction: Direction, amount: usize) void { var i: usize = 0; switch (direction) { diff --git a/src/nfm.zig b/src/nfm.zig index 76decbdc4726..488cc234c5f8 100644 --- a/src/nfm.zig +++ b/src/nfm.zig @@ -35,6 +35,7 @@ const time = std.time; const CommandTokenizer = @import("CommandTokenizer.zig"); const Config = @import("Config.zig"); const DirMap = @import("DirMap.zig"); +const File = @import("File.zig"); const History = @import("History.zig"); const Mode = @import("mode.zig").Mode; const UserInterface = @import("UserInterface.zig"); @@ -570,6 +571,21 @@ fn handleInputUserInput(in: spoon.Input) !void { try handleReturnUserInput(); return; }, + '\t' => { + if (buffer.buffer.items.len < 2) return; + + var buf = buffer.getCursorToken(); + if (buf.len < 2) return; + + const alloc = context.gpa.allocator(); + var to_complete = try util.codepointSliceToUtf8SlizeZAlloc(alloc, buf); + defer alloc.free(to_complete); + + if (try completion(to_complete)) |compl| { + try buffer.insertFormat(compl); + defer alloc.free(compl); + } + }, 127 => { // Backspace. if (context.mode.user_input.history_index != null) return; buffer.delete(.left, 1); @@ -775,6 +791,77 @@ fn mouseClickPathFromTitle(_x: usize) !void { // Fallthrough means the user clicked on the current CWD. } +/// Compare current input with files/directories names. Complete the name if +/// there is only one match else only complete the common part. +/// Case insensitive. +fn completion(input: []const u8) !?[]const u8 { + const alloc = context.gpa.allocator(); + + var found: std.ArrayListUnmanaged(*File) = .{}; + defer found.deinit(alloc); + + for (context.dirmap.cwd.files.items) |*file| { + if (file.name.len <= input.len) continue; + if (ascii.startsWithIgnoreCase(file.name, input)) { + try found.append(alloc, file); + } + } + + if (found.items.len == 0) { + return null; + } else if (found.items.len == 1) { + const escaped = try escapeSpace(found.items[0].name[input.len..]); + return escaped; + } else { + var common: []const u8 = found.items[0].name; + var i: usize = 1; + while (i < found.items.len) : (i += 1) { + common = commonStart(common, found.items[i].name); + } + const escaped = try escapeSpace(common[input.len..]); + return escaped; + } +} + +/// Compare two strings and return the common beginning part. +/// Case insensitive. +fn commonStart(a: []const u8, b: []const u8) []const u8 { + const len: usize = if (a.len < b.len) a.len else b.len; + + var offset: usize = 0; + while (offset < len) : (offset += 1) { + if (ascii.toLower(a[offset]) != ascii.toLower(b[offset])) break; + } + + return a[0..offset]; +} + +/// Escape space in file names. +/// foo bar -> foo\ bar +/// foo\ bar -> foo\\\ bar +fn escapeSpace(s: []const u8) ![]const u8 { + const alloc = context.gpa.allocator(); + var ret: std.ArrayListUnmanaged(u8) = .{}; + + var i: usize = 0; + while (i < s.len) : (i += 1) { + if (s[i] == '\\') { + if (s[i + 1] == ' ') { + try ret.appendSlice(alloc, "\\\\\\ "); + i += 1; + } else { + try ret.append(alloc, s[i]); + } + } else if (s[i] == ' ') { + try ret.appendSlice(alloc, "\\ "); + } else { + try ret.append(alloc, s[i]); + } + } + + return ret.toOwnedSlice(alloc); +} + test { _ = @import("ini.zig"); _ = @import("CommandTokenizer.zig"); -- 2.37.1
builds.sr.ht <builds@sr.ht>nfm/patches: FAILED in 55s [Add tab completion for files][0] v5 from [Hugo Machet][1] [0]: https://lists.sr.ht/~leon_plickat/nfm/patches/34590 [1]: mailto:mail@hmachet.com ✓ #820965 SUCCESS nfm/patches/alpine.yml https://builds.sr.ht/~leon_plickat/job/820965 ✗ #820966 FAILED nfm/patches/freebsd.yml https://builds.sr.ht/~leon_plickat/job/820966