
Closes https://github.com/zed-industries/zed/issues/33522 Turns out a bunch of Zed requests were not checking their capabilities correctly, due to odd copy-paste and due to default that assumed that the capabilities are met. Adjust the code, which includes the document colors, add the test on the colors case. Release Notes: - Fixed excessive document colors requests for unrelated files
805 lines
24 KiB
Rust
805 lines
24 KiB
Rust
use crate::{
|
|
LocationLink,
|
|
lsp_command::{
|
|
LspCommand, file_path_to_lsp_url, location_link_from_lsp, location_link_from_proto,
|
|
location_link_to_proto, location_links_from_lsp, location_links_from_proto,
|
|
location_links_to_proto,
|
|
},
|
|
lsp_store::LspStore,
|
|
make_lsp_text_document_position, make_text_document_identifier,
|
|
};
|
|
use anyhow::{Context as _, Result};
|
|
use async_trait::async_trait;
|
|
use collections::HashMap;
|
|
use gpui::{App, AsyncApp, Entity};
|
|
use language::{
|
|
Buffer, point_to_lsp,
|
|
proto::{deserialize_anchor, serialize_anchor},
|
|
};
|
|
use lsp::{AdapterServerCapabilities, LanguageServer, LanguageServerId};
|
|
use rpc::proto::{self, PeerId};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::{
|
|
path::{Path, PathBuf},
|
|
sync::Arc,
|
|
};
|
|
use task::TaskTemplate;
|
|
use text::{BufferId, PointUtf16, ToPointUtf16};
|
|
|
|
pub enum LspExtExpandMacro {}
|
|
|
|
impl lsp::request::Request for LspExtExpandMacro {
|
|
type Params = ExpandMacroParams;
|
|
type Result = Option<ExpandedMacro>;
|
|
const METHOD: &'static str = "rust-analyzer/expandMacro";
|
|
}
|
|
|
|
#[derive(Deserialize, Serialize, Debug)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct ExpandMacroParams {
|
|
pub text_document: lsp::TextDocumentIdentifier,
|
|
pub position: lsp::Position,
|
|
}
|
|
|
|
#[derive(Default, Deserialize, Serialize, Debug)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct ExpandedMacro {
|
|
pub name: String,
|
|
pub expansion: String,
|
|
}
|
|
|
|
impl ExpandedMacro {
|
|
pub fn is_empty(&self) -> bool {
|
|
self.name.is_empty() && self.expansion.is_empty()
|
|
}
|
|
}
|
|
#[derive(Debug)]
|
|
pub struct ExpandMacro {
|
|
pub position: PointUtf16,
|
|
}
|
|
|
|
#[async_trait(?Send)]
|
|
impl LspCommand for ExpandMacro {
|
|
type Response = ExpandedMacro;
|
|
type LspRequest = LspExtExpandMacro;
|
|
type ProtoRequest = proto::LspExtExpandMacro;
|
|
|
|
fn display_name(&self) -> &str {
|
|
"Expand macro"
|
|
}
|
|
|
|
fn check_capabilities(&self, _: AdapterServerCapabilities) -> bool {
|
|
true
|
|
}
|
|
|
|
fn to_lsp(
|
|
&self,
|
|
path: &Path,
|
|
_: &Buffer,
|
|
_: &Arc<LanguageServer>,
|
|
_: &App,
|
|
) -> Result<ExpandMacroParams> {
|
|
Ok(ExpandMacroParams {
|
|
text_document: make_text_document_identifier(path)?,
|
|
position: point_to_lsp(self.position),
|
|
})
|
|
}
|
|
|
|
async fn response_from_lsp(
|
|
self,
|
|
message: Option<ExpandedMacro>,
|
|
_: Entity<LspStore>,
|
|
_: Entity<Buffer>,
|
|
_: LanguageServerId,
|
|
_: AsyncApp,
|
|
) -> anyhow::Result<ExpandedMacro> {
|
|
Ok(message
|
|
.map(|message| ExpandedMacro {
|
|
name: message.name,
|
|
expansion: message.expansion,
|
|
})
|
|
.unwrap_or_default())
|
|
}
|
|
|
|
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtExpandMacro {
|
|
proto::LspExtExpandMacro {
|
|
project_id,
|
|
buffer_id: buffer.remote_id().into(),
|
|
position: Some(language::proto::serialize_anchor(
|
|
&buffer.anchor_before(self.position),
|
|
)),
|
|
}
|
|
}
|
|
|
|
async fn from_proto(
|
|
message: Self::ProtoRequest,
|
|
_: Entity<LspStore>,
|
|
buffer: Entity<Buffer>,
|
|
mut cx: AsyncApp,
|
|
) -> anyhow::Result<Self> {
|
|
let position = message
|
|
.position
|
|
.and_then(deserialize_anchor)
|
|
.context("invalid position")?;
|
|
Ok(Self {
|
|
position: buffer.read_with(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
|
|
})
|
|
}
|
|
|
|
fn response_to_proto(
|
|
response: ExpandedMacro,
|
|
_: &mut LspStore,
|
|
_: PeerId,
|
|
_: &clock::Global,
|
|
_: &mut App,
|
|
) -> proto::LspExtExpandMacroResponse {
|
|
proto::LspExtExpandMacroResponse {
|
|
name: response.name,
|
|
expansion: response.expansion,
|
|
}
|
|
}
|
|
|
|
async fn response_from_proto(
|
|
self,
|
|
message: proto::LspExtExpandMacroResponse,
|
|
_: Entity<LspStore>,
|
|
_: Entity<Buffer>,
|
|
_: AsyncApp,
|
|
) -> anyhow::Result<ExpandedMacro> {
|
|
Ok(ExpandedMacro {
|
|
name: message.name,
|
|
expansion: message.expansion,
|
|
})
|
|
}
|
|
|
|
fn buffer_id_from_proto(message: &proto::LspExtExpandMacro) -> Result<BufferId> {
|
|
BufferId::new(message.buffer_id)
|
|
}
|
|
}
|
|
|
|
pub enum LspOpenDocs {}
|
|
|
|
impl lsp::request::Request for LspOpenDocs {
|
|
type Params = OpenDocsParams;
|
|
type Result = Option<DocsUrls>;
|
|
const METHOD: &'static str = "experimental/externalDocs";
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct OpenDocsParams {
|
|
pub text_document: lsp::TextDocumentIdentifier,
|
|
pub position: lsp::Position,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Default)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct DocsUrls {
|
|
pub web: Option<String>,
|
|
pub local: Option<String>,
|
|
}
|
|
|
|
impl DocsUrls {
|
|
pub fn is_empty(&self) -> bool {
|
|
self.web.is_none() && self.local.is_none()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct OpenDocs {
|
|
pub position: PointUtf16,
|
|
}
|
|
|
|
#[async_trait(?Send)]
|
|
impl LspCommand for OpenDocs {
|
|
type Response = DocsUrls;
|
|
type LspRequest = LspOpenDocs;
|
|
type ProtoRequest = proto::LspExtOpenDocs;
|
|
|
|
fn display_name(&self) -> &str {
|
|
"Open docs"
|
|
}
|
|
|
|
fn check_capabilities(&self, _: AdapterServerCapabilities) -> bool {
|
|
true
|
|
}
|
|
|
|
fn to_lsp(
|
|
&self,
|
|
path: &Path,
|
|
_: &Buffer,
|
|
_: &Arc<LanguageServer>,
|
|
_: &App,
|
|
) -> Result<OpenDocsParams> {
|
|
Ok(OpenDocsParams {
|
|
text_document: lsp::TextDocumentIdentifier {
|
|
uri: lsp::Url::from_file_path(path).unwrap(),
|
|
},
|
|
position: point_to_lsp(self.position),
|
|
})
|
|
}
|
|
|
|
async fn response_from_lsp(
|
|
self,
|
|
message: Option<DocsUrls>,
|
|
_: Entity<LspStore>,
|
|
_: Entity<Buffer>,
|
|
_: LanguageServerId,
|
|
_: AsyncApp,
|
|
) -> anyhow::Result<DocsUrls> {
|
|
Ok(message
|
|
.map(|message| DocsUrls {
|
|
web: message.web,
|
|
local: message.local,
|
|
})
|
|
.unwrap_or_default())
|
|
}
|
|
|
|
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtOpenDocs {
|
|
proto::LspExtOpenDocs {
|
|
project_id,
|
|
buffer_id: buffer.remote_id().into(),
|
|
position: Some(language::proto::serialize_anchor(
|
|
&buffer.anchor_before(self.position),
|
|
)),
|
|
}
|
|
}
|
|
|
|
async fn from_proto(
|
|
message: Self::ProtoRequest,
|
|
_: Entity<LspStore>,
|
|
buffer: Entity<Buffer>,
|
|
mut cx: AsyncApp,
|
|
) -> anyhow::Result<Self> {
|
|
let position = message
|
|
.position
|
|
.and_then(deserialize_anchor)
|
|
.context("invalid position")?;
|
|
Ok(Self {
|
|
position: buffer.read_with(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
|
|
})
|
|
}
|
|
|
|
fn response_to_proto(
|
|
response: DocsUrls,
|
|
_: &mut LspStore,
|
|
_: PeerId,
|
|
_: &clock::Global,
|
|
_: &mut App,
|
|
) -> proto::LspExtOpenDocsResponse {
|
|
proto::LspExtOpenDocsResponse {
|
|
web: response.web,
|
|
local: response.local,
|
|
}
|
|
}
|
|
|
|
async fn response_from_proto(
|
|
self,
|
|
message: proto::LspExtOpenDocsResponse,
|
|
_: Entity<LspStore>,
|
|
_: Entity<Buffer>,
|
|
_: AsyncApp,
|
|
) -> anyhow::Result<DocsUrls> {
|
|
Ok(DocsUrls {
|
|
web: message.web,
|
|
local: message.local,
|
|
})
|
|
}
|
|
|
|
fn buffer_id_from_proto(message: &proto::LspExtOpenDocs) -> Result<BufferId> {
|
|
BufferId::new(message.buffer_id)
|
|
}
|
|
}
|
|
|
|
pub enum LspSwitchSourceHeader {}
|
|
|
|
impl lsp::request::Request for LspSwitchSourceHeader {
|
|
type Params = SwitchSourceHeaderParams;
|
|
type Result = Option<SwitchSourceHeaderResult>;
|
|
const METHOD: &'static str = "textDocument/switchSourceHeader";
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct SwitchSourceHeaderParams(lsp::TextDocumentIdentifier);
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Default)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct SwitchSourceHeaderResult(pub String);
|
|
|
|
#[derive(Default, Deserialize, Serialize, Debug)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct SwitchSourceHeader;
|
|
|
|
#[derive(Debug)]
|
|
pub struct GoToParentModule {
|
|
pub position: PointUtf16,
|
|
}
|
|
|
|
pub struct LspGoToParentModule {}
|
|
|
|
impl lsp::request::Request for LspGoToParentModule {
|
|
type Params = lsp::TextDocumentPositionParams;
|
|
type Result = Option<Vec<lsp::LocationLink>>;
|
|
const METHOD: &'static str = "experimental/parentModule";
|
|
}
|
|
|
|
#[async_trait(?Send)]
|
|
impl LspCommand for SwitchSourceHeader {
|
|
type Response = SwitchSourceHeaderResult;
|
|
type LspRequest = LspSwitchSourceHeader;
|
|
type ProtoRequest = proto::LspExtSwitchSourceHeader;
|
|
|
|
fn display_name(&self) -> &str {
|
|
"Switch source header"
|
|
}
|
|
|
|
fn check_capabilities(&self, _: AdapterServerCapabilities) -> bool {
|
|
true
|
|
}
|
|
|
|
fn to_lsp(
|
|
&self,
|
|
path: &Path,
|
|
_: &Buffer,
|
|
_: &Arc<LanguageServer>,
|
|
_: &App,
|
|
) -> Result<SwitchSourceHeaderParams> {
|
|
Ok(SwitchSourceHeaderParams(make_text_document_identifier(
|
|
path,
|
|
)?))
|
|
}
|
|
|
|
async fn response_from_lsp(
|
|
self,
|
|
message: Option<SwitchSourceHeaderResult>,
|
|
_: Entity<LspStore>,
|
|
_: Entity<Buffer>,
|
|
_: LanguageServerId,
|
|
_: AsyncApp,
|
|
) -> anyhow::Result<SwitchSourceHeaderResult> {
|
|
Ok(message
|
|
.map(|message| SwitchSourceHeaderResult(message.0))
|
|
.unwrap_or_default())
|
|
}
|
|
|
|
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtSwitchSourceHeader {
|
|
proto::LspExtSwitchSourceHeader {
|
|
project_id,
|
|
buffer_id: buffer.remote_id().into(),
|
|
}
|
|
}
|
|
|
|
async fn from_proto(
|
|
_: Self::ProtoRequest,
|
|
_: Entity<LspStore>,
|
|
_: Entity<Buffer>,
|
|
_: AsyncApp,
|
|
) -> anyhow::Result<Self> {
|
|
Ok(Self {})
|
|
}
|
|
|
|
fn response_to_proto(
|
|
response: SwitchSourceHeaderResult,
|
|
_: &mut LspStore,
|
|
_: PeerId,
|
|
_: &clock::Global,
|
|
_: &mut App,
|
|
) -> proto::LspExtSwitchSourceHeaderResponse {
|
|
proto::LspExtSwitchSourceHeaderResponse {
|
|
target_file: response.0,
|
|
}
|
|
}
|
|
|
|
async fn response_from_proto(
|
|
self,
|
|
message: proto::LspExtSwitchSourceHeaderResponse,
|
|
_: Entity<LspStore>,
|
|
_: Entity<Buffer>,
|
|
_: AsyncApp,
|
|
) -> anyhow::Result<SwitchSourceHeaderResult> {
|
|
Ok(SwitchSourceHeaderResult(message.target_file))
|
|
}
|
|
|
|
fn buffer_id_from_proto(message: &proto::LspExtSwitchSourceHeader) -> Result<BufferId> {
|
|
BufferId::new(message.buffer_id)
|
|
}
|
|
}
|
|
|
|
#[async_trait(?Send)]
|
|
impl LspCommand for GoToParentModule {
|
|
type Response = Vec<LocationLink>;
|
|
type LspRequest = LspGoToParentModule;
|
|
type ProtoRequest = proto::LspExtGoToParentModule;
|
|
|
|
fn display_name(&self) -> &str {
|
|
"Go to parent module"
|
|
}
|
|
|
|
fn check_capabilities(&self, _: AdapterServerCapabilities) -> bool {
|
|
true
|
|
}
|
|
|
|
fn to_lsp(
|
|
&self,
|
|
path: &Path,
|
|
_: &Buffer,
|
|
_: &Arc<LanguageServer>,
|
|
_: &App,
|
|
) -> Result<lsp::TextDocumentPositionParams> {
|
|
make_lsp_text_document_position(path, self.position)
|
|
}
|
|
|
|
async fn response_from_lsp(
|
|
self,
|
|
links: Option<Vec<lsp::LocationLink>>,
|
|
lsp_store: Entity<LspStore>,
|
|
buffer: Entity<Buffer>,
|
|
server_id: LanguageServerId,
|
|
cx: AsyncApp,
|
|
) -> anyhow::Result<Vec<LocationLink>> {
|
|
location_links_from_lsp(
|
|
links.map(lsp::GotoDefinitionResponse::Link),
|
|
lsp_store,
|
|
buffer,
|
|
server_id,
|
|
cx,
|
|
)
|
|
.await
|
|
}
|
|
|
|
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtGoToParentModule {
|
|
proto::LspExtGoToParentModule {
|
|
project_id,
|
|
buffer_id: buffer.remote_id().to_proto(),
|
|
position: Some(language::proto::serialize_anchor(
|
|
&buffer.anchor_before(self.position),
|
|
)),
|
|
}
|
|
}
|
|
|
|
async fn from_proto(
|
|
request: Self::ProtoRequest,
|
|
_: Entity<LspStore>,
|
|
buffer: Entity<Buffer>,
|
|
mut cx: AsyncApp,
|
|
) -> anyhow::Result<Self> {
|
|
let position = request
|
|
.position
|
|
.and_then(deserialize_anchor)
|
|
.context("bad request with bad position")?;
|
|
Ok(Self {
|
|
position: buffer.read_with(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
|
|
})
|
|
}
|
|
|
|
fn response_to_proto(
|
|
links: Vec<LocationLink>,
|
|
lsp_store: &mut LspStore,
|
|
peer_id: PeerId,
|
|
_: &clock::Global,
|
|
cx: &mut App,
|
|
) -> proto::LspExtGoToParentModuleResponse {
|
|
proto::LspExtGoToParentModuleResponse {
|
|
links: location_links_to_proto(links, lsp_store, peer_id, cx),
|
|
}
|
|
}
|
|
|
|
async fn response_from_proto(
|
|
self,
|
|
message: proto::LspExtGoToParentModuleResponse,
|
|
lsp_store: Entity<LspStore>,
|
|
_: Entity<Buffer>,
|
|
cx: AsyncApp,
|
|
) -> anyhow::Result<Vec<LocationLink>> {
|
|
location_links_from_proto(message.links, lsp_store, cx).await
|
|
}
|
|
|
|
fn buffer_id_from_proto(message: &proto::LspExtGoToParentModule) -> Result<BufferId> {
|
|
BufferId::new(message.buffer_id)
|
|
}
|
|
}
|
|
|
|
// https://rust-analyzer.github.io/book/contributing/lsp-extensions.html#runnables
|
|
// Taken from https://github.com/rust-lang/rust-analyzer/blob/a73a37a757a58b43a796d3eb86a1f7dfd0036659/crates/rust-analyzer/src/lsp/ext.rs#L425-L489
|
|
pub enum Runnables {}
|
|
|
|
impl lsp::request::Request for Runnables {
|
|
type Params = RunnablesParams;
|
|
type Result = Vec<Runnable>;
|
|
const METHOD: &'static str = "experimental/runnables";
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct RunnablesParams {
|
|
pub text_document: lsp::TextDocumentIdentifier,
|
|
#[serde(default)]
|
|
pub position: Option<lsp::Position>,
|
|
}
|
|
|
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Runnable {
|
|
pub label: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub location: Option<lsp::LocationLink>,
|
|
pub kind: RunnableKind,
|
|
pub args: RunnableArgs,
|
|
}
|
|
|
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[serde(untagged)]
|
|
pub enum RunnableArgs {
|
|
Cargo(CargoRunnableArgs),
|
|
Shell(ShellRunnableArgs),
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum RunnableKind {
|
|
Cargo,
|
|
Shell,
|
|
}
|
|
|
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct CargoRunnableArgs {
|
|
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
|
pub environment: HashMap<String, String>,
|
|
pub cwd: PathBuf,
|
|
/// Command to be executed instead of cargo
|
|
#[serde(default)]
|
|
pub override_cargo: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub workspace_root: Option<PathBuf>,
|
|
// command, --package and --lib stuff
|
|
#[serde(default)]
|
|
pub cargo_args: Vec<String>,
|
|
// stuff after --
|
|
#[serde(default)]
|
|
pub executable_args: Vec<String>,
|
|
}
|
|
|
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct ShellRunnableArgs {
|
|
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
|
pub environment: HashMap<String, String>,
|
|
pub cwd: PathBuf,
|
|
pub program: String,
|
|
#[serde(default)]
|
|
pub args: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct GetLspRunnables {
|
|
pub buffer_id: BufferId,
|
|
pub position: Option<text::Anchor>,
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
pub struct LspRunnables {
|
|
pub runnables: Vec<(Option<LocationLink>, TaskTemplate)>,
|
|
}
|
|
|
|
#[async_trait(?Send)]
|
|
impl LspCommand for GetLspRunnables {
|
|
type Response = LspRunnables;
|
|
type LspRequest = Runnables;
|
|
type ProtoRequest = proto::LspExtRunnables;
|
|
|
|
fn display_name(&self) -> &str {
|
|
"LSP Runnables"
|
|
}
|
|
|
|
fn check_capabilities(&self, _: AdapterServerCapabilities) -> bool {
|
|
true
|
|
}
|
|
|
|
fn to_lsp(
|
|
&self,
|
|
path: &Path,
|
|
buffer: &Buffer,
|
|
_: &Arc<LanguageServer>,
|
|
_: &App,
|
|
) -> Result<RunnablesParams> {
|
|
let url = file_path_to_lsp_url(path)?;
|
|
Ok(RunnablesParams {
|
|
text_document: lsp::TextDocumentIdentifier::new(url),
|
|
position: self
|
|
.position
|
|
.map(|anchor| point_to_lsp(anchor.to_point_utf16(&buffer.snapshot()))),
|
|
})
|
|
}
|
|
|
|
async fn response_from_lsp(
|
|
self,
|
|
lsp_runnables: Vec<Runnable>,
|
|
lsp_store: Entity<LspStore>,
|
|
buffer: Entity<Buffer>,
|
|
server_id: LanguageServerId,
|
|
mut cx: AsyncApp,
|
|
) -> Result<LspRunnables> {
|
|
let mut runnables = Vec::with_capacity(lsp_runnables.len());
|
|
|
|
for runnable in lsp_runnables {
|
|
let location = match runnable.location {
|
|
Some(location) => Some(
|
|
location_link_from_lsp(location, &lsp_store, &buffer, server_id, &mut cx)
|
|
.await?,
|
|
),
|
|
None => None,
|
|
};
|
|
let mut task_template = TaskTemplate::default();
|
|
task_template.label = runnable.label;
|
|
match runnable.args {
|
|
RunnableArgs::Cargo(cargo) => {
|
|
match cargo.override_cargo {
|
|
Some(override_cargo) => {
|
|
let mut override_parts =
|
|
override_cargo.split(" ").map(|s| s.to_string());
|
|
task_template.command = override_parts
|
|
.next()
|
|
.unwrap_or_else(|| override_cargo.clone());
|
|
task_template.args.extend(override_parts);
|
|
}
|
|
None => task_template.command = "cargo".to_string(),
|
|
};
|
|
task_template.env = cargo.environment;
|
|
task_template.cwd = Some(
|
|
cargo
|
|
.workspace_root
|
|
.unwrap_or(cargo.cwd)
|
|
.to_string_lossy()
|
|
.to_string(),
|
|
);
|
|
task_template.args.extend(cargo.cargo_args);
|
|
if !cargo.executable_args.is_empty() {
|
|
task_template.args.push("--".to_string());
|
|
task_template.args.extend(
|
|
cargo
|
|
.executable_args
|
|
.into_iter()
|
|
// rust-analyzer's doctest data may be smth. like
|
|
// ```
|
|
// command: "cargo",
|
|
// args: [
|
|
// "test",
|
|
// "--doc",
|
|
// "--package",
|
|
// "cargo-output-parser",
|
|
// "--",
|
|
// "X<T>::new",
|
|
// "--show-output",
|
|
// ],
|
|
// ```
|
|
// and `X<T>::new` will cause troubles if not escaped properly, as later
|
|
// the task runs as `$SHELL -i -c "cargo test ..."`.
|
|
//
|
|
// We cannot escape all shell arguments unconditionally, as we use this for ssh commands, which may involve paths starting with `~`.
|
|
// That bit is not auto-expanded when using single quotes.
|
|
// Escape extra cargo args unconditionally as those are unlikely to contain `~`.
|
|
.flat_map(|extra_arg| {
|
|
shlex::try_quote(&extra_arg).ok().map(|s| s.to_string())
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
RunnableArgs::Shell(shell) => {
|
|
task_template.command = shell.program;
|
|
task_template.args = shell.args;
|
|
task_template.env = shell.environment;
|
|
task_template.cwd = Some(shell.cwd.to_string_lossy().to_string());
|
|
}
|
|
}
|
|
|
|
runnables.push((location, task_template));
|
|
}
|
|
|
|
Ok(LspRunnables { runnables })
|
|
}
|
|
|
|
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtRunnables {
|
|
proto::LspExtRunnables {
|
|
project_id,
|
|
buffer_id: buffer.remote_id().to_proto(),
|
|
position: self.position.as_ref().map(serialize_anchor),
|
|
}
|
|
}
|
|
|
|
async fn from_proto(
|
|
message: proto::LspExtRunnables,
|
|
_: Entity<LspStore>,
|
|
_: Entity<Buffer>,
|
|
_: AsyncApp,
|
|
) -> Result<Self> {
|
|
let buffer_id = Self::buffer_id_from_proto(&message)?;
|
|
let position = message.position.and_then(deserialize_anchor);
|
|
Ok(Self {
|
|
buffer_id,
|
|
position,
|
|
})
|
|
}
|
|
|
|
fn response_to_proto(
|
|
response: LspRunnables,
|
|
lsp_store: &mut LspStore,
|
|
peer_id: PeerId,
|
|
_: &clock::Global,
|
|
cx: &mut App,
|
|
) -> proto::LspExtRunnablesResponse {
|
|
proto::LspExtRunnablesResponse {
|
|
runnables: response
|
|
.runnables
|
|
.into_iter()
|
|
.map(|(location, task_template)| proto::LspRunnable {
|
|
location: location
|
|
.map(|location| location_link_to_proto(location, lsp_store, peer_id, cx)),
|
|
task_template: serde_json::to_vec(&task_template).unwrap(),
|
|
})
|
|
.collect(),
|
|
}
|
|
}
|
|
|
|
async fn response_from_proto(
|
|
self,
|
|
message: proto::LspExtRunnablesResponse,
|
|
lsp_store: Entity<LspStore>,
|
|
_: Entity<Buffer>,
|
|
mut cx: AsyncApp,
|
|
) -> Result<LspRunnables> {
|
|
let mut runnables = LspRunnables {
|
|
runnables: Vec::new(),
|
|
};
|
|
|
|
for lsp_runnable in message.runnables {
|
|
let location = match lsp_runnable.location {
|
|
Some(location) => {
|
|
Some(location_link_from_proto(location, lsp_store.clone(), &mut cx).await?)
|
|
}
|
|
None => None,
|
|
};
|
|
let task_template = serde_json::from_slice(&lsp_runnable.task_template)
|
|
.context("deserializing task template from proto")?;
|
|
runnables.runnables.push((location, task_template));
|
|
}
|
|
|
|
Ok(runnables)
|
|
}
|
|
|
|
fn buffer_id_from_proto(message: &proto::LspExtRunnables) -> Result<BufferId> {
|
|
BufferId::new(message.buffer_id)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct LspExtCancelFlycheck {}
|
|
|
|
#[derive(Debug)]
|
|
pub struct LspExtRunFlycheck {}
|
|
|
|
#[derive(Debug)]
|
|
pub struct LspExtClearFlycheck {}
|
|
|
|
impl lsp::notification::Notification for LspExtCancelFlycheck {
|
|
type Params = ();
|
|
const METHOD: &'static str = "rust-analyzer/cancelFlycheck";
|
|
}
|
|
|
|
impl lsp::notification::Notification for LspExtRunFlycheck {
|
|
type Params = RunFlycheckParams;
|
|
const METHOD: &'static str = "rust-analyzer/runFlycheck";
|
|
}
|
|
|
|
#[derive(Deserialize, Serialize, Debug)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct RunFlycheckParams {
|
|
pub text_document: Option<lsp::TextDocumentIdentifier>,
|
|
}
|
|
|
|
impl lsp::notification::Notification for LspExtClearFlycheck {
|
|
type Params = ();
|
|
const METHOD: &'static str = "rust-analyzer/clearFlycheck";
|
|
}
|