Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to comment a VIM macro

Tags:

vim

editor

macros

Is it possible to comment a macro and replay it.

Example

instead of

  dddwj

I would like to comment and execute following fragment

  dd # Delete line
  dw # Delete word
  j  # Move to next line

Some background

We use PICT to generate testcase inputs (All Pair testing). As this is an iterative process, the macro for generating code needs tweaking between subsequent runs. It's hard to modify a macro when everything is on one line, without comments.

The output of a PICT run might be something like this:

1 cInstallationX Pu380
2 cInstallationY U400

wich can be converted to testcases with a macro

procedure TWatchIntegrationTests.Test1;
begin
  //***** Setup
  builder
    .withInstallation(cInstallationX)
    .withIsotope(Pu380)
  .Build;

  //***** Execute
  CreateAndCollectWatches;

  //***** Verify
  VerifyThat
    .toDo;
end;

procedure TWatchIntegrationTests.Test2;
begin
  //***** Setup
  builder
    .withInstallation(cInstallationY)
    .withIsotope(U400)
  .Build;

  //***** Execute
  CreateAndCollectWatches;

  //***** Verify
  VerifyThat
    .toDo;
end;
like image 403
Lieven Keersmaekers Avatar asked Dec 08 '22 00:12

Lieven Keersmaekers


2 Answers

I don't know a good way of doing this with macros, but there are a few options that I can see that might help:

Heavy use of 'normal'

This is the closest to your macro option, but not very nice: make your saved file look like this:

" Delete line
normal dd
" Delete word
normal dw
" Move to next line
normal j

Complicated Substitution

This makes use of regular expressions, but makes those regular expressions be well commented (this is based on your actual example).

let pattern  = '^'              " Start of line
let pattern .= '\(\d\+\)'       " One or more digits (test number)
let pattern .= '\s\+'           " Space or tab as delimiter
let pattern .= '\(\k\+\)'       " Installation name
let pattern .= '\s\+'           " Space or tab as delimiter
let pattern .= '\(\a\+\d\+\)'   " One or more alphabetic characters, then one or more spaces (isotope)
let pattern .= '\s*$'           " Any spaces up to the end of the line

let result  = 'procedure TWatchIntegrationTests.Test\1;\r'
let result .= 'begin\r'
let result .= '  //***** Setup\r'
let result .= '  builder\r'
let result .= '    .withInstallation(\2)\r'
let result .= '    .withIsotope(\3)\r'
let result .= '  .Build;\r'
let result .= '\r'
let result .= '  //***** Execute\r'
let result .= '  CreateAndCollectWatches;\r'
let result .= '\r'
let result .= '  //***** Verify\r'
let result .= '  VerifyThat\r'
let result .= '    .toDo;\r'
let result .= 'end;\r'

exe '%s!' . pattern . '!' . result . '!'

Stick it in a function

Given that this is getting rather complicated, I'd probably do it this way as it gives more room for adjustment. As I see it, you want to split the line on white space and use the three fields, so something like this:

" A command to make it easier to call
" (e.g. :ConvertPICTData or :'<,'>ConvertPICTData)
command! -range=% ConvertPICTData <line1>,<line2>call ConvertPICTData()

" Function that does the work
function! ConvertPICTData() range
    " List of lines producing the required template
    let template = [
                \ 'procedure TWatchIntegrationTests.Test{TestNumber};',
                \ 'begin',
                \ '  //***** Setup',
                \ '  builder',
                \ '    .withInstallation({Installation})',
                \ '    .withIsotope({Isotope})',
                \ '  .Build;',
                \ '',
                \ '  //***** Execute',
                \ '  CreateAndCollectWatches;',
                \ '',
                \ '  //***** Verify',
                \ '  VerifyThat',
                \ '    .toDo;',
                \ 'end;',
                \ '']

    " For each line in the provided range (default, the whole file)
    for linenr in range(a:firstline,a:lastline)
        " Copy the template for this entry
        let this_entry = template[:]

        " Get the line and split it on whitespace
        let line = getline(linenr)
        let parts = split(line, '\s\+')

        " Make a dictionary from the entries in the line.
        " The keys in the dictionary match the bits inside
        " the { and } in the template.
        let lookup = {'TestNumber': parts[0], 
                    \ 'Installation': parts[1],
                    \ 'Isotope': parts[2]}

        " Iterate through this copy of the template and 
        " substitute the {..} bits with the contents of
        " the dictionary
        for template_line in range(len(this_entry))
            let this_entry[template_line] = 
                        \ substitute(this_entry[template_line], 
                        \   '{\(\k\+\)}', 
                        \   '\=lookup[submatch(1)]', 'g')
        endfor

        " Add the filled-in template to the end of the range
        call append(a:lastline, this_entry)
    endfor

    " Now remove the original lines
    exe a:firstline.','.a:lastline.'d'
endfunction

Do it in python

This is the sort of task that is probably easier to do in python:

import sys

template = '''
procedure TWatchIntegrationTests.Test%(TestNumber)s;
begin
  //***** Setup
  builder
    .withInstallation(%(Installation)s)
    .withIsotope(%(Isotope)s)
  .Build;

  //***** Execute
  CreateAndCollectWatches;

  //***** Verify
  VerifyThat
    .toDo;
end;
'''

input_file = sys.argv[1]
output_file = input_file + '.output'

keys = ['TestNumber', 'Installation', 'Isotope']

fhIn = open(input_file, 'r')
fhOut = open(output_file, 'w')

for line in fhIn:
    parts = line.split(' ')
    if len(parts) == len(keys):
        fhOut.write(template % dict(zip(keys, parts)))
fhIn.close()
fhOut.close()

To use this, save it as (e.g.) pict_convert.py and run:

python pict_convert.py input_file.txt

It will produce input_file.txt.output as a result.

like image 87
DrAl Avatar answered Dec 21 '22 23:12

DrAl


First of all let me point out that @Al has posted several excellent solutions and I suggest you use those and not what I am about to post. Especially since that does not seem to work under all circumstances (for reasons I do not understand).

Having said that, the following seems to do what you want at least in this case. It assumes <Space> in normal mode is not used to move the cursor around. Maps it to :" where " is the comment character for cmline mode. Which means <Space> is the character that starts a comment in this case. The newline at the end stops the comment. The # is just there to make it clearer we are dealing with comments. (^[ should be entered as a single escape character).

:nmap <Space> :"
iHallo wereld^[             # Insert text (in dutch, better change that)
Fe                          # Move backwards to e
x                           # Delete
;                           # Move to next e
ro                          # Change to o
Fa                          # Move backwards to a
re                          # Change to e
A!^[                        # Add exclamation mark
like image 33
heijp06 Avatar answered Dec 21 '22 22:12

heijp06