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_abort_inflight"
98 | "bash_status"
99 | "bash_write"
100 | "bash_promote"
101 | "bash_wait_detach"
102 | "bash_regex_match"
103 | "bash_drain_completions"
104 | "bash_notify"
105 | "bash_unnotify"
106 | "bash_ack_completions"
107 ) {
108 return;
109 }
110 if !ctx
111 .bash_background()
112 .has_completions_for_session(Some(session_id))
113 {
114 return;
115 }
116 let completions = ctx
117 .bash_background()
118 .drain_completions_for_session(Some(session_id));
119 if completions.is_empty() {
120 return;
121 }
122 let value = serde_json::json!(completions);
123 match response.data.as_object_mut() {
124 Some(data) => {
125 data.insert("bg_completions".to_string(), value);
126 }
127 None => {
128 response.data = serde_json::json!({ "bg_completions": value });
129 }
130 }
131}
132
133pub fn attach_status_bar(response: &mut Response, ctx: &AppContext, command: &str) {
140 if response
145 .data
146 .as_object_mut()
147 .and_then(|data| data.remove("_aft_suppress_status_bar"))
148 .is_some()
149 {
150 return;
151 }
152 if matches!(
153 command,
154 "configure"
155 | "ping"
156 | "version"
157 | "status"
158 | "bash_abort_inflight"
159 | "bash_status"
160 | "bash_write"
161 | "bash_promote"
162 | "bash_wait_detach"
163 | "bash_regex_match"
164 | "bash_drain_completions"
165 | "bash_notify"
166 | "bash_unnotify"
167 | "bash_ack_completions"
168 ) {
169 return;
170 }
171 let Some(counts) = ctx.status_bar_counts() else {
172 return;
173 };
174 if !ctx.should_emit_status_bar(&counts) {
175 return;
176 }
177 let value = serde_json::json!({
178 "errors": counts.errors,
179 "warnings": counts.warnings,
180 "dead_code": counts.dead_code,
181 "unused_exports": counts.unused_exports,
182 "duplicates": counts.duplicates,
183 "todos": counts.todos,
184 "tier2_stale": counts.tier2_stale,
185 });
186 match response.data.as_object_mut() {
187 Some(data) => {
188 data.insert("status_bar".to_string(), value);
189 }
190 None => {
191 response.data = serde_json::json!({ "status_bar": value });
192 }
193 }
194}