vtcode_commons/ui_protocol/
types.rs1#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub enum InlineMessageKind {
6 Agent,
7 Error,
8 Info,
9 Policy,
10 Pty,
11 Tool,
12 User,
13 Warning,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct SlashCommandItem {
19 pub name: String,
20 pub description: String,
21}
22
23impl SlashCommandItem {
24 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
25 Self {
26 name: name.into(),
27 description: description.into(),
28 }
29 }
30}
31
32#[derive(Clone, Debug)]
34pub struct InlineListSearchConfig {
35 pub label: String,
36 pub placeholder: Option<String>,
37}
38
39#[derive(Clone, Debug)]
41pub struct SecurePromptConfig {
42 pub label: String,
43 pub placeholder: Option<String>,
45 pub mask_input: bool,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
51pub enum SessionSurface {
52 #[default]
53 Auto,
54 Alternate,
55 Inline,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct KeyboardProtocolSettings {
61 pub enabled: bool,
62 pub mode: String,
63 pub disambiguate_escape_codes: bool,
64 pub report_event_types: bool,
65 pub report_alternate_keys: bool,
66 pub report_all_keys: bool,
67}
68
69impl Default for KeyboardProtocolSettings {
70 fn default() -> Self {
71 Self {
72 enabled: true,
73 mode: "default".to_owned(),
74 disambiguate_escape_codes: true,
75 report_event_types: true,
76 report_alternate_keys: true,
77 report_all_keys: false,
78 }
79 }
80}
81
82#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
84#[serde(rename_all = "snake_case")]
85pub enum UiMode {
86 #[default]
87 Full,
88 Minimal,
89 Focused,
90}
91
92#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
94#[serde(rename_all = "snake_case")]
95pub enum LayoutModeOverride {
96 #[default]
97 Auto,
98 Compact,
99 Standard,
100 Wide,
101}
102
103#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
105#[serde(rename_all = "snake_case")]
106pub enum ReasoningDisplayMode {
107 Always,
108 #[default]
109 Toggle,
110 Hidden,
111}
112
113#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
115#[serde(rename_all = "snake_case")]
116#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
117pub enum ThinkingBlockState {
118 #[default]
120 Collapsed,
121 Extended,
123}
124
125#[derive(Clone, Copy, Debug, PartialEq, Eq)]
127pub enum WizardModalMode {
128 MultiStep,
130 TabbedList,
132}
133
134#[derive(Clone, Debug)]
140pub struct PlanStep {
141 pub number: usize,
142 pub description: String,
143 pub details: Option<String>,
144 pub files: Vec<String>,
145 pub completed: bool,
146}
147
148#[derive(Clone, Debug)]
150pub struct PlanPhase {
151 pub name: String,
152 pub steps: Vec<PlanStep>,
153 pub completed: bool,
154}
155
156#[derive(Clone, Debug)]
158pub struct PlanContent {
159 pub title: String,
160 pub summary: String,
161 pub file_path: Option<String>,
162 pub phases: Vec<PlanPhase>,
163 pub open_questions: Vec<String>,
164 pub raw_content: String,
165 pub total_steps: usize,
166 pub completed_steps: usize,
167}
168
169impl PlanContent {
170 pub fn from_markdown(title: String, content: &str, file_path: Option<String>) -> Self {
172 let mut phases = Vec::new();
173 let mut open_questions = Vec::new();
174 let mut current_phase: Option<PlanPhase> = None;
175 let mut total_steps = 0;
176 let mut completed_steps = 0;
177 let mut summary = String::new();
178
179 for line in content.lines() {
180 let trimmed = line.trim();
181
182 if summary.is_empty() && !trimmed.is_empty() && !trimmed.starts_with('#') {
184 summary = trimmed.to_string();
185 continue;
186 }
187
188 if let Some(phase_name) = trimmed.strip_prefix("## ") {
190 if let Some(phase) = current_phase.take() {
191 phases.push(phase);
192 }
193 current_phase = Some(PlanPhase {
194 name: phase_name.to_string(),
195 steps: Vec::new(),
196 completed: false,
197 });
198 continue;
199 }
200
201 if trimmed == "## Open Questions" {
203 if let Some(phase) = current_phase.take() {
204 phases.push(phase);
205 }
206 continue;
207 }
208
209 if let Some(rest) = trimmed.strip_prefix("[ ] ") {
211 total_steps += 1;
212 if let Some(ref mut phase) = current_phase {
213 phase.steps.push(PlanStep {
214 number: phase.steps.len() + 1,
215 description: rest.to_string(),
216 details: None,
217 files: Vec::new(),
218 completed: false,
219 });
220 }
221 continue;
222 }
223
224 if let Some(rest) = trimmed
225 .strip_prefix("[x] ")
226 .or_else(|| trimmed.strip_prefix("[X] "))
227 {
228 total_steps += 1;
229 completed_steps += 1;
230 if let Some(ref mut phase) = current_phase {
231 phase.steps.push(PlanStep {
232 number: phase.steps.len() + 1,
233 description: rest.to_string(),
234 details: None,
235 files: Vec::new(),
236 completed: true,
237 });
238 }
239 continue;
240 }
241
242 if trimmed.starts_with(|c: char| c.is_ascii_digit()) && trimmed.contains('.') {
244 total_steps += 1;
245 if let Some(ref mut phase) = current_phase {
246 let desc = trimmed.split_once('.').map(|x| x.1).unwrap_or("").trim();
247 phase.steps.push(PlanStep {
248 number: phase.steps.len() + 1,
249 description: desc.to_string(),
250 details: None,
251 files: Vec::new(),
252 completed: false,
253 });
254 }
255 continue;
256 }
257
258 if trimmed.starts_with("- (") || trimmed.starts_with("- ?") {
260 open_questions.push(trimmed.trim_start_matches("- ").to_string());
261 }
262 }
263
264 if let Some(mut phase) = current_phase.take() {
266 phase.completed = phase.steps.iter().all(|s| s.completed);
267 phases.push(phase);
268 }
269
270 for phase in &mut phases {
272 phase.completed = !phase.steps.is_empty() && phase.steps.iter().all(|s| s.completed);
273 }
274
275 Self {
276 title,
277 summary,
278 file_path,
279 phases,
280 open_questions,
281 raw_content: content.to_string(),
282 total_steps,
283 completed_steps,
284 }
285 }
286
287 #[allow(clippy::cast_sign_loss)]
289 pub fn progress_percent(&self) -> u8 {
290 if self.total_steps == 0 {
291 0
292 } else {
293 ((self.completed_steps as f32 / self.total_steps as f32) * 100.0) as u8
294 }
295 }
296}