1" Vim completion script
2" Language:	XHTML 1.0 Strict
3" Maintainer:	Mikolaj Machowski ( mikmach AT wp DOT pl )
4" Last Change:	2005 Sep 19
5
6function! htmlcomplete#CompleteTags(findstart, base)
7  if a:findstart
8    " locate the start of the word
9    let line = getline('.')
10    let start = col('.') - 1
11    while start >= 0 && line[start - 1] !~ '<'
12      let start -= 1
13    endwhile
14	if start < 0
15		let curpos = line('.')
16		let stylestart = searchpair('<style\>', '', '<\/style\>', "bnW")
17		let styleend   = searchpair('<style\>', '', '<\/style\>', "nW")
18		if stylestart != 0 && styleend != 0
19			if stylestart <= curpos && styleend >= curpos
20				let b:csscompl = 1
21				let start = 0
22			endif
23		endif
24	endif
25    return start
26  else
27	" Check if we should do CSS completion inside of <style> tag
28	if exists("b:csscompl")
29		unlet! b:csscompl
30		return csscomplete#CompleteCSS(0, a:base)
31	endif
32	if a:base =~ '>'
33		" Generally if a:base contains > it means we are outside of tag and
34		" should abandon action - with one exception: <style> span { bo
35		if a:base =~ 'style[^>]\{-}>[^<]\{-}$'
36			return csscomplete#CompleteCSS(0, a:base)
37		else
38			return []
39		endif
40	endif
41
42	" Set attribute groups
43    let coreattrs = ["id", "class", "style", "title"]
44    let i18n = ["lang", "xml:lang", "dir=\"ltr\" ", "dir=\"rtl\" "]
45    let events = ["onclick", "ondblclick", "onmousedown", "onmouseup", "onmousemove",
46    			\ "onmouseout", "onkeypress", "onkeydown", "onkeyup"]
47    let focus = ["accesskey", "tabindex", "onfocus", "onblur"]
48    let coregroup = coreattrs + i18n + events
49    let res = []
50    let res2 = []
51    " find tags matching with "a:base"
52	" If a:base contains > it means we are already outside of tag and we
53	" should abandon action
54	" If a:base contains white space it is attribute.
55	" It could be also value of attribute...
56	" We have to get first word to offer
57	" proper completions
58	let tag = split(a:base)[0]
59	" Get last word, it should be attr name
60	let attr = matchstr(a:base, '.*\s\zs.*')
61	" Possible situations where any prediction would be difficult:
62	" 1. Events attributes
63	if a:base =~ '\s'
64		" Sort out style, class, and on* cases
65		" Perfect solution for style would be switching for CSS completion. Is
66		" it possible?
67		" Also retrieving class names from current file and linked
68		" stylesheets.
69		if a:base =~ "\\(on[a-z]*\\|style\\|class\\)\\s*=\\s*[\"']"
70			if a:base =~ "class\\s*=\\s*[\"'][a-zA-Z0-9_ -]*$"
71				" Handle class name completion
72				" 1. Find lines of <link stylesheet>
73				" 1a. Check file for @import
74				" 2. Extract filename(s?) of stylesheet,
75				call cursor(1,1)
76				let head = getline(search('<head\>'), search('<\/head>'))
77				let headjoined = join(copy(head), ' ')
78				if headjoined =~ '<style'
79					let stylehead = substitute(headjoined, '+>\*[', ' ', 'g')
80					let styleheadlines = split(stylehead)
81					let headclasslines = filter(copy(styleheadlines), "v:val =~ '\\([a-zA-Z0-9:]\\+\\)\\?\\.[a-zA-Z0-9_-]\\+'")
82					let internal = 1
83				else
84					let internal = 0
85				endif
86				let styletable = []
87				let secimportfiles = []
88				let filestable = filter(copy(head), "v:val =~ '\\(@import\\|link.*stylesheet\\)'")
89				for line in filestable
90					if line =~ "@import"
91						let styletable += [matchstr(line, "import\\s\\+\\(url(\\)\\?[\"']\\?\\zs\\f\\+\\ze")]
92					elseif line =~ "<link"
93						let styletable += [matchstr(line, "href\\s*=\\s*[\"']\\zs\\f\\+\\ze")]
94					endif
95				endfor
96				for file in filestable
97					if filereadable(file)
98						let stylesheet = readfile(file)
99						let secimport = filter(copy(stylesheet), "v:val =~ '@import'")
100						if len(secimport) > 0
101							for line in secimport
102								let secimportfiles += [matchstr(line, "import\\s\\+\\(url(\\)\\?[\"']\\?\\zs\\f\\+\\ze")]
103							endfor
104						endif
105					endif
106				endfor
107				let cssfiles = styletable + secimportfiles
108				let classes = []
109				for file in cssfiles
110					if filereadable(file)
111						let stylesheet = readfile(file)
112						let stylefile = join(stylesheet)
113						let stylefile = substitute(stylefile, '+>\*[', ' ', 'g')
114						let stylesheet = split(stylefile)
115						let classlines = filter(copy(stylesheet), "v:val =~ '\\([a-zA-Z0-9:]\\+\\)\\?\\.[a-zA-Z0-9_-]\\+'")
116					endif
117					" We gathered classes definitions from all external files
118					let classes += classlines
119				endfor
120				if internal == 1
121					let classes += headclasslines
122				endif
123				let elements = {}
124				for element in classes
125					if element =~ '^\.'
126						let class = matchstr(element, '^\.\zs[a-zA-Z][a-zA-Z0-9_-]*\ze')
127						let class = substitute(class, ':.*', '', '')
128						if has_key(elements, "common")
129							let elements["common"] .= " ".class
130						else
131							let elements["common"] = class
132						endif
133					else
134						let class = matchstr(element, '[a-zA-Z1-6]*\.\zs[a-zA-Z][a-zA-Z0-9_-]*\ze')
135						let tagname = tolower(matchstr(element, '[a-zA-Z1-6]*\ze.'))
136						if tagname != ''
137							if has_key(elements, tagname)
138								let elements[tagname] .= " ".class
139							else
140								let elements[tagname] = class
141							endif
142						endif
143					endif
144				endfor
145
146				if has_key(elements, tag) && has_key(elements, "common")
147					let values = split(elements[tag]." ".elements["common"])
148				elseif has_key(elements, tag) && !has_key(elements, "common")
149					let values = split(elements[tag])
150				elseif !has_key(elements, tag) && has_key(elements, "common")
151					let values = split(elements["common"])
152				else
153					return []
154				endif
155
156				" We need special version of sbase
157				let classbase = matchstr(a:base, ".*[\"']")
158			    let classquote = matchstr(classbase, '.$')
159
160
161				let entered_class = matchstr(attr, ".*=\\s*[\"']\\zs.*")
162
163				for m in sort(values)
164					if m =~? '^'.entered_class
165						call add(res, classbase . m . classquote . ' ')
166					elseif m =~? entered_class
167						call add(res2, classbase . m . classquote . ' ')
168					endif
169				endfor
170
171				return res + res2
172
173			elseif a:base =~ "style\\s*=\\s*[\"'][^\"']*$"
174				return csscomplete#CompleteCSS(0, a:base)
175
176			endif
177			let stripbase = matchstr(a:base, ".*\\(on[a-z]*\\|style\\|class\\)\\s*=\\s*[\"']\\zs.*")
178			" Now we have a:base stripped from all chars up to style/class.
179			" It may fail with some strange style value combinations.
180			if stripbase !~ "[\"']"
181				return []
182			endif
183		endif
184		" If attr contains =\s*[\"'] we catched value of attribute
185		if attr =~ "=\s*[\"']"
186			" Let do attribute specific completion
187			let attrname = matchstr(attr, '.*\ze\s*=')
188			let entered_value = matchstr(attr, ".*=\\s*[\"']\\zs.*")
189			let values = []
190			if attrname == 'media'
191				let values = ["screen", "tty", "tv", "projection", "handheld", "print", "braille", "aural", "all"]
192			elseif attrname == 'xml:space'
193				let values = ["preserve"]
194			elseif attrname == 'shape'
195				if a:base =~ '^a\>'
196					let values = ["rect"]
197				else
198					let values = ["rect", "circle", "poly", "default"]
199				endif
200			elseif attrname == 'valuetype'
201				let values = ["data", "ref", "object"]
202			elseif attrname == 'method'
203				let values = ["get", "post"]
204			elseif attrname == 'dir'
205				let values = ["ltr", "rtl"]
206			elseif attrname == 'frame'
207				let values = ["void", "above", "below", "hsides", "lhs", "rhs", "vsides", "box", "border"]
208			elseif attrname == 'rules'
209				let values = ["none", "groups", "rows", "all"]
210			elseif attrname == 'align'
211				let values = ["left", "center", "right", "justify", "char"]
212			elseif attrname == 'valign'
213				let values = ["top", "middle", "bottom", "baseline"]
214			elseif attrname == 'scope'
215				let values = ["row", "col", "rowgroup", "colgroup"]
216			elseif attrname == 'href'
217				" Now we are looking for local anchors defined by name or id
218				if entered_value =~ '^#'
219					let file = join(getline(1, line('$')), ' ')
220					" Split it be sure there will be one id/name element in
221					" item, it will be also first word [a-zA-Z0-9_-] in element
222					let oneelement = split(file, "\\(meta \\)\\@<!\\(name\\|id\\)\\s*=\\s*[\"']")
223					for i in oneelement
224						let values += ['#'.matchstr(i, "^[a-zA-Z][a-zA-Z0-9%_-]*")]
225					endfor
226				endif
227			elseif attrname == 'type'
228				if a:base =~ '^input'
229					let values = ["text", "password", "checkbox", "radio", "submit", "reset", "file", "hidden", "image", "button"]
230				elseif a:base =~ '^button'
231					let values = ["button", "submit", "reset"]
232				endif
233			else
234				return []
235			endif
236
237			if len(values) == 0
238				return []
239			endif
240
241			" We need special version of sbase
242			let attrbase = matchstr(a:base, ".*[\"']")
243			let attrquote = matchstr(attrbase, '.$')
244
245			for m in values
246				" This if is needed to not offer all completions as-is
247				" alphabetically but sort them. Those beginning with entered
248				" part will be as first choices
249				if m =~ '^'.entered_value
250					call add(res, attrbase . m . attrquote.' ')
251				elseif m =~ entered_value
252					call add(res2, attrbase . m . attrquote.' ')
253				endif
254			endfor
255
256			return res + res2
257
258		endif
259		" Shorten a:base to not include last word
260		let sbase = matchstr(a:base, '.*\ze\s.*')
261		if tag =~ '^\(abbr\|acronym\|b\|bdo\|big\|caption\|cite\|code\|dd\|dfn\|div\|dl\|dt\|em\|fieldset\|h\d\|kbd\|li\|noscript\|ol\|p\|samp\|small\|span\|strong\|sub\|sup\|tt\|ul\|var\)$'
262			let attrs = coregroup
263		elseif tag == 'a'
264			let attrs = coregroup + focus + ["charset", "type", "name", "href", "hreflang", "rel", "rev", "shape", "coords"]
265		elseif tag == 'area'
266			let attrs = coregroup
267		elseif tag == 'base'
268			let attrs = ["href", "id"]
269		elseif tag == 'blockquote'
270			let attrs = coregroup + ["cite"]
271		elseif tag == 'body'
272			let attrs = coregroup + ["onload", "onunload"]
273		elseif tag == 'br'
274			let attrs = coreattrs
275		elseif tag == 'button'
276			let attrs = coreattrs + focus + ["name", "value", "type"]
277		elseif tag == '^\(col\|colgroup\)$'
278			let attrs = coreattrs + ["span", "width", "align", "char", "charoff", "valign"]
279		elseif tag =~ '^\(del\|ins\)$'
280			let attrs = coreattrs + ["cite", "datetime"]
281		elseif tag == 'form'
282			let attrs = coreattrs + ["action", "method=\"get\" ", "method=\"post\" ", "enctype", "onsubmit", "onreset", "accept", "accept-charset"]
283		elseif tag == 'head'
284			let attrs = i18n + ["id", "profile"]
285		elseif tag == 'html'
286			let attrs = i18n + ["id", "xmlns"]
287		elseif tag == 'img'
288			let attrs = coreattrs + ["src", "alt", "longdesc", "height", "width", "usemap", "ismap"]
289		elseif tag == 'input'
290			let attrs = coreattrs + focus + ["type", "name", "value", "checked", "disabled", "readonly", "size", "maxlength", "src", "alt", "usemap", "onselect", "onchange", "accept"]
291		elseif tag == 'label'
292			let attrs = coreattrs + ["for", "accesskey", "onfocus", "onblur"]
293		elseif tag == 'legend'
294			let attrs = coreattrs + ["accesskey"]
295		elseif tag == 'link'
296			let attrs = coreattrs + ["charset", "href", "hreflang", "type", "rel", "rev", "media"]
297		elseif tag == 'map'
298			let attrs = i18n + events + ["id", "class", "style", "title", "name"]
299		elseif tag == 'meta'
300			let attrs = i18n + ["id", "http-equiv", "content", "scheme", "name"]
301		elseif tag == 'title'
302			let attrs = i18n + ["id"]
303		elseif tag == 'object'
304			let attrs = coreattrs + ["declare", "classid", "codebase", "data", "type", "codetype", "archive", "standby", "height", "width", "usemap", "name", "tabindex"]
305		elseif tag == 'optgroup'
306			let attrs = coreattrs + ["disbled", "label"]
307		elseif tag == 'option'
308			let attrs = coreattrs + ["disbled", "selected", "value", "label"]
309		elseif tag == 'param'
310			let attrs = ["id", "name", "value", "valuetype", "type"]
311		elseif tag == 'pre'
312			let attrs = coreattrs + ["xml:space"]
313		elseif tag == 'q'
314			let attrs = coreattrs + ["cite"]
315		elseif tag == 'script'
316			let attrs = ["id", "charset", "type", "src", "defer", "xml:space"]
317		elseif tag == 'select'
318			let attrs = coreattrs + ["name", "size", "multiple", "disabled", "tabindex", "onfocus", "onblur", "onchange"]
319		elseif tag == 'style'
320			let attrs = coreattrs + ["id", "type", "media", "title", "xml:space"]
321		elseif tag == 'table'
322			let attrs = coreattrs + ["summary", "width", "border", "frame", "rules", "cellspacing", "cellpadding"]
323		elseif tag =~ '^\(thead\|tfoot\|tbody\|tr\)$'
324			let attrs = coreattrs + ["align", "char", "charoff", "valign"]
325		elseif tag == 'textarea'
326			let attrs = coreattrs + focus + ["name", "rows", "cols", "disabled", "readonly", "onselect", "onchange"]
327		elseif tag =~ '^\(th\|td\)$'
328			let attrs = coreattrs + ["abbr", "headers", "scope", "rowspan", "colspan", "align", "char", "charoff", "valign"]
329		else
330			return []
331		endif
332
333		for m in sort(attrs)
334			if m =~ '^'.attr
335				if m =~ '^\(ismap\|defer\|declare\|nohref\|checked\|disabled\|selected\|readonly\)$' || m =~ '='
336					call add(res, sbase.' '.m)
337				else
338					call add(res, sbase.' '.m.'="')
339				endif
340			elseif m =~ attr
341				if m =~ '^\(ismap\|defer\|declare\|nohref\|checked\|disabled\|selected\|readonly\)$' || m =~ '='
342					call add(res2, sbase.' '.m)
343				else
344					call add(res2, sbase.' '.m.'="')
345				endif
346			endif
347		endfor
348
349		return res + res2
350
351	endif
352	" Close tag
353	let b:unaryTagsStack = "base meta link hr br param img area input col"
354	if a:base =~ '^\/'
355		let opentag = htmlcomplete#GetLastOpenTag("b:unaryTagsStack")
356		return ["/".opentag.">"]
357	endif
358	" Deal with tag completion.
359	let opentag = htmlcomplete#GetLastOpenTag("b:unaryTagsStack")
360	" Clusters
361	let special = "br span bdo map object img"
362	let phrase =  "em strong dfn code q samp kbd var cite abbr acronym sub sup"
363	let inlineforms = "input select textarea label button"
364	let miscinline = "ins del script"
365	let inline = "a ".special." ".phrase." ".inlineforms." tt i b big small"
366	let misc = "noscript ".miscinline
367	let block = "p h1 h2 h3 h4 h5 h6 div ul ol dl pre hr blockquote address fieldset table"
368
369	if opentag == 'a'
370		let tags = split("tt i b big small ".special." ".phrase." ".inlineforms." ".miscinline)
371	elseif opentag =~ '^\(abbr\|acronym\|address\|b\|p\|h\d\|dt\|span\|bdo\|em\|strong\|dfn\|code\|samp\|kbd\|var\|cite\|q\|sub\|sup\|tt\|i\|big\|small\|label\|caption\)$'
372		let tags = split(inline." ".miscinline)
373	elseif opentag == 'pre'
374		let tags = split("a tt i b big small br span bdo map ".phrase." ".miscinline." ".inlineforms)
375	elseif opentag == 'html'
376		let tags = ["head", "body"]
377	elseif opentag == 'legend'
378		let tags = split(inline." ".miscinline)
379	elseif opentag == 'head'
380		let tags = ["title", "base", "scipt", "style", "meta", "link", "object"]
381	elseif opentag =~ '^\(noscript\|body\|blockquote\)$'
382		let tags = split("form ".block." ".misc)
383	elseif opentag =~ '^\(ul\|ol\)$'
384		let tags = ["li"]
385	elseif opentag == 'dl'
386		let tags = ["dt", "dd"]
387	elseif opentag =~ '^\(ins\|del\|th\|td\|dd\|div\|li\)$'
388		let tags = split("form ".block." ".inline." ".misc)
389	elseif opentag == 'object'
390		let tags = split("param form ".block." ".inline." ".misc)
391	elseif opentag == 'fieldset'
392		let tags = split("legend form ".block." ".inline." ".misc)
393	elseif opentag == 'map'
394		let tags = split("area form ".block." ".misc)
395	elseif opentag == 'form'
396		let tags = split(block." ".misc)
397	elseif opentag == 'select'
398		let tags = ["optgroup", "option"]
399	elseif opentag == 'optgroup'
400		let tags = ["option"]
401	elseif opentag == 'colgroup'
402		let tags = ["col"]
403	elseif opentag == '^\(textarea\|option\|script\|style\|title\)$'
404		let tags = ['empty']
405	elseif opentag == 'button'
406		let tags = ["p", "h1", "h2", "h3", "h4", "h5", "h6", "div", "ul", "ol", "dl", "table"]
407	elseif opentag =~ '^\(thead\|tfoot\|tbody\)$'
408		let tags = ["tr"]
409	elseif opentag == 'tr'
410		let tags = ["th", "td"]
411	elseif opentag == 'table'
412		let tags = ["caption", "col", "colgroup", "thead", "tfoot", "tbody", "tr"]
413	else
414		return []
415	endif
416
417	for m in tags
418		if m =~ '^'.a:base
419			call add(res, m)
420		elseif m =~ a:base
421			call add(res2, m)
422		endif
423	endfor
424
425	return res + res2
426
427  endif
428endfunction
429
430" MM: This is greatly reduced closetag.vim used with kind permission of Steven
431"     Mueller
432"     Changes: strip all comments; delete error messages
433" Author: Steven Mueller <[email protected]>
434" Last Modified: Tue May 24 13:29:48 PDT 2005
435" Version: 0.9.1
436
437function! htmlcomplete#GetLastOpenTag(unaryTagsStack)
438	let linenum=line('.')
439	let lineend=col('.') - 1 " start: cursor position
440	let first=1              " flag for first line searched
441	let b:TagStack=''        " main stack of tags
442	let startInComment=s:InComment()
443
444	let tagpat='</\=\(\k\|[-:]\)\+\|/>'
445	while (linenum>0)
446		let line=getline(linenum)
447		if first
448			let line=strpart(line,0,lineend)
449		else
450			let lineend=strlen(line)
451		endif
452		let b:lineTagStack=''
453		let mpos=0
454		let b:TagCol=0
455		while (mpos > -1)
456			let mpos=matchend(line,tagpat)
457			if mpos > -1
458				let b:TagCol=b:TagCol+mpos
459				let tag=matchstr(line,tagpat)
460
461				if exists('b:closetag_disable_synID') || startInComment==s:InCommentAt(linenum, b:TagCol)
462					let b:TagLine=linenum
463					call s:Push(matchstr(tag,'[^<>]\+'),'b:lineTagStack')
464				endif
465				let lineend=lineend-mpos
466				let line=strpart(line,mpos,lineend)
467			endif
468		endwhile
469		while (!s:EmptystackP('b:lineTagStack'))
470			let tag=s:Pop('b:lineTagStack')
471			if match(tag, '^/') == 0		"found end tag
472				call s:Push(tag,'b:TagStack')
473			elseif s:EmptystackP('b:TagStack') && !s:Instack(tag, a:unaryTagsStack)	"found unclosed tag
474				return tag
475			else
476				let endtag=s:Peekstack('b:TagStack')
477				if endtag == '/'.tag || endtag == '/'
478					call s:Pop('b:TagStack')	"found a open/close tag pair
479				elseif !s:Instack(tag, a:unaryTagsStack) "we have a mismatch error
480					return ''
481				endif
482			endif
483		endwhile
484		let linenum=linenum-1 | let first=0
485	endwhile
486return ''
487endfunction
488
489function! s:InComment()
490	return synIDattr(synID(line('.'), col('.'), 0), 'name') =~ 'Comment'
491endfunction
492
493function! s:InCommentAt(line, col)
494	return synIDattr(synID(a:line, a:col, 0), 'name') =~ 'Comment'
495endfunction
496
497function! s:SetKeywords()
498	let g:IsKeywordBak=&iskeyword
499	let &iskeyword='33-255'
500endfunction
501
502function! s:RestoreKeywords()
503	let &iskeyword=g:IsKeywordBak
504endfunction
505
506function! s:Push(el, sname)
507	if !s:EmptystackP(a:sname)
508		exe 'let '.a:sname."=a:el.' '.".a:sname
509	else
510		exe 'let '.a:sname.'=a:el'
511	endif
512endfunction
513
514function! s:EmptystackP(sname)
515	exe 'let stack='.a:sname
516	if match(stack,'^ *$') == 0
517		return 1
518	else
519		return 0
520	endif
521endfunction
522
523function! s:Instack(el, sname)
524	exe 'let stack='.a:sname
525	call s:SetKeywords()
526	let m=match(stack, '\<'.a:el.'\>')
527	call s:RestoreKeywords()
528	if m < 0
529		return 0
530	else
531		return 1
532	endif
533endfunction
534
535function! s:Peekstack(sname)
536	call s:SetKeywords()
537	exe 'let stack='.a:sname
538	let top=matchstr(stack, '\<.\{-1,}\>')
539	call s:RestoreKeywords()
540	return top
541endfunction
542
543function! s:Pop(sname)
544	if s:EmptystackP(a:sname)
545		return ''
546	endif
547	exe 'let stack='.a:sname
548	call s:SetKeywords()
549	let loc=matchend(stack,'\<.\{-1,}\>')
550	exe 'let '.a:sname.'=strpart(stack, loc+1, strlen(stack))'
551	let top=strpart(stack, match(stack, '\<'), loc)
552	call s:RestoreKeywords()
553	return top
554endfunction
555
556function! s:Clearstack(sname)
557	exe 'let '.a:sname."=''"
558endfunction
559