Skip to content

Commit

Permalink
Merge pull request #36 from fourdigits/23-add-completion-docs
Browse files Browse the repository at this point in the history
Add docs hover and completion items for tags and filters
  • Loading branch information
rubenhesselink authored Jun 21, 2024
2 parents ec5e80f + 5d10102 commit 4af23b8
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 97 deletions.
138 changes: 69 additions & 69 deletions djlsp/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,69 +32,69 @@
"extends": {},
"include": {},
},
"filters": [
"addslashes",
"capfirst",
"escapejs",
"json_script",
"floatformat",
"iriencode",
"linenumbers",
"lower",
"make_list",
"slugify",
"stringformat",
"title",
"truncatechars",
"truncatechars_html",
"truncatewords",
"truncatewords_html",
"upper",
"urlencode",
"urlize",
"urlizetrunc",
"wordcount",
"wordwrap",
"ljust",
"rjust",
"center",
"cut",
"escape",
"force_escape",
"linebreaks",
"linebreaksbr",
"safe",
"safeseq",
"striptags",
"dictsort",
"dictsortreversed",
"first",
"join",
"last",
"length",
"length_is",
"random",
"slice",
"unordered_list",
"add",
"get_digit",
"date",
"time",
"timesince",
"timeuntil",
"default",
"default_if_none",
"divisibleby",
"yesno",
"filesizeformat",
"pluralize",
"phone2numeric",
"pprint",
],
"filters": {
"addslashes": {},
"capfirst": {},
"escapejs": {},
"json_script": {},
"floatformat": {},
"iriencode": {},
"linenumbers": {},
"lower": {},
"make_list": {},
"slugify": {},
"stringformat": {},
"title": {},
"truncatechars": {},
"truncatechars_html": {},
"truncatewords": {},
"truncatewords_html": {},
"upper": {},
"urlencode": {},
"urlize": {},
"urlizetrunc": {},
"wordcount": {},
"wordwrap": {},
"ljust": {},
"rjust": {},
"center": {},
"cut": {},
"escape": {},
"force_escape": {},
"linebreaks": {},
"linebreaksbr": {},
"safe": {},
"safeseq": {},
"striptags": {},
"dictsort": {},
"dictsortreversed": {},
"first": {},
"join": {},
"last": {},
"length": {},
"length_is": {},
"random": {},
"slice": {},
"unordered_list": {},
"add": {},
"get_digit": {},
"date": {},
"time": {},
"timesince": {},
"timeuntil": {},
"default": {},
"default_if_none": {},
"divisibleby": {},
"yesno": {},
"filesizeformat": {},
"pluralize": {},
"phone2numeric": {},
"pprint": {},
},
},
"cache": {
"tags": {"cache": {"inner_tags": [], "closing_tag": "endcache"}},
"filters": [],
"filters": {},
},
"i18n": {
"tags": {
Expand All @@ -109,28 +109,28 @@
"blocktranslate": {},
"language": {"inner_tags": [], "closing_tag": "endlanguage"},
},
"filters": [
"language_name",
"language_name_translated",
"language_name_local",
"language_bidi",
],
"filters": {
"language_name": {},
"language_name_translated": {},
"language_name_local": {},
"language_bidi": {},
},
},
"l10n": {
"tags": {"localize": {"inner_tags": [], "closing_tag": "endlocalize"}},
"filters": ["localize", "unlocalize"],
"filters": {"localize": {}, "unlocalize": {}},
},
"static": {
"tags": {"get_static_prefix": {}, "get_media_prefix": {}, "static": {}},
"filters": [],
"filters": {},
},
"tz": {
"tags": {
"localtime": {"inner_tags": [], "closing_tag": "endlocaltime"},
"timezone": {"inner_tags": [], "closing_tag": "endtimezone"},
"get_current_timezone": {},
},
"filters": ["localtime", "utc", "timezone"],
"filters": {"localtime": {}, "utc": {}, "timezone": {}},
},
},
"templates": {},
Expand Down
18 changes: 16 additions & 2 deletions djlsp/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,22 @@ class Template:
@dataclass
class Tag:
name: str = ""
docs: str = ""
inner_tags: list[str] = ""
closing_tag: str = ""


@dataclass
class Filter:
name: str = ""
docs: str = ""


@dataclass
class Library:
name: str = ""
tags: dict[str, Tag] = field(default_factory=dict)
filters: list[str] = field(default_factory=list)
filters: dict[str, Filter] = field(default_factory=dict)


