wisp/runtime/
dispatcher.rs1use 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}