Skip to repository content215 lines · 7.3 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T04:06:59.613Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
ui_prompt.rs
1use gpui::{
2 App, Decorations, Entity, EventEmitter, FocusHandle, Focusable, PromptButton, PromptHandle,
3 PromptLevel, PromptResponse, RenderablePromptHandle, SharedString, TextStyleRefinement, Window,
4 div, prelude::*,
5};
6use markdown::{Markdown, MarkdownElement, MarkdownStyle};
7use settings::{Settings, SettingsStore};
8use theme::ClientDecorationsExt;
9use theme_settings::ThemeSettings;
10use ui::{FluentBuilder, TintColor, prelude::*};
11use workspace::WorkspaceSettings;
12
13pub fn init(cx: &mut App) {
14 process_settings(cx);
15
16 cx.observe_global::<SettingsStore>(process_settings)
17 .detach();
18}
19
20fn process_settings(cx: &mut App) {
21 let settings = WorkspaceSettings::get_global(cx);
22 if settings.use_system_prompts && cfg!(not(any(target_os = "linux", target_os = "freebsd"))) {
23 cx.reset_prompt_builder();
24 } else {
25 cx.set_prompt_builder(zed_prompt_renderer);
26 }
27}
28
29/// Use this function in conjunction with [App::set_prompt_builder] to force
30/// GPUI to use the internal prompt system.
31fn zed_prompt_renderer(
32 level: PromptLevel,
33 message: &str,
34 detail: Option<&str>,
35 actions: &[PromptButton],
36 handle: PromptHandle,
37 window: &mut Window,
38 cx: &mut App,
39) -> RenderablePromptHandle {
40 let renderer = cx.new({
41 |cx| ZedPromptRenderer {
42 _level: level,
43 message: cx.new(|cx| Markdown::new(SharedString::new(message), None, None, cx)),
44 actions: actions.iter().map(|a| a.label().to_string()).collect(),
45 focus: cx.focus_handle(),
46 active_action_id: 0,
47 detail: detail
48 .filter(|text| !text.is_empty())
49 .map(|text| cx.new(|cx| Markdown::new(SharedString::new(text), None, None, cx))),
50 }
51 });
52
53 handle.with_view(renderer, window, cx)
54}
55
56pub struct ZedPromptRenderer {
57 _level: PromptLevel,
58 message: Entity<Markdown>,
59 actions: Vec<String>,
60 focus: FocusHandle,
61 active_action_id: usize,
62 detail: Option<Entity<Markdown>>,
63}
64
65impl ZedPromptRenderer {
66 fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) {
67 cx.emit(PromptResponse(self.active_action_id));
68 }
69
70 fn cancel(&mut self, _: &menu::Cancel, _window: &mut Window, cx: &mut Context<Self>) {
71 if let Some(ix) = self.actions.iter().position(|a| a == "Cancel") {
72 cx.emit(PromptResponse(ix));
73 }
74 }
75
76 fn select_first(
77 &mut self,
78 _: &menu::SelectFirst,
79 _window: &mut Window,
80 cx: &mut Context<Self>,
81 ) {
82 self.active_action_id = self.actions.len().saturating_sub(1);
83 cx.notify();
84 }
85
86 fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context<Self>) {
87 self.active_action_id = 0;
88 cx.notify();
89 }
90
91 fn select_next(&mut self, _: &menu::SelectNext, _window: &mut Window, cx: &mut Context<Self>) {
92 self.active_action_id = (self.active_action_id + 1) % self.actions.len();
93 cx.notify();
94 }
95
96 fn select_previous(
97 &mut self,
98 _: &menu::SelectPrevious,
99 _window: &mut Window,
100 cx: &mut Context<Self>,
101 ) {
102 if self.active_action_id > 0 {
103 self.active_action_id -= 1;
104 } else {
105 self.active_action_id = self.actions.len().saturating_sub(1);
106 }
107 cx.notify();
108 }
109}
110
111impl Render for ZedPromptRenderer {
112 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
113 let settings = ThemeSettings::get_global(cx);
114
115 let dialog = v_flex()
116 .key_context("Prompt")
117 .cursor_default()
118 .track_focus(&self.focus)
119 .on_action(cx.listener(Self::confirm))
120 .on_action(cx.listener(Self::cancel))
121 .on_action(cx.listener(Self::select_next))
122 .on_action(cx.listener(Self::select_previous))
123 .on_action(cx.listener(Self::select_first))
124 .on_action(cx.listener(Self::select_last))
125 .w_80()
126 .p_4()
127 .gap_4()
128 .elevation_3(cx)
129 .overflow_hidden()
130 .font_family(settings.ui_font.family.clone())
131 .child(div().w_full().child(MarkdownElement::new(
132 self.message.clone(),
133 markdown_style(true, window, cx),
134 )))
135 .children(self.detail.clone().map(|detail| {
136 div().w_full().text_xs().child(MarkdownElement::new(
137 detail,
138 markdown_style(false, window, cx),
139 ))
140 }))
141 .child(
142 v_flex()
143 .gap_1()
144 .children(self.actions.iter().enumerate().map(|(ix, action)| {
145 Button::new(ix, action.clone())
146 .full_width()
147 .style(ButtonStyle::Outlined)
148 .when(ix == self.active_action_id, |s| {
149 s.style(ButtonStyle::Tinted(TintColor::Accent))
150 })
151 .tab_index(ix as isize)
152 .on_click(cx.listener(move |_, _, _window, cx| {
153 cx.emit(PromptResponse(ix));
154 }))
155 })),
156 );
157
158 let decorations = window.window_decorations();
159 let inset = window.client_inset().unwrap_or(Pixels::ZERO);
160
161 div().size_full().child(
162 v_flex()
163 .occlude()
164 .absolute()
165 .inset_0()
166 .bg(gpui::black().opacity(0.2))
167 .map(|this| match decorations {
168 Decorations::Server => this,
169 Decorations::Client { tiling } => this
170 .when(!tiling.top, |this| this.top(inset))
171 .when(!tiling.bottom, |this| this.bottom(inset))
172 .when(!tiling.left, |this| this.left(inset))
173 .when(!tiling.right, |this| this.right(inset))
174 .rounded_client_corners(tiling),
175 })
176 .items_center()
177 .justify_center()
178 .child(dialog),
179 )
180 }
181}
182
183fn markdown_style(main_message: bool, window: &Window, cx: &App) -> MarkdownStyle {
184 let mut base_text_style = window.text_style();
185 let settings = ThemeSettings::get_global(cx);
186 let font_size = settings.ui_font_size(cx).into();
187
188 let color = if main_message {
189 Color::Default.color(cx)
190 } else {
191 Color::Muted.color(cx)
192 };
193
194 base_text_style.refine(&TextStyleRefinement {
195 font_family: Some(settings.ui_font.family.clone()),
196 font_size: Some(font_size),
197 color: Some(color),
198 ..Default::default()
199 });
200
201 MarkdownStyle {
202 base_text_style,
203 selection_background_color: cx.theme().colors().element_selection_background,
204 ..Default::default()
205 }
206}
207
208impl EventEmitter<PromptResponse> for ZedPromptRenderer {}
209
210impl Focusable for ZedPromptRenderer {
211 fn focus_handle(&self, _: &crate::App) -> FocusHandle {
212 self.focus.clone()
213 }
214}
215