Use fdfind and fzf to open files in mpv - lua

kdialog-open-files.lua and zenity-open-files.lua are two projects that use KDE KDialog and Gnome's zenity respectively to open files in mpv. I wish to do the same using fdfind and fzf.
As illustrated in this stackoverflow answer the following works and will be our starting point:
fdfind . /path/to/Music | fzf -m | xargs -d "\n" mpv
We can use the above command to select some files and play them using mpv. However we want a dialog to be created for our file selection. To do this, we create a bash script menu_fzf.sh with the following contents:
#!/bin/bash
urxvt -name fzf_menu -e bash -c "fzf -m $* < /proc/$$/fd/0 > /proc/$$/fd/1"
(urxvt is only an example and any other suitable terminal emulator can be used in the above).
Now if we run:
fdfind . /path/to/Music | menu_fzf.sh
then a new terminal opens that lets you pick multiple files from /path/to/Music with fzf and will return the selection as output to the calling terminal. So in fact right now we could run the following
fdfind . /path/to/Music | menu_fzf.sh | xargs -d "\n" mpv
and it will play the selected files in mpv. So far so good!
The tricky part now is how to do all the above using lua scripts from within mpv. By looking at the kdialog-open-files.lua or zenity-open-files.lua it seems I need to write something like this:
utils = require 'mp.utils'
-- TODO: make the following work so that file_select correctly stores
-- the files selected using menu_fzf.sh:
file_select = utils.subprocess({
args = {'command', 'argument1', 'argument2', 'and so on'},
cancellable = false,
})
-- and then the rest of the code from kdialog-open-files.lua
However I am finding the official manual for utils.subprocess to be terse and inadequate. I have creatively tried picking command, argument1, argument2, and so on in the above, but nothing seems to work!
So I have two questions:
Can you (please) complete the above code template into a working solution?
Do you know where I can find more information on utils.subprocess? Is there a more detailed manual somewhere? Where is the source-code?
Remark: As I have never programmed in lua before I am struggling with solving question-1 above and will probably continue to struggle even if I had the source code for utils.subprocess. So really help with question-1 is of higher priority. But any help with question-2 is also great!
My failed attempts so far:
The following will append all files in current directory to playlist:
file_select = utils.subprocess({
args = {'fdfind', '.', directory},
cancellable = false,
})
The following fails:
file_select = utils.subprocess({
args = {'fdfind', '.', directory, '|', 'fzf'},
cancellable = false,
})
with error message:
[open_files] [fd error]: Search path '|' is not a directory.
[open_files] [fd error]: Search path 'fzf' is not a directory.
The following allows selecting multiple files from $HOME directory but (as expected) does not return the selection:
file_select = utils.subprocess({
args = {'urxvt','-e','fzf', '-m'},
cancellable = false,
})
The following doesn't work:
file_select = utils.subprocess({
args = {'urxvt','-e','fzf', '-m', '>', '/proc/$$/fd/1'},
cancellable = false,
})
The following opens up a new dialog with fzf but has nothing to select from:
file_select = utils.subprocess({
args = {'menu_fzf.sh'},
cancellable = false,
})

Create a bash script mpv_fzf_menu.sh with the following content:
#!/bin/bash
urxvt -name fzf_menu -e bash -c "fdfind . "$1" | fzf -m > /proc/$$/fd/1"
Create a lua file open-files.lua with the following content:
utils = require 'mp.utils'
function select_files_dmenu_fzf()
if mp.get_property("path") == nill then
directory = ""
else
directory = utils.split_path(utils.join_path(mp.get_property("working-directory"), mp.get_property("path")))
end
file_select = utils.subprocess({
args = {'mpv_fzf_menu.sh', directory},
cancellable = false,
})
end
function add_files()
select_files_dmenu_fzf()
if (file_select.status ~= 0) then return end
local first_file = true
for filename in string.gmatch(file_select.stdout, '[^\n]+') do
mp.commandv('loadfile', filename, first_file and 'replace' or 'append')
first_file = false
end
end
mp.add_key_binding("Ctrl+f", "add-files-kdialog", add_files)
(I am assuming you know what to do next: make the bash script executable and put it somewhere where bash finds it in its $PATH. Put the lua script in .config/mpv/scripts and everything should work, i.e. - when you press Ctrl+f within mpv a dialog should open to let you select files).
Remark: All the functionalities of kdialog-open-files.lua have not been implemented. But the above lua script can be easily extended. You can further make things fancy by filtering for files with the specified extension with fdfind. And you can also use the bash script independent of the lua script. If you configure the --preview option of fzf then you can get a file preview before making selection.

