11" Vim plugin for formatting XML
2- " Last Change: 2019 Oct 24
3- " Version: 0.2
2+ " Last Change: 2020 Jan 06
3+ " Version: 0.3
44" Author: Christian Brabandt <[email protected] >55" Repository: https://github.com/chrisbra/vim-xml-ftplugin
66" License: VIM License
@@ -15,7 +15,7 @@ let s:keepcpo = &cpo
1515set cpo &vim
1616
1717" Main function: Format the input {{{1
18- func ! xmlformat#Format ()
18+ func ! xmlformat#Format () abort
1919 " only allow reformatting through the gq command
2020 " (e.g. Vim is in normal mode)
2121 if mode () != ' n'
@@ -40,14 +40,16 @@ func! xmlformat#Format()
4040 continue
4141 elseif line !~# ' <[/]\?[^>]*>'
4242 let nextmatch = match (list , ' <[/]\?[^>]*>' , current)
43- let line .= join (list [(current + 1 ):(nextmatch- 1 )], " \n " )
44- call remove (list , current+ 1 , nextmatch- 1 )
43+ if nextmatch > -1
44+ let line .= ' ' . join (list [(current + 1 ):(nextmatch- 1 )], " " )
45+ call remove (list , current+ 1 , nextmatch- 1 )
46+ endif
4547 endif
4648 " split on `>`, but don't split on very first opening <
4749 " this means, items can be like ['<tag>', 'tag content</tag>']
4850 for item in split (line , ' .\@<=[>]\zs' )
4951 if s: EndTag (item)
50- let s: indent = s: DecreaseIndent ()
52+ call s: DecreaseIndent ()
5153 call add (result, s: Indent (item))
5254 elseif s: EmptyTag (lastitem)
5355 call add (result, s: Indent (item))
@@ -59,13 +61,23 @@ func! xmlformat#Format()
5961 " Simply split on '<', if there is one,
6062 " but reformat according to &textwidth
6163 let t = split (item, ' .<\@=\zs' )
64+
65+ " if the content fits well within a single line, add it there
66+ " so that the output looks like this:
67+ "
68+ " <foobar>1</foobar>
69+ if s: TagContent (lastitem) is # s: TagContent (t [1 ]) && strlen (result[-1 ]) + strlen (item) <= s: Textwidth ()
70+ let result[-1 ] .= item
71+ let lastitem = t [1 ]
72+ continue
73+ endif
6274 " t should only contain 2 items, but just be safe here
6375 if s: IsTag (lastitem)
6476 let s: indent+= 1
6577 endif
6678 let result+= s: FormatContent ([t [0 ]])
6779 if s: EndTag (t [1 ])
68- let s: indent = s: DecreaseIndent ()
80+ call s: DecreaseIndent ()
6981 endif
7082 " for y in t[1:]
7183 let result+= s: FormatContent (t [1 :])
@@ -97,60 +109,69 @@ func! xmlformat#Format()
97109 return 0
98110endfunc
99111" Check if given tag is XML Declaration header {{{1
100- func ! s: IsXMLDecl (tag )
112+ func ! s: IsXMLDecl (tag ) abort
101113 return a: tag = ~? ' ^\s*<?xml\s\?\%(version="[^"]*"\)\?\s\?\%(encoding="[^"]*"\)\? ?>\s*$'
102114endfunc
103115" Return tag indented by current level {{{1
104- func ! s: Indent (item)
116+ func ! s: Indent (item) abort
105117 return repeat (' ' , shiftwidth ()* s: indent ). s: Trim (a: item )
106118endfu
107119" Return item trimmed from leading whitespace {{{1
108- func ! s: Trim (item)
120+ func ! s: Trim (item) abort
109121 if exists (' *trim' )
110122 return trim (a: item )
111123 else
112124 return matchstr (a: item , ' \S\+.*' )
113125 endif
114126endfunc
115127" Check if tag is a new opening tag <tag> {{{1
116- func ! s: StartTag (tag )
128+ func ! s: StartTag (tag ) abort
117129 let is_comment = s: IsComment (a: tag )
118130 return a: tag = ~? ' ^\s*<[^/?]' && ! is_comment
119131endfunc
120132" Check if tag is a Comment start {{{1
121- func ! s: IsComment (tag )
133+ func ! s: IsComment (tag ) abort
122134 return a: tag = ~? ' <!--'
123135endfunc
124136" Remove one level of indentation {{{1
125- func ! s: DecreaseIndent ()
126- return (s: indent > 0 ? s: indent - 1 : 0 )
137+ func ! s: DecreaseIndent () abort
138+ let s: indent = (s: indent > 0 ? s: indent - 1 : 0 )
127139endfunc
128140" Check if tag is a closing tag </tag> {{{1
129- func ! s: EndTag (tag )
141+ func ! s: EndTag (tag ) abort
130142 return a: tag = ~? ' ^\s*</'
131143endfunc
132144" Check that the tag is actually a tag and not {{{1
133145" something like "foobar</foobar>"
134- func ! s: IsTag (tag )
146+ func ! s: IsTag (tag ) abort
135147 return s: Trim (a: tag )[0 ] == ' <'
136148endfunc
137149" Check if tag is empty <tag/> {{{1
138- func ! s: EmptyTag (tag )
150+ func ! s: EmptyTag (tag ) abort
139151 return a: tag = ~ ' />\s*$'
140152endfunc
153+ func ! s: TagContent (tag ) abort " {{{1
154+ " Return content of a tag
155+ return substitute (a: tag , ' ^\s*<[/]\?\([^>]*\)>\s*$' , ' \1' , ' ' )
156+ endfunc
157+ func ! s: Textwidth () abort " {{{1
158+ " return textwidth (or 80 if not set)
159+ return &textwidth == 0 ? 80 : &textwidth
160+ endfunc
141161" Format input line according to textwidth {{{1
142- func ! s: FormatContent (list )
162+ func ! s: FormatContent (list ) abort
143163 let result= []
144- let limit = 80
145- if &textwidth > 0
146- let limit = &textwidth
147- endif
164+ let limit = s: Textwidth ()
148165 let column= 0
149166 let idx = -1
150167 let add_indent = 0
151168 let cnt = 0
152169 for item in a: list
153170 for word in split (item, ' \s\+\S\+\zs' )
171+ if match (word, ' ^\s\+$' ) > -1
172+ " skip empty words
173+ continue
174+ endif
154175 let column += strdisplaywidth (word, column)
155176 if match (word, " ^\\ s*\n \\ +\\ s*$" ) > -1
156177 call add (result, ' ' )
0 commit comments