1use std::{path::Path, sync::Arc};
2
3use gpui::{EventEmitter, FocusHandle, Focusable};
4use ui::{
5 App, Button, ButtonCommon, ButtonStyle, Clickable, Context, FluentBuilder, InteractiveElement,
6 KeyBinding, Label, LabelCommon, LabelSize, ParentElement, Render, SharedString, Styled as _,
7 TintColor, Window, div, h_flex, v_flex,
8};
9use zed_actions::workspace::OpenWithSystem;
10
11use crate::Item;
12
13/// A view to display when a certain buffer fails to open.
14#[derive(Debug)]
15pub struct InvalidItemView {
16 /// Which path was attempted to open.
17 pub abs_path: Arc<Path>,
18 /// An error message, happened when opening the buffer.
19 pub error: SharedString,
20 is_local: bool,
21 focus_handle: FocusHandle,
22}
23
24impl InvalidItemView {
25 pub fn new(
26 abs_path: &Path,
27 is_local: bool,
28 e: &anyhow::Error,
29 _: &mut Window,
30 cx: &mut App,
31 ) -> Self {
32 Self {
33 is_local,
34 abs_path: Arc::from(abs_path),
35 error: format!("{}", e.root_cause()).into(),
36 focus_handle: cx.focus_handle(),
37 }
38 }
39}
40
41impl Item for InvalidItemView {
42 type Event = ();
43
44 fn tab_content_text(&self, mut detail: usize, _: &App) -> SharedString {
45 // Ensure we always render at least the filename.
46 detail += 1;
47
48 let path = self.abs_path.as_ref();
49
50 let mut prefix = path;
51 while detail > 0 {
52 if let Some(parent) = prefix.parent() {
53 prefix = parent;
54 detail -= 1;
55 } else {
56 break;
57 }
58 }
59
60 let path = if detail > 0 {
61 path
62 } else {
63 path.strip_prefix(prefix).unwrap_or(path)
64 };
65
66 SharedString::new(path.to_string_lossy())
67 }
68}
69
70impl EventEmitter<()> for InvalidItemView {}
71
72impl Focusable for InvalidItemView {
73 fn focus_handle(&self, _: &App) -> FocusHandle {
74 self.focus_handle.clone()
75 }
76}
77
78impl Render for InvalidItemView {
79 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl gpui::IntoElement {
80 let abs_path = self.abs_path.clone();
81 let path0 = self.abs_path.clone();
82 let path1 = self.abs_path.clone();
83
84 v_flex()
85 .size_full()
86 .track_focus(&self.focus_handle(cx))
87 .flex_none()
88 .justify_center()
89 .overflow_hidden()
90 .key_context("InvalidBuffer")
91 .child(
92 h_flex().size_full().justify_center().items_center().child(
93 v_flex()
94 .gap_2()
95 .max_w_96()
96 .child(h_flex().justify_center().child("Could not open file"))
97 .child(
98 h_flex().justify_center().child(
99 div()
100 .whitespace_normal()
101 .text_center()
102 .child(Label::new(self.error.clone()).size(LabelSize::Small)),
103 ),
104 )
105 .when(self.is_local, |contents| {
106 contents
107 .child(
108 h_flex().justify_center().child(
109 Button::new("open-with-system", "Open in Default App")
110 .on_click(move |_, _, cx| {
111 cx.open_with_system(&abs_path);
112 })
113 .style(ButtonStyle::Outlined)
114 .key_binding(KeyBinding::for_action(
115 &OpenWithSystem,
116 window,
117 cx,
118 )),
119 ),
120 )
121 .child(
122 h_flex()
123 .justify_center()
124 .child(
125 Button::new(
126 "open-with-encoding",
127 "Open With a Different Encoding",
128 )
129 .style(ButtonStyle::Outlined)
130 .on_click(
131 move |_, window, cx| {
132 window.dispatch_action(
133 Box::new(
134 zed_actions::encodings_ui::Toggle(
135 path0.clone(),
136 ),
137 ),
138 cx,
139 )
140 },
141 ),
142 )
143 .child(
144 Button::new(
145 "accept-risk-and-open",
146 "Accept the Risk and Open",
147 )
148 .style(ButtonStyle::Tinted(TintColor::Warning))
149 .on_click(
150 move |_, window, cx| {
151 window.dispatch_action(
152 Box::new(
153 zed_actions::encodings_ui::ForceOpen(
154 path1.clone(),
155 ),
156 ),
157 cx,
158 );
159 },
160 ),
161 ),
162 )
163 }),
164 ),
165 )
166 }
167}