Related

LSP server on_init per project configuration

I'm noob in Lua and after many hours of searching, it wasn't successful.
I want to load some EFM language server configurations depends on a project setup. So, I found this: https://github.com/neovim/nvim-lspconfig/wiki/Project-local-settings
For example: in A project I have a "black" code formatter, in B project I have "yapf" code formatter.
After that I wrote custom on_init handler:
...
on_init = function(client)
-- We need to register only available linters and formatters, because it improves perfomance↴
local result = vim.fn.systemlist(
[[ poetry run pip list --disable-pip-version-check | grep -w -o '^black \|^yapf \|^flake8 \|^isort \|^mypy ' ]]
for _, package_name in pairs(result) do
local package_config = M[string.gsub(package_name, "%s+", "")]
if package_config then
table.insert(client.config.settings.languages.python, package_config)
end
end
client.notify("workspace/didChangeConfiguration")
return true
end,
...
Well, it works, but i have a problem with a custom command, it's sync and freezes UI on first LSP load. Is it possible to load on_init async or execute cmd async?

Capture output of shell command, line by line, into an array

I want to capture the total number of rubocop offenses to determine whether my codebase is getting better or worse. I have almost no experience with ruby scripting.
This is my script so far:
#!/usr/bin/env ruby
#script/code_coverage
var = `rubocop ../ -f fuubar -o ../coverage/rubocop_results.txt -f offenses`
puts var
So I ./rails-app/script/code_coverage and my terminal displays
...
--
6844 Total
When I var.inspect I get a long string. I want to either read the output of rubocop ../ -f ... line by line (preferred) or capture each line of output into an array.
I would like to be able to do something like this:
var.each |line| do
if line.contains('total')
...
total = ...
end
Is this possible? I guess this would be similar to writing the output to a file and and then reading the file in line by line.
If you want to keep it simple, you can simply split your var string.
var = `rubocop ../ -f fuubar -o ../coverage/rubocop_results.txt -f offenses`.split("\n")
Then you can iterate on var like you wanted to.
use open3 library of ruby. Open3 grants you access to stdin, stdout, stderr and a thread to wait the child process when running another program. You can specify various attributes, redirections, current directory, etc., of the program as Process.spawn.
http://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html
require 'open3'
# Run lynx on this file.
cmd = "lynx -crawl -dump /data/feed/#{file_name}.html > /data/feed/#{file_name}"
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
cmdout = stdout.read
$logger.debug "stdout is:" + stdout.read
$logger.debug "stderr is:" + stderr.read
end

error while executing lua script for redis server

I was following this simple tutorial to try out a simple lua script
http://www.redisgreen.net/blog/2013/03/18/intro-to-lua-for-redis-programmers/
I created a simple hello.lua file with these lines
local msg = "Hello, world!"
return msg
And i tried running simple command
EVAL "$(cat /Users/rsingh/Downloads/hello.lua)" 0
And i am getting this error
(error) ERR Error compiling script (new function): user_script:1: unexpected symbol near '$'
I can't find what is wrong here and i haven't been able to find someone who has come across this.
Any help would be deeply appreciated.
Your problem comes from the fact you are executing this command from an interactive Redis session:
$ redis-cli
127.0.0.1:6379> EVAL "$(cat /path/to/hello.lua)" 0
(error) ERR Error compiling script (new function): user_script:1: unexpected symbol near '$'
Within such a session you cannot use common command-line tools like cat et al. (here cat is used as a convenient way to get the content of your script in-place). In other words: you send "$(cat /path/to/hello.lua)" as a plain string to Redis, which is not Lua code (of course), and Redis complains.
To execute this sample you must stay in the shell:
$ redis-cli EVAL "$(cat /path/to/hello.lua)" 0
"Hello, world!"
If you are coming from windows and trying to run a lua script you should use this format:
redis-cli --eval script.lua
Run this from the folder where your script is located and it will load a multi line file and execute it.
On the off chance that anyone's come to this from Windows instead, I found I had to do a lot of juggling to achieve the same effect. I had to do this:
echo “local msg = 'Hello, world!'; return msg” > hello.lua
for /F "delims=" %i in ('type hello.lua') do #set cmd=%i
redis-cli eval "%cmd%" 0
.. if you want it saved as a file, although you'll have to have all the content on one line. If you don’t just roll the content into a set command
set cmd=“local msg = 'Hello, world!'; return msg”
redis-cli eval "%cmd%" 0

Shell: Find Matching Lines Across Many Files

I am trying to use a shell script (well a "one liner") to find any common lines between around 50 files.
Edit: Note I am looking for a line (lines) that appears in all the files
So far i've tried grep grep -v -x -f file1.sp * which just matches that files contents across ALL the other files.
I've also tried grep -v -x -f file1.sp file2.sp | grep -v -x -f - file3.sp | grep -v -x -f - file4.sp | grep -v -x -f - file5.sp etc... but I believe that searches using the files to be searched as STD in not the pattern to match on.
Does anyone know how to do this with grep or another tool?
I don't mind if it takes a while to run, I've got to add a few lines of code to around 500 files and wanted to find a common line in each of them for it to insert 'after' (they were originally just c&p from one file so hopefully there are some common lines!)
Thanks for your time,
When I first read this I thought you were trying to find 'any common lines'. I took this as meaning "find duplicate lines". If this is the case, the following should suffice:
sort *.sp | uniq -d
Upon re-reading your question, it seems that you are actually trying to find lines that 'appear in all the files'. If this is the case, you will need to know the number of files in your directory:
find . -type f -name "*.sp" | wc -l
If this returns the number 50, you can then use awk like this:
WHINY_USERS=1 awk '{ array[$0]++ } END { for (i in array) if (array[i] == 50) print i }' *.sp
You can consolidate this process and write a one-liner like this:
WHINY_USERS=1 awk -v find=$(find . -type f -name "*.sp" | wc -l) '{ array[$0]++ } END { for (i in array) if (array[i] == find) print i }' *.sp
old, bash answer (O(n); opens 2 * n files)
From #mjgpy3 answer, you just have to make a for loop and use comm, like this:
#!/bin/bash
tmp1="/tmp/tmp1$RANDOM"
tmp2="/tmp/tmp2$RANDOM"
cp "$1" "$tmp1"
shift
for file in "$#"
do
comm -1 -2 "$tmp1" "$file" > "$tmp2"
mv "$tmp2" "$tmp1"
done
cat "$tmp1"
rm "$tmp1"
Save in a comm.sh, make it executable, and call
./comm.sh *.sp
assuming all your filenames end with .sp.
Updated answer, python, opens only each file once
Looking at the other answers, I wanted to give one that opens once each file without using any temporary file, and supports duplicated lines. Additionally, let's process the files in parallel.
Here you go (in python3):
#!/bin/env python
import argparse
import sys
import multiprocessing
import os
EOLS = {'native': os.linesep.encode('ascii'), 'unix': b'\n', 'windows': b'\r\n'}
def extract_set(filename):
with open(filename, 'rb') as f:
return set(line.rstrip(b'\r\n') for line in f)
def find_common_lines(filenames):
pool = multiprocessing.Pool()
line_sets = pool.map(extract_set, filenames)
return set.intersection(*line_sets)
if __name__ == '__main__':
# usage info and argument parsing
parser = argparse.ArgumentParser()
parser.add_argument("in_files", nargs='+',
help="find common lines in these files")
parser.add_argument('--out', type=argparse.FileType('wb'),
help="the output file (default stdout)")
parser.add_argument('--eol-style', choices=EOLS.keys(), default='native',
help="(default: native)")
args = parser.parse_args()
# actual stuff
common_lines = find_common_lines(args.in_files)
# write results to output
to_print = EOLS[args.eol_style].join(common_lines)
if args.out is None:
# find out stdout's encoding, utf-8 if absent
encoding = sys.stdout.encoding or 'utf-8'
sys.stdout.write(to_print.decode(encoding))
else:
args.out.write(to_print)
Save it into a find_common_lines.py, and call
python ./find_common_lines.py *.sp
More usage info with the --help option.
Combining this two answers (ans1 and ans2) I think you can get the result you are needing without sorting the files:
#!/bin/bash
ans="matching_lines"
for file1 in *
do
for file2 in *
do
if [ "$file1" != "$ans" ] && [ "$file2" != "$ans" ] && [ "$file1" != "$file2" ] ; then
echo "Comparing: $file1 $file2 ..." >> $ans
perl -ne 'print if ($seen{$_} .= #ARGV) =~ /10$/' $file1 $file2 >> $ans
fi
done
done
Simply save it, give it execution rights (chmod +x compareFiles.sh) and run it. It will take all the files present in the current working directory and will make an all-vs-all comparison leaving in the "matching_lines" file the result.
Things to be improved:
Skip directories
Avoid comparing all the files two times (file1 vs file2 and file2 vs file1).
Maybe add the line number next to the matching string
Hope this helps.
Best,
Alan Karpovsky
See this answer. I originally though a diff sounded like what you were asking for, but this answer seems much more appropriate.

How can I tell from a within a shell script if the shell that invoked it is an interactive shell?

I'm trying to set up a shell script that will start a screen session (or rejoin an existing one) only if it is invoked from an interactive shell. The solution I have seen is to check if $- contains the letter "i":
#!/bin/sh -e
echo "Testing interactivity..."
echo 'Current value of $- = '"$-"
if [ `echo \$- | grep -qs i` ]; then
echo interactive;
else
echo noninteractive;
fi
However, this fails, because the script is run by a new noninteractive shell, invoked as a result of the #!/bin/sh at the top. If I source the script instead of running it, it works as desired, but that's an ugly hack. I'd rather have it work when I run it.
So how can I test for interactivity within a script?
Give this a try and see if it does what you're looking for:
#!/bin/sh
if [ $_ != $0 ]
then
echo interactive;
else
echo noninteractive;
fi
The underscore ($_) expands to the absolute pathname used to invoke the script. The zero ($0) expands to the name of the script. If they're different then the script was invoked from an interactive shell. In Bash, subsequent expansion of $_ gives the expanded argument to the previous command (it might be a good idea to save the value of $_ in another variable in order to preserve it).
From man bash:
0 Expands to the name of the shell or shell script. This is set
at shell initialization. If bash is invoked with a file of com‐
mands, $0 is set to the name of that file. If bash is started
with the -c option, then $0 is set to the first argument after
the string to be executed, if one is present. Otherwise, it is
set to the file name used to invoke bash, as given by argument
zero.
_ At shell startup, set to the absolute pathname used to invoke
the shell or shell script being executed as passed in the envi‐
ronment or argument list. Subsequently, expands to the last
argument to the previous command, after expansion. Also set to
the full pathname used to invoke each command executed and
placed in the environment exported to that command. When check‐
ing mail, this parameter holds the name of the mail file cur‐
rently being checked.
$_ may not work in every POSIX compatible sh, although it probably works in must.
$PS1 will only be set if the shell is interactive. So this should work:
if [ -z "$PS1" ]; then
echo noninteractive
else
echo interactive
fi
try tty
if tty 2>&1 |grep not ; then echo "Not a tty"; else echo "a tty"; fi
man tty :
The tty utility writes the name of the terminal attached to standard
input to standard output. The name that is written is the string
returned by ttyname(3). If the standard input is not a terminal, the
message ``not a tty'' is written.
You could try using something like...
if [[ -t 0 ]]
then
echo "Interactive...say something!"
read line
echo $line
else
echo "Not Interactive"
fi
The "-t" switch in the test field checks if the file descriptor given matches a terminal (you could also do this to stop the program if the output was going to be printed to a terminal, for example). Here it checks if the standard in of the program matches a terminal.
Simple answer: don't run those commands inside ` ` or [ ].
There is no need for either of those constructs here.
Obviously I can't be sure what you expected
[ `echo \$- | grep -qs i` ]
to be testing, but I don't think it's testing what you think it's testing.
That code will do the following:
Run echo \$- | grep -qs i inside a subshell (due to the ` `).
Capture the subshell's standard output.
Replace the original ` ` expression with a string containing that output.
Pass that string as an argument to the [ command or built-in (depending on your shell).
Produce a successful return code from [ only if that string was nonempty (assuming the string didn't look like an option to [).
Some possible problems:
The -qs options to grep should cause it to produce no output, so I'd expect [ to be testing an empty string regardless of what $- looks like.
It's also possible that the backslash is escaping the dollar sign and causing a literal 'dollar minus' (rather than the contents of a variable) to be sent to grep.
On the other hand, if you removed the [ and backticks and instead said
if echo "$-" | grep -qs i ; then
then:
your current shell would expand "$-" with the value you want to test,
echo ... | would send that to grep on its standard input,
grep would return a successful return code when that input contained the letter i,
grep would print no output, due to the -qs flags, and
the if statement would use grep's return code to decide which branch to take.
Also:
no backticks would replace any commands with the output produced when they were run, and
no [ command would try to replace the return code of grep with some return code that it had tried to reconstruct by itself from the output produced by grep.
For more on how to use the if command, see this section of the excellent BashGuide.
If you want to test the value of $- without forking an external process (e.g. grep) then you can use the following technique:
if [ "${-%i*}" != "$-" ]
then
echo Interactive shell
else
echo Not an interactive shell
fi
This deletes any match for i* from the value of $- then checks to see if this made any difference.
(The ${parameter/from/to} construct (e.g. [ "${-//[!i]/}" = "i" ] is true iff interactive) can be used in Bash scripts but is not present in Dash, which is /bin/sh on Debian and Ubuntu systems.)

Resources