sponsor Vim development Vim logo Vim Book Ad

basic Tip #346: Wrap text in HTML/XML tags after prompting for the tag name

 tip karma   Rating 129/44, Viewed by 7029 

Read and edit this tip on the Vim tip wiki. The wiki may have a more recent version of this tip.

created:   October 16, 2002 8:33      complexity:   basic
author:   [email protected]      as of Vim:   6.0


Someone else posted this sometime ago on this mailing list, I have enhanced it slightly and made a tip out of it.
I thought it was pretty clever and very generic.

If you have a block of text and you want to wrap it in <TAG_NAME>...</TAG_NAME> then this function will prompt you for the tag name and wrap the text.

If there is no text VISUALLY selected, it will wrap the current word in the tag, otherwise it will wrap the visually selected text.

It will also strip off any leading spaces.
For the end tag, it will use the first word of the tag only.

Consider an ANT build file, which has tags like this:
<target name="init">
...
</target>

When prompted for the tag you would enter:
target name="init"

And it will wrap the text in:
<target name="init">
...
</target>


" Tag Select/Wrapper
" These mappings and TagSelection function will allow you to place " an XML tag around either the current word, or the current selected " text
nmap <Leader>t viw<Leader>t
vnoremap <Leader>t <Esc>:call TagSelection()<CR>

nmap <Leader>t viw<Leader>t
vnoremap <Leader>t <Esc>:call TagSelection()<CR>

function! TagSelection()
  let l:tag = input("Tag name? ")
  " exec "normal `>a</" . l:tag . ">\e"
  " Strip off all but the first work in the tag for the end tag
  exec "normal `>a</" .
              \ substitute( l:tag, '[ \t"]*\(\<\w*\>\).*', '\1>\e', "" )
  exec "normal `<i"
              \ substitute( l:tag, '[ \t"]*\(\<.*\)', '<\1>\e', "" )
endfunction

 rate this tip  Life Changing Helpful Unfulfilling 

<< Visual Studio + vim Quickfix mode + cygwin + XFree86 | Format paragraph without changing the cursor position >>

Additional Notes

[email protected], February 18, 2003 9:50
With XML namespace that contains a ":", the first substitute doesn't really work as desired.  It may be changed to the following (note using the \S instead of a \w):

function! TagSelection()
  let l:tag = input("Tag name?")
  " exec "normal `>a</" . l:tag . ">\e"
  " Strip off all but the first work in the tag for the end tag
  exec "normal `>a</" .
              \ substitute( l:tag, '[ \t"]*\(\<\S*\>\).*', '\1>\e', "" )
  exec "normal `<i"
              \ substitute( l:tag, '[ \t"]*\(\<.*\)', '<\1>\e', "" )
endfunction
[email protected], September 18, 2004 20:59
You forgot the trailing "." from the second "exec" line.  Great script though!
[email protected], September 19, 2004 12:58
Some additions:

I found that this script doesn't work correctly in XML files, because when the end tag is started "</", Vim automatically shifts the line to the left.  I guess this is part of the autoindent feature or something similar, I'm fairly new to Vim.  Here's what I wrote to fix that:

function! TagSelection()
  let oldpaste = &paste;
  let tag = input("Tag name?")
  " Turn on paste mode to avoid autoidenting messing with our positions
  set paste
  let start = substitute( tag, '[ \t"]*\(\<.*\)', '<\1>', "" )
  " Strip off all but the first word in the tag for the end tag
  let end   = substitute( tag, '[ \t"]*\(\<\S*\>\).*', '<\/\1>', "" )
  exec "normal `>a" . end
  exec "normal `<i" . start
  " Restore autoindent
  if ! oldpaste
    set nopaste
  endif
endfunction
David Fishburn, December 9, 2004 12:42

I have updated this (been using it a lot lately).

I have incorporated the previous suggestions and made my own (preference) enhancements.
There are 3 maps now, one for each mode.  They all have the same mapping which should make it very easy to remember:
imap ,,,
nmap ,,,
vmap ,,,

Please read the comments below for the enhancements.

