Skip to main content

wisp/runtime/
dispatcher.rs

1use crate::command::{Command, CommandResult, FilesystemCommand, TerminalCommand};
2use crate::runtime::{agent, files};
3use acp_utils::client::AcpClientHandle;
4use crossterm::{execute, style::Print};
5use std::io;
6
7use super::git_review::GitReviewRuntime;
8use super::tasks::{ReadTask, TaskSupervisor};
9
10pub struct CommandDispatcher {
11    client_handle: AcpClientHandle,
12    tasks: TaskSupervisor,
13    git_review: GitReviewRuntime,
14}
15
16impl CommandDispatcher {
17    pub fn new(client_handle: AcpClientHandle) -> Self {
18        Self {
19            git_review: GitReviewRuntime::new(client_handle.clone()),
20            client_handle,
21            tasks: TaskSupervisor::default(),
22        }
23    }
24
25    pub fn dispatch(&mut self, command: Command) -> Option<CommandResult> {
26        match command {
27            Command::Agent(command) => agent::execute(&self.client_handle, command, &mut self.tasks),
28            Command::GitReview(command) => self.git_review.dispatch(command, &mut self.tasks),
29            Command::Filesystem(command) => {
30                let key = match &command {
31                    FilesystemCommand::IndexFiles { .. } => Some(ReadTask::FileIndex),
32                    FilesystemCommand::PrepareSubmission { .. } => Some(ReadTask::AttachmentPreparation),
33                    FilesystemCommand::ListThemes => Some(ReadTask::ThemeList),
34                    FilesystemCommand::ListReviewThemes => Some(ReadTask::ReviewThemeList),
35                    FilesystemCommand::ApplyTheme { .. } => None,
36                };
37                let work = async move { files::execute(command).await };
38                if let Some(key) = key {
39                    self.tasks.spawn_read(key, work);
40                } else {
41                    self.tasks.spawn_mutation(work);
42                }
43                None
44            }
45            Command::Terminal(command) => execute_terminal(&command),
46        }
47    }
48
49    pub fn has_pending_tasks(&self) -> bool {
50        !self.tasks.is_empty() || self.git_review.is_active()
51    }
52
53    pub async fn next_result(&mut self) -> Option<CommandResult> {
54        loop {
55            if !self.git_review.is_active() && self.tasks.is_empty() {
56                return None;
57            }
58            tokio::select! {
59                state = self.git_review.changed() => {
60                    match state {
61                        Some(state) => return Some(CommandResult::GitReview(state)),
62                        None => self.git_review.close(),
63                    }
64                }
65                result = std::future::poll_fn(|cx| self.tasks.poll_result(cx)), if !self.tasks.is_empty() => {
66                    if let Some(result) = result {
67                        return Some(result);
68                    }
69                }
70            }
71        }
72    }
73
74    pub async fn shutdown(&mut self) {
75        self.git_review.close();
76        self.client_handle.disconnect().await;
77        self.tasks.shutdown().await;
78    }
79}
80
81fn execute_terminal(command: &TerminalCommand) -> Option<CommandResult> {
82    let TerminalCommand::RingBell = command;
83    execute!(io::stdout(), Print("\x07")).err().map(|error| CommandResult::TerminalFailed(error.to_string()))
84}