add initial autocomplete for installed templatetags (#46)

This commit is contained in:
Josh Thomas 2024-12-23 19:36:54 -06:00 committed by GitHub
parent 5eb8a775e4
commit c16635b1c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 359 additions and 58 deletions

View file

@ -0,0 +1,8 @@
[package]
name = "djls-project"
version = "0.1.0"
edition = "2021"
[dependencies]
pyo3 = { workspace = true }
tower-lsp = { workspace = true }

View file

@ -0,0 +1,62 @@
mod templatetags;
pub use templatetags::TemplateTags;
use pyo3::prelude::*;
use std::path::{Path, PathBuf};
use tower_lsp::lsp_types::*;
#[derive(Debug)]
pub struct DjangoProject {
path: PathBuf,
template_tags: Option<TemplateTags>,
}
impl DjangoProject {
pub fn new(path: PathBuf) -> Self {
Self {
path,
template_tags: None,
}
}
pub fn from_initialize_params(params: &InitializeParams) -> Option<Self> {
// Try current directory first
let path = std::env::current_dir()
.ok()
// Fall back to workspace root if provided
.or_else(|| {
params
.root_uri
.as_ref()
.and_then(|uri| uri.to_file_path().ok())
});
path.map(Self::new)
}
pub fn initialize(&mut self) -> PyResult<()> {
Python::with_gil(|py| {
// Add project to Python path
let sys = py.import("sys")?;
let py_path = sys.getattr("path")?;
py_path.call_method1("append", (self.path.to_str().unwrap(),))?;
// Setup Django
let django = py.import("django")?;
django.call_method0("setup")?;
self.template_tags = Some(TemplateTags::from_python(py)?);
Ok(())
})
}
pub fn template_tags(&self) -> Option<&TemplateTags> {
self.template_tags.as_ref()
}
pub fn path(&self) -> &Path {
&self.path
}
}

View file

@ -0,0 +1,94 @@
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList};
use std::ops::Deref;
#[derive(Debug, Default, Clone)]
pub struct TemplateTags(Vec<TemplateTag>);
impl Deref for TemplateTags {
type Target = Vec<TemplateTag>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl TemplateTags {
fn new() -> Self {
Self(Vec::new())
}
fn process_library(
module_name: &str,
library: &Bound<'_, PyAny>,
tags: &mut Vec<TemplateTag>,
) -> PyResult<()> {
let tags_dict = library.getattr("tags")?;
let dict = tags_dict.downcast::<PyDict>()?;
for (key, value) in dict.iter() {
let tag_name = key.extract::<String>()?;
let doc = value.getattr("__doc__")?.extract().ok();
let library_name = if module_name.is_empty() {
"builtins".to_string()
} else {
module_name.split('.').last().unwrap_or("").to_string()
};
tags.push(TemplateTag::new(tag_name, library_name, doc));
}
Ok(())
}
pub fn from_python(py: Python) -> PyResult<TemplateTags> {
let mut template_tags = TemplateTags::new();
let engine = py
.import("django.template.engine")?
.getattr("Engine")?
.call_method0("get_default")?;
// Built-in template tags
let builtins_attr = engine.getattr("template_builtins")?;
let builtins = builtins_attr.downcast::<PyList>()?;
for builtin in builtins {
Self::process_library("", &builtin, &mut template_tags.0)?;
}
// Custom template libraries
let libraries_attr = engine.getattr("template_libraries")?;
let libraries = libraries_attr.downcast::<PyDict>()?;
for (module_name, library) in libraries.iter() {
let module_name = module_name.extract::<String>()?;
Self::process_library(&module_name, &library, &mut template_tags.0)?;
}
Ok(template_tags)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TemplateTag {
name: String,
library: String,
doc: Option<String>,
}
impl TemplateTag {
fn new(name: String, library: String, doc: Option<String>) -> Self {
Self { name, library, doc }
}
pub fn name(&self) -> &String {
&self.name
}
pub fn library(&self) -> &String {
&self.library
}
pub fn doc(&self) -> &Option<String> {
&self.doc
}
}