4

In the type of c programming that I do day to day, I often have to "fill" struct members in order to pass them to an API. For example:

struct measurement {
double start_time;
double end_time;
double elapsed_ms;
// …
}

struct measurement my_measurement;
my_measurement.start_time = 0.0;
my_measurement.end_time = 1.0;
my_measurement.elapsed_time = 1.0;
// …

This can get pretty tedious, so I was wondering if there's a plugin that would just expand every field of the struct when pressing a keymap. For example if you imagine that | is my cursor:

struct measurement my_measurement;| <leader><tab>

Vim would output:

my_measurement.start_time = ;
my_measurement.end_time = ;
my_measurement.elapsed_time = ;
// ...
  • 2
    If you could get the struct members (e.g. from a tags file), writing the function to generate this would be do-able. – D. Ben Knoble Dec 26 '19 at 22:26
6

readtags

universal-ctags provide readtags to filter tags:

Read all members of a struct:

readtags -Q '(and (eq? $kind "member") (eq? $scope-name "struct_name") )' -l
  • (and (...) (...)): (...) and (...)
  • (eq? $kind "member") restrict kind to member
  • (eq? $scope-name "struct_name") restrict scope-name to struct_name, your tag fields must include Z for this to work.

Read all enumerators of a enum:

readtags -Q '(and (eq? $kind "enumerator") (eq? $scope-name "enum_name") )' -l

Read members or enumerators:

readtags -Q '(and
             (or (prefix? "member" $kind) (prefix? "enumerator" $kind) )
             (eq? $scope-name "galaxy::BulletClip")
             )' -l

(prefix? ...) is used instead of (eq? ...) to make sure it works for both single letter and full name kind.

Shell script

One drawback of readtags is that it can not read from multiple tag files. We must call it for every tags in :h tagfiles() . It's also a bit hard to use directly, so I build a small script on top of it, I call it readtagsi, it's used to read tag that have a scope.

Examples:

Get member kind, output name only

readtagsi -ok m struct_name

Get member or enumerator kind, output name only

readtagsi -ok m -k e scope_name

Call it in vim

At last create trivial map and command in vim:

nnoremap _m :ReadtagsI -ok m -k e <c-r><c-A><cr>
com -nargs=+ ReadtagsI call s:readtags_i(<q-args>)

function s:readtags_i(args) abort
  let cmd = printf( 'readtagsi %s %s',
              \ join( map( tagfiles(), { i,v-> printf('-t "%s"', v)  } ) ),
              \ a:args )
  call append( line('.'), systemlist(cmd) )
endfunction

Now you can place your cursor at struct or enum name, and do _m

| improve this answer | |
  • Shell scripts should generally set -e and pipefail as well. I prefer to see ifs then chained && (it’s not technically the same). And more functions would be nice. But I learned about readtags today, so +1 from me – D. Ben Knoble Dec 27 '19 at 13:27
  • @D.BenKnoble Thanks, I'm updating it, I was trying to make the code shorter, so i collapsed the ifs to chained &&, are their any difference between if cond ; then dostuff ; fi and cond && dostuff ? – dedowsdi Dec 27 '19 at 13:59
  • It mostly matters for else (which is not equivalent to ||), but in e.g. A && B && C, C may never run (if B fails). – D. Ben Knoble Dec 27 '19 at 14:00
  • @D.BenKnoble Updated. – dedowsdi Dec 27 '19 at 14:26
  • 1
    Late to the party but I noticed the comment "shell scripts should generally set -e". Unfortunately, that flag is plagued with issues. The rules are often impenetrable and some seemingly obvious error conditions are missed. And problems are magnified by the nightmarish mishmash of differences from one shell version to the next. Just FYI. – B Layer Aug 12 at 4:53
1

At this time, I have something extremely similar in my lh-cpp plugin for C++. Within a class context, I type :Constructor init, and my plugin will fetch all the member data (thanks to the API of two other plugins of mine: lh-dev + lh-tags) and generate the constructor. We aren't far from what you wish to accomplish.

The first step will be to extract the type of your variable. Then, we just need to run (universal!) ctags on the current buffer (in its present state, not necessarily saved), extract the attributes and transform them into lines.

Expecting the cursor is on the line where the variable is defined, it can be done with the following

function! s:init_struct(...) abort
  " 1- extract the struct type
  let linenr = get(a:, 1, '.')
  let [all, struct, variable; tail] = matchlist(getline(linenr), '\v<struct>\s+(\k+)\s+(\k+)')
  if empty(struct)
    throw "no struct variable definition on this line"
  endif

  " 2- extract the members
  " from lh-dev + lh-tags
  let attributes = lh#dev#class#attributes(struct, 1)

  " 3- transform the members into the lines to produce
  let lines = map(copy(attributes), {k,v -> substitute(v.name, v.struct.'::', variable.'.', ''). ' = '.lh#marker#txt().';'})

  " 4- insert them!
  put=lines

  " 5- and align!
  '[,']normal! ==
endfunction

command! -nargs=0 InitStruct echo s:init_struct()

NB: that code requires lh-dev, lh-tags, and lh-vim-lib. The lh#marker#txt() function produces a placeholder and comes from lh-brackets. If you use a snippet plugin other than my mu-template one (which is more than likely), you will have use its approach to produce and lines and the placeholders where the cursor will be able to jump to.

| improve this answer | |
0

Here is a dirty and incomplete but "more or less" working solution without any external dependencies.

" search and expand struct fields at cursor position
" input: [tagname, varname]
function s:expand_struct(id) abort
    if empty(a:id)
        return
    endif
    " save current buffer and line number
    let [l:bufnr, l:lnum] = [bufnr(), line('.')]
    " jump to struct definition (current or included file)
    " note: fails if no match is found
    execute 'hide ijump' a:id[0]
    " copy everything inside {}
    normal! ]my]M
    " get back
    execute 'buffer' l:bufnr
    call cursor(l:lnum, 1)
    " save current indent
    let l:indent = indent(l:lnum)
    " now process the yanked text
    for l:line in split(@@, "\n")
        " to keep it simple: 'field' is any ident which ends in ';'
        let l:field = matchstr(l:line, '\K\k*\ze;')
        if !empty(l:field)
            put =repeat(' ', l:indent) . a:id[1] . '.' . l:field . ' = ;'
        endif
    endfor
endfunction

" a mapping to make use of the code above:
" matches 'struct tagname ident' in the current line
nnoremap <silent><leader>x :call <SID>expand_struct(matchlist(getline('.'), '\vstruct\s+(\K\k*)\s+(\K\k*)')[1:2])<CR>
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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