on master help sign in
lsp: Implement support for workspace_folders (currently just one)
8adf0c1Blaž Hrastnik
helix-lsp/src/client.rs+40−13
@@ -31,7 +31,9 @@ pub struct Client {
31 pub(crate) capabilities: OnceCell<lsp::ServerCapabilities>,
31 pub(crate) capabilities: OnceCell<lsp::ServerCapabilities>,
32 offset_encoding: OffsetEncoding,
32 offset_encoding: OffsetEncoding,
33 config: Option<Value>,
33 config: Option<Value>,
34− root_markers: Vec<String>,
34+ root_path: Option<std::path::PathBuf>,
35+ root_uri: Option<lsp::Url>,
36+ workspace_folders: Vec<lsp::WorkspaceFolder>,
35}
37}
36
38
37impl Client {
39impl Client {
@@ -40,7 +42,7 @@ impl Client {
40 cmd: &str,
42 cmd: &str,
41 args: &[String],
43 args: &[String],
42 config: Option<Value>,
44 config: Option<Value>,
43− root_markers: Vec<String>,
45+ root_markers: &[String],
44 id: usize,
46 id: usize,
45 ) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> {
47 ) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> {
46 // Resolve path to the binary
48 // Resolve path to the binary
@@ -65,6 +67,27 @@ impl Client {
65 let (server_rx, server_tx, initialize_notify) =
67 let (server_rx, server_tx, initialize_notify) =
66 Transport::start(reader, writer, stderr, id);
68 Transport::start(reader, writer, stderr, id);
67
69
70+ let root_path = find_root(None, root_markers);
71+
72+ let root_uri = root_path
73+ .clone()
74+ .and_then(|root| lsp::Url::from_file_path(root).ok());
75+
76+ // TODO: support multiple workspace folders
77+ let workspace_folders = root_uri
78+ .clone()
79+ .map(|root| {
80+ vec![lsp::WorkspaceFolder {
81+ name: root
82+ .path_segments()
83+ .and_then(|segments| segments.last())
84+ .map(|basename| basename.to_string())
85+ .unwrap_or_default(),
86+ uri: root,
87+ }]
88+ })
89+ .unwrap_or_default();
90+
68 let client = Self {
91 let client = Self {
69 id,
92 id,
70 _process: process,
93 _process: process,
@@ -73,7 +96,10 @@ impl Client {
73 capabilities: OnceCell::new(),
96 capabilities: OnceCell::new(),
74 offset_encoding: OffsetEncoding::Utf8,
97 offset_encoding: OffsetEncoding::Utf8,
75 config,
98 config,
76− root_markers,
99+
100+ root_path,
101+ root_uri,
102+ workspace_folders,
77 };
103 };
78
104
79 Ok((client, server_rx, initialize_notify))
105 Ok((client, server_rx, initialize_notify))
@@ -117,6 +143,10 @@ impl Client {
117 self.config.as_ref()
143 self.config.as_ref()
118 }
144 }
119
145
146+ pub fn workspace_folders(&self) -> &[lsp::WorkspaceFolder] {
147+ &self.workspace_folders
148+ }
149+
120 /// Execute a RPC request on the language server.
150 /// Execute a RPC request on the language server.
121 async fn request<R: lsp::request::Request>(&self, params: R::Params) -> Result<R::Result>
151 async fn request<R: lsp::request::Request>(&self, params: R::Params) -> Result<R::Result>
122 where
152 where
@@ -234,13 +264,6 @@ impl Client {
234 // -------------------------------------------------------------------------------------------
264 // -------------------------------------------------------------------------------------------
235
265
236 pub(crate) async fn initialize(&self) -> Result<lsp::InitializeResult> {
266 pub(crate) async fn initialize(&self) -> Result<lsp::InitializeResult> {
237− // TODO: delay any requests that are triggered prior to initialize
238− let root_path = find_root(None, &self.root_markers);
239−
240− let root_uri = root_path
241− .clone()
242− .and_then(|root| lsp::Url::from_file_path(root).ok());
243−
244 if self.config.is_some() {
267 if self.config.is_some() {
245 log::info!("Using custom LSP config: {}", self.config.as_ref().unwrap());
268 log::info!("Using custom LSP config: {}", self.config.as_ref().unwrap());
246 }
269 }
@@ -248,10 +271,14 @@ impl Client {
248 #[allow(deprecated)]
271 #[allow(deprecated)]
249 let params = lsp::InitializeParams {
272 let params = lsp::InitializeParams {
250 process_id: Some(std::process::id()),
273 process_id: Some(std::process::id()),
274+ workspace_folders: Some(self.workspace_folders.clone()),
251 // root_path is obsolete, but some clients like pyright still use it so we specify both.
275 // root_path is obsolete, but some clients like pyright still use it so we specify both.
252 // clients will prefer _uri if possible
276 // clients will prefer _uri if possible
277+ root_path: self
278+ .root_path
279+ .clone()
253− root_path: root_path.and_then(|path| path.to_str().map(|path| path.to_owned())),
280+ .and_then(|path| path.to_str().map(|path| path.to_owned())),
254− root_uri,
281+ root_uri: self.root_uri.clone(),
255 initialization_options: self.config.clone(),
282 initialization_options: self.config.clone(),
256 capabilities: lsp::ClientCapabilities {
283 capabilities: lsp::ClientCapabilities {
257 workspace: Some(lsp::WorkspaceClientCapabilities {
284 workspace: Some(lsp::WorkspaceClientCapabilities {
@@ -259,6 +286,7 @@ impl Client {
259 did_change_configuration: Some(lsp::DynamicRegistrationClientCapabilities {
286 did_change_configuration: Some(lsp::DynamicRegistrationClientCapabilities {
260 dynamic_registration: Some(false),
287 dynamic_registration: Some(false),
261 }),
288 }),
289+ workspace_folders: Some(true),
262 ..Default::default()
290 ..Default::default()
263 }),
291 }),
264 text_document: Some(lsp::TextDocumentClientCapabilities {
292 text_document: Some(lsp::TextDocumentClientCapabilities {
@@ -314,7 +342,6 @@ impl Client {
314 ..Default::default()
342 ..Default::default()
315 },
343 },
316 trace: None,
344 trace: None,
317− workspace_folders: None,
318 client_info: None,
345 client_info: None,
319 locale: None, // TODO
346 locale: None, // TODO
320 };
347 };
helix-lsp/src/lib.rs+3−1
@@ -191,6 +191,7 @@ pub mod util {
191pub enum MethodCall {
191pub enum MethodCall {
192 WorkDoneProgressCreate(lsp::WorkDoneProgressCreateParams),
192 WorkDoneProgressCreate(lsp::WorkDoneProgressCreateParams),
193 ApplyWorkspaceEdit(lsp::ApplyWorkspaceEditParams),
193 ApplyWorkspaceEdit(lsp::ApplyWorkspaceEditParams),
194+ WorkspaceFolders,
194 WorkspaceConfiguration(lsp::ConfigurationParams),
195 WorkspaceConfiguration(lsp::ConfigurationParams),
195}
196}
196
197
@@ -210,6 +211,7 @@ impl MethodCall {
210 .expect("Failed to parse ApplyWorkspaceEdit params");
211 .expect("Failed to parse ApplyWorkspaceEdit params");
211 Self::ApplyWorkspaceEdit(params)
212 Self::ApplyWorkspaceEdit(params)
212 }
213 }
214+ lsp::request::WorkspaceFoldersRequest::METHOD => Self::WorkspaceFolders,
213 lsp::request::WorkspaceConfiguration::METHOD => {
215 lsp::request::WorkspaceConfiguration::METHOD => {
214 let params: lsp::ConfigurationParams = params
216 let params: lsp::ConfigurationParams = params
215 .parse()
217 .parse()
@@ -320,7 +322,7 @@ impl Registry {
320 &config.command,
322 &config.command,
321 &config.args,
323 &config.args,
322 language_config.config.clone(),
324 language_config.config.clone(),
323− language_config.roots.clone(),
325+ &language_config.roots,
324 id,
326 id,
325 )?;
327 )?;
326 self.incoming.push(UnboundedReceiverStream::new(incoming));
328 self.incoming.push(UnboundedReceiverStream::new(incoming));
helix-term/src/application.rs+29−36
@@ -601,7 +601,7 @@ impl Application {
601 }
601 }
602 };
602 };
603
603
604− match call {
604+ let reply = match call {
605 MethodCall::WorkDoneProgressCreate(params) => {
605 MethodCall::WorkDoneProgressCreate(params) => {
606 self.lsp_progress.create(server_id, params.token);
606 self.lsp_progress.create(server_id, params.token);
607
607
@@ -613,16 +613,8 @@ impl Application {
613 if spinner.is_stopped() {
613 if spinner.is_stopped() {
614 spinner.start();
614 spinner.start();
615 }
615 }
616− let language_server =
617− match self.editor.language_servers.get_by_id(server_id) {
618− Some(language_server) => language_server,
619− None => {
620− warn!("can't find language server with id `{}`", server_id);
621− return;
622− }
623− };
624
616
625− tokio::spawn(language_server.reply(id, Ok(serde_json::Value::Null)));
617+ Ok(serde_json::Value::Null)
626 }
618 }
627 MethodCall::ApplyWorkspaceEdit(params) => {
619 MethodCall::ApplyWorkspaceEdit(params) => {
628 apply_workspace_edit(
620 apply_workspace_edit(
@@ -631,33 +623,19 @@ impl Application {
631 &params.edit,
623 &params.edit,
632 );
624 );
633
625
626+ Ok(json!(lsp::ApplyWorkspaceEditResponse {
627+ applied: true,
628+ failure_reason: None,
629+ failed_change: None,
630+ }))
631+ }
632+ MethodCall::WorkspaceFolders => {
634 let language_server =
633 let language_server =
635− match self.editor.language_servers.get_by_id(server_id) {
634+ self.editor.language_servers.get_by_id(server_id).unwrap();
636− Some(language_server) => language_server,
637− None => {
638− warn!("can't find language server with id `{}`", server_id);
639− return;
640− }
641− };
642
635
643− tokio::spawn(language_server.reply(
636+ Ok(json!(language_server.workspace_folders()))
644− id,
645− Ok(json!(lsp::ApplyWorkspaceEditResponse {
646− applied: true,
647− failure_reason: None,
648− failed_change: None,
649− })),
650− ));
651 }
637 }
652 MethodCall::WorkspaceConfiguration(params) => {
638 MethodCall::WorkspaceConfiguration(params) => {
653− let language_server =
654− match self.editor.language_servers.get_by_id(server_id) {
655− Some(language_server) => language_server,
656− None => {
657− warn!("can't find language server with id `{}`", server_id);
658− return;
659− }
660− };
661 let result: Vec<_> = params
639 let result: Vec<_> = params
662 .items
640 .items
663 .iter()
641 .iter()
@@ -668,7 +646,12 @@ impl Application {
668 let doc = self.editor.document_by_path(path)?;
646 let doc = self.editor.document_by_path(path)?;
669 doc.language_config()?.config.as_ref()?
647 doc.language_config()?.config.as_ref()?
670 }
648 }
671− None => language_server.config()?,
649+ None => self
650+ .editor
651+ .language_servers
652+ .get_by_id(server_id)
653+ .unwrap()
654+ .config()?,
672 };
655 };
673 if let Some(section) = item.section.as_ref() {
656 if let Some(section) = item.section.as_ref() {
674 for part in section.split('.') {
657 for part in section.split('.') {
@@ -678,9 +661,19 @@ impl Application {
678 Some(config)
661 Some(config)
679 })
662 })
680 .collect();
663 .collect();
681− tokio::spawn(language_server.reply(id, Ok(json!(result))));
664+ Ok(json!(result))
682 }
665 }
683− }
666+ };
667+
668+ let language_server = match self.editor.language_servers.get_by_id(server_id) {
669+ Some(language_server) => language_server,
670+ None => {
671+ warn!("can't find language server with id `{}`", server_id);
672+ return;
673+ }
674+ };
675+
676+ tokio::spawn(language_server.reply(id, reply));
684 }
677 }
685 Call::Invalid { id } => log::error!("LSP invalid method call id={:?}", id),
678 Call::Invalid { id } => log::error!("LSP invalid method call id={:?}", id),
686 }
679 }