Add Vue language server v3 support (#40651)

Closes https://github.com/zed-extensions/vue/issues/48

Migration guide:
https://github.com/vuejs/language-tools/discussions/5456

PR to remove tdsk: https://github.com/zed-extensions/vue/pull/61

Release Notes:

- Added support for Vue language server version 3. Know more
[here](https://github.com/vuejs/language-tools/releases/tag/v3.0.0).

---------

Co-authored-by: MrSubidubi <dev@bahn.sh>
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
This commit is contained in:
Smit Barmase
2025-10-20 13:32:31 +05:30
committed by GitHub
co-authored by MrSubidubi Piotr Osiewicz
parent 1d3bf9789e
commit 92ff29fa7d
3 changed files with 127 additions and 1 deletions
+1 -1
View File
@@ -1821,7 +1821,7 @@
"use_on_type_format": false
},
"Vue.js": {
"language_servers": ["vue-language-server", "..."],
"language_servers": ["vue-language-server", "vtsls", "..."],
"prettier": {
"allowed": true
}
+2
View File
@@ -14,6 +14,7 @@ pub mod json_language_server_ext;
pub mod log_store;
pub mod lsp_ext_command;
pub mod rust_analyzer_ext;
pub mod vue_language_server_ext;
use crate::{
CodeAction, ColorPresentation, Completion, CompletionDisplayOptions, CompletionResponse,
@@ -987,6 +988,7 @@ impl LocalLspStore {
})
.detach();
vue_language_server_ext::register_requests(this.clone(), language_server);
json_language_server_ext::register_requests(this.clone(), language_server);
rust_analyzer_ext::register_notifications(this.clone(), language_server);
clangd_ext::register_notifications(this, language_server, adapter);
@@ -0,0 +1,124 @@
use anyhow::Context as _;
use gpui::{AppContext, WeakEntity};
use lsp::{LanguageServer, LanguageServerName};
use serde_json::Value;
use crate::LspStore;
struct VueServerRequest;
struct TypescriptServerResponse;
impl lsp::notification::Notification for VueServerRequest {
type Params = Vec<(u64, String, serde_json::Value)>;
const METHOD: &'static str = "tsserver/request";
}
impl lsp::notification::Notification for TypescriptServerResponse {
type Params = Vec<(u64, serde_json::Value)>;
const METHOD: &'static str = "tsserver/response";
}
const VUE_SERVER_NAME: LanguageServerName = LanguageServerName::new_static("vue-language-server");
const VTSLS: LanguageServerName = LanguageServerName::new_static("vtsls");
const TS_LS: LanguageServerName = LanguageServerName::new_static("typescript-language-server");
pub fn register_requests(lsp_store: WeakEntity<LspStore>, language_server: &LanguageServer) {
let language_server_name = language_server.name();
if language_server_name == VUE_SERVER_NAME {
let vue_server_id = language_server.server_id();
language_server
.on_notification::<VueServerRequest, _>({
move |params, cx| {
let lsp_store = lsp_store.clone();
let Ok(Some(vue_server)) = lsp_store.read_with(cx, |this, _| {
this.language_server_for_id(vue_server_id)
}) else {
return;
};
let requests = params;
let target_server = match lsp_store.read_with(cx, |this, _| {
let language_server_id = this
.as_local()
.and_then(|local| {
local.language_server_ids.iter().find_map(|(seed, v)| {
[VTSLS, TS_LS].contains(&seed.name).then_some(v.id)
})
})
.context("Could not find language server")?;
this.language_server_for_id(language_server_id)
.context("language server not found")
}) {
Ok(Ok(server)) => server,
other => {
log::warn!(
"vue-language-server forwarding skipped: {other:?}. \
Returning null tsserver responses"
);
if !requests.is_empty() {
let null_responses = requests
.into_iter()
.map(|(id, _, _)| (id, Value::Null))
.collect::<Vec<_>>();
let _ = vue_server
.notify::<TypescriptServerResponse>(null_responses);
}
return;
}
};
let cx = cx.clone();
for (request_id, command, payload) in requests.into_iter() {
let target_server = target_server.clone();
let vue_server = vue_server.clone();
cx.background_spawn(async move {
let response = target_server
.request::<lsp::request::ExecuteCommand>(
lsp::ExecuteCommandParams {
command: "typescript.tsserverRequest".to_owned(),
arguments: vec![Value::String(command), payload],
..Default::default()
},
)
.await;
let response_body = match response {
util::ConnectionResult::Result(Ok(result)) => match result {
Some(Value::Object(mut map)) => map
.remove("body")
.unwrap_or(Value::Object(map)),
Some(other) => other,
None => Value::Null,
},
util::ConnectionResult::Result(Err(error)) => {
log::warn!(
"typescript.tsserverRequest failed: {error:?} for request {request_id}"
);
Value::Null
}
other => {
log::warn!(
"typescript.tsserverRequest did not return a response: {other:?} for request {request_id}"
);
Value::Null
}
};
if let Err(err) = vue_server
.notify::<TypescriptServerResponse>(vec![(request_id, response_body)])
{
log::warn!(
"Failed to notify vue-language-server of tsserver response: {err:?}"
);
}
})
.detach();
}
}
})
.detach();
}
}