mirror of
https://github.com/joshuadavidthomas/django-language-server.git
synced 2025-09-26 20:09:29 +00:00
add initial autocomplete for installed templatetags (#46)
This commit is contained in:
parent
5eb8a775e4
commit
c16635b1c0
8 changed files with 359 additions and 58 deletions
|
@ -4,6 +4,7 @@ resolver = "2"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
djls = { path = "crates/djls" }
|
djls = { path = "crates/djls" }
|
||||||
|
djls-project = { path = "crates/djls-project" }
|
||||||
djls-server = { path = "crates/djls-server" }
|
djls-server = { path = "crates/djls-server" }
|
||||||
djls-template-ast = { path = "crates/djls-template-ast" }
|
djls-template-ast = { path = "crates/djls-template-ast" }
|
||||||
djls-worker = { path = "crates/djls-worker" }
|
djls-worker = { path = "crates/djls-worker" }
|
||||||
|
@ -16,6 +17,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
tokio = { version = "1.42", features = ["full"] }
|
tokio = { version = "1.42", features = ["full"] }
|
||||||
|
tower-lsp = { version = "0.20", features = ["proposed"] }
|
||||||
|
|
||||||
[profile.dev.package]
|
[profile.dev.package]
|
||||||
insta.opt-level = 3
|
insta.opt-level = 3
|
||||||
|
|
8
crates/djls-project/Cargo.toml
Normal file
8
crates/djls-project/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "djls-project"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
pyo3 = { workspace = true }
|
||||||
|
tower-lsp = { workspace = true }
|
62
crates/djls-project/src/lib.rs
Normal file
62
crates/djls-project/src/lib.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
94
crates/djls-project/src/templatetags.rs
Normal file
94
crates/djls-project/src/templatetags.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,13 +4,13 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
djls-project = { workspace = true }
|
||||||
djls-template-ast = { workspace = true }
|
djls-template-ast = { workspace = true }
|
||||||
djls-worker = { workspace = true }
|
djls-worker = { workspace = true }
|
||||||
|
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
pyo3 = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
tower-lsp = { workspace = true }
|
||||||
tower-lsp = { version = "0.20", features = ["proposed"] }
|
|
||||||
lsp-types = "0.97"
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
use djls_project::TemplateTags;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use tower_lsp::lsp_types::{
|
use tower_lsp::lsp_types::{
|
||||||
DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, Position,
|
CompletionItem, CompletionItemKind, CompletionResponse, DidChangeTextDocumentParams,
|
||||||
Range,
|
DidCloseTextDocumentParams, DidOpenTextDocumentParams, Documentation, InsertTextFormat,
|
||||||
|
MarkupContent, MarkupKind, Position, Range,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -102,6 +104,56 @@ impl Store {
|
||||||
pub fn is_version_valid(&self, uri: &str, version: i32) -> bool {
|
pub fn is_version_valid(&self, uri: &str, version: i32) -> bool {
|
||||||
self.get_version(uri).map_or(false, |v| v == version)
|
self.get_version(uri).map_or(false, |v| v == version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_completions(
|
||||||
|
&self,
|
||||||
|
uri: &str,
|
||||||
|
position: Position,
|
||||||
|
tags: &TemplateTags,
|
||||||
|
) -> Option<CompletionResponse> {
|
||||||
|
let document = self.get_document(uri)?;
|
||||||
|
|
||||||
|
if document.language_id != LanguageId::HtmlDjango {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let context = document.get_template_tag_context(position)?;
|
||||||
|
|
||||||
|
let mut completions: Vec<CompletionItem> = tags
|
||||||
|
.iter()
|
||||||
|
.filter(|tag| {
|
||||||
|
context.partial_tag.is_empty() || tag.name().starts_with(&context.partial_tag)
|
||||||
|
})
|
||||||
|
.map(|tag| {
|
||||||
|
let leading_space = if context.needs_leading_space { " " } else { "" };
|
||||||
|
CompletionItem {
|
||||||
|
label: tag.name().to_string(),
|
||||||
|
kind: Some(CompletionItemKind::KEYWORD),
|
||||||
|
detail: Some(format!("Template tag from {}", tag.library())),
|
||||||
|
documentation: tag.doc().as_ref().map(|doc| {
|
||||||
|
Documentation::MarkupContent(MarkupContent {
|
||||||
|
kind: MarkupKind::Markdown,
|
||||||
|
value: doc.to_string(),
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
insert_text: Some(match context.closing_brace {
|
||||||
|
ClosingBrace::None => format!("{}{} %}}", leading_space, tag.name()),
|
||||||
|
ClosingBrace::PartialClose => format!("{}{} %", leading_space, tag.name()),
|
||||||
|
ClosingBrace::FullClose => format!("{}{} ", leading_space, tag.name()),
|
||||||
|
}),
|
||||||
|
insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if completions.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
completions.sort_by(|a, b| a.label.cmp(&b.label));
|
||||||
|
Some(CompletionResponse::Array(completions))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -181,6 +233,32 @@ impl TextDocument {
|
||||||
pub fn line_count(&self) -> usize {
|
pub fn line_count(&self) -> usize {
|
||||||
self.index.line_starts.len()
|
self.index.line_starts.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_template_tag_context(&self, position: Position) -> Option<TemplateTagContext> {
|
||||||
|
let line = self.get_line(position.line.try_into().ok()?)?;
|
||||||
|
let prefix = &line[..position.character.try_into().ok()?];
|
||||||
|
let rest_of_line = &line[position.character.try_into().ok()?..];
|
||||||
|
let rest_trimmed = rest_of_line.trim_start();
|
||||||
|
|
||||||
|
prefix.rfind("{%").map(|tag_start| {
|
||||||
|
// Check if we're immediately after {% with no space
|
||||||
|
let needs_leading_space = prefix.ends_with("{%");
|
||||||
|
|
||||||
|
let closing_brace = if rest_trimmed.starts_with("%}") {
|
||||||
|
ClosingBrace::FullClose
|
||||||
|
} else if rest_trimmed.starts_with("}") {
|
||||||
|
ClosingBrace::PartialClose
|
||||||
|
} else {
|
||||||
|
ClosingBrace::None
|
||||||
|
};
|
||||||
|
|
||||||
|
TemplateTagContext {
|
||||||
|
partial_tag: prefix[tag_start + 2..].trim().to_string(),
|
||||||
|
closing_brace,
|
||||||
|
needs_leading_space,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -248,3 +326,17 @@ impl From<String> for LanguageId {
|
||||||
Self::from(language_id.as_str())
|
Self::from(language_id.as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ClosingBrace {
|
||||||
|
None,
|
||||||
|
PartialClose, // just }
|
||||||
|
FullClose, // %}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TemplateTagContext {
|
||||||
|
pub partial_tag: String,
|
||||||
|
pub closing_brace: ClosingBrace,
|
||||||
|
pub needs_leading_space: bool,
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ mod tasks;
|
||||||
use crate::notifier::TowerLspNotifier;
|
use crate::notifier::TowerLspNotifier;
|
||||||
use crate::server::{DjangoLanguageServer, LspNotification, LspRequest};
|
use crate::server::{DjangoLanguageServer, LspNotification, LspRequest};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use server::LspResponse;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use tower_lsp::jsonrpc::Result as LspResult;
|
use tower_lsp::jsonrpc::Result as LspResult;
|
||||||
|
@ -19,11 +20,16 @@ struct TowerLspBackend {
|
||||||
#[tower_lsp::async_trait]
|
#[tower_lsp::async_trait]
|
||||||
impl LanguageServer for TowerLspBackend {
|
impl LanguageServer for TowerLspBackend {
|
||||||
async fn initialize(&self, params: InitializeParams) -> LspResult<InitializeResult> {
|
async fn initialize(&self, params: InitializeParams) -> LspResult<InitializeResult> {
|
||||||
self.server
|
match self
|
||||||
.read()
|
.server
|
||||||
|
.write()
|
||||||
.await
|
.await
|
||||||
.handle_request(LspRequest::Initialize(params))
|
.handle_request(LspRequest::Initialize(params))
|
||||||
.map_err(|_| tower_lsp::jsonrpc::Error::internal_error())
|
.map_err(|_| tower_lsp::jsonrpc::Error::internal_error())?
|
||||||
|
{
|
||||||
|
LspResponse::Initialize(result) => Ok(result),
|
||||||
|
_ => Err(tower_lsp::jsonrpc::Error::internal_error()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn initialized(&self, params: InitializedParams) {
|
async fn initialized(&self, params: InitializedParams) {
|
||||||
|
@ -77,6 +83,19 @@ impl LanguageServer for TowerLspBackend {
|
||||||
eprintln!("Error handling document close: {}", e);
|
eprintln!("Error handling document close: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn completion(&self, params: CompletionParams) -> LspResult<Option<CompletionResponse>> {
|
||||||
|
match self
|
||||||
|
.server
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.handle_request(LspRequest::Completion(params))
|
||||||
|
.map_err(|_| tower_lsp::jsonrpc::Error::internal_error())?
|
||||||
|
{
|
||||||
|
LspResponse::Completion(result) => Ok(result),
|
||||||
|
_ => Err(tower_lsp::jsonrpc::Error::internal_error()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn serve() -> Result<()> {
|
pub async fn serve() -> Result<()> {
|
||||||
|
|
|
@ -2,7 +2,10 @@ use crate::documents::Store;
|
||||||
use crate::notifier::Notifier;
|
use crate::notifier::Notifier;
|
||||||
use crate::tasks::DebugTask;
|
use crate::tasks::DebugTask;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use djls_project::DjangoProject;
|
||||||
use djls_worker::Worker;
|
use djls_worker::Worker;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tower_lsp::lsp_types::*;
|
use tower_lsp::lsp_types::*;
|
||||||
|
@ -12,6 +15,12 @@ const SERVER_VERSION: &str = "0.1.0";
|
||||||
|
|
||||||
pub enum LspRequest {
|
pub enum LspRequest {
|
||||||
Initialize(InitializeParams),
|
Initialize(InitializeParams),
|
||||||
|
Completion(CompletionParams),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum LspResponse {
|
||||||
|
Initialize(InitializeResult),
|
||||||
|
Completion(Option<CompletionResponse>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum LspNotification {
|
pub enum LspNotification {
|
||||||
|
@ -23,6 +32,7 @@ pub enum LspNotification {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DjangoLanguageServer {
|
pub struct DjangoLanguageServer {
|
||||||
|
project: Option<DjangoProject>,
|
||||||
notifier: Arc<Box<dyn Notifier>>,
|
notifier: Arc<Box<dyn Notifier>>,
|
||||||
documents: Store,
|
documents: Store,
|
||||||
worker: Worker,
|
worker: Worker,
|
||||||
|
@ -33,33 +43,77 @@ impl DjangoLanguageServer {
|
||||||
let notifier = Arc::new(notifier);
|
let notifier = Arc::new(notifier);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
project: None,
|
||||||
notifier,
|
notifier,
|
||||||
documents: Store::new(),
|
documents: Store::new(),
|
||||||
worker: Worker::new(),
|
worker: Worker::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_request(&self, request: LspRequest) -> Result<InitializeResult> {
|
pub fn handle_request(&mut self, request: LspRequest) -> Result<LspResponse> {
|
||||||
match request {
|
match request {
|
||||||
LspRequest::Initialize(_params) => Ok(InitializeResult {
|
LspRequest::Initialize(params) => {
|
||||||
capabilities: ServerCapabilities {
|
if let Some(mut project) = DjangoProject::from_initialize_params(¶ms) {
|
||||||
text_document_sync: Some(TextDocumentSyncCapability::Options(
|
if let Err(e) = project.initialize() {
|
||||||
TextDocumentSyncOptions {
|
self.notifier.log_message(
|
||||||
open_close: Some(true),
|
MessageType::ERROR,
|
||||||
change: Some(TextDocumentSyncKind::INCREMENTAL),
|
&format!("Failed to initialize Django project: {}", e),
|
||||||
will_save: Some(false),
|
)?;
|
||||||
will_save_wait_until: Some(false),
|
} else {
|
||||||
save: Some(SaveOptions::default().into()),
|
self.notifier.log_message(
|
||||||
},
|
MessageType::INFO,
|
||||||
)),
|
&format!("Using project path: {}", project.path().display()),
|
||||||
..Default::default()
|
)?;
|
||||||
},
|
self.project = Some(project);
|
||||||
offset_encoding: None,
|
}
|
||||||
server_info: Some(ServerInfo {
|
}
|
||||||
name: SERVER_NAME.to_string(),
|
|
||||||
version: Some(SERVER_VERSION.to_string()),
|
Ok(LspResponse::Initialize(InitializeResult {
|
||||||
}),
|
capabilities: ServerCapabilities {
|
||||||
}),
|
completion_provider: Some(CompletionOptions {
|
||||||
|
resolve_provider: Some(false),
|
||||||
|
trigger_characters: Some(vec![
|
||||||
|
"{".to_string(),
|
||||||
|
"%".to_string(),
|
||||||
|
" ".to_string(),
|
||||||
|
]),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
text_document_sync: Some(TextDocumentSyncCapability::Options(
|
||||||
|
TextDocumentSyncOptions {
|
||||||
|
open_close: Some(true),
|
||||||
|
change: Some(TextDocumentSyncKind::INCREMENTAL),
|
||||||
|
will_save: Some(false),
|
||||||
|
will_save_wait_until: Some(false),
|
||||||
|
save: Some(SaveOptions::default().into()),
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
offset_encoding: None,
|
||||||
|
server_info: Some(ServerInfo {
|
||||||
|
name: SERVER_NAME.to_string(),
|
||||||
|
version: Some(SERVER_VERSION.to_string()),
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
LspRequest::Completion(params) => {
|
||||||
|
let completions = if let Some(project) = &self.project {
|
||||||
|
if let Some(tags) = project.template_tags() {
|
||||||
|
self.documents.get_completions(
|
||||||
|
params.text_document_position.text_document.uri.as_str(),
|
||||||
|
params.text_document_position.position,
|
||||||
|
tags,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(LspResponse::Completion(completions))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,36 +125,6 @@ impl DjangoLanguageServer {
|
||||||
MessageType::INFO,
|
MessageType::INFO,
|
||||||
&format!("Opened document: {}", params.text_document.uri),
|
&format!("Opened document: {}", params.text_document.uri),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Execute - still sync
|
|
||||||
self.worker.execute(DebugTask::new(
|
|
||||||
"Quick task".to_string(),
|
|
||||||
Duration::from_millis(100),
|
|
||||||
self.notifier.clone(),
|
|
||||||
))?;
|
|
||||||
|
|
||||||
// Submit - spawn async task
|
|
||||||
let worker = self.worker.clone();
|
|
||||||
let task = DebugTask::new(
|
|
||||||
"Important task".to_string(),
|
|
||||||
Duration::from_secs(1),
|
|
||||||
self.notifier.clone(),
|
|
||||||
);
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let _ = worker.submit(task).await;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wait for result - spawn async task
|
|
||||||
let worker = self.worker.clone();
|
|
||||||
let task = DebugTask::new(
|
|
||||||
"Task with result".to_string(),
|
|
||||||
Duration::from_secs(2),
|
|
||||||
self.notifier.clone(),
|
|
||||||
);
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let _ = worker.wait_for(task).await;
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
LspNotification::DidChangeTextDocument(params) => {
|
LspNotification::DidChangeTextDocument(params) => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue