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