1use std::{collections::HashMap, sync::Arc};
38
39use serde_json::{Value, json};
40use tauri::{AppHandle, Emitter};
41use tokio::sync::RwLock;
42use tonic::{Request, Response, Status};
43use ::Vine::Generated::{
44 CancelOperationRequest,
45 Empty,
46 GenericNotification,
47 GenericRequest,
48 GenericResponse,
49 RpcError as RPCError,
50 mountain_service_server::MountainService,
51};
52
53use crate::{RunTime::ApplicationRunTime::ApplicationRunTime, Track, dev_log};
54
55mod ServiceConfig {
57
58 pub const MAX_CONCURRENT_OPERATIONS:usize = 50;
60
61 pub const CANCELLATION_TIMEOUT_MS:u64 = 5000;
63
64 pub const MAX_METHOD_NAME_LENGTH:usize = 128;
66}
67
68pub struct MountainVinegRPCService {
74 ApplicationHandle:AppHandle,
76
77 RunTime:Arc<ApplicationRunTime>,
79
80 ActiveOperations:Arc<RwLock<HashMap<u64, tokio_util::sync::CancellationToken>>>,
83}
84
85impl MountainVinegRPCService {
86 pub fn ApplicationHandle(&self) -> &AppHandle { &self.ApplicationHandle }
92
93 pub fn RunTime(&self) -> &Arc<ApplicationRunTime> { &self.RunTime }
98}
99
100impl MountainVinegRPCService {
101 pub fn Create(ApplicationHandle:AppHandle, RunTime:Arc<ApplicationRunTime>) -> Self {
110 dev_log!("grpc", "[MountainVinegRPCService] New instance created");
111
112 Self {
113 ApplicationHandle,
114
115 RunTime,
116
117 ActiveOperations:Arc::new(RwLock::new(HashMap::new())),
118 }
119 }
120
121 pub async fn RegisterOperation(&self, request_id:u64) -> tokio_util::sync::CancellationToken {
129 let token = tokio_util::sync::CancellationToken::new();
130
131 self.ActiveOperations.write().await.insert(request_id, token.clone());
132
133 dev_log!(
134 "grpc",
135 "[MountainVinegRPCService] Registered operation {} for cancellation",
136 request_id
137 );
138
139 token
140 }
141
142 pub async fn UnregisterOperation(&self, request_id:u64) {
147 self.ActiveOperations.write().await.remove(&request_id);
148
149 dev_log!("grpc", "[MountainVinegRPCService] Unregistered operation {}", request_id);
150 }
151
152 fn ValidateRequest(&self, request:&GenericRequest) -> Result<(), Status> {
161 if request.method.is_empty() {
163 return Err(Status::invalid_argument("Method name cannot be empty"));
164 }
165
166 if request.method.len() > ServiceConfig::MAX_METHOD_NAME_LENGTH {
167 return Err(Status::invalid_argument(format!(
168 "Method name exceeds maximum length of {} characters",
169 ServiceConfig::MAX_METHOD_NAME_LENGTH
170 )));
171 }
172
173 if request.parameter.len() > 4 * 1024 * 1024 {
175 return Err(Status::resource_exhausted("Request parameter size exceeds limit"));
176 }
177
178 if request.method.contains("../") || request.method.contains("::") {
180 return Err(Status::permission_denied("Invalid method name format"));
181 }
182
183 Ok(())
184 }
185
186 fn CreateErrorResponse(RequestIdentifier:u64, code:i32, message:String, data:Option<Vec<u8>>) -> GenericResponse {
197 GenericResponse {
198 request_identifier:RequestIdentifier,
199
200 result:vec![],
201
202 error:Some(RPCError { code, message, data:data.unwrap_or_default() }),
203 }
204 }
205
206 fn CreateSuccessResponse(RequestIdentifier:u64, result:&Value) -> GenericResponse {
215 let result_bytes = match serde_json::to_vec(result) {
216 Ok(bytes) => bytes,
217
218 Err(e) => {
219 dev_log!("grpc", "error: [MountainVinegRPCService] Failed to serialize result: {}", e);
220
221 return Self::CreateErrorResponse(
223 RequestIdentifier,
224 -32603, "Failed to serialize response".to_string(),
226 None,
227 );
228 },
229 };
230
231 GenericResponse { request_identifier:RequestIdentifier, result:result_bytes, error:None }
232 }
233}
234
235#[tonic::async_trait]
236impl MountainService for MountainVinegRPCService {
237 type OpenChannelFromCocoonStream = std::pin::Pin<
243 Box<
244 dyn tonic::codegen::tokio_stream::Stream<Item = Result<::Vine::Generated::Envelope, tonic::Status>>
245 + Send
246 + 'static,
247 >,
248 >;
249
250 async fn open_channel_from_cocoon(
251 &self,
252
253 _request:tonic::Request<tonic::Streaming<::Vine::Generated::Envelope>>,
254 ) -> Result<tonic::Response<Self::OpenChannelFromCocoonStream>, tonic::Status> {
255 Err(tonic::Status::unimplemented(
256 "OpenChannelFromCocoon: streaming multiplexer not yet wired (Patch 14); use unary endpoints",
257 ))
258 }
259
260 async fn process_cocoon_request(
275 &self,
276
277 request:Request<GenericRequest>,
278 ) -> Result<Response<GenericResponse>, Status> {
279 let RequestData = request.into_inner();
280
281 let MethodName = RequestData.method.clone();
282
283 let RequestIdentifier = RequestData.request_identifier;
284
285 let ReceiveInstant = std::time::Instant::now();
286
287 dev_log!(
296 "grpc-verbose",
297 "[MountainVinegRPCService] recv id={} method={} size={}B",
298 RequestIdentifier,
299 MethodName,
300 RequestData.parameter.len()
301 );
302
303 let IsHotRpc = matches!(
312 MethodName.as_str(),
313 "$tree:register" | "tree.register" | "Configuration.Inspect" | "Command.Execute"
314 );
315
316 if IsHotRpc {
317 let InstrumentRecvNs = std::time::SystemTime::now()
318 .duration_since(std::time::UNIX_EPOCH)
319 .map(|D| D.as_nanos())
320 .unwrap_or(0);
321
322 dev_log!(
326 "rpc-latency",
327 "[LandFix:RPC] grpc-recv method={} id={} size={} t_ns={}",
328 MethodName,
329 RequestIdentifier,
330 RequestData.parameter.len(),
331 InstrumentRecvNs
332 );
333 }
334
335 if let Err(status) = self.ValidateRequest(&RequestData) {
337 dev_log!("grpc", "warn: [MountainVinegRPCService] Request validation failed: {}", status);
338
339 return Ok(Response::new(Self::CreateErrorResponse(
340 RequestIdentifier,
341 -32602, status.message().to_string(),
343 None,
344 )));
345 }
346
347 let ParametersValue:Value = match serde_json::from_slice(&RequestData.parameter) {
351 Ok(v) => v,
352
353 Err(e) => {
354 let msg = format!("Failed to deserialize parameters for method '{}': {}", MethodName, e);
355
356 dev_log!("grpc", "error: {}", msg);
357
358 return Ok(Response::new(Self::CreateErrorResponse(
359 RequestIdentifier,
360 -32700, msg,
362 None,
363 )));
364 },
365 };
366
367 let DispatchResult = Track::SideCarRequest::DispatchSideCarRequest::DispatchSideCarRequest(
373 self.ApplicationHandle.clone(),
374 self.RunTime.clone(),
375 "cocoon-main".to_string(),
377 MethodName.clone(),
378 ParametersValue,
379 )
380 .await;
381
382 match DispatchResult {
383 Ok(SuccessfulResult) => {
384 if IsHotRpc {
385 dev_log!(
389 "rpc-latency",
390 "[LandFix:RPC] dispatched method={} id={} elapsed={}ms",
391 MethodName,
392 RequestIdentifier,
393 ReceiveInstant.elapsed().as_millis()
394 );
395 }
396
397 dev_log!(
402 "grpc-verbose",
403 "[MountainVinegRPCService] Request [ID: {}] completed successfully",
404 RequestIdentifier
405 );
406
407 Ok(Response::new(Self::CreateSuccessResponse(RequestIdentifier, &SuccessfulResult)))
408 },
409
410 Err(ErrorString) => {
411 let LowerError = ErrorString.to_lowercase();
422
423 let LooksLike404 = (MethodName == "FileSystem.ReadFile"
435 || MethodName == "FileSystem.Stat"
436 || MethodName == "FileSystem.ReadDirectory")
437 && (LowerError.contains("resource not found")
438 || LowerError.contains("not found")
439 || LowerError.contains("enoent")
440 || LowerError.contains("no such file or directory")
441 || LowerError.contains("entity not found")
442 || LowerError.contains("os error 2")
443 || LowerError.contains("path is outside of the registered workspace")
444 || LowerError.contains("permission denied for operation")
445 || LowerError.contains("workspace is not trusted"));
446
447 if LooksLike404 {
448 dev_log!(
449 "grpc-verbose",
450 "[LandFix:MountainVinegRPC] Request [ID: {}] {} 404 (benign): {}",
451 RequestIdentifier,
452 MethodName,
453 ErrorString
454 );
455 } else {
456 dev_log!(
457 "grpc",
458 "error: [MountainVinegRPCService] Request [ID: {}] failed: {}",
459 RequestIdentifier,
460 ErrorString
461 );
462 }
463
464 let ErrorCode = if LooksLike404 { -32004 } else { -32000 };
468
469 Ok(Response::new(Self::CreateErrorResponse(
470 RequestIdentifier,
471 ErrorCode,
472 ErrorString,
473 None,
474 )))
475 },
476 }
477 }
478
479 async fn send_cocoon_notification(&self, request:Request<GenericNotification>) -> Result<Response<Empty>, Status> {
499 let NotificationData = request.into_inner();
500
501 let MethodName = NotificationData.method;
502
503 dev_log!(
507 "grpc-verbose",
508 "[MountainVinegRPCService] Received gRPC Notification: Method='{}'",
509 MethodName
510 );
511
512 if MethodName.is_empty() {
514 dev_log!(
515 "grpc",
516 "warn: [MountainVinegRPCService] Received notification with empty method name"
517 );
518
519 return Err(Status::invalid_argument("Method name cannot be empty"));
520 }
521
522 let Parameter:Value = if NotificationData.parameter.is_empty() {
533 Value::Null
534 } else {
535 serde_json::from_slice(&NotificationData.parameter).unwrap_or(Value::Null)
536 };
537
538 match MethodName.as_str() {
539 "extensionHostMessage" => {
542 ::Vine::Server::Notification::Support::RelayToSky::Fn(
543 self,
544 "cocoon:extensionHostReply",
545 &Parameter,
546 "",
547 "",
548 );
549 },
550
551 "ExtensionActivated" => {
552 ::Vine::Server::Notification::Support::RelayToSky::Fn(
553 self,
554 "cocoon:extensionActivated",
555 &Parameter,
556 "",
557 "",
558 );
559 },
560
561 "ExtensionDeactivated" => {
562 dev_log!(
563 "grpc",
564 "[Extension] deactivated id={}",
565 Parameter.get("extensionId").and_then(Value::as_str).unwrap_or("?")
566 );
567 },
568
569 "WebviewReady" => {
570 dev_log!(
571 "grpc",
572 "[Webview] ready handle={}",
573 Parameter.get("handle").and_then(Value::as_str).unwrap_or("?")
574 );
575 },
576
577 "progress.start" => {
578 ::Vine::Server::Notification::ProgressStart::ProgressStart(self, &Parameter).await;
579 },
580
581 "progress.report" => {
582 ::Vine::Server::Notification::ProgressReport::ProgressReport(self, &Parameter).await;
583 },
584
585 "progress.end" => {
586 ::Vine::Server::Notification::ProgressEnd::ProgressEnd(self, &Parameter).await;
587 },
588
589 "languages.setDocumentLanguage" => {
590 ::Vine::Server::Notification::Support::RelayToSky::Fn(
591 self,
592 "sky://languages/setDocumentLanguage",
593 &Parameter,
594 "grpc",
595 "",
596 );
597 },
598
599 "workspace.applyEdit" => {
600 ::Vine::Server::Notification::Support::RelayToSky::Fn(
601 self,
602 "sky://workspace/applyEdit",
603 &Parameter,
604 "",
605 "",
606 );
607 },
608
609 "window.showTextDocument" => {
610 ::Vine::Server::Notification::Support::RelayToSky::Fn(
611 self,
612 "sky://window/showTextDocument",
613 &Parameter,
614 "",
615 "",
616 );
617 },
618
619 "webview.setTitle"
625 | "webview.setIconPath"
626 | "webview.setHtml"
627 | "webview.setOptions"
628 | "webview.updateView"
629 | "webview.reveal" => {
630 ::Vine::Server::Notification::WebviewLifecycle::WebviewLifecycle(self, &MethodName, &Parameter).await;
631 },
632
633 "window.createTerminal" => {
634 ::Vine::Server::Notification::WindowCreateTerminal::WindowCreateTerminal(self, &Parameter).await;
635 },
636
637 "terminal.sendText" | "terminal.show" | "terminal.hide" | "terminal.dispose" => {
638 ::Vine::Server::Notification::TerminalLifecycle::TerminalLifecycle(self, &MethodName, &Parameter).await;
639 },
640
641 "tree.refresh" => {
645 ::Vine::Server::Notification::Support::RelayToSky::Fn(
646 self,
647 "sky://tree-view/refresh",
648 &Parameter,
649 "grpc",
650 "[Tree] refresh",
651 );
652 },
653
654 "terminal.envCollection.replace"
659 | "terminal.envCollection.append"
660 | "terminal.envCollection.prepend"
661 | "terminal.envCollection.delete"
662 | "terminal.envCollection.clear"
663 | "terminal.envCollection.setPersistent"
664 | "terminal.envCollection.setDescription" => {
665 super::Notification::TerminalEnvCollection::TerminalEnvCollectionDispatch(
666 self,
667 &MethodName,
668 &Parameter,
669 )
670 .await;
671 },
672
673 "window.createTextEditorDecorationType" | "window.disposeTextEditorDecorationType" => {
674 ::Vine::Server::Notification::DecorationTypeLifecycle::DecorationTypeLifecycle(
675 self,
676 &MethodName,
677 &Parameter,
678 )
679 .await;
680 },
681
682 "window.setTextEditorDecorations" => {
686 ::Vine::Server::Notification::SetTextEditorDecorations::SetTextEditorDecorations(self, &Parameter)
687 .await;
688 },
689
690 "window.applyTextEdits" => {
694 ::Vine::Server::Notification::ApplyTextEdits::ApplyTextEdits(self, &Parameter).await;
695 },
696
697 "debug.addBreakpoints" | "debug.removeBreakpoints" | "debug.consoleAppend" => {
698 ::Vine::Server::Notification::DebugLifecycle::DebugLifecycle(self, &MethodName, &Parameter).await;
699 },
700
701 "statusBar.update" | "statusBar.dispose" => {
702 ::Vine::Server::Notification::StatusBarLifecycle::StatusBarLifecycle(self, &MethodName, &Parameter)
703 .await;
704 },
705
706 "statusBar.message" => {
707 ::Vine::Server::Notification::StatusBarMessage::StatusBarMessage(self, &Parameter).await;
708 },
709
710 "window.showMessage" => {
711 ::Vine::Server::Notification::WindowShowMessage::WindowShowMessage(self, &Parameter).await;
712 },
713
714 "registerCommand" => {
715 ::Vine::Server::Notification::RegisterCommand::RegisterCommand(self, &Parameter).await;
716 },
717
718 "unregisterCommand" => {
719 ::Vine::Server::Notification::UnregisterCommand::UnregisterCommand(self, &Parameter).await;
720 },
721
722 "unregister_authentication_provider" => {
736 ::Vine::Server::Notification::Support::UnregisterByHandle::UnregisterByHandle(
737 self,
738 &Parameter,
739 "authentication",
740 );
741 },
742
743 "unregister_debug_adapter" => {
744 ::Vine::Server::Notification::Support::UnregisterByHandle::UnregisterByHandle(
745 self,
746 &Parameter,
747 "debug_adapter",
748 );
749 },
750
751 "unregister_debug_configuration_provider" => {
752 ::Vine::Server::Notification::Support::UnregisterByHandle::UnregisterByHandle(
753 self,
754 &Parameter,
755 "debug_configuration",
756 );
757 },
758
759 "unregister_external_uri_opener" => {
760 ::Vine::Server::Notification::Support::UnregisterByHandle::UnregisterByHandle(
761 self,
762 &Parameter,
763 "external_uri_opener",
764 );
765 },
766
767 "unregister_remote_authority_resolver" => {
768 ::Vine::Server::Notification::Support::UnregisterByHandle::UnregisterByHandle(
769 self,
770 &Parameter,
771 "remote_authority_resolver",
772 );
773 },
774
775 "unregister_task_provider" => {
776 ::Vine::Server::Notification::Support::UnregisterByHandle::UnregisterByHandle(self, &Parameter, "task");
777 },
778
779 "unregister_file_system_provider" => {
780 dev_log!(
781 "provider-register",
782 "[ProviderUnregister] file_system scheme={}",
783 Parameter.get("scheme").and_then(Value::as_str).unwrap_or("")
784 );
785
786 ::Vine::Server::Notification::Support::UnregisterByHandle::UnregisterByHandle(
787 self,
788 &Parameter,
789 "file_system",
790 );
791 },
792
793 "unregister_scm_provider" => {
795 ::Vine::Server::Notification::UnregisterScmProvider::UnregisterScmProvider(self, &Parameter).await;
796 },
797
798 "unregister_uri_handler" => {
799 dev_log!(
800 "provider-register",
801 "[ProviderUnregister] uri_handler scheme={}",
802 Parameter.get("scheme").and_then(Value::as_str).unwrap_or("")
803 );
804
805 ::Vine::Server::Notification::Support::UnregisterByHandle::UnregisterByHandle(
806 self,
807 &Parameter,
808 "uri_handler",
809 );
810 },
811
812 "update_scm_group" => {
813 ::Vine::Server::Notification::UpdateScmGroup::UpdateScmGroup(self, &Parameter).await;
814 },
815
816 "register_scm_provider" => {
827 ::Vine::Server::Notification::RegisterScmProvider::RegisterScmProvider(self, &Parameter).await;
828 },
829
830 "register_scm_resource_group" => {
831 ::Vine::Server::Notification::RegisterScmResourceGroup::RegisterScmResourceGroup(self, &Parameter)
832 .await;
833 },
834
835 "progress.update" => {
836 ::Vine::Server::Notification::Support::RelayToSky::Fn(
837 self,
838 "sky://notification/progress-update",
839 &Parameter,
840 "grpc",
841 "[Progress] update",
842 );
843 },
844
845 "progress.complete" => {
846 ::Vine::Server::Notification::Support::RelayToSky::Fn(
847 self,
848 "sky://progress/complete",
849 &Parameter,
850 "grpc",
851 "[Progress] complete",
852 );
853 },
854
855 "setStatusBarText" => {
856 ::Vine::Server::Notification::SetStatusBarText::SetStatusBarText(self, &Parameter).await;
857 },
858
859 "disposeStatusBarItem" => {
860 ::Vine::Server::Notification::DisposeStatusBarItem::DisposeStatusBarItem(self, &Parameter).await;
861 },
862
863 "output.create" => {
866 ::Vine::Server::Notification::Support::RelayToSky::Fn(
867 self,
868 "sky://output/create",
869 &Parameter,
870 "grpc",
871 "[Output] create",
872 );
873 },
874
875 "output.append" => {
876 ::Vine::Server::Notification::Support::RelayToSky::Fn(
877 self,
878 "sky://output/append",
879 &Parameter,
880 "grpc",
881 "[Output] append",
882 );
883 },
884
885 "output.appendLine" => {
886 ::Vine::Server::Notification::OutputAppendLine::OutputAppendLine(self, &Parameter).await;
887 },
888
889 "output.clear" => {
890 ::Vine::Server::Notification::Support::RelayToSky::Fn(
891 self,
892 "sky://output/clear",
893 &Parameter,
894 "grpc",
895 "[Output] clear",
896 );
897 },
898
899 "output.show" => {
900 ::Vine::Server::Notification::Support::RelayToSky::Fn(
901 self,
902 "sky://output/show",
903 &Parameter,
904 "grpc",
905 "[Output] show",
906 );
907 },
908
909 "output.dispose" => {
910 ::Vine::Server::Notification::Support::RelayToSky::Fn(
911 self,
912 "sky://output/dispose",
913 &Parameter,
914 "grpc",
915 "[Output] dispose",
916 );
917 },
918
919 "output.replace" => {
920 ::Vine::Server::Notification::OutputReplace::OutputReplace(self, &Parameter).await;
921 },
922
923 "outputChannel.create" => {
924 ::Vine::Server::Notification::Support::RelayToSky::Fn(
925 self,
926 "sky://output/create",
927 &Parameter,
928 "output-verbose",
929 "[OutputChannel] create",
930 );
931 },
932
933 "outputChannel.append" => {
934 ::Vine::Server::Notification::OutputChannelAppend::OutputChannelAppend(self, &Parameter).await;
935 },
936
937 "outputChannel.clear" => {
938 ::Vine::Server::Notification::Support::RelayToSky::Fn(
939 self,
940 "sky://output/clear",
941 &Parameter,
942 "grpc",
943 "[OutputChannel] clear",
944 );
945 },
946
947 "outputChannel.replace" => {
948 ::Vine::Server::Notification::Support::RelayToSky::Fn(
949 self,
950 "sky://output/replace",
951 &Parameter,
952 "grpc",
953 "[OutputChannel] replace",
954 );
955 },
956
957 "outputChannel.show" => {
958 ::Vine::Server::Notification::Support::RelayToSky::Fn(
959 self,
960 "sky://output/show",
961 &Parameter,
962 "grpc",
963 "[OutputChannel] show",
964 );
965 },
966
967 "outputChannel.hide" => {
968 ::Vine::Server::Notification::OutputChannelHide::OutputChannelHide(self, &Parameter).await;
969 },
970
971 "outputChannel.dispose" => {
972 ::Vine::Server::Notification::Support::RelayToSky::Fn(
973 self,
974 "sky://output/dispose",
975 &Parameter,
976 "grpc",
977 "[OutputChannel] dispose",
978 );
979 },
980
981 "webview.postMessage" => {
982 ::Vine::Server::Notification::WebviewPostMessage::WebviewPostMessage(self, &Parameter).await;
983 },
984
985 "webview.dispose" => {
986 ::Vine::Server::Notification::WebviewDispose::WebviewDispose(self, &Parameter).await;
987 },
988
989 "set_language_configuration" => {
990 ::Vine::Server::Notification::SetLanguageConfiguration::SetLanguageConfiguration(self, &Parameter)
991 .await;
992 },
993
994 "openExternal" => {
995 ::Vine::Server::Notification::OpenExternal::OpenExternal(self, &Parameter).await;
996 },
997
998 "security.incident" => {
999 ::Vine::Server::Notification::SecurityIncident::SecurityIncident(self, &Parameter).await;
1000 },
1001
1002 "register_authentication_provider"
1009 | "register_call_hierarchy_provider"
1010 | "register_code_actions_provider"
1011 | "register_code_lens_provider"
1012 | "register_color_provider"
1013 | "register_completion_item_provider"
1014 | "register_debug_adapter"
1015 | "register_debug_configuration_provider"
1016 | "register_declaration_provider"
1017 | "register_definition_provider"
1018 | "register_document_drop_edit_provider"
1019 | "register_document_formatting_provider"
1020 | "register_document_highlight_provider"
1021 | "register_document_link_provider"
1022 | "register_document_paste_edit_provider"
1023 | "register_document_range_formatting_provider"
1024 | "register_document_symbol_provider"
1025 | "register_evaluatable_expression_provider"
1026 | "register_external_uri_opener"
1027 | "register_file_decoration_provider"
1028 | "register_file_system_provider"
1029 | "register_folding_range_provider"
1030 | "register_hover_provider"
1031 | "register_implementation_provider"
1032 | "register_inlay_hints_provider"
1033 | "register_inline_completion_item_provider"
1034 | "register_inline_edit_provider"
1035 | "register_inline_values_provider"
1036 | "register_linked_editing_range_provider"
1037 | "register_mapped_edits_provider"
1038 | "register_multi_document_highlight_provider"
1039 | "register_notebook_content_provider"
1040 | "register_notebook_serializer"
1041 | "register_on_type_formatting_provider"
1042 | "register_reference_provider"
1043 | "register_remote_authority_resolver"
1044 | "register_rename_provider"
1045 | "register_resource_label_formatter"
1046 | "register_selection_range_provider"
1047 | "register_semantic_tokens_provider"
1048 | "register_signature_help_provider"
1049 | "register_task_provider"
1050 | "register_terminal_link_provider"
1051 | "register_terminal_profile_provider"
1052 | "register_text_document_content_provider"
1053 | "register_type_definition_provider"
1054 | "register_type_hierarchy_provider"
1055 | "register_uri_handler"
1056 | "register_workspace_symbol_provider" => {
1057 let _ = ::Vine::Server::Notification::RegisterLanguageProvider::RegisterLanguageProvider(
1058 self,
1059 &MethodName,
1060 &Parameter,
1061 )
1062 .await;
1063 },
1064
1065 _ => {
1066 dev_log!("grpc", "[MountainVinegRPCService] Cocoon notification: {}", MethodName);
1067
1068 let PayloadPreview = if NotificationData.parameter.len() <= 160 {
1081 String::from_utf8_lossy(&NotificationData.parameter).into_owned()
1082 } else {
1083 let Slice = &NotificationData.parameter[..160];
1084
1085 format!("{}…", String::from_utf8_lossy(Slice))
1086 };
1087
1088 dev_log!(
1089 "notif-drop",
1090 "[NotifDrop] method={} payload_bytes={} preview={:?} (falls through to cocoon:{} event)",
1091 MethodName,
1092 NotificationData.parameter.len(),
1093 PayloadPreview,
1094 MethodName
1095 );
1096
1097 let SanitizedMethod:String = MethodName
1103 .chars()
1104 .map(|C| {
1105 match C {
1106 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '/' | ':' | '_' => C,
1107 '.' => '/',
1108 _ => '-',
1109 }
1110 })
1111 .collect();
1112
1113 let EventName = format!("cocoon:{}", SanitizedMethod);
1114
1115 if let Err(Error) = self.ApplicationHandle.emit(&EventName, &Parameter) {
1116 dev_log!(
1117 "grpc",
1118 "warn: [MountainVinegRPCService] Failed to emit {}: {}",
1119 EventName,
1120 Error
1121 );
1122 }
1123 },
1124 }
1125
1126 Ok(Response::new(Empty {}))
1127 }
1128
1129 async fn cancel_operation(&self, request:Request<CancelOperationRequest>) -> Result<Response<Empty>, Status> {
1141 let cancel_request = request.into_inner();
1142
1143 let RequestIdentifierToCancel = cancel_request.request_identifier_to_cancel;
1144
1145 dev_log!(
1146 "grpc",
1147 "[MountainVinegRPCService] Received CancelOperation request for RequestID: {}",
1148 RequestIdentifierToCancel
1149 );
1150
1151 let cancel_token = {
1153 let operations = self.ActiveOperations.read().await;
1154
1155 operations.get(&RequestIdentifierToCancel).cloned()
1156 };
1157
1158 match cancel_token {
1159 Some(token) => {
1160 token.cancel();
1162
1163 dev_log!(
1164 "grpc",
1165 "[MountainVinegRPCService] Successfully initiated cancellation for operation {}",
1166 RequestIdentifierToCancel
1167 );
1168
1169 Ok(Response::new(Empty {}))
1174 },
1175
1176 None => {
1177 dev_log!(
1179 "grpc",
1180 "warn: [MountainVinegRPCService] Cannot cancel operation {}: operation not found (may have \
1181 already completed)",
1182 RequestIdentifierToCancel
1183 );
1184
1185 Ok(Response::new(Empty {}))
1187 },
1188 }
1189 }
1190}