2

Want to read variable definitions from a tcl file, then dynamically define vim global variables.

set PATH_A /full/path/a
set PATH_B /full/path/b
other_tcl_commands

Read one line, split by space and save to list.
Use list[1] as variable name and list[2] as variable value.
Like let g:PATH_A = /full/path/a.

1

We can base a solution on these two commands:

let m = matchlist(getline("."), PATTERN)
exec "let g:" . m[1] . " = '" . m[2] . "'"

The first command processes the current buffer line into a list of pattern (sub)matches. I don't know anything about tcl variable definitions so I'm not sure exactly what your matching requirements are but if you only want to match lines like "set foo baz" then you can use:

^\s*set\s\+\(\S\+\)\s\+\(\S\+\)\s*$

(I'm allowing whitespace before and after the text of interest. Modify the pattern as you see fit.)

Anyways, if you run the following line through the matchlist function..

set PATH_A /full/path/a

The returned list will contain:

['set PATH_A /full/path/a', 'PATH_A', '/full/path/a']

The second command uses the second and third element of the list to execute the appropriate let expression, e.g.

let g:PATH_A = '/full/path/a'

Putting this into practice, we can process an entire file with this:

:silent! g/./ let m=matchlist(getline("."), '^\s*set\s\+\(\S\+\)\s\+\(\S\+\)\s*$') | exec "let g:" . m[1] . " = '" . m[2] . "'"

Besides the stuff we already discussed, silent! is there to ignore any lines that don't match the pattern and g/./ executes the following command(s) for every line in the buffer that matches . (IOW, every non-blank line).

(If you want to process part of a file you can put a range in front of g/./.)

After running this on your sample text we'd see, for example:

:echo g:PATH_B
/full/path/b

Of the various approaches to this problem that I pondered I think this one is pretty straightforward, if a little brute force. (I was originally planning something with lambdas/funcrefs.)


Update: A slightly optimized variation:

:g/^\s*set\s\+\(\S\+\)\s\+\(\S\+\)\s*$/ let m=matchlist(getline("."), @/) | exec "let g:" . m[1] . " = '" . m[2] . "'"

By using the matchlist pattern with :global we only stop at lines that will be processed into var defs. Instead of repeating the pattern for the function parameter we just use the contents of the / register which will now contain the pattern. We can drop silent!, too, since we don't expect any non-matching lines with this version.

  • Your pattern doesnt only match the set statements; it will match for other statements of similar form, no? – D. Ben Knoble Aug 25 '18 at 16:19
  • 1
    @D.BenKnoble Could be. The question isn't very specific about what should be matched...at least not to someone who doesn't know anything about tcl variable definitions. If it's only supposed to match lines of exactly that set foo /bar/baz form I'd be happy to make the pattern more explicit. – B Layer Aug 26 '18 at 6:40
1

This can be done with a macro. (q[register] to begin a macro, then q to end it).

0W"ayiWW"by$:let g:<C-r>a = "<C-r>b"<cr>0j

I am unsure of how to insert the special characters here but replace C-r and cr with their respective codes ^R and ^M using Ctrl-V (or Ctrl-Q on windows) in insert mode when writing the macro directly, or just replace with the actual key sequences (cr : enter) when recording the macro.

What this does is goes to the beginning of the line, to the next WORD of the line, then yanks the WORD into the a-register. Then, to the next WORD, it yanks the rest of the line into the b-register.

Now, in command mode, it will run the command to define a global variable with variable name equal to the contents of the a-register (using C-r a) and value equal to the contents of the b-register (using C-r b).

Finally, the macro moves the cursor to the beginning of the line and moves down one line so it can easily be repeated.

Now, you may want to take a file and execute this macro on all matching lines. You can go to command mode and run:

:%g/^set /normal! @q

This uses the global command over the given range, running a command on all matching lines (lines starting with "set "). If there are other lines you don't want to match that also start with "set " you will need to fiddle with the regex. The command that is run executes a normal-mode command which executes the macro stored in the q-register, which will define the global variable according to the contents of the line (if it is correctly formed, with incorrectly formed lines the macro may mess up and create bad behaviour).

Now this is a lot of work to go through each time, if you overwrite the q-register and lose the macro. So a function would be nice.

In your ~/.vimrc or ~/_vimrc or equivalent, include:

function! TCLLineToVariable()
    execute "normal! 0W\"ayiWW\"by$:let g:\<C-r>a = \"\<C-r>b\"\<cr>0j"
endfunction
function! CreateTCLGlobals()
    %g/^set /call TCLLineToVariable()
endfunction

Escape sequences and execute "normal! ..." are needed because execute preprocesses the escape sequences to pass to a normal! command.

This function, when run, will operate on all lines in the file (% range), creating globals dynamically.

If you want this to happen with any tcl file, use autocommands in your vimrc.

autocmd BufRead *.tcl call CreateTCLGlobals()

If you are using an extension other than .tcl, replace this part with your extension. Add BufEnter, BufWritePost, as well if you want all globals to be updated while you are editing the file (when you switch to the buffer or write it, at least). You may also want to surround this with an autocommand group so you don't duplicate the autocmd every time you source your vimrc.

augroup TCL
    autocmd!
    autocmd BufRead,BufEnter,BufWritePost *.tcl call CreateTCLGlobals()
augroup END

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.