items.rs

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