@dataclass
Expand All @@ -43,10 +50,17 @@ def update(self, django_data: dict):
self.libraries = {
lib_name: Library(
name=lib_name,
filters=lib_data.get("filters", []),
filters={
name: Filter(
name=name,
docs=filter_options.get("docs", ""),
)
for name, filter_options in lib_data.get("filters", {}).items()
},
tags={
tag: Tag(
name=tag,
docs=tag_options.get("docs"),
inner_tags=tag_options.get("inner_tags", []),
closing_tag=tag_options.get("closing_tag"),
)
Expand Down
73 changes: 65 additions & 8 deletions djlsp/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from functools import cached_property
from re import Match

from lsprotocol.types import CompletionItem
from lsprotocol.types import CompletionItem, Hover
from pygls.workspace import TextDocument

from djlsp.constants import BUILTIN
Expand Down Expand Up @@ -34,6 +34,9 @@ def loaded_libraries(self):
logger.debug(f"Loaded libraries: {loaded}")
return loaded

###################################################################################
# Completions
###################################################################################
def completions(self, line, character):
line_fragment = self.document.lines[line][:character]
matchers = [
Expand Down Expand Up @@ -142,25 +145,38 @@ def get_tag_completions(self, match: Match):
for lib_name in self.loaded_libraries:
if lib := self.workspace_index.libraries.get(lib_name):
for tag in lib.tags.values():
tags.append(tag.name)
tags.append(
CompletionItem(
label=tag.name,
documentation=tag.docs,
)
)
# TODO: Only add inner/clossing if there is opening tag
tags.extend(tag.inner_tags)
tags.extend([CompletionItem(label=tag) for tag in tag.inner_tags])
if tag.closing_tag:
tags.append(tag.closing_tag)
tags.append(CompletionItem(label=tag.closing_tag))

return [CompletionItem(label=tag) for tag in tags if tag.startswith(prefix)]
return [tag for tag in tags if tag.label.startswith(prefix)]

def get_filter_completions(self, match: Match):
prefix = match.group(2)
logger.debug(f"Find filter matches for: {prefix}")
filters = []
for lib_name in self.loaded_libraries:
if lib := self.workspace_index.libraries.get(lib_name):
filters.extend(lib.filters)
filters.extend(
[
CompletionItem(
label=filt.name,
documentation=filt.docs,
)
for filt in lib.filters.values()
]
)
return [
CompletionItem(label=filter_name)
filter_name
for filter_name in filters
if filter_name.startswith(prefix)
if filter_name.label.startswith(prefix)
]

def get_context_completions(self, match: Match):
Expand Down Expand Up @@ -195,3 +211,44 @@ def _recursive_context_lookup(self, parts: [str], context: dict[str, str]):

# No suggesions found
return "", []

###################################################################################
# Hover
###################################################################################
def hover(self, line, character):
line_fragment = self.document.lines[line][:character]
matchers = [
(re.compile(r"^.*({%|{{) ?[\w \.\|]*\|(\w*)$"), self.get_filter_hover),
(re.compile(r"^.*{% ?(\w*)$"), self.get_tag_hover),
]
for regex, hover in matchers:
if match := regex.match(line_fragment):
return hover(line, character, match)
return None

def get_filter_hover(self, line, character, match: Match):
filter_name = self._get_full_hover_name(line, character, match.group(2))
logger.debug(f"Find filter hover for: {filter_name}")
for lib in self.workspace_index.libraries.values():
if lib.name in self.loaded_libraries and filter_name in lib.filters:
return Hover(
contents=lib.filters[filter_name].docs,
)
return None

def get_tag_hover(self, line, character, match: Match):
tag_name = self._get_full_hover_name(line, character, match.group(1))
logger.debug(f"Find tag hover for: {tag_name}")
for lib in self.workspace_index.libraries.values():
if lib.name in self.loaded_libraries and tag_name in lib.tags:
return Hover(
contents=lib.tags[tag_name].docs,
)
return None

def _get_full_hover_name(self, line, character, first_part):
if match_after := re.match(
r"^([\w\d]+).*", self.document.lines[line][character:]
):
return first_part + match_after.group(1)
return first_part
34 changes: 23 additions & 11 deletions djlsp/scripts/django-collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,15 +243,16 @@ def get_libraries():
libraries = {
"__builtins__": {
"tags": {},
"filters": [],
"filters": {},
}
}

# Collect builtins
for lib_mod_path in Engine.get_default().builtins:
lib = importlib.import_module(lib_mod_path).register
libraries["__builtins__"]["tags"].update({tag: {} for tag in lib.tags.keys()})
libraries["__builtins__"]["filters"].extend(list(lib.filters.keys()))
parsed_lib = _parse_library(lib)
libraries["__builtins__"]["tags"].update(parsed_lib["tags"])
libraries["__builtins__"]["filters"].update(parsed_lib["filters"])

# Get Django templatetags
django_path = inspect.getabsfile(django.templatetags)
Expand All @@ -262,10 +263,7 @@ def get_libraries():
try:
lib = get_installed_libraries()[django_lib]
lib = importlib.import_module(lib).register
libraries[django_lib] = {
"tags": {tag: {} for tag in lib.tags.keys()},
"filters": list(lib.filters.keys()),
}
libraries[django_lib] = _parse_library(lib)
except (InvalidTemplateLibrary, KeyError):
continue

Expand All @@ -287,10 +285,7 @@ def get_libraries():
except (InvalidTemplateLibrary, KeyError):
continue

libraries[taglib] = {
"tags": {tag: {} for tag in lib.tags.keys()},
"filters": list(lib.filters.keys()),
}
libraries[taglib] = _parse_library(lib)

# Add node tags
for lib_name, tags in LIBRARIES_NODE_TAGS.items():
Expand All @@ -307,6 +302,23 @@ def get_libraries():
return libraries


def _parse_library(lib) -> dict:
return {
"tags": {
name: {
"docs": func.__doc__.strip() if func.__doc__ else "",
}
for name, func in lib.tags.items()
},
"filters": {
name: {
"docs": func.__doc__.strip() if func.__doc__ else "",
}
for name, func in lib.filters.items()
},
}


def get_templates():
template_files = {}
default_engine = Engine.get_default()
Expand Down
Loading

0 comments on commit 4af23b8

Please sign in to comment.