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 { name: name.into(), description: description.into() }
26 }
27}
28
29#[derive(Clone, Debug)]
31pub struct InlineListSearchConfig {
32 pub label: String,
33 pub placeholder: Option<String>,
34}
35
36#[derive(Clone, Debug)]
38pub struct SecurePromptConfig {
39 pub label: String,
40 pub placeholder: Option<String>,
42 pub mask_input: bool,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
48pub enum SessionSurface {
49 #[default]
50 Auto,
51 Alternate,
52 Inline,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
57pub struct KeyboardProtocolSettings {
58 pub enabled: bool,
59 pub mode: String,
60 pub disambiguate_escape_codes: bool,
61 pub report_event_types: bool,
62 pub report_alternate_keys: bool,
63 pub report_all_keys: bool,
64}
65
66impl Default for KeyboardProtocolSettings {
67 fn default() -> Self {
68 Self {
69 enabled: true,
70 mode: "default".to_owned(),
71 disambiguate_escape_codes: true,
72 report_event_types: true,
73 report_alternate_keys: true,
74 report_all_keys: false,
75 }
76 }
77}
78
79#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
81#[serde(rename_all = "snake_case")]
82pub enum UiMode {
83 #[default]
84 Full,
85 Minimal,
86 Focused,
87}
88
89#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
91#[serde(rename_all = "snake_case")]
92pub enum LayoutModeOverride {
93 #[default]
94 Auto,
95 Compact,
96 Standard,
97 Wide,
98}
99
100#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
102#[serde(rename_all = "snake_case")]
103pub enum ReasoningDisplayMode {
104 Always,
105 #[default]
106 Toggle,
107 Hidden,
108}
109
110#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
112#[serde(rename_all = "snake_case")]
113#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
114pub enum ThinkingBlockState {
115 #[default]
117 Collapsed,
118 Extended,
120}
121
122#[derive(Clone, Copy, Debug, PartialEq, Eq)]
124pub enum WizardModalMode {
125 MultiStep,
127 TabbedList,
129}
130
131#[derive(Clone, Debug)]
137pub struct PlanStep {
138 pub number: usize,
139 pub description: String,
140 pub details: Option<String>,
141 pub files: Vec<String>,
142 pub completed: bool,
143}
144
145#[derive(Clone, Debug)]
147pub struct PlanPhase {
148 pub name: String,
149 pub steps: Vec<PlanStep>,
150 pub completed: bool,
151}
152
153#[derive(Clone, Debug)]
155pub struct PlanContent {
156 pub title: String,
157 pub summary: String,
158 pub file_path: Option<String>,
159 pub phases: Vec<PlanPhase>,
160 pub open_questions: Vec<String>,
161 pub raw_content: String,
162 pub total_steps: usize,
163 pub completed_steps: usize,
164}
165
166impl PlanContent {
167 pub fn from_markdown(title: String, content: &str, file_path: Option<String>) -> Self {
169 let mut phases = Vec::new();
170 let mut open_questions = Vec::new();
171 let mut current_phase: Option<PlanPhase> = None;
172 let mut total_steps = 0;
173 let mut completed_steps = 0;
174 let mut summary = String::new();
175
176 for line in content.lines() {
177 let trimmed = line.trim();
178
179 if summary.is_empty() && !trimmed.is_empty() && !trimmed.starts_with('#') {
181 summary = trimmed.to_string();
182 continue;
183 }
184
185 if let Some(phase_name) = trimmed.strip_prefix("## ") {
187 if let Some(phase) = current_phase.take() {
188 phases.push(phase);
189 }
190 current_phase = Some(PlanPhase {
191 name: phase_name.to_string(),
192 steps: Vec::new(),
193 completed: false,
194 });
195 continue;
196 }
197
198 if trimmed == "## Open Questions" {
200 if let Some(phase) = current_phase.take() {
201 phases.push(phase);
202 }
203 continue;
204 }
205
206 if let Some(rest) = trimmed.strip_prefix("[ ] ") {
208 total_steps += 1;
209 if let Some(ref mut phase) = current_phase {
210 phase.steps.push(PlanStep {
211 number: phase.steps.len() + 1,
212 description: rest.to_string(),
213 details: None,
214 files: Vec::new(),
215 completed: false,
216 });
217 }
218 continue;
219 }
220
221 if let Some(rest) =
222 trimmed.strip_prefix("[x] ").or_else(|| trimmed.strip_prefix("[X] "))
223 {
224 total_steps += 1;
225 completed_steps += 1;
226 if let Some(ref mut phase) = current_phase {
227 phase.steps.push(PlanStep {
228 number: phase.steps.len() + 1,
229 description: rest.to_string(),
230 details: None,
231 files: Vec::new(),
232 completed: true,
233 });
234 }
235 continue;
236 }
237
238 if trimmed.starts_with(|c: char| c.is_ascii_digit()) && trimmed.contains('.') {
240 total_steps += 1;
241 if let Some(ref mut phase) = current_phase {
242 let desc = trimmed.split_once('.').map(|x| x.1).unwrap_or("").trim();
243 phase.steps.push(PlanStep {
244 number: phase.steps.len() + 1,
245 description: desc.to_string(),
246 details: None,
247 files: Vec::new(),
248 completed: false,
249 });
250 }
251 continue;
252 }
253
254 if trimmed.starts_with("- (") || trimmed.starts_with("- ?") {
256 open_questions.push(trimmed.trim_start_matches("- ").to_string());
257 }
258 }
259
260 if let Some(mut phase) = current_phase.take() {
262 phase.completed = phase.steps.iter().all(|s| s.completed);
263 phases.push(phase);
264 }
265
266 for phase in &mut phases {
268 phase.completed = !phase.steps.is_empty() && phase.steps.iter().all(|s| s.completed);
269 }
270
271 Self {
272 title,
273 summary,
274 file_path,
275 phases,
276 open_questions,
277 raw_content: content.to_string(),
278 total_steps,
279 completed_steps,
280 }
281 }
282
283 #[allow(clippy::cast_sign_loss)]
285 pub fn progress_percent(&self) -> u8 {
286 if self.total_steps == 0 {
287 0
288 } else {
289 ((self.completed_steps as f32 / self.total_steps as f32) * 100.0) as u8
290 }
291 }
292}