@@ -1451,6 +1451,43 @@ impl Translator {
14511451 let messages: Vec < _ > = all_messages. iter ( ) . take ( limit) . cloned ( ) . collect ( ) ;
14521452 Ok ( ServerMessagesResult { messages } )
14531453 }
1454+
1455+ /// Handle server status request.
1456+ ///
1457+ /// Returns the status of all registered LSP servers, including their language ID,
1458+ /// current state, command, and number of tracked documents.
1459+ ///
1460+ /// # Errors
1461+ ///
1462+ /// This method does not return errors.
1463+ pub async fn handle_server_status ( & self ) -> Result < ServerStatusResult > {
1464+ let mut servers = Vec :: new ( ) ;
1465+
1466+ for ( language_id, client) in & self . lsp_clients {
1467+ let state = client. state ( ) . await ;
1468+ let config = client. config ( ) ;
1469+
1470+ let document_count = self
1471+ . document_tracker
1472+ . documents ( )
1473+ . keys ( )
1474+ . filter ( |path| detect_language ( path) == * language_id)
1475+ . count ( ) ;
1476+
1477+ servers. push ( LspServerStatus {
1478+ language_id : language_id. clone ( ) ,
1479+ status : state. to_string ( ) ,
1480+ command : config. command . clone ( ) ,
1481+ document_count,
1482+ } ) ;
1483+ }
1484+
1485+ let total_servers = servers. len ( ) ;
1486+ Ok ( ServerStatusResult {
1487+ servers,
1488+ total_servers,
1489+ } )
1490+ }
14541491}
14551492
14561493/// Extract hover contents as markdown string.
@@ -2826,4 +2863,197 @@ mod tests {
28262863 assert_eq ! ( deserialized. status, status_value) ;
28272864 }
28282865 }
2866+
2867+ #[ tokio:: test]
2868+ async fn test_handle_server_status_empty_workspace ( ) {
2869+ let mut translator = Translator :: new ( ) ;
2870+
2871+ let result = translator. handle_server_status ( ) . await ;
2872+ assert ! ( result. is_ok( ) ) ;
2873+
2874+ let status = result. unwrap ( ) ;
2875+ assert ! ( status. servers. is_empty( ) ) ;
2876+ assert_eq ! ( status. total_servers, 0 ) ;
2877+ }
2878+
2879+ #[ tokio:: test]
2880+ async fn test_handle_server_status_returns_server_status_result ( ) {
2881+ let translator = Translator :: new ( ) ;
2882+
2883+ let result = translator. handle_server_status ( ) . await ;
2884+ assert ! ( result. is_ok( ) ) ;
2885+
2886+ let status_result = result. unwrap ( ) ;
2887+ assert_eq ! ( status_result. servers. len( ) , status_result. total_servers) ;
2888+ }
2889+
2890+ #[ tokio:: test]
2891+ async fn test_handle_server_status_with_registered_client ( ) {
2892+ use crate :: config:: LspServerConfig ;
2893+
2894+ let mut translator = Translator :: new ( ) ;
2895+
2896+ let config = LspServerConfig :: rust_analyzer ( ) ;
2897+ let client = LspClient :: new ( config) ;
2898+ translator. register_client ( "rust" . to_string ( ) , client) ;
2899+
2900+ let result = translator. handle_server_status ( ) . await ;
2901+ assert ! ( result. is_ok( ) ) ;
2902+
2903+ let status = result. unwrap ( ) ;
2904+ assert_eq ! ( status. total_servers, 1 ) ;
2905+ assert_eq ! ( status. servers. len( ) , 1 ) ;
2906+
2907+ let server_status = & status. servers [ 0 ] ;
2908+ assert_eq ! ( server_status. language_id, "rust" ) ;
2909+ assert_eq ! ( server_status. command, "rust-analyzer" ) ;
2910+ assert_eq ! ( server_status. status, "uninitialized" ) ;
2911+ assert_eq ! ( server_status. document_count, 0 ) ;
2912+ }
2913+
2914+ #[ tokio:: test]
2915+ async fn test_handle_server_status_multiple_servers ( ) {
2916+ use crate :: config:: LspServerConfig ;
2917+
2918+ let mut translator = Translator :: new ( ) ;
2919+
2920+ let rust_config = LspServerConfig :: rust_analyzer ( ) ;
2921+ let rust_client = LspClient :: new ( rust_config) ;
2922+ translator. register_client ( "rust" . to_string ( ) , rust_client) ;
2923+
2924+ let python_config = LspServerConfig :: pyright ( ) ;
2925+ let python_client = LspClient :: new ( python_config) ;
2926+ translator. register_client ( "python" . to_string ( ) , python_client) ;
2927+
2928+ let result = translator. handle_server_status ( ) . await ;
2929+ assert ! ( result. is_ok( ) ) ;
2930+
2931+ let status = result. unwrap ( ) ;
2932+ assert_eq ! ( status. total_servers, 2 ) ;
2933+ assert_eq ! ( status. servers. len( ) , 2 ) ;
2934+
2935+ let language_ids: Vec < & str > = status. servers . iter ( ) . map ( |s| s. language_id . as_str ( ) ) . collect ( ) ;
2936+ assert ! ( language_ids. contains( & "rust" ) ) ;
2937+ assert ! ( language_ids. contains( & "python" ) ) ;
2938+ }
2939+
2940+ #[ tokio:: test]
2941+ async fn test_handle_server_status_document_count ( ) {
2942+ use crate :: config:: LspServerConfig ;
2943+
2944+ let mut translator = Translator :: new ( ) ;
2945+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
2946+
2947+ let test_file1 = temp_dir. path ( ) . join ( "test1.rs" ) ;
2948+ let test_file2 = temp_dir. path ( ) . join ( "test2.rs" ) ;
2949+ fs:: write ( & test_file1, "fn main() {}" ) . unwrap ( ) ;
2950+ fs:: write ( & test_file2, "fn helper() {}" ) . unwrap ( ) ;
2951+
2952+ translator
2953+ . document_tracker_mut ( )
2954+ . open ( test_file1, "fn main() {}" . to_string ( ) )
2955+ . unwrap ( ) ;
2956+ translator
2957+ . document_tracker_mut ( )
2958+ . open ( test_file2, "fn helper() {}" . to_string ( ) )
2959+ . unwrap ( ) ;
2960+
2961+ let config = LspServerConfig :: rust_analyzer ( ) ;
2962+ let client = LspClient :: new ( config) ;
2963+ translator. register_client ( "rust" . to_string ( ) , client) ;
2964+
2965+ let result = translator. handle_server_status ( ) . await ;
2966+ assert ! ( result. is_ok( ) ) ;
2967+
2968+ let status = result. unwrap ( ) ;
2969+ assert_eq ! ( status. servers. len( ) , 1 ) ;
2970+
2971+ let rust_server = & status. servers [ 0 ] ;
2972+ assert_eq ! ( rust_server. language_id, "rust" ) ;
2973+ assert_eq ! ( rust_server. document_count, 2 ) ;
2974+ }
2975+
2976+ #[ tokio:: test]
2977+ async fn test_handle_server_status_document_count_per_language ( ) {
2978+ use crate :: config:: LspServerConfig ;
2979+
2980+ let mut translator = Translator :: new ( ) ;
2981+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
2982+
2983+ let rust_file = temp_dir. path ( ) . join ( "test.rs" ) ;
2984+ let python_file = temp_dir. path ( ) . join ( "test.py" ) ;
2985+ fs:: write ( & rust_file, "fn main() {}" ) . unwrap ( ) ;
2986+ fs:: write ( & python_file, "def main(): pass" ) . unwrap ( ) ;
2987+
2988+ translator
2989+ . document_tracker_mut ( )
2990+ . open ( rust_file, "fn main() {}" . to_string ( ) )
2991+ . unwrap ( ) ;
2992+ translator
2993+ . document_tracker_mut ( )
2994+ . open ( python_file, "def main(): pass" . to_string ( ) )
2995+ . unwrap ( ) ;
2996+
2997+ let rust_config = LspServerConfig :: rust_analyzer ( ) ;
2998+ let rust_client = LspClient :: new ( rust_config) ;
2999+ translator. register_client ( "rust" . to_string ( ) , rust_client) ;
3000+
3001+ let python_config = LspServerConfig :: pyright ( ) ;
3002+ let python_client = LspClient :: new ( python_config) ;
3003+ translator. register_client ( "python" . to_string ( ) , python_client) ;
3004+
3005+ let result = translator. handle_server_status ( ) . await ;
3006+ assert ! ( result. is_ok( ) ) ;
3007+
3008+ let status = result. unwrap ( ) ;
3009+ assert_eq ! ( status. total_servers, 2 ) ;
3010+
3011+ for server in & status. servers {
3012+ if server. language_id == "rust" {
3013+ assert_eq ! ( server. document_count, 1 ) ;
3014+ } else if server. language_id == "python" {
3015+ assert_eq ! ( server. document_count, 1 ) ;
3016+ }
3017+ }
3018+ }
3019+
3020+ #[ tokio:: test]
3021+ async fn test_handle_server_status_status_lowercase ( ) {
3022+ use crate :: config:: LspServerConfig ;
3023+
3024+ let mut translator = Translator :: new ( ) ;
3025+
3026+ let config = LspServerConfig :: rust_analyzer ( ) ;
3027+ let client = LspClient :: new ( config) ;
3028+ translator. register_client ( "rust" . to_string ( ) , client) ;
3029+
3030+ let result = translator. handle_server_status ( ) . await ;
3031+ assert ! ( result. is_ok( ) ) ;
3032+
3033+ let status = result. unwrap ( ) ;
3034+ let server_status = & status. servers [ 0 ] ;
3035+
3036+ let valid_statuses = [ "ready" , "initializing" , "uninitialized" , "shutting_down" , "shutdown" ] ;
3037+ assert ! (
3038+ valid_statuses. contains( & server_status. status. as_str( ) ) ,
3039+ "Status '{}' should be lowercase" ,
3040+ server_status. status
3041+ ) ;
3042+ }
3043+
3044+ #[ tokio:: test]
3045+ async fn test_handle_server_status_json_serializable ( ) {
3046+ let mut translator = Translator :: new ( ) ;
3047+
3048+ let result = translator. handle_server_status ( ) . await ;
3049+ assert ! ( result. is_ok( ) ) ;
3050+
3051+ let status = result. unwrap ( ) ;
3052+ let json_result = serde_json:: to_string ( & status) ;
3053+ assert ! ( json_result. is_ok( ) ) ;
3054+
3055+ let json = json_result. unwrap ( ) ;
3056+ assert ! ( json. contains( "\" servers\" " ) ) ;
3057+ assert ! ( json. contains( "\" total_servers\" " ) ) ;
3058+ }
28293059}
0 commit comments