@ -32,14 +32,17 @@ use tokio::{
} ,
} ,
} ;
} ;
fn workspace_for_uri ( uri : lsp ::Url ) -> WorkspaceFolder {
fn workspace_for_path ( path : & Path ) -> WorkspaceFolder {
let name = path
. iter ( )
. last ( )
. expect ( "workspace paths should be non-empty" )
. to_string_lossy ( )
. to_string ( ) ;
lsp ::WorkspaceFolder {
lsp ::WorkspaceFolder {
name : uri
name ,
. path_segments ( )
uri : lsp ::Url ::from_file_path ( path ) . expect ( "absolute paths can be converted to `Url`s" ) ,
. and_then ( | segments | segments . last ( ) )
. map ( | basename | basename . to_string ( ) )
. unwrap_or_default ( ) ,
uri ,
}
}
}
}
@ -55,7 +58,7 @@ pub struct Client {
config : Option < Value > ,
config : Option < Value > ,
root_path : std ::path ::PathBuf ,
root_path : std ::path ::PathBuf ,
root_uri : Option < lsp ::Url > ,
root_uri : Option < lsp ::Url > ,
workspace_folders : Mutex < Vec < lsp::WorkspaceFolder > > ,
workspace_folders : Mutex < Vec < PathBuf > > ,
initialize_notify : Arc < Notify > ,
initialize_notify : Arc < Notify > ,
/// workspace folders added while the server is still initializing
/// workspace folders added while the server is still initializing
req_timeout : u64 ,
req_timeout : u64 ,
@ -80,16 +83,13 @@ impl Client {
& workspace ,
& workspace ,
workspace_is_cwd ,
workspace_is_cwd ,
) ;
) ;
let root_uri = root
. as_ref ( )
. and_then ( | root | lsp ::Url ::from_file_path ( root ) . ok ( ) ) ;
if self . root_path = = root . unwrap_or( workspace )
if & self . root_path = = root . as_ref ( ) . unwrap_or ( & workspace )
| | root _uri . as_ref ( ) . map_or ( false , | root _uri | {
| | root . as_ref ( ) . map_or ( false , | root | {
self . workspace_folders
self . workspace_folders
. lock ( )
. lock ( )
. iter ( )
. iter ( )
. any ( | workspace | & workspace . uri = = root _uri )
. any ( | workspace | workspace = = root )
} )
} )
{
{
// workspace URI is already registered so we can use this client
// workspace URI is already registered so we can use this client
@ -113,15 +113,16 @@ impl Client {
// wait and see if anyone ever runs into it.
// wait and see if anyone ever runs into it.
tokio ::spawn ( async move {
tokio ::spawn ( async move {
client . initialize_notify . notified ( ) . await ;
client . initialize_notify . notified ( ) . await ;
if let Some ( workspace_folders_caps ) = client
if let Some ( ( workspace_folders_caps , root ) ) = client
. capabilities ( )
. capabilities ( )
. workspace
. workspace
. as_ref ( )
. as_ref ( )
. and_then ( | cap | cap . workspace_folders . as_ref ( ) )
. and_then ( | cap | cap . workspace_folders . as_ref ( ) )
. filter ( | cap | cap . supported . unwrap_or ( false ) )
. filter ( | cap | cap . supported . unwrap_or ( false ) )
. zip ( root )
{
{
client . add_workspace_folder (
client . add_workspace_folder (
root _uri ,
root ,
workspace_folders_caps . change_notifications . as_ref ( ) ,
workspace_folders_caps . change_notifications . as_ref ( ) ,
) ;
) ;
}
}
@ -129,16 +130,14 @@ impl Client {
return true ;
return true ;
} ;
} ;
if let Some ( workspace_folders_caps ) = capabilities
if let Some ( ( workspace_folders_caps , root ) ) = capabilities
. workspace
. workspace
. as_ref ( )
. as_ref ( )
. and_then ( | cap | cap . workspace_folders . as_ref ( ) )
. and_then ( | cap | cap . workspace_folders . as_ref ( ) )
. filter ( | cap | cap . supported . unwrap_or ( false ) )
. filter ( | cap | cap . supported . unwrap_or ( false ) )
. zip ( root )
{
{
self . add_workspace_folder (
self . add_workspace_folder ( root , workspace_folders_caps . change_notifications . as_ref ( ) ) ;
root_uri ,
workspace_folders_caps . change_notifications . as_ref ( ) ,
) ;
true
true
} else {
} else {
// the server doesn't support multi workspaces, we need a new client
// the server doesn't support multi workspaces, we need a new client
@ -148,29 +147,19 @@ impl Client {
fn add_workspace_folder (
fn add_workspace_folder (
& self ,
& self ,
root _uri: Option < lsp ::Url > ,
root : PathBuf ,
change_notifications : Option < & OneOf < bool , String > > ,
change_notifications : Option < & OneOf < bool , String > > ,
) {
) {
// root_uri is None just means that there isn't really any LSP workspace
let workspace = workspace_for_path ( & root ) ;
// associated with this file. For servers that support multiple workspaces
// there is just one server so we can always just use that shared instance.
// No need to add a new workspace root here as there is no logical root for this file
// let the server deal with this
let Some ( root_uri ) = root_uri else {
return ;
} ;
// server supports workspace folders, let's add the new root to the list
// server supports workspace folders, let's add the new root to the list
self . workspace_folders
self . workspace_folders . lock ( ) . push ( root ) ;
. lock ( )
. push ( workspace_for_uri ( root_uri . clone ( ) ) ) ;
if Some ( & OneOf ::Left ( false ) ) = = change_notifications {
if Some ( & OneOf ::Left ( false ) ) = = change_notifications {
// server specifically opted out of DidWorkspaceChange notifications
// server specifically opted out of DidWorkspaceChange notifications
// let's assume the server will request the workspace folders itself
// let's assume the server will request the workspace folders itself
// and that we can therefore reuse the client (but are done now)
// and that we can therefore reuse the client (but are done now)
return ;
return ;
}
}
tokio ::spawn ( self . did_change_workspace ( vec! [ workspace _for_uri( root_uri ) ] , Vec ::new ( ) ) ) ;
tokio ::spawn ( self . did_change_workspace ( vec! [ workspace ] , Vec ::new ( ) ) ) ;
}
}
#[ allow(clippy::type_complexity, clippy::too_many_arguments) ]
#[ allow(clippy::type_complexity, clippy::too_many_arguments) ]
@ -179,8 +168,8 @@ impl Client {
args : & [ String ] ,
args : & [ String ] ,
config : Option < Value > ,
config : Option < Value > ,
server_environment : HashMap < String , String > ,
server_environment : HashMap < String , String > ,
root _path: PathBuf ,
root : Option < PathBuf > ,
root_uri: Option < lsp ::Url > ,
workspace: PathBuf ,
id : LanguageServerId ,
id : LanguageServerId ,
name : String ,
name : String ,
req_timeout : u64 ,
req_timeout : u64 ,
@ -212,10 +201,13 @@ impl Client {
let ( server_rx , server_tx , initialize_notify ) =
let ( server_rx , server_tx , initialize_notify ) =
Transport ::start ( reader , writer , stderr , id , name . clone ( ) ) ;
Transport ::start ( reader , writer , stderr , id , name . clone ( ) ) ;
let workspace_folders = root_uri
let workspace_folders = root . clone ( ) . into_iter ( ) . collect ( ) ;
. clone ( )
let root_uri = root . clone ( ) . map ( | root | {
. map ( | root | vec! [ workspace_for_uri ( root ) ] )
lsp ::Url ::from_file_path ( root ) . expect ( "absolute paths can be converted to `Url`s" )
. unwrap_or_default ( ) ;
} ) ;
// `root_uri` and `workspace_folder` can be empty in case there is no workspace
// `root_url` can not, use `workspace` as a fallback
let root_path = root . unwrap_or ( workspace ) ;
let client = Self {
let client = Self {
id ,
id ,
@ -376,10 +368,12 @@ impl Client {
self . config . as_ref ( )
self . config . as_ref ( )
}
}
pub async fn workspace_folders (
pub async fn workspace_folders ( & self ) -> Vec < lsp ::WorkspaceFolder > {
& self ,
self . workspace_folders
) -> parking_lot ::MutexGuard < ' _ , Vec < lsp ::WorkspaceFolder > > {
. lock ( )
self . workspace_folders . lock ( )
. iter ( )
. map ( | path | workspace_for_path ( path ) )
. collect ( )
}
}
/// Execute a RPC request on the language server.
/// Execute a RPC request on the language server.
@ -526,7 +520,7 @@ impl Client {
#[ allow(deprecated) ]
#[ allow(deprecated) ]
let params = lsp ::InitializeParams {
let params = lsp ::InitializeParams {
process_id : Some ( std ::process ::id ( ) ) ,
process_id : Some ( std ::process ::id ( ) ) ,
workspace_folders : Some ( self . workspace_folders .lock ( ) . clone ( ) ) ,
workspace_folders : Some ( self . workspace_folders () . await ) ,
// root_path is obsolete, but some clients like pyright still use it so we specify both.
// root_path is obsolete, but some clients like pyright still use it so we specify both.
// clients will prefer _uri if possible
// clients will prefer _uri if possible
root_path : self . root_path . to_str ( ) . map ( | path | path . to_owned ( ) ) ,
root_path : self . root_path . to_str ( ) . map ( | path | path . to_owned ( ) ) ,