From a26b0667885a76f9eb2558e0581729fa9c222774 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 3 Nov 2021 17:27:51 +0100 Subject: [PATCH 1/8] Introduce a status bar and add the cursor position to it Co-Authored-By: Nathan Sobo Co-Authored-By: Max Brunsfeld --- crates/editor/src/lib.rs | 15 ++++ crates/gpui/src/app.rs | 6 ++ crates/theme/src/lib.rs | 9 ++ crates/workspace/src/items.rs | 84 +++++++++++++++++- crates/workspace/src/lib.rs | 26 +++++- crates/workspace/src/status_bar.rs | 127 ++++++++++++++++++++++++++++ crates/zed/assets/themes/_base.toml | 8 +- 7 files changed, 269 insertions(+), 6 deletions(-) create mode 100644 crates/workspace/src/status_bar.rs diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 56f93eb5af88bc827b576936103aa178fa88903b..1f03133c84d3c1f03751b3f397b65b70ab0a4021 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -2219,6 +2219,21 @@ impl Editor { .map(|(set_id, _)| *set_id) } + pub fn last_selection(&self, cx: &AppContext) -> Selection { + if let Some(pending_selection) = self.pending_selection.as_ref() { + pending_selection.clone() + } else { + let buffer = self.buffer.read(cx); + let last_selection = buffer + .selection_set(self.selection_set_id) + .unwrap() + .point_selections(buffer) + .max_by_key(|s| s.id) + .unwrap(); + last_selection + } + } + pub fn selections_in_range<'a>( &'a self, set_id: SelectionSetId, diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index dc7e4d19b584a1650bd97a4da20b7844444ff927..a41ec8c96bc1d4aa2ba65e57bcd2a7a761f6068c 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -2456,6 +2456,12 @@ impl UpdateModel for RenderContext<'_, V> { } } +impl ReadView for RenderContext<'_, V> { + fn read_view(&self, handle: &ViewHandle) -> &T { + self.app.read_view(handle) + } +} + impl AsRef for ViewContext<'_, M> { fn as_ref(&self) -> &AppContext { &self.app.cx diff --git a/crates/theme/src/lib.rs b/crates/theme/src/lib.rs index 2a0abc4395011c5a954c1af1a827981a5800af39..eb60f6cf3a5d8f93da8bc11e6636c34510529d7e 100644 --- a/crates/theme/src/lib.rs +++ b/crates/theme/src/lib.rs @@ -35,6 +35,7 @@ pub struct Workspace { pub pane_divider: Border, pub left_sidebar: Sidebar, pub right_sidebar: Sidebar, + pub status_bar: StatusBar, } #[derive(Clone, Deserialize, Default)] @@ -88,6 +89,14 @@ pub struct SidebarItem { pub height: f32, } +#[derive(Deserialize, Default)] +pub struct StatusBar { + #[serde(flatten)] + pub container: ContainerStyle, + pub height: f32, + pub cursor_position: TextStyle, +} + #[derive(Deserialize, Default)] pub struct ChatPanel { #[serde(flatten)] diff --git a/crates/workspace/src/items.rs b/crates/workspace/src/items.rs index 0b4b5f0d51719843d6ff61fc6ca5720784e8f196..8692b072d48884116f9574620db32bf27d2f7ca9 100644 --- a/crates/workspace/src/items.rs +++ b/crates/workspace/src/items.rs @@ -1,11 +1,16 @@ use super::{Item, ItemView}; -use crate::Settings; +use crate::{status_bar::StatusItemView, Settings}; use anyhow::Result; +use buffer::{Point, ToOffset}; use editor::{Editor, EditorSettings, Event}; -use gpui::{fonts::TextStyle, AppContext, ModelHandle, Task, ViewContext}; +use gpui::{ + elements::*, fonts::TextStyle, AppContext, Entity, ModelHandle, RenderContext, Subscription, + Task, View, ViewContext, ViewHandle, +}; use language::{Buffer, File as _}; use postage::watch; use project::{ProjectPath, Worktree}; +use std::fmt::Write; use std::path::Path; impl Item for Buffer { @@ -156,3 +161,78 @@ impl ItemView for Editor { self.buffer().read(cx).has_conflict() } } + +pub struct CursorPosition { + position: Option, + selected_count: usize, + settings: watch::Receiver, + _observe_active_editor: Option, +} + +impl CursorPosition { + pub fn new(settings: watch::Receiver) -> Self { + Self { + position: None, + selected_count: 0, + settings, + _observe_active_editor: None, + } + } + + fn update_position(&mut self, editor: ViewHandle, cx: &mut ViewContext) { + let editor = editor.read(cx); + + let last_selection = editor.last_selection(cx); + self.position = Some(last_selection.head()); + if last_selection.is_empty() { + self.selected_count = 0; + } else { + let buffer = editor.buffer().read(cx); + let start = last_selection.start.to_offset(buffer); + let end = last_selection.end.to_offset(buffer); + self.selected_count = end - start; + } + cx.notify(); + } +} + +impl Entity for CursorPosition { + type Event = (); +} + +impl View for CursorPosition { + fn ui_name() -> &'static str { + "CursorPosition" + } + + fn render(&mut self, _: &mut RenderContext) -> ElementBox { + if let Some(position) = self.position { + let theme = &self.settings.borrow().theme.workspace.status_bar; + let mut text = format!("{},{}", position.row + 1, position.column + 1); + if self.selected_count > 0 { + write!(text, " ({} selected)", self.selected_count).unwrap(); + } + Label::new(text, theme.cursor_position.clone()).boxed() + } else { + Empty::new().boxed() + } + } +} + +impl StatusItemView for CursorPosition { + fn set_active_pane_item( + &mut self, + active_pane_item: Option<&dyn crate::ItemViewHandle>, + cx: &mut ViewContext, + ) { + if let Some(editor) = active_pane_item.and_then(|item| item.to_any().downcast::()) { + self._observe_active_editor = Some(cx.observe(&editor, Self::update_position)); + self.update_position(editor, cx); + } else { + self.position = None; + self._observe_active_editor = None; + } + + cx.notify(); + } +} diff --git a/crates/workspace/src/lib.rs b/crates/workspace/src/lib.rs index ec1f39e48019ab90fc1826b20cea777173b4b285..08d26e29a2bc1a589ec74fd10d22263ef1f85679 100644 --- a/crates/workspace/src/lib.rs +++ b/crates/workspace/src/lib.rs @@ -3,15 +3,16 @@ pub mod pane; pub mod pane_group; pub mod settings; pub mod sidebar; +mod status_bar; use anyhow::Result; -use language::{Buffer, LanguageRegistry}; use client::{Authenticate, ChannelList, Client, UserStore}; use gpui::{ action, elements::*, json::to_string_pretty, keymap::Binding, platform::CursorStyle, AnyViewHandle, AppContext, ClipboardItem, Entity, ModelHandle, MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, WeakModelHandle, }; +use language::{Buffer, LanguageRegistry}; use log::error; pub use pane::*; pub use pane_group::*; @@ -26,6 +27,8 @@ use std::{ sync::Arc, }; +use crate::status_bar::StatusBar; + action!(OpenNew, WorkspaceParams); action!(Save); action!(DebugElements); @@ -311,6 +314,7 @@ pub struct Workspace { right_sidebar: Sidebar, panes: Vec>, active_pane: ViewHandle, + status_bar: ViewHandle, project: ModelHandle, items: Vec>, loading_items: HashMap< @@ -345,6 +349,13 @@ impl Workspace { .detach(); cx.focus(&pane); + let cursor_position = cx.add_view(|_| items::CursorPosition::new(params.settings.clone())); + let status_bar = cx.add_view(|cx| { + let mut status_bar = StatusBar::new(&pane, params.settings.clone(), cx); + status_bar.add_right_item(cursor_position, cx); + status_bar + }); + let mut current_user = params.user_store.read(cx).watch_current_user().clone(); let mut connection_status = params.client.status().clone(); let _observe_current_user = cx.spawn_weak(|this, mut cx| async move { @@ -367,6 +378,7 @@ impl Workspace { center: PaneGroup::new(pane.id()), panes: vec![pane.clone()], active_pane: pane.clone(), + status_bar, settings: params.settings.clone(), client: params.client.clone(), user_store: params.user_store.clone(), @@ -824,6 +836,9 @@ impl Workspace { fn activate_pane(&mut self, pane: ViewHandle, cx: &mut ViewContext) { self.active_pane = pane; + self.status_bar.update(cx, |status_bar, cx| { + status_bar.set_active_pane(&self.active_pane, cx); + }); cx.focus(&self.active_pane); cx.notify(); } @@ -1017,7 +1032,14 @@ impl View for Workspace { content.add_child(Flexible::new(0.8, element).boxed()); } content.add_child( - Expanded::new(1.0, self.center.render(&settings.theme)).boxed(), + Flex::column() + .with_child( + Expanded::new(1.0, self.center.render(&settings.theme)) + .boxed(), + ) + .with_child(ChildView::new(self.status_bar.id()).boxed()) + .expanded(1.) + .boxed(), ); if let Some(element) = self.right_sidebar.render_active_item(&settings, cx) diff --git a/crates/workspace/src/status_bar.rs b/crates/workspace/src/status_bar.rs new file mode 100644 index 0000000000000000000000000000000000000000..970078dcb50dfa78a2913107a8029dd492230957 --- /dev/null +++ b/crates/workspace/src/status_bar.rs @@ -0,0 +1,127 @@ +use crate::{ItemViewHandle, Pane, Settings}; +use gpui::{ + elements::*, ElementBox, Entity, MutableAppContext, RenderContext, Subscription, View, + ViewContext, ViewHandle, +}; +use postage::watch; + +pub trait StatusItemView: View { + fn set_active_pane_item( + &mut self, + active_pane_item: Option<&dyn crate::ItemViewHandle>, + cx: &mut ViewContext, + ); +} + +trait StatusItemViewHandle { + fn id(&self) -> usize; + fn set_active_pane_item( + &self, + active_pane_item: Option<&dyn ItemViewHandle>, + cx: &mut MutableAppContext, + ); +} + +pub struct StatusBar { + left_items: Vec>, + right_items: Vec>, + active_pane: ViewHandle, + _observe_active_pane: Subscription, + settings: watch::Receiver, +} + +impl Entity for StatusBar { + type Event = (); +} + +impl View for StatusBar { + fn ui_name() -> &'static str { + "StatusBar" + } + + fn render(&mut self, _: &mut RenderContext) -> ElementBox { + let theme = &self.settings.borrow().theme.workspace.status_bar; + Flex::row() + .with_children( + self.left_items + .iter() + .map(|i| ChildView::new(i.id()).aligned().boxed()), + ) + .with_child(Empty::new().expanded(1.).boxed()) + .with_children( + self.right_items + .iter() + .map(|i| ChildView::new(i.id()).aligned().boxed()), + ) + .contained() + .with_style(theme.container) + .constrained() + .with_height(theme.height) + .boxed() + } +} + +impl StatusBar { + pub fn new( + active_pane: &ViewHandle, + settings: watch::Receiver, + cx: &mut ViewContext, + ) -> Self { + let mut this = Self { + left_items: Default::default(), + right_items: Default::default(), + active_pane: active_pane.clone(), + _observe_active_pane: cx + .observe(active_pane, |this, _, cx| this.update_active_pane_item(cx)), + settings, + }; + this.update_active_pane_item(cx); + this + } + + pub fn add_left_item(&mut self, item: ViewHandle, cx: &mut ViewContext) + where + T: 'static + StatusItemView, + { + self.left_items.push(Box::new(item)); + cx.notify(); + } + + pub fn add_right_item(&mut self, item: ViewHandle, cx: &mut ViewContext) + where + T: 'static + StatusItemView, + { + self.right_items.push(Box::new(item)); + cx.notify(); + } + + pub fn set_active_pane(&mut self, active_pane: &ViewHandle, cx: &mut ViewContext) { + self.active_pane = active_pane.clone(); + self._observe_active_pane = + cx.observe(active_pane, |this, _, cx| this.update_active_pane_item(cx)); + self.update_active_pane_item(cx); + } + + fn update_active_pane_item(&mut self, cx: &mut ViewContext) { + let active_pane_item = self.active_pane.read(cx).active_item(); + for item in self.left_items.iter().chain(&self.right_items) { + item.set_active_pane_item(active_pane_item.as_deref(), cx); + } + } +} + +impl StatusItemViewHandle for ViewHandle { + fn id(&self) -> usize { + self.id() + } + + fn set_active_pane_item( + &self, + active_pane_item: Option<&dyn ItemViewHandle>, + cx: &mut MutableAppContext, + ) { + self.update(cx, |this, cx| { + this.set_active_pane_item(active_pane_item, cx) + }); + } +} diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index d384b85d68c93cb7c7bcce4fc477f73970926fd1..a1a2397f9a5c60159e014bb93c0226eed38f9f32 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -60,6 +60,11 @@ border = { width = 1, color = "$border.0", right = true } extends = "$workspace.sidebar" border = { width = 1, color = "$border.0", left = true } +[workspace.status_bar] +padding = { left = 6, right = 6 } +height = 24 +cursor_position = "$text.2" + [panel] padding = { top = 12, left = 12, bottom = 12, right = 12 } @@ -164,7 +169,7 @@ corner_radius = 6 [project_panel] extends = "$panel" -padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2 +padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2 [project_panel.entry] text = "$text.1" @@ -226,7 +231,6 @@ line_number = "$text.2.color" line_number_active = "$text.0.color" selection = "$selection.host" guest_selections = "$selection.guests" - error_underline = "$status.bad" warning_underline = "$status.warn" info_underline = "$status.info" From 9dc3c7426069fd4cf1a76f340b99551362480db7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 3 Nov 2021 17:51:57 +0100 Subject: [PATCH 2/8] Make resolving selections generic Co-Authored-By: Nathan Sobo Co-Authored-By: Max Brunsfeld --- crates/buffer/src/anchor.rs | 49 +++++-------- crates/buffer/src/lib.rs | 29 +++++--- crates/buffer/src/selection.rs | 28 +++----- crates/buffer/src/tests.rs | 6 +- crates/editor/src/lib.rs | 124 +++++++++++++++------------------ crates/language/src/lib.rs | 4 +- crates/language/src/tests.rs | 2 +- crates/project/src/worktree.rs | 2 +- 8 files changed, 112 insertions(+), 132 deletions(-) diff --git a/crates/buffer/src/anchor.rs b/crates/buffer/src/anchor.rs index d1577230cc49ba4a28c57e358ca57fbe9eb38c26..da47d8924b3b655967119054f0a36f44fbcfc2c5 100644 --- a/crates/buffer/src/anchor.rs +++ b/crates/buffer/src/anchor.rs @@ -1,3 +1,5 @@ +use crate::rope::TextDimension; + use super::{Buffer, Content, FromAnchor, FullOffset, Point, ToOffset}; use anyhow::Result; use std::{ @@ -183,30 +185,21 @@ impl AnchorRangeMap { Self { version, entries } } - pub fn full_offset_ranges(&self) -> impl Iterator, &T)> { - self.entries - .iter() - .map(|(range, value)| (range.start.0..range.end.0, value)) - } - - pub fn point_ranges<'a>( + pub fn ranges<'a, D>( &'a self, content: impl Into> + 'a, - ) -> impl Iterator, &'a T)> + 'a { + ) -> impl Iterator, &'a T)> + 'a + where + D: 'a + TextDimension<'a>, + { let content = content.into(); - content - .summaries_for_anchor_ranges(self) - .map(move |(range, value)| ((range.start.lines..range.end.lines), value)) + content.summaries_for_anchor_ranges(self) } - pub fn offset_ranges<'a>( - &'a self, - content: impl Into> + 'a, - ) -> impl Iterator, &'a T)> + 'a { - let content = content.into(); - content - .summaries_for_anchor_ranges(self) - .map(move |(range, value)| ((range.start.bytes..range.end.bytes), value)) + pub fn full_offset_ranges(&self) -> impl Iterator, &T)> { + self.entries + .iter() + .map(|(range, value)| (range.start.0..range.end.0, value)) } } @@ -248,18 +241,12 @@ impl AnchorRangeSet { self.0.version() } - pub fn offset_ranges<'a>( - &'a self, - content: impl Into> + 'a, - ) -> impl Iterator> + 'a { - self.0.offset_ranges(content).map(|(range, _)| range) - } - - pub fn point_ranges<'a>( - &'a self, - content: impl Into> + 'a, - ) -> impl Iterator> + 'a { - self.0.point_ranges(content).map(|(range, _)| range) + pub fn ranges<'a, D, C>(&'a self, content: C) -> impl 'a + Iterator> + where + D: 'a + TextDimension<'a>, + C: 'a + Into>, + { + self.0.ranges(content).map(|(range, _)| range) } } diff --git a/crates/buffer/src/lib.rs b/crates/buffer/src/lib.rs index bf97574d34a33116530ba85917463499579a78f2..85847468c8dac66a370b5e11d5a3b0945f0ef0a6 100644 --- a/crates/buffer/src/lib.rs +++ b/crates/buffer/src/lib.rs @@ -1486,10 +1486,14 @@ impl Buffer { Ok(selections) } - pub fn selection_ranges<'a>(&'a self, set_id: SelectionSetId) -> Result>> { + #[cfg(test)] + pub fn selection_ranges<'a, D>(&'a self, set_id: SelectionSetId) -> Result>> + where + D: 'a + TextDimension<'a>, + { Ok(self .selection_set(set_id)? - .offset_selections(self) + .selections(self) .map(move |selection| { if selection.reversed { selection.end..selection.start @@ -1500,9 +1504,13 @@ impl Buffer { .collect()) } - pub fn all_selection_ranges<'a>( + #[cfg(test)] + pub fn all_selection_ranges<'a, D>( &'a self, - ) -> impl 'a + Iterator>)> { + ) -> impl 'a + Iterator>)> + where + D: 'a + TextDimension<'a>, + { self.selections .keys() .map(move |set_id| (*set_id, self.selection_ranges(*set_id).unwrap())) @@ -1751,12 +1759,15 @@ impl<'a> Content<'a> { }) } - fn summaries_for_anchor_ranges( + fn summaries_for_anchor_ranges( &self, map: &'a AnchorRangeMap, - ) -> impl Iterator, &'a T)> { + ) -> impl Iterator, &'a T)> + where + D: TextDimension<'a>, + { let cx = Some(map.version.clone()); - let mut summary = TextSummary::default(); + let mut summary = D::default(); let mut rope_cursor = self.visible_text.cursor(0); let mut cursor = self.fragments.cursor::<(VersionedFullOffset, usize)>(); map.entries.iter().map(move |(range, value)| { @@ -1775,7 +1786,7 @@ impl<'a> Content<'a> { } else { 0 }; - summary += rope_cursor.summary::(cursor.start().1 + overshoot); + summary.add_assign(&rope_cursor.summary::(cursor.start().1 + overshoot)); let start_summary = summary.clone(); cursor.seek_forward(&VersionedFullOffset::Offset(*end_offset), *end_bias, &cx); @@ -1784,7 +1795,7 @@ impl<'a> Content<'a> { } else { 0 }; - summary += rope_cursor.summary::(cursor.start().1 + overshoot); + summary.add_assign(&rope_cursor.summary::(cursor.start().1 + overshoot)); let end_summary = summary.clone(); (start_summary..end_summary, value) diff --git a/crates/buffer/src/selection.rs b/crates/buffer/src/selection.rs index c55a5f423ba62309445fc75b0df771d1a15ff748..8d804c522d0e23b5287a292c9e3a3dd3c6a899e3 100644 --- a/crates/buffer/src/selection.rs +++ b/crates/buffer/src/selection.rs @@ -1,3 +1,5 @@ +use crate::rope::TextDimension; + use super::{AnchorRangeMap, Buffer, Content, Point, ToOffset, ToPoint}; use std::{cmp::Ordering, ops::Range, sync::Arc}; @@ -97,27 +99,13 @@ impl SelectionSet { self.selections.len() } - pub fn offset_selections<'a>( - &'a self, - content: impl Into> + 'a, - ) -> impl 'a + Iterator> { - self.selections - .offset_ranges(content) - .map(|(range, state)| Selection { - id: state.id, - start: range.start, - end: range.end, - reversed: state.reversed, - goal: state.goal, - }) - } - - pub fn point_selections<'a>( - &'a self, - content: impl Into> + 'a, - ) -> impl 'a + Iterator> { + pub fn selections<'a, D, C>(&'a self, content: C) -> impl 'a + Iterator> + where + D: 'a + TextDimension<'a>, + C: 'a + Into>, + { self.selections - .point_ranges(content) + .ranges(content) .map(|(range, state)| Selection { id: state.id, start: range.start, diff --git a/crates/buffer/src/tests.rs b/crates/buffer/src/tests.rs index 68d6e6aa355a735f040ee3ce47a9c6190f29af21..2a8b938114904cf47fa7a9f2df128c8cd2dcbda9 100644 --- a/crates/buffer/src/tests.rs +++ b/crates/buffer/src/tests.rs @@ -576,9 +576,11 @@ fn test_random_concurrent_edits(mut rng: StdRng) { first_buffer.selection_sets().collect::>() ); assert_eq!( - buffer.all_selection_ranges().collect::>(), + buffer + .all_selection_ranges::() + .collect::>(), first_buffer - .all_selection_ranges() + .all_selection_ranges::() .collect::>() ); } diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 1f03133c84d3c1f03751b3f397b65b70ab0a4021..275fa804b120cdfcaad6db5a99f6f4c8747cf99e 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -5,6 +5,7 @@ pub mod movement; #[cfg(test)] mod test; +use buffer::rope::TextDimension; use clock::ReplicaId; pub use display_map::DisplayPoint; use display_map::*; @@ -512,7 +513,7 @@ impl Editor { return false; } - let mut selections = self.point_selections(cx).peekable(); + let mut selections = self.selections::(cx).peekable(); let first_cursor_top = selections .peek() .unwrap() @@ -564,7 +565,7 @@ impl Editor { cx: &mut ViewContext, ) -> bool { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let selections = self.point_selections(cx); + let selections = self.selections::(cx); let mut target_left = std::f32::INFINITY; let mut target_right = 0.0_f32; for selection in selections { @@ -655,7 +656,7 @@ impl Editor { fn end_selection(&mut self, cx: &mut ViewContext) { if let Some(selection) = self.pending_selection.take() { - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); let ix = self.selection_insertion_index(&selections, selection.start); selections.insert(ix, selection); self.update_selections(selections, false, cx); @@ -668,12 +669,12 @@ impl Editor { pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { if let Some(pending_selection) = self.pending_selection.take() { - if self.point_selections(cx).next().is_none() { + if self.selections::(cx).next().is_none() { self.update_selections(vec![pending_selection], true, cx); } } else { let selection_count = self.selection_count(cx); - let selections = self.point_selections(cx); + let selections = self.selections::(cx); let mut oldest_selection = selections.min_by_key(|s| s.id).unwrap().clone(); if selection_count == 1 { oldest_selection.start = oldest_selection.head().clone(); @@ -760,7 +761,7 @@ impl Editor { self.start_transaction(cx); let mut old_selections = SmallVec::<[_; 32]>::new(); { - let selections = self.point_selections(cx).collect::>(); + let selections = self.selections::(cx).collect::>(); let buffer = self.buffer.read(cx); for selection in selections.iter() { let start_point = selection.start; @@ -882,7 +883,7 @@ impl Editor { fn insert(&mut self, text: &str, cx: &mut ViewContext) { self.start_transaction(cx); - let old_selections = self.offset_selections(cx).collect::>(); + let old_selections = self.selections::(cx).collect::>(); let mut new_selections = Vec::new(); self.buffer.update(cx, |buffer, cx| { let edit_ranges = old_selections.iter().map(|s| s.start..s.end); @@ -913,7 +914,7 @@ impl Editor { } fn autoclose_pairs(&mut self, cx: &mut ViewContext) { - let selections = self.offset_selections(cx).collect::>(); + let selections = self.selections::(cx).collect::>(); let new_autoclose_pair_state = self.buffer.update(cx, |buffer, cx| { let autoclose_pair = buffer.language().and_then(|language| { let first_selection_start = selections.first().unwrap().start; @@ -970,7 +971,7 @@ impl Editor { fn skip_autoclose_end(&mut self, text: &str, cx: &mut ViewContext) -> bool { let old_selection_count = self.selection_count(cx); - let old_selections = self.offset_selections(cx).collect::>(); + let old_selections = self.selections::(cx).collect::>(); let autoclose_pair_state = if let Some(autoclose_pair_state) = self.autoclose_stack.last() { autoclose_pair_state } else { @@ -985,7 +986,7 @@ impl Editor { let buffer = self.buffer.read(cx); if old_selections .iter() - .zip(autoclose_pair_state.ranges.offset_ranges(buffer)) + .zip(autoclose_pair_state.ranges.ranges::(buffer)) .all(|(selection, autoclose_range)| { let autoclose_range_end = autoclose_range.end.to_offset(buffer); selection.is_empty() && selection.start == autoclose_range_end @@ -1021,7 +1022,7 @@ impl Editor { pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext) { self.start_transaction(cx); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); for selection in &mut selections { if selection.is_empty() { @@ -1041,7 +1042,7 @@ impl Editor { pub fn delete(&mut self, _: &Delete, cx: &mut ViewContext) { self.start_transaction(cx); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { if selection.is_empty() { let head = selection.head().to_display_point(&display_map, Bias::Left); @@ -1060,7 +1061,7 @@ impl Editor { pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext) { self.start_transaction(cx); let tab_size = self.build_settings.borrow()(cx).tab_size; - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); self.buffer.update(cx, |buffer, cx| { let mut last_indented_row = None; for selection in &mut selections { @@ -1101,7 +1102,7 @@ impl Editor { pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext) { self.start_transaction(cx); - let selections = self.point_selections(cx).collect::>(); + let selections = self.selections::(cx).collect::>(); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = self.buffer.read(cx); @@ -1176,7 +1177,7 @@ impl Editor { pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext) { self.start_transaction(cx); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = self.buffer.read(cx); @@ -1234,7 +1235,7 @@ impl Editor { pub fn move_line_up(&mut self, _: &MoveLineUp, cx: &mut ViewContext) { self.start_transaction(cx); - let selections = self.point_selections(cx).collect::>(); + let selections = self.selections::(cx).collect::>(); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = self.buffer.read(cx); @@ -1324,7 +1325,7 @@ impl Editor { pub fn move_line_down(&mut self, _: &MoveLineDown, cx: &mut ViewContext) { self.start_transaction(cx); - let selections = self.point_selections(cx).collect::>(); + let selections = self.selections::(cx).collect::>(); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = self.buffer.read(cx); @@ -1411,7 +1412,7 @@ impl Editor { pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext) { self.start_transaction(cx); let mut text = String::new(); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); let mut clipboard_selections = Vec::with_capacity(selections.len()); { let buffer = self.buffer.read(cx); @@ -1442,7 +1443,7 @@ impl Editor { } pub fn copy(&mut self, _: &Copy, cx: &mut ViewContext) { - let selections = self.point_selections(cx).collect::>(); + let selections = self.selections::(cx).collect::>(); let buffer = self.buffer.read(cx); let max_point = buffer.max_point(); let mut text = String::new(); @@ -1474,7 +1475,7 @@ impl Editor { if let Some(item) = cx.as_mut().read_from_clipboard() { let clipboard_text = item.text(); if let Some(mut clipboard_selections) = item.metadata::>() { - let mut selections = self.offset_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); let all_selections_were_entire_line = clipboard_selections.iter().all(|s| s.is_entire_line); if clipboard_selections.len() != selections.len() { @@ -1537,7 +1538,7 @@ impl Editor { pub fn move_left(&mut self, _: &MoveLeft, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { let start = selection.start.to_display_point(&display_map, Bias::Left); let end = selection.end.to_display_point(&display_map, Bias::Left); @@ -1559,7 +1560,7 @@ impl Editor { pub fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { let head = selection.head().to_display_point(&display_map, Bias::Left); let cursor = movement::left(&display_map, head) @@ -1573,7 +1574,7 @@ impl Editor { pub fn move_right(&mut self, _: &MoveRight, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { let start = selection.start.to_display_point(&display_map, Bias::Left); let end = selection.end.to_display_point(&display_map, Bias::Left); @@ -1595,7 +1596,7 @@ impl Editor { pub fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { let head = selection.head().to_display_point(&display_map, Bias::Left); let cursor = movement::right(&display_map, head) @@ -1614,7 +1615,7 @@ impl Editor { } let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { let start = selection.start.to_display_point(&display_map, Bias::Left); let end = selection.end.to_display_point(&display_map, Bias::Left); @@ -1634,7 +1635,7 @@ impl Editor { pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { let head = selection.head().to_display_point(&display_map, Bias::Left); let (head, goal) = movement::up(&display_map, head, selection.goal).unwrap(); @@ -1652,7 +1653,7 @@ impl Editor { } let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { let start = selection.start.to_display_point(&display_map, Bias::Left); let end = selection.end.to_display_point(&display_map, Bias::Left); @@ -1672,7 +1673,7 @@ impl Editor { pub fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { let head = selection.head().to_display_point(&display_map, Bias::Left); let (head, goal) = movement::down(&display_map, head, selection.goal).unwrap(); @@ -1689,7 +1690,7 @@ impl Editor { cx: &mut ViewContext, ) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { let head = selection.head().to_display_point(&display_map, Bias::Left); let new_head = movement::prev_word_boundary(&display_map, head).unwrap(); @@ -1708,7 +1709,7 @@ impl Editor { cx: &mut ViewContext, ) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { let head = selection.head().to_display_point(&display_map, Bias::Left); let new_head = movement::prev_word_boundary(&display_map, head).unwrap(); @@ -1726,7 +1727,7 @@ impl Editor { ) { self.start_transaction(cx); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { if selection.is_empty() { let head = selection.head().to_display_point(&display_map, Bias::Left); @@ -1747,7 +1748,7 @@ impl Editor { cx: &mut ViewContext, ) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { let head = selection.head().to_display_point(&display_map, Bias::Left); let new_head = movement::next_word_boundary(&display_map, head).unwrap(); @@ -1766,7 +1767,7 @@ impl Editor { cx: &mut ViewContext, ) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { let head = selection.head().to_display_point(&display_map, Bias::Left); let new_head = movement::next_word_boundary(&display_map, head).unwrap(); @@ -1784,7 +1785,7 @@ impl Editor { ) { self.start_transaction(cx); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { if selection.is_empty() { let head = selection.head().to_display_point(&display_map, Bias::Left); @@ -1805,7 +1806,7 @@ impl Editor { cx: &mut ViewContext, ) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { let head = selection.head().to_display_point(&display_map, Bias::Left); let new_head = movement::line_beginning(&display_map, head, true).unwrap(); @@ -1824,7 +1825,7 @@ impl Editor { cx: &mut ViewContext, ) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { let head = selection.head().to_display_point(&display_map, Bias::Left); let new_head = movement::line_beginning(&display_map, head, *toggle_indent).unwrap(); @@ -1847,7 +1848,7 @@ impl Editor { pub fn move_to_end_of_line(&mut self, _: &MoveToEndOfLine, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); { for selection in &mut selections { let head = selection.head().to_display_point(&display_map, Bias::Left); @@ -1864,7 +1865,7 @@ impl Editor { pub fn select_to_end_of_line(&mut self, _: &SelectToEndOfLine, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { let head = selection.head().to_display_point(&display_map, Bias::Left); let new_head = movement::line_end(&display_map, head).unwrap(); @@ -1900,7 +1901,7 @@ impl Editor { } pub fn select_to_beginning(&mut self, _: &SelectToBeginning, cx: &mut ViewContext) { - let mut selection = self.point_selections(cx).last().unwrap().clone(); + let mut selection = self.selections::(cx).last().unwrap().clone(); selection.set_head(Point::zero()); self.update_selections(vec![selection], true, cx); } @@ -1919,7 +1920,7 @@ impl Editor { } pub fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext) { - let mut selection = self.offset_selections(cx).last().unwrap().clone(); + let mut selection = self.selections::(cx).last().unwrap().clone(); selection.set_head(self.buffer.read(cx).len()); self.update_selections(vec![selection], true, cx); } @@ -1937,7 +1938,7 @@ impl Editor { pub fn select_line(&mut self, _: &SelectLine, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); let buffer = self.buffer.read(cx); let max_point = buffer.max_point(); for selection in &mut selections { @@ -1954,7 +1955,7 @@ impl Editor { _: &SplitSelectionIntoLines, cx: &mut ViewContext, ) { - let selections = self.point_selections(cx).collect::>(); + let selections = self.selections::(cx).collect::>(); let buffer = self.buffer.read(cx); let mut to_unfold = Vec::new(); @@ -2002,7 +2003,7 @@ impl Editor { fn add_selection(&mut self, above: bool, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let mut selections = self.point_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); let mut state = self.add_selections_state.take().unwrap_or_else(|| { let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone(); let range = oldest_selection.display_range(&display_map).sorted(); @@ -2098,7 +2099,7 @@ impl Editor { _: &SelectLargerSyntaxNode, cx: &mut ViewContext, ) { - let old_selections = self.offset_selections(cx).collect::>(); + let old_selections = self.selections::(cx).collect::>(); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = self.buffer.read(cx); @@ -2156,7 +2157,7 @@ impl Editor { _: &MoveToEnclosingBracket, cx: &mut ViewContext, ) { - let mut selections = self.offset_selections(cx).collect::>(); + let mut selections = self.selections::(cx).collect::>(); let buffer = self.buffer.read(cx.as_ref()); for selection in &mut selections { if let Some((open_range, close_range)) = @@ -2227,7 +2228,7 @@ impl Editor { let last_selection = buffer .selection_set(self.selection_set_id) .unwrap() - .point_selections(buffer) + .selections::(buffer) .max_by_key(|s| s.id) .unwrap(); last_selection @@ -2245,7 +2246,7 @@ impl Editor { let selections = buffer .selection_set(set_id) .unwrap() - .point_selections(buffer) + .selections::(buffer) .collect::>(); let start = range.start.to_buffer_point(&display_map, Bias::Left); let start_index = self.selection_insertion_index(&selections, start); @@ -2282,28 +2283,19 @@ impl Editor { } } - fn point_selections<'a>( + fn selections<'a, D>( &mut self, cx: &'a mut ViewContext, - ) -> impl 'a + Iterator> { - self.end_selection(cx); - let buffer = self.buffer.read(cx); - buffer - .selection_set(self.selection_set_id) - .unwrap() - .point_selections(buffer) - } - - fn offset_selections<'a>( - &mut self, - cx: &'a mut ViewContext, - ) -> impl 'a + Iterator> { + ) -> impl 'a + Iterator> + where + D: 'a + TextDimension<'a>, + { self.end_selection(cx); let buffer = self.buffer.read(cx); buffer .selection_set(self.selection_set_id) .unwrap() - .offset_selections(buffer) + .selections::(buffer) } fn selection_count(&mut self, cx: &mut ViewContext) -> usize { @@ -2344,7 +2336,7 @@ impl Editor { if selections.len() == autoclose_pair_state.ranges.len() { selections .iter() - .zip(autoclose_pair_state.ranges.point_ranges(buffer)) + .zip(autoclose_pair_state.ranges.ranges::(buffer)) .all(|(selection, autoclose_range)| { let head = selection.head().to_point(&*buffer); autoclose_range.start <= head && autoclose_range.end >= head @@ -2404,7 +2396,7 @@ impl Editor { pub fn fold(&mut self, _: &Fold, cx: &mut ViewContext) { let mut fold_ranges = Vec::new(); - let selections = self.point_selections(cx).collect::>(); + let selections = self.selections::(cx).collect::>(); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); for selection in selections { let range = selection.display_range(&display_map).sorted(); @@ -2427,7 +2419,7 @@ impl Editor { } pub fn unfold(&mut self, _: &Unfold, cx: &mut ViewContext) { - let selections = self.point_selections(cx).collect::>(); + let selections = self.selections::(cx).collect::>(); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = self.buffer.read(cx); let ranges = selections @@ -2488,7 +2480,7 @@ impl Editor { } pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext) { - let selections = self.point_selections(cx); + let selections = self.selections::(cx); let ranges = selections.map(|s| s.start..s.end).collect(); self.fold_ranges(ranges, cx); } diff --git a/crates/language/src/lib.rs b/crates/language/src/lib.rs index 893dc164a66d21f5b980f4b0f4c1075528a59d6c..fb259a704d044602280922301655cb12e7fb1fbb 100644 --- a/crates/language/src/lib.rs +++ b/crates/language/src/lib.rs @@ -893,7 +893,7 @@ impl Buffer { if let Some(inserted) = request.inserted.as_ref() { let inserted_row_ranges = contiguous_ranges( inserted - .point_ranges(&snapshot) + .ranges::(&snapshot) .flat_map(|range| range.start.row..range.end.row + 1), max_rows_between_yields, ); @@ -941,7 +941,7 @@ impl Buffer { for selection_set_id in &selection_set_ids { if let Ok(set) = self.selection_set(*selection_set_id) { let new_selections = set - .point_selections(&*self) + .selections::(&*self) .map(|selection| { if selection.start.column == 0 { let delta = Point::new( diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 0bc81b08086d5e4c309cb9a2c7c94391ff2f59ba..b8dfbdfd50f4b4384f63e1d346ad196344c6edad 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -311,7 +311,7 @@ fn test_autoindent_moves_selections(cx: &mut MutableAppContext) { let selection_ranges = buffer .selection_set(selection_set_id) .unwrap() - .point_selections(&buffer) + .selections::(&buffer) .map(|selection| selection.point_range(&buffer)) .collect::>(); diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 13f33bfdb14e0d84d7950ebf72f6ee9f22d542ac..d7336b782396cb7222e5932e143c7f224e5e5dd7 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3538,7 +3538,7 @@ mod tests { let cursor_positions = buffer .selection_set(selection_set_id) .unwrap() - .point_selections(&*buffer) + .selections::(&*buffer) .map(|selection| { assert_eq!(selection.start, selection.end); selection.start From c07d794249bf9e516b279563c0213e5248439380 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 3 Nov 2021 19:07:06 +0100 Subject: [PATCH 3/8] Avoid ending the pending selection until updating selections Co-Authored-By: Max Brunsfeld --- crates/buffer/src/anchor.rs | 47 +++++------- crates/buffer/src/lib.rs | 30 +++++--- crates/buffer/src/selection.rs | 30 ++++---- crates/editor/src/lib.rs | 131 +++++++++++++++++++++------------ crates/language/src/lib.rs | 9 ++- crates/workspace/src/items.rs | 26 ++++--- 6 files changed, 159 insertions(+), 114 deletions(-) diff --git a/crates/buffer/src/anchor.rs b/crates/buffer/src/anchor.rs index da47d8924b3b655967119054f0a36f44fbcfc2c5..bb0e7b386a92fc53de013fa2187e406e1ea01438 100644 --- a/crates/buffer/src/anchor.rs +++ b/crates/buffer/src/anchor.rs @@ -113,6 +113,14 @@ impl Anchor { buffer.anchor_after(self) } } + + pub fn summary<'a, D, C>(&self, content: C) -> D + where + D: TextDimension<'a>, + C: Into>, + { + content.into().summary_for_anchor(self) + } } impl AnchorMap { @@ -124,24 +132,15 @@ impl AnchorMap { self.entries.len() } - pub fn offsets<'a>( - &'a self, - content: impl Into> + 'a, - ) -> impl Iterator + 'a { - let content = content.into(); - content - .summaries_for_anchors(self) - .map(move |(sum, value)| (sum.bytes, value)) - } - - pub fn points<'a>( - &'a self, - content: impl Into> + 'a, - ) -> impl Iterator + 'a { + pub fn iter<'a, D, C>(&'a self, content: C) -> impl Iterator + 'a + where + D: 'a + TextDimension<'a>, + C: 'a + Into>, + { let content = content.into(); content .summaries_for_anchors(self) - .map(move |(sum, value)| (sum.lines, value)) + .map(move |(sum, value)| (sum, value)) } } @@ -154,18 +153,12 @@ impl AnchorSet { self.0.len() } - pub fn offsets<'a>( - &'a self, - content: impl Into> + 'a, - ) -> impl Iterator + 'a { - self.0.offsets(content).map(|(offset, _)| offset) - } - - pub fn points<'a>( - &'a self, - content: impl Into> + 'a, - ) -> impl Iterator + 'a { - self.0.points(content).map(|(point, _)| point) + pub fn iter<'a, D, C>(&'a self, content: C) -> impl Iterator + 'a + where + D: 'a + TextDimension<'a>, + C: 'a + Into>, + { + self.0.iter(content).map(|(position, _)| position) } } diff --git a/crates/buffer/src/lib.rs b/crates/buffer/src/lib.rs index 85847468c8dac66a370b5e11d5a3b0945f0ef0a6..a01430d3096b7c6fb808a4bd4add82027fd04a25 100644 --- a/crates/buffer/src/lib.rs +++ b/crates/buffer/src/lib.rs @@ -1719,7 +1719,10 @@ impl<'a> Content<'a> { result } - fn summary_for_anchor(&self, anchor: &Anchor) -> TextSummary { + fn summary_for_anchor(&self, anchor: &Anchor) -> D + where + D: TextDimension<'a>, + { let cx = Some(anchor.version.clone()); let mut cursor = self.fragments.cursor::<(VersionedFullOffset, usize)>(); cursor.seek( @@ -1735,16 +1738,19 @@ impl<'a> Content<'a> { self.text_summary_for_range(0..cursor.start().1 + overshoot) } - fn text_summary_for_range(&self, range: Range) -> TextSummary { + fn text_summary_for_range(&self, range: Range) -> D + where + D: TextDimension<'a>, + { self.visible_text.cursor(range.start).summary(range.end) } - fn summaries_for_anchors( - &self, - map: &'a AnchorMap, - ) -> impl Iterator { + fn summaries_for_anchors(&self, map: &'a AnchorMap) -> impl Iterator + where + D: TextDimension<'a>, + { let cx = Some(map.version.clone()); - let mut summary = TextSummary::default(); + let mut summary = D::default(); let mut rope_cursor = self.visible_text.cursor(0); let mut cursor = self.fragments.cursor::<(VersionedFullOffset, usize)>(); map.entries.iter().map(move |((offset, bias), value)| { @@ -1754,7 +1760,7 @@ impl<'a> Content<'a> { } else { 0 }; - summary += rope_cursor.summary::(cursor.start().1 + overshoot); + summary.add_assign(&rope_cursor.summary(cursor.start().1 + overshoot)); (summary.clone(), value) }) } @@ -1928,7 +1934,7 @@ impl<'a> Content<'a> { fn point_for_offset(&self, offset: usize) -> Result { if offset <= self.len() { - Ok(self.text_summary_for_range(0..offset).lines) + Ok(self.text_summary_for_range(0..offset)) } else { Err(anyhow!("offset out of bounds")) } @@ -2316,13 +2322,13 @@ impl ToOffset for usize { impl ToOffset for Anchor { fn to_offset<'a>(&self, content: impl Into>) -> usize { - content.into().summary_for_anchor(self).bytes + content.into().summary_for_anchor(self) } } impl<'a> ToOffset for &'a Anchor { fn to_offset<'b>(&self, content: impl Into>) -> usize { - content.into().summary_for_anchor(self).bytes + content.into().summary_for_anchor(self) } } @@ -2332,7 +2338,7 @@ pub trait ToPoint { impl ToPoint for Anchor { fn to_point<'a>(&self, content: impl Into>) -> Point { - content.into().summary_for_anchor(self).lines + content.into().summary_for_anchor(self) } } diff --git a/crates/buffer/src/selection.rs b/crates/buffer/src/selection.rs index 8d804c522d0e23b5287a292c9e3a3dd3c6a899e3..dfd4522368067a9c095c9957014f3a0e0c90be84 100644 --- a/crates/buffer/src/selection.rs +++ b/crates/buffer/src/selection.rs @@ -36,18 +36,28 @@ pub struct SelectionState { pub goal: SelectionGoal, } -impl Selection { - pub fn is_empty(&self) -> bool { - self.start == self.end +impl Selection { + pub fn head(&self) -> T { + if self.reversed { + self.start.clone() + } else { + self.end.clone() + } } - pub fn head(&self) -> T { + pub fn tail(&self) -> T { if self.reversed { - self.start + self.end.clone() } else { - self.end + self.start.clone() } } +} + +impl Selection { + pub fn is_empty(&self) -> bool { + self.start == self.end + } pub fn set_head(&mut self, head: T) { if head.cmp(&self.tail()) < Ordering::Equal { @@ -65,14 +75,6 @@ impl Selection { } } - pub fn tail(&self) -> T { - if self.reversed { - self.end - } else { - self.start - } - } - pub fn point_range(&self, buffer: &Buffer) -> Range { let start = self.start.to_point(buffer); let end = self.end.to_point(buffer); diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 275fa804b120cdfcaad6db5a99f6f4c8747cf99e..7cccd0438ace198680b7ab4c87dee794e5e83c6c 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -21,7 +21,8 @@ use smallvec::SmallVec; use smol::Timer; use std::{ cell::RefCell, - cmp, iter, mem, + cmp::{self, Ordering}, + iter, mem, ops::{Range, RangeInclusive}, rc::Rc, sync::Arc, @@ -293,7 +294,7 @@ pub struct Editor { buffer: ModelHandle, display_map: ModelHandle, selection_set_id: SelectionSetId, - pending_selection: Option>, + pending_selection: Option>, next_selection_id: usize, add_selections_state: Option, autoclose_stack: Vec, @@ -618,10 +619,11 @@ impl Editor { } let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let cursor = position.to_buffer_point(&display_map, Bias::Left); + let buffer = self.buffer.read(cx); + let cursor = buffer.anchor_before(position.to_buffer_point(&display_map, Bias::Left)); let selection = Selection { id: post_inc(&mut self.next_selection_id), - start: cursor, + start: cursor.clone(), end: cursor, reversed: false, goal: SelectionGoal::None, @@ -642,9 +644,22 @@ impl Editor { cx: &mut ViewContext, ) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let cursor = position.to_buffer_point(&display_map, Bias::Left); - if let Some(selection) = self.pending_selection.as_mut() { - selection.set_head(cursor); + if let Some(pending_selection) = self.pending_selection.as_mut() { + let buffer = self.buffer.read(cx); + let cursor = buffer.anchor_before(position.to_buffer_point(&display_map, Bias::Left)); + if cursor.cmp(&pending_selection.tail(), buffer).unwrap() < Ordering::Equal { + if !pending_selection.reversed { + pending_selection.end = pending_selection.start.clone(); + pending_selection.reversed = true; + } + pending_selection.start = cursor; + } else { + if pending_selection.reversed { + pending_selection.start = pending_selection.end.clone(); + pending_selection.reversed = false; + } + pending_selection.end = cursor; + } } else { log::error!("update_selection dispatched with no pending selection"); return; @@ -655,12 +670,8 @@ impl Editor { } fn end_selection(&mut self, cx: &mut ViewContext) { - if let Some(selection) = self.pending_selection.take() { - let mut selections = self.selections::(cx).collect::>(); - let ix = self.selection_insertion_index(&selections, selection.start); - selections.insert(ix, selection); - self.update_selections(selections, false, cx); - } + let selections = self.selections::(cx).collect::>(); + self.update_selections(selections, false, cx); } pub fn is_selecting(&self) -> bool { @@ -669,13 +680,27 @@ impl Editor { pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { if let Some(pending_selection) = self.pending_selection.take() { + let buffer = self.buffer.read(cx); + let pending_selection = Selection { + id: pending_selection.id, + start: pending_selection.start.to_point(buffer), + end: pending_selection.end.to_point(buffer), + reversed: pending_selection.reversed, + goal: pending_selection.goal, + }; if self.selections::(cx).next().is_none() { self.update_selections(vec![pending_selection], true, cx); } } else { - let selection_count = self.selection_count(cx); let selections = self.selections::(cx); - let mut oldest_selection = selections.min_by_key(|s| s.id).unwrap().clone(); + let mut selection_count = 0; + let mut oldest_selection = selections + .min_by_key(|s| { + selection_count += 1; + s.id + }) + .unwrap() + .clone(); if selection_count == 1 { oldest_selection.start = oldest_selection.head().clone(); oldest_selection.end = oldest_selection.head().clone(); @@ -970,7 +995,6 @@ impl Editor { } fn skip_autoclose_end(&mut self, text: &str, cx: &mut ViewContext) -> bool { - let old_selection_count = self.selection_count(cx); let old_selections = self.selections::(cx).collect::>(); let autoclose_pair_state = if let Some(autoclose_pair_state) = self.autoclose_stack.last() { autoclose_pair_state @@ -981,7 +1005,7 @@ impl Editor { return false; } - debug_assert_eq!(old_selection_count, autoclose_pair_state.ranges.len()); + debug_assert_eq!(old_selections.len(), autoclose_pair_state.ranges.len()); let buffer = self.buffer.read(cx); if old_selections @@ -2220,21 +2244,6 @@ impl Editor { .map(|(set_id, _)| *set_id) } - pub fn last_selection(&self, cx: &AppContext) -> Selection { - if let Some(pending_selection) = self.pending_selection.as_ref() { - pending_selection.clone() - } else { - let buffer = self.buffer.read(cx); - let last_selection = buffer - .selection_set(self.selection_set_id) - .unwrap() - .selections::(buffer) - .max_by_key(|s| s.id) - .unwrap(); - last_selection - } - } - pub fn selections_in_range<'a>( &'a self, set_id: SelectionSetId, @@ -2251,10 +2260,14 @@ impl Editor { let start = range.start.to_buffer_point(&display_map, Bias::Left); let start_index = self.selection_insertion_index(&selections, start); let pending_selection = if set_id.replica_id == self.buffer.read(cx).replica_id() { - self.pending_selection.as_ref().and_then(|s| { - let selection_range = s.display_range(&display_map); - if selection_range.start <= range.end || selection_range.end <= range.end { - Some(selection_range) + self.pending_selection.as_ref().and_then(|pending| { + let mut selection_start = pending.start.to_display_point(&display_map, Bias::Left); + let mut selection_end = pending.end.to_display_point(&display_map, Bias::Left); + if pending.reversed { + mem::swap(&mut selection_start, &mut selection_end); + } + if selection_start <= range.end || selection_end <= range.end { + Some(selection_start..selection_end) } else { None } @@ -2283,25 +2296,46 @@ impl Editor { } } - fn selections<'a, D>( - &mut self, - cx: &'a mut ViewContext, - ) -> impl 'a + Iterator> + pub fn selections<'a, D>(&self, cx: &'a AppContext) -> impl 'a + Iterator> where - D: 'a + TextDimension<'a>, + D: 'a + TextDimension<'a> + Ord, { - self.end_selection(cx); let buffer = self.buffer.read(cx); - buffer + let mut selections = buffer .selection_set(self.selection_set_id) .unwrap() .selections::(buffer) - } + .peekable(); + let mut pending_selection = self.pending_selection.clone().map(|selection| Selection { + id: selection.id, + start: selection.start.summary::(buffer), + end: selection.end.summary::(buffer), + reversed: selection.reversed, + goal: selection.goal, + }); + iter::from_fn(move || { + if let Some(pending) = pending_selection.as_mut() { + while let Some(next_selection) = selections.peek() { + if pending.start <= next_selection.end && pending.end >= next_selection.start { + let next_selection = selections.next().unwrap(); + if next_selection.start < pending.start { + pending.start = next_selection.start; + } + if next_selection.end > pending.end { + pending.end = next_selection.end; + } + } else if next_selection.end < pending.start { + return selections.next(); + } else { + break; + } + } - fn selection_count(&mut self, cx: &mut ViewContext) -> usize { - self.end_selection(cx); - let buffer = self.buffer.read(cx); - buffer.selection_set(self.selection_set_id).unwrap().len() + pending_selection.take() + } else { + selections.next() + } + }) } fn update_selections( @@ -2329,6 +2363,7 @@ impl Editor { } } + self.pending_selection = None; self.add_selections_state = None; self.select_larger_syntax_node_stack.clear(); while let Some(autoclose_pair_state) = self.autoclose_stack.last() { diff --git a/crates/language/src/lib.rs b/crates/language/src/lib.rs index fb259a704d044602280922301655cb12e7fb1fbb..7e38cc0c16f924105ca26bb557fcf241e5e2d3a0 100644 --- a/crates/language/src/lib.rs +++ b/crates/language/src/lib.rs @@ -830,9 +830,14 @@ impl Buffer { for request in autoindent_requests { let old_to_new_rows = request .edited - .points(&request.before_edit) + .iter::(&request.before_edit) .map(|point| point.row) - .zip(request.edited.points(&snapshot).map(|point| point.row)) + .zip( + request + .edited + .iter::(&snapshot) + .map(|point| point.row), + ) .collect::>(); let mut old_suggestions = HashMap::::default(); diff --git a/crates/workspace/src/items.rs b/crates/workspace/src/items.rs index 8692b072d48884116f9574620db32bf27d2f7ca9..d6c20c8fc08455de1886efb9e4ea81c4b52c8935 100644 --- a/crates/workspace/src/items.rs +++ b/crates/workspace/src/items.rs @@ -1,7 +1,7 @@ use super::{Item, ItemView}; use crate::{status_bar::StatusItemView, Settings}; use anyhow::Result; -use buffer::{Point, ToOffset}; +use buffer::{Point, Selection, ToPoint}; use editor::{Editor, EditorSettings, Event}; use gpui::{ elements::*, fonts::TextStyle, AppContext, Entity, ModelHandle, RenderContext, Subscription, @@ -181,17 +181,21 @@ impl CursorPosition { fn update_position(&mut self, editor: ViewHandle, cx: &mut ViewContext) { let editor = editor.read(cx); - - let last_selection = editor.last_selection(cx); - self.position = Some(last_selection.head()); - if last_selection.is_empty() { - self.selected_count = 0; - } else { - let buffer = editor.buffer().read(cx); - let start = last_selection.start.to_offset(buffer); - let end = last_selection.end.to_offset(buffer); - self.selected_count = end - start; + let buffer = editor.buffer().read(cx); + + self.selected_count = 0; + let mut last_selection: Option> = None; + for selection in editor.selections::(cx) { + self.selected_count += selection.end - selection.start; + if last_selection + .as_ref() + .map_or(true, |last_selection| selection.id > last_selection.id) + { + last_selection = Some(selection); + } } + self.position = last_selection.map(|s| s.head().to_point(buffer)); + cx.notify(); } } From 941d935c4aac22344aaf13237af5713151a2132f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 3 Nov 2021 19:15:54 +0100 Subject: [PATCH 4/8] End pending selection when starting a transaction Co-Authored-By: Max Brunsfeld Co-Authored-By: Nathan Sobo --- crates/editor/src/lib.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 7cccd0438ace198680b7ab4c87dee794e5e83c6c..4a1f6392e4eb8469c9f500c352a98c5b39bcb458 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -670,8 +670,10 @@ impl Editor { } fn end_selection(&mut self, cx: &mut ViewContext) { - let selections = self.selections::(cx).collect::>(); - self.update_selections(selections, false, cx); + if self.pending_selection.is_some() { + let selections = self.selections::(cx).collect::>(); + self.update_selections(selections, false, cx); + } } pub fn is_selecting(&self) -> bool { @@ -2404,7 +2406,8 @@ impl Editor { cx.notify(); } - fn start_transaction(&self, cx: &mut ViewContext) { + fn start_transaction(&mut self, cx: &mut ViewContext) { + self.end_selection(cx); self.buffer.update(cx, |buffer, _| { buffer .start_transaction(Some(self.selection_set_id)) From 8b5089c75964ddf699a5065e2cb9fa75e56f86be Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 3 Nov 2021 12:33:16 -0700 Subject: [PATCH 5/8] In the status bar, show the diagnostic under the cursor --- crates/language/src/lib.rs | 8 ++- crates/theme/src/lib.rs | 4 ++ crates/workspace/src/items.rs | 80 ++++++++++++++++++++++++++++- crates/workspace/src/lib.rs | 2 + crates/zed/assets/themes/_base.toml | 4 ++ 5 files changed, 95 insertions(+), 3 deletions(-) diff --git a/crates/language/src/lib.rs b/crates/language/src/lib.rs index 7e38cc0c16f924105ca26bb557fcf241e5e2d3a0..a4ac4977761e57a15f3500ebbe8606c539477728 100644 --- a/crates/language/src/lib.rs +++ b/crates/language/src/lib.rs @@ -780,10 +780,14 @@ impl Buffer { Ok(Operation::UpdateDiagnostics(self.diagnostics.clone())) } - pub fn diagnostics_in_range<'a, T: 'a + ToOffset>( + pub fn diagnostics_in_range<'a, T, O>( &'a self, range: Range, - ) -> impl Iterator, &Diagnostic)> + 'a { + ) -> impl Iterator, &Diagnostic)> + 'a + where + T: 'a + ToOffset, + O: 'a + FromAnchor, + { let content = self.content(); self.diagnostics .intersecting_ranges(range, content, true) diff --git a/crates/theme/src/lib.rs b/crates/theme/src/lib.rs index eb60f6cf3a5d8f93da8bc11e6636c34510529d7e..22d1dcd95885356b67634bcfb13ad287d56be96e 100644 --- a/crates/theme/src/lib.rs +++ b/crates/theme/src/lib.rs @@ -95,6 +95,10 @@ pub struct StatusBar { pub container: ContainerStyle, pub height: f32, pub cursor_position: TextStyle, + pub diagnostic_error: TextStyle, + pub diagnostic_warning: TextStyle, + pub diagnostic_information: TextStyle, + pub diagnostic_hint: TextStyle, } #[derive(Deserialize, Default)] diff --git a/crates/workspace/src/items.rs b/crates/workspace/src/items.rs index d6c20c8fc08455de1886efb9e4ea81c4b52c8935..8a3ce6b5c89b85d68c8eb92575497fadfc8a330e 100644 --- a/crates/workspace/src/items.rs +++ b/crates/workspace/src/items.rs @@ -7,7 +7,7 @@ use gpui::{ elements::*, fonts::TextStyle, AppContext, Entity, ModelHandle, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, }; -use language::{Buffer, File as _}; +use language::{Buffer, Diagnostic, DiagnosticSeverity, File as _}; use postage::watch; use project::{ProjectPath, Worktree}; use std::fmt::Write; @@ -240,3 +240,81 @@ impl StatusItemView for CursorPosition { cx.notify(); } } + +pub struct DiagnosticMessage { + settings: watch::Receiver, + diagnostic: Option, + _observe_active_editor: Option, +} + +impl DiagnosticMessage { + pub fn new(settings: watch::Receiver) -> Self { + Self { + diagnostic: None, + settings, + _observe_active_editor: None, + } + } + + fn update(&mut self, editor: ViewHandle, cx: &mut ViewContext) { + let editor = editor.read(cx); + let cursor_position = editor + .selections::(cx) + .max_by_key(|selection| selection.id) + .unwrap() + .head(); + let new_diagnostic = editor + .buffer() + .read(cx) + .diagnostics_in_range::(cursor_position..cursor_position) + .min_by_key(|(range, diagnostic)| (diagnostic.severity, range.len())) + .map(|(_, diagnostic)| diagnostic.clone()); + if new_diagnostic != self.diagnostic { + self.diagnostic = new_diagnostic; + cx.notify(); + } + } +} + +impl Entity for DiagnosticMessage { + type Event = (); +} + +impl View for DiagnosticMessage { + fn ui_name() -> &'static str { + "DiagnosticMessage" + } + + fn render(&mut self, _: &mut RenderContext) -> ElementBox { + if let Some(diagnostic) = &self.diagnostic { + let theme = &self.settings.borrow().theme.workspace.status_bar; + let style = match diagnostic.severity { + DiagnosticSeverity::ERROR => theme.diagnostic_error.clone(), + DiagnosticSeverity::WARNING => theme.diagnostic_warning.clone(), + DiagnosticSeverity::INFORMATION => theme.diagnostic_information.clone(), + DiagnosticSeverity::HINT => theme.diagnostic_hint.clone(), + _ => Default::default(), + }; + Label::new(diagnostic.message.replace('\n', " "), style).boxed() + } else { + Empty::new().boxed() + } + } +} + +impl StatusItemView for DiagnosticMessage { + fn set_active_pane_item( + &mut self, + active_pane_item: Option<&dyn crate::ItemViewHandle>, + cx: &mut ViewContext, + ) { + if let Some(editor) = active_pane_item.and_then(|item| item.to_any().downcast::()) { + self._observe_active_editor = Some(cx.observe(&editor, Self::update)); + self.update(editor, cx); + } else { + self.diagnostic = Default::default(); + self._observe_active_editor = None; + } + cx.notify(); + } +} diff --git a/crates/workspace/src/lib.rs b/crates/workspace/src/lib.rs index 08d26e29a2bc1a589ec74fd10d22263ef1f85679..0ca07d2246b19ac125284dba4403719fde05f10d 100644 --- a/crates/workspace/src/lib.rs +++ b/crates/workspace/src/lib.rs @@ -350,8 +350,10 @@ impl Workspace { cx.focus(&pane); let cursor_position = cx.add_view(|_| items::CursorPosition::new(params.settings.clone())); + let diagnostic = cx.add_view(|_| items::DiagnosticMessage::new(params.settings.clone())); let status_bar = cx.add_view(|cx| { let mut status_bar = StatusBar::new(&pane, params.settings.clone(), cx); + status_bar.add_left_item(diagnostic, cx); status_bar.add_right_item(cursor_position, cx); status_bar }); diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index a1a2397f9a5c60159e014bb93c0226eed38f9f32..1697422a8d116c4de44b2d980cc7eecf093352b2 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -64,6 +64,10 @@ border = { width = 1, color = "$border.0", left = true } padding = { left = 6, right = 6 } height = 24 cursor_position = "$text.2" +diagnostic_error = { extends = "$text.2", color = "$status.bad" } +diagnostic_warning = { extends = "$text.2", color = "$status.warn" } +diagnostic_information = { extends = "$text.2", color = "$status.info" } +diagnostic_hint = { extends = "$text.2", color = "$status.info" } [panel] padding = { top = 12, left = 12, bottom = 12, right = 12 } From 9384823e471e9ede386803cecb6da3062262d606 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 3 Nov 2021 12:36:57 -0700 Subject: [PATCH 6/8] Remove logging in worktree tests --- crates/project/src/worktree.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index d7336b782396cb7222e5932e143c7f224e5e5dd7..10025b305431e042f251f5dd61bbfeb30091083d 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3569,8 +3569,6 @@ mod tests { #[gpui::test] async fn test_language_server_diagnostics(mut cx: gpui::TestAppContext) { - simplelog::SimpleLogger::init(log::LevelFilter::Info, Default::default()).unwrap(); - let (language_server_config, mut fake_server) = LanguageServerConfig::fake(cx.background()).await; let mut languages = LanguageRegistry::new(); From baf636a4a427006bede32743ba8477d55f8b7efb Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 3 Nov 2021 13:49:39 -0700 Subject: [PATCH 7/8] Extend empty diagnostic ranges at the ends of lines --- crates/language/src/lib.rs | 4 ++ crates/language/src/tests.rs | 89 ++++++++++++++++++++++++++++++------ 2 files changed, 78 insertions(+), 15 deletions(-) diff --git a/crates/language/src/lib.rs b/crates/language/src/lib.rs index a4ac4977761e57a15f3500ebbe8606c539477728..58f0ecad6455edbffb54760f3d5fec84cb7e1f24 100644 --- a/crates/language/src/lib.rs +++ b/crates/language/src/lib.rs @@ -751,6 +751,10 @@ impl Buffer { if range.start == range.end { range.end.column += 1; range.end = content.clip_point_utf16(range.end, Bias::Right); + if range.start == range.end && range.end.column > 0 { + range.start.column -= 1; + range.start = content.clip_point_utf16(range.start, Bias::Left); + } } Some(( range, diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index b8dfbdfd50f4b4384f63e1d346ad196344c6edad..b83fb4c671ac6ddc3cb87bd89fc29c8215e7353f 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -633,24 +633,83 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) { ] ); }); +} - fn chunks_with_diagnostics( - buffer: &Buffer, - range: Range, - ) -> Vec<(String, Option)> { - let mut chunks: Vec<(String, Option)> = Vec::new(); - for chunk in buffer.snapshot().highlighted_text_for_range(range) { - if chunks - .last() - .map_or(false, |prev_chunk| prev_chunk.1 == chunk.diagnostic) - { - chunks.last_mut().unwrap().0.push_str(chunk.text); - } else { - chunks.push((chunk.text.to_string(), chunk.diagnostic)); - } +#[gpui::test] +async fn test_empty_diagnostic_ranges(mut cx: gpui::TestAppContext) { + cx.add_model(|cx| { + let text = concat!( + "let one = ;\n", // + "let two = \n", + "let three = 3;\n", + ); + + let mut buffer = Buffer::new(0, text, cx); + buffer.set_language(Some(Arc::new(rust_lang())), None, cx); + buffer + .update_diagnostics( + None, + vec![ + lsp::Diagnostic { + range: lsp::Range::new( + lsp::Position::new(0, 10), + lsp::Position::new(0, 10), + ), + severity: Some(lsp::DiagnosticSeverity::ERROR), + message: "syntax error 1".to_string(), + ..Default::default() + }, + lsp::Diagnostic { + range: lsp::Range::new( + lsp::Position::new(1, 10), + lsp::Position::new(1, 10), + ), + severity: Some(lsp::DiagnosticSeverity::ERROR), + message: "syntax error 2".to_string(), + ..Default::default() + }, + ], + cx, + ) + .unwrap(); + + // An empty range is extended forward to include the following character. + // At the end of a line, an empty range is extended backward to include + // the preceding character. + let chunks = chunks_with_diagnostics(&buffer, 0..buffer.len()); + assert_eq!( + chunks + .iter() + .map(|(s, d)| (s.as_str(), *d)) + .collect::>(), + &[ + ("let one = ", None), + (";", Some(lsp::DiagnosticSeverity::ERROR)), + ("\nlet two =", None), + (" ", Some(lsp::DiagnosticSeverity::ERROR)), + ("\nlet three = 3;\n", None) + ] + ); + buffer + }); +} + +fn chunks_with_diagnostics( + buffer: &Buffer, + range: Range, +) -> Vec<(String, Option)> { + let mut chunks: Vec<(String, Option)> = Vec::new(); + for chunk in buffer.snapshot().highlighted_text_for_range(range) { + if chunks + .last() + .map_or(false, |prev_chunk| prev_chunk.1 == chunk.diagnostic) + { + chunks.last_mut().unwrap().0.push_str(chunk.text); + } else { + chunks.push((chunk.text.to_string(), chunk.diagnostic)); } - chunks } + chunks } #[test] From 1ec31738e63da9934645753ce5c9885b8d8dd65e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 3 Nov 2021 14:15:22 -0700 Subject: [PATCH 8/8] Improve styling of diagnostic status bar item --- crates/theme/src/lib.rs | 8 ++++---- crates/workspace/src/items.rs | 28 +++++++++++++++++++--------- crates/zed/assets/icons/warning.svg | 3 +++ crates/zed/assets/themes/_base.toml | 10 +++++----- 4 files changed, 31 insertions(+), 18 deletions(-) create mode 100644 crates/zed/assets/icons/warning.svg diff --git a/crates/theme/src/lib.rs b/crates/theme/src/lib.rs index 22d1dcd95885356b67634bcfb13ad287d56be96e..c4fbcc3031a8c5d6c9f7265686172f63242a1e7b 100644 --- a/crates/theme/src/lib.rs +++ b/crates/theme/src/lib.rs @@ -95,10 +95,10 @@ pub struct StatusBar { pub container: ContainerStyle, pub height: f32, pub cursor_position: TextStyle, - pub diagnostic_error: TextStyle, - pub diagnostic_warning: TextStyle, - pub diagnostic_information: TextStyle, - pub diagnostic_hint: TextStyle, + pub diagnostic_icon_size: f32, + pub diagnostic_icon_spacing: f32, + pub diagnostic_icon_color: Color, + pub diagnostic_message: TextStyle, } #[derive(Deserialize, Default)] diff --git a/crates/workspace/src/items.rs b/crates/workspace/src/items.rs index 8a3ce6b5c89b85d68c8eb92575497fadfc8a330e..61e2d65986227d7e1b8dfb65ca40b578261201c5 100644 --- a/crates/workspace/src/items.rs +++ b/crates/workspace/src/items.rs @@ -7,7 +7,7 @@ use gpui::{ elements::*, fonts::TextStyle, AppContext, Entity, ModelHandle, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, }; -use language::{Buffer, Diagnostic, DiagnosticSeverity, File as _}; +use language::{Buffer, Diagnostic, File as _}; use postage::watch; use project::{ProjectPath, Worktree}; use std::fmt::Write; @@ -288,14 +288,24 @@ impl View for DiagnosticMessage { fn render(&mut self, _: &mut RenderContext) -> ElementBox { if let Some(diagnostic) = &self.diagnostic { let theme = &self.settings.borrow().theme.workspace.status_bar; - let style = match diagnostic.severity { - DiagnosticSeverity::ERROR => theme.diagnostic_error.clone(), - DiagnosticSeverity::WARNING => theme.diagnostic_warning.clone(), - DiagnosticSeverity::INFORMATION => theme.diagnostic_information.clone(), - DiagnosticSeverity::HINT => theme.diagnostic_hint.clone(), - _ => Default::default(), - }; - Label::new(diagnostic.message.replace('\n', " "), style).boxed() + Flex::row() + .with_child( + Svg::new("icons/warning.svg") + .with_color(theme.diagnostic_icon_color) + .constrained() + .with_height(theme.diagnostic_icon_size) + .contained() + .with_margin_right(theme.diagnostic_icon_spacing) + .boxed(), + ) + .with_child( + Label::new( + diagnostic.message.replace('\n', " "), + theme.diagnostic_message.clone(), + ) + .boxed(), + ) + .boxed() } else { Empty::new().boxed() } diff --git a/crates/zed/assets/icons/warning.svg b/crates/zed/assets/icons/warning.svg new file mode 100644 index 0000000000000000000000000000000000000000..09ebc28669a6ed2a92fd43a36e60c6e3f330ceb6 --- /dev/null +++ b/crates/zed/assets/icons/warning.svg @@ -0,0 +1,3 @@ + + + diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index 1697422a8d116c4de44b2d980cc7eecf093352b2..77167df8b0a9fa5971f45e83e190278efc1c374e 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -64,10 +64,10 @@ border = { width = 1, color = "$border.0", left = true } padding = { left = 6, right = 6 } height = 24 cursor_position = "$text.2" -diagnostic_error = { extends = "$text.2", color = "$status.bad" } -diagnostic_warning = { extends = "$text.2", color = "$status.warn" } -diagnostic_information = { extends = "$text.2", color = "$status.info" } -diagnostic_hint = { extends = "$text.2", color = "$status.info" } +diagnostic_message = "$text.2" +diagnostic_icon_size = 18 +diagnostic_icon_spacing = 8 +diagnostic_icon_color = "$text.2.color" [panel] padding = { top = 12, left = 12, bottom = 12, right = 12 } @@ -173,7 +173,7 @@ corner_radius = 6 [project_panel] extends = "$panel" -padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2 +padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2 [project_panel.entry] text = "$text.1"