From a451f8c48c8ccf5c370e5bc67976d42abb89c5ed Mon Sep 17 00:00:00 2001 From: Maikel Martens Date: Tue, 27 Aug 2024 19:41:04 +0200 Subject: [PATCH] Add goto support for tags and filters --- djlsp/index.py | 4 +++ djlsp/parser.py | 45 ++++++++++++++++++++++--------- djlsp/scripts/django-collector.py | 25 +++++++++++++++++ 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/djlsp/index.py b/djlsp/index.py index dbc1e9c..edeee0f 100644 --- a/djlsp/index.py +++ b/djlsp/index.py @@ -14,6 +14,7 @@ class Template: class Tag: name: str = "" docs: str = "" + source: str = "" inner_tags: list[str] = "" closing_tag: str = "" @@ -22,6 +23,7 @@ class Tag: class Filter: name: str = "" docs: str = "" + source: str = "" @dataclass @@ -56,6 +58,7 @@ def update(self, django_data: dict): name: Filter( name=name, docs=filter_options.get("docs", ""), + source=filter_options.get("source", ""), ) for name, filter_options in lib_data.get("filters", {}).items() }, @@ -63,6 +66,7 @@ def update(self, django_data: dict): tag: Tag( name=tag, docs=tag_options.get("docs"), + source=tag_options.get("source", ""), inner_tags=tag_options.get("inner_tags", []), closing_tag=tag_options.get("closing_tag"), ) diff --git a/djlsp/parser.py b/djlsp/parser.py index a04055d..3b40d6f 100644 --- a/djlsp/parser.py +++ b/djlsp/parser.py @@ -360,6 +360,8 @@ def goto_definition(self, line, character): re.compile(r""".*{% ?(extends|include) ('|")([\w\-\./]*)$"""), self.get_template_definition, ), + (re.compile(r"^.*{% ?(\w*)$"), self.get_tag_definition), + (re.compile(r"^.*({%|{{).*?\|(\w*)$"), self.get_filter_definition), ( re.compile(r".*({{|{% \w+ ).*?([\w\d_\.]*)$"), self.get_context_definition, @@ -370,24 +372,43 @@ def goto_definition(self, line, character): return definition(line, character, match) return None + def create_location(self, location, path, line): + root_path = ( + self.workspace_index.src_path + if location == "src" + else self.workspace_index.env_path + ) + return Location( + uri=f"file://{root_path}/{path}", + range=Range( + start=Position(line=int(line), character=0), + end=Position(line=int(line), character=0), + ), + ) + def get_template_definition(self, line, character, match: Match): if match_after := re.match(r'^(.*)".*', self.document.lines[line][character:]): template_name = match.group(3) + match_after.group(1) logger.debug(f"Find template goto definition for: {template_name}") if template := self.workspace_index.templates.get(template_name): location, path = template.path.split(":") - root_path = ( - self.workspace_index.src_path - if location == "src" - else self.workspace_index.env_path - ) - return Location( - uri=f"file://{root_path}/{path}", - range=Range( - start=Position(line=0, character=0), - end=Position(line=0, character=0), - ), - ) + return self.create_location(location, path, 0) + + def get_tag_definition(self, line, character, match: Match): + full_match = self._get_full_definition_name(line, character, match.group(1)) + logger.debug(f"Find tag goto definition for: {full_match}") + for lib in self.loaded_libraries: + if tag := self.workspace_index.libraries[lib].tags.get(full_match): + if tag.source: + return self.create_location(*tag.source.split(":")) + + def get_filter_definition(self, line, character, match: Match): + full_match = self._get_full_definition_name(line, character, match.group(2)) + logger.debug(f"Find filter goto definition for: {full_match}") + for lib in self.loaded_libraries: + if filter_ := self.workspace_index.libraries[lib].filters.get(full_match): + if filter_.source: + return self.create_location(*filter_.source.split(":")) def get_context_definition(self, line, character, match: Match): first_match = match.group(2) diff --git a/djlsp/scripts/django-collector.py b/djlsp/scripts/django-collector.py index d75cd20..bfddcba 100644 --- a/djlsp/scripts/django-collector.py +++ b/djlsp/scripts/django-collector.py @@ -187,6 +187,29 @@ def to_json(self): indent=4, ) + def get_source_from_type(self, type_): + def unwrap(func): + while hasattr(func, "__wrapped__"): + func = func.__wrapped__ + return func + + type_ = unwrap(type_) + + try: + source_file = inspect.getsourcefile(type_) + line = inspect.getsourcelines(type_)[1] + except Exception as e: + logger.error(e) + return "" + + if source_file.startswith(self.project_src_path): + path = source_file.removeprefix(self.project_src_path).lstrip("/") + return f"src:{path}:{line}" + elif source_file.startswith(sys.prefix): + path = source_file.removeprefix(sys.prefix).lstrip("/") + return f"env:{path}:{line}" + return "" + # File watcher globs # --------------------------------------------------------------------------------- def get_file_watcher_globs(self): @@ -336,12 +359,14 @@ def _parse_library(self, lib) -> dict: "tags": { name: { "docs": func.__doc__.strip() if func.__doc__ else "", + "source": self.get_source_from_type(func), } for name, func in lib.tags.items() }, "filters": { name: { "docs": func.__doc__.strip() if func.__doc__ else "", + "source": self.get_source_from_type(func), } for name, func in lib.filters.items() },