1use crate::context::AppContext;
2use crate::protocol::Response;
3
4pub fn finalize_response(
6 response: &mut Response,
7 ctx: &AppContext,
8 session_id: &str,
9 attach_command: &str,
10) {
11 finalize_response_with_bg_completions(response, ctx, session_id, attach_command, true);
12}
13
14pub fn finalize_response_with_bg_completions(
15 response: &mut Response,
16 ctx: &AppContext,
17 session_id: &str,
18 attach_command: &str,
19 allow_bg_completions: bool,
20) {
21 if allow_bg_completions {
22 attach_bg_completions(response, ctx, session_id, attach_command);
23 }
24 attach_status_bar(response, ctx, attach_command);
25}
26
27pub enum DispatchOutcome {
28 Immediate(Response),
29 Deferred(PendingResponse),
30}
31
32pub type PendingResponsePoll = Box<dyn FnMut(&AppContext) -> Option<Response>>;
33
34pub struct PendingResponse {
35 pub request_id: String,
36 pub session_id: String,
37 pub attach_command: String,
38 pub poll: PendingResponsePoll,
39}
40
41pub struct ResolvedPending {
42 pub response: Response,
43 pub session_id: String,
44 pub attach_command: String,
45}
46
47#[derive(Default)]
48pub struct PendingResponses {
49 entries: Vec<PendingResponse>,
50}
51
52impl PendingResponses {
53 pub fn register(&mut self, pending: PendingResponse) {
54 self.entries
55 .retain(|entry| entry.request_id != pending.request_id);
56 self.entries.push(pending);
57 }
58
59 pub fn poll_ready(&mut self, ctx: &AppContext) -> Vec<ResolvedPending> {
60 let mut ready = Vec::new();
61 let mut waiting = Vec::with_capacity(self.entries.len());
62
63 for mut pending in self.entries.drain(..) {
64 if let Some(response) = (pending.poll)(ctx) {
65 ready.push(ResolvedPending {
66 response,
67 session_id: pending.session_id,
68 attach_command: pending.attach_command,
69 });
70 } else {
71 waiting.push(pending);
72 }
73 }
74
75 self.entries = waiting;
76 ready
77 }
78
79 pub fn is_empty(&self) -> bool {
80 self.entries.is_empty()
81 }
82
83 pub fn drain_on_shutdown(&mut self) {
84 self.entries.clear();
85 }
86}
87
88pub fn attach_bg_completions(
89 response: &mut Response,
90 ctx: &AppContext,
91 session_id: &str,
92 command: &str,
93) {
94 if matches!(
95 command,
96 "configure"
97 | "bash_status"
98 | "bash_write"
99 | "bash_promote"
100 | "bash_wait_detach"
101 | "bash_regex_match"
102 | "bash_drain_completions"
103 | "bash_notify"
104 | "bash_unnotify"
105 | "bash_ack_completions"
106 ) {
107 return;
108 }
109 if !ctx
110 .bash_background()
111 .has_completions_for_session(Some(session_id))
112 {
113 return;
114 }
115 let completions = ctx
116 .bash_background()
117 .drain_completions_for_session(Some(session_id));
118 if completions.is_empty() {
119 return;
120 }
121 let value = serde_json::json!(completions);
122 match response.data.as_object_mut() {
123 Some(data) => {
124 data.insert("bg_completions".to_string(), value);
125 }
126 None => {
127 response.data = serde_json::json!({ "bg_completions": value });
128 }
129 }
130}
131
132pub fn attach_status_bar(response: &mut Response, ctx: &AppContext, command: &str) {
139 if response
144 .data
145 .as_object_mut()
146 .and_then(|data| data.remove("_aft_suppress_status_bar"))
147 .is_some()
148 {
149 return;
150 }
151 if matches!(
152 command,
153 "configure"
154 | "ping"
155 | "version"
156 | "status"
157 | "bash_status"
158 | "bash_write"
159 | "bash_promote"
160 | "bash_wait_detach"
161 | "bash_regex_match"
162 | "bash_drain_completions"
163 | "bash_notify"
164 | "bash_unnotify"
165 | "bash_ack_completions"
166 ) {
167 return;
168 }
169 let Some(counts) = ctx.status_bar_counts() else {
170 return;
171 };
172 if !ctx.should_emit_status_bar(&counts) {
173 return;
174 }
175 let value = serde_json::json!({
176 "errors": counts.errors,
177 "warnings": counts.warnings,
178 "dead_code": counts.dead_code,
179 "unused_exports": counts.unused_exports,
180 "duplicates": counts.duplicates,
181 "todos": counts.todos,
182 "tier2_stale": counts.tier2_stale,
183 });
184 match response.data.as_object_mut() {
185 Some(data) => {
186 data.insert("status_bar".to_string(), value);
187 }
188 None => {
189 response.data = serde_json::json!({ "status_bar": value });
190 }
191 }
192}