vtcode_tui/core_tui/session/render/
modal_renderer.rs1use super::*;
2
3pub fn render_modal(session: &mut Session, frame: &mut Frame<'_>, viewport: Rect) {
4 if viewport.width == 0 || viewport.height == 0 {
5 return;
6 }
7
8 if session.skip_confirmations
10 && let Some(mut modal) = session.modal.take()
11 {
12 if let Some(list) = &mut modal.list
13 && let Some(_selection) = list.current_selection()
14 {
15 }
20 session.input_enabled = modal.restore_input;
21 session.cursor_visible = modal.restore_cursor;
22 session.needs_full_clear = true;
23 session.needs_redraw = true;
24 return;
25 }
26
27 let styles = modal_render_styles(session);
28 if let Some(wizard) = session.wizard_modal.as_mut() {
29 let _is_multistep = wizard.mode == crate::ui::tui::types::WizardModalMode::MultiStep;
30 let mut width_lines = Vec::new();
31 width_lines.push(wizard.question_header());
32 if let Some(step) = wizard.steps.get(wizard.current_step) {
33 width_lines.push(step.question.clone());
34 }
35 if let Some(notes) = wizard.notes_line() {
36 width_lines.push(notes);
37 }
38 width_lines.extend(wizard.instruction_lines());
39
40 let text_lines = width_lines.len();
41 let search_lines = wizard.search.as_ref().map(|_| 3).unwrap_or(0);
42 let area = compute_modal_area(viewport, text_lines, 0, search_lines, true);
43
44 let block = Block::bordered()
45 .title(Line::styled(wizard.title.clone(), styles.title))
46 .border_type(terminal_capabilities::get_border_type())
47 .border_style(styles.border);
48
49 frame.render_widget(Clear, area);
50 frame.render_widget(block, area);
51
52 if area.width <= 2 || area.height <= 2 {
53 return;
54 }
55
56 let inner = Rect {
57 x: area.x.saturating_add(1),
58 y: area.y.saturating_add(1),
59 width: area.width.saturating_sub(2),
60 height: area.height.saturating_sub(2),
61 };
62
63 if inner.width == 0 || inner.height == 0 {
64 return;
65 }
66
67 render_wizard_modal_body(frame, inner, wizard, &styles);
68 return;
69 }
70
71 let Some(modal) = session.modal.as_mut() else {
72 return;
73 };
74
75 let prompt_lines = if modal.secure_prompt.is_some() { 2 } else { 0 };
76 let search_lines = modal.search.as_ref().map(|_| 3).unwrap_or(0);
77 let area = compute_modal_area(
78 viewport,
79 modal.lines.len(),
80 prompt_lines,
81 search_lines,
82 modal.list.is_some(),
83 );
84
85 let block = Block::bordered()
86 .title(Line::styled(modal.title.clone(), styles.title))
87 .border_type(terminal_capabilities::get_border_type())
88 .border_style(styles.border);
89
90 frame.render_widget(Clear, area);
91 frame.render_widget(block, area);
92
93 if area.width <= 2 || area.height <= 2 {
94 return;
95 }
96
97 let inner = Rect {
98 x: area.x.saturating_add(1),
99 y: area.y.saturating_add(1),
100 width: area.width.saturating_sub(2),
101 height: area.height.saturating_sub(2),
102 };
103
104 if inner.width == 0 || inner.height == 0 {
105 return;
106 }
107
108 render_modal_body(
109 frame,
110 inner,
111 ModalBodyContext {
112 instructions: &modal.lines,
113 footer_hint: modal.footer_hint.as_deref(),
114 list: modal.list.as_mut(),
115 styles: &styles,
116 secure_prompt: modal.secure_prompt.as_ref(),
117 search: modal.search.as_ref(),
118 input: session.input_manager.content(),
119 cursor: session.input_manager.cursor(),
120 },
121 );
122}
123
124fn modal_render_styles(session: &Session) -> ModalRenderStyles {
125 ModalRenderStyles {
126 border: border_style(session),
127 highlight: modal_list_highlight_style(session),
128 badge: session.section_title_style().add_modifier(Modifier::DIM),
129 header: session.section_title_style(),
130 selectable: default_style(session).add_modifier(Modifier::BOLD),
131 detail: default_style(session).add_modifier(Modifier::DIM),
132 search_match: accent_style(session).add_modifier(Modifier::BOLD | Modifier::UNDERLINED),
133 title: Style::default().add_modifier(Modifier::BOLD),
134 divider: default_style(session).add_modifier(Modifier::DIM | Modifier::ITALIC),
135 instruction_border: border_style(session),
136 instruction_title: session.section_title_style(),
137 instruction_bullet: accent_style(session).add_modifier(Modifier::BOLD),
138 instruction_body: default_style(session),
139 hint: default_style(session).add_modifier(Modifier::DIM | Modifier::ITALIC),
140 }
141}
142
143#[allow(dead_code)]
144pub(super) fn handle_tool_code_fence_marker(session: &mut Session, text: &str) -> bool {
145 let trimmed = text.trim();
146 let stripped = trimmed
147 .strip_prefix("```")
148 .or_else(|| trimmed.strip_prefix("~~~"));
149
150 let Some(rest) = stripped else {
151 return false;
152 };
153
154 if rest.contains("```") || rest.contains("~~~") {
155 return false;
156 }
157
158 if session.in_tool_code_fence {
159 session.in_tool_code_fence = false;
160 remove_trailing_empty_tool_line(session);
161 } else {
162 session.in_tool_code_fence = true;
163 }
164
165 true
166}
167
168#[allow(dead_code)]
169fn remove_trailing_empty_tool_line(session: &mut Session) {
170 let should_remove = session
171 .lines
172 .last()
173 .map(|line| line.kind == InlineMessageKind::Tool && line.segments.is_empty())
174 .unwrap_or(false);
175 if should_remove {
176 session.lines.pop();
177 invalidate_scroll_metrics(session);
178 }
179}