Merge pull request #221 from zed-industries/status-bar-errors

Add a status bar that shows the cursor position and the error under the cursor
This commit is contained in:
Nathan Sobo 2021-11-03 15:46:33 -06:00 committed by GitHub
commit 595dbd44ae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 699 additions and 242 deletions

View file

@ -1,3 +1,5 @@
use crate::rope::TextDimension;
use super::{Buffer, Content, FromAnchor, FullOffset, Point, ToOffset};
use anyhow::Result;
use std::{
@ -111,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<'a>>,
{
content.into().summary_for_anchor(self)
}
}
impl<T> AnchorMap<T> {
@ -122,24 +132,15 @@ impl<T> AnchorMap<T> {
self.entries.len()
}
pub fn offsets<'a>(
&'a self,
content: impl Into<Content<'a>> + 'a,
) -> impl Iterator<Item = (usize, &'a T)> + 'a {
pub fn iter<'a, D, C>(&'a self, content: C) -> impl Iterator<Item = (D, &'a T)> + 'a
where
D: 'a + TextDimension<'a>,
C: 'a + Into<Content<'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<Content<'a>> + 'a,
) -> impl Iterator<Item = (Point, &'a T)> + 'a {
let content = content.into();
content
.summaries_for_anchors(self)
.map(move |(sum, value)| (sum.lines, value))
.map(move |(sum, value)| (sum, value))
}
}
@ -152,18 +153,12 @@ impl AnchorSet {
self.0.len()
}
pub fn offsets<'a>(
&'a self,
content: impl Into<Content<'a>> + 'a,
) -> impl Iterator<Item = usize> + 'a {
self.0.offsets(content).map(|(offset, _)| offset)
}
pub fn points<'a>(
&'a self,
content: impl Into<Content<'a>> + 'a,
) -> impl Iterator<Item = Point> + 'a {
self.0.points(content).map(|(point, _)| point)
pub fn iter<'a, D, C>(&'a self, content: C) -> impl Iterator<Item = D> + 'a
where
D: 'a + TextDimension<'a>,
C: 'a + Into<Content<'a>>,
{
self.0.iter(content).map(|(position, _)| position)
}
}
@ -183,31 +178,22 @@ impl<T> AnchorRangeMap<T> {
Self { version, entries }
}
pub fn ranges<'a, D>(
&'a self,
content: impl Into<Content<'a>> + 'a,
) -> impl Iterator<Item = (Range<D>, &'a T)> + 'a
where
D: 'a + TextDimension<'a>,
{
let content = content.into();
content.summaries_for_anchor_ranges(self)
}
pub fn full_offset_ranges(&self) -> impl Iterator<Item = (Range<FullOffset>, &T)> {
self.entries
.iter()
.map(|(range, value)| (range.start.0..range.end.0, value))
}
pub fn point_ranges<'a>(
&'a self,
content: impl Into<Content<'a>> + 'a,
) -> impl Iterator<Item = (Range<Point>, &'a T)> + 'a {
let content = content.into();
content
.summaries_for_anchor_ranges(self)
.map(move |(range, value)| ((range.start.lines..range.end.lines), value))
}
pub fn offset_ranges<'a>(
&'a self,
content: impl Into<Content<'a>> + 'a,
) -> impl Iterator<Item = (Range<usize>, &'a T)> + 'a {
let content = content.into();
content
.summaries_for_anchor_ranges(self)
.map(move |(range, value)| ((range.start.bytes..range.end.bytes), value))
}
}
impl<T: PartialEq> PartialEq for AnchorRangeMap<T> {
@ -248,18 +234,12 @@ impl AnchorRangeSet {
self.0.version()
}
pub fn offset_ranges<'a>(
&'a self,
content: impl Into<Content<'a>> + 'a,
) -> impl Iterator<Item = Range<usize>> + 'a {
self.0.offset_ranges(content).map(|(range, _)| range)
}
pub fn point_ranges<'a>(
&'a self,
content: impl Into<Content<'a>> + 'a,
) -> impl Iterator<Item = Range<Point>> + 'a {
self.0.point_ranges(content).map(|(range, _)| range)
pub fn ranges<'a, D, C>(&'a self, content: C) -> impl 'a + Iterator<Item = Range<Point>>
where
D: 'a + TextDimension<'a>,
C: 'a + Into<Content<'a>>,
{
self.0.ranges(content).map(|(range, _)| range)
}
}

