vim-test / vim-test/vim-test

Transforming program arguments to JSON

Open
#563 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Vim Script
Stars
3.2k
Forks
411
Avg merge
1h 30m
Merged PRs (30d)
5

Description

Hello. I have used vim-test for a long time now and it has made testing in vim a real joy, thank you for this plugin.

I have recently started trying to get a debugger into my testing workflow when running tests with neovim, and found vimspector. It works surprisingly well, and I thought I would try to get something working so I can use it with the convenience of vim-test.

vimspector uses the vscode-node debug adapter for javascript/typescript tests. In practice, this means I need to configure how the test is invoked by modifying a file in the working directory, .vimspector.json, that looks similar to vscode's launch.json, i.e. something like this:

{
  "configurations": {
    "run": {
      "adapter": "vscode-node",
      "configuration": {
        "request": "launch",
        "protocol": "auto",
        "stopOnEntry": true,
        "console": "integratedTerminal",
        "program": "${workspaceRoot}/node_modules/.bin/jest",
        "args": ["--", "./src/test.js"],
        "cwd": "${workspaceRoot}"
      }
    }
  }
}

In short I need to convert the test command to program and args values.

I tried my hand at integrating a vim-test custom strategy:

function VimTestVimspectorStrategy(cmd)
  if !(filereadable(".vimspector.json"))
    echo "No '.vimspector.json' found, starting with vscode-node sample"
    system('cp ~/.vimspector.json.sample ./.vimspector.json')
  endif

  let l:vimspector_config = json_decode(join(readfile('.vimspector.json')))

  " Assign program
  let l:program = split(a:cmd, ' ')[0]
  let l:vimspector_config['configurations']['run']['configuration']['program'] =
        \ '${workspaceFolder}/' . l:program

  " Assign program_args
  " Remove the program from the string
  let l:program_args = join(split(a:cmd, " ")[1:], " ")

  " split by spaces unless within quotes.  No good way to do this with vimscript
  " TODO: is this escaping enough?
  let l:split_cmd = 'printf "%s" "' . escape(l:program_args, '"') . '" | xargs -n 1 printf "%s\n"'
  let l:print_output = system(l:split_cmd)
  let l:program_args = split(l:print_output, "\n")

  " Re-quote strings with spaces in them
  call map(l:program_args, { _, arg -> match(arg, ' ') >= 0 ? "'" . arg . "'" : arg })

  let l:vimspector_config['configurations']['run']['configuration']['args'] = l:program_args

  call writefile(split(json_encode(l:vimspector_config), "\n"), glob('.vimspector.json'), 'b')

  " Start the debugger
  call vimspector#Continue()
endfunction

let g:test#custom_strategies = {
      \ ...
      \ 'vimspector': function('VimTestVimspectorStrategy'),
      \ }

nnoremap <space>dn :TestNearest -strategy=vimspector<CR>
nnoremap <space>df :TestFile -strategy=vimspector<CR>
nnoremap <space>ds :TestSuite -strategy=vimspector<CR>

A bit scrappy, but it works! Since I needed the program arguments to be a JSON array, splitting the single a:cmd into this array was very difficult since it can include search patterns wrapped in quotes, for example:

echo a:cmd
" node_modules/.bin/jest --no-coverage -t '^this is a test name$' -- src/example/vimspector.test.ts

into

{
  "configurations": {
    "run": {
      "configuration": {
        ...
        "program": "${workspaceFolder}/node_modules/.bin/jest",
        "args": [
          "--no-coverage",
          "-t",
          "'^this is a test name$'",
          "--",
          "src/example/vimspector.test.ts"
        ],
        ...
    }
  }
}

What I'm wondering is, is there any better approach for this? Is there some API where I could receive the program arguments as a list before they are joined together into 1 string? For example if the custom_strategies dictionary could use a special token in the key to indicate that the parameters are passed as a list instead of a joined string. For example:

let g:test#custom_strategies = {
      \ 'vimspector*': function('VimTestVimspectorStrategy'),
      \ }

(fzf does something similar, by allowing users to specify a sink or sink* key to fzf#run() function to change how the callback function is invoked).

However I suppose this wouldn't work with the :TestNearest --strategy=vimspector syntax? Not quite sure, just throwing it out there.

Any guidance is highly appreciated, thanks for your time.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

The issue names custom_strategies and the :TestNearest, :TestFile, and :TestSuite entry points; start by tracing how these commands invoke a strategy and turn command arguments into a:cmd. Compare that path with the requested list-preserving callback behavior. Done means a strategy can receive program arguments as a JSON-ready list while preserving existing --strategy=vimspector usage and the quoted-argument examples.

Written by the indexing model from the issue text.

Assessment

Tech stack
vim
Domain
testing, tooling
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.