diff --git a/GetLatest/GetLatestVimScripts.dat b/GetLatest/GetLatestVimScripts.dat index 5375347..de6141c 100644 --- a/GetLatest/GetLatestVimScripts.dat +++ b/GetLatest/GetLatestVimScripts.dat @@ -8,7 +8,8 @@ ScriptID SourceID Filename 2727 11120 jsbeautify.vim 2666 13424 Mark 2262 8944 occur.vim -910 14079 pydoc.vim +910 14349 pydoc.vim +1879 11894 AutoComplPop #2421 9423 pysmell.vim 152 3342 showmarks.vim 2540 11006 snipMate.vim @@ -27,7 +28,7 @@ ScriptID SourceID Filename # compiler 891 10365 pylint.vim # ftplugin -2441 14215 pyflakes.vim +2441 14288 pyflakes.vim 30 9196 python_fn.vim ### indent 1936 7708 javascript.vim diff --git a/autoload/acp.vim b/autoload/acp.vim new file mode 100644 index 0000000..827bbcc --- /dev/null +++ b/autoload/acp.vim @@ -0,0 +1,431 @@ +"============================================================================= +" Copyright (c) 2007-2009 Takeshi NISHIDA +" +"============================================================================= +" LOAD GUARD {{{1 + +if exists('g:loaded_autoload_acp') || v:version < 702 + finish +endif +let g:loaded_autoload_acp = 1 + +" }}}1 +"============================================================================= +" GLOBAL FUNCTIONS: {{{1 + +" +function acp#enable() + call acp#disable() + + augroup AcpGlobalAutoCommand + autocmd! + autocmd InsertEnter * unlet! s:posLast s:lastUncompletable + autocmd InsertLeave * call s:finishPopup(1) + augroup END + + if g:acp_mappingDriven + call s:mapForMappingDriven() + else + autocmd AcpGlobalAutoCommand CursorMovedI * call s:feedPopup() + endif + + nnoremap i i=feedPopup() + nnoremap a a=feedPopup() + nnoremap R R=feedPopup() +endfunction + +" +function acp#disable() + call s:unmapForMappingDriven() + augroup AcpGlobalAutoCommand + autocmd! + augroup END + nnoremap i | nunmap i + nnoremap a | nunmap a + nnoremap R | nunmap R +endfunction + +" +function acp#lock() + let s:lockCount += 1 +endfunction + +" +function acp#unlock() + let s:lockCount -= 1 + if s:lockCount < 0 + let s:lockCount = 0 + throw "AutoComplPop: not locked" + endif +endfunction + +" +function acp#meetsForSnipmate(context) + if g:acp_behaviorSnipmateLength < 0 + return 0 + endif + let matches = matchlist(a:context, '\(^\|\s\|\<\)\(\u\{' . + \ g:acp_behaviorSnipmateLength . ',}\)$') + return !empty(matches) && !empty(s:getMatchingSnipItems(matches[2])) +endfunction + +" +function acp#meetsForKeyword(context) + if g:acp_behaviorKeywordLength < 0 + return 0 + endif + let matches = matchlist(a:context, '\(\k\{' . g:acp_behaviorKeywordLength . ',}\)$') + if empty(matches) + return 0 + endif + for ignore in g:acp_behaviorKeywordIgnores + if stridx(ignore, matches[1]) == 0 + return 0 + endif + endfor + return 1 +endfunction + +" +function acp#meetsForFile(context) + if g:acp_behaviorFileLength < 0 + return 0 + endif + if has('win32') || has('win64') + let separator = '[/\\]' + else + let separator = '\/' + endif + if a:context !~ '\f' . separator . '\f\{' . g:acp_behaviorFileLength . ',}$' + return 0 + endif + return a:context !~ '[*/\\][/\\]\f*$\|[^[:print:]]\f*$' +endfunction + +" +function acp#meetsForRubyOmni(context) + if !has('ruby') + return 0 + endif + if g:acp_behaviorRubyOmniMethodLength >= 0 && + \ a:context =~ '[^. \t]\(\.\|::\)\k\{' . + \ g:acp_behaviorRubyOmniMethodLength . ',}$' + return 1 + endif + if g:acp_behaviorRubyOmniSymbolLength >= 0 && + \ a:context =~ '\(^\|[^:]\):\k\{' . + \ g:acp_behaviorRubyOmniSymbolLength . ',}$' + return 1 + endif + return 0 +endfunction + +" +function acp#meetsForPythonOmni(context) + return has('python') && g:acp_behaviorPythonOmniLength >= 0 && + \ a:context =~ '\k\.\k\{' . g:acp_behaviorPythonOmniLength . ',}$' +endfunction + +" +function acp#meetsForPerlOmni(context) + return g:acp_behaviorPerlOmniLength >= 0 && + \ a:context =~ '\w->\k\{' . g:acp_behaviorPerlOmniLength . ',}$' +endfunction + +" +function acp#meetsForXmlOmni(context) + return g:acp_behaviorXmlOmniLength >= 0 && + \ a:context =~ '\(<\|<\/\|<[^>]\+ \|<[^>]\+=\"\)\k\{' . + \ g:acp_behaviorXmlOmniLength . ',}$' +endfunction + +" +function acp#meetsForHtmlOmni(context) + return g:acp_behaviorHtmlOmniLength >= 0 && + \ a:context =~ '\(<\|<\/\|<[^>]\+ \|<[^>]\+=\"\)\k\{' . + \ g:acp_behaviorHtmlOmniLength . ',}$' +endfunction + +" +function acp#meetsForCssOmni(context) + if g:acp_behaviorCssOmniPropertyLength >= 0 && + \ a:context =~ '\(^\s\|[;{]\)\s*\k\{' . + \ g:acp_behaviorCssOmniPropertyLength . ',}$' + return 1 + endif + if g:acp_behaviorCssOmniValueLength >= 0 && + \ a:context =~ '[:@!]\s*\k\{' . + \ g:acp_behaviorCssOmniValueLength . ',}$' + return 1 + endif + return 0 +endfunction + +" +function acp#completeSnipmate(findstart, base) + if a:findstart + let s:posSnipmateCompletion = len(matchstr(s:getCurrentText(), '.*\U')) + return s:posSnipmateCompletion + endif + let lenBase = len(a:base) + let items = filter(GetSnipsInCurrentScope(), + \ 'strpart(v:key, 0, lenBase) ==? a:base') + return map(sort(items(items)), 's:makeSnipmateItem(v:val[0], v:val[1])') +endfunction + +" +function acp#onPopupCloseSnipmate() + let word = s:getCurrentText()[s:posSnipmateCompletion :] + for trigger in keys(GetSnipsInCurrentScope()) + if word ==# trigger + call feedkeys("\=TriggerSnippet()\", "n") + return 0 + endif + endfor + return 1 +endfunction + +" +function acp#onPopupPost() + " to clear = expression on command-line + echo '' + if pumvisible() + inoremap acp#onBs() + inoremap acp#onBs() + " a command to restore to original text and select the first match + return (s:behavsCurrent[s:iBehavs].command =~# "\" ? "\\" + \ : "\\") + endif + let s:iBehavs += 1 + if len(s:behavsCurrent) > s:iBehavs + call s:setCompletefunc() + return printf("\%s\=acp#onPopupPost()\", + \ s:behavsCurrent[s:iBehavs].command) + else + let s:lastUncompletable = { + \ 'word': s:getCurrentWord(), + \ 'commands': map(copy(s:behavsCurrent), 'v:val.command')[1:], + \ } + call s:finishPopup(0) + return "\" + endif +endfunction + +" +function acp#onBs() + " using "matchstr" and not "strpart" in order to handle multi-byte + " characters + if call(s:behavsCurrent[s:iBehavs].meets, + \ [matchstr(s:getCurrentText(), '.*\ze.')]) + return "\" + endif + return "\\" +endfunction + +" }}}1 +"============================================================================= +" LOCAL FUNCTIONS: {{{1 + +" +function s:mapForMappingDriven() + call s:unmapForMappingDriven() + let s:keysMappingDriven = [ + \ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + \ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + \ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + \ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + \ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + \ '-', '_', '~', '^', '.', ',', ':', '!', '#', '=', '%', '$', '@', '<', '>', '/', '\', + \ '', '', '', ] + for key in s:keysMappingDriven + execute printf('inoremap %s %s=feedPopup()', + \ key, key) + endfor +endfunction + +" +function s:unmapForMappingDriven() + if !exists('s:keysMappingDriven') + return + endif + for key in s:keysMappingDriven + execute 'iunmap ' . key + endfor + let s:keysMappingDriven = [] +endfunction + +" +function s:setTempOption(group, name, value) + call extend(s:tempOptionSet[a:group], { a:name : eval('&' . a:name) }, 'keep') + execute printf('let &%s = a:value', a:name) +endfunction + +" +function s:restoreTempOptions(group) + for [name, value] in items(s:tempOptionSet[a:group]) + execute printf('let &%s = value', name) + endfor + let s:tempOptionSet[a:group] = {} +endfunction + +" +function s:getCurrentWord() + return matchstr(s:getCurrentText(), '\k*$') +endfunction + +" +function s:getCurrentText() + return strpart(getline('.'), 0, col('.') - 1) +endfunction + +" +function s:getPostText() + return strpart(getline('.'), col('.') - 1) +endfunction + +" +function s:isModifiedSinceLastCall() + if exists('s:posLast') + let posPrev = s:posLast + let nLinesPrev = s:nLinesLast + let textPrev = s:textLast + endif + let s:posLast = getpos('.') + let s:nLinesLast = line('$') + let s:textLast = getline('.') + if !exists('posPrev') + return 1 + elseif posPrev[1] != s:posLast[1] || nLinesPrev != s:nLinesLast + return (posPrev[1] - s:posLast[1] == nLinesPrev - s:nLinesLast) + elseif textPrev ==# s:textLast + return 0 + elseif posPrev[2] > s:posLast[2] + return 1 + elseif has('gui_running') && has('multi_byte') + " NOTE: auto-popup causes a strange behavior when IME/XIM is working + return posPrev[2] + 1 == s:posLast[2] + endif + return posPrev[2] != s:posLast[2] +endfunction + +" +function s:makeCurrentBehaviorSet() + let modified = s:isModifiedSinceLastCall() + if exists('s:behavsCurrent[s:iBehavs].repeat') && s:behavsCurrent[s:iBehavs].repeat + let behavs = [ s:behavsCurrent[s:iBehavs] ] + elseif exists('s:behavsCurrent[s:iBehavs]') + return [] + elseif modified + let behavs = copy(exists('g:acp_behavior[&filetype]') + \ ? g:acp_behavior[&filetype] + \ : g:acp_behavior['*']) + else + return [] + endif + let text = s:getCurrentText() + call filter(behavs, 'call(v:val.meets, [text])') + let s:iBehavs = 0 + if exists('s:lastUncompletable') && + \ stridx(s:getCurrentWord(), s:lastUncompletable.word) == 0 && + \ map(copy(behavs), 'v:val.command') ==# s:lastUncompletable.commands + let behavs = [] + else + unlet! s:lastUncompletable + endif + return behavs +endfunction + +" +function s:feedPopup() + " NOTE: CursorMovedI is not triggered while the popup menu is visible. And + " it will be triggered when popup menu is disappeared. + if s:lockCount > 0 || pumvisible() || &paste + return '' + endif + if exists('s:behavsCurrent[s:iBehavs].onPopupClose') + if !call(s:behavsCurrent[s:iBehavs].onPopupClose, []) + call s:finishPopup(1) + return '' + endif + endif + let s:behavsCurrent = s:makeCurrentBehaviorSet() + if empty(s:behavsCurrent) + call s:finishPopup(1) + return '' + endif + " In case of dividing words by symbols (e.g. "for(int", "ab==cd") while a + " popup menu is visible, another popup is not available unless input + " or try popup once. So first completion is duplicated. + call insert(s:behavsCurrent, s:behavsCurrent[s:iBehavs]) + call s:setTempOption(s:GROUP0, 'spell', 0) + call s:setTempOption(s:GROUP0, 'completeopt', 'menuone' . (g:acp_completeoptPreview ? ',preview' : '')) + call s:setTempOption(s:GROUP0, 'complete', g:acp_completeOption) + call s:setTempOption(s:GROUP0, 'ignorecase', g:acp_ignorecaseOption) + " NOTE: With CursorMovedI driven, Set 'lazyredraw' to avoid flickering. + " With Mapping driven, set 'nolazyredraw' to make a popup menu visible. + call s:setTempOption(s:GROUP0, 'lazyredraw', !g:acp_mappingDriven) + " NOTE: 'textwidth' must be restored after . + call s:setTempOption(s:GROUP1, 'textwidth', 0) + call s:setCompletefunc() + call feedkeys(s:behavsCurrent[s:iBehavs].command . "\=acp#onPopupPost()\", 'n') + return '' " this function is called by = +endfunction + +" +function s:finishPopup(fGroup1) + inoremap | iunmap + inoremap | iunmap + let s:behavsCurrent = [] + call s:restoreTempOptions(s:GROUP0) + if a:fGroup1 + call s:restoreTempOptions(s:GROUP1) + endif +endfunction + +" +function s:setCompletefunc() + if exists('s:behavsCurrent[s:iBehavs].completefunc') + call s:setTempOption(0, 'completefunc', s:behavsCurrent[s:iBehavs].completefunc) + endif +endfunction + +" +function s:makeSnipmateItem(key, snip) + if type(a:snip) == type([]) + let descriptions = map(copy(a:snip), 'v:val[0]') + let snipFormatted = '[MULTI] ' . join(descriptions, ', ') + else + let snipFormatted = substitute(a:snip, '\(\n\|\s\)\+', ' ', 'g') + endif + return { + \ 'word': a:key, + \ 'menu': strpart(snipFormatted, 0, 80), + \ } +endfunction + +" +function s:getMatchingSnipItems(base) + let key = a:base . "\n" + if !exists('s:snipItems[key]') + let s:snipItems[key] = items(GetSnipsInCurrentScope()) + call filter(s:snipItems[key], 'strpart(v:val[0], 0, len(a:base)) ==? a:base') + call map(s:snipItems[key], 's:makeSnipmateItem(v:val[0], v:val[1])') + endif + return s:snipItems[key] +endfunction + +" }}}1 +"============================================================================= +" INITIALIZATION {{{1 + +let s:GROUP0 = 0 +let s:GROUP1 = 1 +let s:lockCount = 0 +let s:behavsCurrent = [] +let s:iBehavs = 0 +let s:tempOptionSet = [{}, {}] +let s:snipItems = {} + +" }}}1 +"============================================================================= +" vim: set fdm=marker: diff --git a/doc/acp.txt b/doc/acp.txt new file mode 100644 index 0000000..324c88b --- /dev/null +++ b/doc/acp.txt @@ -0,0 +1,512 @@ +*acp.txt* Automatically opens popup menu for completions. + + Copyright (c) 2007-2009 Takeshi NISHIDA + +AutoComplPop *autocomplpop* *acp* + +INTRODUCTION |acp-introduction| +INSTALLATION |acp-installation| +USAGE |acp-usage| +COMMANDS |acp-commands| +OPTIONS |acp-options| +SPECIAL THANKS |acp-thanks| +CHANGELOG |acp-changelog| +ABOUT |acp-about| + + +============================================================================== +INTRODUCTION *acp-introduction* + +With this plugin, your vim comes to automatically opens popup menu for +completions when you enter characters or move the cursor in Insert mode. It +won't prevent you continuing entering characters. + + +============================================================================== +INSTALLATION *acp-installation* + +Put all files into your runtime directory. If you have the zip file, extract +it to your runtime directory. + +You should place the files as follows: +> + /plugin/acp.vim + /doc/acp.txt + ... +< +If you disgust to jumble up this plugin and other plugins in your runtime +directory, put the files into new directory and just add the directory path to +'runtimepath'. It's easy to uninstall the plugin. + +And then update your help tags files to enable fuzzyfinder help. See +|add-local-help| for details. + + +============================================================================== +USAGE *acp-usage* + +Once this plugin is installed, auto-popup is enabled at startup by default. + +Which completion method is used depends on the text before the cursor. The +default behavior is as follows: + + kind filetype text before the cursor ~ + Keyword * two keyword characters + Filename * a filename character + a path separator + + 0 or more filename character + Omni ruby ".", "::" or non-word character + ":" + (|+ruby| required.) + Omni python "." (|+python| required.) + Omni xml "<", "" characters + " ") + Omni html/xhtml "<", "" characters + " ") + Omni css (":", ";", "{", "^", "@", or "!") + + 0 or 1 space + +Also, you can make user-defined completion and snipMate's trigger completion +(|acp-snipMate|) auto-popup if the options are set. + +These behavior are customizable. + + *acp-snipMate* +snipMate's Trigger Completion ~ + +snipMate's trigger completion enables you to complete a snippet trigger +provided by snipMate plugin +(http://www.vim.org/scripts/script.php?script_id=2540) and expand it. + + +To enable auto-popup for this completion, add following function to +plugin/snipMate.vim: +> + fun! GetSnipsInCurrentScope() + let snips = {} + for scope in [bufnr('%')] + split(&ft, '\.') + ['_'] + call extend(snips, get(s:snippets, scope, {}), 'keep') + call extend(snips, get(s:multi_snips, scope, {}), 'keep') + endfor + return snips + endf +< +And set |g:acp_behaviorSnipmateLength| option to 1. + +There is the restriction on this auto-popup, that the word before cursor must +consist only of uppercase characters. + + *acp-perl-omni* +Perl Omni-Completion ~ + +AutoComplPop supports perl-completion.vim +(http://www.vim.org/scripts/script.php?script_id=2852). + +To enable auto-popup for this completion, set |g:acp_behaviorPerlOmniLength| +option to 0 or more. + + +============================================================================== +COMMANDS *acp-commands* + + *:AcpEnable* +:AcpEnable + enables auto-popup. + + *:AcpDisable* +:AcpDisable + disables auto-popup. + + *:AcpLock* +:AcpLock + suspends auto-popup temporarily. + + For the purpose of avoiding interruption to another script, it is + recommended to insert this command and |:AcpUnlock| than |:AcpDisable| + and |:AcpEnable| . + + *:AcpUnlock* +:AcpUnlock + resumes auto-popup suspended by |:AcpLock| . + + +============================================================================== +OPTIONS *acp-options* + + *g:acp_enableAtStartup* > + let g:acp_enableAtStartup = 1 +< + If non-zero, auto-popup is enabled at startup. + + *g:acp_mappingDriven* > + let g:acp_mappingDriven = 0 +< + If non-zero, auto-popup is triggered by key mappings instead of + |CursorMovedI| event. This is useful to avoid auto-popup by moving + cursor in Insert mode. + + *g:acp_ignorecaseOption* > + let g:acp_ignorecaseOption = 1 +< + Value set to 'ignorecase' temporarily when auto-popup. + + *g:acp_completeOption* > + let g:acp_completeOption = '.,w,b,k' +< + Value set to 'complete' temporarily when auto-popup. + + *g:acp_completeoptPreview* > + let g:acp_completeoptPreview = 0 +< + If non-zero, "preview" is added to 'completeopt' when auto-popup. + + *g:acp_behaviorUserDefinedFunction* > + let g:acp_behaviorUserDefinedFunction = '' +< + |g:acp_behavior-completefunc| for user-defined completion. If empty, + this completion will be never attempted. + + *g:acp_behaviorUserDefinedMeets* > + let g:acp_behaviorUserDefinedMeets = '' +< + |g:acp_behavior-meets| for user-defined completion. If empty, this + completion will be never attempted. + + *g:acp_behaviorSnipmateLength* > + let g:acp_behaviorSnipmateLength = -1 +< + Pattern before the cursor, which are needed to attempt + snipMate-trigger completion. + + *g:acp_behaviorKeywordCommand* > + let g:acp_behaviorKeywordCommand = "\" +< + Command for keyword completion. This option is usually set "\" or + "\". + + *g:acp_behaviorKeywordLength* > + let g:acp_behaviorKeywordLength = 2 +< + Length of keyword characters before the cursor, which are needed to + attempt keyword completion. If negative value, this completion will be + never attempted. + + *g:acp_behaviorKeywordIgnores* > + let g:acp_behaviorKeywordIgnores = [] +< + List of string. If a word before the cursor matches to the front part + of one of them, keyword completion won't be attempted. + + E.g., when there are too many keywords beginning with "get" for the + completion and auto-popup by entering "g", "ge", or "get" causes + response degradation, set ["get"] to this option and avoid it. + + *g:acp_behaviorFileLength* > + let g:acp_behaviorFileLength = 0 +< + Length of filename characters before the cursor, which are needed to + attempt filename completion. If negative value, this completion will + be never attempted. + + *g:acp_behaviorRubyOmniMethodLength* > + let g:acp_behaviorRubyOmniMethodLength = 0 +< + Length of keyword characters before the cursor, which are needed to + attempt ruby omni-completion for methods. If negative value, this + completion will be never attempted. + + *g:acp_behaviorRubyOmniSymbolLength* > + let g:acp_behaviorRubyOmniSymbolLength = 1 +< + Length of keyword characters before the cursor, which are needed to + attempt ruby omni-completion for symbols. If negative value, this + completion will be never attempted. + + *g:acp_behaviorPythonOmniLength* > + let g:acp_behaviorPythonOmniLength = 0 +< + Length of keyword characters before the cursor, which are needed to + attempt python omni-completion. If negative value, this completion + will be never attempted. + + *g:acp_behaviorPerlOmniLength* > + let g:acp_behaviorPerlOmniLength = -1 +< + Length of keyword characters before the cursor, which are needed to + attempt perl omni-completion. If negative value, this completion will + be never attempted. + + See also: |acp-perl-omni| + + *g:acp_behaviorXmlOmniLength* > + let g:acp_behaviorXmlOmniLength = 0 +< + Length of keyword characters before the cursor, which are needed to + attempt XML omni-completion. If negative value, this completion will + be never attempted. + + *g:acp_behaviorHtmlOmniLength* > + let g:acp_behaviorHtmlOmniLength = 0 +< + Length of keyword characters before the cursor, which are needed to + attempt HTML omni-completion. If negative value, this completion will + be never attempted. + + *g:acp_behaviorCssOmniPropertyLength* > + let g:acp_behaviorCssOmniPropertyLength = 1 +< + Length of keyword characters before the cursor, which are needed to + attempt CSS omni-completion for properties. If negative value, this + completion will be never attempted. + + *g:acp_behaviorCssOmniValueLength* > + let g:acp_behaviorCssOmniValueLength = 0 +< + Length of keyword characters before the cursor, which are needed to + attempt CSS omni-completion for values. If negative value, this + completion will be never attempted. + + *g:acp_behavior* > + let g:acp_behavior = {} +< + This option is for advanced users. This setting overrides other + behavior options. This is a |Dictionary|. Each key corresponds to a + filetype. '*' is default. Each value is a list. These are attempted in + sequence until completion item is found. Each element is a + |Dictionary| which has following items: + + "command": *g:acp_behavior-command* + Command to be fed to open popup menu for completions. + + "completefunc": *g:acp_behavior-completefunc* + 'completefunc' will be set to this user-provided function during the + completion. Only makes sense when "command" is "". + + "meets": *g:acp_behavior-meets* + Name of the function which dicides whether or not to attempt this + completion. It will be attempted if this function returns non-zero. + This function takes a text before the cursor. + + "onPopupClose": *g:acp_behavior-onPopupClose* + Name of the function which is called when popup menu for this + completion is closed. Following completions will be suppressed if + this function returns zero. + + "repeat": *g:acp_behavior-repeat* + If non-zero, the last completion is automatically repeated. + + +============================================================================== +SPECIAL THANKS *acp-thanks* + +- Daniel Schierbeck +- Ingo Karkat + + +============================================================================== +CHANGELOG *acp-changelog* + +2.14.1 + - Changed the way of auto-popup for avoiding an issue about filename + completion. + - Fixed a bug that popup menu was opened twice when auto-popup was done. + +2.14 + - Added the support for perl-completion.vim. + +2.13 + - Changed to sort snipMate's triggers. + - Fixed a bug that a wasted character was inserted after snipMate's trigger + completion. + +2.12.1 + - Changed to avoid a strange behavior with Microsoft IME. + +2.12 + - Added g:acp_behaviorKeywordIgnores option. + - Added g:acp_behaviorUserDefinedMeets option and removed + g:acp_behaviorUserDefinedPattern. + - Changed to do auto-popup only when a buffer is modified. + - Changed the structure of g:acp_behavior option. + - Changed to reflect a change of behavior options (named g:acp_behavior*) + any time it is done. + - Fixed a bug that completions after omni completions or snipMate's trigger + completion were never attempted when no candidate for the former + completions was found. + +2.11.1 + - Fixed a bug that a snipMate's trigger could not be expanded when it was + completed. + +2.11 + - Implemented experimental feature which is snipMate's trigger completion. + +2.10 + - Improved the response by changing not to attempt any completion when + keyword characters are entered after a word which has been found that it + has no completion candidate at the last attempt of completions. + - Improved the response by changing to close popup menu when was + pressed and the text before the cursor would not match with the pattern of + current behavior. + +2.9 + - Changed default behavior to support XML omni completion. + - Changed default value of g:acp_behaviorKeywordCommand option. + The option with "\" cause a problem which inserts a match without + when 'dictionary' has been set and keyword completion is done. + - Changed to show error message when incompatible with a installed vim. + +2.8.1 + - Fixed a bug which inserted a selected match to the next line when + auto-wrapping (enabled with 'formatoptions') was performed. + +2.8 + - Added g:acp_behaviorUserDefinedFunction option and + g:acp_behaviorUserDefinedPattern option for users who want to make custom + completion auto-popup. + - Fixed a bug that setting 'spell' on a new buffer made typing go crazy. + +2.7 + - Changed naming conventions for filenames, functions, commands, and options + and thus renamed them. + - Added g:acp_behaviorKeywordCommand option. If you prefer the previous + behavior for keyword completion, set this option "\". + - Changed default value of g:acp_ignorecaseOption option. + + The following were done by Ingo Karkat: + + - ENH: Added support for setting a user-provided 'completefunc' during the + completion, configurable via g:acp_behavior. + - BUG: When the configured completion is or , the command to + restore the original text (in on_popup_post()) must be reverted, too. + - BUG: When using a custom completion function () that also uses + an s:...() function name, the s:GetSidPrefix() function dynamically + determines the wrong SID. Now calling s:DetermineSidPrefix() once during + sourcing and caching the value in s:SID. + - BUG: Should not use custom defined completion mappings. Now + consistently using unmapped completion commands everywhere. (Beforehand, + s:PopupFeeder.feed() used mappings via feedkeys(..., 'm'), but + s:PopupFeeder.on_popup_post() did not due to its invocation via + :map-expr.) + +2.6: + - Improved the behavior of omni completion for HTML/XHTML. + +2.5: + - Added some options to customize behavior easily: + g:AutoComplPop_BehaviorKeywordLength + g:AutoComplPop_BehaviorFileLength + g:AutoComplPop_BehaviorRubyOmniMethodLength + g:AutoComplPop_BehaviorRubyOmniSymbolLength + g:AutoComplPop_BehaviorPythonOmniLength + g:AutoComplPop_BehaviorHtmlOmniLength + g:AutoComplPop_BehaviorCssOmniPropertyLength + g:AutoComplPop_BehaviorCssOmniValueLength + +2.4: + - Added g:AutoComplPop_MappingDriven option. + +2.3.1: + - Changed to set 'lazyredraw' while a popup menu is visible to avoid + flickering. + - Changed a behavior for CSS. + - Added support for GetLatestVimScripts. + +2.3: + - Added a behavior for Python to support omni completion. + - Added a behavior for CSS to support omni completion. + +2.2: + - Changed not to work when 'paste' option is set. + - Fixed AutoComplPopEnable command and AutoComplPopDisable command to + map/unmap "i" and "R". + +2.1: + - Fixed the problem caused by "." command in Normal mode. + - Changed to map "i" and "R" to feed completion command after starting + Insert mode. + - Avoided the problem caused by Windows IME. + +2.0: + - Changed to use CursorMovedI event to feed a completion command instead of + key mapping. Now the auto-popup is triggered by moving the cursor. + - Changed to feed completion command after starting Insert mode. + - Removed g:AutoComplPop_MapList option. + +1.7: + - Added behaviors for HTML/XHTML. Now supports the omni completion for + HTML/XHTML. + - Changed not to show expressions for CTRL-R =. + - Changed not to set 'nolazyredraw' while a popup menu is visible. + +1.6.1: + - Changed not to trigger the filename completion by a text which has + multi-byte characters. + +1.6: + - Redesigned g:AutoComplPop_Behavior option. + - Changed default value of g:AutoComplPop_CompleteOption option. + - Changed default value of g:AutoComplPop_MapList option. + +1.5: + - Implemented continuous-completion for the filename completion. And added + new option to g:AutoComplPop_Behavior. + +1.4: + - Fixed the bug that the auto-popup was not suspended in fuzzyfinder. + - Fixed the bug that an error has occurred with Ruby-omni-completion unless + Ruby interface. + +1.3: + - Supported Ruby-omni-completion by default. + - Supported filename completion by default. + - Added g:AutoComplPop_Behavior option. + - Added g:AutoComplPop_CompleteoptPreview option. + - Removed g:AutoComplPop_MinLength option. + - Removed g:AutoComplPop_MaxLength option. + - Removed g:AutoComplPop_PopupCmd option. + +1.2: + - Fixed bugs related to 'completeopt'. + +1.1: + - Added g:AutoComplPop_IgnoreCaseOption option. + - Added g:AutoComplPop_NotEnableAtStartup option. + - Removed g:AutoComplPop_LoadAndEnable option. +1.0: + - g:AutoComplPop_LoadAndEnable option for a startup activation is added. + - AutoComplPopLock command and AutoComplPopUnlock command are added to + suspend and resume. + - 'completeopt' and 'complete' options are changed temporarily while + completing by this script. + +0.4: + - The first match are selected when the popup menu is Opened. You can insert + the first match with CTRL-Y. + +0.3: + - Fixed the problem that the original text is not restored if 'longest' is + not set in 'completeopt'. Now the plugin works whether or not 'longest' is + set in 'completeopt', and also 'menuone'. + +0.2: + - When completion matches are not found, insert CTRL-E to stop completion. + - Clear the echo area. + - Fixed the problem in case of dividing words by symbols, popup menu is + not opened. + +0.1: + - First release. + + +============================================================================== +ABOUT *acp-about* *acp-contact* *acp-author* + +Author: Takeshi NISHIDA +Licence: MIT Licence +URL: http://www.vim.org/scripts/script.php?script_id=1879 + http://bitbucket.org/ns9tks/vim-autocomplpop/ + +Bugs/Issues/Suggestions/Improvements ~ + +Please submit to http://bitbucket.org/ns9tks/vim-autocomplpop/issues/ . + +============================================================================== + vim:tw=78:ts=8:ft=help:norl: + diff --git a/ftplugin/python/pydoc.vim b/ftplugin/python/pydoc.vim index 251be39..f83ddd2 100644 --- a/ftplugin/python/pydoc.vim +++ b/ftplugin/python/pydoc.vim @@ -85,16 +85,15 @@ function! ShowPyDoc(name, type) endif endfunction - +"highlighting function! Highlight(name) execute "sb __doc__" set filetype=man - syn on - execute 'syntax keyword pydoc '.s:name2 + "syn on + execute 'syntax keyword pydoc '.a:name hi pydoc gui=reverse endfunction - "mappings au FileType python,man map pw :call ShowPyDoc('', 1) au FileType python,man map pW :call ShowPyDoc('', 1) diff --git a/ftplugin/python/pyflakes.vim b/ftplugin/python/pyflakes.vim index 18e9eb7..b89705a 100644 --- a/ftplugin/python/pyflakes.vim +++ b/ftplugin/python/pyflakes.vim @@ -46,14 +46,20 @@ if sys.version_info[:2] < (2, 5): scriptdir = os.path.join(os.path.dirname(vim.eval('expand("")')), 'pyflakes') sys.path.insert(0, scriptdir) -from pyflakes import checker, ast, messages +import ast +from pyflakes import checker, messages from operator import attrgetter import re +class loc(object): + def __init__(self, lineno, col=None): + self.lineno = lineno + self.col_offset = col + class SyntaxError(messages.Message): message = 'could not compile: %s' def __init__(self, filename, lineno, col, message): - messages.Message.__init__(self, filename, lineno, col) + messages.Message.__init__(self, filename, loc(lineno, col)) self.message_args = (message,) class blackhole(object): @@ -67,13 +73,12 @@ def check(buffer): # assume everything else that follows is encoded in the encoding. encoding_found = False for n, line in enumerate(contents): - if not encoding_found: - if re.match(r'^# -\*- coding: .+? -*-', line): - encoding_found = True - else: - # skip all preceeding lines - contents = [''] * n + contents[n:] + if n >= 2: break + elif re.match(r'#.*coding[:=]\s*([-\w.]+)', line): + contents = ['']*(n+1) + contents[n+1:] + break + contents = '\n'.join(contents) + '\n' vimenc = vim.eval('&encoding') @@ -82,7 +87,7 @@ def check(buffer): builtins = [] try: - builtins = eval(vim.eval('string(g:pyflakes_builtins)')) + builtins = set(eval(vim.eval('string(g:pyflakes_builtins)'))) except Exception: pass @@ -90,7 +95,7 @@ def check(buffer): # TODO: use warnings filters instead of ignoring stderr old_stderr, sys.stderr = sys.stderr, blackhole() try: - tree = ast.parse(contents, filename) + tree = ast.parse(contents, filename or '') finally: sys.stderr = old_stderr except: @@ -104,7 +109,15 @@ def check(buffer): return [SyntaxError(filename, lineno, offset, str(value))] else: - w = checker.Checker(tree, filename, builtins = builtins) + # pyflakes looks to _MAGIC_GLOBALS in checker.py to see which + # UndefinedNames to ignore + old_globals = getattr(checker,' _MAGIC_GLOBALS', []) + checker._MAGIC_GLOBALS = set(old_globals) | builtins + + w = checker.Checker(tree, filename) + + checker._MAGIC_GLOBALS = old_globals + w.messages.sort(key = attrgetter('lineno')) return w.messages @@ -233,7 +246,7 @@ for w in check(vim.current.buffer): vim.command("let l:qf_item.text = '%s'" % vim_quote(w.message % w.message_args)) vim.command("let l:qf_item.type = 'E'") - if w.col is None or isinstance(w, SyntaxError): + if getattr(w, 'col', None) is None or isinstance(w, SyntaxError): # without column information, just highlight the whole line # (minus the newline) vim.command(r"let s:mID = matchadd('PyFlakes', '\%" + str(w.lineno) + r"l\n\@!')") diff --git a/ftplugin/python/pyflakes/NEWS.txt b/ftplugin/python/pyflakes/NEWS.txt new file mode 100644 index 0000000..2a3e316 --- /dev/null +++ b/ftplugin/python/pyflakes/NEWS.txt @@ -0,0 +1,29 @@ +0.4.0 (2009-11-25): + - Fix reporting for certain SyntaxErrors which lack line number + information. + - Check for syntax errors more rigorously. + - Support checking names used with the class decorator syntax in versions + of Python which have it. + - Detect local variables which are bound but never used. + - Handle permission errors when trying to read source files. + - Handle problems with the encoding of source files. + - Support importing dotted names so as not to incorrectly report them as + redefined unused names. + - Support all forms of the with statement. + - Consider static `__all__` definitions and avoid reporting unused names + if the names are listed there. + - Fix incorrect checking of class names with respect to the names of their + bases in the class statement. + - Support the `__path__` global in `__init__.py`. + +0.3.0 (2009-01-30): + - Display more informative SyntaxError messages. + - Don't hang flymake with unmatched triple quotes (only report a single + line of source for a multiline syntax error). + - Recognize __builtins__ as a defined name. + - Improve pyflakes support for python versions 2.3-2.5 + - Support for if-else expressions and with statements. + - Warn instead of error on non-existant file paths. + - Check for __future__ imports after other statements. + - Add reporting for some types of import shadowing. + - Improve reporting of unbound locals diff --git a/ftplugin/python/pyflakes/README.rst b/ftplugin/python/pyflakes/README.rst index 9f75ef2..124fc3e 100644 --- a/ftplugin/python/pyflakes/README.rst +++ b/ftplugin/python/pyflakes/README.rst @@ -1,36 +1,80 @@ -pyflakes -======== +pyflakes-vim +============ -This version of PyFlakes_ has been improved to use Python's newer ``ast`` -module, instead of ``compiler``. So code checking happens faster, and will stay -up to date with new language changes. +A Vim plugin for checking Python code on the fly. -.. _PyFlakes: http://www.divmod.org/trac/wiki/DivmodPyflakes +PyFlakes catches common Python errors like mistyping a variable name or +accessing a local before it is bound, and also gives warnings for things like +unused imports. + +pyflakes-vim uses the output from PyFlakes to highlight errors in your code. +To locate errors quickly, use quickfix_ commands like :cc. + +Make sure to check vim.org_ for the latest updates. + +.. _pyflakes.vim: http://www.vim.org/scripts/script.php?script_id=2441 +.. _vim.org: http://www.vim.org/scripts/script.php?script_id=2441 +.. _quickfix: http://vimdoc.sourceforge.net/htmldoc/quickfix.html#quickfix + +Quick Installation +------------------ + +1. Make sure your ``.vimrc`` has:: + + filetype on " enables filetype detection + filetype plugin on " enables filetype specific plugins + +2. Download the latest release_. + +3. Unzip ``pyflakes.vim`` and the ``pyflakes`` directory into + ``~/.vim/ftplugin/python`` (or somewhere similar on your + `runtime path`_ that will be sourced for Python files). + +.. _release: http://www.vim.org/scripts/script.php?script_id=2441 +.. _runtime path: http://vimdoc.sourceforge.net/htmldoc/options.html#'runtimepath' + +Installation +------------ + +If you downloaded this from vim.org_, then just drop the contents of the zip +file into ``~/.vim/ftplugin/python``. + +Otherwise, if you're running "from source," you'll need PyFlakes on your +PYTHONPATH somewhere. I recommend getting my PyFlakes_ fork, which retains +column number information and has therfore has more specific error locations. + +.. _vim.org: http://www.vim.org/scripts/script.php?script_id=2441 +.. _PyFlakes: http://github.com/kevinw/pyflakes + +Hacking +------- + +:: + + git clone git://github.com/kevinw/pyflakes-vim.git + cd pyflakes-vim + git clone git://github.com/kevinw/pyflakes.git + +Options +------- + +Set this option to you vimrc file to disable quickfix support:: + + let g:pyflakes_use_quickfix = 0 + +The value is set to 1 by default. TODO ---- + * signs_ support (show warning and error icons to left of the buffer area) + * configuration variables + * parse or intercept useful output from the warnings module -Importing several modules from the same package results in unnecessary warnings: +.. _signs: http://www.vim.org/htmldoc/sign.html -:: +Changelog +--------- - import a.b - import a.c # Redefinition of unused "a" from line 1 - -The following construct for defining a function differently depending on some -condition results in a redefinition warning: - -:: - - if some_condition: - def foo(): do_foo() - else: - def foo(): do_bar() # redefinition of function 'foo' from line 2 - -IDE Integration ---------------- - -* vim: pyflakes-vim_ - -.. _pyflakes-vim: http://github.com/kevinw/pyflakes-vim +Please see http://www.vim.org/scripts/script.php?script_id=2441 for a history of +all changes. diff --git a/ftplugin/python/pyflakes/TODO b/ftplugin/python/pyflakes/TODO deleted file mode 100644 index 69f3f12..0000000 --- a/ftplugin/python/pyflakes/TODO +++ /dev/null @@ -1,11 +0,0 @@ - - Check for methods that override other methods except that they vary by case. - - assign/increment + unbound local error not caught - def foo(): - bar = 5 - def meep(): - bar += 2 - meep() - print bar - - print foo() - diff --git a/ftplugin/python/pyflakes/pyflakes/__init__.py b/ftplugin/python/pyflakes/pyflakes/__init__.py index e69de29..652a8f4 100644 --- a/ftplugin/python/pyflakes/pyflakes/__init__.py +++ b/ftplugin/python/pyflakes/pyflakes/__init__.py @@ -0,0 +1,2 @@ + +__version__ = '0.4.0' diff --git a/ftplugin/python/pyflakes/pyflakes/checker.py b/ftplugin/python/pyflakes/pyflakes/checker.py index b3d6960..7c348b8 100644 --- a/ftplugin/python/pyflakes/pyflakes/checker.py +++ b/ftplugin/python/pyflakes/pyflakes/checker.py @@ -1,54 +1,147 @@ -import ast -from pyflakes import messages +# -*- test-case-name: pyflakes -*- +# (c) 2005-2010 Divmod, Inc. +# See LICENSE file for details + import __builtin__ +import os.path +import _ast + +from pyflakes import messages -allowed_before_future = (ast.Module, ast.ImportFrom, ast.Expr, ast.Str) -defined_names = set(('__file__', '__builtins__')) +# utility function to iterate over an AST node's children, adapted +# from Python 2.6's standard ast module +try: + import ast + iter_child_nodes = ast.iter_child_nodes +except (ImportError, AttributeError): + def iter_child_nodes(node, astcls=_ast.AST): + """ + Yield all direct child nodes of *node*, that is, all fields that are nodes + and all items of fields that are lists of nodes. + """ + for name in node._fields: + field = getattr(node, name, None) + if isinstance(field, astcls): + yield field + elif isinstance(field, list): + for item in field: + yield item + class Binding(object): """ + Represents the binding of a value to a name. + + The checker uses this to keep track of which names have been bound and + which names have not. See L{Assignment} for a special type of binding that + is checked with stricter rules. + @ivar used: pair of (L{Scope}, line-number) indicating the scope and line number that this binding was last used """ + def __init__(self, name, source): self.name = name self.source = source self.used = False + def __str__(self): return self.name + def __repr__(self): return '<%s object %r from line %r at 0x%x>' % (self.__class__.__name__, self.name, self.source.lineno, id(self)) + + class UnBinding(Binding): '''Created by the 'del' operator.''' + + class Importation(Binding): + """ + A binding created by an import statement. + + @ivar fullName: The complete name given to the import statement, + possibly including multiple dotted components. + @type fullName: C{str} + """ def __init__(self, name, source): + self.fullName = name name = name.split('.')[0] super(Importation, self).__init__(name, source) + + +class Argument(Binding): + """ + Represents binding a name as an argument. + """ + + + class Assignment(Binding): - pass + """ + Represents binding a name with an explicit assignment. + + The checker will raise warnings for any Assignment that isn't used. Also, + the checker does not consider assignments in tuple/list unpacking to be + Assignments, rather it treats them as simple Bindings. + """ + + class FunctionDefinition(Binding): - _property_decorator = False + pass + + + +class ExportBinding(Binding): + """ + A binding created by an C{__all__} assignment. If the names in the list + can be determined statically, they will be treated as names for export and + additional checking applied to them. + + The only C{__all__} assignment that can be recognized is one which takes + the value of a literal list containing literal strings. For example:: + + __all__ = ["foo", "bar"] + + Names which are imported and not otherwise used but appear in the value of + C{__all__} will not have an unused import warning reported for them. + """ + def names(self): + """ + Return a list of the names referenced by this binding. + """ + names = [] + if isinstance(self.source, _ast.List): + for node in self.source.elts: + if isinstance(node, _ast.Str): + names.append(node.s) + return names + class Scope(dict): - import_starred = False # set to True when import * is found + importStarred = False # set to True when import * is found + def __repr__(self): return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self), dict.__repr__(self)) + def __init__(self): super(Scope, self).__init__() + + class ClassScope(Scope): pass @@ -69,340 +162,464 @@ class FunctionScope(Scope): class ModuleScope(Scope): pass -class Checker(ast.NodeVisitor): - def __init__(self, tree, filename='(none)', builtins = None): - ast.NodeVisitor.__init__(self) - self.deferred = [] +# Globally defined names which are not attributes of the __builtin__ module. +_MAGIC_GLOBALS = ['__file__', '__builtins__'] + + + +class Checker(object): + """ + I check the cleanliness and sanity of Python code. + + @ivar _deferredFunctions: Tracking list used by L{deferFunction}. Elements + of the list are two-tuples. The first element is the callable passed + to L{deferFunction}. The second element is a copy of the scope stack + at the time L{deferFunction} was called. + + @ivar _deferredAssignments: Similar to C{_deferredFunctions}, but for + callables which are deferred assignment checks. + """ + + nodeDepth = 0 + traceTree = False + + def __init__(self, tree, filename='(none)'): + self._deferredFunctions = [] + self._deferredAssignments = [] self.dead_scopes = [] self.messages = [] self.filename = filename - self.scope_stack = [ModuleScope()] - self.futures_allowed = True - self.builtins = frozenset(builtins or []) - - self.visit(tree) - for handler, scope in self.deferred: - self.scope_stack = scope - handler() - del self.scope_stack[1:] - self.pop_scope() + self.scopeStack = [ModuleScope()] + self.futuresAllowed = True + self.handleChildren(tree) + self._runDeferred(self._deferredFunctions) + # Set _deferredFunctions to None so that deferFunction will fail + # noisily if called after we've run through the deferred functions. + self._deferredFunctions = None + self._runDeferred(self._deferredAssignments) + # Set _deferredAssignments to None so that deferAssignment will fail + # noisly if called after we've run through the deferred assignments. + self._deferredAssignments = None + del self.scopeStack[1:] + self.popScope() self.check_dead_scopes() - def defer(self, callable): - '''Schedule something to be called after just before completion. + + def deferFunction(self, callable): + ''' + Schedule a function handler to be called just before completion. This is used for handling function bodies, which must be deferred because code later in the file might modify the global scope. When `callable` is called, the scope at the time this is called will be restored, however it will contain any new bindings added to it. ''' - self.deferred.append( (callable, self.scope_stack[:]) ) + self._deferredFunctions.append((callable, self.scopeStack[:])) + + + def deferAssignment(self, callable): + """ + Schedule an assignment handler to be called just after deferred + function handlers. + """ + self._deferredAssignments.append((callable, self.scopeStack[:])) + + + def _runDeferred(self, deferred): + """ + Run the callables in C{deferred} using their associated scope stack. + """ + for handler, scope in deferred: + self.scopeStack = scope + handler() + + + def scope(self): + return self.scopeStack[-1] + scope = property(scope) + + def popScope(self): + self.dead_scopes.append(self.scopeStack.pop()) + def check_dead_scopes(self): - # Check for modules that were imported but unused + """ + Look at scopes which have been fully examined and report names in them + which were imported but unused. + """ for scope in self.dead_scopes: + export = isinstance(scope.get('__all__'), ExportBinding) + if export: + all = scope['__all__'].names() + if os.path.split(self.filename)[1] != '__init__.py': + # Look for possible mistakes in the export list + undefined = set(all) - set(scope) + for name in undefined: + self.report( + messages.UndefinedExport, + scope['__all__'].source, + name) + else: + all = [] + + # Look for imported names that aren't used. for importation in scope.itervalues(): - if isinstance(importation, Importation) and not importation.used: - self.report(messages.UnusedImport, importation.source.lineno, importation.name) + if isinstance(importation, Importation): + if not importation.used and importation.name not in all: + self.report( + messages.UnusedImport, + importation.source, + importation.name) - def push_function_scope(self): - self.scope_stack.append(FunctionScope()) - def push_class_scope(self): - self.scope_stack.append(ClassScope()) + def pushFunctionScope(self): + self.scopeStack.append(FunctionScope()) - def pop_scope(self): - scope = self.scope_stack.pop() - self.dead_scopes.append(scope) + def pushClassScope(self): + self.scopeStack.append(ClassScope()) - @property - def scope(self): - return self.scope_stack[-1] + def report(self, messageClass, *args, **kwargs): + self.messages.append(messageClass(self.filename, *args, **kwargs)) - def report(self, message_class, *args, **kwargs): - self.messages.append(message_class(self.filename, *args, **kwargs)) + def handleChildren(self, tree): + for node in iter_child_nodes(tree): + self.handleNode(node, tree) - def visit_Import(self, node): - for name_node in node.names: - # "import bar as foo" -> name=bar, asname=foo - name = name_node.asname or name_node.name - self.add_binding(node, Importation(name, node)) - - def visit_GeneratorExp(self, node): - for generator in node.generators: - self.visit(generator.iter) - self.assign_vars(generator.target) - - for generator in node.generators: - if hasattr(node, 'elt'): - self.visit(node.elt) - - self.visit_nodes(generator.ifs) - - visit_ListComp = visit_GeneratorExp - - def visit_For(self, node): - ''' - Process bindings for loop variables. - ''' - self.visit_nodes(node.iter) - - for var in self.flatten(node.target): - upval = self.scope.get(var.id) - if isinstance(upval, Importation) and upval.used: - self.report(messages.ImportShadowedByLoopVar, - node.lineno, node.col_offset, var.id, upval.source.lineno) - - self.add_binding(var, Assignment(var.id, var)) - - self.visit_nodes(node.body + node.orelse) - - def visit_FunctionDef(self, node): + def isDocstring(self, node): + """ + Determine if the given node is a docstring, as long as it is at the + correct place in the node tree. + """ + return isinstance(node, _ast.Str) or \ + (isinstance(node, _ast.Expr) and + isinstance(node.value, _ast.Str)) + def handleNode(self, node, parent): + node.parent = parent + if self.traceTree: + print ' ' * self.nodeDepth + node.__class__.__name__ + self.nodeDepth += 1 + if self.futuresAllowed and not \ + (isinstance(node, _ast.ImportFrom) or self.isDocstring(node)): + self.futuresAllowed = False + nodeType = node.__class__.__name__.upper() try: - decorators = node.decorator_list - except AttributeError: - # Use .decorators for Python 2.5 compatibility - decorators = node.decorators + handler = getattr(self, nodeType) + handler(node) + finally: + self.nodeDepth -= 1 + if self.traceTree: + print ' ' * self.nodeDepth + 'end ' + node.__class__.__name__ - self.visit_nodes(decorators) + def ignore(self, node): + pass - # Check for property decorator - func_def = FunctionDefinition(node.name, node) + # "stmt" type nodes + RETURN = DELETE = PRINT = WHILE = IF = WITH = RAISE = TRYEXCEPT = \ + TRYFINALLY = ASSERT = EXEC = EXPR = handleChildren - for decorator in decorators: - if getattr(decorator, 'attr', None) in ('setter', 'deleter'): - func_def._property_decorator = True + CONTINUE = BREAK = PASS = ignore - self.add_binding(node, func_def) + # "expr" type nodes + BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = YIELD = COMPARE = \ + CALL = REPR = ATTRIBUTE = SUBSCRIPT = LIST = TUPLE = handleChildren - self.visit_Lambda(node) + NUM = STR = ELLIPSIS = ignore - def visit_Lambda(self, node): - self.visit_nodes(node.args.defaults) + # "slice" type nodes + SLICE = EXTSLICE = INDEX = handleChildren - def run_function(): - self.push_function_scope() + # expression contexts are node instances too, though being constants + LOAD = STORE = DEL = AUGLOAD = AUGSTORE = PARAM = ignore - # Check for duplicate arguments - argnames = set() - for arg in self.flatten(node.args.args): - if arg.id in argnames: - self.report(messages.DuplicateArgument, arg.lineno, arg.col_offset, arg.id) - argnames.add(arg.id) + # same for operators + AND = OR = ADD = SUB = MULT = DIV = MOD = POW = LSHIFT = RSHIFT = \ + BITOR = BITXOR = BITAND = FLOORDIV = INVERT = NOT = UADD = USUB = \ + EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = ignore - self.assign_vars(node.args.args, report_redef=False) - if node.args.vararg is not None: - self.add_binding(node, Assignment(node.args.vararg, node), False) - if node.args.kwarg is not None: - self.add_binding(node, Assignment(node.args.kwarg, node), False) - self.visit_nodes(node.body) - self.pop_scope() + # additional node types + COMPREHENSION = EXCEPTHANDLER = KEYWORD = handleChildren - self.defer(run_function) + def addBinding(self, loc, value, reportRedef=True): + '''Called when a binding is altered. - def visit_Name(self, node): + - `loc` is the location (an object with lineno and optionally + col_offset attributes) of the statement responsible for the change + - `value` is the optional new value, a Binding instance, associated + with the binding; if None, the binding is deleted if it exists. + - if `reportRedef` is True (default), rebinding while unused will be + reported. ''' - Locate names in locals / function / globals scopes. - ''' - scope, name = self.scope, node.id + if (isinstance(self.scope.get(value.name), FunctionDefinition) + and isinstance(value, FunctionDefinition)): + self.report(messages.RedefinedFunction, + loc, value.name, self.scope[value.name].source) - # try local scope - import_starred = scope.import_starred - try: - scope[name].used = (scope, node.lineno, node.col_offset) - except KeyError: - pass - else: - return + if not isinstance(self.scope, ClassScope): + for scope in self.scopeStack[::-1]: + existing = scope.get(value.name) + if (isinstance(existing, Importation) + and not existing.used + and (not isinstance(value, Importation) or value.fullName == existing.fullName) + and reportRedef): - # try enclosing function scopes - for func_scope in self.scope_stack[-2:0:-1]: - import_starred = import_starred or func_scope.import_starred - if not isinstance(func_scope, FunctionScope): - continue + self.report(messages.RedefinedWhileUnused, + loc, value.name, scope[value.name].source) + + if isinstance(value, UnBinding): try: - func_scope[name].used = (scope, node.lineno, node.col_offset) + del self.scope[value.name] + except KeyError: + self.report(messages.UndefinedName, loc, value.name) + else: + self.scope[value.name] = value + + def GLOBAL(self, node): + """ + Keep track of globals declarations. + """ + if isinstance(self.scope, FunctionScope): + self.scope.globals.update(dict.fromkeys(node.names)) + + def LISTCOMP(self, node): + # handle generators before element + for gen in node.generators: + self.handleNode(gen, node) + self.handleNode(node.elt, node) + + GENERATOREXP = SETCOMP = LISTCOMP + + # dictionary comprehensions; introduced in Python 2.7 + def DICTCOMP(self, node): + for gen in node.generators: + self.handleNode(gen, node) + self.handleNode(node.key, node) + self.handleNode(node.value, node) + + def FOR(self, node): + """ + Process bindings for loop variables. + """ + vars = [] + def collectLoopVars(n): + if isinstance(n, _ast.Name): + vars.append(n.id) + elif isinstance(n, _ast.expr_context): + return + else: + for c in iter_child_nodes(n): + collectLoopVars(c) + + collectLoopVars(node.target) + for varn in vars: + if (isinstance(self.scope.get(varn), Importation) + # unused ones will get an unused import warning + and self.scope[varn].used): + self.report(messages.ImportShadowedByLoopVar, + node, varn, self.scope[varn].source) + + self.handleChildren(node) + + def NAME(self, node): + """ + Handle occurrence of Name (which can be a load/store/delete access.) + """ + # Locate the name in locals / function / globals scopes. + if isinstance(node.ctx, (_ast.Load, _ast.AugLoad)): + # try local scope + importStarred = self.scope.importStarred + try: + self.scope[node.id].used = (self.scope, node) except KeyError: pass else: return - # try global scope - import_starred = import_starred or self.scope_stack[0].import_starred - try: - self.scope_stack[0][node.id].used = (scope, node.lineno, node.col_offset) - except KeyError: - if not import_starred and not self.is_builtin(name): - self.report(messages.UndefinedName, node.lineno, node.col_offset, name) + # try enclosing function scopes - def assign_vars(self, targets, report_redef=True): - scope = self.scope + for scope in self.scopeStack[-2:0:-1]: + importStarred = importStarred or scope.importStarred + if not isinstance(scope, FunctionScope): + continue + try: + scope[node.id].used = (self.scope, node) + except KeyError: + pass + else: + return - for target in self.flatten(targets): - name = target.id + # try global scope + + importStarred = importStarred or self.scopeStack[0].importStarred + try: + self.scopeStack[0][node.id].used = (self.scope, node) + except KeyError: + if ((not hasattr(__builtin__, node.id)) + and node.id not in _MAGIC_GLOBALS + and not importStarred): + if (os.path.basename(self.filename) == '__init__.py' and + node.id == '__path__'): + # the special name __path__ is valid only in packages + pass + else: + self.report(messages.UndefinedName, node, node.id) + elif isinstance(node.ctx, (_ast.Store, _ast.AugStore)): # if the name hasn't already been defined in the current scope - if isinstance(scope, FunctionScope) and name not in scope: + if isinstance(self.scope, FunctionScope) and node.id not in self.scope: # for each function or module scope above us - for upscope in self.scope_stack[:-1]: - if not isinstance(upscope, (FunctionScope, ModuleScope)): + for scope in self.scopeStack[:-1]: + if not isinstance(scope, (FunctionScope, ModuleScope)): continue - - upval = upscope.get(name) # if the name was defined in that scope, and the name has # been accessed already in the current scope, and hasn't # been declared global - if upval is not None: - if upval.used and upval.used[0] is scope and name not in scope.globals: - # then it's probably a mistake - self.report(messages.UndefinedLocal, - upval.used[1], upval.used[2], name, upval.source.lineno, upval.source.col_offset) + if (node.id in scope + and scope[node.id].used + and scope[node.id].used[0] is self.scope + and node.id not in self.scope.globals): + # then it's probably a mistake + self.report(messages.UndefinedLocal, + scope[node.id].used[1], + node.id, + scope[node.id].source) + break - self.add_binding(target, Assignment(name, target), report_redef) - - def visit_Assign(self, node): - for target in node.targets: - self.visit_nodes(node.value) - self.assign_vars(node.targets) - - def visit_Delete(self, node): - for target in self.flatten(node.targets): - if isinstance(self.scope, FunctionScope) and target.id in self.scope.globals: - del self.scope.globals[target.id] + if isinstance(node.parent, + (_ast.For, _ast.comprehension, _ast.Tuple, _ast.List)): + binding = Binding(node.id, node) + elif (node.id == '__all__' and + isinstance(self.scope, ModuleScope)): + binding = ExportBinding(node.id, node.parent.value) else: - self.add_binding(target, UnBinding(target.id, target)) - - def visit_With(self, node): - self.visit(node.context_expr) - - # handle new bindings made by optional "as" part - if node.optional_vars is not None: - self.assign_vars(node.optional_vars) - - self.visit_nodes(node.body) - - def visit_ImportFrom(self, node): - if node.module == '__future__': - if not self.futures_allowed: - self.report(messages.LateFutureImport, node.lineno, node.col_offset, [alias.name for alias in node.names]) + binding = Assignment(node.id, node) + if node.id in self.scope: + binding.used = self.scope[node.id].used + self.addBinding(node, binding) + elif isinstance(node.ctx, _ast.Del): + if isinstance(self.scope, FunctionScope) and \ + node.id in self.scope.globals: + del self.scope.globals[node.id] + else: + self.addBinding(node, UnBinding(node.id, node)) else: - self.futures_allowed = False + # must be a Param context -- this only happens for names in function + # arguments, but these aren't dispatched through here + raise RuntimeError( + "Got impossible expression context: %r" % (node.ctx,)) + + + def FUNCTIONDEF(self, node): + # the decorators attribute is called decorator_list as of Python 2.6 + if hasattr(node, 'decorators'): + for deco in node.decorators: + self.handleNode(deco, node) + else: + for deco in node.decorator_list: + self.handleNode(deco, node) + self.addBinding(node, FunctionDefinition(node.name, node)) + self.LAMBDA(node) + + def LAMBDA(self, node): + for default in node.args.defaults: + self.handleNode(default, node) + + def runFunction(): + args = [] + + def addArgs(arglist): + for arg in arglist: + if isinstance(arg, _ast.Tuple): + addArgs(arg.elts) + else: + if arg.id in args: + self.report(messages.DuplicateArgument, + node, arg.id) + args.append(arg.id) + + self.pushFunctionScope() + addArgs(node.args.args) + # vararg/kwarg identifiers are not Name nodes + if node.args.vararg: + args.append(node.args.vararg) + if node.args.kwarg: + args.append(node.args.kwarg) + for name in args: + self.addBinding(node, Argument(name, node), reportRedef=False) + if isinstance(node.body, list): + # case for FunctionDefs + for stmt in node.body: + self.handleNode(stmt, node) + else: + # case for Lambdas + self.handleNode(node.body, node) + def checkUnusedAssignments(): + """ + Check to see if any assignments have not been used. + """ + for name, binding in self.scope.iteritems(): + if (not binding.used and not name in self.scope.globals + and isinstance(binding, Assignment)): + self.report(messages.UnusedVariable, + binding.source, name) + self.deferAssignment(checkUnusedAssignments) + self.popScope() + + self.deferFunction(runFunction) + + + def CLASSDEF(self, node): + """ + Check names used in a class definition, including its decorators, base + classes, and the body of its definition. Additionally, add its name to + the current scope. + """ + # decorator_list is present as of Python 2.6 + for deco in getattr(node, 'decorator_list', []): + self.handleNode(deco, node) + for baseNode in node.bases: + self.handleNode(baseNode, node) + self.pushClassScope() + for stmt in node.body: + self.handleNode(stmt, node) + self.popScope() + self.addBinding(node, Binding(node.name, node)) + + def ASSIGN(self, node): + self.handleNode(node.value, node) + for target in node.targets: + self.handleNode(target, node) + + def AUGASSIGN(self, node): + # AugAssign is awkward: must set the context explicitly and visit twice, + # once with AugLoad context, once with AugStore context + node.target.ctx = _ast.AugLoad() + self.handleNode(node.target, node) + self.handleNode(node.value, node) + node.target.ctx = _ast.AugStore() + self.handleNode(node.target, node) + + def IMPORT(self, node): + for alias in node.names: + name = alias.asname or alias.name + importation = Importation(name, node) + self.addBinding(node, importation) + + def IMPORTFROM(self, node): + if node.module == '__future__': + if not self.futuresAllowed: + self.report(messages.LateFutureImport, node, + [n.name for n in node.names]) + else: + self.futuresAllowed = False for alias in node.names: if alias.name == '*': - self.scope.import_starred = True - self.report(messages.ImportStarUsed, node.lineno, node.col_offset, node.module) + self.scope.importStarred = True + self.report(messages.ImportStarUsed, node, node.module) continue name = alias.asname or alias.name importation = Importation(name, node) if node.module == '__future__': - importation.used = (self.scope, node.lineno, node.col_offset) - self.add_binding(node, importation) - - def visit_Global(self, node): - ''' - Keep track of global declarations. - ''' - scope = self.scope - if isinstance(scope, FunctionScope): - scope.globals.update(dict.fromkeys(node.names)) - - def visit_ClassDef(self, node): - try: - decorators = node.decorator_list - except AttributeError: - # Use .decorators for Python 2.5 compatibility - decorators = getattr(node, 'decorators', []) - - self.visit_nodes(decorators) - - self.add_binding(node, Assignment(node.name, node)) - self.visit_nodes(node.bases) - - self.push_class_scope() - self.visit_nodes(node.body) - self.pop_scope() - - def visit_excepthandler(self, node): - if node.type is not None: - self.visit(node.type) - if node.name is not None: - self.assign_vars(node.name) - self.visit_nodes(node.body) - - visit_ExceptHandler = visit_excepthandler # in 2.6, this was CamelCased - - def flatten(self, nodes): - if isinstance(nodes, ast.Attribute): - self.visit(nodes) - return [] - elif isinstance(nodes, ast.Subscript): - self.visit(nodes.value) - self.visit(nodes.slice) - return [] - elif isinstance(nodes, ast.Name): - return [nodes] - elif isinstance(nodes, (ast.Tuple, ast.List)): - return self.flatten(nodes.elts) - - flattened_nodes = [] - for node in nodes: - if hasattr(node, 'elts'): - flattened_nodes += self.flatten(node.elts) - elif node is not None: - flattened_nodes += self.flatten(node) - - return flattened_nodes - - def add_binding(self, node, value, report_redef=True): - line, col, scope, name = node.lineno, node.col_offset, self.scope, value.name - - # Check for a redefined function - func = scope.get(name) - if (isinstance(func, FunctionDefinition) and isinstance(value, FunctionDefinition)): - # Property-decorated functions (@x.setter) should have duplicate names - if not value._property_decorator: - self.report(messages.RedefinedFunction, line, name, func.source.lineno) - - # Check for redefining an unused import - if report_redef and not isinstance(scope, ClassScope): - for up_scope in self.scope_stack[::-1]: - upval = up_scope.get(name) - if isinstance(upval, Importation) and not upval.used: - self.report(messages.RedefinedWhileUnused, line, col, name, upval.source.lineno) - - # Check for "del undefined_name" - if isinstance(value, UnBinding): - try: - del scope[name] - except KeyError: - self.report(messages.UndefinedName, line, col, name) - else: - scope[name] = value - - def visit(self, node): - if not isinstance(node, allowed_before_future): - self.futures_allowed = False - - return super(Checker, self).visit(node) - - def visit_nodes(self, nodes): - try: - nodes = list(getattr(nodes, 'elts', nodes)) - except TypeError: - nodes = [nodes] - - for node in nodes: - self.visit(node) - - def is_builtin(self, name): - if hasattr(__builtin__, name): - return True - if name in defined_names: - return True - if name in self.builtins: - return True - - return False - + importation.used = (self.scope, node) + self.addBinding(node, importation) diff --git a/ftplugin/python/pyflakes/pyflakes/messages.py b/ftplugin/python/pyflakes/pyflakes/messages.py index 15a920e..73bf4cc 100644 --- a/ftplugin/python/pyflakes/pyflakes/messages.py +++ b/ftplugin/python/pyflakes/pyflakes/messages.py @@ -3,75 +3,94 @@ class Message(object): message = '' message_args = () - def __init__(self, filename, lineno, col = None): + def __init__(self, filename, loc, use_column=True): self.filename = filename - self.lineno = lineno - self.col = col + self.lineno = loc.lineno + self.col = getattr(loc, 'col_offset', None) if use_column else None + def __str__(self): - if self.col is not None: - return '%s:%s(%d): %s' % (self.filename, self.lineno, self.col, self.message % self.message_args) - else: - return '%s:%s: %s' % (self.filename, self.lineno, self.message % self.message_args) + return '%s:%s: %s' % (self.filename, self.lineno, self.message % self.message_args) class UnusedImport(Message): message = '%r imported but unused' - def __init__(self, filename, lineno, name): - Message.__init__(self, filename, lineno) + def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc, use_column=False) self.message_args = (name,) class RedefinedWhileUnused(Message): message = 'redefinition of unused %r from line %r' - def __init__(self, filename, lineno, col, name, orig_lineno): - Message.__init__(self, filename, lineno) - self.message_args = (name, orig_lineno) + def __init__(self, filename, loc, name, orig_loc): + Message.__init__(self, filename, loc) + self.message_args = (name, orig_loc.lineno) class ImportShadowedByLoopVar(Message): message = 'import %r from line %r shadowed by loop variable' - def __init__(self, filename, lineno, col, name, orig_lineno): - Message.__init__(self, filename, lineno, col) - self.message_args = (name, orig_lineno) + def __init__(self, filename, loc, name, orig_loc): + Message.__init__(self, filename, loc) + self.message_args = (name, orig_loc.lineno) class ImportStarUsed(Message): message = "'from %s import *' used; unable to detect undefined names" - def __init__(self, filename, lineno, col, modname): - Message.__init__(self, filename, lineno, col) + def __init__(self, filename, loc, modname): + Message.__init__(self, filename, loc) self.message_args = (modname,) class UndefinedName(Message): message = 'undefined name %r' - def __init__(self, filename, lineno, col, name): - Message.__init__(self, filename, lineno, col) + def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc) self.message_args = (name,) + +class UndefinedExport(Message): + message = 'undefined name %r in __all__' + def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc) + self.message_args = (name,) + + + class UndefinedLocal(Message): message = "local variable %r (defined in enclosing scope on line %r) referenced before assignment" - def __init__(self, filename, lineno, col, name, orig_lineno, orig_col): - Message.__init__(self, filename, lineno) - self.message_args = (name, orig_lineno) + def __init__(self, filename, loc, name, orig_loc): + Message.__init__(self, filename, loc) + self.message_args = (name, orig_loc.lineno) class DuplicateArgument(Message): message = 'duplicate argument %r in function definition' - def __init__(self, filename, lineno, col, name): - Message.__init__(self, filename, lineno, col) + def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc) self.message_args = (name,) class RedefinedFunction(Message): message = 'redefinition of function %r from line %r' - def __init__(self, filename, lineno, name, orig_lineno): - Message.__init__(self, filename, lineno) - self.message_args = (name, orig_lineno) + def __init__(self, filename, loc, name, orig_loc): + Message.__init__(self, filename, loc) + self.message_args = (name, orig_loc.lineno) class LateFutureImport(Message): message = 'future import(s) %r after other statements' - def __init__(self, filename, lineno, col, names): - Message.__init__(self, filename, lineno) + def __init__(self, filename, loc, names): + Message.__init__(self, filename, loc) + self.message_args = (names,) + + +class UnusedVariable(Message): + """ + Indicates that a variable has been explicity assigned to but not actually + used. + """ + + message = 'local variable %r is assigned to but never used' + def __init__(self, filename, loc, names): + Message.__init__(self, filename, loc) self.message_args = (names,) diff --git a/ftplugin/python/pyflakes/pyflakes/scripts/pyflakes.py b/ftplugin/python/pyflakes/pyflakes/scripts/pyflakes.py index 06937a7..6b1dae2 100644 --- a/ftplugin/python/pyflakes/pyflakes/scripts/pyflakes.py +++ b/ftplugin/python/pyflakes/pyflakes/scripts/pyflakes.py @@ -3,29 +3,55 @@ Implementation of the command-line I{pyflakes} tool. """ -import _ast import sys import os +import _ast checker = __import__('pyflakes.checker').checker def check(codeString, filename): + """ + Check the Python source given by C{codeString} for flakes. + + @param codeString: The Python source to check. + @type codeString: C{str} + + @param filename: The name of the file the source came from, used to report + errors. + @type filename: C{str} + + @return: The number of warnings emitted. + @rtype: C{int} + """ + # First, compile into an AST and handle syntax errors. try: - tree = compile(codeString, filename, 'exec', _ast.PyCF_ONLY_AST) - except (SyntaxError, IndentationError): - value = sys.exc_info()[1] - try: - (lineno, offset, line) = value[1][1:] - except IndexError: - print >> sys.stderr, 'could not compile %r' % (filename,) - return 1 - if line.endswith("\n"): - line = line[:-1] - print >> sys.stderr, '%s:%d: could not compile' % (filename, lineno) - print >> sys.stderr, line - print >> sys.stderr, " " * (offset-2), "^" + tree = compile(codeString, filename, "exec", _ast.PyCF_ONLY_AST) + except SyntaxError, value: + msg = value.args[0] + + (lineno, offset, text) = value.lineno, value.offset, value.text + + # If there's an encoding problem with the file, the text is None. + if text is None: + # Avoid using msg, since for the only known case, it contains a + # bogus message that claims the encoding the file declared was + # unknown. + print >> sys.stderr, "%s: problem decoding source" % (filename, ) + else: + line = text.splitlines()[-1] + + if offset is not None: + offset = offset - (len(text) - len(line)) + + print >> sys.stderr, '%s:%d: %s' % (filename, lineno, msg) + print >> sys.stderr, line + + if offset is not None: + print >> sys.stderr, " " * offset, "^" + return 1 else: + # Okay, it's syntactically valid. Now check it. w = checker.Checker(tree, filename) w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno)) for warning in w.messages: @@ -39,12 +65,13 @@ def checkPath(filename): @return: the number of warnings printed """ - if os.path.exists(filename): + try: return check(file(filename, 'U').read() + '\n', filename) - else: - print >> sys.stderr, '%s: no such file' % (filename,) + except IOError, msg: + print >> sys.stderr, "%s: %s" % (filename, msg.args[1]) return 1 + def main(): warnings = 0 args = sys.argv[1:] diff --git a/ftplugin/python/pyflakes/pyflakes/test/harness.py b/ftplugin/python/pyflakes/pyflakes/test/harness.py index 765cda4..7cd2277 100644 --- a/ftplugin/python/pyflakes/pyflakes/test/harness.py +++ b/ftplugin/python/pyflakes/pyflakes/test/harness.py @@ -1,15 +1,18 @@ import textwrap +import _ast from twisted.trial import unittest -from pyflakes import checker, ast +from pyflakes import checker class Test(unittest.TestCase): - def flakes(self, input, *expectedOutputs): - w = checker.Checker(ast.parse(textwrap.dedent(input))) + def flakes(self, input, *expectedOutputs, **kw): + ast = compile(textwrap.dedent(input), "", "exec", + _ast.PyCF_ONLY_AST) + w = checker.Checker(ast, **kw) outputs = [type(o) for o in w.messages] expectedOutputs = list(expectedOutputs) outputs.sort() diff --git a/ftplugin/python/pyflakes/pyflakes/test/test_imports.py b/ftplugin/python/pyflakes/pyflakes/test/test_imports.py index 4f87f3e..08e4580 100644 --- a/ftplugin/python/pyflakes/pyflakes/test/test_imports.py +++ b/ftplugin/python/pyflakes/pyflakes/test/test_imports.py @@ -51,6 +51,19 @@ class Test(harness.Test): pass ''', m.RedefinedWhileUnused) + + def test_redefinedBySubclass(self): + """ + If an imported name is redefined by a class statement which also uses + that name in the bases list, no warning is emitted. + """ + self.flakes(''' + from fu import bar + class bar(bar): + pass + ''') + + def test_redefinedInClass(self): """ Test that shadowing a global with a class attribute does not produce a @@ -377,9 +390,55 @@ class Test(harness.Test): def test_importStar(self): self.flakes('from fu import *', m.ImportStarUsed) + def test_packageImport(self): - self.flakes('import fu.bar; fu.bar') - test_packageImport.todo = "this has been hacked to treat 'import fu.bar' as just 'import fu'" + """ + If a dotted name is imported and used, no warning is reported. + """ + self.flakes(''' + import fu.bar + fu.bar + ''') + + + def test_unusedPackageImport(self): + """ + If a dotted name is imported and not used, an unused import warning is + reported. + """ + self.flakes('import fu.bar', m.UnusedImport) + + + def test_duplicateSubmoduleImport(self): + """ + If a submodule of a package is imported twice, an unused import warning + and a redefined while unused warning are reported. + """ + self.flakes(''' + import fu.bar, fu.bar + fu.bar + ''', m.RedefinedWhileUnused) + self.flakes(''' + import fu.bar + import fu.bar + fu.bar + ''', m.RedefinedWhileUnused) + + + def test_differentSubmoduleImport(self): + """ + If two different submodules of a package are imported, no duplicate + import warning is reported for the package. + """ + self.flakes(''' + import fu.bar, fu.baz + fu.bar, fu.baz + ''') + self.flakes(''' + import fu.bar + import fu.baz + fu.bar, fu.baz + ''') def test_assignRHSFirst(self): self.flakes('import fu; fu = fu') @@ -401,6 +460,7 @@ class Test(harness.Test): import fu def a(): fu = 3 + return fu fu ''') @@ -431,11 +491,6 @@ class Test(harness.Test): ''') test_importingForImportError.todo = '' - def test_explicitlyPublic(self): - '''imports mentioned in __all__ are not unused''' - self.flakes('import fu; __all__ = ["fu"]') - test_explicitlyPublic.todo = "this would require importing the module or doing smarter parsing" - def test_importedInClass(self): '''Imports in class scope can be used through self''' self.flakes(''' @@ -449,6 +504,10 @@ class Test(harness.Test): def test_futureImport(self): '''__future__ is special''' self.flakes('from __future__ import division') + self.flakes(''' + "docstring is allowed before future import" + from __future__ import division + ''') def test_futureImportFirst(self): """ @@ -458,15 +517,82 @@ class Test(harness.Test): x = 5 from __future__ import division ''', m.LateFutureImport) + self.flakes(''' + from foo import bar + from __future__ import division + bar + ''', m.LateFutureImport) -class Python24Tests(harness.Test): +class TestSpecialAll(harness.Test): """ - Tests for checking of syntax which is valid in Python 2.4 and newer. + Tests for suppression of unused import warnings by C{__all__}. """ - if version_info < (2, 4): - skip = "Python 2.4 required for generator expression and decorator tests." + def test_ignoredInFunction(self): + """ + An C{__all__} definition does not suppress unused import warnings in a + function scope. + """ + self.flakes(''' + def foo(): + import bar + __all__ = ["bar"] + ''', m.UnusedImport, m.UnusedVariable) + + + def test_ignoredInClass(self): + """ + An C{__all__} definition does not suppress unused import warnings in a + class scope. + """ + self.flakes(''' + class foo: + import bar + __all__ = ["bar"] + ''', m.UnusedImport) + + + def test_warningSuppressed(self): + """ + If a name is imported and unused but is named in C{__all__}, no warning + is reported. + """ + self.flakes(''' + import foo + __all__ = ["foo"] + ''') + + + def test_unrecognizable(self): + """ + If C{__all__} is defined in a way that can't be recognized statically, + it is ignored. + """ + self.flakes(''' + import foo + __all__ = ["f" + "oo"] + ''', m.UnusedImport) + self.flakes(''' + import foo + __all__ = [] + ["foo"] + ''', m.UnusedImport) + + + def test_unboundExported(self): + """ + If C{__all__} includes a name which is not bound, a warning is emitted. + """ + self.flakes(''' + __all__ = ["foo"] + ''', m.UndefinedExport) + + # Skip this in __init__.py though, since the rules there are a little + # different. + for filename in ["foo/__init__.py", "__init__.py"]: + self.flakes(''' + __all__ = ["foo"] + ''', filename=filename) def test_usedInGenExp(self): @@ -510,3 +636,38 @@ class Python24Tests(harness.Test): def f(): return "hello" ''', m.UndefinedName) + + +class Python26Tests(harness.Test): + """ + Tests for checking of syntax which is valid in PYthon 2.6 and newer. + """ + if version_info < (2, 6): + skip = "Python 2.6 required for class decorator tests." + + + def test_usedAsClassDecorator(self): + """ + Using an imported name as a class decorator results in no warnings, + but using an undefined name as a class decorator results in an + undefined name warning. + """ + self.flakes(''' + from interior import decorate + @decorate + class foo: + pass + ''') + + self.flakes(''' + from interior import decorate + @decorate("foo") + class bar: + pass + ''') + + self.flakes(''' + @decorate + class foo: + pass + ''', m.UndefinedName) diff --git a/ftplugin/python/pyflakes/pyflakes/test/test_other.py b/ftplugin/python/pyflakes/pyflakes/test/test_other.py index 26a306c..2b7723c 100644 --- a/ftplugin/python/pyflakes/pyflakes/test/test_other.py +++ b/ftplugin/python/pyflakes/pyflakes/test/test_other.py @@ -1,4 +1,4 @@ -# (c) 2005-2008 Divmod, Inc. +# (c) 2005-2010 Divmod, Inc. # See LICENSE file for details """ @@ -74,6 +74,259 @@ class Test(harness.Test): self.flakes('+1') + def test_undefinedBaseClass(self): + """ + If a name in the base list of a class definition is undefined, a + warning is emitted. + """ + self.flakes(''' + class foo(foo): + pass + ''', m.UndefinedName) + + + def test_classNameUndefinedInClassBody(self): + """ + If a class name is used in the body of that class's definition and + the name is not already defined, a warning is emitted. + """ + self.flakes(''' + class foo: + foo + ''', m.UndefinedName) + + + def test_classNameDefinedPreviously(self): + """ + If a class name is used in the body of that class's definition and + the name was previously defined in some other way, no warning is + emitted. + """ + self.flakes(''' + foo = None + class foo: + foo + ''') + + + def test_comparison(self): + """ + If a defined name is used on either side of any of the six comparison + operators, no warning is emitted. + """ + self.flakes(''' + x = 10 + y = 20 + x < y + x <= y + x == y + x != y + x >= y + x > y + ''') + + + def test_identity(self): + """ + If a deefined name is used on either side of an identity test, no + warning is emitted. + """ + self.flakes(''' + x = 10 + y = 20 + x is y + x is not y + ''') + + + def test_containment(self): + """ + If a defined name is used on either side of a containment test, no + warning is emitted. + """ + self.flakes(''' + x = 10 + y = 20 + x in y + x not in y + ''') + + + def test_loopControl(self): + """ + break and continue statements are supported. + """ + self.flakes(''' + for x in [1, 2]: + break + ''') + self.flakes(''' + for x in [1, 2]: + continue + ''') + + + def test_ellipsis(self): + """ + Ellipsis in a slice is supported. + """ + self.flakes(''' + [1, 2][...] + ''') + + + def test_extendedSlice(self): + """ + Extended slices are supported. + """ + self.flakes(''' + x = 3 + [1, 2][x,:] + ''') + + + +class TestUnusedAssignment(harness.Test): + """ + Tests for warning about unused assignments. + """ + + def test_unusedVariable(self): + """ + Warn when a variable in a function is assigned a value that's never + used. + """ + self.flakes(''' + def a(): + b = 1 + ''', m.UnusedVariable) + + + def test_assignToGlobal(self): + """ + Assigning to a global and then not using that global is perfectly + acceptable. Do not mistake it for an unused local variable. + """ + self.flakes(''' + b = 0 + def a(): + global b + b = 1 + ''') + + + def test_assignToMember(self): + """ + Assigning to a member of another object and then not using that member + variable is perfectly acceptable. Do not mistake it for an unused + local variable. + """ + # XXX: Adding this test didn't generate a failure. Maybe not + # necessary? + self.flakes(''' + class b: + pass + def a(): + b.foo = 1 + ''') + + + def test_assignInForLoop(self): + """ + Don't warn when a variable in a for loop is assigned to but not used. + """ + self.flakes(''' + def f(): + for i in range(10): + pass + ''') + + + def test_assignInListComprehension(self): + """ + Don't warn when a variable in a list comprehension is assigned to but + not used. + """ + self.flakes(''' + def f(): + [None for i in range(10)] + ''') + + + def test_generatorExpression(self): + """ + Don't warn when a variable in a generator expression is assigned to but not used. + """ + self.flakes(''' + def f(): + (None for i in range(10)) + ''') + + + def test_assignmentInsideLoop(self): + """ + Don't warn when a variable assignment occurs lexically after its use. + """ + self.flakes(''' + def f(): + x = None + for i in range(10): + if i > 2: + return x + x = i * 2 + ''') + + + def test_tupleUnpacking(self): + """ + Don't warn when a variable included in tuple unpacking is unused. It's + very common for variables in a tuple unpacking assignment to be unused + in good Python code, so warning will only create false positives. + """ + self.flakes(''' + def f(): + (x, y) = 1, 2 + ''') + + + def test_listUnpacking(self): + """ + Don't warn when a variable included in list unpacking is unused. + """ + self.flakes(''' + def f(): + [x, y] = [1, 2] + ''') + + + def test_closedOver(self): + """ + Don't warn when the assignment is used in an inner function. + """ + self.flakes(''' + def barMaker(): + foo = 5 + def bar(): + return foo + return bar + ''') + + + def test_doubleClosedOver(self): + """ + Don't warn when the assignment is used in an inner function, even if + that inner function itself is in an inner function. + """ + self.flakes(''' + def barMaker(): + foo = 5 + def bar(): + def baz(): + return foo + return bar + ''') + + class Python25Test(harness.Test): """ @@ -117,6 +370,45 @@ class Python25Test(harness.Test): ''') + def test_withStatementAttributeName(self): + """ + No warnings are emitted for using an attribute as the target of a + C{with} statement. + """ + self.flakes(''' + from __future__ import with_statement + import foo + with open('foo') as foo.bar: + pass + ''') + + + def test_withStatementSubscript(self): + """ + No warnings are emitted for using a subscript as the target of a + C{with} statement. + """ + self.flakes(''' + from __future__ import with_statement + import foo + with open('foo') as foo[0]: + pass + ''') + + + def test_withStatementSubscriptUndefined(self): + """ + An undefined name warning is emitted if the subscript used as the + target of a C{with} statement is not defined. + """ + self.flakes(''' + from __future__ import with_statement + import foo + with open('foo') as foo[bar]: + pass + ''', m.UndefinedName) + + def test_withStatementTupleNames(self): """ No warnings are emitted for using any of the tuple of names defined by @@ -130,6 +422,36 @@ class Python25Test(harness.Test): ''') + def test_withStatementListNames(self): + """ + No warnings are emitted for using any of the list of names defined by a + C{with} statement within the suite or afterwards. + """ + self.flakes(''' + from __future__ import with_statement + with open('foo') as [bar, baz]: + bar, baz + bar, baz + ''') + + + def test_withStatementComplicatedTarget(self): + """ + If the target of a C{with} statement uses any or all of the valid forms + for that part of the grammar (See + U{http://docs.python.org/reference/compound_stmts.html#the-with-statement}), + the names involved are checked both for definedness and any bindings + created are respected in the suite of the statement and afterwards. + """ + self.flakes(''' + from __future__ import with_statement + c = d = e = g = h = i = None + with open('foo') as [(a, b), c[d], e.f, g[h:i]]: + a, b, c, d, e, g, h, i + a, b, c, d, e, g, h, i + ''') + + def test_withStatementSingleNameUndefined(self): """ An undefined name warning is emitted if the name first defined by a @@ -226,9 +548,28 @@ class Python25Test(harness.Test): pass ''', m.UndefinedName) - def test_listNestedListComprehension(self): + + +class Python27Test(harness.Test): + """ + Tests for checking of syntax only available in Python 2.7 and newer. + """ + if version_info < (2, 7): + skip = "Python 2.7 required for dict/set comprehension tests" + + def test_dictComprehension(self): + """ + Dict comprehensions are properly handled. + """ self.flakes(''' - root = [['213', '123'], ['4354']] - foo = [int(c) for group in root for c in group] + a = {1: x for x in range(10)} ''') + def test_setComprehensionAndLiteral(self): + """ + Set comprehensions are properly handled. + """ + self.flakes(''' + a = {1, 2, 3} + b = {x for x in range(10)} + ''') diff --git a/ftplugin/python/pyflakes/pyflakes/test/test_script.py b/ftplugin/python/pyflakes/pyflakes/test/test_script.py index b6fb685..233e59e 100644 --- a/ftplugin/python/pyflakes/pyflakes/test/test_script.py +++ b/ftplugin/python/pyflakes/pyflakes/test/test_script.py @@ -44,5 +44,142 @@ class CheckTests(TestCase): """ err = StringIO() count = withStderrTo(err, lambda: checkPath('extremo')) - self.assertEquals(err.getvalue(), 'extremo: no such file\n') + self.assertEquals(err.getvalue(), 'extremo: No such file or directory\n') self.assertEquals(count, 1) + + + def test_multilineSyntaxError(self): + """ + Source which includes a syntax error which results in the raised + L{SyntaxError.text} containing multiple lines of source are reported + with only the last line of that source. + """ + source = """\ +def foo(): + ''' + +def bar(): + pass + +def baz(): + '''quux''' +""" + + # Sanity check - SyntaxError.text should be multiple lines, if it + # isn't, something this test was unprepared for has happened. + def evaluate(source): + exec source + exc = self.assertRaises(SyntaxError, evaluate, source) + self.assertTrue(exc.text.count('\n') > 1) + + sourcePath = FilePath(self.mktemp()) + sourcePath.setContent(source) + err = StringIO() + count = withStderrTo(err, lambda: checkPath(sourcePath.path)) + self.assertEqual(count, 1) + + self.assertEqual( + err.getvalue(), + """\ +%s:8: invalid syntax + '''quux''' + ^ +""" % (sourcePath.path,)) + + + def test_eofSyntaxError(self): + """ + The error reported for source files which end prematurely causing a + syntax error reflects the cause for the syntax error. + """ + source = "def foo(" + sourcePath = FilePath(self.mktemp()) + sourcePath.setContent(source) + err = StringIO() + count = withStderrTo(err, lambda: checkPath(sourcePath.path)) + self.assertEqual(count, 1) + self.assertEqual( + err.getvalue(), + """\ +%s:1: unexpected EOF while parsing +def foo( + ^ +""" % (sourcePath.path,)) + + + def test_nonDefaultFollowsDefaultSyntaxError(self): + """ + Source which has a non-default argument following a default argument + should include the line number of the syntax error. However these + exceptions do not include an offset. + """ + source = """\ +def foo(bar=baz, bax): + pass +""" + sourcePath = FilePath(self.mktemp()) + sourcePath.setContent(source) + err = StringIO() + count = withStderrTo(err, lambda: checkPath(sourcePath.path)) + self.assertEqual(count, 1) + self.assertEqual( + err.getvalue(), + """\ +%s:1: non-default argument follows default argument +def foo(bar=baz, bax): +""" % (sourcePath.path,)) + + + def test_nonKeywordAfterKeywordSyntaxError(self): + """ + Source which has a non-keyword argument after a keyword argument should + include the line number of the syntax error. However these exceptions + do not include an offset. + """ + source = """\ +foo(bar=baz, bax) +""" + sourcePath = FilePath(self.mktemp()) + sourcePath.setContent(source) + err = StringIO() + count = withStderrTo(err, lambda: checkPath(sourcePath.path)) + self.assertEqual(count, 1) + self.assertEqual( + err.getvalue(), + """\ +%s:1: non-keyword arg after keyword arg +foo(bar=baz, bax) +""" % (sourcePath.path,)) + + + def test_permissionDenied(self): + """ + If the a source file is not readable, this is reported on standard + error. + """ + sourcePath = FilePath(self.mktemp()) + sourcePath.setContent('') + sourcePath.chmod(0) + err = StringIO() + count = withStderrTo(err, lambda: checkPath(sourcePath.path)) + self.assertEquals(count, 1) + self.assertEquals( + err.getvalue(), "%s: Permission denied\n" % (sourcePath.path,)) + + + def test_misencodedFile(self): + """ + If a source file contains bytes which cannot be decoded, this is + reported on stderr. + """ + source = u"""\ +# coding: ascii +x = "\N{SNOWMAN}" +""".encode('utf-8') + sourcePath = FilePath(self.mktemp()) + sourcePath.setContent(source) + err = StringIO() + count = withStderrTo(err, lambda: checkPath(sourcePath.path)) + self.assertEquals(count, 1) + self.assertEquals( + err.getvalue(), "%s: problem decoding source\n" % (sourcePath.path,)) diff --git a/ftplugin/python/pyflakes/pyflakes/test/test_undefined_names.py b/ftplugin/python/pyflakes/pyflakes/test/test_undefined_names.py index 57e4e3c..309f0b9 100644 --- a/ftplugin/python/pyflakes/pyflakes/test/test_undefined_names.py +++ b/ftplugin/python/pyflakes/pyflakes/test/test_undefined_names.py @@ -1,7 +1,9 @@ -from sys import version_info +from _ast import PyCF_ONLY_AST -from pyflakes import messages as m +from twisted.trial.unittest import TestCase + +from pyflakes import messages as m, checker from pyflakes.test import harness @@ -24,9 +26,40 @@ class Test(harness.Test): def test_builtins(self): self.flakes('range(10)') - def test_magic_globals(self): + + def test_magicGlobalsFile(self): + """ + Use of the C{__file__} magic global should not emit an undefined name + warning. + """ self.flakes('__file__') + + def test_magicGlobalsBuiltins(self): + """ + Use of the C{__builtins__} magic global should not emit an undefined + name warning. + """ + self.flakes('__builtins__') + + + def test_magicGlobalsName(self): + """ + Use of the C{__name__} magic global should not emit an undefined name + warning. + """ + self.flakes('__name__') + + + def test_magicGlobalsPath(self): + """ + Use of the C{__path__} magic global should not emit an undefined name + warning, if you refer to it from a file called __init__.py. + """ + self.flakes('__path__', m.UndefinedName) + self.flakes('__path__', filename='package/__init__.py') + + def test_globalImportStar(self): '''Can't find undefined names with import *''' self.flakes('from fu import *; bar', m.ImportStarUsed) @@ -54,6 +87,16 @@ class Test(harness.Test): ''') test_definedByGlobal.todo = '' + def test_globalInGlobalScope(self): + """ + A global statement in the global scope is ignored. + """ + self.flakes(''' + global x + def foo(): + print x + ''', m.UndefinedName) + def test_del(self): '''del deletes bindings''' self.flakes('a = 1; del a; a', m.UndefinedName) @@ -91,6 +134,7 @@ class Test(harness.Test): def fun(): a a = 2 + return a ''', m.UndefinedLocal) def test_laterRedefinedGlobalFromNestedScope2(self): @@ -106,6 +150,26 @@ class Test(harness.Test): def fun2(): a a = 2 + return a + ''', m.UndefinedLocal) + + + def test_intermediateClassScopeIgnored(self): + """ + If a name defined in an enclosing scope is shadowed by a local variable + and the name is used locally before it is bound, an unbound local + warning is emitted, even if there is a class scope between the enclosing + scope and the local scope. + """ + self.flakes(''' + def f(): + x = 1 + class g: + def h(self): + a = x + x = None + print x, a + print x ''', m.UndefinedLocal) @@ -124,6 +188,9 @@ class Test(harness.Test): def c(): x x = 3 + return x + return x + return x ''', m.UndefinedLocal).messages[0] self.assertEqual(exc.message_args, ('x', 5)) @@ -139,6 +206,8 @@ class Test(harness.Test): def fun2(): a a = 1 + return a + return a ''', m.UndefinedLocal) def test_nestedClass(self): @@ -161,18 +230,16 @@ class Test(harness.Test): class C: bar = foo foo = 456 - + return foo f() ''', m.UndefinedName) - - -class Python24Test(harness.Test): - """ - Tests for checking of syntax which is valid in Python 2.4 and newer. - """ - if version_info < (2, 4): - skip = "Python 2.4 required for generator expression tests." + def test_definedAsStarArgs(self): + '''star and double-star arg names are defined''' + self.flakes(''' + def f(a, *b, **c): + print a, b, c + ''') def test_definedInGenExp(self): """ @@ -180,3 +247,19 @@ class Python24Test(harness.Test): warnings. """ self.flakes('(a for a in xrange(10) if a)') + + + +class NameTests(TestCase): + """ + Tests for some extra cases of name handling. + """ + def test_impossibleContext(self): + """ + A Name node with an unrecognized context results in a RuntimeError being + raised. + """ + tree = compile("x = 10", "", "exec", PyCF_ONLY_AST) + # Make it into something unrecognizable. + tree.body[0].targets[0].ctx = object() + self.assertRaises(RuntimeError, checker.Checker, tree) diff --git a/ftplugin/python/pyflakes/setup.py b/ftplugin/python/pyflakes/setup.py index d875a9e..8507337 100644 --- a/ftplugin/python/pyflakes/setup.py +++ b/ftplugin/python/pyflakes/setup.py @@ -1,19 +1,28 @@ #!/usr/bin/python -# (c) 2005 Divmod, Inc. See LICENSE file for details +# (c) 2005-2009 Divmod, Inc. See LICENSE file for details from distutils.core import setup setup( name="pyflakes", license="MIT", - version="0.2.1", + version="0.4.0", description="passive checker of Python programs", author="Phil Frost", maintainer="Moe Aboulkheir", maintainer_email="moe@divmod.com", - url="http://www.divmod.org/projects/pyflakes", - packages=["pyflakes", "pyflakes.scripts"], + url="http://www.divmod.org/trac/wiki/DivmodPyflakes", + packages=["pyflakes", "pyflakes.scripts", "pyflakes.test"], scripts=["bin/pyflakes"], long_description="""Pyflakes is program to analyze Python programs and detect various errors. It works by parsing the source file, not importing it, so it is safe to use on -modules with side effects. It's also much faster.""") +modules with side effects. It's also much faster.""", + classifiers=[ + "Development Status :: 6 - Mature", + "Environment :: Console", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Topic :: Software Development", + "Topic :: Utilities", + ]) diff --git a/plugin/acp.vim b/plugin/acp.vim new file mode 100644 index 0000000..0c01a31 --- /dev/null +++ b/plugin/acp.vim @@ -0,0 +1,170 @@ +"============================================================================= +" Copyright (c) 2007-2009 Takeshi NISHIDA +" +" GetLatestVimScripts: 1879 1 :AutoInstall: AutoComplPop +"============================================================================= +" LOAD GUARD {{{1 + +if exists('g:loaded_acp') + finish +elseif v:version < 702 + echoerr 'AutoComplPop does not support this version of vim (' . v:version . ').' + finish +endif +let g:loaded_acp = 1 + +" }}}1 +"============================================================================= +" FUNCTION: {{{1 + +" +function s:defineOption(name, default) + if !exists(a:name) + let {a:name} = a:default + endif +endfunction + +" +function s:makeDefaultBehavior() + let behavs = { + \ '*' : [], + \ 'ruby' : [], + \ 'python' : [], + \ 'perl' : [], + \ 'xml' : [], + \ 'html' : [], + \ 'xhtml' : [], + \ 'css' : [], + \ } + "--------------------------------------------------------------------------- + if !empty(g:acp_behaviorUserDefinedFunction) && + \ !empty(g:acp_behaviorUserDefinedMeets) + for key in keys(behavs) + call add(behavs[key], { + \ 'command' : "\\", + \ 'completefunc' : g:acp_behaviorUserDefinedFunction, + \ 'meets' : g:acp_behaviorUserDefinedMeets, + \ 'repeat' : 0, + \ }) + endfor + endif + "--------------------------------------------------------------------------- + for key in keys(behavs) + call add(behavs[key], { + \ 'command' : "\\", + \ 'completefunc' : 'acp#completeSnipmate', + \ 'meets' : 'acp#meetsForSnipmate', + \ 'onPopupClose' : 'acp#onPopupCloseSnipmate', + \ 'repeat' : 0, + \ }) + endfor + "--------------------------------------------------------------------------- + for key in keys(behavs) + call add(behavs[key], { + \ 'command' : g:acp_behaviorKeywordCommand, + \ 'meets' : 'acp#meetsForKeyword', + \ 'repeat' : 0, + \ }) + endfor + "--------------------------------------------------------------------------- + for key in keys(behavs) + call add(behavs[key], { + \ 'command' : "\\", + \ 'meets' : 'acp#meetsForFile', + \ 'repeat' : 1, + \ }) + endfor + "--------------------------------------------------------------------------- + call add(behavs.ruby, { + \ 'command' : "\\", + \ 'meets' : 'acp#meetsForRubyOmni', + \ 'repeat' : 0, + \ }) + "--------------------------------------------------------------------------- + call add(behavs.python, { + \ 'command' : "\\", + \ 'meets' : 'acp#meetsForPythonOmni', + \ 'repeat' : 0, + \ }) + "--------------------------------------------------------------------------- + call add(behavs.perl, { + \ 'command' : "\\", + \ 'meets' : 'acp#meetsForPerlOmni', + \ 'repeat' : 0, + \ }) + "--------------------------------------------------------------------------- + call add(behavs.xml, { + \ 'command' : "\\", + \ 'meets' : 'acp#meetsForXmlOmni', + \ 'repeat' : 1, + \ }) + "--------------------------------------------------------------------------- + call add(behavs.html, { + \ 'command' : "\\", + \ 'meets' : 'acp#meetsForHtmlOmni', + \ 'repeat' : 1, + \ }) + "--------------------------------------------------------------------------- + call add(behavs.xhtml, { + \ 'command' : "\\", + \ 'meets' : 'acp#meetsForHtmlOmni', + \ 'repeat' : 1, + \ }) + "--------------------------------------------------------------------------- + call add(behavs.css, { + \ 'command' : "\\", + \ 'meets' : 'acp#meetsForCssOmni', + \ 'repeat' : 0, + \ }) + "--------------------------------------------------------------------------- + return behavs +endfunction + +" }}}1 +"============================================================================= +" INITIALIZATION {{{1 + +"----------------------------------------------------------------------------- +call s:defineOption('g:acp_enableAtStartup', 1) +call s:defineOption('g:acp_mappingDriven', 0) +call s:defineOption('g:acp_ignorecaseOption', 1) +call s:defineOption('g:acp_completeOption', '.,w,b,k') +call s:defineOption('g:acp_completeoptPreview', 0) +call s:defineOption('g:acp_behaviorUserDefinedFunction', '') +call s:defineOption('g:acp_behaviorUserDefinedMeets', '') +call s:defineOption('g:acp_behaviorSnipmateLength', -1) +call s:defineOption('g:acp_behaviorKeywordCommand', "\") +call s:defineOption('g:acp_behaviorKeywordLength', 2) +call s:defineOption('g:acp_behaviorKeywordIgnores', []) +call s:defineOption('g:acp_behaviorFileLength', 0) +call s:defineOption('g:acp_behaviorRubyOmniMethodLength', 0) +call s:defineOption('g:acp_behaviorRubyOmniSymbolLength', 1) +call s:defineOption('g:acp_behaviorPythonOmniLength', 0) +call s:defineOption('g:acp_behaviorPerlOmniLength', -1) +call s:defineOption('g:acp_behaviorXmlOmniLength', 0) +call s:defineOption('g:acp_behaviorHtmlOmniLength', 0) +call s:defineOption('g:acp_behaviorCssOmniPropertyLength', 1) +call s:defineOption('g:acp_behaviorCssOmniValueLength', 0) +call s:defineOption('g:acp_behavior', {}) +"----------------------------------------------------------------------------- +call extend(g:acp_behavior, s:makeDefaultBehavior(), 'keep') +"----------------------------------------------------------------------------- +command! -bar -narg=0 AcpEnable call acp#enable() +command! -bar -narg=0 AcpDisable call acp#disable() +command! -bar -narg=0 AcpLock call acp#lock() +command! -bar -narg=0 AcpUnlock call acp#unlock() +"----------------------------------------------------------------------------- +" legacy commands +command! -bar -narg=0 AutoComplPopEnable AcpEnable +command! -bar -narg=0 AutoComplPopDisable AcpDisable +command! -bar -narg=0 AutoComplPopLock AcpLock +command! -bar -narg=0 AutoComplPopUnlock AcpUnlock +"----------------------------------------------------------------------------- +if g:acp_enableAtStartup + AcpEnable +endif +"----------------------------------------------------------------------------- + +" }}}1 +"============================================================================= +" vim: set fdm=marker: