dot_files/vim-plugins/bundle/vim-htmldjango_omnicomplete/autoload/htmldjangocomplete.vim
2017-07-09 00:26:06 +03:00

504 lines
16 KiB
VimL

" Vim completion script
" Language: htmldjango
" Maintainer: Michael Brown
" Last Change: Sun 13 May 2012 16:39:45 EST
" Version: 0.9.2
" Omnicomplete for django template taga/variables/filters
" {{{1 Environment Settings
if !exists('g:htmldjangocomplete_html_flavour')
" :verbose function htmlcomplete#CheckDoctype for details
" No html5!
"'html401t' 'xhtml10s' 'html32' 'html40t' 'html40f' 'html40s'
"'html401t' 'html401f' 'html401s' 'xhtml10t' 'xhtml10f' 'xhtml10s'
"'xhtml11'
let g:htmldjangocomplete_html_flavour = 'xhtml11'
endif
"Allow settings of DEBUG
if !exists('g:htmldjangocomplete_debug')
let g:htmldjangocomplete_debug = 0
endif
"{{{1 The actual omnifunc
function! htmldjangocomplete#CompleteDjango(findstart, base)
"{{{2 findstart = 1 when we need to get the text length
"
if a:findstart == 1
"Fallback to htmlcomplete
if searchpair('{{','','}}','nc') == 0 && searchpair('{%',"",'%}','nc') == 0
if !exists('b:html_doctype')
let b:html_doctype = 1
let b:html_omni_flavor = g:htmldjangocomplete_html_flavour
endif
return htmlcomplete#CompleteTags(a:findstart,a:base)
endif
" locate the start of the word
let line = getline('.')
let start = col('.') - 1
"special case for {% extends %} {% import %} needs to grab /'s
"TODO make this match more flexible. It needs to know its in a string
"also need to handle inline imports
if s:get_context() == 'extends'|| s:get_context() == 'include'
while start > 0 && line[start - 1] != '"' && line[start -1] != "'"
\ && line[start -1] != ' '
let start -= 1
endwhile
return start
endif
"
"default <word> case
while start > 0 && line[start - 1] =~ '\a'
let start -= 1
endwhile
return start
"{{{2 findstart = 0 when we need to return the list of completions
else
"Fallback to htmlcomplete
if searchpair('{{','','}}','nc') == 0 && searchpair('{%',"",'%}','nc') == 0
let matches = htmlcomplete#CompleteTags(a:findstart,a:base)
"suppress all DOCTYPE matches
call filter(matches, 'stridx(v:val["word"],"DOCTYPE") == -1')
return matches
endif
let context = s:get_context()
if context == 'extends' || context == 'include'
let context = 'template'
endif
"TODO: Reduce load always nature of this plugin
call s:load_libs()
"get context look for {% {{ and |
let line = getline('.')
let start = col('.') -1
" Special case for extends and import
" TODO 'filter' should really just be string filters
if index(['template','load','url','filter','block', 'static'],context) != -1
execute "python htmldjangocomplete('" . context . "', '" . a:base . "')"
return g:htmldjangocomplete_completions
endif
while start > 0
if line[start] == ':' && s:in_django(line,start) == 1
execute "python htmldjangocomplete('variable', '" . a:base . "')"
return g:htmldjangocomplete_completions
elseif line[start] == '|' && s:in_django(line,start)
execute "python htmldjangocomplete('filter', '" . a:base . "')"
return g:htmldjangocomplete_completions
elseif line[start] == '{' && line[start -1] == '{'
execute "python htmldjangocomplete('variable', '" . a:base . "')"
return g:htmldjangocomplete_completions
elseif line[start] == '%' && line[start -1] == '{'
execute "python htmldjangocomplete('tag', '" . a:base . "')"
return g:htmldjangocomplete_completions
else
let start -= 1
endif
endwhile
return [ {'word': "nomatch"} ]
"fallback to htmlcomplete TODO This doesn't work as expected.
"Might need to turn off some doctype setting.
"
"call htmlcomplete#CompleteTags(1, a:base)
"return htmlcomplete#CompleteTags(0, a:base)
endif
endfunction
"Supporting vim functions {{{1
function! s:get_context()
let curpos = getpos('.')
let line = getline('.')
"tags
let starttag = searchpairpos('{%', '', '%}', 'bn')
if starttag != [0,0]
let fragment = line[starttag[1]:curpos[2]]
return split(fragment,' ')[1]
endif
return "other"
endfunction
"TODO This could probably be neater with an index check. need to get strings
"working
function! s:in_django(l,s)
let line = a:l
let start = a:s
while start >= 0
if line[start] == '}'
return 0
elseif line[start] == '{'
return 1
endif
let start -= 1
endwhile
return 0
endfunction
"Python section {{{1
"imports {{{2
let g:htmldjangocomplete_completions = []
function! s:load_libs()
if has('python')
python << EOF
try:
HTMLDJANGO_DEBUG = vim.eval("g:htmldjangocomplete_debug") == 0 and True or False
except:
HTMLDJANGO_DEBUG = False
TEMPLATE_EXTS = ['.html','.txt','.htm']
import warnings
warnings.sys.warnoptions = ['-W']
import logging
import vim
import os
# Install the django-configurations importer (before Django setup).
if os.environ.get('DJANGO_CONFIGURATION'):
try:
import configurations.importer
configurations.importer.install()
except:
sys.exit()
# Setup Django (required for >= 1.7).
import django
if hasattr(django, 'setup'):
django.setup()
try:
#for OLD versions od django
from django.template import get_library
except:
#for django 1.8+
try:
from django.template.base import get_library
except:
#for django 1.9+
from django.template.backends.django import get_installed_libraries
def get_library(libname):
return get_installed_libraries()[libname]
from django.template.loaders import filesystem, app_directories
#Later versions of django seem to be fussy about get_library paths.
try:
from django.template import import_library
except ImportError:
try:
from django.template.base import import_library
except ImportError:
import_library = get_library
try:
from django.template.loaders.app_directories import app_template_dirs
except:
from django.template.loaders.app_directories import get_app_template_dirs
app_template_dirs = get_app_template_dirs('templates')
from django.conf import settings as mysettings
from django.template.loader import get_template
from django.template.loader_tags import ExtendsNode, BlockNode
from django.template import Template
import re
from operator import itemgetter
import pkgutil
import os
from glob import glob
try:
from django.template import get_templatetags_modules
except ImportError:
#I've lifted this version from the django source
try:
from importlib import import_module
except ImportError:
from django.utils.importlib import import_module
def get_templatetags_modules():
"""
Return the list of all available template tag modules.
Caches the result for faster access.
"""
_templatetags_modules = []
# Populate list once per process. Mutate the local list first, and
# then assign it to the global name to ensure there are no cases where
# two threads try to populate it simultaneously.
for app_module in ['django'] + list(mysettings.INSTALLED_APPS):
try:
templatetag_module = '%s.templatetags' % app_module
import_module(templatetag_module)
_templatetags_modules.append(templatetag_module)
except ImportError:
continue
return _templatetags_modules
# {{{2 Support functions
def get_block_tags(start=''):
#use regexp for extends as get_template will fail on tag errors
rexp = re.compile('{%\s*extends\s*[\'"](.*)["\']\s*%}')
base = None
templates = [] # for cycle detection
for l in vim.current.buffer[0:10]:
match = rexp.match(l)
if match:
try:
base = get_template(match.groups()[0])
except:
return []
if not base:
return []
def _get_blocks(tpl, menu_prefix='', add_name = True):
"""
recursive worker function
"""
# TODO I Think this is a 1.8 compatibility thing sending the wrapper
# class
tpl = getattr(tpl, 'name', None) and tpl or tpl.template
if tpl.name in templates and isinstance(tpl, Template):
logging.info("cyclic extends detected!")
return []
else:
templates.append(tpl.name)
if menu_prefix == '' and isinstance(tpl, Template):
menu_prefix = "%s > " % tpl.name
blocks = [(block, block.name, menu_prefix)
for block in tpl.nodelist if isinstance(block, BlockNode)]
for block, name, _ in blocks:
if add_name:
blocks += _get_blocks(block, '%s%s > ' % (menu_prefix, name))
else:
blocks += _get_blocks(block, '%s' % (menu_prefix))
if len(tpl.nodelist) > 0 and isinstance(tpl.nodelist[0], ExtendsNode):
logging.debug("parent_name")
logging.debug(tpl.nodelist[0].parent_name)
parent_template = str(tpl.nodelist[0].parent_name).replace(
'"', '').replace("''", '')
try:
parent_tpl = get_template(parent_template)
blocks += _get_blocks(parent_tpl)
except TemplateDoesNotExist:
logging.info("get_template:TemplateDoesNotExist - '%s'" %
parent_template)
for node in tpl.nodelist:
if not isinstance(node, BlockNode) and not isinstance(node,
ExtendsNode):
try:
blocks += _get_blocks(node, menu_prefix, add_name=False)
except AttributeError:
logging.info("node %s: no nodelist" % node)
return blocks
full_matches = _get_blocks(base)
names = []
matches = []
# dedup matches TODO might be a better way of picking matches
for match in full_matches:
if not match[0] in names:
matches.append(match)
names.append(match[0])
return [{'word':n,'menu':m} for b, n, m in matches if n.startswith(start)]
def get_template_names(pattern):
dirs = getattr(mysettings, "TEMPLATE_DIRS", ()) + app_template_dirs
if hasattr(mysettings, "TEMPLATES"):
for d in [e["DIRS"] for e in mysettings.TEMPLATES]:
dirs += tuple(d)
matches = []
for d in dirs:
d = d + (os.path.sep if not d.endswith(os.path.sep) else '')
for m in glob(os.path.join(d,pattern + '*')):
if os.path.isdir(m):
for root,dirnames,filenames in os.walk(m):
for f in filenames:
fn,ext = os.path.splitext(f)
if ext in TEMPLATE_EXTS:
matches.append({
'word' : os.path.join(root,f).replace(d,''),
'info' : 'found in %s' % d
}
)
else:
matches.append({
'word' : m.replace(d,''),
'info' : 'found in %s' % d
}
)
return matches
try:
from django.contrib.staticfiles import finders, storage
def get_staticfiles(pattern):
dirs = mysettings.STATICFILES_DIRS
#TODO crude matching
line = vim.current.line
if 'script' in line:
ext = ".*\.js$"
elif 'style' in line:
ext = ".*\.css$"
elif 'img' in line:
ext = ".*\.(gif|jpg|jpeg|png)$"
else:
ext = '.*'
matches = []
for finder in finders.get_finders():
for path, storage in finder.list([]):
if re.compile(ext,re.IGNORECASE).match(path) \
and path.startswith(pattern):
matches.append(dict(word=path,info=''))
return matches
except:
def get_staticfiles(pattern):
return []
def get_tag_libraries():
opts = []
for module in get_templatetags_modules():
mod = __import__(module,fromlist=['foo'])
for l,m,i in pkgutil.iter_modules([os.path.dirname(mod.__file__)]):
opts.append({'word':m,'menu':mod.__name__})
return opts
def _get_doc(doc, name):
if doc:
return doc.replace('"',' ').replace("'",' ')
return '%s: no doc' % name
def _get_opt_dict(lib,t,libname=''):
opts = getattr(lib,t)
return [
{'word':f, 'info': _get_doc(opts[f].__doc__,f),'menu':libname} \
for f in opts.keys()]
def load_app_tags():
cb = vim.current.buffer
for line in cb:
m = re.compile('{% load (.*)%}').match(line)
if m:
for lib in m.groups()[0].rstrip().split(' '):
try:
l = get_library(lib)
htmldjango_opts['filter'] += _get_opt_dict(l,'filters',lib)
htmldjango_opts['tag'] += _get_opt_dict(l,'tags',lib)
except Exception as e:
if HTMLDJANGO_DEBUG:
print "FAILED TO LOAD: %s" % lib
raise e
# {{{2 load options
# TODO At the moment this is being loaded every match
htmldjango_opts = {}
htmldjango_opts['load'] = get_tag_libraries()
def_filters = import_library('django.template.defaultfilters')
htmldjango_opts['filter'] = _get_opt_dict(def_filters,'filters','default')
def_tags = import_library('django.template.defaulttags')
htmldjango_opts['tag'] = _get_opt_dict(def_tags,'tags','default')
load_app_tags()
try:
urls = __import__(mysettings.ROOT_URLCONF,fromlist=['foo'])
except:
urls = None
def htmldjango_urls(pattern):
matches = []
def get_urls(urllist,parent=None):
for entry in urllist:
if hasattr(entry,'name') and entry.name:
matches.append(dict(
word = entry.name,
info = entry.regex.pattern,
menu = parent and parent.urlconf_name or '')
)
if hasattr(entry, 'url_patterns'):
get_urls(entry.url_patterns, entry)
if urls:
get_urls(urls.urlpatterns)
return matches
#TODO I may be able to populate RequestContext via middleware component
htmldjango_opts['variable'] = []
#TODO Write a function that gets all ancestor template blocks
htmldjango_opts['block'] = []
# Main Python function {{{2
def htmldjangocomplete(context,match):
if context == 'template':
all = get_template_names(match)
elif context == 'static':
all = get_staticfiles(match)
elif context == 'url':
all = htmldjango_urls(match)
elif context == 'block':
all = get_block_tags(match)
else:
all = htmldjango_opts[context]
vim.command("silent let g:htmldjangocomplete_completions = []")
dictstr = '['
# have to do this for double quoting
all = [a for a in all if a['word'].startswith(match)]
all = sorted(all, key=itemgetter('word'))
for cmpl in all:
dictstr += '{'
for x in cmpl: dictstr += '"%s":"%s",' % (x,cmpl[x].replace("\\", "\\\\")) # Windows slashes must be escaped
dictstr += '"icase":0},'
if dictstr[-1] == ',': dictstr = dictstr[:-1]
dictstr += ']'
vim.command("silent let g:htmldjangocomplete_completions = %s" % dictstr)
EOF
endif
endfunction
" Test Area {{{1
function! TestLoadLibs()
call s:load_libs()
endfunction
function! HtmlDjangoDebug(on)
if a:on
echo "adding Breakpoint"
breakadd func htmldjangocomplete#CompleteDjango
else
echo "remove Breakpoint"
breakdel func htmldjangocomplete#CompleteDjango
endif
endfunction
" vim:set foldmethod=marker: