1use editor::{Editor, EditorElement, EditorStyle, MultiBufferOffset, ToOffset};
2use gpui::{Action, App, Entity, FocusHandle, Hsla, IntoElement, TextStyle};
3use settings::Settings;
4use theme_settings::ThemeSettings;
5use ui::{IconButton, IconButtonShape};
6use ui::{Tooltip, prelude::*};
7
8pub(super) enum HistoryNavigationDirection {
9 Previous,
10 Next,
11}
12
13pub(super) fn should_navigate_history(
14 editor: &Entity<Editor>,
15 direction: HistoryNavigationDirection,
16 cx: &App,
17) -> bool {
18 let editor_ref = editor.read(cx);
19 let snapshot = editor_ref.buffer().read(cx).snapshot(cx);
20 if snapshot.max_point().row == 0 {
21 return true;
22 }
23 let selections = editor_ref.selections.disjoint_anchors();
24 if let [selection] = selections {
25 let offset = selection.end.to_offset(&snapshot);
26 match direction {
27 HistoryNavigationDirection::Previous => offset == MultiBufferOffset(0),
28 HistoryNavigationDirection::Next => offset == snapshot.len(),
29 }
30 } else {
31 true
32 }
33}
34
35pub(super) enum ActionButtonState {
36 Disabled,
37 Toggled,
38}
39
40pub(super) fn render_action_button(
41 id_prefix: &'static str,
42 icon: ui::IconName,
43 button_state: Option<ActionButtonState>,
44 tooltip: &'static str,
45 action: &'static dyn Action,
46 focus_handle: FocusHandle,
47) -> impl IntoElement {
48 IconButton::new(
49 SharedString::from(format!("{id_prefix}-{}", action.name())),
50 icon,
51 )
52 .shape(IconButtonShape::Square)
53 .on_click({
54 let focus_handle = focus_handle.clone();
55 move |_, window, cx| {
56 if !focus_handle.is_focused(window) {
57 window.focus(&focus_handle, cx);
58 }
59 window.dispatch_action(action.boxed_clone(), cx);
60 }
61 })
62 .tooltip(move |_window, cx| Tooltip::for_action_in(tooltip, action, &focus_handle, cx))
63 .when_some(button_state, |this, state| match state {
64 ActionButtonState::Toggled => this.toggle_state(true),
65 ActionButtonState::Disabled => this.disabled(true),
66 })
67}
68
69pub(crate) fn input_base_styles(border_color: Hsla, map: impl FnOnce(Div) -> Div) -> Div {
70 h_flex()
71 .map(map)
72 .min_w_32()
73 .min_h_8()
74 .pl_2()
75 .pr_1()
76 .border_1()
77 .border_color(border_color)
78 .rounded_md()
79}
80pub(crate) fn filter_search_results_input(
81 border_color: Hsla,
82 map: impl FnOnce(Div) -> Div,
83 cx: &App,
84) -> Div {
85 input_base_styles(border_color, map).pl_0().child(
86 h_flex()
87 .mr_2()
88 .px_2()
89 .h_full()
90 .border_r_1()
91 .border_color(cx.theme().colors().border)
92 .bg(cx.theme().colors().text_accent.opacity(0.05))
93 .child(Label::new("Find in Results").color(Color::Muted)),
94 )
95}
96
97pub(crate) fn render_text_input(
98 editor: &Entity<Editor>,
99 color_override: Option<Color>,
100 app: &App,
101) -> impl IntoElement {
102 let (color, use_syntax) = if editor.read(app).read_only(app) {
103 (app.theme().colors().text_disabled, false)
104 } else {
105 match color_override {
106 Some(color_override) => (color_override.color(app), false),
107 None => (app.theme().colors().text, true),
108 }
109 };
110
111 let settings = ThemeSettings::get_global(app);
112 let text_style = TextStyle {
113 color,
114 font_family: settings.buffer_font.family.clone(),
115 font_features: settings.buffer_font.features.clone(),
116 font_fallbacks: settings.buffer_font.fallbacks.clone(),
117 font_size: rems(0.875).into(),
118 font_weight: settings.buffer_font.weight,
119 line_height: relative(1.3),
120 ..TextStyle::default()
121 };
122
123 let mut editor_style = EditorStyle {
124 background: app.theme().colors().toolbar_background,
125 local_player: app.theme().players().local(),
126 text: text_style,
127 ..EditorStyle::default()
128 };
129 if use_syntax {
130 editor_style.syntax = app.theme().syntax().clone();
131 }
132
133 EditorElement::new(editor, editor_style)
134}
135
136/// This element makes all search inputs align as if they were in the same column
137pub(crate) fn alignment_element() -> Div {
138 div().size_5().flex_none().ml_0p5()
139}