1use std::{
2 collections::HashMap,
3 path::PathBuf,
4 sync::{
5 atomic::{AtomicU64, Ordering},
6 Arc,
7 },
8};
9
10use crossbeam_channel::{Receiver, Sender};
11use lsp_types::{
12 request::GotoTypeDefinitionResponse, CodeAction, CodeActionResponse,
13 CompletionItem, DocumentSymbolResponse, GotoDefinitionResponse, Hover,
14 InlayHint, Location, Position, PrepareRenameResponse, SelectionRange,
15 SymbolInformation, TextDocumentItem, TextEdit, WorkspaceEdit,
16};
17use parking_lot::Mutex;
18use serde::{Deserialize, Serialize};
19use lapce_xi_rope::RopeDelta;
20
21use crate::{
22 buffer::BufferId,
23 file::FileNodeItem,
24 plugin::{PluginId, VoltInfo, VoltMetadata},
25 source_control::FileDiff,
26 style::SemanticStyles,
27 terminal::TermId,
28 RequestId, RpcError, RpcMessage,
29};
30
31#[allow(clippy::large_enum_variant)]
32pub enum ProxyRpc {
33 Request(RequestId, ProxyRequest),
34 Notification(ProxyNotification),
35 Shutdown,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
39#[serde(rename_all = "snake_case")]
40#[serde(tag = "method", content = "params")]
41pub enum ProxyRequest {
42 NewBuffer {
43 buffer_id: BufferId,
44 path: PathBuf,
45 },
46 BufferHead {
47 path: PathBuf,
48 },
49 GlobalSearch {
50 pattern: String,
51 case_sensitive: bool,
52 },
53 CompletionResolve {
54 plugin_id: PluginId,
55 completion_item: Box<CompletionItem>,
56 },
57 CodeActionResolve {
58 plugin_id: PluginId,
59 action_item: Box<CodeAction>,
60 },
61 GetHover {
62 request_id: usize,
63 path: PathBuf,
64 position: Position,
65 },
66 GetSignature {
67 buffer_id: BufferId,
68 position: Position,
69 },
70 GetSelectionRange {
71 path: PathBuf,
72 positions: Vec<Position>,
73 },
74 GitGetRemoteFileUrl {
75 file: PathBuf,
76 },
77 GetReferences {
78 path: PathBuf,
79 position: Position,
80 },
81 GetDefinition {
82 request_id: usize,
83 path: PathBuf,
84 position: Position,
85 },
86 GetTypeDefinition {
87 request_id: usize,
88 path: PathBuf,
89 position: Position,
90 },
91 GetInlayHints {
92 path: PathBuf,
93 },
94 GetSemanticTokens {
95 path: PathBuf,
96 },
97 PrepareRename {
98 path: PathBuf,
99 position: Position,
100 },
101 Rename {
102 path: PathBuf,
103 position: Position,
104 new_name: String,
105 },
106 GetCodeActions {
107 path: PathBuf,
108 position: Position,
109 },
110 GetDocumentSymbols {
111 path: PathBuf,
112 },
113 GetWorkspaceSymbols {
114 query: String,
116 },
117 GetDocumentFormatting {
118 path: PathBuf,
119 },
120 GetOpenFilesContent {},
121 GetFiles {
122 path: String,
123 },
124 ReadDir {
125 path: PathBuf,
126 },
127 Save {
128 rev: u64,
129 path: PathBuf,
130 },
131 SaveBufferAs {
132 buffer_id: BufferId,
133 path: PathBuf,
134 rev: u64,
135 content: String,
136 },
137 CreateFile {
138 path: PathBuf,
139 },
140 CreateDirectory {
141 path: PathBuf,
142 },
143 TrashPath {
144 path: PathBuf,
145 },
146 RenamePath {
147 from: PathBuf,
148 to: PathBuf,
149 },
150}
151#[derive(Debug, Clone, Serialize, Deserialize)]
152#[serde(rename_all = "snake_case")]
153#[serde(tag = "method", content = "params")]
154pub enum ProxyNotification {
155 Initialize {
156 workspace: Option<PathBuf>,
157 disabled_volts: Vec<String>,
158 plugin_configurations: HashMap<String, HashMap<String, serde_json::Value>>,
159 window_id: usize,
160 tab_id: usize,
161 },
162 OpenFileChanged {
163 path: PathBuf,
164 },
165 OpenPaths {
166 folders: Vec<PathBuf>,
167 files: Vec<PathBuf>,
168 },
169 Shutdown {},
170 Completion {
171 request_id: usize,
172 path: PathBuf,
173 input: String,
174 position: Position,
175 },
176 Update {
177 path: PathBuf,
178 delta: RopeDelta,
179 rev: u64,
180 },
181 UpdatePluginConfigs {
182 configs: HashMap<String, HashMap<String, serde_json::Value>>,
183 },
184 NewTerminal {
185 term_id: TermId,
186 cwd: Option<PathBuf>,
187 shell: String,
188 },
189 InstallVolt {
190 volt: VoltInfo,
191 },
192 RemoveVolt {
193 volt: VoltMetadata,
194 },
195 ReloadVolt {
196 volt: VoltMetadata,
197 },
198 DisableVolt {
199 volt: VoltInfo,
200 },
201 EnableVolt {
202 volt: VoltInfo,
203 },
204 GitCommit {
205 message: String,
206 diffs: Vec<FileDiff>,
207 },
208 GitCheckout {
209 branch: String,
210 },
211 GitDiscardFilesChanges {
212 files: Vec<PathBuf>,
213 },
214 GitDiscardWorkspaceChanges {},
215 GitInit {},
216 TerminalWrite {
217 term_id: TermId,
218 content: String,
219 },
220 TerminalResize {
221 term_id: TermId,
222 width: usize,
223 height: usize,
224 },
225 TerminalClose {
226 term_id: TermId,
227 },
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
231#[serde(rename_all = "snake_case")]
232#[serde(tag = "method", content = "params")]
233pub enum ProxyResponse {
234 GitGetRemoteFileUrl {
235 file_url: String,
236 },
237 NewBufferResponse {
238 content: String,
239 },
240 BufferHeadResponse {
241 version: String,
242 content: String,
243 },
244 ReadDirResponse {
245 items: HashMap<PathBuf, FileNodeItem>,
246 },
247 CompletionResolveResponse {
248 item: Box<CompletionItem>,
249 },
250 CodeActionResolveResponse {
251 item: Box<CodeAction>,
252 },
253 HoverResponse {
254 request_id: usize,
255 hover: Hover,
256 },
257 GetDefinitionResponse {
258 request_id: usize,
259 definition: GotoDefinitionResponse,
260 },
261 GetTypeDefinition {
262 request_id: usize,
263 definition: GotoTypeDefinitionResponse,
264 },
265 GetReferencesResponse {
266 references: Vec<Location>,
267 },
268 GetCodeActionsResponse {
269 plugin_id: PluginId,
270 resp: CodeActionResponse,
271 },
272 GetFilesResponse {
273 items: Vec<PathBuf>,
274 },
275 GetDocumentFormatting {
276 edits: Vec<TextEdit>,
277 },
278 GetDocumentSymbols {
279 resp: DocumentSymbolResponse,
280 },
281 GetWorkspaceSymbols {
282 symbols: Vec<SymbolInformation>,
283 },
284 GetSelectionRange {
285 ranges: Vec<SelectionRange>,
286 },
287 GetInlayHints {
288 hints: Vec<InlayHint>,
289 },
290 GetSemanticTokens {
291 styles: SemanticStyles,
292 },
293 PrepareRename {
294 resp: PrepareRenameResponse,
295 },
296 Rename {
297 edit: WorkspaceEdit,
298 },
299 GetOpenFilesContentResponse {
300 items: Vec<TextDocumentItem>,
301 },
302 GlobalSearchResponse {
303 #[allow(clippy::type_complexity)]
304 matches: HashMap<PathBuf, Vec<(usize, (usize, usize), String)>>,
305 },
306 Success {},
307 SaveResponse {},
308}
309
310pub type ProxyMessage = RpcMessage<ProxyRequest, ProxyNotification, ProxyResponse>;
311
312#[derive(Debug, Clone, Serialize, Deserialize)]
313pub struct ReadDirResponse {
314 pub items: HashMap<PathBuf, FileNodeItem>,
315}
316
317pub trait ProxyCallback: Send + FnOnce(Result<ProxyResponse, RpcError>) {}
318
319impl<F: Send + FnOnce(Result<ProxyResponse, RpcError>)> ProxyCallback for F {}
320
321enum ResponseHandler {
322 Callback(Box<dyn ProxyCallback>),
323 Chan(Sender<Result<ProxyResponse, RpcError>>),
324}
325
326impl ResponseHandler {
327 fn invoke(self, result: Result<ProxyResponse, RpcError>) {
328 match self {
329 ResponseHandler::Callback(f) => f(result),
330 ResponseHandler::Chan(tx) => {
331 let _ = tx.send(result);
332 }
333 }
334 }
335}
336
337pub trait ProxyHandler {
338 fn handle_notification(&mut self, rpc: ProxyNotification);
339 fn handle_request(&mut self, id: RequestId, rpc: ProxyRequest);
340}
341
342#[derive(Clone)]
343pub struct ProxyRpcHandler {
344 tx: Sender<ProxyRpc>,
345 rx: Receiver<ProxyRpc>,
346 id: Arc<AtomicU64>,
347 pending: Arc<Mutex<HashMap<u64, ResponseHandler>>>,
348}
349
350impl ProxyRpcHandler {
351 pub fn new() -> Self {
352 let (tx, rx) = crossbeam_channel::unbounded();
353 Self {
354 tx,
355 rx,
356 id: Arc::new(AtomicU64::new(0)),
357 pending: Arc::new(Mutex::new(HashMap::new())),
358 }
359 }
360
361 pub fn rx(&self) -> &Receiver<ProxyRpc> {
362 &self.rx
363 }
364
365 pub fn mainloop<H>(&self, handler: &mut H)
366 where
367 H: ProxyHandler,
368 {
369 use ProxyRpc::*;
370 for msg in &self.rx {
371 match msg {
372 Request(id, request) => {
373 handler.handle_request(id, request);
374 }
375 Notification(notification) => {
376 handler.handle_notification(notification);
377 }
378 Shutdown => {
379 return;
380 }
381 }
382 }
383 }
384
385 fn request_common(&self, request: ProxyRequest, rh: ResponseHandler) {
386 let id = self.id.fetch_add(1, Ordering::Relaxed);
387
388 self.pending.lock().insert(id, rh);
389
390 let _ = self.tx.send(ProxyRpc::Request(id, request));
391 }
392
393 fn request(&self, request: ProxyRequest) -> Result<ProxyResponse, RpcError> {
394 let (tx, rx) = crossbeam_channel::bounded(1);
395 self.request_common(request, ResponseHandler::Chan(tx));
396 rx.recv().unwrap_or_else(|_| {
397 Err(RpcError {
398 code: 0,
399 message: "io error".to_string(),
400 })
401 })
402 }
403
404 pub fn request_async(
405 &self,
406 request: ProxyRequest,
407 f: impl ProxyCallback + 'static,
408 ) {
409 self.request_common(request, ResponseHandler::Callback(Box::new(f)))
410 }
411
412 pub fn handle_response(
413 &self,
414 id: RequestId,
415 result: Result<ProxyResponse, RpcError>,
416 ) {
417 let handler = { self.pending.lock().remove(&id) };
418 if let Some(handler) = handler {
419 handler.invoke(result);
420 }
421 }
422
423 pub fn notification(&self, notification: ProxyNotification) {
424 let _ = self.tx.send(ProxyRpc::Notification(notification));
425 }
426
427 pub fn git_init(&self) {
428 self.notification(ProxyNotification::GitInit {});
429 }
430
431 pub fn git_commit(&self, message: String, diffs: Vec<FileDiff>) {
432 self.notification(ProxyNotification::GitCommit { message, diffs });
433 }
434
435 pub fn git_checkout(&self, branch: String) {
436 self.notification(ProxyNotification::GitCheckout { branch });
437 }
438
439 pub fn install_volt(&self, volt: VoltInfo) {
440 self.notification(ProxyNotification::InstallVolt { volt });
441 }
442
443 pub fn reload_volt(&self, volt: VoltMetadata) {
444 self.notification(ProxyNotification::ReloadVolt { volt });
445 }
446
447 pub fn remove_volt(&self, volt: VoltMetadata) {
448 self.notification(ProxyNotification::RemoveVolt { volt });
449 }
450
451 pub fn disable_volt(&self, volt: VoltInfo) {
452 self.notification(ProxyNotification::DisableVolt { volt });
453 }
454
455 pub fn enable_volt(&self, volt: VoltInfo) {
456 self.notification(ProxyNotification::EnableVolt { volt });
457 }
458
459 pub fn shutdown(&self) {
460 self.notification(ProxyNotification::Shutdown {});
461 let _ = self.tx.send(ProxyRpc::Shutdown);
462 }
463
464 pub fn initialize(
465 &self,
466 workspace: Option<PathBuf>,
467 disabled_volts: Vec<String>,
468 plugin_configurations: HashMap<String, HashMap<String, serde_json::Value>>,
469 window_id: usize,
470 tab_id: usize,
471 ) {
472 self.notification(ProxyNotification::Initialize {
473 workspace,
474 disabled_volts,
475 plugin_configurations,
476 window_id,
477 tab_id,
478 });
479 }
480
481 pub fn completion(
482 &self,
483 request_id: usize,
484 path: PathBuf,
485 input: String,
486 position: Position,
487 ) {
488 self.notification(ProxyNotification::Completion {
489 request_id,
490 path,
491 input,
492 position,
493 });
494 }
495
496 pub fn new_terminal(
497 &self,
498 term_id: TermId,
499 cwd: Option<PathBuf>,
500 shell: String,
501 ) {
502 self.notification(ProxyNotification::NewTerminal {
503 term_id,
504 cwd,
505 shell,
506 })
507 }
508
509 pub fn terminal_close(&self, term_id: TermId) {
510 self.notification(ProxyNotification::TerminalClose { term_id });
511 }
512
513 pub fn terminal_resize(&self, term_id: TermId, width: usize, height: usize) {
514 self.notification(ProxyNotification::TerminalResize {
515 term_id,
516 width,
517 height,
518 });
519 }
520
521 pub fn terminal_write(&self, term_id: TermId, content: &str) {
522 self.notification(ProxyNotification::TerminalWrite {
523 term_id,
524 content: content.to_string(),
525 });
526 }
527
528 pub fn new_buffer(
529 &self,
530 buffer_id: BufferId,
531 path: PathBuf,
532 f: impl ProxyCallback + 'static,
533 ) {
534 self.request_async(ProxyRequest::NewBuffer { buffer_id, path }, f);
535 }
536
537 pub fn get_buffer_head(
538 &self,
539 _buffer_id: BufferId,
540 path: PathBuf,
541 f: impl ProxyCallback + 'static,
542 ) {
543 self.request_async(ProxyRequest::BufferHead { path }, f);
544 }
545
546 pub fn create_file(&self, path: PathBuf, f: impl ProxyCallback + 'static) {
547 self.request_async(ProxyRequest::CreateFile { path }, f);
548 }
549
550 pub fn create_directory(&self, path: PathBuf, f: impl ProxyCallback + 'static) {
551 self.request_async(ProxyRequest::CreateDirectory { path }, f);
552 }
553
554 pub fn trash_path(&self, path: PathBuf, f: impl ProxyCallback + 'static) {
555 self.request_async(ProxyRequest::TrashPath { path }, f);
556 }
557
558 pub fn rename_path(
559 &self,
560 from: PathBuf,
561 to: PathBuf,
562 f: impl ProxyCallback + 'static,
563 ) {
564 self.request_async(ProxyRequest::RenamePath { from, to }, f);
565 }
566
567 pub fn save_buffer_as(
568 &self,
569 buffer_id: BufferId,
570 path: PathBuf,
571 rev: u64,
572 content: String,
573 f: impl ProxyCallback + 'static,
574 ) {
575 self.request_async(
576 ProxyRequest::SaveBufferAs {
577 buffer_id,
578 path,
579 rev,
580 content,
581 },
582 f,
583 );
584 }
585
586 pub fn global_search(
587 &self,
588 pattern: String,
589 case_sensitive: bool,
590 f: impl ProxyCallback + 'static,
591 ) {
592 self.request_async(
593 ProxyRequest::GlobalSearch {
594 pattern,
595 case_sensitive,
596 },
597 f,
598 );
599 }
600
601 pub fn save(&self, rev: u64, path: PathBuf, f: impl ProxyCallback + 'static) {
602 self.request_async(ProxyRequest::Save { rev, path }, f);
603 }
604
605 pub fn get_files(&self, f: impl ProxyCallback + 'static) {
606 self.request_async(
607 ProxyRequest::GetFiles {
608 path: "path".into(),
609 },
610 f,
611 );
612 }
613
614 pub fn get_open_files_content(&self) -> Result<ProxyResponse, RpcError> {
615 self.request(ProxyRequest::GetOpenFilesContent {})
616 }
617
618 pub fn read_dir(&self, path: PathBuf, f: impl ProxyCallback + 'static) {
619 self.request_async(ProxyRequest::ReadDir { path }, f);
620 }
621
622 pub fn completion_resolve(
623 &self,
624 plugin_id: PluginId,
625 completion_item: CompletionItem,
626 f: impl ProxyCallback + 'static,
627 ) {
628 self.request_async(
629 ProxyRequest::CompletionResolve {
630 plugin_id,
631 completion_item: Box::new(completion_item),
632 },
633 f,
634 );
635 }
636
637 pub fn code_action_resolve(
638 &self,
639 action_item: CodeAction,
640 plugin_id: PluginId,
641 f: impl ProxyCallback + 'static,
642 ) {
643 self.request_async(
644 ProxyRequest::CodeActionResolve {
645 action_item: Box::new(action_item),
646 plugin_id,
647 },
648 f,
649 );
650 }
651
652 pub fn get_hover(
653 &self,
654 request_id: usize,
655 path: PathBuf,
656 position: Position,
657 f: impl ProxyCallback + 'static,
658 ) {
659 self.request_async(
660 ProxyRequest::GetHover {
661 request_id,
662 path,
663 position,
664 },
665 f,
666 );
667 }
668
669 pub fn get_definition(
670 &self,
671 request_id: usize,
672 path: PathBuf,
673 position: Position,
674 f: impl ProxyCallback + 'static,
675 ) {
676 self.request_async(
677 ProxyRequest::GetDefinition {
678 request_id,
679 path,
680 position,
681 },
682 f,
683 );
684 }
685
686 pub fn get_type_definition(
687 &self,
688 request_id: usize,
689 path: PathBuf,
690 position: Position,
691 f: impl ProxyCallback + 'static,
692 ) {
693 self.request_async(
694 ProxyRequest::GetTypeDefinition {
695 request_id,
696 path,
697 position,
698 },
699 f,
700 );
701 }
702
703 pub fn get_references(
704 &self,
705 path: PathBuf,
706 position: Position,
707 f: impl ProxyCallback + 'static,
708 ) {
709 self.request_async(ProxyRequest::GetReferences { path, position }, f);
710 }
711
712 pub fn get_code_actions(
713 &self,
714 path: PathBuf,
715 position: Position,
716 f: impl ProxyCallback + 'static,
717 ) {
718 self.request_async(ProxyRequest::GetCodeActions { path, position }, f);
719 }
720
721 pub fn get_document_formatting(
722 &self,
723 path: PathBuf,
724 f: impl ProxyCallback + 'static,
725 ) {
726 self.request_async(ProxyRequest::GetDocumentFormatting { path }, f);
727 }
728
729 pub fn get_semantic_tokens(
730 &self,
731 path: PathBuf,
732 f: impl ProxyCallback + 'static,
733 ) {
734 self.request_async(ProxyRequest::GetSemanticTokens { path }, f);
735 }
736
737 pub fn get_document_symbols(
738 &self,
739 path: PathBuf,
740 f: impl ProxyCallback + 'static,
741 ) {
742 self.request_async(ProxyRequest::GetDocumentSymbols { path }, f);
743 }
744
745 pub fn get_workspace_symbols(
746 &self,
747 query: String,
748 f: impl ProxyCallback + 'static,
749 ) {
750 self.request_async(ProxyRequest::GetWorkspaceSymbols { query }, f);
751 }
752
753 pub fn prepare_rename(
754 &self,
755 path: PathBuf,
756 position: Position,
757 f: impl ProxyCallback + 'static,
758 ) {
759 self.request_async(ProxyRequest::PrepareRename { path, position }, f);
760 }
761
762 pub fn git_get_remote_file_url(
763 &self,
764 file: PathBuf,
765 f: impl ProxyCallback + 'static,
766 ) {
767 self.request_async(ProxyRequest::GitGetRemoteFileUrl { file }, f);
768 }
769
770 pub fn rename(
771 &self,
772 path: PathBuf,
773 position: Position,
774 new_name: String,
775 f: impl ProxyCallback + 'static,
776 ) {
777 self.request_async(
778 ProxyRequest::Rename {
779 path,
780 position,
781 new_name,
782 },
783 f,
784 );
785 }
786
787 pub fn get_inlay_hints(&self, path: PathBuf, f: impl ProxyCallback + 'static) {
788 self.request_async(ProxyRequest::GetInlayHints { path }, f);
789 }
790
791 pub fn update(&self, path: PathBuf, delta: RopeDelta, rev: u64) {
792 self.notification(ProxyNotification::Update { path, delta, rev });
793 }
794
795 pub fn update_plugin_configs(
796 &self,
797 configs: HashMap<String, HashMap<String, serde_json::Value>>,
798 ) {
799 self.notification(ProxyNotification::UpdatePluginConfigs { configs });
800 }
801
802 pub fn git_discard_files_changes(&self, files: Vec<PathBuf>) {
803 self.notification(ProxyNotification::GitDiscardFilesChanges { files });
804 }
805
806 pub fn git_discard_workspace_changes(&self) {
807 self.notification(ProxyNotification::GitDiscardWorkspaceChanges {});
808 }
809
810 pub fn get_selection_range(
811 &self,
812 path: PathBuf,
813 positions: Vec<Position>,
814 f: impl ProxyCallback + 'static,
815 ) {
816 self.request_async(ProxyRequest::GetSelectionRange { path, positions }, f);
817 }
818}
819
820impl Default for ProxyRpcHandler {
821 fn default() -> Self {
822 Self::new()
823 }
824}