View file

@ -1486,10 +1486,14 @@ impl Buffer {
Ok(selections)
}
pub fn selection_ranges<'a>(&'a self, set_id: SelectionSetId) -> Result<Vec<Range<usize>>> {
#[cfg(test)]
pub fn selection_ranges<'a, D>(&'a self, set_id: SelectionSetId) -> Result<Vec<Range<D>>>
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<Item = (SelectionSetId, Vec<Range<usize>>)> {
) -> impl 'a + Iterator<Item = (SelectionSetId, Vec<Range<usize>>)>
where
D: 'a + TextDimension<'a>,
{
self.selections
.keys()
.map(move |set_id| (*set_id, self.selection_ranges(*set_id).unwrap()))
@ -1711,7 +1719,10 @@ impl<'a> Content<'a> {
result
}
fn summary_for_anchor(&self, anchor: &Anchor) -> TextSummary {
fn summary_for_anchor<D>(&self, anchor: &Anchor) -> D
where
D: TextDimension<'a>,
{
let cx = Some(anchor.version.clone());
let mut cursor = self.fragments.cursor::<(VersionedFullOffset, usize)>();
cursor.seek(
@ -1727,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<usize>) -> TextSummary {
fn text_summary_for_range<D>(&self, range: Range<usize>) -> D
where
D: TextDimension<'a>,
{
self.visible_text.cursor(range.start).summary(range.end)
}
fn summaries_for_anchors<T>(
&self,
map: &'a AnchorMap<T>,
) -> impl Iterator<Item = (TextSummary, &'a T)> {
fn summaries_for_anchors<D, T>(&self, map: &'a AnchorMap<T>) -> impl Iterator<Item = (D, &'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 |((offset, bias), value)| {
@ -1746,17 +1760,20 @@ impl<'a> Content<'a> {
} else {
0
};
summary += rope_cursor.summary::<TextSummary>(cursor.start().1 + overshoot);
summary.add_assign(&rope_cursor.summary(cursor.start().1 + overshoot));
(summary.clone(), value)
})
}
fn summaries_for_anchor_ranges<T>(
fn summaries_for_anchor_ranges<D, T>(
&self,
map: &'a AnchorRangeMap<T>,
) -> impl Iterator<Item = (Range<TextSummary>, &'a T)> {
) -> impl Iterator<Item = (Range<D>, &'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 +1792,7 @@ impl<'a> Content<'a> {
} else {
0
};
summary += rope_cursor.summary::<TextSummary>(cursor.start().1 + overshoot);
summary.add_assign(&rope_cursor.summary::<D>(cursor.start().1 + overshoot));
let start_summary = summary.clone();
cursor.seek_forward(&VersionedFullOffset::Offset(*end_offset), *end_bias, &cx);
@ -1784,7 +1801,7 @@ impl<'a> Content<'a> {
} else {
0
};
summary += rope_cursor.summary::<TextSummary>(cursor.start().1 + overshoot);
summary.add_assign(&rope_cursor.summary::<D>(cursor.start().1 + overshoot));
let end_summary = summary.clone();
(start_summary..end_summary, value)
@ -1917,7 +1934,7 @@ impl<'a> Content<'a> {
fn point_for_offset(&self, offset: usize) -> Result<Point> {
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"))
}
@ -2305,13 +2322,13 @@ impl ToOffset for usize {
impl ToOffset for Anchor {
fn to_offset<'a>(&self, content: impl Into<Content<'a>>) -> 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<Content<'b>>) -> usize {
content.into().summary_for_anchor(self).bytes
content.into().summary_for_anchor(self)
}
}
@ -2321,7 +2338,7 @@ pub trait ToPoint {
impl ToPoint for Anchor {
fn to_point<'a>(&self, content: impl Into<Content<'a>>) -> Point {
content.into().summary_for_anchor(self).lines
content.into().summary_for_anchor(self)
}
}

View file

@ -1,3 +1,5 @@
use crate::rope::TextDimension;
use super::{AnchorRangeMap, Buffer, Content, Point, ToOffset, ToPoint};
use std::{cmp::Ordering, ops::Range, sync::Arc};
@ -34,19 +36,29 @@ pub struct SelectionState {
pub goal: SelectionGoal,
}
impl<T: Clone> Selection<T> {
pub fn head(&self) -> T {
if self.reversed {
self.start.clone()
} else {
self.end.clone()
}
}
pub fn tail(&self) -> T {
if self.reversed {
self.end.clone()
} else {
self.start.clone()
}
}
}
impl<T: ToOffset + ToPoint + Copy + Ord> Selection<T> {
pub fn is_empty(&self) -> bool {
self.start == self.end
}
pub fn head(&self) -> T {
if self.reversed {
self.start
} else {
self.end
}
}
pub fn set_head(&mut self, head: T) {
if head.cmp(&self.tail()) < Ordering::Equal {
if !self.reversed {
@ -63,14 +75,6 @@ impl<T: ToOffset + ToPoint + Copy + Ord> Selection<T> {
}
}
pub fn tail(&self) -> T {
if self.reversed {
self.end
} else {
self.start
}
}
pub fn point_range(&self, buffer: &Buffer) -> Range<Point> {
let start = self.start.to_point(buffer);
let end = self.end.to_point(buffer);
@ -97,27 +101,13 @@ impl SelectionSet {
self.selections.len()
}
pub fn offset_selections<'a>(
&'a self,
content: impl Into<Content<'a>> + 'a,
) -> impl 'a + Iterator<Item = Selection<usize>> {
pub fn selections<'a, D, C>(&'a self, content: C) -> impl 'a + Iterator<Item = Selection<D>>
where
D: 'a + TextDimension<'a>,
C: 'a + Into<Content<'a>>,
{
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<Content<'a>> + 'a,
) -> impl 'a + Iterator<Item = Selection<Point>> {
self.selections
.point_ranges(content)
.ranges(content)
.map(|(range, state)| Selection {
id: state.id,
start: range.start,

View file

@ -576,9 +576,11 @@ fn test_random_concurrent_edits(mut rng: StdRng) {
first_buffer.selection_sets().collect::<HashMap<_, _>>()
);
assert_eq!(
buffer.all_selection_ranges().collect::<HashMap<_, _>>(),
buffer
.all_selection_ranges::<usize>()
.collect::<HashMap<_, _>>(),
first_buffer
.all_selection_ranges()
.all_selection_ranges::<usize>()
.collect::<HashMap<_, _>>()
);
}

View file

@ -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::*;
@ -20,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,
@ -292,7 +294,7 @@ pub struct Editor {
buffer: ModelHandle<Buffer>,
display_map: ModelHandle<DisplayMap>,
selection_set_id: SelectionSetId,
pending_selection: Option<Selection<Point>>,
pending_selection: Option<Selection<Anchor>>,
next_selection_id: usize,
add_selections_state: Option<AddSelectionsState>,
autoclose_stack: Vec<BracketPairState>,
@ -512,7 +514,7 @@ impl Editor {
return false;
}
let mut selections = self.point_selections(cx).peekable();
let mut selections = self.selections::<Point>(cx).peekable();
let first_cursor_top = selections
.peek()
.unwrap()
@ -564,7 +566,7 @@ impl Editor {
cx: &mut ViewContext<Self>,
) -> bool {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let selections = self.point_selections(cx);
let selections = self.selections::<Point>(cx);
let mut target_left = std::f32::INFINITY;
let mut target_right = 0.0_f32;
for selection in selections {
@ -617,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,
@ -641,9 +644,22 @@ impl Editor {
cx: &mut ViewContext<Self>,
) {
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;
@ -654,10 +670,8 @@ impl Editor {
}
fn end_selection(&mut self, cx: &mut ViewContext<Self>) {
if let Some(selection) = self.pending_selection.take() {
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let ix = self.selection_insertion_index(&selections, selection.start);
selections.insert(ix, selection);
if self.pending_selection.is_some() {
let selections = self.selections::<usize>(cx).collect::<Vec<_>>();
self.update_selections(selections, false, cx);
}
}
@ -668,13 +682,27 @@ impl Editor {
pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
if let Some(pending_selection) = self.pending_selection.take() {
if self.point_selections(cx).next().is_none() {
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::<Point>(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 mut oldest_selection = selections.min_by_key(|s| s.id).unwrap().clone();
let selections = self.selections::<Point>(cx);
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();
@ -760,7 +788,7 @@ impl Editor {
self.start_transaction(cx);
let mut old_selections = SmallVec::<[_; 32]>::new();
{
let selections = self.point_selections(cx).collect::<Vec<_>>();
let selections = self.selections::<Point>(cx).collect::<Vec<_>>();
let buffer = self.buffer.read(cx);
for selection in selections.iter() {
let start_point = selection.start;
@ -882,7 +910,7 @@ impl Editor {
fn insert(&mut self, text: &str, cx: &mut ViewContext<Self>) {
self.start_transaction(cx);
let old_selections = self.offset_selections(cx).collect::<SmallVec<[_; 32]>>();
let old_selections = self.selections::<usize>(cx).collect::<SmallVec<[_; 32]>>();
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 +941,7 @@ impl Editor {
}
fn autoclose_pairs(&mut self, cx: &mut ViewContext<Self>) {
let selections = self.offset_selections(cx).collect::<Vec<_>>();
let selections = self.selections::<usize>(cx).collect::<Vec<_>>();
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;
@ -969,8 +997,7 @@ impl Editor {
}
fn skip_autoclose_end(&mut self, text: &str, cx: &mut ViewContext<Self>) -> bool {
let old_selection_count = self.selection_count(cx);
let old_selections = self.offset_selections(cx).collect::<Vec<_>>();
let old_selections = self.selections::<usize>(cx).collect::<Vec<_>>();
let autoclose_pair_state = if let Some(autoclose_pair_state) = self.autoclose_stack.last() {
autoclose_pair_state
} else {
@ -980,12 +1007,12 @@ 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
.iter()
.zip(autoclose_pair_state.ranges.offset_ranges(buffer))
.zip(autoclose_pair_state.ranges.ranges::<usize, _>(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 +1048,7 @@ impl Editor {
pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
self.start_transaction(cx);
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
for selection in &mut selections {
if selection.is_empty() {
@ -1041,7 +1068,7 @@ impl Editor {
pub fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
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::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
for selection in &mut selections {
if selection.is_empty() {
let head = selection.head().to_display_point(&display_map, Bias::Left);
@ -1060,7 +1087,7 @@ impl Editor {
pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
self.start_transaction(cx);
let tab_size = self.build_settings.borrow()(cx).tab_size;
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
self.buffer.update(cx, |buffer, cx| {
let mut last_indented_row = None;
for selection in &mut selections {
@ -1101,7 +1128,7 @@ impl Editor {
pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext<Self>) {
self.start_transaction(cx);
let selections = self.point_selections(cx).collect::<Vec<_>>();
let selections = self.selections::<Point>(cx).collect::<Vec<_>>();
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = self.buffer.read(cx);
@ -1176,7 +1203,7 @@ impl Editor {
pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext<Self>) {
self.start_transaction(cx);
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = self.buffer.read(cx);
@ -1234,7 +1261,7 @@ impl Editor {
pub fn move_line_up(&mut self, _: &MoveLineUp, cx: &mut ViewContext<Self>) {
self.start_transaction(cx);
let selections = self.point_selections(cx).collect::<Vec<_>>();
let selections = self.selections::<Point>(cx).collect::<Vec<_>>();
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = self.buffer.read(cx);
@ -1324,7 +1351,7 @@ impl Editor {
pub fn move_line_down(&mut self, _: &MoveLineDown, cx: &mut ViewContext<Self>) {
self.start_transaction(cx);
let selections = self.point_selections(cx).collect::<Vec<_>>();
let selections = self.selections::<Point>(cx).collect::<Vec<_>>();
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = self.buffer.read(cx);
@ -1411,7 +1438,7 @@ impl Editor {
pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext<Self>) {
self.start_transaction(cx);
let mut text = String::new();
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
let mut clipboard_selections = Vec::with_capacity(selections.len());
{
let buffer = self.buffer.read(cx);
@ -1442,7 +1469,7 @@ impl Editor {
}
pub fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
let selections = self.point_selections(cx).collect::<Vec<_>>();
let selections = self.selections::<Point>(cx).collect::<Vec<_>>();
let buffer = self.buffer.read(cx);
let max_point = buffer.max_point();
let mut text = String::new();
@ -1474,7 +1501,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::<Vec<ClipboardSelection>>() {
let mut selections = self.offset_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<usize>(cx).collect::<Vec<_>>();
let all_selections_were_entire_line =
clipboard_selections.iter().all(|s| s.is_entire_line);
if clipboard_selections.len() != selections.len() {
@ -1537,7 +1564,7 @@ impl Editor {
pub fn move_left(&mut self, _: &MoveLeft, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
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 +1586,7 @@ impl Editor {
pub fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
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 +1600,7 @@ impl Editor {
pub fn move_right(&mut self, _: &MoveRight, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
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 +1622,7 @@ impl Editor {
pub fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
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 +1641,7 @@ impl Editor {
}
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
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 +1661,7 @@ impl Editor {
pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
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 +1679,7 @@ impl Editor {
}
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
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 +1699,7 @@ impl Editor {
pub fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
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 +1716,7 @@ impl Editor {
cx: &mut ViewContext<Self>,
) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
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 +1735,7 @@ impl Editor {
cx: &mut ViewContext<Self>,
) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
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 +1753,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::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
for selection in &mut selections {
if selection.is_empty() {
let head = selection.head().to_display_point(&display_map, Bias::Left);
@ -1747,7 +1774,7 @@ impl Editor {
cx: &mut ViewContext<Self>,
) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
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 +1793,7 @@ impl Editor {
cx: &mut ViewContext<Self>,
) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
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 +1811,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::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
for selection in &mut selections {
if selection.is_empty() {
let head = selection.head().to_display_point(&display_map, Bias::Left);
@ -1805,7 +1832,7 @@ impl Editor {
cx: &mut ViewContext<Self>,
) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
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 +1851,7 @@ impl Editor {
cx: &mut ViewContext<Self>,
) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
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 +1874,7 @@ impl Editor {
pub fn move_to_end_of_line(&mut self, _: &MoveToEndOfLine, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
{
for selection in &mut selections {
let head = selection.head().to_display_point(&display_map, Bias::Left);
@ -1864,7 +1891,7 @@ impl Editor {
pub fn select_to_end_of_line(&mut self, _: &SelectToEndOfLine, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
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 +1927,7 @@ impl Editor {
}
pub fn select_to_beginning(&mut self, _: &SelectToBeginning, cx: &mut ViewContext<Self>) {
let mut selection = self.point_selections(cx).last().unwrap().clone();
let mut selection = self.selections::<Point>(cx).last().unwrap().clone();
selection.set_head(Point::zero());
self.update_selections(vec![selection], true, cx);
}
@ -1919,7 +1946,7 @@ impl Editor {
}
pub fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext<Self>) {
let mut selection = self.offset_selections(cx).last().unwrap().clone();
let mut selection = self.selections::<usize>(cx).last().unwrap().clone();
selection.set_head(self.buffer.read(cx).len());
self.update_selections(vec![selection], true, cx);
}
@ -1937,7 +1964,7 @@ impl Editor {
pub fn select_line(&mut self, _: &SelectLine, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
let buffer = self.buffer.read(cx);
let max_point = buffer.max_point();
for selection in &mut selections {
@ -1954,7 +1981,7 @@ impl Editor {
_: &SplitSelectionIntoLines,
cx: &mut ViewContext<Self>,
) {
let selections = self.point_selections(cx).collect::<Vec<_>>();
let selections = self.selections::<Point>(cx).collect::<Vec<_>>();
let buffer = self.buffer.read(cx);
let mut to_unfold = Vec::new();
@ -2002,7 +2029,7 @@ impl Editor {
fn add_selection(&mut self, above: bool, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut selections = self.point_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<Point>(cx).collect::<Vec<_>>();
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 +2125,7 @@ impl Editor {
_: &SelectLargerSyntaxNode,
cx: &mut ViewContext<Self>,
) {
let old_selections = self.offset_selections(cx).collect::<Box<_>>();
let old_selections = self.selections::<usize>(cx).collect::<Box<_>>();
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = self.buffer.read(cx);
@ -2156,7 +2183,7 @@ impl Editor {
_: &MoveToEnclosingBracket,
cx: &mut ViewContext<Self>,
) {
let mut selections = self.offset_selections(cx).collect::<Vec<_>>();
let mut selections = self.selections::<usize>(cx).collect::<Vec<_>>();
let buffer = self.buffer.read(cx.as_ref());
for selection in &mut selections {
if let Some((open_range, close_range)) =
@ -2230,15 +2257,19 @@ impl Editor {
let selections = buffer
.selection_set(set_id)
.unwrap()
.point_selections(buffer)
.selections::<Point, _>(buffer)
.collect::<Vec<_>>();
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
}
@ -2267,34 +2298,46 @@ impl Editor {
}
}
fn point_selections<'a>(
&mut self,
cx: &'a mut ViewContext<Self>,
) -> impl 'a + Iterator<Item = Selection<Point>> {
self.end_selection(cx);
pub fn selections<'a, D>(&self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Selection<D>>
where
D: 'a + TextDimension<'a> + Ord,
{
let buffer = self.buffer.read(cx);
buffer
let mut selections = buffer
.selection_set(self.selection_set_id)
.unwrap()
.point_selections(buffer)
}
.selections::<D, _>(buffer)
.peekable();
let mut pending_selection = self.pending_selection.clone().map(|selection| Selection {
id: selection.id,
start: selection.start.summary::<D, _>(buffer),
end: selection.end.summary::<D, _>(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 offset_selections<'a>(
&mut self,
cx: &'a mut ViewContext<Self>,
) -> impl 'a + Iterator<Item = Selection<usize>> {
self.end_selection(cx);
let buffer = self.buffer.read(cx);
buffer
.selection_set(self.selection_set_id)
.unwrap()
.offset_selections(buffer)
}
fn selection_count(&mut self, cx: &mut ViewContext<Self>) -> 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<T>(
@ -2322,6 +2365,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() {
@ -2329,7 +2373,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::<Point, _>(buffer))
.all(|(selection, autoclose_range)| {
let head = selection.head().to_point(&*buffer);
autoclose_range.start <= head && autoclose_range.end >= head
@ -2362,7 +2406,8 @@ impl Editor {
cx.notify();
}
fn start_transaction(&self, cx: &mut ViewContext<Self>) {
fn start_transaction(&mut self, cx: &mut ViewContext<Self>) {
self.end_selection(cx);
self.buffer.update(cx, |buffer, _| {
buffer
.start_transaction(Some(self.selection_set_id))
@ -2389,7 +2434,7 @@ impl Editor {
pub fn fold(&mut self, _: &Fold, cx: &mut ViewContext<Self>) {
let mut fold_ranges = Vec::new();
let selections = self.point_selections(cx).collect::<Vec<_>>();
let selections = self.selections::<Point>(cx).collect::<Vec<_>>();
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();
@ -2412,7 +2457,7 @@ impl Editor {
}
pub fn unfold(&mut self, _: &Unfold, cx: &mut ViewContext<Self>) {
let selections = self.point_selections(cx).collect::<Vec<_>>();
let selections = self.selections::<Point>(cx).collect::<Vec<_>>();
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = self.buffer.read(cx);
let ranges = selections
@ -2473,7 +2518,7 @@ impl Editor {
}
pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext<Self>) {
let selections = self.point_selections(cx);
let selections = self.selections::<Point>(cx);
let ranges = selections.map(|s| s.start..s.end).collect();
self.fold_ranges(ranges, cx);
}

View file

@ -2456,6 +2456,12 @@ impl<V: View> UpdateModel for RenderContext<'_, V> {
}
}
impl<V: View> ReadView for RenderContext<'_, V> {
fn read_view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
self.app.read_view(handle)
}
}
impl<M> AsRef<AppContext> for ViewContext<'_, M> {
fn as_ref(&self) -> &AppContext {
&self.app.cx

View file

@ -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,
@ -780,10 +784,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<T>,
) -> impl Iterator<Item = (Range<Point>, &Diagnostic)> + 'a {
) -> impl Iterator<Item = (Range<O>, &Diagnostic)> + 'a
where
T: 'a + ToOffset,
O: 'a + FromAnchor,
{
let content = self.content();
self.diagnostics
.intersecting_ranges(range, content, true)
@ -830,9 +838,14 @@ impl Buffer {
for request in autoindent_requests {
let old_to_new_rows = request
.edited
.points(&request.before_edit)
.iter::<Point, _>(&request.before_edit)
.map(|point| point.row)
.zip(request.edited.points(&snapshot).map(|point| point.row))
.zip(
request
.edited
.iter::<Point, _>(&snapshot)
.map(|point| point.row),
)
.collect::<BTreeMap<u32, u32>>();
let mut old_suggestions = HashMap::<u32, u32>::default();
@ -893,7 +906,7 @@ impl Buffer {
if let Some(inserted) = request.inserted.as_ref() {
let inserted_row_ranges = contiguous_ranges(
inserted
.point_ranges(&snapshot)
.ranges::<Point, _>(&snapshot)
.flat_map(|range| range.start.row..range.end.row + 1),
max_rows_between_yields,
);
@ -941,7 +954,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::<Point, _>(&*self)
.map(|selection| {
if selection.start.column == 0 {
let delta = Point::new(

View file

@ -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::<Point, _>(&buffer)
.map(|selection| selection.point_range(&buffer))
.collect::<Vec<_>>();
@ -633,24 +633,83 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) {
]
);
});
}
fn chunks_with_diagnostics<T: ToOffset>(
buffer: &Buffer,
range: Range<T>,
) -> Vec<(String, Option<DiagnosticSeverity>)> {
let mut chunks: Vec<(String, Option<DiagnosticSeverity>)> = 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::<Vec<_>>(),
&[
("let one = ", None),
(";", Some(lsp::DiagnosticSeverity::ERROR)),
("\nlet two =", None),
(" ", Some(lsp::DiagnosticSeverity::ERROR)),
("\nlet three = 3;\n", None)
]
);
buffer
});
}
fn chunks_with_diagnostics<T: ToOffset>(
buffer: &Buffer,
range: Range<T>,
) -> Vec<(String, Option<DiagnosticSeverity>)> {
let mut chunks: Vec<(String, Option<DiagnosticSeverity>)> = 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]

View file

@ -3538,7 +3538,7 @@ mod tests {
let cursor_positions = buffer
.selection_set(selection_set_id)
.unwrap()
.point_selections(&*buffer)
.selections::<Point, _>(&*buffer)
.map(|selection| {
assert_eq!(selection.start, selection.end);
selection.start
@ -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();

View file

@ -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,18 @@ pub struct SidebarItem {
pub height: f32,
}
#[derive(Deserialize, Default)]
pub struct StatusBar {
#[serde(flatten)]
pub container: ContainerStyle,
pub height: f32,
pub cursor_position: TextStyle,
pub diagnostic_icon_size: f32,
pub diagnostic_icon_spacing: f32,
pub diagnostic_icon_color: Color,
pub diagnostic_message: TextStyle,
}
#[derive(Deserialize, Default)]
pub struct ChatPanel {
#[serde(flatten)]

View file

@ -1,11 +1,16 @@
use super::{Item, ItemView};
use crate::Settings;
use crate::{status_bar::StatusItemView, Settings};
use anyhow::Result;
use buffer::{Point, Selection, ToPoint};
use editor::{Editor, EditorSettings, Event};
use gpui::{fonts::TextStyle, AppContext, ModelHandle, Task, ViewContext};
use language::{Buffer, File as _};
use gpui::{
elements::*, fonts::TextStyle, AppContext, Entity, ModelHandle, RenderContext, Subscription,
Task, View, ViewContext, ViewHandle,
};
use language::{Buffer, Diagnostic, File as _};
use postage::watch;
use project::{ProjectPath, Worktree};
use std::fmt::Write;
use std::path::Path;
impl Item for Buffer {
@ -156,3 +161,170 @@ impl ItemView for Editor {
self.buffer().read(cx).has_conflict()
}
}
pub struct CursorPosition {
position: Option<Point>,
selected_count: usize,
settings: watch::Receiver<Settings>,
_observe_active_editor: Option<Subscription>,
}
impl CursorPosition {
pub fn new(settings: watch::Receiver<Settings>) -> Self {
Self {
position: None,
selected_count: 0,
settings,
_observe_active_editor: None,
}
}
fn update_position(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
let editor = editor.read(cx);
let buffer = editor.buffer().read(cx);
self.selected_count = 0;
let mut last_selection: Option<Selection<usize>> = None;
for selection in editor.selections::<usize>(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();
}
}
impl Entity for CursorPosition {
type Event = ();
}
impl View for CursorPosition {
fn ui_name() -> &'static str {
"CursorPosition"
}
fn render(&mut self, _: &mut RenderContext<Self>) -> 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<Self>,
) {
if let Some(editor) = active_pane_item.and_then(|item| item.to_any().downcast::<Editor>()) {
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();
}
}
pub struct DiagnosticMessage {
settings: watch::Receiver<Settings>,
diagnostic: Option<Diagnostic>,
_observe_active_editor: Option<Subscription>,
}
impl DiagnosticMessage {
pub fn new(settings: watch::Receiver<Settings>) -> Self {
Self {
diagnostic: None,
settings,
_observe_active_editor: None,
}
}
fn update(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
let editor = editor.read(cx);
let cursor_position = editor
.selections::<usize>(cx)
.max_by_key(|selection| selection.id)
.unwrap()
.head();
let new_diagnostic = editor
.buffer()
.read(cx)
.diagnostics_in_range::<usize, usize>(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<Self>) -> ElementBox {
if let Some(diagnostic) = &self.diagnostic {
let theme = &self.settings.borrow().theme.workspace.status_bar;
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()
}
}
}
impl StatusItemView for DiagnosticMessage {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn crate::ItemViewHandle>,
cx: &mut ViewContext<Self>,
) {
if let Some(editor) = active_pane_item.and_then(|item| item.to_any().downcast::<Editor>()) {
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();
}
}

View file

@ -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<ViewHandle<Pane>>,
active_pane: ViewHandle<Pane>,
status_bar: ViewHandle<StatusBar>,
project: ModelHandle<Project>,
items: Vec<Box<dyn WeakItemHandle>>,
loading_items: HashMap<
@ -345,6 +349,15 @@ impl Workspace {
.detach();
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
});
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 +380,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 +838,9 @@ impl Workspace {
fn activate_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
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 +1034,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)

View file

@ -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<Self>,
);
}
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<Box<dyn StatusItemViewHandle>>,
right_items: Vec<Box<dyn StatusItemViewHandle>>,
active_pane: ViewHandle<Pane>,
_observe_active_pane: Subscription,
settings: watch::Receiver<Settings>,
}
impl Entity for StatusBar {
type Event = ();
}
impl View for StatusBar {
fn ui_name() -> &'static str {
"StatusBar"
}
fn render(&mut self, _: &mut RenderContext<Self>) -> 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<Pane>,
settings: watch::Receiver<Settings>,
cx: &mut ViewContext<Self>,
) -> 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<T>(&mut self, item: ViewHandle<T>, cx: &mut ViewContext<Self>)
where
T: 'static + StatusItemView,
{
self.left_items.push(Box::new(item));
cx.notify();
}
pub fn add_right_item<T>(&mut self, item: ViewHandle<T>, cx: &mut ViewContext<Self>)
where
T: 'static + StatusItemView,
{
self.right_items.push(Box::new(item));
cx.notify();
}
pub fn set_active_pane(&mut self, active_pane: &ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
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<Self>) {
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<T: StatusItemView> StatusItemViewHandle for ViewHandle<T> {
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)
});
}
}

View file

@ -0,0 +1,3 @@
<svg width="12" height="10" viewBox="0 0 12 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.1329 8.29967L6.76186 0.840494C6.59383 0.553602 6.29874 0.410156 6.00365 0.410156C5.70856 0.410156 5.41347 0.553602 5.22699 0.840494L0.858047 8.29967C0.540622 8.87141 0.959504 9.59068 1.63347 9.59068H10.3755C11.0468 9.59068 11.4669 8.87346 11.1329 8.29967ZM1.83512 8.60706L5.98521 1.49215L10.1718 8.60706H1.83512ZM6.00365 6.66234C5.64791 6.66234 5.35937 6.95087 5.35937 7.30662C5.35937 7.66236 5.64852 7.95089 6.00447 7.95089C6.36042 7.95089 6.64793 7.66236 6.64793 7.30662C6.64711 6.95128 6.36022 6.66234 6.00365 6.66234ZM5.51184 3.52498V5.49223C5.51184 5.76478 5.73315 5.98405 6.00365 5.98405C6.27415 5.98405 6.49546 5.76376 6.49546 5.49223V3.52498C6.49546 3.25448 6.2762 3.03316 6.00365 3.03316C5.7311 3.03316 5.51184 3.25448 5.51184 3.52498Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 877 B

View file

@ -60,6 +60,15 @@ 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"
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 }
@ -226,7 +235,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"