1use {
6 reovim_protocol::v2::{
7 DebugCaptureRequest,
9 DebugCaptureResponse,
10 DebugGetCursorRequest,
11 DebugGetCursorResponse,
12 DebugGetExtensionStateRequest,
13 DebugGetExtensionStateResponse,
14 DebugGetModeRequest,
15 DebugGetModeResponse,
16 DebugListClientsRequest,
17 DebugListClientsResponse,
18 DebugListExtensionsRequest,
19 DebugListExtensionsResponse,
20 DebugSendKeysRequest,
21 DebugSendKeysResponse,
22 GetCursorRequest,
23 GetCursorResponse,
24 GetModeRequest,
25 GetModeResponse,
26 GetRawContentRequest,
27 GetRawContentResponse,
28 GetRegistersRequest,
29 GetRegistersResponse,
30 GetScreenContentRequest,
31 GetScreenContentResponse,
32 InfoRequest,
33 InfoResponse,
34 JoinRequest,
36 JoinResponse,
37 LeaveRequest,
38 LeaveResponse,
39 ListBuffersRequest,
40 ListBuffersResponse,
41 ListClientsRequest,
42 ListClientsResponse,
43 LogTailRequest,
45 LogTailResponse,
46 PingRequest,
47 PingResponse,
48 SendKeysRequest,
49 SendKeysResponse,
50 SetSyncModeRequest,
51 SetSyncModeResponse,
52 UpdatePresenceRequest,
53 UpdatePresenceResponse,
54 buffer_service_client::BufferServiceClient,
55 debug_service_client::DebugServiceClient,
56 input_service_client::InputServiceClient,
57 presence_service_client::PresenceServiceClient,
58 server_service_client::ServerServiceClient,
59 state_service_client::StateServiceClient,
60 },
61 tonic::{Request, transport::Channel},
62};
63
64#[derive(Debug)]
66pub enum GrpcClientError {
67 ConnectionFailed(String),
69 GrpcError(tonic::Status),
71 InvalidArgument(String),
73 CaptureError(String),
75}
76
77impl std::fmt::Display for GrpcClientError {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 match self {
80 Self::ConnectionFailed(msg) => write!(f, "Connection failed: {msg}"),
81 Self::GrpcError(status) => write!(f, "gRPC error: {status}"),
82 Self::InvalidArgument(msg) => write!(f, "Invalid argument: {msg}"),
83 Self::CaptureError(msg) => write!(f, "Capture error: {msg}"),
84 }
85 }
86}
87
88impl std::error::Error for GrpcClientError {}
89
90#[cfg_attr(coverage_nightly, coverage(off))]
91impl From<tonic::Status> for GrpcClientError {
92 fn from(status: tonic::Status) -> Self {
93 Self::GrpcError(status)
94 }
95}
96
97#[cfg_attr(coverage_nightly, coverage(off))]
98impl From<tonic::transport::Error> for GrpcClientError {
99 fn from(err: tonic::transport::Error) -> Self {
100 Self::ConnectionFailed(err.to_string())
101 }
102}
103
104pub struct GrpcClient {
114 input: InputServiceClient<Channel>,
115 state: StateServiceClient<Channel>,
116 buffer: BufferServiceClient<Channel>,
117 server: ServerServiceClient<Channel>,
118 presence: PresenceServiceClient<Channel>,
119 debug: DebugServiceClient<Channel>,
120 address: String,
122 client_id: Option<u64>,
124 session_token: Option<String>,
129}
130
131impl GrpcClient {
132 #[cfg_attr(coverage_nightly, coverage(off))]
142 pub async fn connect(addr: &str) -> Result<Self, GrpcClientError> {
143 let url = format!("http://{addr}");
145 let channel = Channel::from_shared(url)
146 .map_err(|e| GrpcClientError::ConnectionFailed(e.to_string()))?
147 .connect()
148 .await?;
149
150 Ok(Self {
151 input: InputServiceClient::new(channel.clone()),
152 state: StateServiceClient::new(channel.clone()),
153 buffer: BufferServiceClient::new(channel.clone()),
154 server: ServerServiceClient::new(channel.clone()),
155 presence: PresenceServiceClient::new(channel.clone()),
156 debug: DebugServiceClient::new(channel),
157 address: addr.to_string(),
158 client_id: None,
159 session_token: None,
160 })
161 }
162
163 #[cfg_attr(coverage_nightly, coverage(off))]
169 fn make_request<T>(&self, body: T) -> Request<T> {
170 let mut request = Request::new(body);
171 if let Some(ref token) = self.session_token {
172 request
173 .metadata_mut()
174 .insert("x-reovim-token", token.parse().expect("session token is valid ASCII"));
175 }
176 request
177 }
178
179 #[cfg_attr(coverage_nightly, coverage(off))]
186 async fn ensure_joined(&mut self) -> u64 {
187 if let Some(id) = self.client_id {
188 return id;
189 }
190
191 match self.presence_join("cli", "CLI").await {
192 Ok(resp) => {
193 self.client_id = Some(resp.client_id);
194 resp.client_id
195 }
196 Err(e) => panic!(
197 "FATAL: Failed to join presence session.\n\
198 Server: {}\n\
199 Error: {e}\n\
200 Ensure server is running and accepts connections.",
201 self.address
202 ),
203 }
204 }
205
206 #[allow(dead_code)]
213 #[cfg_attr(coverage_nightly, coverage(off))]
214 fn handle_grpc_error(e: &tonic::Status, operation: &str) -> ! {
215 use tonic::Code;
216
217 match e.code() {
218 Code::NotFound
220 | Code::InvalidArgument
221 | Code::PermissionDenied
222 | Code::FailedPrecondition
223 | Code::Internal
224 | Code::Unimplemented => {
225 panic!(
226 "FATAL: {operation} failed\n\
227 Code: {:?}\n\
228 Message: {}\n\
229 This indicates a client bug or misconfiguration.",
230 e.code(),
231 e.message()
232 );
233 }
234 Code::Unavailable
236 | Code::ResourceExhausted
237 | Code::DeadlineExceeded
238 | Code::Aborted => {
239 panic!(
240 "FATAL: {operation} failed (transient)\n\
241 Code: {:?}\n\
242 Message: {}\n\
243 Server may be temporarily unavailable.",
244 e.code(),
245 e.message()
246 );
247 }
248 _ => {
250 panic!(
251 "FATAL: {operation} failed (unknown)\n\
252 Code: {:?}\n\
253 Message: {}",
254 e.code(),
255 e.message()
256 );
257 }
258 }
259 }
260
261 #[cfg_attr(coverage_nightly, coverage(off))]
280 pub async fn send_keys(&mut self, keys: &str) -> Result<SendKeysResponse, GrpcClientError> {
281 self.ensure_joined().await;
282 let request = self.make_request(SendKeysRequest {
283 keys: keys.to_string(),
284 });
285 let response = self.input.send_keys(request).await?;
286 Ok(response.into_inner())
287 }
288
289 #[cfg_attr(coverage_nightly, coverage(off))]
299 pub async fn get_mode(&mut self) -> Result<GetModeResponse, GrpcClientError> {
300 self.ensure_joined().await;
301 let request = self.make_request(GetModeRequest { client_id: 0 });
302 let response = self.state.get_mode(request).await?;
303 Ok(response.into_inner())
304 }
305
306 #[cfg_attr(coverage_nightly, coverage(off))]
316 pub async fn get_mode_for_client(
317 &mut self,
318 client_id: u64,
319 ) -> Result<GetModeResponse, GrpcClientError> {
320 let request = self.make_request(GetModeRequest { client_id });
321 let response = self.state.get_mode(request).await?;
322 Ok(response.into_inner())
323 }
324
325 #[cfg_attr(coverage_nightly, coverage(off))]
335 pub async fn get_cursor(&mut self) -> Result<GetCursorResponse, GrpcClientError> {
336 self.ensure_joined().await;
337 let request = self.make_request(GetCursorRequest {
338 window_id: None,
339 client_id: 0,
340 });
341 let response = self.state.get_cursor(request).await?;
342 Ok(response.into_inner())
343 }
344
345 #[cfg_attr(coverage_nightly, coverage(off))]
355 pub async fn get_cursor_for_client(
356 &mut self,
357 client_id: u64,
358 ) -> Result<GetCursorResponse, GrpcClientError> {
359 let request = self.make_request(GetCursorRequest {
360 window_id: None,
361 client_id,
362 });
363 let response = self.state.get_cursor(request).await?;
364 Ok(response.into_inner())
365 }
366
367 #[cfg_attr(coverage_nightly, coverage(off))]
373 pub async fn list_buffers(&mut self) -> Result<ListBuffersResponse, GrpcClientError> {
374 let request = self.make_request(ListBuffersRequest {});
375 let response = self.buffer.list(request).await?;
376 Ok(response.into_inner())
377 }
378
379 #[cfg_attr(coverage_nightly, coverage(off))]
389 pub async fn get_buffer_content(
390 &mut self,
391 buffer_id: Option<u64>,
392 ) -> Result<GetRawContentResponse, GrpcClientError> {
393 let request = self.make_request(GetRawContentRequest {
394 buffer_id,
395 start_line: None,
396 end_line: None,
397 });
398 let response = self.buffer.get_raw_content(request).await?;
399 Ok(response.into_inner())
400 }
401
402 #[cfg_attr(coverage_nightly, coverage(off))]
408 pub async fn ping(&mut self) -> Result<PingResponse, GrpcClientError> {
409 let request = self.make_request(PingRequest {});
410 let response = self.server.ping(request).await?;
411 Ok(response.into_inner())
412 }
413
414 #[cfg_attr(coverage_nightly, coverage(off))]
420 pub async fn info(&mut self) -> Result<InfoResponse, GrpcClientError> {
421 let request = self.make_request(InfoRequest {});
422 let response = self.server.info(request).await?;
423 Ok(response.into_inner())
424 }
425
426 #[cfg_attr(coverage_nightly, coverage(off))]
436 pub async fn get_registers(
437 &mut self,
438 names: Vec<String>,
439 ) -> Result<GetRegistersResponse, GrpcClientError> {
440 let request = self.make_request(GetRegistersRequest {
441 names,
442 client_id: 0, });
444 let response = self.state.get_registers(request).await?;
445 Ok(response.into_inner())
446 }
447
448 #[cfg_attr(coverage_nightly, coverage(off))]
461 pub async fn get_screen_content(
462 &mut self,
463 client_id: u64,
464 format: &str,
465 ) -> Result<GetScreenContentResponse, GrpcClientError> {
466 let request = self.make_request(GetScreenContentRequest {
468 client_id,
469 format: format.to_string(),
470 });
471 let response = self.state.get_screen_content(request).await?;
472 Ok(response.into_inner())
473 }
474
475 #[cfg_attr(coverage_nightly, coverage(off))]
496 pub async fn presence_join(
497 &mut self,
498 client_type: &str,
499 display_name: &str,
500 ) -> Result<JoinResponse, GrpcClientError> {
501 let request = JoinRequest {
502 client_type: client_type.to_string(),
503 display_name: display_name.to_string(),
504 };
505 let response = self.presence.join(request).await?.into_inner();
506 self.client_id = Some(response.client_id);
508 if !response.session_token.is_empty() {
509 self.session_token = Some(response.session_token.clone());
510 }
511 Ok(response)
512 }
513
514 #[cfg_attr(coverage_nightly, coverage(off))]
522 pub async fn presence_leave(&mut self) -> Result<LeaveResponse, GrpcClientError> {
523 let request = self.make_request(LeaveRequest {});
524 let response = self.presence.leave(request).await?;
525 Ok(response.into_inner())
526 }
527
528 #[cfg_attr(coverage_nightly, coverage(off))]
538 pub async fn presence_list(&mut self) -> Result<ListClientsResponse, GrpcClientError> {
539 let request = self.make_request(ListClientsRequest {});
540 let response = self.presence.list_clients(request).await?;
541 Ok(response.into_inner())
542 }
543
544 #[cfg_attr(coverage_nightly, coverage(off))]
552 pub async fn presence_update(
553 &mut self,
554 buffer_id: Option<u64>,
555 mode: Option<String>,
556 ) -> Result<UpdatePresenceResponse, GrpcClientError> {
557 let request = self.make_request(UpdatePresenceRequest {
558 buffer_id,
559 visible_lines: None,
560 mode,
561 });
562 let response = self.presence.update_presence(request).await?;
563 Ok(response.into_inner())
564 }
565
566 #[cfg_attr(coverage_nightly, coverage(off))]
574 pub async fn presence_set_sync_mode(
575 &mut self,
576 sync_mode: i32,
577 follow_target: Option<u64>,
578 ) -> Result<SetSyncModeResponse, GrpcClientError> {
579 let request = self.make_request(SetSyncModeRequest {
580 mode: sync_mode,
581 follow_target,
582 });
583 let response = self.presence.set_sync_mode(request).await?;
584 Ok(response.into_inner())
585 }
586
587 #[cfg_attr(coverage_nightly, coverage(off))]
608 pub async fn log_tail(
609 &mut self,
610 count: u32,
611 level: Option<String>,
612 target: Option<String>,
613 grep: Option<String>,
614 ) -> Result<LogTailResponse, GrpcClientError> {
615 let request = self.make_request(LogTailRequest {
616 count,
617 level,
618 target,
619 grep,
620 });
621 let response = self.debug.log_tail(request).await?;
622 Ok(response.into_inner())
623 }
624
625 #[cfg_attr(coverage_nightly, coverage(off))]
640 pub async fn debug_send_keys(
641 &mut self,
642 keys: &str,
643 target_client_id: u64,
644 ) -> Result<DebugSendKeysResponse, GrpcClientError> {
645 let request = Request::new(DebugSendKeysRequest {
646 keys: keys.to_string(),
647 target_client_id,
648 });
649 let response = self.debug.debug_send_keys(request).await?;
650 Ok(response.into_inner())
651 }
652
653 #[cfg_attr(coverage_nightly, coverage(off))]
661 pub async fn debug_capture(
662 &mut self,
663 target_client_id: u64,
664 format: &str,
665 ) -> Result<DebugCaptureResponse, GrpcClientError> {
666 let request = Request::new(DebugCaptureRequest {
667 target_client_id,
668 format: format.to_string(),
669 });
670 let response = self.debug.debug_capture(request).await?;
671 Ok(response.into_inner())
672 }
673
674 #[cfg_attr(coverage_nightly, coverage(off))]
682 pub async fn debug_get_mode(
683 &mut self,
684 target_client_id: u64,
685 ) -> Result<DebugGetModeResponse, GrpcClientError> {
686 let request = Request::new(DebugGetModeRequest { target_client_id });
687 let response = self.debug.debug_get_mode(request).await?;
688 Ok(response.into_inner())
689 }
690
691 #[cfg_attr(coverage_nightly, coverage(off))]
699 pub async fn debug_get_cursor(
700 &mut self,
701 target_client_id: u64,
702 ) -> Result<DebugGetCursorResponse, GrpcClientError> {
703 let request = Request::new(DebugGetCursorRequest { target_client_id });
704 let response = self.debug.debug_get_cursor(request).await?;
705 Ok(response.into_inner())
706 }
707
708 #[cfg_attr(coverage_nightly, coverage(off))]
716 pub async fn debug_list_clients(
717 &mut self,
718 ) -> Result<DebugListClientsResponse, GrpcClientError> {
719 let request = Request::new(DebugListClientsRequest {});
720 let response = self.debug.debug_list_clients(request).await?;
721 Ok(response.into_inner())
722 }
723
724 #[cfg_attr(coverage_nightly, coverage(off))]
732 pub async fn debug_get_extension_state(
733 &mut self,
734 kind: &str,
735 target_client_id: u64,
736 ) -> Result<DebugGetExtensionStateResponse, GrpcClientError> {
737 let request = Request::new(DebugGetExtensionStateRequest {
738 kind: kind.to_string(),
739 target_client_id,
740 });
741 let response = self.debug.debug_get_extension_state(request).await?;
742 Ok(response.into_inner())
743 }
744
745 #[cfg_attr(coverage_nightly, coverage(off))]
753 pub async fn debug_list_extensions(
754 &mut self,
755 ) -> Result<DebugListExtensionsResponse, GrpcClientError> {
756 let request = Request::new(DebugListExtensionsRequest {});
757 let response = self.debug.debug_list_extensions(request).await?;
758 Ok(response.into_inner())
759 }
760}
761
762#[cfg(test)]
763#[path = "client_tests.rs"]
764mod tests;