Filter your selections more easily

Inspired by @gustavo-hms’s post Array/iterator and looping behaviour in kak script

The following snippets is build on top of kakoune’s $ command.
It helps you filter your selections more conveniently by using Boolean expression from more expressive language ( lua and python supported for the moment)

Example:

Lets say you have the following text:

Só outro silêncio. O senhor sabe o que o silêncio é? É a gente mesmo, demais

And you select all the word with S<space><ret>
You should have:

[Só] [outro] [silêncio]. [O] [senhor] [sabe] [o] [que] [o] [silêncio] [é]? [É] [a] [gente] [mesmo], [demais].

Now let’s keep only the selections of word longer than 5 character, using our plugin and a simple python expression. sel is the default name of the variable holding the selection:

$ kak_filter.py "len(sel) > 5"

You should have:

Só [outro] [silêncio.] O [senhor] [sabe] o que o [silêncio] é? É a [gente] [mesmo,] [demais]

You could also use more complex expression:

$ kak_filter.py "len(sel) > 3 and 'o' in sel"

To get:

Só [outro] [silêncio.] O [senhor] sabe o que o [silêncio] é? É a gente [mesmo,] demais

Install

Paste one of the following into an executable file in a directory within your $PATH:

python script

kak_filter.py

#! /usr/bin/env python3.7

from sys import argv

f = 'lambda sel: ' + argv[1]
filter = eval(f)

def main(input):
    if filter(input):
        exit(0)
    else:
        exit(1)

main(input())

lua script

kak_filter.lua

#! /usr/bin/env lua

f = 'function (sel) return ' .. arg[1] .. ' end'
func, err = load('return ' .. f)
ok, filter = pcall(func)
function main (input)
    if filter(input) then
        os.exit(0)
    else
        os.exit(1)
    end
end
main(io.read())

mapping

I suggest to also put the following mapping inside your kakrc:

map global normal <a-$> '$ kak_filter.py ""<left>'

7 Likes

Thanks for these wrappers.

I’ve added the link to this discussion on a wiki page which was dealing with this filtering problematic but with a shell only approach: https://github.com/mawww/kakoune/wiki/Selections#filtering-reducing-the-number-of-multiple-selections

2 Likes

Thx, I remember now that I pumped into this wiki page a while ago.

I tried to implement the selection based on index feature, but couldn’t grab the kak_main_reg_hash variable. I used
os.environ['kak_main_reg_hash'] but didn’t work. Any idea why ?

OK. got it.

Apparently $kak_main_reg_hash is evaluated inside kakoune’s prompt. It doesn’t exist outside.

The solution:
$kak_filter.py "$kak_main_reg_hash % 2 == 0"

It’s a bit more subtle than that. Before Kakoune runs a shell command (such as a %sh{} block, or the $ command, or the | command), it scans the text for kak_* tokens. If it sees any it recognises, it exports those values into the environment where they’ll be accessible to all downstream processes. If it doesn’t see any, it exports nothing.

Taking an example from :doc expansions shell-expansions, if you run this command:

echo %sh{ env | grep ^kak_ }

… you get no output, because Kakoune doesn’t put any kak_* variables into the environment. However, if you run this command:

echo %sh{ env | grep ^kak_ # kak_session }

…then the session ID gets printed, because the shell block contains kak_session, even though it’s just in a comment.

1 Like

Interesting. Is this for performance issue ? Why not exporting all the values ?

I don’t know about performance specifically, but Kakoune has a lot of options and values, and many of them can be expanded in different ways (kak_*, kak_main_*, kak_quoted_*) which I can imagine might add a lot of setup overhead for every shell process.

Nice idea, @scr!

Let me share then I trick I’ve been using for mapping things with the | key. I made this script

#!/usr/bin/env lua
local command = arg[1]

local map = assert(load("return " .. command))

for line_ in io.lines() do
    _ = line_
	print((map()))
end

wrote it to a file called map, made it executable (chmod +x map) and put it on my PATH.

Then, from within Kakoune, I can change selections with, for instance, | map 'tonumber(_) + 1'. Here, _ is the line coming from stdin.

Some things to note:

  • it uses a loop to iterate over the lines. Although it’s not really necessary with the | key, it’s useful to run simple one line scripts from a pipe on a terminal:
cat "music.txt" | map '_:find("Forró") and "Found!" or "Not found!"'
  • since it iterates over the lines, multi line operations aren’t possible. But it’s easy to extend the plugin to handle them, since Lua patterns (the equivalent of regular expressions in Lua’s standard library) work across lines by default.

And to make the script even more handy, I implemented a split function to split strings (since the string module don’t have such a function) and made some aliases for useful functions:

#!/usr/bin/lua

concat = table.concat
sort = table.sort
format = string.format
unpack = table.unpack

function split(s, boundary)
    local t = {}
    local pattern = format("[^%s]+", boundary)
    for elem in s:gmatch(pattern) do
        t[#t+1] = elem
    end
    return unpack(t)
end

local command = arg[1]

local map = assert(load("return " .. command))

for line_ in io.lines() do
    _ = line_
	print((map()))
end
1 Like