Find File (improvements needed)

def findit -params 1 -shell-script-candidates %{ pt --nogroup --nocolor --column -g "" } %{ edit %arg{1} } -docstring "Uses pt to find file"

This works decently (forgot the basis of it, probably @mawww’s kakrc) , but one thing it doesn’t work well with – which I like to use is multiple tiers of filtering.

So if I type like :findit foo bar baz when I tab complete to the file I need, it only replaces baz rather than foo bar baz.

Should also probably not hardcode the search tool, should like reference a generalized search tool finder like referenced in Best practices for "finding executable"

I stole “find” from mawww, also. One problem I have is that I will narrow the selection to one file and press “enter” without first pressing “tab”, making a buffer named after what I typed rather than the completion of what I typed.

Since I almost never want to create a file, I was thinking making it never create a file, and finding the single completion if the input isn’t a file. This seems like a lot of work though. I wonder if there’s a better way to do it.

I don’t need find because I’m using fzf.kak, but I was missing the gf from Vim, which searches for files under path variable, so I’ve implemented this:

define-command -docstring \
"search-file <filename>: search for file recusively under path option: %opt{path}" \
search-file -params 1 %{ evaluate-commands %sh{
    if [ -n "$(command -v fd)" ]; then                          # create find command template
        find='fd -L --type f "${file}" "${path}"'               # if `fd' is installed it will
    else                                                        # be used because it is faster
        find='find -L "${path}" -mount -type f -name "${file}"' # if not, we fallback to find.
    fi

    file=$(printf "%s\n" $1 | sed -E "s:^~/:$HOME/:") # we want full path

    eval "set -- ${kak_buflist}"
    while [ $# -gt 0 ]; do            # Check if buffer with this
        if [ "${file}" = "$1" ]; then # file already exists. Basically
            printf "%s\n" "buffer $1" # emulating what edit command does
            exit
        fi
        shift
    done

    if [ -e "${file}" ]; then                     # Test if file exists under
        printf "%s\n" "edit -existing %{${file}}" # servers' working directory
        exit                                      # this is last resort until
    fi                                            # we start recursive searchimg

    # if everthing  above fails - search for file under `path'
    eval "set -- ${kak_opt_path}"
    while [ $# -gt 0 ]; do                # Since we want to check fewer places,
        case $1 in                        # I've swapped ./ and %/ because
            ./) path=${kak_buffile%/*} ;; # %/ usually has smaller scope. So
            %/) path=${PWD}            ;; # this trick is a speedi-up hack.
            *)  path=$1                ;; # This means that `path' option should
        esac                              # first contain `./' and then `%/'

        if [ -z "${file##*/*}" ] && [ -e "${path}/${file}" ]; then
            printf "%s\n" "edit -existing %{${path}/${file}}"
            exit
        else
            # build list of candidates or automatically select if only one found
            # this doesn't support files with newlines in them unfortunately
            IFS='
'
            for candidate in $(eval "${find}"); do
                [ -n "${candidate}" ] && candidates="${candidates} %{${candidate}} %{evaluate-commands %{edit -existing %{${candidate}}}}"
            done

            # we want to get out as early as possible
            # so if any candidate found in current cycle
            # we prompt it in menu and exit
            if [ -n "${candidates}" ]; then
                printf "%s\n" "menu -auto-single ${candidates}"
                exit
            fi
        fi

        shift
    done

    printf "%s\n" "echo -markup %{{Error}unable to find file '${file}'}"
}}

This command searches for the filename under path option recursively. I’ve mapped it to gf in Kakoune like so:

map -docstring "file (non-recursive)" global goto '<a-f>' '<esc>gf'
map -docstring "file (recursive)"     global goto 'f'     '<esc>: smart-select WORD; search-file %val{selection}<ret>'

where smart-select is defined like so

2 Likes

@andreyorst wow – I adore how aggressive that function is, really ends up being a “do what I want” style thing.

yeah, basically it suits my needs though

I have been thinking about adding a switch to specify basically that completions candidates are the only valid inputs to a command, which would then make it reasonable to decide that if we have a single candidate remaining, we can assume it is what we want without requiring an explicit tab.

@mawww I think that would be amazing for a ton of use cases!

@mawww that would be awesome

Pushed a POC on the menu-completions branch, the -menu switch to define-command opts-in this behaviour, and a few built-in commands do that (buffer, source…). The completion should show the first candidate selected, hinting that it will be used on validation.

I would be interested in some feedback on that, on one hand it is pretty useful in many commands, on the other hand, it reduces uniformity by having some commands automatically validating completions while others do not.

commands.cc: In instantiation of ‘Kakoune::{anonymous}::add_flags(Completer&&, Kakoune::Completions::Flags)::<lambda(const Kakoune::Context&, Kakoune::CompletionFlags, const Kakoune::String&, Kakoune::ByteCount)> [with Completer = Kakoune::Completions (&)(const Kakoune::Context&, Kakoune::CompletionFlags, Kakoune::StringView, Kakoune::ByteCount)]
’
commands.cc:110:22:   required from ‘struct Kakoune::{anonymous}::add_flags(Completer&&, Kakoune::Completions::Flags) [with Completer = Kakoune::Completions (&)(const Kakoune::Context&, Kakoune::CompletionFlags, Kakoune::StringView, Kakoune::ByteCount)]::<lambda(const class Kakoune::Context&, enum class Kakoune::CompletionFlags, const class Kakoune::String&, struct Kakoune::ByteCount)>’
commands.cc:112:5:   required from ‘auto Kakoune::{anonymous}::add_flags(Completer&&, Kakoune::Completions::Flags) [with Completer = Kakoune::Completions (&)(const Kakoune::Context&, Kakoune::CompletionFlags, Kakoune::StringView, Kakoune::ByteCount)]’
commands.cc:118:21:   required from ‘auto Kakoune::{anonymous}::menu(Completer&&) [with Completer = Kakoune::Completions (&)(const Kakoune::Context&, Kakoune::CompletionFlags, Kakoune::StringView, Kakoune::ByteCount)]’
commands.cc:712:51:   required from here
commands.cc:109:27: error: variable ‘completer’ has function type
         Completions res = completer(context, flags, prefix, cursor_pos);
                           ^~~~~~~~~
commands.cc:109:27: error: variable ‘completer’ has function type
commands.cc: In instantiation of ‘struct Kakoune::{anonymous}::add_flags(Completer&&, Kakoune::Completions::Flags) [with Completer = Kakoune::Completions (&)(const Kakoune::Context&, Kakoune::CompletionFlags, Kakoune::StringView, Kakoune::ByteCount)]::<lambda(const class Kakoune::Context&, enum class Kakoune::CompletionFlags, const class Kakoune::String&, struct Kakoune::ByteCount)>’:
commands.cc:112:5:   required from ‘auto Kakoune::{anonymous}::add_flags(Completer&&, Kakoune::Completions::Flags) [with Completer = Kakoune::Completions (&)(const Kakoune::Context&, Kakoune::CompletionFlags, Kakoune::StringView, Kakoune::ByteCount)]’
commands.cc:118:21:   required from ‘auto Kakoune::{anonymous}::menu(Completer&&) [with Completer = Kakoune::Completions (&)(const Kakoune::Context&, Kakoune::CompletionFlags, Kakoune::StringView, Kakoune::ByteCount)]’
commands.cc:712:51:   required from here
commands.cc:109:27: error: field ‘Kakoune::{anonymous}::add_flags(Completer&&, Kakoune::Completions::Flags) [with Completer = Kakoune::Completions (&)(const Kakoune::Context&, Kakoune::CompletionFlags, Kakoune::StringView, Kakoune::ByteCount)]::<lambda(const Kakoune::Context&, Kakoune::CompletionFlags, const Kakoune::String&, Kakoune::ByteCount)>::<completer capture>’ invalidly declared function type
g++  -O3 -pedantic -std=c++17 -g -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare -Wno-address -MD -MP -MF .insert_completer.opt.d -c -o .insert_completer.opt.o insert_completer.cc
g++  -O3 -pedantic -std=c++17 -g -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare -Wno-address -MD -MP -MF .json_ui.opt.d -c -o .json_ui.opt.o json_ui.cc
g++  -O3 -pedantic -std=c++17 -g -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare -Wno-address -MD -MP -MF .keymap_manager.opt.d -c -o .keymap_manager.opt.o keymap_manager.cc
g++  -O3 -pedantic -std=c++17 -g -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare -Wno-address -MD -MP -MF .keys.opt.d -c -o .keys.opt.o keys.cc
g++  -O3 -pedantic -std=c++17 -g -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare -Wno-address -MD -MP -MF .line_modification.opt.d -c -o .line_modification.opt.o line_modification.cc
g++  -O3 -pedantic -std=c++17 -g -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare -Wno-address -MD -MP -MF .main.opt.d -c -o .main.opt.o main.cc
g++  -O3 -pedantic -std=c++17 -g -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare -Wno-address -MD -MP -MF .memory.opt.d -c -o .memory.opt.o memory.cc
g++ -D_GNU_SOURCE -D_DEFAULT_SOURCE -I/usr/include/ncursesw -O3 -pedantic -std=c++17 -g -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare -Wno-address -MD -MP -MF .ncurses_ui.opt.d -c -o .ncurses_ui.opt.o ncurses_ui.cc
g++  -O3 -pedantic -std=c++17 -g -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare -Wno-address -MD -MP -MF .normal.opt.d -c -o .normal.opt.o normal.cc
Makefile:102: recipe for target '.commands.opt.o' failed
make: *** [.commands.opt.o] Error 1

Which version of gcc are you using ?

Should be fixed (force pushed)

gcc (Ubuntu 7.4.0-1ubuntu1~18.04) 7.4.0

This is working nicely for me!

I really like this change, enough that when switching boxes it instantly made me feel broken.

Any suggestion for a better naming (do you think -menu is a good switch ?)

-bestMatch? -closestMatch?