1 " PDV (phpDocumentor for Vim)
2 " ===========================
6 " Copyright 2005 by Tobias Schlitt <toby@php.net>
7 " Inspired by phpDoc script for Vim by Vidyut Luther (http://www.phpcult.com/).
9 " Provided under the GPL (http://www.gnu.org/copyleft/gpl.html).
11 " This script provides functions to generate phpDocumentor conform
12 " documentation blocks for your PHP code. The script currently
19 " All of those supporting all PHP 4 and 5 syntax elements.
21 " Beside that it allows you to define default values for phpDocumentor tags
22 " like @version (I use $id$ here), @author, @license and so on.
24 " For function/method parameters and attributes, the script tries to guess the
25 " type as good as possible from PHP5 type hints or default values (array, bool,
28 " You can use this script by mapping the function PhpDoc() to any
29 " key combination. Hit this on the line where the element to document
30 " resides and the doc block will be created directly above that line.
35 " For example include into your .vimrc:
37 " source ~/.vim/php-doc.vim
38 " imap <C-o>
\e:set paste<CR>:exe PhpDoc()<CR>:set nopaste<CR>i
40 " This includes the script and maps the combination <ctrl>+o (only in
41 " insert mode) to the doc function.
49 " * Created the initial version of this script while playing around with VIM
50 " scripting the first time and trying to fix Vidyut's solution, which
51 " resulted in a complete rewrite.
55 " * Fixed issues when using tabs instead of spaces.
56 " * Fixed some parsing bugs when using a different coding style.
57 " * Fixed bug with call-by-reference parameters.
58 " * ATTENTION: This version already has code for the next version 1.1.0,
59 " which is propably not working!
61 " Version 1.1.0 (preview)
63 " * Added foldmarker generation.
66 if has ("user_commands")
70 " After phpDoc standard
71 let g:pdv_cfg_CommentHead = "/**"
72 let g:pdv_cfg_Comment1 = " * "
73 let g:pdv_cfg_Commentn = " * "
74 let g:pdv_cfg_CommentTail = " */"
75 let g:pdv_cfg_CommentSingle = "//"
78 let g:pdv_cfg_Type = "mixed"
79 let g:pdv_cfg_Package = ""
80 let g:pdv_cfg_Version = "$id$"
81 let g:pdv_cfg_Author = "Tobias Schlitt <toby@php.net>"
82 let g:pdv_cfg_Copyright = "1997-2005 The PHP Group"
83 let g:pdv_cfg_License = "PHP Version 3.0 {@link http://www.php.net/license/3_0.txt}"
85 let g:pdv_cfg_ReturnVal = "void"
87 " Wether to create @uses tags for implementation of interfaces and inheritance
88 let g:pdv_cfg_Uses = 1
91 " :set paste before documenting (1|0)? Recommended.
92 let g:pdv_cfg_paste = 1
94 " Wether for PHP5 code PHP4 tags should be set, like @access,... (1|0)?
95 let g:pdv_cfg_php4always = 1
97 " Wether to guess scopes after PEAR coding standards:
98 " $_foo/_bar() == <private|protected> (1|0)?
99 let g:pdv_cfg_php4guess = 1
101 " If you selected 1 for the last value, this scope identifier will be used for
102 " the identifiers having an _ in the first place.
103 let g:pdv_cfg_php4guessval = "protected"
106 " Regular expressions
109 let g:pdv_re_comment = ' *\*/ *'
111 " (private|protected|public)
112 let g:pdv_re_scope = '\(private\|protected\|public\)'
114 let g:pdv_re_static = '\(static\)'
116 let g:pdv_re_abstract = '\(abstract\)'
118 let g:pdv_re_final = '\(final\)'
120 " [:space:]*(private|protected|public|static|abstract)*[:space:]+[:identifier:]+\([:params:]\)
121 let g:pdv_re_func = '^\s*\([a-zA-Z ]*\)function\s\+\([^ (]\+\)\s*(\s*\(.*\)\s*)\s*[{;]\?$'
122 " [:typehint:]*[:space:]*$[:identifier]\([:space:]*=[:space:]*[:value:]\)?
123 let g:pdv_re_param = ' *\([^ &]*\) *&\?\$\([A-Za-z_][A-Za-z0-9_]*\) *=\? *\(.*\)\?$'
125 " [:space:]*(private|protected|public\)[:space:]*$[:identifier:]+\([:space:]*=[:space:]*[:value:]+\)*;
126 let g:pdv_re_attribute = '^\s*\(\(private\|public\|protected\|var\|static\)\+\)\s*\$\([^ ;=]\+\)[ =]*\(.*\);\?$'
128 " [:spacce:]*(abstract|final|)[:space:]*(class|interface)+[:space:]+\(extends ([:identifier:])\)?[:space:]*\(implements ([:identifier:][, ]*)+\)?
129 let g:pdv_re_class = '^\s*\([a-zA-Z]*\)\s*\(interface\|class\)\s*\([^ ]\+\)\s*\(extends\)\?\s*\([a-zA-Z0-9]*\)\?\s*\(implements*\)\? *\([a-zA-Z0-9_ ,]*\)\?.*$'
131 let g:pdv_re_array = "^array *(.*"
132 let g:pdv_re_float = '^[0-9.]\+'
133 let g:pdv_re_int = '^[0-9]\+$'
134 let g:pdv_re_string = "['\"].*"
135 let g:pdv_re_bool = "\(true\|false\)"
137 let g:pdv_re_indent = '^\s*'
139 " Shortcuts for editing the text:
140 let g:pdv_cfg_BOL = "norm! o"
141 let g:pdv_cfg_EOL = "
\e"
146 " Document a single line of code ( does not check if doc block already exists )
149 let l:endline = line(".") + 1
151 exe "norm! " . l:endline . "G$"
156 " Documents a whole range of code lines ( does not add defualt doc block to
157 " unknown types of lines ). Skips elements where a docblock is already
159 func! PhpDocRange() range
160 let l:line = a:firstline
161 let l:endLine = a:lastline
162 let l:elementName = ""
163 while l:line <= l:endLine
164 " TODO: Replace regex check for existing doc with check more lines
166 if (getline(l:line) =~ g:pdv_re_func || getline(l:line) =~ g:pdv_re_attribute || getline(l:line) =~ g:pdv_re_class) && getline(l:line - 1) !~ g:pdv_re_comment
168 " Ensure we are on the correct line to run PhpDoc()
169 exe "norm! " . l:line . "G$"
170 " No matter what, this returns the element name
171 let l:elementName = PhpDoc()
172 let l:endLine = l:endLine + (line(".") - l:line) + 1
173 let l:line = line(".") + 1
175 let l:line = l:line + 1
182 " func! PhpDocFold(name)
183 " let l:startline = line(".")
184 " let l:currentLine = l:startLine
185 " let l:commentHead = escape(g:pdv_cfg_CommentHead, "*.");
186 " let l:txtBOL = g:pdv_cfg_BOL . matchstr(l:name, '^\s*')
187 " " Search above for comment start
188 " while (l:currentLine > 1)
189 " if (matchstr(l:commentHead, getline(l:currentLine)))
192 " let l:currentLine = l:currentLine + 1
194 " " Goto 1 line above and open a newline
195 " exe "norm! " . (l:currentLine - 1) . "Go\<ESC>"
196 " " Write the fold comment
197 " exe l:txtBOL . g:pdv_cfg_CommentSingle . " {"."{{ " . a:name . g:pdv_cfg_EOL
198 " " Add another newline below that
199 " exe "norm! o\<ESC>"
200 " " Search for our comment line
201 " let l:currentLine = line(".")
202 " while (l:currentLine <= line("$"))
215 " Needed for my .vimrc: Switch off all other enhancements while generating docs
216 let l:paste = &g:paste
217 let &g:paste = g:pdv_cfg_paste == 1 ? 1 : &g:paste
219 let l:line = getline(".")
222 if l:line =~ g:pdv_re_func
223 let l:result = PhpDocFunc()
225 elseif l:line =~ g:pdv_re_attribute
226 let l:result = PhpDocVar()
228 elseif l:line =~ g:pdv_re_class
229 let l:result = PhpDocClass()
232 let l:result = PhpDocDefault()
236 " if g:pdv_cfg_folds == 1
237 " PhpDocFolds(l:result)
240 let &g:paste = l:paste
249 " Line for the comment to begin
250 let commentline = line (".") - 1
252 let l:name = substitute (getline ("."), '^\(.*\)\/\/.*$', '\1', "")
254 "exe g:pdv_cfg_BOL . "DEBUG:" . name. g:pdv_cfg_EOL
256 " First some things to make it more easy for us:
257 " tab -> space && space+ -> space
258 " let l:name = substitute (l:name, '\t', ' ', "")
259 " Orphan. We're now using \s everywhere...
261 " Now we have to split DECL in three parts:
262 " \[(skopemodifier\)]\(funcname\)\(parameters\)
263 let l:indent = matchstr(l:name, g:pdv_re_indent)
265 let l:modifier = substitute (l:name, g:pdv_re_func, '\1', "g")
266 let l:funcname = substitute (l:name, g:pdv_re_func, '\2', "g")
267 let l:parameters = substitute (l:name, g:pdv_re_func, '\3', "g") . ","
268 let l:scope = PhpDocScope(l:modifier, l:funcname)
269 let l:static = g:pdv_cfg_php4always == 1 ? matchstr(l:modifier, g:pdv_re_static) : ""
270 let l:abstract = g:pdv_cfg_php4always == 1 ? matchstr(l:modifier, g:pdv_re_abstract) : ""
271 let l:final = g:pdv_cfg_php4always == 1 ? matchstr(l:modifier, g:pdv_re_final) : ""
273 exe "norm! " . commentline . "G$"
276 let l:txtBOL = g:pdv_cfg_BOL . l:indent
278 exe l:txtBOL . g:pdv_cfg_CommentHead . g:pdv_cfg_EOL
279 exe l:txtBOL . g:pdv_cfg_Comment1 . funcname . " " . g:pdv_cfg_EOL
280 exe l:txtBOL . g:pdv_cfg_Commentn . g:pdv_cfg_EOL
282 while (l:parameters != ",") && (l:parameters != "")
284 let _p = substitute (l:parameters, '\([^,]*\) *, *\(.*\)', '\1', "")
285 " Remove this one from list
286 let l:parameters = substitute (l:parameters, '\([^,]*\) *, *\(.*\)', '\2', "")
288 let l:paramtype = substitute (_p, g:pdv_re_param, '\1', "")
290 let l:paramname = substitute (_p, g:pdv_re_param, '\2', "")
292 let l:paramdefault = substitute (_p, g:pdv_re_param, '\3', "")
295 let l:paramtype = PhpDocType(l:paramdefault)
299 let l:paramtype = " " . l:paramtype
301 exe l:txtBOL . g:pdv_cfg_Commentn . "@param" . l:paramtype . " $" . l:paramname . " " . g:pdv_cfg_EOL
305 exe l:txtBOL . g:pdv_cfg_Commentn . "@static" . g:pdv_cfg_EOL
308 exe l:txtBOL . g:pdv_cfg_Commentn . "@abstract" . g:pdv_cfg_EOL
311 exe l:txtBOL . g:pdv_cfg_Commentn . "@final" . g:pdv_cfg_EOL
314 exe l:txtBOL . g:pdv_cfg_Commentn . "@access " . l:scope . g:pdv_cfg_EOL
316 exe l:txtBOL . g:pdv_cfg_Commentn . "@return " . g:pdv_cfg_ReturnVal . g:pdv_cfg_EOL
318 " Close the comment block.
319 exe l:txtBOL . g:pdv_cfg_CommentTail . g:pdv_cfg_EOL
320 return l:modifier ." ". l:funcname
327 " Line for the comment to begin
328 let commentline = line (".") - 1
330 let l:name = substitute (getline ("."), '^\(.*\)\/\/.*$', '\1', "")
332 " Now we have to split DECL in three parts:
333 " \[(skopemodifier\)]\(funcname\)\(parameters\)
334 " let l:name = substitute (l:name, '\t', ' ', "")
335 " Orphan. We're now using \s everywhere...
337 let l:indent = matchstr(l:name, g:pdv_re_indent)
339 let l:modifier = substitute (l:name, g:pdv_re_attribute, '\1', "g")
340 let l:varname = substitute (l:name, g:pdv_re_attribute, '\3', "g")
341 let l:default = substitute (l:name, g:pdv_re_attribute, '\4', "g")
342 let l:scope = PhpDocScope(l:modifier, l:varname)
344 let l:static = g:pdv_cfg_php4always == 1 ? matchstr(l:modifier, g:pdv_re_static) : ""
346 let l:type = PhpDocType(l:default)
348 exe "norm! " . commentline . "G$"
351 let l:txtBOL = g:pdv_cfg_BOL . l:indent
353 exe l:txtBOL . g:pdv_cfg_CommentHead . g:pdv_cfg_EOL
354 exe l:txtBOL . g:pdv_cfg_Comment1 . l:varname . " " . g:pdv_cfg_EOL
355 exe l:txtBOL . g:pdv_cfg_Commentn . g:pdv_cfg_EOL
357 exe l:txtBOL . g:pdv_cfg_Commentn . "@static" . g:pdv_cfg_EOL
359 exe l:txtBOL . g:pdv_cfg_Commentn . "@var " . l:type . g:pdv_cfg_EOL
361 exe l:txtBOL . g:pdv_cfg_Commentn . "@access " . l:scope . g:pdv_cfg_EOL
364 " Close the comment block.
365 exe l:txtBOL . g:pdv_cfg_CommentTail . g:pdv_cfg_EOL
366 return l:modifier ." ". l:varname
373 " Line for the comment to begin
374 let commentline = line (".") - 1
376 let l:name = substitute (getline ("."), '^\(.*\)\/\/.*$', '\1', "")
378 "exe g:pdv_cfg_BOL . "DEBUG:" . name. g:pdv_cfg_EOL
380 " First some things to make it more easy for us:
381 " tab -> space && space+ -> space
382 " let l:name = substitute (l:name, '\t', ' ', "")
383 " Orphan. We're now using \s everywhere...
385 " Now we have to split DECL in three parts:
386 " \[(skopemodifier\)]\(classname\)\(parameters\)
387 let l:indent = matchstr(l:name, g:pdv_re_indent)
389 let l:modifier = substitute (l:name, g:pdv_re_class, '\1', "g")
390 let l:classname = substitute (l:name, g:pdv_re_class, '\3', "g")
391 let l:extends = g:pdv_cfg_Uses == 1 ? substitute (l:name, g:pdv_re_class, '\5', "g") : ""
392 let l:interfaces = g:pdv_cfg_Uses == 1 ? substitute (l:name, g:pdv_re_class, '\7', "g") . "," : ""
394 let l:abstract = g:pdv_cfg_php4always == 1 ? matchstr(l:modifier, g:pdv_re_abstract) : ""
395 let l:final = g:pdv_cfg_php4always == 1 ? matchstr(l:modifier, g:pdv_re_final) : ""
397 exe "norm! " . commentline . "G$"
400 let l:txtBOL = g:pdv_cfg_BOL . l:indent
402 exe l:txtBOL . g:pdv_cfg_CommentHead . g:pdv_cfg_EOL
403 exe l:txtBOL . g:pdv_cfg_Comment1 . l:classname . " " . g:pdv_cfg_EOL
404 exe l:txtBOL . g:pdv_cfg_Commentn . g:pdv_cfg_EOL
405 if l:extends != "" && l:extends != "implements"
406 exe l:txtBOL . g:pdv_cfg_Commentn . "@uses " . l:extends . g:pdv_cfg_EOL
409 while (l:interfaces != ",") && (l:interfaces != "")
411 let interface = substitute (l:interfaces, '\([^, ]*\) *, *\(.*\)', '\1', "")
412 " Remove this one from list
413 let l:interfaces = substitute (l:interfaces, '\([^, ]*\) *, *\(.*\)', '\2', "")
414 exe l:txtBOL . g:pdv_cfg_Commentn . "@uses " . l:interface . g:pdv_cfg_EOL
418 exe l:txtBOL . g:pdv_cfg_Commentn . "@abstract" . g:pdv_cfg_EOL
421 exe l:txtBOL . g:pdv_cfg_Commentn . "@final" . g:pdv_cfg_EOL
423 exe l:txtBOL . g:pdv_cfg_Commentn . "@package " . g:pdv_cfg_Package . g:pdv_cfg_EOL
424 exe l:txtBOL . g:pdv_cfg_Commentn . "@version " . g:pdv_cfg_Version . g:pdv_cfg_EOL
425 exe l:txtBOL . g:pdv_cfg_Commentn . "@copyright " . g:pdv_cfg_Copyright . g:pdv_cfg_EOL
426 exe l:txtBOL . g:pdv_cfg_Commentn . "@author " . g:pdv_cfg_Author g:pdv_cfg_EOL
427 exe l:txtBOL . g:pdv_cfg_Commentn . "@license " . g:pdv_cfg_License . g:pdv_cfg_EOL
429 " Close the comment block.
430 exe l:txtBOL . g:pdv_cfg_CommentTail . g:pdv_cfg_EOL
431 return l:modifier ." ". l:classname
437 func! PhpDocScope(modifiers, identifier)
438 " exe g:pdv_cfg_BOL . DEBUG: . a:modifiers . g:pdv_cfg_EOL
440 if matchstr (a:modifiers, g:pdv_re_scope) != ""
441 if g:pdv_cfg_php4always == 1
442 let l:scope = matchstr (a:modifiers, g:pdv_re_scope)
447 if l:scope =~ "^\s*$" && g:pdv_cfg_php4guess
448 if a:identifier[0] == "_"
449 let l:scope = g:pdv_cfg_php4guessval
451 let l:scope = "public"
454 return l:scope != "x" ? l:scope : ""
460 func! PhpDocType(typeString)
462 if a:typeString =~ g:pdv_re_array
465 if a:typeString =~ g:pdv_re_float
468 if a:typeString =~ g:pdv_re_int
471 if a:typeString =~ g:pdv_re_string
472 let l:type = "string"
474 if a:typeString =~ g:pdv_re_bool
478 let l:type = g:pdv_cfg_Type
484 " {{{ PhpDocDefault()
486 func! PhpDocDefault()
487 " Line for the comment to begin
488 let commentline = line (".") - 1
490 let l:indent = matchstr(getline("."), '^\ *')
492 exe "norm! " . commentline . "G$"
495 let l:txtBOL = g:pdv_cfg_BOL . indent
497 exe l:txtBOL . g:pdv_cfg_CommentHead . g:pdv_cfg_EOL
498 exe l:txtBOL . g:pdv_cfg_Commentn . " " . g:pdv_cfg_EOL
500 " Close the comment block.
501 exe l:txtBOL . g:pdv_cfg_CommentTail . g:pdv_cfg_EOL
506 endif " user_commands