1

I have the following windows.json file

[{
    "id":5178,
    "pid":40345,
    "app":"Alacritty",
    "title":"Alacritty",
    "frame":{
        "w":880.0000,
        "h":531.0000
    }
},{
    "id":5780,
    "pid":44378,
    "app":"Skim",
    "title":"Brezis.pdf",
    "frame":{
        "w":980.0000,
        "h":532.0000
    }
},{
    "id":5771,
    "pid":44378,
    "app":"Skim",
    "title":"struct.pdf",
    "frame":{
        "w":980.0000,
        "h":532.0000
    }
}]

and I'd like to do three things:

1) get the elements whose app is "Skim" and store them in a variable;

2) count how many elements there are with the app "Skim", and store that in a variable.

3) get the titles of all the elements with the app "Skim", and store them in a list.

I can do the first two in a shell with

$ Skim_windows=$(cat ~/windows.json | jq -r ".[] | select(.app==\"Skim\")")
$ Skim_windows_count=$(echo $Skim_windows | jq -r ".id" | wc -l)

and I'm trying to replicate that inside a vim function. The code I currently have is

let Skim_windows = system("cat ~/windows.json | jq -r \".[] | select(.app==\\\"Skim\\\")\"")
let Skim_windows_count = system("echo ".shellescape(Skim_windows,1).(" | jq -r \".id\" | wc -l"))

which doesn't work, because if I echo Skim_windows_count I get the error

parse error: Invalid numeric literal at line 2, column 0

I also have no idea how to get all the titles and assign them to a list (in vim).

2 Answers 2

6

VimScript has a JSON parser of its own. There's no need to invoke an external utility.

A sample code:

" read in file
let s:js_data = readfile('windows.json')
" decode into Vim variable
let s:list = js_decode(join(s:js_data))
" filter out the skims
let s:skims = filter(s:list, 'v:val.app=="Skim"')
" do whatever...
echo s:skims
echo len(s:skims)
for s:item in s:skims
    echom s:item.title
endfor
5

As @Matt points out, you can parse JSON natively in Vimscript, that's the best solution to your problem.

But still, two main recommendations here, for interacting with external commands from Vimscript:

  1. Use systemlist() instead of system(). systemlist() returns a list of lines, which ends up being more natural to handle in Vimscript.

  2. Both systemlist() and system() take a second optional {input} argument, with a list of lines that is to be passed to the external command as its standard input.

With the two of these together, you can implement this all without using any pipes at all. It might also help with quoting, you won't need to do too much shell quoting anymore.


1) get the elements whose app is "Skim" and store them in a variable;

2) count how many elements there are with the app "Skim", and store that in a variable.

let windows_lines = readfile(expand('~/windows.json'))
let skim_windows = systemlist('jq -r ".[] | select(.app==\"Skim\")"', windows_lines)
let skim_windows_ids = systemlist('jq -r ".id"', skim_windows)
let skim_windows_count = len(skim_windows_id)

Since you have a list in Vim, you can simply use len(...) to get its length, bypassing the need for an external wc -l call.

(Note that you could also merge the two jq commands into one, but I kept your original expressions to illustrate how you can still chain these together with multiple calls to systemlist().)

3) get the titles of all the elements with the app "Skim", and store them in a list.

let skim_windows_titles = systemlist('jq -r ".title"', skim_windows)

You could have used the titles for counting too.

Perhaps simpler is:

let windows_lines = readfile(expand('~/windows.json'))
let skim_windows_titles = systemlist('jq -r ".[] | select(.app==\"Skim\") | .title"', windows_lines)
let skim_windows_count = len(skim_windows_titles)

And if you happen to be editing the windows.json file in the current buffer, you can then use:

let windows_lines = getline(1, '$')

To get the contents of the current buffer as a list of lines.

1
  • 2
    I'm just starting getting my feet wet with VimScript and comments like this really do help a lot. Thanks for taking the time.
    – noibe
    May 1, 2020 at 15:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.