~leon_plickat/nfm

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
2 2

[PATCH nfm v2] Add tab completion for files

Details
Message ID
<20220616194620.18738-1-mail@hmachet.com>
DKIM signature
pass
Download raw message
Patch: +55 -0
Implements: https://todo.sr.ht/~leon_plickat/nfm/17
---
v1 -> v2:
  Rebased and modifed to work with dirmap re-design

 src/nfm.zig | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 55 insertions(+)

diff --git a/src/nfm.zig b/src/nfm.zig
index bc5fe5870bc6..e39f578cd173 100644
--- a/src/nfm.zig
+++ b/src/nfm.zig
@@ -512,6 +512,19 @@ fn handleInputUserInput(in: spoon.Input) !void {
                try handleReturnUserInput();
                return;
            },
            '\t' => {
                var current = try util.codepointSliceToUtf8SlizeZAlloc(context.gpa, buffer.buffer.items);
                defer context.gpa.free(current);

                var it = mem.tokenize(u8, current, " ");
                var buf: []const u8 = undefined;
                while (it.next()) |tok| buf = tok;

                if (buf.len < 2) return;
                var comp = try completion(buf);
                if (mem.eql(u8, comp, buf)) return;
                try buffer.insertFormat(comp);
            },
            127 => { // Backspace.
                if (context.mode.user_input.history_index != null) return;
                buffer.delete(.left, 1);
@@ -623,6 +636,48 @@ fn handleReturnUserInput() !void {
    }
}

/// 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 {
    var found: std.ArrayListUnmanaged([:0]const u8) = .{};
    defer found.deinit(context.gpa);

    for (context.dirmap.cwd.files.items) |file| {
        if (!context.show_hidden and file.isHidden()) continue;
        if (file.name.len <= input.len) continue;
        if (ascii.startsWithIgnoreCase(file.name, input)) {
            try found.append(context.gpa, file.name);
        }
    }

    if (found.items.len == 0) {
        return input;
    } else if (found.items.len == 1) {
        return found.items[0][input.len..];
    } else {
        var common: []const u8 = found.items[0];
        var i: usize = 1;
        while (i < found.items.len) : (i += 1) {
            common = commonStart(common, found.items[i]);
        }
        return common[input.len..];
    }
}

/// 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];
}

test {
    _ = @import("ini.zig");
    _ = @import("CommandTokenizer.zig");
-- 
2.36.1

[nfm/patches] build failed

builds.sr.ht <builds@sr.ht>
Details
Message ID
<CKRTGLE0BTGA.2I15Z7US28QG6@cirno2>
In-Reply-To
<20220616194620.18738-1-mail@hmachet.com> (view parent)
DKIM signature
missing
Download raw message
nfm/patches: FAILED in 54s

[Add tab completion for files][0] v2 from [Hugo Machet][1]

[0]: https://lists.sr.ht/~leon_plickat/nfm/patches/33059
[1]: mail@hmachet.com

✓ #781943 SUCCESS nfm/patches/alpine.yml  https://builds.sr.ht/~leon_plickat/job/781943
✗ #781944 FAILED  nfm/patches/freebsd.yml https://builds.sr.ht/~leon_plickat/job/781944
Details
Message ID
<CKSJKOOSHCPM.32X1POT0ZXPAU@m5>
In-Reply-To
<20220616194620.18738-1-mail@hmachet.com> (view parent)
DKIM signature
missing
Download raw message
On Thu Jun 16, 2022 at 9:46 PM CEST, Hugo Machet wrote:
> + '\t' => {
> + var current = try util.codepointSliceToUtf8SlizeZAlloc(context.gpa,
> buffer.buffer.items);
> + defer context.gpa.free(current);
> +
> + var it = mem.tokenize(u8, current, " ");
> + var buf: []const u8 = undefined;
> + while (it.next()) |tok| buf = tok;
> +
> + if (buf.len < 2) return;
> + var comp = try completion(buf);
> + if (mem.eql(u8, comp, buf)) return;
> + try buffer.insertFormat(comp);
> + },

I think it might be better to first tokenize and then allocate an
utf8 slice containing only the word we are interested in.

Not sure if mem.tokenize() will play along, but since we are only
looking for whitespace, this could be done by hand anyway. Just start
at the cursor position and iterate backwards until you find whitespace
(careful to ignore whitespace directly at the curosr).

> +/// 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 {
> + var found: std.ArrayListUnmanaged([:0]const u8) = .{};
> + defer found.deinit(context.gpa);
> +
> + for (context.dirmap.cwd.files.items) |file| {
> + if (!context.show_hidden and file.isHidden()) continue;

I think we came to the conclusion to not skip hidden files, so this
check should be removed (otherwise you could just use DirMap.visible_files).

Also maybe a list of *File pointers might be better than duping all
similar names we find.

> + if (found.items.len == 0) {
> + return input;
> + } else if (found.items.len == 1) {
> + return found.items[0][input.len..];
> + } else {
> + var common: []const u8 = found.items[0];
> + var i: usize = 1;
> + while (i < found.items.len) : (i += 1) {
> + common = commonStart(common, found.items[i]);
> + }
> + return common[input.len..];
> + }
> +}

I think it might be nicer if this function did not return the entire
string, but only the completion bit. So it "pro" gets completed to
"projects", only "jects" should be returned. If no completion is found
return null. That way, callsite can manage the function a bit nicer
I think, as you'd only have to do something like this:

if (complete(token)) |compl| {
    input_buffer.insert(compl);
}

> +
> +/// 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];
> +}
> +

Neat!


BTW, I think there'll be a problem with file names containing spaces.
Either we should insert quotes, or automatically escape them.


Friendly greetings,
Leon Henrik Plickat
Reply to thread Export thread (mbox)