11" Vim plugin for formatting XML
2- " Last Change: Thu, 07 Dec 2018
3- " Version: 0.1
2+ " Last Change: 2019 Oct 24
3+ " Version: 0.2
44" Author: Christian Brabandt <[email protected] >55" Repository: https://github.com/chrisbra/vim-xml-ftplugin
66" License: VIM License
@@ -22,44 +22,73 @@ func! xmlformat#Format()
2222 " do not fall back to internal formatting
2323 return 0
2424 endif
25+ let count_orig = v: count
2526 let sw = shiftwidth ()
2627 let prev = prevnonblank (v: lnum- 1 )
2728 let s: indent = indent (prev )/sw
2829 let result = []
2930 let lastitem = prev ? getline (prev ) : ' '
3031 let is_xml_decl = 0
31- " split on `<`, but don't split on very first opening <
32- for item in split (join (getline (v: lnum , (v: lnum + v: count - 1 ))), ' .\@<=[>]\zs' )
33- if s: EndTag (item)
34- let s: indent = s: DecreaseIndent ()
35- call add (result, s: Indent (item))
36- elseif s: EmptyTag (lastitem)
37- call add (result, s: Indent (item))
38- elseif s: StartTag (lastitem) && s: IsTag (item)
39- let s: indent += 1
40- call add (result, s: Indent (item))
41- else
42- if ! s: IsTag (item)
43- " Simply split on '<'
44- let t = split (item, ' .<\@=\zs' )
45- let s: indent+= 1
46- call add (result, s: Indent (t [0 ]))
47- let s: indent = s: DecreaseIndent ()
48- call add (result, s: Indent (t [1 ]))
49- else
32+ " go through every line, but don't join all content together and join it
33+ " back. We might lose empty lines
34+ let list = getline (v: lnum , (v: lnum + count_orig - 1 ))
35+ let current = 0
36+ for line in list
37+ " Keep empty input lines?
38+ if empty (line )
39+ call add (result, ' ' )
40+ continue
41+ elseif line !~# ' <[/]\?[^>]*>'
42+ let nextmatch = match (list , ' <[/]\?[^>]*>' , current)
43+ let line .= join (list [(current + 1 ):(nextmatch- 1 )], " \n " )
44+ call remove (list , current+ 1 , nextmatch- 1 )
45+ endif
46+ " split on `>`, but don't split on very first opening <
47+ " this means, items can be like ['<tag>', 'tag content</tag>']
48+ for item in split (line , ' .\@<=[>]\zs' )
49+ if s: EndTag (item)
50+ let s: indent = s: DecreaseIndent ()
51+ call add (result, s: Indent (item))
52+ elseif s: EmptyTag (lastitem)
53+ call add (result, s: Indent (item))
54+ elseif s: StartTag (lastitem) && s: IsTag (item)
55+ let s: indent += 1
5056 call add (result, s: Indent (item))
57+ else
58+ if ! s: IsTag (item)
59+ " Simply split on '<', if there is one,
60+ " but reformat according to &textwidth
61+ let t = split (item, ' .<\@=\zs' )
62+ " t should only contain 2 items, but just be safe here
63+ if s: IsTag (lastitem)
64+ let s: indent+= 1
65+ endif
66+ let result+= s: FormatContent ([t [0 ]])
67+ if s: EndTag (t [1 ])
68+ let s: indent = s: DecreaseIndent ()
69+ endif
70+ " for y in t[1:]
71+ let result+= s: FormatContent (t [1 :])
72+ " endfor
73+ else
74+ call add (result, s: Indent (item))
75+ endif
5176 endif
52- endif
53- let lastitem = item
54- endfor
77+ let lastitem = item
78+ endfor
79+ let current += 1
80+ endfor
5581
56- if ! empty (result)
57- exe v: lnum . " ," . (v: lnum + v: count - 1 ). ' d'
82+ if ! empty (result)
83+ let lastprevline = getline (v: lnum + count_orig)
84+ let delete_lastline = v: lnum + count_orig - 1 == line (' $' )
85+ exe v: lnum . " ," . (v: lnum + count_orig - 1 ). ' d'
5886 call append (v: lnum - 1 , result)
5987 " Might need to remove the last line, if it became empty because of the
6088 " append() call
6189 let last = v: lnum + len (result)
62- if getline (last ) is ' '
90+ " do not use empty(), it returns true for `empty(0)`
91+ if getline (last ) is ' ' && lastprevline is ' ' && delete_lastline
6392 exe last . ' d'
6493 endif
6594 endif
@@ -88,6 +117,7 @@ func! s:StartTag(tag)
88117 let is_comment = s: IsComment (a: tag )
89118 return a: tag = ~? ' ^\s*<[^/?]' && ! is_comment
90119endfunc
120+ " Check if tag is a Comment start {{{1
91121func ! s: IsComment (tag )
92122 return a: tag = ~? ' <!--'
93123endfunc
@@ -108,6 +138,43 @@ endfunc
108138func ! s: EmptyTag (tag )
109139 return a: tag = ~ ' />\s*$'
110140endfunc
141+ " Format input line according to textwidth {{{1
142+ func ! s: FormatContent (list )
143+ let result= []
144+ let limit = 80
145+ if &textwidth > 0
146+ let limit = &textwidth
147+ endif
148+ let column= 0
149+ let idx = -1
150+ let add_indent = 0
151+ let cnt = 0
152+ for item in a: list
153+ for word in split (item, ' \s\+\S\+\zs' )
154+ let column += strdisplaywidth (word, column)
155+ if match (word, " ^\\ s*\n \\ +\\ s*$" ) > -1
156+ call add (result, ' ' )
157+ let idx += 1
158+ let column = 0
159+ let add_indent = 1
160+ elseif column > limit || cnt == 0
161+ let add = s: Indent (s: Trim (word))
162+ call add (result, add )
163+ let column = strdisplaywidth (add )
164+ let idx += 1
165+ else
166+ if add_indent
167+ let result[idx] = s: Indent (s: Trim (word))
168+ else
169+ let result[idx] .= ' ' . s: Trim (word)
170+ endif
171+ let add_indent = 0
172+ endif
173+ let cnt += 1
174+ endfor
175+ endfor
176+ return result
177+ endfunc
111178" Restoration And Modelines: {{{1
112179let &cpo = s: keepcpo
113180unlet s: keepcpo
0 commit comments