Skip to content

Commit

Permalink
Merge pull request #14 from fourdigits/add-context-completion
Browse files Browse the repository at this point in the history
Add support for simple context completions
  • Loading branch information
rubenhesselink authored Jun 13, 2024
2 parents a496e4a + f16bb76 commit 95316e7
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 0 deletions.
3 changes: 3 additions & 0 deletions djlsp/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,7 @@
},
},
"templates": {},
"global_template_context": {
"csrf_token": {},
},
}
5 changes: 5 additions & 0 deletions djlsp/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class WorkspaceIndex:
urls: [str] = field(default_factory=list)
libraries: dict[str, Library] = field(default_factory=dict)
templates: dict[str, Template] = field(default_factory=dict)
global_template_context: dict[str, str] = field(default_factory=dict)
object_types: dict[str, dict] = field(default_factory=dict)

def update(self, django_data: dict):
self.file_watcher_globs = django_data.get(
Expand Down Expand Up @@ -58,3 +60,6 @@ def update(self, django_data: dict):
name: Template(name=name, **options)
for name, options in django_data.get("templates", {}).items()
}

self.global_template_context = django_data.get("global_template_context", {})
self.object_types = django_data.get("object_types", {})
28 changes: 28 additions & 0 deletions djlsp/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class TemplateParser:
re_tag = re.compile(r"^.*{% ?(\w*)$")
re_filter = re.compile(r"^.*({%|{{) ?[\w \.\|]*\|(\w*)$")
re_template = re.compile(r""".*{% ?(extends|include) ('|")([\w\-:]*)$""")
re_context = re.compile(r".*({{|{% \w+).* ([\w\d_\.]*)$")

def __init__(self, workspace_index: WorkspaceIndex, document: TextDocument):
self.workspace_index: WorkspaceIndex = workspace_index
Expand Down Expand Up @@ -54,6 +55,8 @@ def completions(self, line, character):
return self.get_tag_completions(match)
elif match := self.re_filter.match(line_fragment):
return self.get_filter_completions(match)
elif match := self.re_context.match(line_fragment):
return self.get_context_completions(match)

return []

Expand Down Expand Up @@ -123,3 +126,28 @@ def get_filter_completions(self, match: Match):
return sorted(
[filter_name for filter_name in filters if filter_name.startswith(prefix)]
)

def get_context_completions(self, match: Match):
prefix = match.group(2)
logger.debug(f"Finde context matches for: {prefix}")
context = self.workspace_index.global_template_context.copy()

prefix, lookup_context = self._recursive_context_lookup(
prefix.strip().split("."), context
)

return [var for var in lookup_context if var.startswith(prefix)]

def _recursive_context_lookup(self, parts: [str], context: dict[str, str]):
if len(parts) == 1:
return parts[0], context

variable, *parts = parts

# Get new context
if variable_type := context.get(variable):
if new_context := self.workspace_index.object_types.get(variable_type):
return self._recursive_context_lookup(parts, new_context)

# No suggesions found
return "", []
64 changes: 64 additions & 0 deletions djlsp/scripts/django-collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import django
from django.apps import apps
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.staticfiles.finders import get_finders
from django.template.backends.django import get_installed_libraries
from django.template.engine import Engine
Expand Down Expand Up @@ -83,6 +84,51 @@
},
}

# Context processors are functions and therefore hard to parse
# Use hardcoded mapping for know context processors.
TEMPLATE_CONTEXT_PROCESSORS = {
# Django
"django.template.context_processors.csrf": {
"csrf_token": None,
},
"django.template.context_processors.debug": {
"debug": None,
"sql_queries": None,
},
"django.template.context_processors.i18n": {
"LANGUAGES": None,
"LANGUAGE_CODE": None,
"LANGUAGE_BIDI": None,
},
"django.template.context_processors.tz": {
"TIME_ZONE": None,
},
"django.template.context_processors.static": {
"STATIC_URL": None,
},
"django.template.context_processors.media": {
"MEDIA_URL": None,
},
"django.template.context_processors.request": {
"request": None,
},
# Django: auth
"django.contrib.auth.context_processors.auth": {
"user": None,
"perms": None,
},
# Django: messages
"django.contrib.messages.context_processors.messages": {
"messages": None,
"DEFAULT_MESSAGE_LEVELS": None,
},
# Wagtail: settings
"wagtail.contrib.settings.context_processors.settings": {
# TODO: add fake settings object type with reference to models
"settings": None,
},
}


def get_file_watcher_globs():
"""
Expand Down Expand Up @@ -268,6 +314,23 @@ def _get_template_content(engine: Engine, template_name):
re_block = re.compile(r".*{% ?block (\w*) ?%}.*")


def get_global_template_context():
global_context = {}

# Update object types
TEMPLATE_CONTEXT_PROCESSORS["django.contrib.auth.context_processors.auth"][
"user"
] = f"{get_user_model().__module__}.{get_user_model().__name__}"

for context_processor in Engine.get_default().template_context_processors:
module_path = ".".join(
[context_processor.__module__, context_processor.__name__]
)
if context := TEMPLATE_CONTEXT_PROCESSORS.get(module_path):
global_context.update(context)
return global_context


def _parse_template(content):
extends = None
blocks = set()
Expand All @@ -291,6 +354,7 @@ def collect_project_data():
"urls": get_urls(),
"libraries": get_libraries(),
"templates": get_templates(),
"global_template_context": get_global_template_context(),
"object_types": get_object_types(),
}

Expand Down
3 changes: 3 additions & 0 deletions djlsp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ def get_django_data(self):
logger.info(f" - Templates: {len(django_data['templates'])}")
logger.info(f" - Static files: {len(django_data['static_files'])}")
logger.info(f" - Urls: {len(django_data['urls'])}")
logger.info(
f" - Global context: {len(django_data['global_template_context'])}"
)
else:
logger.info("Could not collect Django data")

Expand Down

0 comments on commit 95316e7

Please sign in to comment.