" These mappings and TagSelection function will allow you to place
" an XML tag around either the current word, or the current selected
" text.
" If the visual select is on a single line, the tag is wrapped
" around the text <this>way</this>.  If the visual select extends
" over multiple lines, the tag is wrapped around the text
"     <this>
"     way
"     </this>
"    
" When you are prompted for the tag name, you can enter:
"     Tag name?  p class="classname" attri="bute"
" The select is wrapped with:
"     <p class="classname" attri="bute">
"     Your selection
"     </p>
" Notice the attributes have been stripped from the closing tag.
"
" Use nmap, not nnoremap, since we do want to use an existing mapping
nmap ,,, viw,,,
vnoremap ,,, <Esc>:call TagSelection()<CR>

function! TagSelection()
  let tag = input("Tag name (include attributes)? ")

  if strlen(tag) == 0
      return
  endif

  " Save b register
  let saveB = @b
  let curl  = line(".")
  let curc  = col(".")

  " If the visual selection is over multiple lines, then place the
  " data between the tags on newlines:
  "    <tag>
  "    data
  "    </tag>
  let newline = ''
  if getline("'>") != getline("'<")
      let newline = "\n"
      let curl  = line("'>")
  endif

  " Strip off all but the first word in the tag for the end tag
  let @b = newline . substitute( tag, '[ \t"]*\(\<\S*\>\).*', '<\/\1>\e', "" )
  let curc = curc + strlen(@b)
  exec "normal `>a\<C-R>b"

  let @b = substitute( tag, '[ \t"]*\(\<.*\)', '<\1>\e', "" ) . newline
  let curc = curc + strlen(@b)
  exec "normal `<i\<C-R>b"

  " Restore b register
  let @b = saveB

  call cursor(curl, curc)
endfunction


I have also added a note to this Tip:
" Tip #465: Generic xml imap to make an element of any word you type
" http://vim.sourceforge.net/tips/tip.php?tip_id=465 (vimtip #465)

To make some minor corrections to the map to handle 1 character tags:
inoremap ,,, <esc>diwi<<esc>pa><cr></<esc>pa><esc>kA

David Fishburn, May 8, 2006 12:23
Hopefully everything is escaped properly for the post.

This is a new version which resets the indentkeys setting until the function completes.  When editing HTML files, adding the end tag would often cause the line to be re-indented.  So the beginning tag was added to the wrong location.

" Use nmap, not nnoremap, since we do want to use an existing mapping
nmap ,,, viw,,,
vnoremap ,,, <Esc>:call TagSelection()<CR>

function! TagSelection()
  let tag = input("Tag name (include attributes)? ")

  if strlen(tag) == 0
      return
  endif

  " Save b register
  let saveB       = @b
  " <C-R> seems to automatically reindent the line for some filetypes
  " this will disable it until we have applied our changes
  let saveIndent  = &indentexpr;
  let curl        = line(".")
  let curc        = col(".")
  let &indentexpr; = ''

  " If the visual selection is over multiple lines, then place the
  " data between the tags on newlines:
  "    <tag>
  "    data
  "    </tag>
  let newline = ''
  if getline("'>") != getline("'<")
      let newline = "\n"
      let curl  = line("'>")
  endif

  " Strip off all but the first word in the tag for the end tag
  let @b = newline . substitute( tag, '^[ \t"]*\(\<\S*\>\).*', '<\/\1>\e', "" )
  let curc = curc + strlen(@b)
  exec "normal `>a\<C-R>b"

  let @b = substitute( tag, '^[ \t"]*\(\<.*\)', '<\1>\e', "" ) . newline
  let curc = curc + strlen(@b)
  exec "normal `<i\<C-R>b"

  " Now format the area
  exec "normal `<V'>j="

  " Restore b register
  let @b          = saveB
  let &indentexpr; = saveIndent

  call cursor(curl, curc)
endfunction
If you have questions or remarks about this site, visit the vimonline development pages. Please use this site responsibly.
Questions about Vim should go to the maillist. Help Bram help Uganda.
   
Sponsored by Web Concept Group Inc. SourceForge.net Logo