1use std::{path::Path, sync::Arc};
2
3use gpui::{EventEmitter, FocusHandle, Focusable};
4use ui::{
5 h_flex, v_flex, App, Button, ButtonCommon, ButtonStyle, Clickable, Context, FluentBuilder,
6 InteractiveElement, KeyBinding, Label, LabelCommon, LabelSize, ParentElement, Render,
7 SharedString, Styled as _, TintColor, Window,
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().child(
93 v_flex()
94 .justify_center()
95 .gap_2()
96 .child(h_flex().justify_center().child("Could not open file"))
97 .child(
98 h_flex()
99 .justify_center()
100 .child(Label::new(self.error.clone()).size(LabelSize::Small)),
101 )
102 .when(self.is_local, |contents| {
103 contents
104 .child(
105 h_flex().justify_center().child(
106 Button::new("open-with-system", "Open in Default App")
107 .on_click(move |_, _, cx| {
108 cx.open_with_system(&abs_path);
109 })
110 .style(ButtonStyle::Outlined)
111 .key_binding(KeyBinding::for_action(
112 &OpenWithSystem,
113 window,
114 cx,
115 )),
116 ),
117 )
118 .child(
119 h_flex()
120 .justify_center()
121 .child(
122 Button::new(
123 "open-with-encoding",
124 "Open With a Different Encoding",
125 )
126 .style(ButtonStyle::Outlined)
127 .on_click(
128 move |_, window, cx| {
129 window.dispatch_action(
130 Box::new(
131 zed_actions::encodings_ui::Toggle(
132 path0.clone(),
133 ),
134 ),
135 cx,
136 )
137 },
138 ),
139 )
140 .child(
141 Button::new(
142 "accept-risk-and-open",
143 "Accept the Risk and Open",
144 )
145 .style(ButtonStyle::Tinted(TintColor::Warning))
146 .on_click(
147 move |_, window, cx| {
148 window.dispatch_action(
149 Box::new(
150 zed_actions::encodings_ui::ForceOpen(
151 path1.clone(),
152 ),
153 ),
154 cx,
155 );
156 },
157 ),
158 ),
159 )
160 }),
161 ),
162 )
163 }
164}