1use crate::{Anchor, Autoscroll, Editor, Event, NavigationData, ToOffset, ToPoint as _};
2use anyhow::{anyhow, Result};
3use gpui::{
4 elements::*, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Subscription,
5 Task, View, ViewContext, ViewHandle,
6};
7use language::{Bias, Buffer, Diagnostic, File as _};
8use project::{File, Project, ProjectEntryId, ProjectPath};
9use rpc::proto::{self, update_view};
10use std::{fmt::Write, path::PathBuf};
11use text::{Point, Selection};
12use util::ResultExt;
13use workspace::{
14 FollowableItem, Item, ItemHandle, ItemNavHistory, ProjectItem, Settings, StatusItemView,
15};
16
17impl FollowableItem for Editor {
18 fn for_state_message(
19 pane: ViewHandle<workspace::Pane>,
20 project: ModelHandle<Project>,
21 state: &mut Option<proto::view::Variant>,
22 cx: &mut MutableAppContext,
23 ) -> Option<Task<Result<ViewHandle<Self>>>> {
24 let state = if matches!(state, Some(proto::view::Variant::Editor(_))) {
25 if let Some(proto::view::Variant::Editor(state)) = state.take() {
26 state
27 } else {
28 unreachable!()
29 }
30 } else {
31 return None;
32 };
33
34 let buffer = project.update(cx, |project, cx| {
35 project.open_buffer_by_id(state.buffer_id, cx)
36 });
37 Some(cx.spawn(|mut cx| async move {
38 let buffer = buffer.await?;
39 Ok(pane
40 .read_with(&cx, |pane, cx| {
41 pane.items_of_type::<Self>().find(|editor| {
42 editor.read(cx).buffer.read(cx).as_singleton().as_ref() == Some(&buffer)
43 })
44 })
45 .unwrap_or_else(|| {
46 cx.add_view(pane.window_id(), |cx| {
47 Editor::for_buffer(buffer, Some(project), cx)
48 })
49 }))
50 }))
51 }
52
53 fn set_leader_replica_id(
54 &mut self,
55 leader_replica_id: Option<u16>,
56 cx: &mut ViewContext<Self>,
57 ) {
58 let prev_leader_replica_id = self.leader_replica_id;
59 self.leader_replica_id = leader_replica_id;
60 if self.leader_replica_id.is_some() {
61 self.show_local_selections = false;
62 self.buffer.update(cx, |buffer, cx| {
63 buffer.remove_active_selections(cx);
64 });
65 } else {
66 self.show_local_selections = true;
67 if let Some(leader_replica_id) = prev_leader_replica_id {
68 let selections = self
69 .buffer
70 .read(cx)
71 .snapshot(cx)
72 .remote_selections_in_range(&(Anchor::min()..Anchor::max()))
73 .filter_map(|(replica_id, selections)| {
74 if replica_id == leader_replica_id {
75 Some(selections)
76 } else {
77 None
78 }
79 })
80 .collect::<Vec<_>>();
81 if !selections.is_empty() {
82 self.set_selections(selections.into(), None, cx);
83 }
84 }
85 self.buffer.update(cx, |buffer, cx| {
86 if self.focused {
87 buffer.set_active_selections(&self.selections, cx);
88 }
89 });
90 }
91 cx.notify();
92 }
93
94 fn to_state_message(&self, cx: &AppContext) -> Option<proto::view::Variant> {
95 let buffer_id = self.buffer.read(cx).as_singleton()?.read(cx).remote_id();
96 Some(proto::view::Variant::Editor(proto::view::Editor {
97 buffer_id,
98 scroll_top: self
99 .scroll_top_anchor
100 .as_ref()
101 .map(|anchor| language::proto::serialize_anchor(&anchor.text_anchor)),
102 }))
103 }
104
105 fn to_update_message(
106 &self,
107 event: &Self::Event,
108 _: &AppContext,
109 ) -> Option<update_view::Variant> {
110 match event {
111 Event::ScrollPositionChanged => {
112 Some(update_view::Variant::Editor(update_view::Editor {
113 scroll_top: self
114 .scroll_top_anchor
115 .as_ref()
116 .map(|anchor| language::proto::serialize_anchor(&anchor.text_anchor)),
117 }))
118 }
119 _ => None,
120 }
121 }
122
123 fn apply_update_message(
124 &mut self,
125 message: update_view::Variant,
126 cx: &mut ViewContext<Self>,
127 ) -> Result<()> {
128 match message {
129 update_view::Variant::Editor(message) => {
130 if let Some(anchor) = message.scroll_top {
131 let anchor = language::proto::deserialize_anchor(anchor)
132 .ok_or_else(|| anyhow!("invalid scroll top"))?;
133 let anchor = {
134 let buffer = self.buffer.read(cx);
135 let buffer = buffer.read(cx);
136 let (excerpt_id, _, _) = buffer.as_singleton().unwrap();
137 buffer.anchor_in_excerpt(excerpt_id.clone(), anchor)
138 };
139 self.set_scroll_top_anchor(Some(anchor), cx);
140 } else {
141 self.set_scroll_top_anchor(None, cx);
142 }
143 }
144 }
145 Ok(())
146 }
147}
148
149impl Item for Editor {
150 fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) {
151 if let Some(data) = data.downcast_ref::<NavigationData>() {
152 let buffer = self.buffer.read(cx).read(cx);
153 let offset = if buffer.can_resolve(&data.anchor) {
154 data.anchor.to_offset(&buffer)
155 } else {
156 buffer.clip_offset(data.offset, Bias::Left)
157 };
158
159 drop(buffer);
160 let nav_history = self.nav_history.take();
161 self.select_ranges([offset..offset], Some(Autoscroll::Fit), cx);
162 self.nav_history = nav_history;
163 }
164 }
165
166 fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox {
167 let title = self.title(cx);
168 Label::new(title, style.label.clone()).boxed()
169 }
170
171 fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
172 File::from_dyn(self.buffer().read(cx).file(cx)).map(|file| ProjectPath {
173 worktree_id: file.worktree_id(cx),
174 path: file.path().clone(),
175 })
176 }
177
178 fn project_entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId> {
179 File::from_dyn(self.buffer().read(cx).file(cx)).and_then(|file| file.project_entry_id(cx))
180 }
181
182 fn clone_on_split(&self, cx: &mut ViewContext<Self>) -> Option<Self>
183 where
184 Self: Sized,
185 {
186 Some(self.clone(cx))
187 }
188
189 fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
190 self.nav_history = Some(history);
191 }
192
193 fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
194 let selection = self.newest_anchor_selection();
195 self.push_to_nav_history(selection.head(), None, cx);
196 }
197
198 fn is_dirty(&self, cx: &AppContext) -> bool {
199 self.buffer().read(cx).read(cx).is_dirty()
200 }
201
202 fn has_conflict(&self, cx: &AppContext) -> bool {
203 self.buffer().read(cx).read(cx).has_conflict()
204 }
205
206 fn can_save(&self, cx: &AppContext) -> bool {
207 !self.buffer().read(cx).is_singleton() || self.project_path(cx).is_some()
208 }
209
210 fn save(
211 &mut self,
212 project: ModelHandle<Project>,
213 cx: &mut ViewContext<Self>,
214 ) -> Task<Result<()>> {
215 let buffer = self.buffer().clone();
216 let buffers = buffer.read(cx).all_buffers();
217 let transaction = project.update(cx, |project, cx| project.format(buffers, true, cx));
218 cx.spawn(|this, mut cx| async move {
219 let transaction = transaction.await.log_err();
220 this.update(&mut cx, |editor, cx| {
221 editor.request_autoscroll(Autoscroll::Fit, cx)
222 });
223 buffer
224 .update(&mut cx, |buffer, cx| {
225 if let Some(transaction) = transaction {
226 if !buffer.is_singleton() {
227 buffer.push_transaction(&transaction.0);
228 }
229 }
230
231 buffer.save(cx)
232 })
233 .await?;
234 Ok(())
235 })
236 }
237
238 fn can_save_as(&self, cx: &AppContext) -> bool {
239 self.buffer().read(cx).is_singleton()
240 }
241
242 fn save_as(
243 &mut self,
244 project: ModelHandle<Project>,
245 abs_path: PathBuf,
246 cx: &mut ViewContext<Self>,
247 ) -> Task<Result<()>> {
248 let buffer = self
249 .buffer()
250 .read(cx)
251 .as_singleton()
252 .expect("cannot call save_as on an excerpt list")
253 .clone();
254
255 project.update(cx, |project, cx| {
256 project.save_buffer_as(buffer, abs_path, cx)
257 })
258 }
259
260 fn should_activate_item_on_event(event: &Event) -> bool {
261 matches!(event, Event::Activate)
262 }
263
264 fn should_close_item_on_event(event: &Event) -> bool {
265 matches!(event, Event::Closed)
266 }
267
268 fn should_update_tab_on_event(event: &Event) -> bool {
269 matches!(event, Event::Saved | Event::Dirtied | Event::TitleChanged)
270 }
271}
272
273impl ProjectItem for Editor {
274 type Item = Buffer;
275
276 fn for_project_item(
277 project: ModelHandle<Project>,
278 buffer: ModelHandle<Buffer>,
279 cx: &mut ViewContext<Self>,
280 ) -> Self {
281 Self::for_buffer(buffer, Some(project), cx)
282 }
283}
284
285pub struct CursorPosition {
286 position: Option<Point>,
287 selected_count: usize,
288 _observe_active_editor: Option<Subscription>,
289}
290
291impl CursorPosition {
292 pub fn new() -> Self {
293 Self {
294 position: None,
295 selected_count: 0,
296 _observe_active_editor: None,
297 }
298 }
299
300 fn update_position(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
301 let editor = editor.read(cx);
302 let buffer = editor.buffer().read(cx).snapshot(cx);
303
304 self.selected_count = 0;
305 let mut last_selection: Option<Selection<usize>> = None;
306 for selection in editor.local_selections::<usize>(cx) {
307 self.selected_count += selection.end - selection.start;
308 if last_selection
309 .as_ref()
310 .map_or(true, |last_selection| selection.id > last_selection.id)
311 {
312 last_selection = Some(selection);
313 }
314 }
315 self.position = last_selection.map(|s| s.head().to_point(&buffer));
316
317 cx.notify();
318 }
319}
320
321impl Entity for CursorPosition {
322 type Event = ();
323}
324
325impl View for CursorPosition {
326 fn ui_name() -> &'static str {
327 "CursorPosition"
328 }
329
330 fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
331 if let Some(position) = self.position {
332 let theme = &cx.global::<Settings>().theme.workspace.status_bar;
333 let mut text = format!("{},{}", position.row + 1, position.column + 1);
334 if self.selected_count > 0 {
335 write!(text, " ({} selected)", self.selected_count).unwrap();
336 }
337 Label::new(text, theme.cursor_position.clone()).boxed()
338 } else {
339 Empty::new().boxed()
340 }
341 }
342}
343
344impl StatusItemView for CursorPosition {
345 fn set_active_pane_item(
346 &mut self,
347 active_pane_item: Option<&dyn ItemHandle>,
348 cx: &mut ViewContext<Self>,
349 ) {
350 if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
351 self._observe_active_editor = Some(cx.observe(&editor, Self::update_position));
352 self.update_position(editor, cx);
353 } else {
354 self.position = None;
355 self._observe_active_editor = None;
356 }
357
358 cx.notify();
359 }
360}
361
362pub struct DiagnosticMessage {
363 diagnostic: Option<Diagnostic>,
364 _observe_active_editor: Option<Subscription>,
365}
366
367impl DiagnosticMessage {
368 pub fn new() -> Self {
369 Self {
370 diagnostic: None,
371 _observe_active_editor: None,
372 }
373 }
374
375 fn update(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
376 let editor = editor.read(cx);
377 let buffer = editor.buffer().read(cx);
378 let cursor_position = editor
379 .newest_selection_with_snapshot::<usize>(&buffer.read(cx))
380 .head();
381 let new_diagnostic = buffer
382 .read(cx)
383 .diagnostics_in_range::<_, usize>(cursor_position..cursor_position, false)
384 .filter(|entry| !entry.range.is_empty())
385 .min_by_key(|entry| (entry.diagnostic.severity, entry.range.len()))
386 .map(|entry| entry.diagnostic);
387 if new_diagnostic != self.diagnostic {
388 self.diagnostic = new_diagnostic;
389 cx.notify();
390 }
391 }
392}
393
394impl Entity for DiagnosticMessage {
395 type Event = ();
396}
397
398impl View for DiagnosticMessage {
399 fn ui_name() -> &'static str {
400 "DiagnosticMessage"
401 }
402
403 fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
404 if let Some(diagnostic) = &self.diagnostic {
405 let theme = &cx.global::<Settings>().theme.workspace.status_bar;
406 Label::new(
407 diagnostic.message.split('\n').next().unwrap().to_string(),
408 theme.diagnostic_message.clone(),
409 )
410 .boxed()
411 } else {
412 Empty::new().boxed()
413 }
414 }
415}
416
417impl StatusItemView for DiagnosticMessage {
418 fn set_active_pane_item(
419 &mut self,
420 active_pane_item: Option<&dyn ItemHandle>,
421 cx: &mut ViewContext<Self>,
422 ) {
423 if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
424 self._observe_active_editor = Some(cx.observe(&editor, Self::update));
425 self.update(editor, cx);
426 } else {
427 self.diagnostic = Default::default();
428 self._observe_active_editor = None;
429 }
430 cx.notify();
431 }
432}