From 8dc91973249c2ebb761ed5fb42fb08ddd4adc6e4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 3 Jan 2022 16:06:35 +0100 Subject: [PATCH 01/43] Position cursors correctly in `Editor::delete_line` in a multi-buffer --- crates/editor/src/editor.rs | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d626f28e7c..16334f3016 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1564,7 +1564,6 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = self.buffer.read(cx).snapshot(cx); - let mut row_delta = 0; let mut new_cursors = Vec::new(); let mut edit_ranges = Vec::new(); let mut selections = selections.iter().peekable(); @@ -1590,7 +1589,7 @@ impl Editor { // If there's a line after the range, delete the \n from the end of the row range // and position the cursor on the next line. edit_end = Point::new(rows.end, 0).to_offset(&buffer); - cursor_buffer_row = rows.start; + cursor_buffer_row = rows.end; } else { // If there isn't a line after the range, delete the \n from the line before the // start of the row range and position the cursor there. @@ -1599,29 +1598,35 @@ impl Editor { cursor_buffer_row = rows.start.saturating_sub(1); } - let mut cursor = - Point::new(cursor_buffer_row - row_delta, 0).to_display_point(&display_map); + let mut cursor = Point::new(cursor_buffer_row, 0).to_display_point(&display_map); *cursor.column_mut() = cmp::min(goal_display_column, display_map.line_len(cursor.row())); - row_delta += rows.len() as u32; - new_cursors.push((selection.id, cursor.to_point(&display_map))); + new_cursors.push(( + selection.id, + buffer.anchor_after(cursor.to_point(&display_map)), + )); edit_ranges.push(edit_start..edit_end); } - new_cursors.sort_unstable_by_key(|(_, point)| point.clone()); + new_cursors.sort_unstable_by(|a, b| a.1.cmp(&b.1, &buffer).unwrap()); + let buffer = self.buffer.update(cx, |buffer, cx| { + buffer.edit(edit_ranges, "", cx); + buffer.snapshot(cx) + }); let new_selections = new_cursors .into_iter() - .map(|(id, cursor)| Selection { - id, - start: cursor, - end: cursor, - reversed: false, - goal: SelectionGoal::None, + .map(|(id, cursor)| { + let cursor = cursor.to_point(&buffer); + Selection { + id, + start: cursor, + end: cursor, + reversed: false, + goal: SelectionGoal::None, + } }) .collect(); - self.buffer - .update(cx, |buffer, cx| buffer.edit(edit_ranges, "", cx)); self.update_selections(new_selections, Some(Autoscroll::Fit), cx); self.end_transaction(cx); } From ed361f2d1a312a87b29a4d1c5c59e2e0e3e19fbf Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 3 Jan 2022 16:21:30 +0100 Subject: [PATCH 02/43] Position selections correctly when duplicating lines in a multi-buffer --- crates/editor/src/editor.rs | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 16334f3016..d7e3a14500 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1634,7 +1634,7 @@ impl Editor { pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext) { self.start_transaction(cx); - let mut selections = self.local_selections::(cx); + let selections = self.local_selections::(cx); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = &display_map.buffer_snapshot; @@ -1664,28 +1664,13 @@ impl Editor { edits.push((start, text, rows.len() as u32)); } - let mut edits_iter = edits.iter().peekable(); - let mut row_delta = 0; - for selection in selections.iter_mut() { - while let Some((point, _, line_count)) = edits_iter.peek() { - if *point <= selection.start { - row_delta += line_count; - edits_iter.next(); - } else { - break; - } - } - selection.start.row += row_delta; - selection.end.row += row_delta; - } - self.buffer.update(cx, |buffer, cx| { for (point, text, _) in edits.into_iter().rev() { buffer.edit(Some(point..point), text, cx); } }); - self.update_selections(selections, Some(Autoscroll::Fit), cx); + self.request_autoscroll(Autoscroll::Fit, cx); self.end_transaction(cx); } From 2b31a48ef96dc4097f41f5e69e256b8a1165d64b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 3 Jan 2022 16:26:42 +0100 Subject: [PATCH 03/43] Clip right when moving to next word in case we land on a block line --- crates/editor/src/movement.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 217b1e63e4..466b6e9323 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -172,7 +172,7 @@ pub fn next_word_boundary(map: &DisplaySnapshot, mut point: DisplayPoint) -> Dis } prev_char_kind = Some(char_kind); } - point + map.clip_point(point, Bias::Right) } pub fn is_inside_word(map: &DisplaySnapshot, point: DisplayPoint) -> bool { From 496066db59f90e26c970479c0c0ddf423a241b97 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 3 Jan 2022 16:58:36 +0100 Subject: [PATCH 04/43] Run `Project::diagnose` when registering a new language on Worktree --- crates/project/src/project.rs | 5 ++++- crates/project/src/worktree.rs | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 182e205cc2..f23df06eb7 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -475,7 +475,10 @@ impl Project { fn add_worktree(&mut self, worktree: ModelHandle, cx: &mut ModelContext) { cx.observe(&worktree, |_, _, cx| cx.notify()).detach(); - cx.subscribe(&worktree, |_, worktree, event, cx| match event { + cx.subscribe(&worktree, |this, worktree, event, cx| match event { + worktree::Event::LanguageRegistered => { + this.diagnose(cx); + } worktree::Event::DiagnosticsUpdated(path) => { cx.emit(Event::DiagnosticsUpdated(ProjectPath { worktree_id: worktree.id(), diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 86bdeae706..82a0755223 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -68,6 +68,7 @@ pub enum Worktree { #[derive(Debug)] pub enum Event { + LanguageRegistered, DiagnosticsUpdated(Arc), } @@ -1060,6 +1061,7 @@ impl LocalWorktree { ) -> Option> { if !self.languages.iter().any(|l| Arc::ptr_eq(l, language)) { self.languages.push(language.clone()); + cx.emit(Event::LanguageRegistered); } if let Some(server) = self.language_servers.get(language.name()) { From 508b9dc0241bec2ba2c3b29869675757d9b7b198 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 4 Jan 2022 16:11:29 +0100 Subject: [PATCH 05/43] Rip out "diagnostic providers" --- crates/diagnostics/src/diagnostics.rs | 28 +-- crates/editor/src/editor.rs | 19 +- crates/editor/src/items.rs | 6 +- crates/editor/src/multi_buffer.rs | 5 +- crates/language/src/buffer.rs | 77 +++----- crates/language/src/diagnostic_set.rs | 17 +- crates/language/src/language.rs | 22 +-- crates/language/src/proto.rs | 134 ++++++-------- crates/language/src/tests.rs | 146 +++++++-------- crates/project/src/project.rs | 37 +--- crates/project/src/worktree.rs | 248 +++++++++----------------- crates/rpc/proto/zed.proto | 13 +- crates/rpc/src/peer.rs | 8 +- crates/server/src/rpc.rs | 2 +- crates/workspace/src/workspace.rs | 14 +- crates/zed/src/language.rs | 179 ------------------- 16 files changed, 267 insertions(+), 688 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 87d9c521ab..f523b4ca7f 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -571,7 +571,7 @@ mod tests { use super::*; use client::{http::ServerResponse, test::FakeHttpClient, Client, UserStore}; use gpui::TestAppContext; - use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, LanguageRegistry}; + use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, LanguageRegistry, PointUtf16}; use project::FakeFs; use serde_json::json; use std::sync::Arc; @@ -629,11 +629,12 @@ mod tests { worktree.update(&mut cx, |worktree, cx| { worktree - .update_diagnostics_from_provider( + .update_diagnostic_entries( Arc::from("/test/main.rs".as_ref()), + None, vec![ DiagnosticEntry { - range: 20..21, + range: PointUtf16::new(1, 8)..PointUtf16::new(1, 9), diagnostic: Diagnostic { message: "move occurs because `x` has type `Vec`, which does not implement the `Copy` trait" @@ -646,7 +647,7 @@ mod tests { }, }, DiagnosticEntry { - range: 40..41, + range: PointUtf16::new(2, 8)..PointUtf16::new(2, 9), diagnostic: Diagnostic { message: "move occurs because `y` has type `Vec`, which does not implement the `Copy` trait" @@ -659,7 +660,7 @@ mod tests { }, }, DiagnosticEntry { - range: 58..59, + range: PointUtf16::new(3, 6)..PointUtf16::new(3, 7), diagnostic: Diagnostic { message: "value moved here".to_string(), severity: DiagnosticSeverity::INFORMATION, @@ -670,7 +671,7 @@ mod tests { }, }, DiagnosticEntry { - range: 68..69, + range: PointUtf16::new(4, 6)..PointUtf16::new(4, 7), diagnostic: Diagnostic { message: "value moved here".to_string(), severity: DiagnosticSeverity::INFORMATION, @@ -681,7 +682,7 @@ mod tests { }, }, DiagnosticEntry { - range: 112..113, + range: PointUtf16::new(7, 6)..PointUtf16::new(7, 7), diagnostic: Diagnostic { message: "use of moved value".to_string(), severity: DiagnosticSeverity::ERROR, @@ -692,7 +693,7 @@ mod tests { }, }, DiagnosticEntry { - range: 112..113, + range: PointUtf16::new(7, 6)..PointUtf16::new(7, 7), diagnostic: Diagnostic { message: "value used here after move".to_string(), severity: DiagnosticSeverity::INFORMATION, @@ -703,7 +704,7 @@ mod tests { }, }, DiagnosticEntry { - range: 122..123, + range: PointUtf16::new(8, 6)..PointUtf16::new(8, 7), diagnostic: Diagnostic { message: "use of moved value".to_string(), severity: DiagnosticSeverity::ERROR, @@ -714,7 +715,7 @@ mod tests { }, }, DiagnosticEntry { - range: 122..123, + range: PointUtf16::new(8, 6)..PointUtf16::new(8, 7), diagnostic: Diagnostic { message: "value used here after move".to_string(), severity: DiagnosticSeverity::INFORMATION, @@ -782,11 +783,12 @@ mod tests { worktree.update(&mut cx, |worktree, cx| { worktree - .update_diagnostics_from_provider( + .update_diagnostic_entries( Arc::from("/test/a.rs".as_ref()), + None, vec![ DiagnosticEntry { - range: 15..15, + range: PointUtf16::new(0, 15)..PointUtf16::new(0, 15), diagnostic: Diagnostic { message: "mismatched types".to_string(), severity: DiagnosticSeverity::ERROR, @@ -797,7 +799,7 @@ mod tests { }, }, DiagnosticEntry { - range: 15..15, + range: PointUtf16::new(0, 15)..PointUtf16::new(0, 15), diagnostic: Diagnostic { message: "expected `usize`, found `char`".to_string(), severity: DiagnosticSeverity::INFORMATION, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d7e3a14500..f418efd8d0 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2857,19 +2857,19 @@ impl Editor { loop { let next_group = buffer .diagnostics_in_range::<_, usize>(search_start..buffer.len()) - .find_map(|(provider_name, entry)| { + .find_map(|entry| { if entry.diagnostic.is_primary && !entry.range.is_empty() && Some(entry.range.end) != active_primary_range.as_ref().map(|r| *r.end()) { - Some((provider_name, entry.range, entry.diagnostic.group_id)) + Some((entry.range, entry.diagnostic.group_id)) } else { None } }); - if let Some((provider_name, primary_range, group_id)) = next_group { - self.activate_diagnostics(provider_name, group_id, cx); + if let Some((primary_range, group_id)) = next_group { + self.activate_diagnostics(group_id, cx); self.update_selections( vec![Selection { id: selection.id, @@ -2897,7 +2897,7 @@ impl Editor { let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer); let is_valid = buffer .diagnostics_in_range::<_, usize>(active_diagnostics.primary_range.clone()) - .any(|(_, entry)| { + .any(|entry| { entry.diagnostic.is_primary && !entry.range.is_empty() && entry.range.start == primary_range_start @@ -2923,12 +2923,7 @@ impl Editor { } } - fn activate_diagnostics( - &mut self, - provider_name: &str, - group_id: usize, - cx: &mut ViewContext, - ) { + fn activate_diagnostics(&mut self, group_id: usize, cx: &mut ViewContext) { self.dismiss_diagnostics(cx); self.active_diagnostics = self.display_map.update(cx, |display_map, cx| { let buffer = self.buffer.read(cx).snapshot(cx); @@ -2937,7 +2932,7 @@ impl Editor { let mut primary_message = None; let mut group_end = Point::zero(); let diagnostic_group = buffer - .diagnostic_group::(provider_name, group_id) + .diagnostic_group::(group_id) .map(|entry| { if entry.range.end > group_end { group_end = entry.range.end; diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index df819ec520..d88315fff7 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -298,9 +298,9 @@ impl DiagnosticMessage { let new_diagnostic = buffer .read(cx) .diagnostics_in_range::<_, usize>(cursor_position..cursor_position) - .filter(|(_, entry)| !entry.range.is_empty()) - .min_by_key(|(_, entry)| (entry.diagnostic.severity, entry.range.len())) - .map(|(_, entry)| entry.diagnostic); + .filter(|entry| !entry.range.is_empty()) + .min_by_key(|entry| (entry.diagnostic.severity, entry.range.len())) + .map(|entry| entry.diagnostic); if new_diagnostic != self.diagnostic { self.diagnostic = new_diagnostic; cx.notify(); diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index c875ce29a6..e96b8200f7 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -1487,7 +1487,6 @@ impl MultiBufferSnapshot { pub fn diagnostic_group<'a, O>( &'a self, - provider_name: &'a str, group_id: usize, ) -> impl Iterator> + 'a where @@ -1495,13 +1494,13 @@ impl MultiBufferSnapshot { { self.as_singleton() .into_iter() - .flat_map(move |buffer| buffer.diagnostic_group(provider_name, group_id)) + .flat_map(move |buffer| buffer.diagnostic_group(group_id)) } pub fn diagnostics_in_range<'a, T, O>( &'a self, range: Range, - ) -> impl Iterator)> + 'a + ) -> impl Iterator> + 'a where T: 'a + ToOffset, O: 'a + text::FromAnchor, diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 0c8d7ae574..a23631a577 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -66,7 +66,7 @@ pub struct Buffer { parsing_in_background: bool, parse_count: usize, remote_selections: TreeMap]>>, - diagnostic_sets: Vec, + diagnostics: DiagnosticSet, diagnostics_update_count: usize, language_server: Option, deferred_ops: OperationQueue, @@ -77,7 +77,7 @@ pub struct Buffer { pub struct BufferSnapshot { text: text::BufferSnapshot, tree: Option, - diagnostic_sets: Vec, + diagnostics: DiagnosticSet, remote_selections: TreeMap]>>, diagnostics_update_count: usize, is_parsing: bool, @@ -121,7 +121,6 @@ struct LanguageServerSnapshot { pub enum Operation { Buffer(text::Operation), UpdateDiagnostics { - provider_name: String, diagnostics: Arc<[DiagnosticEntry]>, lamport_timestamp: clock::Lamport, }, @@ -306,17 +305,11 @@ impl Buffer { ); } let snapshot = this.snapshot(); - for diagnostic_set in message.diagnostic_sets { - let (provider_name, entries) = proto::deserialize_diagnostic_set(diagnostic_set); - this.apply_diagnostic_update( - DiagnosticSet::from_sorted_entries( - provider_name, - entries.into_iter().cloned(), - &snapshot, - ), - cx, - ); - } + let entries = proto::deserialize_diagnostics(message.diagnostics); + this.apply_diagnostic_update( + DiagnosticSet::from_sorted_entries(entries.into_iter().cloned(), &snapshot), + cx, + ); Ok(this) } @@ -338,13 +331,7 @@ impl Buffer { selections: proto::serialize_selections(selections), }) .collect(), - diagnostic_sets: self - .diagnostic_sets - .iter() - .map(|set| { - proto::serialize_diagnostic_set(set.provider_name().to_string(), set.iter()) - }) - .collect(), + diagnostics: proto::serialize_diagnostics(self.diagnostics.iter()), } } @@ -379,7 +366,7 @@ impl Buffer { pending_autoindent: Default::default(), language: None, remote_selections: Default::default(), - diagnostic_sets: Default::default(), + diagnostics: Default::default(), diagnostics_update_count: 0, language_server: None, deferred_ops: OperationQueue::new(), @@ -393,7 +380,7 @@ impl Buffer { text: self.text.snapshot(), tree: self.syntax_tree(), remote_selections: self.remote_selections.clone(), - diagnostic_sets: self.diagnostic_sets.clone(), + diagnostics: self.diagnostics.clone(), diagnostics_update_count: self.diagnostics_update_count, is_parsing: self.parsing_in_background, language: self.language.clone(), @@ -743,7 +730,6 @@ impl Buffer { pub fn update_diagnostics( &mut self, - provider_name: Arc, version: Option, mut diagnostics: Vec>, cx: &mut ModelContext, @@ -833,10 +819,9 @@ impl Buffer { } drop(edits_since_save); - let set = DiagnosticSet::new(provider_name, sanitized_diagnostics, content); + let set = DiagnosticSet::new(sanitized_diagnostics, content); self.apply_diagnostic_update(set.clone(), cx); Ok(Operation::UpdateDiagnostics { - provider_name: set.provider_name().to_string(), diagnostics: set.iter().cloned().collect(), lamport_timestamp: self.text.lamport_clock.tick(), }) @@ -1347,17 +1332,12 @@ impl Buffer { unreachable!("buffer operations should never be applied at this layer") } Operation::UpdateDiagnostics { - provider_name, diagnostics: diagnostic_set, .. } => { let snapshot = self.snapshot(); self.apply_diagnostic_update( - DiagnosticSet::from_sorted_entries( - provider_name, - diagnostic_set.iter().cloned(), - &snapshot, - ), + DiagnosticSet::from_sorted_entries(diagnostic_set.iter().cloned(), &snapshot), cx, ); } @@ -1379,15 +1359,8 @@ impl Buffer { } } - fn apply_diagnostic_update(&mut self, set: DiagnosticSet, cx: &mut ModelContext) { - match self - .diagnostic_sets - .binary_search_by_key(&set.provider_name(), |set| set.provider_name()) - { - Ok(ix) => self.diagnostic_sets[ix] = set.clone(), - Err(ix) => self.diagnostic_sets.insert(ix, set.clone()), - } - + fn apply_diagnostic_update(&mut self, diagnostics: DiagnosticSet, cx: &mut ModelContext) { + self.diagnostics = diagnostics; self.diagnostics_update_count += 1; cx.notify(); cx.emit(Event::DiagnosticsUpdated); @@ -1625,7 +1598,7 @@ impl BufferSnapshot { let mut highlights = None; let mut diagnostic_endpoints = Vec::::new(); if let Some(theme) = theme { - for (_, entry) in self.diagnostics_in_range::<_, usize>(range.clone()) { + for entry in self.diagnostics_in_range::<_, usize>(range.clone()) { diagnostic_endpoints.push(DiagnosticEndpoint { offset: entry.range.start, is_start: true, @@ -1756,38 +1729,28 @@ impl BufferSnapshot { pub fn diagnostics_in_range<'a, T, O>( &'a self, search_range: Range, - ) -> impl 'a + Iterator)> + ) -> impl 'a + Iterator> where T: 'a + Clone + ToOffset, O: 'a + FromAnchor, { - self.diagnostic_sets.iter().flat_map(move |set| { - set.range(search_range.clone(), self, true) - .map(|e| (set.provider_name(), e)) - }) + self.diagnostics.range(search_range.clone(), self, true) } pub fn diagnostic_groups(&self) -> Vec> { let mut groups = Vec::new(); - for set in &self.diagnostic_sets { - set.groups(&mut groups, self); - } + self.diagnostics.groups(&mut groups, self); groups } pub fn diagnostic_group<'a, O>( &'a self, - provider_name: &str, group_id: usize, ) -> impl 'a + Iterator> where O: 'a + FromAnchor, { - self.diagnostic_sets - .iter() - .find(|s| s.provider_name() == provider_name) - .into_iter() - .flat_map(move |s| s.group(group_id, self)) + self.diagnostics.group(group_id, self) } pub fn diagnostics_update_count(&self) -> usize { @@ -1805,7 +1768,7 @@ impl Clone for BufferSnapshot { text: self.text.clone(), tree: self.tree.clone(), remote_selections: self.remote_selections.clone(), - diagnostic_sets: self.diagnostic_sets.clone(), + diagnostics: self.diagnostics.clone(), diagnostics_update_count: self.diagnostics_update_count, is_parsing: self.is_parsing, language: self.language.clone(), diff --git a/crates/language/src/diagnostic_set.rs b/crates/language/src/diagnostic_set.rs index 05e19e635a..047513fce7 100644 --- a/crates/language/src/diagnostic_set.rs +++ b/crates/language/src/diagnostic_set.rs @@ -4,14 +4,12 @@ use std::{ cmp::{Ordering, Reverse}, iter, ops::Range, - sync::Arc, }; use sum_tree::{self, Bias, SumTree}; use text::{Anchor, FromAnchor, Point, ToOffset}; #[derive(Clone, Debug)] pub struct DiagnosticSet { - provider_name: Arc, diagnostics: SumTree>, } @@ -36,32 +34,22 @@ pub struct Summary { } impl DiagnosticSet { - pub fn provider_name(&self) -> &str { - &self.provider_name - } - - pub fn from_sorted_entries( - provider_name: impl Into>, - iter: I, - buffer: &text::BufferSnapshot, - ) -> Self + pub fn from_sorted_entries(iter: I, buffer: &text::BufferSnapshot) -> Self where I: IntoIterator>, { Self { - provider_name: provider_name.into(), diagnostics: SumTree::from_iter(iter, buffer), } } - pub fn new(provider_name: Arc, iter: I, buffer: &text::BufferSnapshot) -> Self + pub fn new(iter: I, buffer: &text::BufferSnapshot) -> Self where I: IntoIterator>, { let mut entries = iter.into_iter().collect::>(); entries.sort_unstable_by_key(|entry| (entry.range.start, Reverse(entry.range.end))); Self { - provider_name, diagnostics: SumTree::from_iter( entries.into_iter().map(|entry| DiagnosticEntry { range: buffer.anchor_before(entry.range.start) @@ -159,7 +147,6 @@ impl DiagnosticSet { impl Default for DiagnosticSet { fn default() -> Self { Self { - provider_name: "".into(), diagnostics: Default::default(), } } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index d6c13a7fd4..fe832929a9 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -6,10 +6,9 @@ pub mod proto; mod tests; use anyhow::{anyhow, Result}; -use async_trait::async_trait; pub use buffer::Operation; pub use buffer::*; -use collections::{HashMap, HashSet}; +use collections::HashSet; pub use diagnostic_set::DiagnosticEntry; use gpui::AppContext; use highlight_map::HighlightMap; @@ -60,18 +59,9 @@ pub struct BracketPair { pub newline: bool, } -#[async_trait] -pub trait DiagnosticProvider: 'static + Send + Sync { - async fn diagnose( - &self, - path: Arc, - ) -> Result, Vec>>>; -} - pub struct Language { pub(crate) config: LanguageConfig, pub(crate) grammar: Option>, - pub(crate) diagnostic_provider: Option>, } pub struct Grammar { @@ -136,7 +126,6 @@ impl Language { highlight_map: Default::default(), }) }), - diagnostic_provider: None, } } @@ -170,11 +159,6 @@ impl Language { Ok(self) } - pub fn with_diagnostic_provider(mut self, source: impl DiagnosticProvider) -> Self { - self.diagnostic_provider = Some(Arc::new(source)); - self - } - pub fn name(&self) -> &str { self.config.name.as_str() } @@ -208,10 +192,6 @@ impl Language { } } - pub fn diagnostic_provider(&self) -> Option<&Arc> { - self.diagnostic_provider.as_ref() - } - pub fn disk_based_diagnostic_sources(&self) -> Option<&HashSet> { self.config .language_server diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 727b6a4d79..200a687052 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -57,16 +57,12 @@ pub fn serialize_operation(operation: &Operation) -> proto::Operation { lamport_timestamp: lamport_timestamp.value, }), Operation::UpdateDiagnostics { - provider_name, diagnostics, lamport_timestamp, - } => proto::operation::Variant::UpdateDiagnosticSet(proto::UpdateDiagnosticSet { + } => proto::operation::Variant::UpdateDiagnostics(proto::UpdateDiagnostics { replica_id: lamport_timestamp.replica_id as u32, lamport_timestamp: lamport_timestamp.value, - diagnostic_set: Some(serialize_diagnostic_set( - provider_name.clone(), - diagnostics.iter(), - )), + diagnostics: serialize_diagnostics(diagnostics.iter()), }), }), } @@ -103,33 +99,29 @@ pub fn serialize_selections(selections: &Arc<[Selection]>) -> Vec( - provider_name: String, +pub fn serialize_diagnostics<'a>( diagnostics: impl IntoIterator>, -) -> proto::DiagnosticSet { - proto::DiagnosticSet { - provider_name, - diagnostics: diagnostics - .into_iter() - .map(|entry| proto::Diagnostic { - start: Some(serialize_anchor(&entry.range.start)), - end: Some(serialize_anchor(&entry.range.end)), - message: entry.diagnostic.message.clone(), - severity: match entry.diagnostic.severity { - DiagnosticSeverity::ERROR => proto::diagnostic::Severity::Error, - DiagnosticSeverity::WARNING => proto::diagnostic::Severity::Warning, - DiagnosticSeverity::INFORMATION => proto::diagnostic::Severity::Information, - DiagnosticSeverity::HINT => proto::diagnostic::Severity::Hint, - _ => proto::diagnostic::Severity::None, - } as i32, - group_id: entry.diagnostic.group_id as u64, - is_primary: entry.diagnostic.is_primary, - is_valid: entry.diagnostic.is_valid, - code: entry.diagnostic.code.clone(), - is_disk_based: entry.diagnostic.is_disk_based, - }) - .collect(), - } +) -> Vec { + diagnostics + .into_iter() + .map(|entry| proto::Diagnostic { + start: Some(serialize_anchor(&entry.range.start)), + end: Some(serialize_anchor(&entry.range.end)), + message: entry.diagnostic.message.clone(), + severity: match entry.diagnostic.severity { + DiagnosticSeverity::ERROR => proto::diagnostic::Severity::Error, + DiagnosticSeverity::WARNING => proto::diagnostic::Severity::Warning, + DiagnosticSeverity::INFORMATION => proto::diagnostic::Severity::Information, + DiagnosticSeverity::HINT => proto::diagnostic::Severity::Hint, + _ => proto::diagnostic::Severity::None, + } as i32, + group_id: entry.diagnostic.group_id as u64, + is_primary: entry.diagnostic.is_primary, + is_valid: entry.diagnostic.is_valid, + code: entry.diagnostic.code.clone(), + is_disk_based: entry.diagnostic.is_disk_based, + }) + .collect() } fn serialize_anchor(anchor: &Anchor) -> proto::Anchor { @@ -215,21 +207,13 @@ pub fn deserialize_operation(message: proto::Operation) -> Result { value: message.lamport_timestamp, }, }, - proto::operation::Variant::UpdateDiagnosticSet(message) => { - let (provider_name, diagnostics) = deserialize_diagnostic_set( - message - .diagnostic_set - .ok_or_else(|| anyhow!("missing diagnostic set"))?, - ); - Operation::UpdateDiagnostics { - provider_name, - diagnostics, - lamport_timestamp: clock::Lamport { - replica_id: message.replica_id as ReplicaId, - value: message.lamport_timestamp, - }, - } - } + proto::operation::Variant::UpdateDiagnostics(message) => Operation::UpdateDiagnostics { + diagnostics: deserialize_diagnostics(message.diagnostics), + lamport_timestamp: clock::Lamport { + replica_id: message.replica_id as ReplicaId, + value: message.lamport_timestamp, + }, + }, }, ) } @@ -269,40 +253,32 @@ pub fn deserialize_selections(selections: Vec) -> Arc<[Selecti ) } -pub fn deserialize_diagnostic_set( - message: proto::DiagnosticSet, -) -> (String, Arc<[DiagnosticEntry]>) { - ( - message.provider_name, - message - .diagnostics - .into_iter() - .filter_map(|diagnostic| { - Some(DiagnosticEntry { - range: deserialize_anchor(diagnostic.start?)? - ..deserialize_anchor(diagnostic.end?)?, - diagnostic: Diagnostic { - severity: match proto::diagnostic::Severity::from_i32(diagnostic.severity)? - { - proto::diagnostic::Severity::Error => DiagnosticSeverity::ERROR, - proto::diagnostic::Severity::Warning => DiagnosticSeverity::WARNING, - proto::diagnostic::Severity::Information => { - DiagnosticSeverity::INFORMATION - } - proto::diagnostic::Severity::Hint => DiagnosticSeverity::HINT, - proto::diagnostic::Severity::None => return None, - }, - message: diagnostic.message, - group_id: diagnostic.group_id as usize, - code: diagnostic.code, - is_valid: diagnostic.is_valid, - is_primary: diagnostic.is_primary, - is_disk_based: diagnostic.is_disk_based, +pub fn deserialize_diagnostics( + diagnostics: Vec, +) -> Arc<[DiagnosticEntry]> { + diagnostics + .into_iter() + .filter_map(|diagnostic| { + Some(DiagnosticEntry { + range: deserialize_anchor(diagnostic.start?)?..deserialize_anchor(diagnostic.end?)?, + diagnostic: Diagnostic { + severity: match proto::diagnostic::Severity::from_i32(diagnostic.severity)? { + proto::diagnostic::Severity::Error => DiagnosticSeverity::ERROR, + proto::diagnostic::Severity::Warning => DiagnosticSeverity::WARNING, + proto::diagnostic::Severity::Information => DiagnosticSeverity::INFORMATION, + proto::diagnostic::Severity::Hint => DiagnosticSeverity::HINT, + proto::diagnostic::Severity::None => return None, }, - }) + message: diagnostic.message, + group_id: diagnostic.group_id as usize, + code: diagnostic.code, + is_valid: diagnostic.is_valid, + is_primary: diagnostic.is_primary, + is_disk_based: diagnostic.is_disk_based, + }, }) - .collect(), - ) + }) + .collect() } fn deserialize_anchor(anchor: proto::Anchor) -> Option { diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 4db5e788f8..e94ff781f3 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -455,7 +455,6 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) { // Receive diagnostics for an earlier version of the buffer. buffer .update_diagnostics( - "lsp".into(), Some(open_notification.text_document.version), vec![ DiagnosticEntry { @@ -503,34 +502,28 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) { .diagnostics_in_range::<_, Point>(Point::new(3, 0)..Point::new(5, 0)) .collect::>(), &[ - ( - "lsp", - DiagnosticEntry { - range: Point::new(3, 9)..Point::new(3, 11), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::ERROR, - message: "undefined variable 'BB'".to_string(), - is_disk_based: true, - group_id: 1, - is_primary: true, - ..Default::default() - }, + DiagnosticEntry { + range: Point::new(3, 9)..Point::new(3, 11), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::ERROR, + message: "undefined variable 'BB'".to_string(), + is_disk_based: true, + group_id: 1, + is_primary: true, + ..Default::default() + }, + }, + DiagnosticEntry { + range: Point::new(4, 9)..Point::new(4, 12), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::ERROR, + message: "undefined variable 'CCC'".to_string(), + is_disk_based: true, + group_id: 2, + is_primary: true, + ..Default::default() } - ), - ( - "lsp", - DiagnosticEntry { - range: Point::new(4, 9)..Point::new(4, 12), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::ERROR, - message: "undefined variable 'CCC'".to_string(), - is_disk_based: true, - group_id: 2, - is_primary: true, - ..Default::default() - } - } - ) + } ] ); assert_eq!( @@ -557,7 +550,6 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) { // Ensure overlapping diagnostics are highlighted correctly. buffer .update_diagnostics( - "lsp".into(), Some(open_notification.text_document.version), vec![ DiagnosticEntry { @@ -591,33 +583,27 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) { .diagnostics_in_range::<_, Point>(Point::new(2, 0)..Point::new(3, 0)) .collect::>(), &[ - ( - "lsp", - DiagnosticEntry { - range: Point::new(2, 9)..Point::new(2, 12), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::WARNING, - message: "unreachable statement".to_string(), - group_id: 1, - is_primary: true, - ..Default::default() - } + DiagnosticEntry { + range: Point::new(2, 9)..Point::new(2, 12), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::WARNING, + message: "unreachable statement".to_string(), + group_id: 1, + is_primary: true, + ..Default::default() } - ), - ( - "lsp", - DiagnosticEntry { - range: Point::new(2, 9)..Point::new(2, 10), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::ERROR, - message: "undefined variable 'A'".to_string(), - is_disk_based: true, - group_id: 0, - is_primary: true, - ..Default::default() - }, - } - ) + }, + DiagnosticEntry { + range: Point::new(2, 9)..Point::new(2, 10), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::ERROR, + message: "undefined variable 'A'".to_string(), + is_disk_based: true, + group_id: 0, + is_primary: true, + ..Default::default() + }, + } ] ); assert_eq!( @@ -654,7 +640,6 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) { buffer.update(&mut cx, |buffer, cx| { buffer .update_diagnostics( - "lsp".into(), Some(change_notification_2.text_document.version), vec![ DiagnosticEntry { @@ -689,34 +674,28 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) { .diagnostics_in_range::<_, Point>(0..buffer.len()) .collect::>(), &[ - ( - "lsp", - DiagnosticEntry { - range: Point::new(2, 21)..Point::new(2, 22), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::ERROR, - message: "undefined variable 'A'".to_string(), - is_disk_based: true, - group_id: 0, - is_primary: true, - ..Default::default() - } + DiagnosticEntry { + range: Point::new(2, 21)..Point::new(2, 22), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::ERROR, + message: "undefined variable 'A'".to_string(), + is_disk_based: true, + group_id: 0, + is_primary: true, + ..Default::default() } - ), - ( - "lsp", - DiagnosticEntry { - range: Point::new(3, 9)..Point::new(3, 11), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::ERROR, - message: "undefined variable 'BB'".to_string(), - is_disk_based: true, - group_id: 1, - is_primary: true, - ..Default::default() - }, - } - ) + }, + DiagnosticEntry { + range: Point::new(3, 9)..Point::new(3, 11), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::ERROR, + message: "undefined variable 'BB'".to_string(), + is_disk_based: true, + group_id: 1, + is_primary: true, + ..Default::default() + }, + } ] ); }); @@ -735,7 +714,6 @@ async fn test_empty_diagnostic_ranges(mut cx: gpui::TestAppContext) { buffer.set_language(Some(Arc::new(rust_lang())), None, cx); buffer .update_diagnostics( - "lsp".into(), None, vec![ DiagnosticEntry { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f23df06eb7..499b3d4a52 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -18,7 +18,7 @@ use std::{ path::Path, sync::{atomic::AtomicBool, Arc}, }; -use util::{ResultExt, TryFutureExt as _}; +use util::TryFutureExt as _; pub use fs::*; pub use worktree::*; @@ -475,10 +475,7 @@ impl Project { fn add_worktree(&mut self, worktree: ModelHandle, cx: &mut ModelContext) { cx.observe(&worktree, |_, _, cx| cx.notify()).detach(); - cx.subscribe(&worktree, |this, worktree, event, cx| match event { - worktree::Event::LanguageRegistered => { - this.diagnose(cx); - } + cx.subscribe(&worktree, |_, worktree, event, cx| match event { worktree::Event::DiagnosticsUpdated(path) => { cx.emit(Event::DiagnosticsUpdated(ProjectPath { worktree_id: worktree.id(), @@ -506,36 +503,6 @@ impl Project { } } - pub fn diagnose(&self, cx: &mut ModelContext) { - for worktree_handle in &self.worktrees { - if let Some(worktree) = worktree_handle.read(cx).as_local() { - for language in worktree.languages() { - if let Some(provider) = language.diagnostic_provider().cloned() { - let worktree_path = worktree.abs_path().clone(); - let worktree_handle = worktree_handle.downgrade(); - cx.spawn_weak(|_, mut cx| async move { - let diagnostics = provider.diagnose(worktree_path).await.log_err()?; - let worktree_handle = worktree_handle.upgrade(&cx)?; - worktree_handle.update(&mut cx, |worktree, cx| { - for (path, diagnostics) in diagnostics { - worktree - .update_diagnostics_from_provider( - path.into(), - diagnostics, - cx, - ) - .log_err()?; - } - Some(()) - }) - }) - .detach(); - } - } - } - } - } - pub fn diagnostic_summaries<'a>( &'a self, cx: &'a AppContext, diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 82a0755223..76f0e2b270 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -50,8 +50,6 @@ use util::{post_inc, ResultExt, TryFutureExt}; lazy_static! { static ref GITIGNORE: &'static OsStr = OsStr::new(".gitignore"); - static ref DIAGNOSTIC_PROVIDER_NAME: Arc = Arc::from("diagnostic_source"); - static ref LSP_PROVIDER_NAME: Arc = Arc::from("lsp"); } #[derive(Clone, Debug)] @@ -68,7 +66,6 @@ pub enum Worktree { #[derive(Debug)] pub enum Event { - LanguageRegistered, DiagnosticsUpdated(Arc), } @@ -675,7 +672,7 @@ impl Worktree { } } - pub fn update_diagnostics_from_lsp( + pub fn update_diagnostics( &mut self, mut params: lsp::PublishDiagnosticsParams, disk_based_sources: &HashSet, @@ -745,6 +742,17 @@ impl Worktree { }) .collect::>(); + self.update_diagnostic_entries(worktree_path, params.version, diagnostics, cx)?; + Ok(()) + } + + pub fn update_diagnostic_entries( + &mut self, + worktree_path: Arc, + version: Option, + diagnostics: Vec>, + cx: &mut ModelContext, + ) -> Result<()> { let this = self.as_local_mut().unwrap(); for buffer in this.open_buffers.values() { if let Some(buffer) = buffer.upgrade(cx) { @@ -756,12 +764,7 @@ impl Worktree { let (remote_id, operation) = buffer.update(cx, |buffer, cx| { ( buffer.remote_id(), - buffer.update_diagnostics( - LSP_PROVIDER_NAME.clone(), - params.version, - diagnostics.clone(), - cx, - ), + buffer.update_diagnostics(version, diagnostics.clone(), cx), ) }); self.send_buffer_update(remote_id, operation?, cx); @@ -773,51 +776,11 @@ impl Worktree { let this = self.as_local_mut().unwrap(); this.diagnostic_summaries .insert(worktree_path.clone(), DiagnosticSummary::new(&diagnostics)); - this.lsp_diagnostics - .insert(worktree_path.clone(), diagnostics); + this.diagnostics.insert(worktree_path.clone(), diagnostics); cx.emit(Event::DiagnosticsUpdated(worktree_path.clone())); Ok(()) } - pub fn update_diagnostics_from_provider( - &mut self, - path: Arc, - diagnostics: Vec>, - cx: &mut ModelContext, - ) -> Result<()> { - let this = self.as_local_mut().unwrap(); - for buffer in this.open_buffers.values() { - if let Some(buffer) = buffer.upgrade(cx) { - if buffer - .read(cx) - .file() - .map_or(false, |file| *file.path() == path) - { - let (remote_id, operation) = buffer.update(cx, |buffer, cx| { - ( - buffer.remote_id(), - buffer.update_diagnostics( - DIAGNOSTIC_PROVIDER_NAME.clone(), - None, - diagnostics.clone(), - cx, - ), - ) - }); - self.send_buffer_update(remote_id, operation?, cx); - break; - } - } - } - - let this = self.as_local_mut().unwrap(); - this.diagnostic_summaries - .insert(path.clone(), DiagnosticSummary::new(&diagnostics)); - this.provider_diagnostics.insert(path.clone(), diagnostics); - cx.emit(Event::DiagnosticsUpdated(path.clone())); - Ok(()) - } - fn send_buffer_update( &mut self, buffer_id: u64, @@ -888,8 +851,7 @@ pub struct LocalWorktree { loading_buffers: LoadingBuffers, open_buffers: HashMap>, shared_buffers: HashMap>>, - lsp_diagnostics: HashMap, Vec>>, - provider_diagnostics: HashMap, Vec>>, + diagnostics: HashMap, Vec>>, diagnostic_summaries: BTreeMap, DiagnosticSummary>, queued_operations: Vec<(u64, Operation)>, language_registry: Arc, @@ -997,8 +959,7 @@ impl LocalWorktree { loading_buffers: Default::default(), open_buffers: Default::default(), shared_buffers: Default::default(), - lsp_diagnostics: Default::default(), - provider_diagnostics: Default::default(), + diagnostics: Default::default(), diagnostic_summaries: Default::default(), queued_operations: Default::default(), language_registry: languages, @@ -1061,7 +1022,6 @@ impl LocalWorktree { ) -> Option> { if !self.languages.iter().any(|l| Arc::ptr_eq(l, language)) { self.languages.push(language.clone()); - cx.emit(Event::LanguageRegistered); } if let Some(server) = self.language_servers.get(language.name()) { @@ -1087,7 +1047,7 @@ impl LocalWorktree { while let Ok(diagnostics) = diagnostics_rx.recv().await { if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { handle.update(&mut cx, |this, cx| { - this.update_diagnostics_from_lsp(diagnostics, &disk_based_sources, cx) + this.update_diagnostics(diagnostics, &disk_based_sources, cx) .log_err(); }); } else { @@ -1138,35 +1098,25 @@ impl LocalWorktree { .update(&mut cx, |t, cx| t.as_local().unwrap().load(&path, cx)) .await?; - let (lsp_diagnostics, provider_diagnostics, language, language_server) = - this.update(&mut cx, |this, cx| { - let this = this.as_local_mut().unwrap(); - let lsp_diagnostics = this.lsp_diagnostics.remove(&path); - let provider_diagnostics = this.provider_diagnostics.remove(&path); - let language = this - .language_registry - .select_language(file.full_path()) - .cloned(); - let server = language - .as_ref() - .and_then(|language| this.register_language(language, cx)); - (lsp_diagnostics, provider_diagnostics, language, server) - }); + let (diagnostics, language, language_server) = this.update(&mut cx, |this, cx| { + let this = this.as_local_mut().unwrap(); + let diagnostics = this.diagnostics.remove(&path); + let language = this + .language_registry + .select_language(file.full_path()) + .cloned(); + let server = language + .as_ref() + .and_then(|language| this.register_language(language, cx)); + (diagnostics, language, server) + }); let mut buffer_operations = Vec::new(); let buffer = cx.add_model(|cx| { let mut buffer = Buffer::from_file(0, contents, Box::new(file), cx); buffer.set_language(language, language_server, cx); - if let Some(diagnostics) = lsp_diagnostics { - let op = buffer - .update_diagnostics(LSP_PROVIDER_NAME.clone(), None, diagnostics, cx) - .unwrap(); - buffer_operations.push(op); - } - if let Some(diagnostics) = provider_diagnostics { - let op = buffer - .update_diagnostics(DIAGNOSTIC_PROVIDER_NAME.clone(), None, diagnostics, cx) - .unwrap(); + if let Some(diagnostics) = diagnostics { + let op = buffer.update_diagnostics(None, diagnostics, cx).unwrap(); buffer_operations.push(op); } buffer @@ -3739,19 +3689,16 @@ mod tests { .collect::>(); assert_eq!( diagnostics, - &[( - LSP_PROVIDER_NAME.as_ref(), - DiagnosticEntry { - range: Point::new(0, 9)..Point::new(0, 10), - diagnostic: Diagnostic { - severity: lsp::DiagnosticSeverity::ERROR, - message: "undefined variable 'A'".to_string(), - group_id: 0, - is_primary: true, - ..Default::default() - } + &[DiagnosticEntry { + range: Point::new(0, 9)..Point::new(0, 10), + diagnostic: Diagnostic { + severity: lsp::DiagnosticSeverity::ERROR, + message: "undefined variable 'A'".to_string(), + group_id: 0, + is_primary: true, + ..Default::default() } - )] + }] ) }); } @@ -3896,7 +3843,7 @@ mod tests { worktree .update(&mut cx, |tree, cx| { - tree.update_diagnostics_from_lsp(message, &Default::default(), cx) + tree.update_diagnostics(message, &Default::default(), cx) }) .unwrap(); let buffer = buffer.read_with(&cx, |buffer, _| buffer.snapshot()); @@ -3906,78 +3853,61 @@ mod tests { .diagnostics_in_range::<_, Point>(0..buffer.len()) .collect::>(), &[ - ( - LSP_PROVIDER_NAME.as_ref(), - DiagnosticEntry { - range: Point::new(1, 8)..Point::new(1, 9), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::WARNING, - message: "error 1".to_string(), - group_id: 0, - is_primary: true, - ..Default::default() - } + DiagnosticEntry { + range: Point::new(1, 8)..Point::new(1, 9), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::WARNING, + message: "error 1".to_string(), + group_id: 0, + is_primary: true, + ..Default::default() } - ), - ( - LSP_PROVIDER_NAME.as_ref(), - DiagnosticEntry { - range: Point::new(1, 8)..Point::new(1, 9), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::HINT, - message: "error 1 hint 1".to_string(), - group_id: 0, - is_primary: false, - ..Default::default() - } + }, + DiagnosticEntry { + range: Point::new(1, 8)..Point::new(1, 9), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::HINT, + message: "error 1 hint 1".to_string(), + group_id: 0, + is_primary: false, + ..Default::default() } - ), - ( - LSP_PROVIDER_NAME.as_ref(), - DiagnosticEntry { - range: Point::new(1, 13)..Point::new(1, 15), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::HINT, - message: "error 2 hint 1".to_string(), - group_id: 1, - is_primary: false, - ..Default::default() - } + }, + DiagnosticEntry { + range: Point::new(1, 13)..Point::new(1, 15), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::HINT, + message: "error 2 hint 1".to_string(), + group_id: 1, + is_primary: false, + ..Default::default() } - ), - ( - LSP_PROVIDER_NAME.as_ref(), - DiagnosticEntry { - range: Point::new(1, 13)..Point::new(1, 15), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::HINT, - message: "error 2 hint 2".to_string(), - group_id: 1, - is_primary: false, - ..Default::default() - } + }, + DiagnosticEntry { + range: Point::new(1, 13)..Point::new(1, 15), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::HINT, + message: "error 2 hint 2".to_string(), + group_id: 1, + is_primary: false, + ..Default::default() } - ), - ( - LSP_PROVIDER_NAME.as_ref(), - DiagnosticEntry { - range: Point::new(2, 8)..Point::new(2, 17), - diagnostic: Diagnostic { - severity: DiagnosticSeverity::ERROR, - message: "error 2".to_string(), - group_id: 1, - is_primary: true, - ..Default::default() - } + }, + DiagnosticEntry { + range: Point::new(2, 8)..Point::new(2, 17), + diagnostic: Diagnostic { + severity: DiagnosticSeverity::ERROR, + message: "error 2".to_string(), + group_id: 1, + is_primary: true, + ..Default::default() } - ) + } ] ); assert_eq!( - buffer - .diagnostic_group::(&LSP_PROVIDER_NAME, 0) - .collect::>(), + buffer.diagnostic_group::(0).collect::>(), &[ DiagnosticEntry { range: Point::new(1, 8)..Point::new(1, 9), @@ -4002,9 +3932,7 @@ mod tests { ] ); assert_eq!( - buffer - .diagnostic_group::(&LSP_PROVIDER_NAME, 1) - .collect::>(), + buffer.diagnostic_group::(1).collect::>(), &[ DiagnosticEntry { range: Point::new(1, 13)..Point::new(1, 15), diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 0bcd992788..5ef34960e7 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -265,7 +265,7 @@ message Buffer { string content = 2; repeated Operation.Edit history = 3; repeated SelectionSet selections = 4; - repeated DiagnosticSet diagnostic_sets = 5; + repeated Diagnostic diagnostics = 5; } message SelectionSet { @@ -292,15 +292,10 @@ enum Bias { Right = 1; } -message UpdateDiagnosticSet { +message UpdateDiagnostics { uint32 replica_id = 1; uint32 lamport_timestamp = 2; - DiagnosticSet diagnostic_set = 3; -} - -message DiagnosticSet { - string provider_name = 1; - repeated Diagnostic diagnostics = 2; + repeated Diagnostic diagnostics = 3; } message Diagnostic { @@ -329,7 +324,7 @@ message Operation { Undo undo = 2; UpdateSelections update_selections = 3; RemoveSelections remove_selections = 4; - UpdateDiagnosticSet update_diagnostic_set = 5; + UpdateDiagnostics update_diagnostics = 5; } message Edit { diff --git a/crates/rpc/src/peer.rs b/crates/rpc/src/peer.rs index 7d4adededd..bd5d1c384f 100644 --- a/crates/rpc/src/peer.rs +++ b/crates/rpc/src/peer.rs @@ -401,7 +401,7 @@ mod tests { content: "path/one content".to_string(), history: vec![], selections: vec![], - diagnostic_sets: vec![], + diagnostics: vec![], }), } ); @@ -424,7 +424,7 @@ mod tests { content: "path/two content".to_string(), history: vec![], selections: vec![], - diagnostic_sets: vec![], + diagnostics: vec![], }), } ); @@ -455,7 +455,7 @@ mod tests { content: "path/one content".to_string(), history: vec![], selections: vec![], - diagnostic_sets: vec![], + diagnostics: vec![], }), } } @@ -467,7 +467,7 @@ mod tests { content: "path/two content".to_string(), history: vec![], selections: vec![], - diagnostic_sets: vec![], + diagnostics: vec![], }), } } diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index e507d3d7b8..0b1b7e7aab 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -1831,7 +1831,7 @@ mod tests { buffer .snapshot() .diagnostics_in_range::<_, Point>(0..buffer.len()) - .map(|(_, entry)| entry) + .map(|entry| entry) .collect::>(), &[ DiagnosticEntry { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ffbfee7d6d..b45d292cf2 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -791,24 +791,16 @@ impl Workspace { { error!("failed to save item: {:?}, ", error); } - - handle.update(&mut cx, |this, cx| { - this.project.update(cx, |project, cx| project.diagnose(cx)) - }); }) .detach(); } }, ); } else { - cx.spawn(|this, mut cx| async move { + cx.spawn(|_, mut cx| async move { if let Err(error) = cx.update(|cx| item.save(cx)).unwrap().await { error!("failed to save item: {:?}, ", error); } - - this.update(&mut cx, |this, cx| { - this.project.update(cx, |project, cx| project.diagnose(cx)) - }); }) .detach(); } @@ -840,10 +832,6 @@ impl Workspace { if let Err(error) = result { error!("failed to save item: {:?}, ", error); } - - handle.update(&mut cx, |this, cx| { - this.project.update(cx, |project, cx| project.diagnose(cx)) - }); }) .detach() } diff --git a/crates/zed/src/language.rs b/crates/zed/src/language.rs index 293deada40..a84d2cbd40 100644 --- a/crates/zed/src/language.rs +++ b/crates/zed/src/language.rs @@ -7,184 +7,6 @@ use std::{str, sync::Arc}; #[folder = "languages"] struct LanguageDir; -mod rust { - use anyhow::Result; - use async_trait::async_trait; - use collections::{HashMap, HashSet}; - use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity}; - use parking_lot::Mutex; - use serde::Deserialize; - use serde_json::Deserializer; - use smol::process::Command; - use std::path::{Path, PathBuf}; - use std::sync::Arc; - - #[derive(Default)] - pub struct DiagnosticProvider { - reported_paths: Mutex>>, - } - - #[derive(Debug, Deserialize)] - struct Check { - message: CompilerMessage, - } - - #[derive(Debug, Deserialize)] - struct CompilerMessage { - code: Option, - spans: Vec, - message: String, - level: ErrorLevel, - children: Vec, - } - - #[derive(Debug, Deserialize)] - enum ErrorLevel { - #[serde(rename = "warning")] - Warning, - #[serde(rename = "error")] - Error, - #[serde(rename = "help")] - Help, - #[serde(rename = "note")] - Note, - } - - #[derive(Debug, Deserialize)] - struct ErrorCode { - code: String, - } - - #[derive(Clone, Debug, Deserialize)] - struct Span { - is_primary: bool, - file_name: PathBuf, - byte_start: usize, - byte_end: usize, - expansion: Option>, - } - - #[derive(Clone, Debug, Deserialize)] - struct Expansion { - span: Span, - } - - #[async_trait] - impl language::DiagnosticProvider for DiagnosticProvider { - async fn diagnose( - &self, - root_path: Arc, - ) -> Result, Vec>>> { - let output = Command::new("cargo") - .arg("check") - .args(["--message-format", "json"]) - .current_dir(&root_path) - .output() - .await?; - - let mut group_id = 0; - let mut diagnostics_by_path = HashMap::default(); - let mut new_reported_paths = HashSet::default(); - for value in - Deserializer::from_slice(&output.stdout).into_iter::<&serde_json::value::RawValue>() - { - if let Ok(check) = serde_json::from_str::(value?.get()) { - let check_severity = match check.message.level { - ErrorLevel::Warning => DiagnosticSeverity::WARNING, - ErrorLevel::Error => DiagnosticSeverity::ERROR, - ErrorLevel::Help => DiagnosticSeverity::HINT, - ErrorLevel::Note => DiagnosticSeverity::INFORMATION, - }; - - let mut primary_span = None; - for mut span in check.message.spans { - if let Some(mut expansion) = span.expansion { - expansion.span.is_primary = span.is_primary; - span = expansion.span; - } - - let span_path: Arc = span.file_name.as_path().into(); - new_reported_paths.insert(span_path.clone()); - diagnostics_by_path - .entry(span_path) - .or_insert(Vec::new()) - .push(DiagnosticEntry { - range: span.byte_start..span.byte_end, - diagnostic: Diagnostic { - code: check.message.code.as_ref().map(|c| c.code.clone()), - severity: check_severity, - message: check.message.message.clone(), - group_id, - is_valid: true, - is_primary: span.is_primary, - is_disk_based: true, - }, - }); - - if span.is_primary { - primary_span = Some(span); - } - } - - for mut child in check.message.children { - if child.spans.is_empty() { - if let Some(primary_span) = primary_span.clone() { - child.spans.push(primary_span); - } - } else { - // TODO - continue; - } - - let child_severity = match child.level { - ErrorLevel::Warning => DiagnosticSeverity::WARNING, - ErrorLevel::Error => DiagnosticSeverity::ERROR, - ErrorLevel::Help => DiagnosticSeverity::HINT, - ErrorLevel::Note => DiagnosticSeverity::INFORMATION, - }; - - for mut span in child.spans { - if let Some(expansion) = span.expansion { - span = expansion.span; - } - - let span_path: Arc = span.file_name.as_path().into(); - new_reported_paths.insert(span_path.clone()); - diagnostics_by_path - .entry(span_path) - .or_insert(Vec::new()) - .push(DiagnosticEntry { - range: span.byte_start..span.byte_end, - diagnostic: Diagnostic { - code: child.code.as_ref().map(|c| c.code.clone()), - severity: child_severity, - message: child.message.clone(), - group_id, - is_valid: true, - is_primary: false, - is_disk_based: true, - }, - }); - } - } - - group_id += 1; - } - } - - let reported_paths = &mut *self.reported_paths.lock(); - for old_reported_path in reported_paths.iter() { - if !diagnostics_by_path.contains_key(old_reported_path) { - diagnostics_by_path.insert(old_reported_path.clone(), Default::default()); - } - } - *reported_paths = new_reported_paths; - - Ok(diagnostics_by_path) - } - } -} - pub fn build_language_registry() -> LanguageRegistry { let mut languages = LanguageRegistry::default(); languages.add(Arc::new(rust())); @@ -202,7 +24,6 @@ fn rust() -> Language { .unwrap() .with_indents_query(load_query("rust/indents.scm").as_ref()) .unwrap() - .with_diagnostic_provider(rust::DiagnosticProvider::default()) } fn markdown() -> Language { From ad1db117e6ceec1b594eff0ac4a201a186590b12 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 4 Jan 2022 17:38:45 +0100 Subject: [PATCH 06/43] Re-enable cargo check for rust-analyzer --- crates/diagnostics/src/diagnostics.rs | 80 +++++++++++++++------------ crates/language/src/diagnostic_set.rs | 1 + crates/language/src/language.rs | 8 +++ crates/lsp/src/lsp.rs | 18 +++--- crates/project/src/project.rs | 8 ++- crates/project/src/worktree.rs | 52 +++++++++++++++-- crates/zed/languages/rust/config.toml | 1 + 7 files changed, 118 insertions(+), 50 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index f523b4ca7f..aa917461c1 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -11,7 +11,7 @@ use gpui::{ }; use language::{Bias, Buffer, Diagnostic, DiagnosticEntry, Point}; use postage::watch; -use project::Project; +use project::{Project, ProjectPath}; use std::{cmp::Ordering, ops::Range, path::Path, sync::Arc}; use util::TryFutureExt; use workspace::Workspace; @@ -41,9 +41,11 @@ struct ProjectDiagnostics { } struct ProjectDiagnosticsEditor { + project: ModelHandle, editor: ViewHandle, excerpts: ModelHandle, path_states: Vec<(Arc, Vec)>, + paths_to_update: HashMap>, build_settings: BuildSettings, } @@ -95,41 +97,19 @@ impl ProjectDiagnosticsEditor { settings: watch::Receiver, cx: &mut ViewContext, ) -> Self { - let project_paths = project - .read(cx) - .diagnostic_summaries(cx) - .map(|e| e.0) - .collect::>(); - - cx.spawn(|this, mut cx| { - let project = project.clone(); - async move { - for project_path in project_paths { - let buffer = project - .update(&mut cx, |project, cx| project.open_buffer(project_path, cx)) - .await?; - this.update(&mut cx, |view, cx| view.populate_excerpts(buffer, cx)) + cx.subscribe(&project, |this, _, event, cx| match event { + project::Event::DiskBasedDiagnosticsUpdated { worktree_id } => { + if let Some(paths) = this.paths_to_update.remove(&worktree_id) { + this.update_excerpts(paths, cx); } - Result::<_, anyhow::Error>::Ok(()) } - }) - .detach(); - - cx.subscribe(&project, |_, project, event, cx| { - if let project::Event::DiagnosticsUpdated(project_path) = event { - let project_path = project_path.clone(); - cx.spawn(|this, mut cx| { - async move { - let buffer = project - .update(&mut cx, |project, cx| project.open_buffer(project_path, cx)) - .await?; - this.update(&mut cx, |view, cx| view.populate_excerpts(buffer, cx)); - Ok(()) - } - .log_err() - }) - .detach(); + project::Event::DiagnosticsUpdated(path) => { + this.paths_to_update + .entry(path.worktree_id) + .or_default() + .insert(path.clone()); } + _ => {} }) .detach(); @@ -139,12 +119,22 @@ impl ProjectDiagnosticsEditor { cx.add_view(|cx| Editor::for_buffer(excerpts.clone(), build_settings.clone(), cx)); cx.subscribe(&editor, |_, _, event, cx| cx.emit(*event)) .detach(); - Self { + + let paths_to_update = project + .read(cx) + .diagnostic_summaries(cx) + .map(|e| e.0) + .collect(); + let this = Self { + project, excerpts, editor, build_settings, path_states: Default::default(), - } + paths_to_update: Default::default(), + }; + this.update_excerpts(paths_to_update, cx); + this } #[cfg(test)] @@ -189,6 +179,23 @@ impl ProjectDiagnosticsEditor { .update(cx, |editor, cx| editor.remove_blocks(blocks_to_delete, cx)); } + fn update_excerpts(&self, paths: HashSet, cx: &mut ViewContext) { + let project = self.project.clone(); + cx.spawn(|this, mut cx| { + async move { + for path in paths { + let buffer = project + .update(&mut cx, |project, cx| project.open_buffer(path, cx)) + .await?; + this.update(&mut cx, |view, cx| view.populate_excerpts(buffer, cx)) + } + Result::<_, anyhow::Error>::Ok(()) + } + .log_err() + }) + .detach(); + } + fn populate_excerpts(&mut self, buffer: ModelHandle, cx: &mut ViewContext) { let snapshot; let path; @@ -572,7 +579,7 @@ mod tests { use client::{http::ServerResponse, test::FakeHttpClient, Client, UserStore}; use gpui::TestAppContext; use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, LanguageRegistry, PointUtf16}; - use project::FakeFs; + use project::{worktree, FakeFs}; use serde_json::json; use std::sync::Arc; use unindent::Unindent as _; @@ -813,6 +820,7 @@ mod tests { cx, ) .unwrap(); + cx.emit(worktree::Event::DiskBasedDiagnosticsUpdated); }); view.condition(&mut cx, |view, cx| view.text(cx).contains("const a")) diff --git a/crates/language/src/diagnostic_set.rs b/crates/language/src/diagnostic_set.rs index 047513fce7..9c2091739f 100644 --- a/crates/language/src/diagnostic_set.rs +++ b/crates/language/src/diagnostic_set.rs @@ -19,6 +19,7 @@ pub struct DiagnosticEntry { pub diagnostic: Diagnostic, } +#[derive(Debug)] pub struct DiagnosticGroup { pub entries: Vec>, pub primary_ix: usize, diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index fe832929a9..bd5f91b792 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -46,6 +46,7 @@ pub struct LanguageConfig { pub struct LanguageServerConfig { pub binary: String, pub disk_based_diagnostic_sources: HashSet, + pub disk_based_diagnostics_progress_token: Option, #[cfg(any(test, feature = "test-support"))] #[serde(skip)] pub fake_server: Option<(Arc, Arc)>, @@ -199,6 +200,13 @@ impl Language { .map(|config| &config.disk_based_diagnostic_sources) } + pub fn disk_based_diagnostics_progress_token(&self) -> Option<&String> { + self.config + .language_server + .as_ref() + .and_then(|config| config.disk_based_diagnostics_progress_token.as_ref()) + } + pub fn brackets(&self) -> &[BracketPair] { &self.config.brackets } diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 769922523c..d0ce93b973 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -28,7 +28,7 @@ pub use lsp_types::*; const JSON_RPC_VERSION: &'static str = "2.0"; const CONTENT_LEN_HEADER: &'static str = "Content-Length: "; -type NotificationHandler = Box; +type NotificationHandler = Box; type ResponseHandler = Box)>; pub struct LanguageServer { @@ -139,7 +139,7 @@ impl LanguageServer { if let Ok(AnyNotification { method, params }) = serde_json::from_slice(&buffer) { - if let Some(handler) = notification_handlers.read().get(method) { + if let Some(handler) = notification_handlers.write().get_mut(method) { handler(params.get()); } else { log::info!( @@ -226,15 +226,15 @@ impl LanguageServer { process_id: Default::default(), root_path: Default::default(), root_uri: Some(root_uri), - initialization_options: Some(json!({ - "checkOnSave": { - "enable": false - }, - })), + initialization_options: Default::default(), capabilities: lsp_types::ClientCapabilities { experimental: Some(json!({ "serverStatusNotification": true, })), + window: Some(lsp_types::WindowClientCapabilities { + work_done_progress: Some(true), + ..Default::default() + }), ..Default::default() }, trace: Default::default(), @@ -283,10 +283,10 @@ impl LanguageServer { } } - pub fn on_notification(&self, f: F) -> Subscription + pub fn on_notification(&self, mut f: F) -> Subscription where T: lsp_types::notification::Notification, - F: 'static + Send + Sync + Fn(T::Params), + F: 'static + Send + Sync + FnMut(T::Params), { let prev_handler = self.notification_handlers.write().insert( T::METHOD, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 499b3d4a52..acabca0033 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1,6 +1,6 @@ pub mod fs; mod ignore; -mod worktree; +pub mod worktree; use anyhow::{anyhow, Result}; use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore}; @@ -60,6 +60,7 @@ pub struct Collaborator { pub enum Event { ActiveEntryChanged(Option), WorktreeRemoved(usize), + DiskBasedDiagnosticsUpdated { worktree_id: usize }, DiagnosticsUpdated(ProjectPath), } @@ -482,6 +483,11 @@ impl Project { path: path.clone(), })); } + worktree::Event::DiskBasedDiagnosticsUpdated => { + cx.emit(Event::DiskBasedDiagnosticsUpdated { + worktree_id: worktree.id(), + }); + } }) .detach(); self.worktrees.push(worktree); diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 76f0e2b270..c74a2dc62f 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -66,6 +66,7 @@ pub enum Worktree { #[derive(Debug)] pub enum Event { + DiskBasedDiagnosticsUpdated, DiagnosticsUpdated(Arc), } @@ -1037,18 +1038,61 @@ impl LocalWorktree { .disk_based_diagnostic_sources() .cloned() .unwrap_or_default(); + let disk_based_diagnostics_progress_token = + language.disk_based_diagnostics_progress_token().cloned(); let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded(); language_server .on_notification::(move |params| { smol::block_on(diagnostics_tx.send(params)).ok(); }) .detach(); + cx.spawn_weak(|this, mut cx| { + let has_disk_based_diagnostic_progress_token = + disk_based_diagnostics_progress_token.is_some(); + async move { + while let Ok(diagnostics) = diagnostics_rx.recv().await { + if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { + handle.update(&mut cx, |this, cx| { + this.update_diagnostics(diagnostics, &disk_based_sources, cx) + .log_err(); + if !has_disk_based_diagnostic_progress_token { + cx.emit(Event::DiskBasedDiagnosticsUpdated); + } + }); + } else { + break; + } + } + } + }) + .detach(); + + let (mut disk_based_diagnostics_done_tx, mut disk_based_diagnostics_done_rx) = + watch::channel_with(()); + language_server + .on_notification::(move |params| { + let token = match params.token { + lsp::NumberOrString::Number(_) => None, + lsp::NumberOrString::String(token) => Some(token), + }; + + if token == disk_based_diagnostics_progress_token { + match params.value { + lsp::ProgressParamsValue::WorkDone(progress) => match progress { + lsp::WorkDoneProgress::End(_) => { + smol::block_on(disk_based_diagnostics_done_tx.send(())).ok(); + } + _ => {} + }, + } + } + }) + .detach(); cx.spawn_weak(|this, mut cx| async move { - while let Ok(diagnostics) = diagnostics_rx.recv().await { + while let Some(()) = disk_based_diagnostics_done_rx.recv().await { if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { - handle.update(&mut cx, |this, cx| { - this.update_diagnostics(diagnostics, &disk_based_sources, cx) - .log_err(); + handle.update(&mut cx, |_, cx| { + cx.emit(Event::DiskBasedDiagnosticsUpdated); }); } else { break; diff --git a/crates/zed/languages/rust/config.toml b/crates/zed/languages/rust/config.toml index 655a264e6c..426dcc2b48 100644 --- a/crates/zed/languages/rust/config.toml +++ b/crates/zed/languages/rust/config.toml @@ -13,3 +13,4 @@ brackets = [ [language_server] binary = "rust-analyzer" disk_based_diagnostic_sources = ["rustc"] +disk_based_diagnostics_progress_token = "rustAnalyzer/cargo check" From d8b888c9cb366870748b318b157209c84a714812 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 4 Jan 2022 14:29:22 -0800 Subject: [PATCH 07/43] Replicate diagnostic summaries Co-Authored-By: Antonio Scandurra --- crates/gpui/src/app.rs | 4 ++ crates/gpui/src/executor.rs | 20 +++++++-- crates/project/src/project.rs | 44 ++++++++++++++++++- crates/project/src/worktree.rs | 77 ++++++++++++++++++++++++++++++---- crates/rpc/proto/zed.proto | 52 +++++++++++++---------- crates/rpc/src/proto.rs | 10 +++-- crates/server/src/rpc.rs | 74 ++++++++++++++++++++++++++------ 7 files changed, 231 insertions(+), 50 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index a14664a9f8..e1b919e5fe 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -2672,6 +2672,10 @@ impl ModelHandle { } } + if cx.borrow_mut().foreground().would_park() { + panic!("parked while waiting on condition"); + } + rx.recv() .await .expect("model dropped with pending condition"); diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index 23b870c11f..3283ab7553 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -206,10 +206,7 @@ impl Deterministic { } let state = self.state.lock(); - if state.scheduled_from_foreground.is_empty() - && state.scheduled_from_background.is_empty() - && state.spawned_from_foreground.is_empty() - { + if state.would_park() { return None; } } @@ -261,6 +258,14 @@ impl Deterministic { } } +impl DeterministicState { + fn would_park(&self) -> bool { + self.scheduled_from_foreground.is_empty() + && self.scheduled_from_background.is_empty() + && self.spawned_from_foreground.is_empty() + } +} + #[derive(Default)] struct Trace { executed: Vec, @@ -433,6 +438,13 @@ impl Foreground { *any_value.downcast().unwrap() } + pub fn would_park(&self) -> bool { + match self { + Self::Deterministic(executor) => executor.state.lock().would_park(), + _ => panic!("this method can only be called on a deterministic executor"), + } + } + pub fn forbid_parking(&self) { match self { Self::Deterministic(executor) => { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index c05e0c8f4a..8ce3504e5d 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -70,7 +70,7 @@ pub struct ProjectPath { pub path: Arc, } -#[derive(Clone)] +#[derive(Clone, Debug, Default, PartialEq)] pub struct DiagnosticSummary { pub error_count: usize, pub warning_count: usize, @@ -243,6 +243,12 @@ impl Project { client.subscribe_to_entity(remote_id, cx, Self::handle_share_worktree), client.subscribe_to_entity(remote_id, cx, Self::handle_unregister_worktree), client.subscribe_to_entity(remote_id, cx, Self::handle_update_worktree), + client.subscribe_to_entity(remote_id, cx, Self::handle_update_diagnostic_summary), + client.subscribe_to_entity( + remote_id, + cx, + Self::handle_disk_based_diagnostics_updated, + ), client.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer), client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved), ], @@ -661,6 +667,42 @@ impl Project { Ok(()) } + fn handle_update_diagnostic_summary( + &mut self, + envelope: TypedEnvelope, + _: Arc, + cx: &mut ModelContext, + ) -> Result<()> { + let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); + if let Some(worktree) = self.worktree_for_id(worktree_id, cx) { + worktree.update(cx, |worktree, cx| { + worktree + .as_remote_mut() + .unwrap() + .update_diagnostic_summary(envelope, cx); + }); + } + Ok(()) + } + + fn handle_disk_based_diagnostics_updated( + &mut self, + envelope: TypedEnvelope, + _: Arc, + cx: &mut ModelContext, + ) -> Result<()> { + let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); + if let Some(worktree) = self.worktree_for_id(worktree_id, cx) { + worktree.update(cx, |worktree, cx| { + worktree + .as_remote() + .unwrap() + .disk_based_diagnostics_updated(cx); + }); + } + Ok(()) + } + pub fn handle_update_buffer( &mut self, envelope: TypedEnvelope, diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 030e55deae..f7538f6294 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -777,10 +777,38 @@ impl Worktree { } let this = self.as_local_mut().unwrap(); + let summary = DiagnosticSummary::new(&diagnostics); this.diagnostic_summaries - .insert(worktree_path.clone(), DiagnosticSummary::new(&diagnostics)); + .insert(worktree_path.clone(), summary.clone()); this.diagnostics.insert(worktree_path.clone(), diagnostics); + cx.emit(Event::DiagnosticsUpdated(worktree_path.clone())); + + if let Some(share) = this.share.as_ref() { + cx.foreground() + .spawn({ + let client = this.client.clone(); + let project_id = share.project_id; + let worktree_id = this.id().to_proto(); + let path = worktree_path.to_string_lossy().to_string(); + async move { + client + .send(proto::UpdateDiagnosticSummary { + project_id, + worktree_id, + path, + error_count: summary.error_count as u32, + warning_count: summary.warning_count as u32, + info_count: summary.info_count as u32, + hint_count: summary.hint_count as u32, + }) + .await + .log_err() + } + }) + .detach(); + } + Ok(()) } @@ -1063,6 +1091,8 @@ impl LocalWorktree { let disk_based_diagnostics_progress_token = language.disk_based_diagnostics_progress_token().cloned(); let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded(); + let (disk_based_diagnostics_done_tx, disk_based_diagnostics_done_rx) = + smol::channel::unbounded(); language_server .on_notification::(move |params| { smol::block_on(diagnostics_tx.send(params)).ok(); @@ -1071,6 +1101,7 @@ impl LocalWorktree { cx.spawn_weak(|this, mut cx| { let has_disk_based_diagnostic_progress_token = disk_based_diagnostics_progress_token.is_some(); + let disk_based_diagnostics_done_tx = disk_based_diagnostics_done_tx.clone(); async move { while let Ok(diagnostics) = diagnostics_rx.recv().await { if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { @@ -1078,9 +1109,9 @@ impl LocalWorktree { this.update_diagnostics(diagnostics, &disk_based_sources, cx) .log_err(); if !has_disk_based_diagnostic_progress_token { - cx.emit(Event::DiskBasedDiagnosticsUpdated); + smol::block_on(disk_based_diagnostics_done_tx.send(())).ok(); } - }); + }) } else { break; } @@ -1089,8 +1120,6 @@ impl LocalWorktree { }) .detach(); - let (mut disk_based_diagnostics_done_tx, mut disk_based_diagnostics_done_rx) = - watch::channel_with(()); language_server .on_notification::(move |params| { let token = match params.token { @@ -1110,12 +1139,24 @@ impl LocalWorktree { } }) .detach(); + let rpc = self.client.clone(); cx.spawn_weak(|this, mut cx| async move { - while let Some(()) = disk_based_diagnostics_done_rx.recv().await { + while let Ok(()) = disk_based_diagnostics_done_rx.recv().await { if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { - handle.update(&mut cx, |_, cx| { + let message = handle.update(&mut cx, |this, cx| { cx.emit(Event::DiskBasedDiagnosticsUpdated); + let this = this.as_local().unwrap(); + this.share + .as_ref() + .map(|share| proto::DiskBasedDiagnosticsUpdated { + project_id: share.project_id, + worktree_id: this.id().to_proto(), + }) }); + + if let Some(message) = message { + rpc.send(message).await.log_err(); + } } else { break; } @@ -1572,6 +1613,28 @@ impl RemoteWorktree { Ok(()) } + pub fn update_diagnostic_summary( + &mut self, + envelope: TypedEnvelope, + cx: &mut ModelContext, + ) { + let path: Arc = Path::new(&envelope.payload.path).into(); + self.diagnostic_summaries.insert( + path.clone(), + DiagnosticSummary { + error_count: envelope.payload.error_count as usize, + warning_count: envelope.payload.warning_count as usize, + info_count: envelope.payload.info_count as usize, + hint_count: envelope.payload.hint_count as usize, + }, + ); + cx.emit(Event::DiagnosticsUpdated(path)); + } + + pub fn disk_based_diagnostics_updated(&self, cx: &mut ModelContext) { + cx.emit(Event::DiskBasedDiagnosticsUpdated); + } + pub fn remove_collaborator(&mut self, replica_id: ReplicaId, cx: &mut ModelContext) { for (_, buffer) in &self.open_buffers { if let Some(buffer) = buffer.upgrade(cx) { diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 5ef34960e7..9ec5f90719 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -23,32 +23,33 @@ message Envelope { RegisterWorktree register_worktree = 17; UnregisterWorktree unregister_worktree = 18; - ShareWorktree share_worktree = 100; - UpdateWorktree update_worktree = 19; - UpdateDiagnosticSummary update_diagnostic_summary = 20; + ShareWorktree share_worktree = 19; + UpdateWorktree update_worktree = 20; + UpdateDiagnosticSummary update_diagnostic_summary = 21; + DiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 22; - OpenBuffer open_buffer = 22; - OpenBufferResponse open_buffer_response = 23; - CloseBuffer close_buffer = 24; - UpdateBuffer update_buffer = 25; - SaveBuffer save_buffer = 26; - BufferSaved buffer_saved = 27; + OpenBuffer open_buffer = 23; + OpenBufferResponse open_buffer_response = 24; + CloseBuffer close_buffer = 25; + UpdateBuffer update_buffer = 26; + SaveBuffer save_buffer = 27; + BufferSaved buffer_saved = 28; - GetChannels get_channels = 28; - GetChannelsResponse get_channels_response = 29; - JoinChannel join_channel = 30; - JoinChannelResponse join_channel_response = 31; - LeaveChannel leave_channel = 32; - SendChannelMessage send_channel_message = 33; - SendChannelMessageResponse send_channel_message_response = 34; - ChannelMessageSent channel_message_sent = 35; - GetChannelMessages get_channel_messages = 36; - GetChannelMessagesResponse get_channel_messages_response = 37; + GetChannels get_channels = 29; + GetChannelsResponse get_channels_response = 30; + JoinChannel join_channel = 31; + JoinChannelResponse join_channel_response = 32; + LeaveChannel leave_channel = 33; + SendChannelMessage send_channel_message = 34; + SendChannelMessageResponse send_channel_message_response = 35; + ChannelMessageSent channel_message_sent = 36; + GetChannelMessages get_channel_messages = 37; + GetChannelMessagesResponse get_channel_messages_response = 38; - UpdateContacts update_contacts = 38; + UpdateContacts update_contacts = 39; - GetUsers get_users = 39; - GetUsersResponse get_users_response = 40; + GetUsers get_users = 40; + GetUsersResponse get_users_response = 41; } } @@ -172,6 +173,13 @@ message UpdateDiagnosticSummary { string path = 3; uint32 error_count = 4; uint32 warning_count = 5; + uint32 info_count = 6; + uint32 hint_count = 7; +} + +message DiskBasedDiagnosticsUpdated { + uint64 project_id = 1; + uint64 worktree_id = 2; } message GetChannels {} diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 9274049a7b..2cabdc9218 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -125,6 +125,7 @@ messages!( BufferSaved, ChannelMessageSent, CloseBuffer, + DiskBasedDiagnosticsUpdated, Error, GetChannelMessages, GetChannelMessagesResponse, @@ -155,6 +156,7 @@ messages!( UnshareProject, UpdateBuffer, UpdateContacts, + UpdateDiagnosticSummary, UpdateWorktree, ); @@ -178,17 +180,19 @@ request_messages!( entity_messages!( project_id, AddProjectCollaborator, - RemoveProjectCollaborator, + BufferSaved, + CloseBuffer, + DiskBasedDiagnosticsUpdated, JoinProject, LeaveProject, - BufferSaved, OpenBuffer, - CloseBuffer, + RemoveProjectCollaborator, SaveBuffer, ShareWorktree, UnregisterWorktree, UnshareProject, UpdateBuffer, + UpdateDiagnosticSummary, UpdateWorktree, ); diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 0b1b7e7aab..7e955ca335 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -71,6 +71,8 @@ impl Server { .add_handler(Server::unregister_worktree) .add_handler(Server::share_worktree) .add_handler(Server::update_worktree) + .add_handler(Server::update_diagnostic_summary) + .add_handler(Server::disk_based_diagnostics_updated) .add_handler(Server::open_buffer) .add_handler(Server::close_buffer) .add_handler(Server::update_buffer) @@ -517,6 +519,38 @@ impl Server { Ok(()) } + async fn update_diagnostic_summary( + self: Arc, + request: TypedEnvelope, + ) -> tide::Result<()> { + let receiver_ids = self + .state() + .project_connection_ids(request.payload.project_id, request.sender_id) + .ok_or_else(|| anyhow!(NO_SUCH_PROJECT))?; + broadcast(request.sender_id, receiver_ids, |connection_id| { + self.peer + .forward_send(request.sender_id, connection_id, request.payload.clone()) + }) + .await?; + Ok(()) + } + + async fn disk_based_diagnostics_updated( + self: Arc, + request: TypedEnvelope, + ) -> tide::Result<()> { + let receiver_ids = self + .state() + .project_connection_ids(request.payload.project_id, request.sender_id) + .ok_or_else(|| anyhow!(NO_SUCH_PROJECT))?; + broadcast(request.sender_id, receiver_ids, |connection_id| { + self.peer + .forward_send(request.sender_id, connection_id, request.payload.clone()) + }) + .await?; + Ok(()) + } + async fn open_buffer( self: Arc, request: TypedEnvelope, @@ -1026,7 +1060,7 @@ mod tests { LanguageRegistry, LanguageServerConfig, Point, }, lsp, - project::Project, + project::{DiagnosticSummary, Project}, }; #[gpui::test] @@ -1781,6 +1815,19 @@ mod tests { .await .unwrap(); + // Join the worktree as client B. + let project_b = Project::remote( + project_id, + client_b.clone(), + client_b.user_store.clone(), + lang_registry.clone(), + fs.clone(), + &mut cx_b.to_async(), + ) + .await + .unwrap(); + let worktree_b = project_b.update(&mut cx_b, |p, _| p.worktrees()[0].clone()); + // Simulate a language server reporting errors for a file. fake_language_server .notify::(lsp::PublishDiagnosticsParams { @@ -1806,18 +1853,19 @@ mod tests { }) .await; - // Join the worktree as client B. - let project_b = Project::remote( - project_id, - client_b.clone(), - client_b.user_store.clone(), - lang_registry.clone(), - fs.clone(), - &mut cx_b.to_async(), - ) - .await - .unwrap(); - let worktree_b = project_b.update(&mut cx_b, |p, _| p.worktrees()[0].clone()); + worktree_b + .condition(&cx_b, |worktree, _| { + worktree.diagnostic_summaries().collect::>() + == &[( + Arc::from(Path::new("a.rs")), + DiagnosticSummary { + error_count: 1, + warning_count: 1, + ..Default::default() + }, + )] + }) + .await; // Open the file with the errors. let buffer_b = cx_b From 0bcd0a3f08144351a50dd11edb8902c49e473bec Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 4 Jan 2022 15:49:29 -0800 Subject: [PATCH 08/43] Forward events from remote worktrees to their projects --- crates/project/src/project.rs | 66 ++++++++++++++++++++--------------- crates/server/src/rpc.rs | 16 +++++---- 2 files changed, 48 insertions(+), 34 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 8ce3504e5d..a0ed7b811d 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -229,35 +229,45 @@ impl Project { collaborators.insert(collaborator.peer_id, collaborator); } - Ok(cx.add_model(|cx| Self { - worktrees, - active_entry: None, - collaborators, - languages, - user_store, - fs, - subscriptions: vec![ - client.subscribe_to_entity(remote_id, cx, Self::handle_unshare_project), - client.subscribe_to_entity(remote_id, cx, Self::handle_add_collaborator), - client.subscribe_to_entity(remote_id, cx, Self::handle_remove_collaborator), - client.subscribe_to_entity(remote_id, cx, Self::handle_share_worktree), - client.subscribe_to_entity(remote_id, cx, Self::handle_unregister_worktree), - client.subscribe_to_entity(remote_id, cx, Self::handle_update_worktree), - client.subscribe_to_entity(remote_id, cx, Self::handle_update_diagnostic_summary), - client.subscribe_to_entity( + Ok(cx.add_model(|cx| { + let mut this = Self { + worktrees: Vec::new(), + active_entry: None, + collaborators, + languages, + user_store, + fs, + subscriptions: vec![ + client.subscribe_to_entity(remote_id, cx, Self::handle_unshare_project), + client.subscribe_to_entity(remote_id, cx, Self::handle_add_collaborator), + client.subscribe_to_entity(remote_id, cx, Self::handle_remove_collaborator), + client.subscribe_to_entity(remote_id, cx, Self::handle_share_worktree), + client.subscribe_to_entity(remote_id, cx, Self::handle_unregister_worktree), + client.subscribe_to_entity(remote_id, cx, Self::handle_update_worktree), + client.subscribe_to_entity( + remote_id, + cx, + Self::handle_update_diagnostic_summary, + ), + client.subscribe_to_entity( + remote_id, + cx, + Self::handle_disk_based_diagnostics_updated, + ), + client.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer), + client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved), + ], + client, + client_state: ProjectClientState::Remote { + sharing_has_stopped: false, remote_id, - cx, - Self::handle_disk_based_diagnostics_updated, - ), - client.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer), - client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved), - ], - client, - client_state: ProjectClientState::Remote { - sharing_has_stopped: false, - remote_id, - replica_id, - }, + replica_id, + }, + }; + for worktree in worktrees { + this.add_worktree(worktree, cx); + } + this })) } diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 7e955ca335..0d84a89e14 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -1060,7 +1060,7 @@ mod tests { LanguageRegistry, LanguageServerConfig, Point, }, lsp, - project::{DiagnosticSummary, Project}, + project::{DiagnosticSummary, Project, ProjectPath}, }; #[gpui::test] @@ -1801,6 +1801,7 @@ mod tests { let project_id = project_a .update(&mut cx_a, |project, _| project.next_remote_id()) .await; + let worktree_id = worktree_a.read_with(&cx_a, |tree, _| tree.id()); project_a .update(&mut cx_a, |project, cx| project.share(cx)) .await @@ -1826,7 +1827,6 @@ mod tests { ) .await .unwrap(); - let worktree_b = project_b.update(&mut cx_b, |p, _| p.worktrees()[0].clone()); // Simulate a language server reporting errors for a file. fake_language_server @@ -1853,11 +1853,14 @@ mod tests { }) .await; - worktree_b - .condition(&cx_b, |worktree, _| { - worktree.diagnostic_summaries().collect::>() + project_b + .condition(&cx_b, |project, cx| { + project.diagnostic_summaries(cx).collect::>() == &[( - Arc::from(Path::new("a.rs")), + ProjectPath { + worktree_id, + path: Arc::from(Path::new("a.rs")), + }, DiagnosticSummary { error_count: 1, warning_count: 1, @@ -1868,6 +1871,7 @@ mod tests { .await; // Open the file with the errors. + let worktree_b = project_b.update(&mut cx_b, |p, _| p.worktrees()[0].clone()); let buffer_b = cx_b .background() .spawn(worktree_b.update(&mut cx_b, |worktree, cx| worktree.open_buffer("a.rs", cx))) From fe28abe8cf464251791afccb3f11606a7ae26c68 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 5 Jan 2022 17:25:03 +0100 Subject: [PATCH 09/43] Show a message when no diagnostics are available --- crates/diagnostics/src/diagnostics.rs | 66 +++++++++------------------ crates/theme/src/theme.rs | 8 ++++ crates/zed/assets/themes/_base.toml | 4 ++ 3 files changed, 34 insertions(+), 44 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 42230c6d1b..da1b661333 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -17,21 +17,12 @@ use util::TryFutureExt; use workspace::Workspace; action!(Toggle); -action!(ClearInvalid); const CONTEXT_LINE_COUNT: u32 = 1; pub fn init(cx: &mut MutableAppContext) { - cx.add_bindings([ - Binding::new("alt-shift-D", Toggle, None), - Binding::new( - "alt-shift-C", - ClearInvalid, - Some("ProjectDiagnosticsEditor"), - ), - ]); + cx.add_bindings([Binding::new("alt-shift-D", Toggle, None)]); cx.add_action(ProjectDiagnosticsEditor::toggle); - cx.add_action(ProjectDiagnosticsEditor::clear_invalid); } type Event = editor::Event; @@ -47,6 +38,7 @@ struct ProjectDiagnosticsEditor { path_states: Vec<(Arc, Vec)>, paths_to_update: HashMap>, build_settings: BuildSettings, + settings: watch::Receiver, } struct DiagnosticGroupState { @@ -83,11 +75,25 @@ impl View for ProjectDiagnosticsEditor { } fn render(&mut self, _: &mut RenderContext) -> ElementBox { - ChildView::new(self.editor.id()).boxed() + if self.path_states.is_empty() { + let theme = &self.settings.borrow().theme.project_diagnostics; + Label::new( + "No problems detected in the project".to_string(), + theme.empty_message.clone(), + ) + .aligned() + .contained() + .with_style(theme.container) + .boxed() + } else { + ChildView::new(self.editor.id()).boxed() + } } fn on_focus(&mut self, cx: &mut ViewContext) { - cx.focus(&self.editor); + if !self.path_states.is_empty() { + cx.focus(&self.editor); + } } } @@ -130,6 +136,7 @@ impl ProjectDiagnosticsEditor { excerpts, editor, build_settings, + settings, path_states: Default::default(), paths_to_update: Default::default(), }; @@ -147,38 +154,6 @@ impl ProjectDiagnosticsEditor { workspace.add_item(diagnostics, cx); } - fn clear_invalid(&mut self, _: &ClearInvalid, cx: &mut ViewContext) { - let mut blocks_to_delete = HashSet::default(); - let mut excerpts_to_delete = Vec::new(); - let mut path_ixs_to_delete = Vec::new(); - for (ix, (_, groups)) in self.path_states.iter_mut().enumerate() { - groups.retain(|group| { - if group.is_valid { - true - } else { - blocks_to_delete.extend(group.blocks.keys().copied()); - excerpts_to_delete.extend(group.excerpts.iter().cloned()); - false - } - }); - - if groups.is_empty() { - path_ixs_to_delete.push(ix); - } - } - - for ix in path_ixs_to_delete.into_iter().rev() { - self.path_states.remove(ix); - } - - self.excerpts.update(cx, |excerpts, cx| { - excerpts_to_delete.sort_unstable(); - excerpts.remove_excerpts(&excerpts_to_delete, cx) - }); - self.editor - .update(cx, |editor, cx| editor.remove_blocks(blocks_to_delete, cx)); - } - fn update_excerpts(&self, paths: HashSet, cx: &mut ViewContext) { let project = self.project.clone(); cx.spawn(|this, mut cx| { @@ -488,6 +463,9 @@ impl ProjectDiagnosticsEditor { self.path_states.remove(path_ix); } + if self.editor.is_focused(cx) && self.path_states.is_empty() { + cx.focus_self(); + } cx.notify(); } } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 6fca696689..6a67789afe 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -24,6 +24,7 @@ pub struct Theme { pub project_panel: ProjectPanel, pub selector: Selector, pub editor: EditorStyle, + pub project_diagnostics: ProjectDiagnostics, } #[derive(Deserialize, Default)] @@ -226,6 +227,13 @@ pub struct ContainedLabel { pub label: LabelStyle, } +#[derive(Clone, Deserialize, Default)] +pub struct ProjectDiagnostics { + #[serde(flatten)] + pub container: ContainerStyle, + pub empty_message: TextStyle, +} + #[derive(Clone, Deserialize, Default)] pub struct EditorStyle { pub text: TextStyle, diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index 958620521d..4063fc92f9 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -257,3 +257,7 @@ information_diagnostic = { text = "$status.info" } invalid_information_diagnostic = { text = "$text.3.color" } hint_diagnostic = { text = "$status.info" } invalid_hint_diagnostic = { text = "$text.3.color" } + +[project_diagnostics] +background = "$surface.1" +empty_message = "$text.0" From 29b63ae4c68123af148af47f8b07962aa6f4b0b4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 5 Jan 2022 17:35:45 +0100 Subject: [PATCH 10/43] Remove invalid excerpts as opposed to styling them differently --- crates/diagnostics/src/diagnostics.rs | 89 ++------------------------- crates/editor/src/editor.rs | 4 -- 2 files changed, 5 insertions(+), 88 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index da1b661333..8820c1097d 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -46,7 +46,6 @@ struct DiagnosticGroupState { excerpts: Vec, blocks: HashMap, block_count: usize, - is_valid: bool, } enum DiagnosticBlock { @@ -207,18 +206,10 @@ impl ProjectDiagnosticsEditor { let mut groups_to_add = Vec::new(); let mut group_ixs_to_remove = Vec::new(); let mut blocks_to_add = Vec::new(); - let mut blocks_to_restyle = HashMap::default(); let mut blocks_to_remove = HashSet::default(); - let selected_excerpts = self - .editor - .read(cx) - .local_anchor_selections() - .iter() - .flat_map(|s| [s.start.excerpt_id().clone(), s.end.excerpt_id().clone()]) - .collect::>(); let mut diagnostic_blocks = Vec::new(); let excerpts_snapshot = self.excerpts.update(cx, |excerpts, excerpts_cx| { - let mut old_groups = groups.iter_mut().enumerate().peekable(); + let mut old_groups = groups.iter().enumerate().peekable(); let mut new_groups = snapshot .diagnostic_groups() .into_iter() @@ -228,7 +219,6 @@ impl ProjectDiagnosticsEditor { loop { let mut to_insert = None; let mut to_invalidate = None; - let mut to_validate = None; match (old_groups.peek(), new_groups.peek()) { (None, None) => break, (None, Some(_)) => to_insert = new_groups.next(), @@ -239,7 +229,7 @@ impl ProjectDiagnosticsEditor { match compare_diagnostics(old_primary, new_primary, &snapshot) { Ordering::Less => to_invalidate = old_groups.next(), Ordering::Equal => { - to_validate = old_groups.next(); + old_groups.next(); new_groups.next(); } Ordering::Greater => to_insert = new_groups.next(), @@ -253,7 +243,6 @@ impl ProjectDiagnosticsEditor { excerpts: Default::default(), blocks: Default::default(), block_count: 0, - is_valid: true, }; let mut pending_range: Option<(Range, usize)> = None; let mut is_first_excerpt_for_group = true; @@ -345,76 +334,9 @@ impl ProjectDiagnosticsEditor { groups_to_add.push(group_state); } else if let Some((group_ix, group_state)) = to_invalidate { - if group_state - .excerpts - .iter() - .any(|excerpt_id| selected_excerpts.contains(excerpt_id)) - { - for (block_id, block) in &group_state.blocks { - match block { - DiagnosticBlock::Header(diagnostic) => { - blocks_to_restyle.insert( - *block_id, - diagnostic_header_renderer( - buffer.clone(), - diagnostic.clone(), - false, - self.build_settings.clone(), - ), - ); - } - DiagnosticBlock::Inline(diagnostic) => { - blocks_to_restyle.insert( - *block_id, - diagnostic_block_renderer( - diagnostic.clone(), - false, - self.build_settings.clone(), - ), - ); - } - DiagnosticBlock::Context => {} - } - } - - group_state.is_valid = false; - prev_excerpt_id = group_state.excerpts.last().unwrap().clone(); - } else { - excerpts.remove_excerpts(group_state.excerpts.iter(), excerpts_cx); - group_ixs_to_remove.push(group_ix); - blocks_to_remove.extend(group_state.blocks.keys().copied()); - } - } else if let Some((_, group_state)) = to_validate { - for (block_id, block) in &group_state.blocks { - match block { - DiagnosticBlock::Header(diagnostic) => { - blocks_to_restyle.insert( - *block_id, - diagnostic_header_renderer( - buffer.clone(), - diagnostic.clone(), - true, - self.build_settings.clone(), - ), - ); - } - DiagnosticBlock::Inline(diagnostic) => { - blocks_to_restyle.insert( - *block_id, - diagnostic_block_renderer( - diagnostic.clone(), - true, - self.build_settings.clone(), - ), - ); - } - DiagnosticBlock::Context => {} - } - } - group_state.is_valid = true; - prev_excerpt_id = group_state.excerpts.last().unwrap().clone(); - } else { - unreachable!(); + excerpts.remove_excerpts(group_state.excerpts.iter(), excerpts_cx); + group_ixs_to_remove.push(group_ix); + blocks_to_remove.extend(group_state.blocks.keys().copied()); } } @@ -423,7 +345,6 @@ impl ProjectDiagnosticsEditor { self.editor.update(cx, |editor, cx| { editor.remove_blocks(blocks_to_remove, cx); - editor.replace_blocks(blocks_to_restyle, cx); let mut block_ids = editor .insert_blocks( blocks_to_add.into_iter().map(|block| { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index f418efd8d0..ddf3fbd37f 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3098,10 +3098,6 @@ impl Editor { .collect() } - pub fn local_anchor_selections(&self) -> &Arc<[Selection]> { - &self.selections - } - fn resolve_selections<'a, D, I>( &self, selections: I, From 85a13fa4772ee6f72db29098120f2ae12a48baa8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 5 Jan 2022 11:28:49 -0800 Subject: [PATCH 11/43] Fix panic when resolving anchors after an excerpt id has been recycled Co-Authored-By: Nathan Sobo --- crates/editor/src/multi_buffer.rs | 57 +++++++++++++++++++++++- crates/editor/src/multi_buffer/anchor.rs | 5 +++ crates/text/src/text.rs | 4 +- 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 7e71855236..4d39b92140 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -1291,7 +1291,7 @@ impl MultiBufferSnapshot { let mut position = D::from_text_summary(&cursor.start().text); if let Some(excerpt) = cursor.item() { - if excerpt.id == anchor.excerpt_id { + if excerpt.id == anchor.excerpt_id && excerpt.buffer_id == anchor.buffer_id { let excerpt_buffer_start = excerpt.range.start.summary::(&excerpt.buffer); let buffer_position = anchor.text_anchor.summary::(&excerpt.buffer); if buffer_position > excerpt_buffer_start { @@ -1312,6 +1312,7 @@ impl MultiBufferSnapshot { let mut summaries = Vec::new(); while let Some(anchor) = anchors.peek() { let excerpt_id = &anchor.excerpt_id; + let buffer_id = anchor.buffer_id; let excerpt_anchors = iter::from_fn(|| { let anchor = anchors.peek()?; if anchor.excerpt_id == *excerpt_id { @@ -1328,7 +1329,7 @@ impl MultiBufferSnapshot { let position = D::from_text_summary(&cursor.start().text); if let Some(excerpt) = cursor.item() { - if excerpt.id == *excerpt_id { + if excerpt.id == *excerpt_id && excerpt.buffer_id == buffer_id { let excerpt_buffer_start = excerpt.range.start.summary::(&excerpt.buffer); summaries.extend( excerpt @@ -1379,6 +1380,7 @@ impl MultiBufferSnapshot { let text_anchor = excerpt.clip_anchor(excerpt.buffer.anchor_at(buffer_start + overshoot, bias)); Anchor { + buffer_id: excerpt.buffer_id, excerpt_id: excerpt.id.clone(), text_anchor, } @@ -1397,6 +1399,7 @@ impl MultiBufferSnapshot { let text_anchor = excerpt.clip_anchor(text_anchor); drop(cursor); return Anchor { + buffer_id: excerpt.buffer_id, excerpt_id, text_anchor, }; @@ -1595,10 +1598,12 @@ impl MultiBufferSnapshot { .flat_map(move |(replica_id, selections)| { selections.map(move |selection| { let mut start = Anchor { + buffer_id: excerpt.buffer_id, excerpt_id: excerpt.id.clone(), text_anchor: selection.start.clone(), }; let mut end = Anchor { + buffer_id: excerpt.buffer_id, excerpt_id: excerpt.id.clone(), text_anchor: selection.end.clone(), }; @@ -2349,6 +2354,54 @@ mod tests { assert_eq!(old_snapshot.anchor_after(10).to_offset(&new_snapshot), 14); } + #[gpui::test] + fn test_multibuffer_anchors_after_replacing_excerpts(cx: &mut MutableAppContext) { + let buffer_1 = cx.add_model(|cx| Buffer::new(0, "abcd", cx)); + let buffer_2 = cx.add_model(|cx| Buffer::new(0, "efghi", cx)); + + // Create an insertion id in buffer 1 that doesn't exist in buffer 2 + buffer_1.update(cx, |buffer, cx| { + buffer.edit([4..4], "123", cx); + }); + + let multibuffer = cx.add_model(|_| MultiBuffer::new(0)); + let excerpt_id = multibuffer.update(cx, |multibuffer, cx| { + multibuffer.push_excerpt( + ExcerptProperties { + buffer: &buffer_1, + range: 0..7, + }, + cx, + ) + }); + + // Create an anchor in the second insertion of buffer 1 + let anchor = multibuffer.read(cx).read(cx).anchor_before(7); + + multibuffer.update(cx, |multibuffer, cx| { + multibuffer.remove_excerpts([&excerpt_id], cx); + let new_excerpt_id = multibuffer.push_excerpt( + ExcerptProperties { + buffer: &buffer_2, + range: 0..5, + }, + cx, + ); + + // The same id is reused for an excerpt in a different buffer. + assert_eq!(new_excerpt_id, excerpt_id); + + // We don't attempt to resolve the text anchor from buffer 1 + // in buffer 2. + let snapshot = multibuffer.snapshot(cx); + assert_eq!(snapshot.summary_for_anchor::(&anchor), 0); + assert_eq!( + snapshot.summaries_for_anchors::(&[anchor]), + vec![0] + ); + }); + } + #[gpui::test(iterations = 100)] fn test_random_excerpts(cx: &mut MutableAppContext, mut rng: StdRng) { let operations = env::var("OPERATIONS") diff --git a/crates/editor/src/multi_buffer/anchor.rs b/crates/editor/src/multi_buffer/anchor.rs index 758a62526b..2e1e1a9246 100644 --- a/crates/editor/src/multi_buffer/anchor.rs +++ b/crates/editor/src/multi_buffer/anchor.rs @@ -9,6 +9,7 @@ use text::{rope::TextDimension, Point}; #[derive(Clone, Eq, PartialEq, Debug, Hash)] pub struct Anchor { + pub(crate) buffer_id: usize, pub(crate) excerpt_id: ExcerptId, pub(crate) text_anchor: text::Anchor, } @@ -16,6 +17,7 @@ pub struct Anchor { impl Anchor { pub fn min() -> Self { Self { + buffer_id: 0, excerpt_id: ExcerptId::min(), text_anchor: text::Anchor::min(), } @@ -23,6 +25,7 @@ impl Anchor { pub fn max() -> Self { Self { + buffer_id: 0, excerpt_id: ExcerptId::max(), text_anchor: text::Anchor::max(), } @@ -54,6 +57,7 @@ impl Anchor { if self.text_anchor.bias != Bias::Left { if let Some(buffer_snapshot) = snapshot.buffer_snapshot_for_excerpt(&self.excerpt_id) { return Self { + buffer_id: self.buffer_id, excerpt_id: self.excerpt_id.clone(), text_anchor: self.text_anchor.bias_left(buffer_snapshot), }; @@ -66,6 +70,7 @@ impl Anchor { if self.text_anchor.bias != Bias::Right { if let Some(buffer_snapshot) = snapshot.buffer_snapshot_for_excerpt(&self.excerpt_id) { return Self { + buffer_id: self.buffer_id, excerpt_id: self.excerpt_id.clone(), text_anchor: self.text_anchor.bias_right(buffer_snapshot), }; diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 9db7591f22..402cbfecfa 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1495,7 +1495,7 @@ impl BufferSnapshot { insertion_cursor.prev(&()); } let insertion = insertion_cursor.item().expect("invalid insertion"); - debug_assert_eq!(insertion.timestamp, anchor.timestamp, "invalid insertion"); + assert_eq!(insertion.timestamp, anchor.timestamp, "invalid insertion"); fragment_cursor.seek_forward(&Some(&insertion.fragment_id), Bias::Left, &None); let fragment = fragment_cursor.item().unwrap(); @@ -1537,7 +1537,7 @@ impl BufferSnapshot { insertion_cursor.prev(&()); } let insertion = insertion_cursor.item().expect("invalid insertion"); - debug_assert_eq!(insertion.timestamp, anchor.timestamp, "invalid insertion"); + assert_eq!(insertion.timestamp, anchor.timestamp, "invalid insertion"); let mut fragment_cursor = self.fragments.cursor::<(Option<&Locator>, usize)>(); fragment_cursor.seek(&Some(&insertion.fragment_id), Bias::Left, &None); From 5a53eeef63859ff3e2faa6fa6879c5a19d0713e6 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 5 Jan 2022 12:31:00 -0800 Subject: [PATCH 12/43] Don't scroll editors away from the top of their buffer when content changes Co-Authored-By: Nathan Sobo --- crates/editor/src/editor.rs | 41 +++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ddf3fbd37f..b2b76d2ffa 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -365,7 +365,7 @@ pub struct Editor { select_larger_syntax_node_stack: Vec]>>, active_diagnostics: Option, scroll_position: Vector2F, - scroll_top_anchor: Anchor, + scroll_top_anchor: Option, autoscroll_request: Option, build_settings: BuildSettings, focused: bool, @@ -383,7 +383,7 @@ pub struct EditorSnapshot { pub placeholder_text: Option>, is_focused: bool, scroll_position: Vector2F, - scroll_top_anchor: Anchor, + scroll_top_anchor: Option, } struct PendingSelection { @@ -495,7 +495,7 @@ impl Editor { active_diagnostics: None, build_settings, scroll_position: Vector2F::zero(), - scroll_top_anchor: Anchor::min(), + scroll_top_anchor: None, autoscroll_request: None, focused: false, show_local_cursors: false, @@ -565,15 +565,22 @@ impl Editor { pub fn set_scroll_position(&mut self, scroll_position: Vector2F, cx: &mut ViewContext) { let map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let scroll_top_buffer_offset = - DisplayPoint::new(scroll_position.y() as u32, 0).to_offset(&map, Bias::Right); - self.scroll_top_anchor = map - .buffer_snapshot - .anchor_at(scroll_top_buffer_offset, Bias::Right); - self.scroll_position = vec2f( - scroll_position.x(), - scroll_position.y() - self.scroll_top_anchor.to_display_point(&map).row() as f32, - ); + + if scroll_position.y() == 0. { + self.scroll_top_anchor = None; + self.scroll_position = scroll_position; + } else { + let scroll_top_buffer_offset = + DisplayPoint::new(scroll_position.y() as u32, 0).to_offset(&map, Bias::Right); + let anchor = map + .buffer_snapshot + .anchor_at(scroll_top_buffer_offset, Bias::Right); + self.scroll_position = vec2f( + scroll_position.x(), + scroll_position.y() - anchor.to_display_point(&map).row() as f32, + ); + self.scroll_top_anchor = Some(anchor); + } cx.notify(); } @@ -3631,10 +3638,14 @@ impl EditorSettings { fn compute_scroll_position( snapshot: &DisplaySnapshot, mut scroll_position: Vector2F, - scroll_top_anchor: &Anchor, + scroll_top_anchor: &Option, ) -> Vector2F { - let scroll_top = scroll_top_anchor.to_display_point(snapshot).row() as f32; - scroll_position.set_y(scroll_top + scroll_position.y()); + if let Some(anchor) = scroll_top_anchor { + let scroll_top = anchor.to_display_point(snapshot).row() as f32; + scroll_position.set_y(scroll_top + scroll_position.y()); + } else { + scroll_position.set_y(0.); + } scroll_position } From 7340e8305902eb5bce9efd28a5d60f8280bf1cd2 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 5 Jan 2022 21:12:49 -0800 Subject: [PATCH 13/43] WIP - MultiBuffer::refresh_anchors --- crates/editor/src/editor.rs | 13 +++ crates/editor/src/multi_buffer.rs | 175 +++++++++++++++++++++++++----- 2 files changed, 160 insertions(+), 28 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b2b76d2ffa..84735c3aa0 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3258,6 +3258,19 @@ impl Editor { ); } + pub fn refresh_selections(&mut self, cx: &mut ViewContext) { + let anchors = self.buffer.update(cx, |buffer, cx| { + let snapshot = buffer.read(cx); + snapshot.refresh_anchors( + self.selections + .iter() + .flat_map(|selection| [&selection.start, &selection.end]), + ) + }); + + todo!(); + } + fn set_selections(&mut self, selections: Arc<[Selection]>, cx: &mut ViewContext) { self.selections = selections; self.buffer.update(cx, |buffer, cx| { diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 236aa1d0b7..7f56239f83 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -1302,6 +1302,61 @@ impl MultiBufferSnapshot { position } + pub fn refresh_anchors<'a, I>(&'a self, anchors: I) -> Vec + where + I: 'a + IntoIterator, + { + let mut anchors = anchors.into_iter().peekable(); + let mut cursor = self.excerpts.cursor::>(); + let mut result = Vec::new(); + while let Some(anchor) = anchors.peek() { + let old_excerpt_id = &anchor.excerpt_id; + + // Find the location where this anchor's excerpt should be, + cursor.seek_forward(&Some(old_excerpt_id), Bias::Left, &()); + if cursor.item().is_none() { + cursor.next(&()); + } + + let next_excerpt = cursor.item(); + let prev_excerpt = cursor.prev_item(); + + // Process all of the anchors for this excerpt. + while let Some(&anchor) = anchors.peek() { + if anchor.excerpt_id != *old_excerpt_id { + break; + } + let mut anchor = anchors.next().unwrap().clone(); + + // If the old excerpt no longer exists at this location, then attempt to + // find an equivalent position for this anchor in an adjacent excerpt. + if next_excerpt.map_or(true, |e| e.id != *old_excerpt_id) { + for excerpt in [next_excerpt, prev_excerpt].iter().filter_map(|e| *e) { + if excerpt.buffer_id == anchor.buffer_id + && excerpt + .range + .start + .cmp(&anchor.text_anchor, &excerpt.buffer) + .unwrap() + .is_le() + && excerpt + .range + .end + .cmp(&anchor.text_anchor, &excerpt.buffer) + .unwrap() + .is_ge() + { + anchor.excerpt_id = excerpt.id.clone(); + } + } + } + + result.push(anchor); + } + } + result + } + pub fn summaries_for_anchors<'a, D, I>(&'a self, anchors: I) -> Vec where D: TextDimension + Ord + Sub, @@ -2375,17 +2430,17 @@ mod tests { } #[gpui::test] - fn test_multibuffer_anchors_after_replacing_excerpts(cx: &mut MutableAppContext) { + fn test_multibuffer_resolving_anchors_after_replacing_their_excerpts( + cx: &mut MutableAppContext, + ) { let buffer_1 = cx.add_model(|cx| Buffer::new(0, "abcd", cx)); - let buffer_2 = cx.add_model(|cx| Buffer::new(0, "efghi", cx)); - - // Create an insertion id in buffer 1 that doesn't exist in buffer 2 - buffer_1.update(cx, |buffer, cx| { - buffer.edit([4..4], "123", cx); - }); - + let buffer_2 = cx.add_model(|cx| Buffer::new(0, "ABCDEFGHIJKLMNOP", cx)); let multibuffer = cx.add_model(|_| MultiBuffer::new(0)); - let excerpt_id = multibuffer.update(cx, |multibuffer, cx| { + + // Create an insertion id in buffer 1 that doesn't exist in buffer 2. + // Add an excerpt from buffer 1 that spans this new insertion. + buffer_1.update(cx, |buffer, cx| buffer.edit([4..4], "123", cx)); + let excerpt_id_1 = multibuffer.update(cx, |multibuffer, cx| { multibuffer.push_excerpt( ExcerptProperties { buffer: &buffer_1, @@ -2395,31 +2450,95 @@ mod tests { ) }); - // Create an anchor in the second insertion of buffer 1 - let anchor = multibuffer.read(cx).read(cx).anchor_before(7); + let snapshot_1 = multibuffer.read(cx).snapshot(cx); + assert_eq!(snapshot_1.text(), "abcd123"); - multibuffer.update(cx, |multibuffer, cx| { - multibuffer.remove_excerpts([&excerpt_id], cx); - let new_excerpt_id = multibuffer.push_excerpt( + // Replace the buffer 1 excerpt with new excerpts from buffer 2. + let (excerpt_id_2, excerpt_id_3, excerpt_id_4) = + multibuffer.update(cx, |multibuffer, cx| { + multibuffer.remove_excerpts([&excerpt_id_1], cx); + ( + multibuffer.push_excerpt( + ExcerptProperties { + buffer: &buffer_2, + range: 0..4, + }, + cx, + ), + multibuffer.push_excerpt( + ExcerptProperties { + buffer: &buffer_2, + range: 6..10, + }, + cx, + ), + multibuffer.push_excerpt( + ExcerptProperties { + buffer: &buffer_2, + range: 12..16, + }, + cx, + ), + ) + }); + let snapshot_2 = multibuffer.read(cx).snapshot(cx); + assert_eq!(snapshot_2.text(), "ABCD\nGHIJ\nMNOP"); + + // And excerpt id has been reused. + assert_eq!(excerpt_id_2, excerpt_id_1); + + // Resolve some anchors from the previous snapshot in the new snapshot. + // Although there is still an excerpt with the same id, it is for + // a different buffer, so we don't attempt to resolve the old text + // anchor in the new buffer. + assert_eq!( + snapshot_2.summary_for_anchor::(&snapshot_1.anchor_before(2)), + 0 + ); + assert_eq!( + snapshot_2.summaries_for_anchors::(&[ + snapshot_1.anchor_before(2), + snapshot_1.anchor_after(3) + ]), + vec![0, 0] + ); + + // Replace the middle excerpt with a smaller excerpt in buffer 2, + // that intersects the old excerpt. + let excerpt_id_5 = multibuffer.update(cx, |multibuffer, cx| { + multibuffer.remove_excerpts([&excerpt_id_3], cx); + multibuffer.insert_excerpt_after( + &excerpt_id_3, ExcerptProperties { buffer: &buffer_2, - range: 0..5, + range: 5..8, }, cx, - ); - - // The same id is reused for an excerpt in a different buffer. - assert_eq!(new_excerpt_id, excerpt_id); - - // We don't attempt to resolve the text anchor from buffer 1 - // in buffer 2. - let snapshot = multibuffer.snapshot(cx); - assert_eq!(snapshot.summary_for_anchor::(&anchor), 0); - assert_eq!( - snapshot.summaries_for_anchors::(&[anchor]), - vec![0] - ); + ) }); + + let snapshot_3 = multibuffer.read(cx).snapshot(cx); + assert_eq!(snapshot_3.text(), "ABCD\nFGH\nMNOP"); + assert_ne!(excerpt_id_5, excerpt_id_3); + + // Resolve some anchors from the previous snapshot in the new snapshot. + // The anchor in the middle excerpt snaps to the beginning of the + // excerpt, since it is not + let anchors = [ + snapshot_2.anchor_after(2), + snapshot_2.anchor_after(6), + snapshot_2.anchor_after(14), + ]; + assert_eq!( + snapshot_3.summaries_for_anchors::(&anchors), + &[2, 9, 13] + ); + + let new_anchors = snapshot_3.refresh_anchors(&anchors); + assert_eq!( + snapshot_3.summaries_for_anchors::(&new_anchors), + &[2, 7, 13] + ); } #[gpui::test(iterations = 100)] From f37f8393302c9f67a0272ba475eccbd784681e65 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 6 Jan 2022 15:32:37 +0100 Subject: [PATCH 14/43] Wire up `refresh_anchors` in `Editor::refresh_selections` and call it --- crates/diagnostics/src/diagnostics.rs | 2 ++ crates/editor/src/editor.rs | 13 +++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 8820c1097d..28f82d7fe3 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -364,6 +364,8 @@ impl ProjectDiagnosticsEditor { for group_state in &mut groups_to_add { group_state.blocks = block_ids.by_ref().take(group_state.block_count).collect(); } + + editor.refresh_selections(cx); }); for ix in group_ixs_to_remove.into_iter().rev() { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 84735c3aa0..a75e139724 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3267,8 +3267,17 @@ impl Editor { .flat_map(|selection| [&selection.start, &selection.end]), ) }); - - todo!(); + self.selections = self + .selections + .iter() + .cloned() + .zip(anchors.chunks(2)) + .map(|(mut selection, anchors)| { + selection.start = anchors[0].clone(); + selection.end = anchors[1].clone(); + selection + }) + .collect(); } fn set_selections(&mut self, selections: Arc<[Selection]>, cx: &mut ViewContext) { From d0f7e5f075b85b25b09b76868da4d144385734b5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 6 Jan 2022 15:33:02 +0100 Subject: [PATCH 15/43] Maintain excerpt ordering correctly when some errors don't change --- crates/diagnostics/src/diagnostics.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 28f82d7fe3..5c7f4f01bd 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -219,6 +219,7 @@ impl ProjectDiagnosticsEditor { loop { let mut to_insert = None; let mut to_invalidate = None; + let mut to_keep = None; match (old_groups.peek(), new_groups.peek()) { (None, None) => break, (None, Some(_)) => to_insert = new_groups.next(), @@ -229,7 +230,7 @@ impl ProjectDiagnosticsEditor { match compare_diagnostics(old_primary, new_primary, &snapshot) { Ordering::Less => to_invalidate = old_groups.next(), Ordering::Equal => { - old_groups.next(); + to_keep = old_groups.next(); new_groups.next(); } Ordering::Greater => to_insert = new_groups.next(), @@ -337,6 +338,8 @@ impl ProjectDiagnosticsEditor { excerpts.remove_excerpts(group_state.excerpts.iter(), excerpts_cx); group_ixs_to_remove.push(group_ix); blocks_to_remove.extend(group_state.blocks.keys().copied()); + } else if let Some((_, group)) = to_keep { + prev_excerpt_id = group.excerpts.last().unwrap().clone(); } } From 1875a0e349b955e802d46a9c444532d34da01b4f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 6 Jan 2022 16:17:53 +0100 Subject: [PATCH 16/43] Polish rendering of inline errors - Don't soft-wrap - Render multiple lines Co-Authored-By: Nathan Sobo --- crates/diagnostics/src/diagnostics.rs | 2 +- crates/editor/src/editor.rs | 7 ++++++- crates/gpui/src/elements/text.rs | 22 ++++++++++++++++++---- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 5c7f4f01bd..3b495b414c 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -285,7 +285,7 @@ impl ProjectDiagnosticsEditor { diagnostic_blocks.push(DiagnosticBlock::Header(primary.clone())); blocks_to_add.push(BlockProperties { position: header_position, - height: 2, + height: primary.message.matches('\n').count() as u8 + 2, render: diagnostic_header_renderer( buffer.clone(), primary.clone(), diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index a75e139724..c8d95da3eb 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3800,6 +3800,7 @@ pub fn diagnostic_block_renderer( let mut text_style = settings.style.text.clone(); text_style.color = diagnostic_style(diagnostic.severity, is_valid, &settings.style).text; Text::new(diagnostic.message.clone(), text_style) + .with_soft_wrap(false) .contained() .with_margin_left(cx.anchor_x) .boxed() @@ -3823,7 +3824,11 @@ pub fn diagnostic_header_renderer( }; Flex::column() - .with_child(Label::new(diagnostic.message.clone(), text_style).boxed()) + .with_child( + Text::new(diagnostic.message.clone(), text_style) + .with_soft_wrap(false) + .boxed(), + ) .with_child(Label::new(file_path, settings.style.text.clone()).boxed()) .boxed() }) diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index 623af72af6..2f20b77d56 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -14,6 +14,7 @@ use serde_json::json; pub struct Text { text: String, style: TextStyle, + soft_wrap: bool, } pub struct LayoutState { @@ -23,13 +24,22 @@ pub struct LayoutState { impl Text { pub fn new(text: String, style: TextStyle) -> Self { - Self { text, style } + Self { + text, + style, + soft_wrap: true, + } } pub fn with_default_color(mut self, color: Color) -> Self { self.style.color = color; self } + + pub fn with_soft_wrap(mut self, soft_wrap: bool) -> Self { + self.soft_wrap = soft_wrap; + self + } } impl Element for Text { @@ -54,9 +64,13 @@ impl Element for Text { self.style.font_size, &[(line.len(), self.style.to_run())], ); - let wrap_boundaries = wrapper - .wrap_shaped_line(line, &shaped_line, constraint.max.x()) - .collect::>(); + let wrap_boundaries = if self.soft_wrap { + wrapper + .wrap_shaped_line(line, &shaped_line, constraint.max.x()) + .collect::>() + } else { + Vec::new() + }; max_line_width = max_line_width.max(shaped_line.width()); line_count += wrap_boundaries.len() + 1; From 571d0386e25b77bd207ef078855f9eca63ab715d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 6 Jan 2022 08:35:31 -0700 Subject: [PATCH 17/43] Re-focus diagnostics editor when transitioning from an empty to a populated state Co-Authored-By: Antonio Scandurra --- crates/diagnostics/src/diagnostics.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 3b495b414c..2aa062af91 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -389,8 +389,14 @@ impl ProjectDiagnosticsEditor { self.path_states.remove(path_ix); } - if self.editor.is_focused(cx) && self.path_states.is_empty() { - cx.focus_self(); + if self.path_states.is_empty() { + if self.editor.is_focused(cx) { + cx.focus_self(); + } + } else { + if cx.handle().is_focused(cx) { + cx.focus(&self.editor); + } } cx.notify(); } From d7a78e14ac6d40ea7f51c2a98a8a48967d7e2c82 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 6 Jan 2022 09:32:08 -0700 Subject: [PATCH 18/43] Allow disk-based diagnostic progress begin/end events to interleave When multiple saves occur, we can have multiple start events followed by multiple end events. We don't want to update our project diagnostics view until all pending progress is finished. Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map.rs | 9 ++-- crates/editor/src/display_map/wrap_map.rs | 11 ++--- crates/editor/src/test.rs | 27 ------------ crates/gpui/src/app.rs | 2 +- crates/gpui/src/test.rs | 52 ++++++++++++++++++++++- crates/language/src/language.rs | 1 + crates/lsp/src/lsp.rs | 16 +++++++ crates/project/src/worktree.rs | 36 +++++++++++++++- 8 files changed, 114 insertions(+), 40 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index cf436971a5..342ef90b38 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -446,10 +446,11 @@ impl ToDisplayPoint for Anchor { #[cfg(test)] mod tests { use super::*; - use crate::{movement, test::*}; - use gpui::{color::Color, elements::*, MutableAppContext}; + use crate::movement; + use gpui::{color::Color, elements::*, test::observe, MutableAppContext}; use language::{Buffer, Language, LanguageConfig, RandomCharIter, SelectionGoal}; use rand::{prelude::*, Rng}; + use smol::stream::StreamExt; use std::{env, sync::Arc}; use theme::SyntaxTheme; use util::test::sample_text; @@ -493,7 +494,7 @@ mod tests { let map = cx.add_model(|cx| { DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, wrap_width, cx) }); - let (_observer, notifications) = Observer::new(&map, &mut cx); + let mut notifications = observe(&map, &mut cx); let mut fold_count = 0; let mut blocks = Vec::new(); @@ -589,7 +590,7 @@ mod tests { } if map.read_with(&cx, |map, cx| map.is_rewrapping(cx)) { - notifications.recv().await.unwrap(); + notifications.next().await.unwrap(); } let snapshot = map.update(&mut cx, |map, cx| map.snapshot(cx)); diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index b7e96c4906..8b02dbbd15 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1014,11 +1014,12 @@ mod tests { use super::*; use crate::{ display_map::{fold_map::FoldMap, tab_map::TabMap}, - test::Observer, MultiBuffer, }; + use gpui::test::observe; use language::RandomCharIter; use rand::prelude::*; + use smol::stream::StreamExt; use std::{cmp, env}; use text::Rope; @@ -1072,10 +1073,10 @@ mod tests { let (wrap_map, _) = cx.update(|cx| WrapMap::new(tabs_snapshot.clone(), font_id, font_size, wrap_width, cx)); - let (_observer, notifications) = Observer::new(&wrap_map, &mut cx); + let mut notifications = observe(&wrap_map, &mut cx); if wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) { - notifications.recv().await.unwrap(); + notifications.next().await.unwrap(); } let (initial_snapshot, _) = wrap_map.update(&mut cx, |map, cx| { @@ -1148,7 +1149,7 @@ mod tests { if wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) && rng.gen_bool(0.4) { log::info!("Waiting for wrapping to finish"); while wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) { - notifications.recv().await.unwrap(); + notifications.next().await.unwrap(); } wrap_map.read_with(&cx, |map, _| assert!(map.pending_edits.is_empty())); } @@ -1236,7 +1237,7 @@ mod tests { if wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) { log::info!("Waiting for wrapping to finish"); while wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) { - notifications.recv().await.unwrap(); + notifications.next().await.unwrap(); } } wrap_map.read_with(&cx, |map, _| assert!(map.pending_edits.is_empty())); diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index 3fb538dfbd..f4622d1f6e 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -1,33 +1,6 @@ -use gpui::{Entity, ModelHandle}; -use smol::channel; -use std::marker::PhantomData; - #[cfg(test)] #[ctor::ctor] fn init_logger() { // std::env::set_var("RUST_LOG", "info"); env_logger::init(); } - -pub struct Observer(PhantomData); - -impl Entity for Observer { - type Event = (); -} - -impl Observer { - pub fn new( - handle: &ModelHandle, - cx: &mut gpui::TestAppContext, - ) -> (ModelHandle, channel::Receiver<()>) { - let (notify_tx, notify_rx) = channel::unbounded(); - let observer = cx.add_model(|cx| { - cx.observe(handle, move |_, _, _| { - let _ = notify_tx.try_send(()); - }) - .detach(); - Observer(PhantomData) - }); - (observer, notify_rx) - } -} diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 2e7e08d0a5..e42e889496 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -992,7 +992,7 @@ impl MutableAppContext { }) } - fn observe(&mut self, handle: &H, mut callback: F) -> Subscription + pub fn observe(&mut self, handle: &H, mut callback: F) -> Subscription where E: Entity, E::Event: 'static, diff --git a/crates/gpui/src/test.rs b/crates/gpui/src/test.rs index 59d49cac8d..ef95ea435a 100644 --- a/crates/gpui/src/test.rs +++ b/crates/gpui/src/test.rs @@ -7,7 +7,13 @@ use std::{ }, }; -use crate::{executor, platform, FontCache, MutableAppContext, Platform, TestAppContext}; +use futures::StreamExt; +use smol::channel; + +use crate::{ + executor, platform, Entity, FontCache, Handle, MutableAppContext, Platform, Subscription, + TestAppContext, +}; #[cfg(test)] #[ctor::ctor] @@ -87,3 +93,47 @@ pub fn run_test( } } } + +pub struct Observation { + rx: channel::Receiver, + _subscription: Subscription, +} + +impl futures::Stream for Observation { + type Item = T; + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + self.rx.poll_next_unpin(cx) + } +} + +pub fn observe(entity: &impl Handle, cx: &mut TestAppContext) -> Observation<()> { + let (tx, rx) = smol::channel::unbounded(); + let _subscription = cx.update(|cx| { + cx.observe(entity, move |_, _| { + let _ = smol::block_on(tx.send(())); + }) + }); + + Observation { rx, _subscription } +} + +pub fn subscribe( + entity: &impl Handle, + cx: &mut TestAppContext, +) -> Observation +where + T::Event: Clone, +{ + let (tx, rx) = smol::channel::unbounded(); + let _subscription = cx.update(|cx| { + cx.subscribe(entity, move |_, event, _| { + let _ = smol::block_on(tx.send(event.clone())); + }) + }); + + Observation { rx, _subscription } +} diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index bd5f91b792..9f7f9f75ac 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -237,6 +237,7 @@ impl LanguageServerConfig { ( Self { fake_server: Some((server, started)), + disk_based_diagnostics_progress_token: Some("fakeServer/check".to_string()), ..Default::default() }, fake, diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index d0ce93b973..c3d264e8a9 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -514,6 +514,22 @@ impl FakeLanguageServer { notification.params } + pub async fn start_progress(&mut self, token: impl Into) { + self.notify::(ProgressParams { + token: NumberOrString::String(token.into()), + value: ProgressParamsValue::WorkDone(WorkDoneProgress::Begin(Default::default())), + }) + .await; + } + + pub async fn end_progress(&mut self, token: impl Into) { + self.notify::(ProgressParams { + token: NumberOrString::String(token.into()), + value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(Default::default())), + }) + .await; + } + async fn send(&mut self, message: Vec) { self.stdout .write_all(CONTENT_LEN_HEADER.as_bytes()) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index f7538f6294..3e76151fdf 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -67,7 +67,7 @@ pub enum Worktree { Remote(RemoteWorktree), } -#[derive(Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum Event { DiskBasedDiagnosticsUpdated, DiagnosticsUpdated(Arc), @@ -1120,6 +1120,7 @@ impl LocalWorktree { }) .detach(); + let mut pending_disk_based_diagnostics: i32 = 0; language_server .on_notification::(move |params| { let token = match params.token { @@ -1130,8 +1131,15 @@ impl LocalWorktree { if token == disk_based_diagnostics_progress_token { match params.value { lsp::ProgressParamsValue::WorkDone(progress) => match progress { + lsp::WorkDoneProgress::Begin(_) => { + pending_disk_based_diagnostics += 1; + } lsp::WorkDoneProgress::End(_) => { - smol::block_on(disk_based_diagnostics_done_tx.send(())).ok(); + pending_disk_based_diagnostics -= 1; + if pending_disk_based_diagnostics == 0 { + smol::block_on(disk_based_diagnostics_done_tx.send(())) + .ok(); + } } _ => {} }, @@ -3107,6 +3115,7 @@ mod tests { use anyhow::Result; use client::test::{FakeHttpClient, FakeServer}; use fs::RealFs; + use gpui::test::subscribe; use language::{tree_sitter_rust, DiagnosticEntry, LanguageServerConfig}; use language::{Diagnostic, LanguageConfig}; use lsp::Url; @@ -3756,6 +3765,10 @@ mod tests { async fn test_language_server_diagnostics(mut cx: gpui::TestAppContext) { let (language_server_config, mut fake_server) = LanguageServerConfig::fake(cx.background()).await; + let progress_token = language_server_config + .disk_based_diagnostics_progress_token + .clone() + .unwrap(); let mut languages = LanguageRegistry::new(); languages.add(Arc::new(Language::new( LanguageConfig { @@ -3795,6 +3808,13 @@ mod tests { .await .unwrap(); + let mut events = subscribe(&tree, &mut cx); + + fake_server.start_progress(&progress_token).await; + fake_server.start_progress(&progress_token).await; + fake_server.end_progress(&progress_token).await; + fake_server.start_progress(&progress_token).await; + fake_server .notify::(lsp::PublishDiagnosticsParams { uri: Url::from_file_path(dir.path().join("a.rs")).unwrap(), @@ -3808,6 +3828,18 @@ mod tests { }) .await; + let event = events.next().await.unwrap(); + assert_eq!( + event, + Event::DiagnosticsUpdated(Arc::from(Path::new("a.rs"))) + ); + + fake_server.end_progress(&progress_token).await; + fake_server.end_progress(&progress_token).await; + + let event = events.next().await.unwrap(); + assert_eq!(event, Event::DiskBasedDiagnosticsUpdated); + let buffer = tree .update(&mut cx, |tree, cx| tree.open_buffer("a.rs", cx)) .await From 2dbee1d9140396d3fa12b788f91491043f1c6856 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 6 Jan 2022 12:11:06 -0700 Subject: [PATCH 19/43] Send diagnostic summaries to guests when they join the project Co-Authored-By: Max Brunsfeld --- crates/editor/src/multi_buffer.rs | 53 ++++++++++--------- crates/project/src/project.rs | 10 ++++ crates/project/src/worktree.rs | 82 ++++++++++++++++++----------- crates/rpc/proto/zed.proto | 5 ++ crates/server/src/rpc.rs | 86 ++++++++++++++++++++++++++++--- crates/server/src/rpc/store.rs | 37 +++++++++++-- crates/sum_tree/src/tree_map.rs | 10 ++++ 7 files changed, 217 insertions(+), 66 deletions(-) diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 7f56239f83..f399a53412 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -2454,33 +2454,32 @@ mod tests { assert_eq!(snapshot_1.text(), "abcd123"); // Replace the buffer 1 excerpt with new excerpts from buffer 2. - let (excerpt_id_2, excerpt_id_3, excerpt_id_4) = - multibuffer.update(cx, |multibuffer, cx| { - multibuffer.remove_excerpts([&excerpt_id_1], cx); - ( - multibuffer.push_excerpt( - ExcerptProperties { - buffer: &buffer_2, - range: 0..4, - }, - cx, - ), - multibuffer.push_excerpt( - ExcerptProperties { - buffer: &buffer_2, - range: 6..10, - }, - cx, - ), - multibuffer.push_excerpt( - ExcerptProperties { - buffer: &buffer_2, - range: 12..16, - }, - cx, - ), - ) - }); + let (excerpt_id_2, excerpt_id_3, _) = multibuffer.update(cx, |multibuffer, cx| { + multibuffer.remove_excerpts([&excerpt_id_1], cx); + ( + multibuffer.push_excerpt( + ExcerptProperties { + buffer: &buffer_2, + range: 0..4, + }, + cx, + ), + multibuffer.push_excerpt( + ExcerptProperties { + buffer: &buffer_2, + range: 6..10, + }, + cx, + ), + multibuffer.push_excerpt( + ExcerptProperties { + buffer: &buffer_2, + range: 12..16, + }, + cx, + ), + ) + }); let snapshot_2 = multibuffer.read(cx).snapshot(cx); assert_eq!(snapshot_2.text(), "ABCD\nGHIJ\nMNOP"); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a0ed7b811d..325ac682eb 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -101,6 +101,16 @@ impl DiagnosticSummary { this } + + pub fn to_proto(&self, path: Arc) -> proto::DiagnosticSummary { + proto::DiagnosticSummary { + path: path.to_string_lossy().to_string(), + error_count: self.error_count as u32, + warning_count: self.warning_count as u32, + info_count: self.info_count as u32, + hint_count: self.hint_count as u32, + } + } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 3e76151fdf..3533714c04 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -7,8 +7,7 @@ use ::ignore::gitignore::{Gitignore, GitignoreBuilder}; use anyhow::{anyhow, Context, Result}; use client::{proto, Client, PeerId, TypedEnvelope, UserStore}; use clock::ReplicaId; -use collections::{hash_map, HashMap}; -use collections::{BTreeMap, HashSet}; +use collections::{hash_map, HashMap, HashSet}; use futures::{Stream, StreamExt}; use fuzzy::CharBag; use gpui::{ @@ -44,7 +43,7 @@ use std::{ }, time::{Duration, SystemTime}, }; -use sum_tree::Bias; +use sum_tree::{Bias, TreeMap}; use sum_tree::{Edit, SeekTarget, SumTree}; use util::{post_inc, ResultExt, TryFutureExt}; @@ -142,7 +141,7 @@ impl Worktree { .map(|c| c.to_ascii_lowercase()) .collect(); let root_name = worktree.root_name.clone(); - let (entries_by_path, entries_by_id) = cx + let (entries_by_path, entries_by_id, diagnostic_summaries) = cx .background() .spawn(async move { let mut entries_by_path_edits = Vec::new(); @@ -166,7 +165,22 @@ impl Worktree { let mut entries_by_id = SumTree::new(); entries_by_path.edit(entries_by_path_edits, &()); entries_by_id.edit(entries_by_id_edits, &()); - (entries_by_path, entries_by_id) + + let diagnostic_summaries = TreeMap::from_ordered_entries( + worktree.diagnostic_summaries.into_iter().map(|summary| { + ( + PathKey(PathBuf::from(summary.path).into()), + DiagnosticSummary { + error_count: summary.error_count as usize, + warning_count: summary.warning_count as usize, + info_count: summary.info_count as usize, + hint_count: summary.hint_count as usize, + }, + ) + }), + ); + + (entries_by_path, entries_by_id, diagnostic_summaries) }) .await; @@ -183,6 +197,7 @@ impl Worktree { entries_by_id, removed_entry_ids: Default::default(), next_entry_id: Default::default(), + diagnostic_summaries, }; let (updates_tx, mut updates_rx) = postage::mpsc::channel(64); @@ -223,7 +238,6 @@ impl Worktree { client: client.clone(), loading_buffers: Default::default(), open_buffers: Default::default(), - diagnostic_summaries: Default::default(), queued_operations: Default::default(), languages, user_store, @@ -351,7 +365,7 @@ impl Worktree { Worktree::Remote(worktree) => &worktree.diagnostic_summaries, } .iter() - .map(|(path, summary)| (path.clone(), summary.clone())) + .map(|(path, summary)| (path.0.clone(), summary.clone())) } pub fn loading_buffers<'a>(&'a mut self) -> &'a mut LoadingBuffers { @@ -778,8 +792,9 @@ impl Worktree { let this = self.as_local_mut().unwrap(); let summary = DiagnosticSummary::new(&diagnostics); - this.diagnostic_summaries - .insert(worktree_path.clone(), summary.clone()); + this.snapshot + .diagnostic_summaries + .insert(PathKey(worktree_path.clone()), summary.clone()); this.diagnostics.insert(worktree_path.clone(), diagnostics); cx.emit(Event::DiagnosticsUpdated(worktree_path.clone())); @@ -796,11 +811,13 @@ impl Worktree { .send(proto::UpdateDiagnosticSummary { project_id, worktree_id, - path, - error_count: summary.error_count as u32, - warning_count: summary.warning_count as u32, - info_count: summary.info_count as u32, - hint_count: summary.hint_count as u32, + summary: Some(proto::DiagnosticSummary { + path, + error_count: summary.error_count as u32, + warning_count: summary.warning_count as u32, + info_count: summary.info_count as u32, + hint_count: summary.hint_count as u32, + }), }) .await .log_err() @@ -890,6 +907,7 @@ pub struct Snapshot { entries_by_id: SumTree, removed_entry_ids: HashMap, next_entry_id: Arc, + diagnostic_summaries: TreeMap, } pub struct LocalWorktree { @@ -904,7 +922,6 @@ pub struct LocalWorktree { open_buffers: HashMap>, shared_buffers: HashMap>>, diagnostics: HashMap, Vec>>, - diagnostic_summaries: BTreeMap, DiagnosticSummary>, queued_operations: Vec<(u64, Operation)>, language_registry: Arc, client: Arc, @@ -928,7 +945,6 @@ pub struct RemoteWorktree { replica_id: ReplicaId, loading_buffers: LoadingBuffers, open_buffers: HashMap, - diagnostic_summaries: BTreeMap, DiagnosticSummary>, languages: Arc, user_store: ModelHandle, queued_operations: Vec<(u64, Operation)>, @@ -986,6 +1002,7 @@ impl LocalWorktree { entries_by_id: Default::default(), removed_entry_ids: Default::default(), next_entry_id: Arc::new(next_entry_id), + diagnostic_summaries: Default::default(), }; if let Some(metadata) = metadata { snapshot.insert_entry( @@ -1011,7 +1028,6 @@ impl LocalWorktree { open_buffers: Default::default(), shared_buffers: Default::default(), diagnostics: Default::default(), - diagnostic_summaries: Default::default(), queued_operations: Default::default(), language_registry: languages, client, @@ -1626,17 +1642,19 @@ impl RemoteWorktree { envelope: TypedEnvelope, cx: &mut ModelContext, ) { - let path: Arc = Path::new(&envelope.payload.path).into(); - self.diagnostic_summaries.insert( - path.clone(), - DiagnosticSummary { - error_count: envelope.payload.error_count as usize, - warning_count: envelope.payload.warning_count as usize, - info_count: envelope.payload.info_count as usize, - hint_count: envelope.payload.hint_count as usize, - }, - ); - cx.emit(Event::DiagnosticsUpdated(path)); + if let Some(summary) = envelope.payload.summary { + let path: Arc = Path::new(&summary.path).into(); + self.snapshot.diagnostic_summaries.insert( + PathKey(path.clone()), + DiagnosticSummary { + error_count: summary.error_count as usize, + warning_count: summary.warning_count as usize, + info_count: summary.info_count as usize, + hint_count: summary.hint_count as usize, + }, + ); + cx.emit(Event::DiagnosticsUpdated(path)); + } } pub fn disk_based_diagnostics_updated(&self, cx: &mut ModelContext) { @@ -1679,10 +1697,15 @@ impl Snapshot { root_name, entries: self .entries_by_path - .cursor::<()>() + .iter() .filter(|e| !e.is_ignored) .map(Into::into) .collect(), + diagnostic_summaries: self + .diagnostic_summaries + .iter() + .map(|(path, summary)| summary.to_proto(path.0.clone())) + .collect(), } } @@ -4160,6 +4183,7 @@ mod tests { root_name: Default::default(), root_char_bag: Default::default(), next_entry_id: next_entry_id.clone(), + diagnostic_summaries: Default::default(), }; initial_snapshot.insert_entry( Entry::new( diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 71ceb4d9ac..669ffdbd7e 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -170,6 +170,10 @@ message BufferSaved { message UpdateDiagnosticSummary { uint64 project_id = 1; uint64 worktree_id = 2; + DiagnosticSummary summary = 3; +} + +message DiagnosticSummary { string path = 3; uint32 error_count = 4; uint32 warning_count = 5; @@ -256,6 +260,7 @@ message Worktree { uint64 id = 1; string root_name = 2; repeated Entry entries = 3; + repeated DiagnosticSummary diagnostic_summaries = 4; } message Entry { diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 0d84a89e14..19792caace 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -17,7 +17,7 @@ use rpc::{ Connection, ConnectionId, Peer, TypedEnvelope, }; use sha1::{Digest as _, Sha1}; -use std::{any::TypeId, future::Future, mem, sync::Arc, time::Instant}; +use std::{any::TypeId, future::Future, mem, path::PathBuf, sync::Arc, time::Instant}; use store::{Store, Worktree}; use surf::StatusCode; use tide::log; @@ -302,6 +302,11 @@ impl Server { id: *id, root_name: worktree.root_name.clone(), entries: share.entries.values().cloned().collect(), + diagnostic_summaries: share + .diagnostic_summaries + .values() + .cloned() + .collect(), }) }) .collect(); @@ -473,11 +478,17 @@ impl Server { .map(|entry| (entry.id, entry)) .collect(); + let diagnostic_summaries = mem::take(&mut worktree.diagnostic_summaries) + .into_iter() + .map(|summary| (PathBuf::from(summary.path.clone()), summary)) + .collect(); + let contact_user_ids = self.state_mut().share_worktree( request.payload.project_id, worktree.id, request.sender_id, entries, + diagnostic_summaries, ); if let Some(contact_user_ids) = contact_user_ids { self.peer.respond(request.receipt(), proto::Ack {}).await?; @@ -520,13 +531,23 @@ impl Server { } async fn update_diagnostic_summary( - self: Arc, + mut self: Arc, request: TypedEnvelope, ) -> tide::Result<()> { - let receiver_ids = self - .state() - .project_connection_ids(request.payload.project_id, request.sender_id) + let receiver_ids = request + .payload + .summary + .clone() + .and_then(|summary| { + self.state_mut().update_diagnostic_summary( + request.payload.project_id, + request.payload.worktree_id, + request.sender_id, + summary, + ) + }) .ok_or_else(|| anyhow!(NO_SUCH_PROJECT))?; + broadcast(request.sender_id, receiver_ids, |connection_id| { self.peer .forward_send(request.sender_id, connection_id, request.payload.clone()) @@ -1816,6 +1837,39 @@ mod tests { .await .unwrap(); + // Simulate a language server reporting errors for a file. + fake_language_server + .notify::(lsp::PublishDiagnosticsParams { + uri: lsp::Url::from_file_path("/a/a.rs").unwrap(), + version: None, + diagnostics: vec![lsp::Diagnostic { + severity: Some(lsp::DiagnosticSeverity::ERROR), + range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)), + message: "message 1".to_string(), + ..Default::default() + }], + }) + .await; + + // Wait for server to see the diagnostics update. + server + .condition(|store| { + let worktree = store + .project(project_id) + .unwrap() + .worktrees + .get(&worktree_id.to_proto()) + .unwrap(); + + !worktree + .share + .as_ref() + .unwrap() + .diagnostic_summaries + .is_empty() + }) + .await; + // Join the worktree as client B. let project_b = Project::remote( project_id, @@ -1828,7 +1882,24 @@ mod tests { .await .unwrap(); - // Simulate a language server reporting errors for a file. + project_b.read_with(&cx_b, |project, cx| { + assert_eq!( + project.diagnostic_summaries(cx).collect::>(), + &[( + ProjectPath { + worktree_id, + path: Arc::from(Path::new("a.rs")), + }, + DiagnosticSummary { + error_count: 1, + warning_count: 0, + ..Default::default() + }, + )] + ) + }); + + // Simulate a language server reporting more errors for a file. fake_language_server .notify::(lsp::PublishDiagnosticsParams { uri: lsp::Url::from_file_path("/a/a.rs").unwrap(), @@ -1853,6 +1924,7 @@ mod tests { }) .await; + // Client b gets the updated summaries project_b .condition(&cx_b, |project, cx| { project.diagnostic_summaries(cx).collect::>() @@ -1870,7 +1942,7 @@ mod tests { }) .await; - // Open the file with the errors. + // Open the file with the errors on client B. They should be present. let worktree_b = project_b.update(&mut cx_b, |p, _| p.worktrees()[0].clone()); let buffer_b = cx_b .background() diff --git a/crates/server/src/rpc/store.rs b/crates/server/src/rpc/store.rs index e4d740629f..7e8523b06c 100644 --- a/crates/server/src/rpc/store.rs +++ b/crates/server/src/rpc/store.rs @@ -1,8 +1,8 @@ use crate::db::{ChannelId, UserId}; use anyhow::anyhow; -use collections::{HashMap, HashSet}; +use collections::{BTreeMap, HashMap, HashSet}; use rpc::{proto, ConnectionId}; -use std::collections::hash_map; +use std::{collections::hash_map, path::PathBuf}; #[derive(Default)] pub struct Store { @@ -41,6 +41,7 @@ pub struct ProjectShare { pub struct WorktreeShare { pub entries: HashMap, + pub diagnostic_summaries: BTreeMap, } #[derive(Default)] @@ -385,17 +386,42 @@ impl Store { worktree_id: u64, connection_id: ConnectionId, entries: HashMap, + diagnostic_summaries: BTreeMap, ) -> Option> { let project = self.projects.get_mut(&project_id)?; let worktree = project.worktrees.get_mut(&worktree_id)?; if project.host_connection_id == connection_id && project.share.is_some() { - worktree.share = Some(WorktreeShare { entries }); + worktree.share = Some(WorktreeShare { + entries, + diagnostic_summaries, + }); Some(project.authorized_user_ids()) } else { None } } + pub fn update_diagnostic_summary( + &mut self, + project_id: u64, + worktree_id: u64, + connection_id: ConnectionId, + summary: proto::DiagnosticSummary, + ) -> Option> { + let project = self.projects.get_mut(&project_id)?; + let worktree = project.worktrees.get_mut(&worktree_id)?; + if project.host_connection_id == connection_id { + if let Some(share) = worktree.share.as_mut() { + share + .diagnostic_summaries + .insert(summary.path.clone().into(), summary); + return Some(project.connection_ids()); + } + } + + None + } + pub fn join_project( &mut self, connection_id: ConnectionId, @@ -497,6 +523,11 @@ impl Store { Some(self.channels.get(&channel_id)?.connection_ids()) } + #[cfg(test)] + pub fn project(&self, project_id: u64) -> Option<&Project> { + self.projects.get(&project_id) + } + pub fn read_project(&self, project_id: u64, connection_id: ConnectionId) -> Option<&Project> { let project = self.projects.get(&project_id)?; if project.host_connection_id == connection_id diff --git a/crates/sum_tree/src/tree_map.rs b/crates/sum_tree/src/tree_map.rs index f50c233d05..1de6b2f589 100644 --- a/crates/sum_tree/src/tree_map.rs +++ b/crates/sum_tree/src/tree_map.rs @@ -21,6 +21,16 @@ pub struct MapKey(K); pub struct MapKeyRef<'a, K>(Option<&'a K>); impl TreeMap { + pub fn from_ordered_entries(entries: impl IntoIterator) -> Self { + let tree = SumTree::from_iter( + entries + .into_iter() + .map(|(key, value)| MapEntry { key, value }), + &(), + ); + Self(tree) + } + pub fn get<'a>(&self, key: &'a K) -> Option<&V> { let mut cursor = self.0.cursor::>(); cursor.seek(&MapKeyRef(Some(key)), Bias::Left, &()); From 943571af2ab6bb80837948227eea819455fb2dc9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 6 Jan 2022 13:33:55 -0700 Subject: [PATCH 20/43] Report backtraces of pending conditions when deterministic executor illegally parks Co-Authored-By: Max Brunsfeld --- crates/gpui/src/app.rs | 10 ++- crates/gpui/src/executor.rs | 123 +++++++++++++++++++++++++----------- crates/server/src/rpc.rs | 33 ++++++---- 3 files changed, 108 insertions(+), 58 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index e42e889496..e57bcbc764 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -2660,8 +2660,6 @@ impl ModelHandle { loop { { let cx = cx.borrow(); - let executor = cx.foreground(); - let cx = cx.as_ref(); if predicate( handle @@ -2672,15 +2670,13 @@ impl ModelHandle { ) { break; } - - if executor.parking_forbidden() && executor.would_park() { - panic!("parked while waiting on condition"); - } } + cx.borrow().foreground().start_waiting(); rx.recv() .await .expect("model dropped with pending condition"); + cx.borrow().foreground().finish_waiting(); } }) .await @@ -2920,9 +2916,11 @@ impl ViewHandle { } } + cx.borrow().foreground().start_waiting(); rx.recv() .await .expect("view dropped with pending condition"); + cx.borrow().foreground().finish_waiting(); } }) .await diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index 6b20f56f9e..596b1be184 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -77,6 +77,7 @@ struct DeterministicState { block_on_ticks: RangeInclusive, now: Instant, pending_timers: Vec<(Instant, barrier::Sender)>, + waiting_backtrace: Option, } pub struct Deterministic { @@ -97,6 +98,7 @@ impl Deterministic { block_on_ticks: 0..=1000, now: Instant::now(), pending_timers: Default::default(), + waiting_backtrace: None, })), parker: Default::default(), } @@ -143,8 +145,8 @@ impl Deterministic { return result; } - if !woken.load(SeqCst) && self.state.lock().forbid_parking { - panic!("deterministic executor parked after a call to forbid_parking"); + if !woken.load(SeqCst) { + self.state.lock().will_park(); } woken.store(false, SeqCst); @@ -206,7 +208,11 @@ impl Deterministic { } let state = self.state.lock(); - if state.would_park() { + + if state.scheduled_from_foreground.is_empty() + && state.scheduled_from_background.is_empty() + && state.spawned_from_foreground.is_empty() + { return None; } } @@ -241,11 +247,9 @@ impl Deterministic { if let Poll::Ready(result) = future.as_mut().poll(&mut cx) { return Some(result); } - let state = self.state.lock(); + let mut state = self.state.lock(); if state.scheduled_from_background.is_empty() { - if state.forbid_parking { - panic!("deterministic executor parked after a call to forbid_parking"); - } + state.will_park(); drop(state); self.parker.lock().park(); } @@ -259,11 +263,22 @@ impl Deterministic { } impl DeterministicState { - fn would_park(&self) -> bool { - self.forbid_parking - && self.scheduled_from_foreground.is_empty() - && self.scheduled_from_background.is_empty() - && self.spawned_from_foreground.is_empty() + fn will_park(&mut self) { + if self.forbid_parking { + let mut backtrace_message = String::new(); + if let Some(backtrace) = self.waiting_backtrace.as_mut() { + backtrace.resolve(); + backtrace_message = format!( + "\nbacktrace of waiting future:\n{:?}", + CwdBacktrace::new(backtrace) + ); + } + + panic!( + "deterministic executor parked after a call to forbid_parking{}", + backtrace_message + ); + } } } @@ -312,32 +327,53 @@ impl Trace { } } -impl Debug for Trace { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - struct FirstCwdFrameInBacktrace<'a>(&'a Backtrace); +struct CwdBacktrace<'a> { + backtrace: &'a Backtrace, + first_frame_only: bool, +} - impl<'a> Debug for FirstCwdFrameInBacktrace<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { - let cwd = std::env::current_dir().unwrap(); - let mut print_path = |fmt: &mut fmt::Formatter<'_>, path: BytesOrWideString<'_>| { - fmt::Display::fmt(&path, fmt) - }; - let mut fmt = BacktraceFmt::new(f, backtrace::PrintFmt::Full, &mut print_path); - for frame in self.0.frames() { - let mut formatted_frame = fmt.frame(); - if frame - .symbols() - .iter() - .any(|s| s.filename().map_or(false, |f| f.starts_with(&cwd))) - { - formatted_frame.backtrace_frame(frame)?; - break; - } +impl<'a> CwdBacktrace<'a> { + fn new(backtrace: &'a Backtrace) -> Self { + Self { + backtrace, + first_frame_only: false, + } + } + + fn first_frame(backtrace: &'a Backtrace) -> Self { + Self { + backtrace, + first_frame_only: true, + } + } +} + +impl<'a> Debug for CwdBacktrace<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { + let cwd = std::env::current_dir().unwrap(); + let mut print_path = |fmt: &mut fmt::Formatter<'_>, path: BytesOrWideString<'_>| { + fmt::Display::fmt(&path, fmt) + }; + let mut fmt = BacktraceFmt::new(f, backtrace::PrintFmt::Full, &mut print_path); + for frame in self.backtrace.frames() { + let mut formatted_frame = fmt.frame(); + if frame + .symbols() + .iter() + .any(|s| s.filename().map_or(false, |f| f.starts_with(&cwd))) + { + formatted_frame.backtrace_frame(frame)?; + if self.first_frame_only { + break; } - fmt.finish() } } + fmt.finish() + } +} +impl Debug for Trace { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for ((backtrace, scheduled), spawned_from_foreground) in self .executed .iter() @@ -346,7 +382,7 @@ impl Debug for Trace { { writeln!(f, "Scheduled")?; for backtrace in scheduled { - writeln!(f, "- {:?}", FirstCwdFrameInBacktrace(backtrace))?; + writeln!(f, "- {:?}", CwdBacktrace::first_frame(backtrace))?; } if scheduled.is_empty() { writeln!(f, "None")?; @@ -355,14 +391,14 @@ impl Debug for Trace { writeln!(f, "Spawned from foreground")?; for backtrace in spawned_from_foreground { - writeln!(f, "- {:?}", FirstCwdFrameInBacktrace(backtrace))?; + writeln!(f, "- {:?}", CwdBacktrace::first_frame(backtrace))?; } if spawned_from_foreground.is_empty() { writeln!(f, "None")?; } writeln!(f, "==========")?; - writeln!(f, "Run: {:?}", FirstCwdFrameInBacktrace(backtrace))?; + writeln!(f, "Run: {:?}", CwdBacktrace::first_frame(backtrace))?; writeln!(f, "+++++++++++++++++++")?; } @@ -446,9 +482,20 @@ impl Foreground { } } - pub fn would_park(&self) -> bool { + pub fn start_waiting(&self) { match self { - Self::Deterministic(executor) => executor.state.lock().would_park(), + Self::Deterministic(executor) => { + executor.state.lock().waiting_backtrace = Some(Backtrace::new_unresolved()); + } + _ => panic!("this method can only be called on a deterministic executor"), + } + } + + pub fn finish_waiting(&self) { + match self { + Self::Deterministic(executor) => { + executor.state.lock().waiting_backtrace.take(); + } _ => panic!("this method can only be called on a deterministic executor"), } } diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 19792caace..510b701ddd 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -1054,7 +1054,7 @@ mod tests { }; use ::rpc::Peer; use async_std::task; - use gpui::{ModelHandle, TestAppContext}; + use gpui::{executor, ModelHandle, TestAppContext}; use parking_lot::Mutex; use postage::{mpsc, watch}; use rpc::PeerId; @@ -1063,6 +1063,7 @@ mod tests { use std::{ ops::Deref, path::Path, + rc::Rc, sync::{ atomic::{AtomicBool, Ordering::SeqCst}, Arc, @@ -1092,7 +1093,7 @@ mod tests { cx_a.foreground().forbid_parking(); // Connect to a server as 2 clients. - let mut server = TestServer::start().await; + let mut server = TestServer::start(cx_a.foreground()).await; let client_a = server.create_client(&mut cx_a, "user_a").await; let client_b = server.create_client(&mut cx_b, "user_b").await; @@ -1225,7 +1226,7 @@ mod tests { cx_a.foreground().forbid_parking(); // Connect to a server as 2 clients. - let mut server = TestServer::start().await; + let mut server = TestServer::start(cx_a.foreground()).await; let client_a = server.create_client(&mut cx_a, "user_a").await; let client_b = server.create_client(&mut cx_b, "user_b").await; @@ -1301,7 +1302,7 @@ mod tests { cx_a.foreground().forbid_parking(); // Connect to a server as 3 clients. - let mut server = TestServer::start().await; + let mut server = TestServer::start(cx_a.foreground()).await; let client_a = server.create_client(&mut cx_a, "user_a").await; let client_b = server.create_client(&mut cx_b, "user_b").await; let client_c = server.create_client(&mut cx_c, "user_c").await; @@ -1451,7 +1452,7 @@ mod tests { let fs = Arc::new(FakeFs::new()); // Connect to a server as 2 clients. - let mut server = TestServer::start().await; + let mut server = TestServer::start(cx_a.foreground()).await; let client_a = server.create_client(&mut cx_a, "user_a").await; let client_b = server.create_client(&mut cx_b, "user_b").await; @@ -1547,7 +1548,7 @@ mod tests { let fs = Arc::new(FakeFs::new()); // Connect to a server as 2 clients. - let mut server = TestServer::start().await; + let mut server = TestServer::start(cx_a.foreground()).await; let client_a = server.create_client(&mut cx_a, "user_a").await; let client_b = server.create_client(&mut cx_b, "user_b").await; @@ -1627,7 +1628,7 @@ mod tests { let fs = Arc::new(FakeFs::new()); // Connect to a server as 2 clients. - let mut server = TestServer::start().await; + let mut server = TestServer::start(cx_a.foreground()).await; let client_a = server.create_client(&mut cx_a, "user_a").await; let client_b = server.create_client(&mut cx_b, "user_b").await; @@ -1702,7 +1703,7 @@ mod tests { let fs = Arc::new(FakeFs::new()); // Connect to a server as 2 clients. - let mut server = TestServer::start().await; + let mut server = TestServer::start(cx_a.foreground()).await; let client_a = server.create_client(&mut cx_a, "user_a").await; let client_b = server.create_client(&mut cx_b, "user_b").await; @@ -1789,7 +1790,7 @@ mod tests { ))); // Connect to a server as 2 clients. - let mut server = TestServer::start().await; + let mut server = TestServer::start(cx_a.foreground()).await; let client_a = server.create_client(&mut cx_a, "user_a").await; let client_b = server.create_client(&mut cx_b, "user_b").await; @@ -1988,7 +1989,7 @@ mod tests { cx_a.foreground().forbid_parking(); // Connect to a server as 2 clients. - let mut server = TestServer::start().await; + let mut server = TestServer::start(cx_a.foreground()).await; let client_a = server.create_client(&mut cx_a, "user_a").await; let client_b = server.create_client(&mut cx_b, "user_b").await; @@ -2127,7 +2128,7 @@ mod tests { async fn test_chat_message_validation(mut cx_a: TestAppContext) { cx_a.foreground().forbid_parking(); - let mut server = TestServer::start().await; + let mut server = TestServer::start(cx_a.foreground()).await; let client_a = server.create_client(&mut cx_a, "user_a").await; let db = &server.app_state.db; @@ -2188,7 +2189,7 @@ mod tests { cx_a.foreground().forbid_parking(); // Connect to a server as 2 clients. - let mut server = TestServer::start().await; + let mut server = TestServer::start(cx_a.foreground()).await; let client_a = server.create_client(&mut cx_a, "user_a").await; let client_b = server.create_client(&mut cx_b, "user_b").await; let mut status_b = client_b.status(); @@ -2406,7 +2407,7 @@ mod tests { let fs = Arc::new(FakeFs::new()); // Connect to a server as 3 clients. - let mut server = TestServer::start().await; + let mut server = TestServer::start(cx_a.foreground()).await; let client_a = server.create_client(&mut cx_a, "user_a").await; let client_b = server.create_client(&mut cx_b, "user_b").await; let client_c = server.create_client(&mut cx_c, "user_c").await; @@ -2539,6 +2540,7 @@ mod tests { peer: Arc, app_state: Arc, server: Arc, + foreground: Rc, notifications: mpsc::Receiver<()>, connection_killers: Arc>>>>, forbid_connections: Arc, @@ -2546,7 +2548,7 @@ mod tests { } impl TestServer { - async fn start() -> Self { + async fn start(foreground: Rc) -> Self { let test_db = TestDb::new(); let app_state = Self::build_app_state(&test_db).await; let peer = Peer::new(); @@ -2556,6 +2558,7 @@ mod tests { peer, app_state, server, + foreground, notifications: notifications.1, connection_killers: Default::default(), forbid_connections: Default::default(), @@ -2671,7 +2674,9 @@ mod tests { { async_std::future::timeout(Duration::from_millis(500), async { while !(predicate)(&*self.server.store.read()) { + self.foreground.start_waiting(); self.notifications.recv().await; + self.foreground.finish_waiting(); } }) .await From 10548c2038427a293005ebe6875dd2a9e9299bd3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 6 Jan 2022 14:22:28 -0800 Subject: [PATCH 21/43] Always group diagnostics the way they're grouped in the LSP message Co-Authored-By: Nathan Sobo --- crates/project/src/worktree.rs | 147 ++++++++++++++++++--------------- 1 file changed, 79 insertions(+), 68 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 3533714c04..c3bd588684 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -34,7 +34,6 @@ use std::{ ffi::{OsStr, OsString}, fmt, future::Future, - mem, ops::{Deref, Range}, path::{Path, PathBuf}, sync::{ @@ -691,7 +690,7 @@ impl Worktree { pub fn update_diagnostics( &mut self, - mut params: lsp::PublishDiagnosticsParams, + params: lsp::PublishDiagnosticsParams, disk_based_sources: &HashSet, cx: &mut ModelContext, ) -> Result<()> { @@ -706,58 +705,92 @@ impl Worktree { .context("path is not within worktree")?, ); - let mut group_ids_by_diagnostic_range = HashMap::default(); - let mut diagnostics_by_group_id = HashMap::default(); let mut next_group_id = 0; - for diagnostic in &mut params.diagnostics { + let mut diagnostics = Vec::default(); + let mut primary_diagnostic_group_ids = HashMap::default(); + let mut sources_by_group_id = HashMap::default(); + let mut supporting_diagnostic_severities = HashMap::default(); + for diagnostic in ¶ms.diagnostics { let source = diagnostic.source.as_ref(); - let code = diagnostic.code.as_ref(); - let group_id = diagnostic_ranges(&diagnostic, &abs_path) - .find_map(|range| group_ids_by_diagnostic_range.get(&(source, code, range))) - .copied() - .unwrap_or_else(|| { - let group_id = post_inc(&mut next_group_id); - for range in diagnostic_ranges(&diagnostic, &abs_path) { - group_ids_by_diagnostic_range.insert((source, code, range), group_id); - } - group_id + let code = diagnostic.code.as_ref().map(|code| match code { + lsp::NumberOrString::Number(code) => code.to_string(), + lsp::NumberOrString::String(code) => code.clone(), + }); + let range = range_from_lsp(diagnostic.range); + let is_supporting = diagnostic + .related_information + .as_ref() + .map_or(false, |infos| { + infos.iter().any(|info| { + primary_diagnostic_group_ids.contains_key(&( + source, + code.clone(), + range_from_lsp(info.location.range), + )) + }) }); - diagnostics_by_group_id - .entry(group_id) - .or_insert(Vec::new()) - .push(DiagnosticEntry { - range: diagnostic.range.start.to_point_utf16() - ..diagnostic.range.end.to_point_utf16(), + if is_supporting { + if let Some(severity) = diagnostic.severity { + supporting_diagnostic_severities + .insert((source, code.clone(), range), severity); + } + } else { + let group_id = post_inc(&mut next_group_id); + let is_disk_based = + source.map_or(false, |source| disk_based_sources.contains(source)); + + sources_by_group_id.insert(group_id, source); + primary_diagnostic_group_ids + .insert((source, code.clone(), range.clone()), group_id); + + diagnostics.push(DiagnosticEntry { + range, diagnostic: Diagnostic { - code: diagnostic.code.clone().map(|code| match code { - lsp::NumberOrString::Number(code) => code.to_string(), - lsp::NumberOrString::String(code) => code, - }), + code: code.clone(), severity: diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR), - message: mem::take(&mut diagnostic.message), + message: diagnostic.message.clone(), group_id, - is_primary: false, + is_primary: true, is_valid: true, - is_disk_based: diagnostic - .source - .as_ref() - .map_or(false, |source| disk_based_sources.contains(source)), + is_disk_based, }, }); + if let Some(infos) = &diagnostic.related_information { + for info in infos { + if info.location.uri == params.uri { + let range = range_from_lsp(info.location.range); + diagnostics.push(DiagnosticEntry { + range, + diagnostic: Diagnostic { + code: code.clone(), + severity: DiagnosticSeverity::INFORMATION, + message: info.message.clone(), + group_id, + is_primary: false, + is_valid: true, + is_disk_based, + }, + }); + } + } + } + } } - let diagnostics = diagnostics_by_group_id - .into_values() - .flat_map(|mut diagnostics| { - let primary = diagnostics - .iter_mut() - .min_by_key(|entry| entry.diagnostic.severity) - .unwrap(); - primary.diagnostic.is_primary = true; - diagnostics - }) - .collect::>(); + for entry in &mut diagnostics { + let diagnostic = &mut entry.diagnostic; + if !diagnostic.is_primary { + let source = *sources_by_group_id.get(&diagnostic.group_id).unwrap(); + if let Some(&severity) = supporting_diagnostic_severities.get(&( + source, + diagnostic.code.clone(), + entry.range.clone(), + )) { + diagnostic.severity = severity; + } + } + } self.update_diagnostic_entries(worktree_path, params.version, diagnostics, cx)?; Ok(()) @@ -3103,32 +3136,10 @@ impl ToPointUtf16 for lsp::Position { } } -fn diagnostic_ranges<'a>( - diagnostic: &'a lsp::Diagnostic, - abs_path: &'a Path, -) -> impl 'a + Iterator> { - diagnostic - .related_information - .iter() - .flatten() - .filter_map(move |info| { - if info.location.uri.to_file_path().ok()? == abs_path { - let info_start = PointUtf16::new( - info.location.range.start.line, - info.location.range.start.character, - ); - let info_end = PointUtf16::new( - info.location.range.end.line, - info.location.range.end.character, - ); - Some(info_start..info_end) - } else { - None - } - }) - .chain(Some( - diagnostic.range.start.to_point_utf16()..diagnostic.range.end.to_point_utf16(), - )) +fn range_from_lsp(range: lsp::Range) -> Range { + let start = PointUtf16::new(range.start.line, range.start.character); + let end = PointUtf16::new(range.end.line, range.end.character); + start..end } #[cfg(test)] From 7357b3ff2a2468c4b8f414855b34253c9957f3dc Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 6 Jan 2022 14:38:13 -0800 Subject: [PATCH 22/43] Revert "Remove special handling of multi-line primary diagnostic messages and fix tests" This reverts commit ce4142eab39118a2ecd43a0473a3e7035087191e. --- crates/diagnostics/src/diagnostics.rs | 84 +++++++++------------------ 1 file changed, 28 insertions(+), 56 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 2aa062af91..8b54fa7813 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -281,14 +281,17 @@ impl ProjectDiagnosticsEditor { if is_first_excerpt_for_group { is_first_excerpt_for_group = false; let primary = &group.entries[group.primary_ix].diagnostic; + let mut header = primary.clone(); + header.message = + primary.message.split('\n').next().unwrap().to_string(); group_state.block_count += 1; - diagnostic_blocks.push(DiagnosticBlock::Header(primary.clone())); + diagnostic_blocks.push(DiagnosticBlock::Header(header.clone())); blocks_to_add.push(BlockProperties { position: header_position, - height: primary.message.matches('\n').count() as u8 + 2, + height: 2, render: diagnostic_header_renderer( buffer.clone(), - primary.clone(), + header, true, self.build_settings.clone(), ), @@ -306,17 +309,21 @@ impl ProjectDiagnosticsEditor { } for entry in &group.entries[*start_ix..ix] { - if !entry.diagnostic.is_primary { + let mut diagnostic = entry.diagnostic.clone(); + if diagnostic.is_primary { + diagnostic.message = + entry.diagnostic.message.split('\n').skip(1).collect(); + } + + if !diagnostic.message.is_empty() { group_state.block_count += 1; diagnostic_blocks - .push(DiagnosticBlock::Inline(entry.diagnostic.clone())); + .push(DiagnosticBlock::Inline(diagnostic.clone())); blocks_to_add.push(BlockProperties { position: (excerpt_id.clone(), entry.range.start.clone()), - height: entry.diagnostic.message.matches('\n').count() - as u8 - + 1, + height: diagnostic.message.matches('\n').count() as u8 + 1, render: diagnostic_block_renderer( - entry.diagnostic.clone(), + diagnostic, true, self.build_settings.clone(), ), @@ -601,7 +608,7 @@ mod tests { DiagnosticEntry { range: PointUtf16::new(7, 6)..PointUtf16::new(7, 7), diagnostic: Diagnostic { - message: "use of moved value".to_string(), + message: "use of moved value\nvalue used here after move".to_string(), severity: DiagnosticSeverity::ERROR, is_primary: true, is_disk_based: true, @@ -609,21 +616,10 @@ mod tests { ..Default::default() }, }, - DiagnosticEntry { - range: PointUtf16::new(7, 6)..PointUtf16::new(7, 7), - diagnostic: Diagnostic { - message: "value used here after move".to_string(), - severity: DiagnosticSeverity::INFORMATION, - is_primary: false, - is_disk_based: true, - group_id: 0, - ..Default::default() - }, - }, DiagnosticEntry { range: PointUtf16::new(8, 6)..PointUtf16::new(8, 7), diagnostic: Diagnostic { - message: "use of moved value".to_string(), + message: "use of moved value\nvalue used here after move".to_string(), severity: DiagnosticSeverity::ERROR, is_primary: true, is_disk_based: true, @@ -631,17 +627,6 @@ mod tests { ..Default::default() }, }, - DiagnosticEntry { - range: PointUtf16::new(8, 6)..PointUtf16::new(8, 7), - diagnostic: Diagnostic { - message: "value used here after move".to_string(), - severity: DiagnosticSeverity::INFORMATION, - is_primary: false, - is_disk_based: true, - group_id: 1, - ..Default::default() - }, - }, ], cx, ) @@ -703,30 +688,17 @@ mod tests { .update_diagnostic_entries( Arc::from("/test/a.rs".as_ref()), None, - vec![ - DiagnosticEntry { - range: PointUtf16::new(0, 15)..PointUtf16::new(0, 15), - diagnostic: Diagnostic { - message: "mismatched types".to_string(), - severity: DiagnosticSeverity::ERROR, - is_primary: true, - is_disk_based: true, - group_id: 0, - ..Default::default() - }, + vec![DiagnosticEntry { + range: PointUtf16::new(0, 15)..PointUtf16::new(0, 15), + diagnostic: Diagnostic { + message: "mismatched types\nexpected `usize`, found `char`".to_string(), + severity: DiagnosticSeverity::ERROR, + is_primary: true, + is_disk_based: true, + group_id: 0, + ..Default::default() }, - DiagnosticEntry { - range: PointUtf16::new(0, 15)..PointUtf16::new(0, 15), - diagnostic: Diagnostic { - message: "expected `usize`, found `char`".to_string(), - severity: DiagnosticSeverity::INFORMATION, - is_primary: false, - is_disk_based: true, - group_id: 0, - ..Default::default() - }, - }, - ], + }], cx, ) .unwrap(); From 9bbe67f0ea643a3ef019f8eb86791faf6b287cd7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 6 Jan 2022 15:04:06 -0800 Subject: [PATCH 23/43] Don't clobber diagnostics when getting new snapshot from background scanner Co-Authored-By: Nathan Sobo --- crates/project/src/worktree.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index c3bd588684..6108e9eb10 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -34,6 +34,7 @@ use std::{ ffi::{OsStr, OsString}, fmt, future::Future, + mem, ops::{Deref, Range}, path::{Path, PathBuf}, sync::{ @@ -583,7 +584,9 @@ impl Worktree { match self { Self::Local(worktree) => { let is_fake_fs = worktree.fs.is_fake(); - worktree.snapshot = worktree.background_snapshot.lock().clone(); + worktree + .snapshot + .assign(worktree.background_snapshot.lock().clone()); if worktree.is_scanning() { if worktree.poll_task.is_none() { worktree.poll_task = Some(cx.spawn(|this, mut cx| async move { @@ -1842,6 +1845,14 @@ impl Snapshot { Ok(()) } + fn assign(&mut self, mut other: Self) { + mem::swap( + &mut self.diagnostic_summaries, + &mut other.diagnostic_summaries, + ); + *self = other; + } + pub fn file_count(&self) -> usize { self.entries_by_path.summary().file_count } From b19d92e918b8d799712e5e225f9aac54d02e1705 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 6 Jan 2022 17:01:13 -0800 Subject: [PATCH 24/43] Keep selections at the top of the project diagnostics view when it is first populated Co-Authored-By: Nathan Sobo --- crates/diagnostics/src/diagnostics.rs | 23 +++++++- crates/editor/src/editor.rs | 78 +++++++++++++-------------- 2 files changed, 60 insertions(+), 41 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 8b54fa7813..dfaac9f150 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -9,7 +9,7 @@ use gpui::{ action, elements::*, keymap::Binding, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, }; -use language::{Bias, Buffer, Diagnostic, DiagnosticEntry, Point}; +use language::{Bias, Buffer, Diagnostic, DiagnosticEntry, Point, Selection, SelectionGoal}; use postage::watch; use project::{Project, ProjectPath, WorktreeId}; use std::{cmp::Ordering, ops::Range, path::Path, sync::Arc}; @@ -183,6 +183,7 @@ impl ProjectDiagnosticsEditor { } } + let was_empty = self.path_states.is_empty(); let path_ix = match self .path_states .binary_search_by_key(&path.as_ref(), |e| e.0.as_ref()) @@ -375,7 +376,21 @@ impl ProjectDiagnosticsEditor { group_state.blocks = block_ids.by_ref().take(group_state.block_count).collect(); } - editor.refresh_selections(cx); + if was_empty { + editor.update_selections( + vec![Selection { + id: 0, + start: 0, + end: 0, + reversed: false, + goal: SelectionGoal::None, + }], + None, + cx, + ); + } else { + editor.refresh_selections(cx); + } }); for ix in group_ixs_to_remove.into_iter().rev() { @@ -681,6 +696,10 @@ mod tests { "}" ) ); + + view.editor.update(cx, |editor, cx| { + assert_eq!(editor.selected_ranges::(cx), [0..0]); + }); }); worktree.update(&mut cx, |worktree, cx| { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index c8d95da3eb..4d3aa058be 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1056,6 +1056,45 @@ impl Editor { } } + #[cfg(any(test, feature = "test-support"))] + pub fn selected_ranges>( + &self, + cx: &mut MutableAppContext, + ) -> Vec> { + self.local_selections::(cx) + .iter() + .map(|s| { + if s.reversed { + s.end.clone()..s.start.clone() + } else { + s.start.clone()..s.end.clone() + } + }) + .collect() + } + + #[cfg(any(test, feature = "test-support"))] + pub fn selected_display_ranges(&self, cx: &mut MutableAppContext) -> Vec> { + let display_map = self + .display_map + .update(cx, |display_map, cx| display_map.snapshot(cx)); + self.selections + .iter() + .chain( + self.pending_selection + .as_ref() + .map(|pending| &pending.selection), + ) + .map(|s| { + if s.reversed { + s.end.to_display_point(&display_map)..s.start.to_display_point(&display_map) + } else { + s.start.to_display_point(&display_map)..s.end.to_display_point(&display_map) + } + }) + .collect() + } + pub fn select_ranges( &mut self, ranges: I, @@ -6179,45 +6218,6 @@ mod tests { }); } - impl Editor { - fn selected_ranges>( - &self, - cx: &mut MutableAppContext, - ) -> Vec> { - self.local_selections::(cx) - .iter() - .map(|s| { - if s.reversed { - s.end.clone()..s.start.clone() - } else { - s.start.clone()..s.end.clone() - } - }) - .collect() - } - - fn selected_display_ranges(&self, cx: &mut MutableAppContext) -> Vec> { - let display_map = self - .display_map - .update(cx, |display_map, cx| display_map.snapshot(cx)); - self.selections - .iter() - .chain( - self.pending_selection - .as_ref() - .map(|pending| &pending.selection), - ) - .map(|s| { - if s.reversed { - s.end.to_display_point(&display_map)..s.start.to_display_point(&display_map) - } else { - s.start.to_display_point(&display_map)..s.end.to_display_point(&display_map) - } - }) - .collect() - } - } - fn empty_range(row: usize, column: usize) -> Range { let point = DisplayPoint::new(row as u32, column as u32); point..point From 1f762e482d5e35c362643d4171fdb546133100ca Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 6 Jan 2022 17:29:34 -0800 Subject: [PATCH 25/43] Unify Flexible and Expanded elements We'll use the name Expanded for something else now. Co-Authored-By: Nathan Sobo --- crates/chat_panel/src/chat_panel.rs | 2 +- crates/contacts_panel/src/contacts_panel.rs | 2 +- crates/file_finder/src/file_finder.rs | 3 +- crates/gpui/src/elements.rs | 4 +- crates/gpui/src/elements/flex.rs | 77 +-------------------- crates/theme_selector/src/theme_selector.rs | 2 +- crates/workspace/src/pane.rs | 14 ++-- crates/workspace/src/pane_group.rs | 2 +- crates/workspace/src/sidebar.rs | 21 +++--- crates/workspace/src/status_bar.rs | 2 +- crates/workspace/src/workspace.rs | 74 +++++++++----------- 11 files changed, 58 insertions(+), 145 deletions(-) diff --git a/crates/chat_panel/src/chat_panel.rs b/crates/chat_panel/src/chat_panel.rs index 0ec3884a85..993a0cec0e 100644 --- a/crates/chat_panel/src/chat_panel.rs +++ b/crates/chat_panel/src/chat_panel.rs @@ -233,7 +233,7 @@ impl ChatPanel { Empty::new().boxed() }; - Expanded::new(1., messages).boxed() + Flexible::new(1., true, messages).boxed() } fn render_message(&self, message: &ChannelMessage) -> ElementBox { diff --git a/crates/contacts_panel/src/contacts_panel.rs b/crates/contacts_panel/src/contacts_panel.rs index 2fcdf79bce..fc875889aa 100644 --- a/crates/contacts_panel/src/contacts_panel.rs +++ b/crates/contacts_panel/src/contacts_panel.rs @@ -214,7 +214,7 @@ impl ContactsPanel { })); } }) - .expanded(1.0) + .flexible(1., true) .boxed() }) .constrained() diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index c973551f8d..f317a18916 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -83,7 +83,7 @@ impl View for FileFinder { .with_style(settings.theme.selector.input_editor.container) .boxed(), ) - .with_child(Flexible::new(1.0, self.render_matches()).boxed()) + .with_child(Flexible::new(1.0, false, self.render_matches()).boxed()) .boxed(), ) .with_style(settings.theme.selector.container) @@ -175,6 +175,7 @@ impl FileFinder { .with_child( Flexible::new( 1.0, + false, Flex::column() .with_child( Label::new(file_name.to_string(), style.label.clone()) diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index c0b6cdd143..8e14cd89ce 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -130,11 +130,11 @@ pub trait Element { Container::new(self.boxed()) } - fn expanded(self, flex: f32) -> Expanded + fn flexible(self, flex: f32, expanded: bool) -> Flexible where Self: 'static + Sized, { - Expanded::new(flex, self.boxed()) + Flexible::new(flex, expanded, self.boxed()) } } diff --git a/crates/gpui/src/elements/flex.rs b/crates/gpui/src/elements/flex.rs index e2bd7eb1c9..6b884289a2 100644 --- a/crates/gpui/src/elements/flex.rs +++ b/crates/gpui/src/elements/flex.rs @@ -228,88 +228,15 @@ struct FlexParentData { expanded: bool, } -pub struct Expanded { - metadata: FlexParentData, - child: ElementBox, -} - -impl Expanded { - pub fn new(flex: f32, child: ElementBox) -> Self { - Expanded { - metadata: FlexParentData { - flex, - expanded: true, - }, - child, - } - } -} - -impl Element for Expanded { - type LayoutState = (); - type PaintState = (); - - fn layout( - &mut self, - constraint: SizeConstraint, - cx: &mut LayoutContext, - ) -> (Vector2F, Self::LayoutState) { - let size = self.child.layout(constraint, cx); - (size, ()) - } - - fn paint( - &mut self, - bounds: RectF, - visible_bounds: RectF, - _: &mut Self::LayoutState, - cx: &mut PaintContext, - ) -> Self::PaintState { - self.child.paint(bounds.origin(), visible_bounds, cx) - } - - fn dispatch_event( - &mut self, - event: &Event, - _: RectF, - _: &mut Self::LayoutState, - _: &mut Self::PaintState, - cx: &mut EventContext, - ) -> bool { - self.child.dispatch_event(event, cx) - } - - fn metadata(&self) -> Option<&dyn Any> { - Some(&self.metadata) - } - - fn debug( - &self, - _: RectF, - _: &Self::LayoutState, - _: &Self::PaintState, - cx: &DebugContext, - ) -> Value { - json!({ - "type": "Expanded", - "flex": self.metadata.flex, - "child": self.child.debug(cx) - }) - } -} - pub struct Flexible { metadata: FlexParentData, child: ElementBox, } impl Flexible { - pub fn new(flex: f32, child: ElementBox) -> Self { + pub fn new(flex: f32, expanded: bool, child: ElementBox) -> Self { Flexible { - metadata: FlexParentData { - flex, - expanded: false, - }, + metadata: FlexParentData { flex, expanded }, child, } } diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index 4d226c3880..df7713ad1f 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -293,7 +293,7 @@ impl View for ThemeSelector { Container::new( Flex::new(Axis::Vertical) .with_child(ChildView::new(self.query_editor.id()).boxed()) - .with_child(Flexible::new(1.0, self.render_matches(cx)).boxed()) + .with_child(Flexible::new(1.0, false, self.render_matches(cx)).boxed()) .boxed(), ) .with_style(settings.theme.selector.container) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 5a3411c926..2ef586a8fb 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -314,13 +314,11 @@ impl Pane { } row.add_child( - Expanded::new( - 0.0, - Container::new(Empty::new().boxed()) - .with_border(theme.workspace.tab.container.border) - .boxed(), - ) - .named("filler"), + Empty::new() + .contained() + .with_border(theme.workspace.tab.container.border) + .flexible(0., true) + .named("filler"), ); row.boxed() @@ -345,7 +343,7 @@ impl View for Pane { if let Some(active_item) = self.active_item() { Flex::column() .with_child(self.render_tabs(cx)) - .with_child(Expanded::new(1.0, ChildView::new(active_item.id()).boxed()).boxed()) + .with_child(ChildView::new(active_item.id()).flexible(1., true).boxed()) .named("pane") } else { Empty::new().named("pane") diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index f61fd6f1af..a2b3803b85 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -183,7 +183,7 @@ impl PaneAxis { member = Container::new(member).with_border(border).boxed(); } - Expanded::new(1.0, member).boxed() + Flexible::new(1.0, true, member).boxed() })) .boxed() } diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/sidebar.rs index ea3b3cb920..8d3d0f6370 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/sidebar.rs @@ -135,19 +135,16 @@ impl Sidebar { } container.add_child( - Flexible::new( - 1., - Hook::new( - ConstrainedBox::new(ChildView::new(active_item.id()).boxed()) - .with_max_width(*self.width.borrow()) - .boxed(), - ) - .on_after_layout({ - let width = self.width.clone(); - move |size, _| *width.borrow_mut() = size.x() - }) - .boxed(), + Hook::new( + ConstrainedBox::new(ChildView::new(active_item.id()).boxed()) + .with_max_width(*self.width.borrow()) + .boxed(), ) + .on_after_layout({ + let width = self.width.clone(); + move |size, _| *width.borrow_mut() = size.x() + }) + .flexible(1., false) .boxed(), ); if matches!(self.side, Side::Left) { diff --git a/crates/workspace/src/status_bar.rs b/crates/workspace/src/status_bar.rs index 970078dcb5..162394ed00 100644 --- a/crates/workspace/src/status_bar.rs +++ b/crates/workspace/src/status_bar.rs @@ -47,7 +47,7 @@ impl View for StatusBar { .iter() .map(|i| ChildView::new(i.id()).aligned().boxed()), ) - .with_child(Empty::new().expanded(1.).boxed()) + .with_child(Empty::new().flexible(1., true).boxed()) .with_children( self.right_items .iter() diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 69a7004c44..6bb6da2157 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1191,50 +1191,40 @@ impl View for Workspace { fn render(&mut self, cx: &mut RenderContext) -> ElementBox { let settings = self.settings.borrow(); let theme = &settings.theme; - Container::new( - Flex::column() - .with_child(self.render_titlebar(&theme, cx)) - .with_child( - Expanded::new( - 1.0, - Stack::new() - .with_child({ - let mut content = Flex::row(); - content.add_child(self.left_sidebar.render(&settings, cx)); - if let Some(element) = - self.left_sidebar.render_active_item(&settings, cx) - { - content.add_child(Flexible::new(0.8, element).boxed()); - } - content.add_child( - 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.) + Flex::column() + .with_child(self.render_titlebar(&theme, cx)) + .with_child( + Stack::new() + .with_child({ + let mut content = Flex::row(); + content.add_child(self.left_sidebar.render(&settings, cx)); + if let Some(element) = self.left_sidebar.render_active_item(&settings, cx) { + content.add_child(Flexible::new(0.8, false, element).boxed()); + } + content.add_child( + Flex::column() + .with_child( + Flexible::new(1., true, self.center.render(&settings.theme)) .boxed(), - ); - if let Some(element) = - self.right_sidebar.render_active_item(&settings, cx) - { - content.add_child(Flexible::new(0.8, element).boxed()); - } - content.add_child(self.right_sidebar.render(&settings, cx)); - content.boxed() - }) - .with_children( - self.modal.as_ref().map(|m| ChildView::new(m.id()).boxed()), - ) - .boxed(), - ) + ) + .with_child(ChildView::new(self.status_bar.id()).boxed()) + .flexible(1., true) + .boxed(), + ); + if let Some(element) = self.right_sidebar.render_active_item(&settings, cx) + { + content.add_child(Flexible::new(0.8, false, element).boxed()); + } + content.add_child(self.right_sidebar.render(&settings, cx)); + content.boxed() + }) + .with_children(self.modal.as_ref().map(|m| ChildView::new(m.id()).boxed())) + .flexible(1.0, true) .boxed(), - ) - .boxed(), - ) - .with_background_color(settings.theme.workspace.background) - .named("workspace") + ) + .contained() + .with_background_color(settings.theme.workspace.background) + .named("workspace") } fn on_focus(&mut self, cx: &mut ViewContext) { From 2b36ab0de7a69a382b85d74d7641e377b5dace9e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 6 Jan 2022 17:35:45 -0800 Subject: [PATCH 26/43] Introduce Expanded element Co-Authored-By: Nathan Sobo --- crates/gpui/src/elements.rs | 9 +++ crates/gpui/src/elements/expanded.rs | 90 ++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 crates/gpui/src/elements/expanded.rs diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index 8e14cd89ce..6830923953 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -4,6 +4,7 @@ mod constrained_box; mod container; mod empty; mod event_handler; +mod expanded; mod flex; mod hook; mod image; @@ -16,6 +17,7 @@ mod svg; mod text; mod uniform_list; +use self::expanded::Expanded; pub use self::{ align::*, canvas::*, constrained_box::*, container::*, empty::*, event_handler::*, flex::*, hook::*, image::*, label::*, list::*, mouse_event_handler::*, overlay::*, stack::*, svg::*, @@ -130,6 +132,13 @@ pub trait Element { Container::new(self.boxed()) } + fn expanded(self) -> Expanded + where + Self: 'static + Sized, + { + Expanded::new(self.boxed()) + } + fn flexible(self, flex: f32, expanded: bool) -> Flexible where Self: 'static + Sized, diff --git a/crates/gpui/src/elements/expanded.rs b/crates/gpui/src/elements/expanded.rs new file mode 100644 index 0000000000..cbeef598da --- /dev/null +++ b/crates/gpui/src/elements/expanded.rs @@ -0,0 +1,90 @@ +use crate::{ + geometry::{rect::RectF, vector::Vector2F}, + json, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, + SizeConstraint, +}; +use serde_json::json; + +pub struct Expanded { + child: ElementBox, + full_width: bool, + full_height: bool, +} + +impl Expanded { + pub fn new(child: ElementBox) -> Self { + Self { + child, + full_width: true, + full_height: true, + } + } + + pub fn to_full_width(mut self) -> Self { + self.full_width = true; + self.full_height = false; + self + } + + pub fn to_full_height(mut self) -> Self { + self.full_width = false; + self.full_height = true; + self + } +} + +impl Element for Expanded { + type LayoutState = (); + type PaintState = (); + + fn layout( + &mut self, + mut constraint: SizeConstraint, + cx: &mut LayoutContext, + ) -> (Vector2F, Self::LayoutState) { + if self.full_width { + constraint.min.set_x(constraint.max.x()); + } + if self.full_height { + constraint.min.set_y(constraint.max.y()); + } + let size = self.child.layout(constraint, cx); + (size, ()) + } + + fn paint( + &mut self, + bounds: RectF, + visible_bounds: RectF, + _: &mut Self::LayoutState, + cx: &mut PaintContext, + ) -> Self::PaintState { + self.child.paint(bounds.origin(), visible_bounds, cx); + } + + fn dispatch_event( + &mut self, + event: &Event, + _: RectF, + _: &mut Self::LayoutState, + _: &mut Self::PaintState, + cx: &mut EventContext, + ) -> bool { + self.child.dispatch_event(event, cx) + } + + fn debug( + &self, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + cx: &DebugContext, + ) -> json::Value { + json!({ + "type": "Expanded", + "full_width": self.full_width, + "full_height": self.full_height, + "child": self.child.debug(cx) + }) + } +} From 94e9c7fd5b25599876bd665985b173a8b4c26544 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 6 Jan 2022 17:55:56 -0800 Subject: [PATCH 27/43] Give a full-width background to the diagnostic headers --- crates/diagnostics/src/diagnostics.rs | 2 +- crates/editor/src/editor.rs | 8 +++++++- crates/theme/src/theme.rs | 2 ++ crates/zed/assets/themes/_base.toml | 22 +++++++++++++++++----- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index dfaac9f150..f6cc659818 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -289,7 +289,7 @@ impl ProjectDiagnosticsEditor { diagnostic_blocks.push(DiagnosticBlock::Header(header.clone())); blocks_to_add.push(BlockProperties { position: header_position, - height: 2, + height: 3, render: diagnostic_header_renderer( buffer.clone(), header, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 4d3aa058be..b9c57de130 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3855,7 +3855,8 @@ pub fn diagnostic_header_renderer( Arc::new(move |cx| { let settings = build_settings(cx); let mut text_style = settings.style.text.clone(); - text_style.color = diagnostic_style(diagnostic.severity, is_valid, &settings.style).text; + let diagnostic_style = diagnostic_style(diagnostic.severity, is_valid, &settings.style); + text_style.color = diagnostic_style.text; let file_path = if let Some(file) = buffer.read(&**cx).file() { file.path().to_string_lossy().to_string() } else { @@ -3869,6 +3870,11 @@ pub fn diagnostic_header_renderer( .boxed(), ) .with_child(Label::new(file_path, settings.style.text.clone()).boxed()) + .aligned() + .left() + .contained() + .with_style(diagnostic_style.header) + .expanded() .boxed() }) } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 6a67789afe..e91f0addb7 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -261,6 +261,8 @@ pub struct EditorStyle { #[derive(Copy, Clone, Deserialize, Default)] pub struct DiagnosticStyle { pub text: Color, + #[serde(default)] + pub header: ContainerStyle, } #[derive(Clone, Copy, Default, Deserialize)] diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index 4063fc92f9..2fa2bde545 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -185,7 +185,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" @@ -249,15 +249,27 @@ line_number_active = "$text.0.color" selection = "$selection.host" guest_selections = "$selection.guests" error_color = "$status.bad" -error_diagnostic = { text = "$status.bad" } invalid_error_diagnostic = { text = "$text.3.color" } -warning_diagnostic = { text = "$status.warn" } invalid_warning_diagnostic = { text = "$text.3.color" } -information_diagnostic = { text = "$status.info" } invalid_information_diagnostic = { text = "$text.3.color" } -hint_diagnostic = { text = "$status.info" } invalid_hint_diagnostic = { text = "$text.3.color" } +[editor.error_diagnostic] +text = "$status.bad" +header = { padding = { left = 10 }, background = "#ffffff08" } + +[editor.warning_diagnostic] +text = "$status.warn" +header = { padding = { left = 10 }, background = "#ffffff08" } + +[editor.information_diagnostic] +text = "$status.info" +header = { padding = { left = 10 }, background = "#ffffff08" } + +[editor.hint_diagnostic] +text = "$status.info" +header = { padding = { left = 10 }, background = "#ffffff08" } + [project_diagnostics] background = "$surface.1" empty_message = "$text.0" From 67f672d0cc2209903d5a34c5877fc0a2f65d3633 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 7 Jan 2022 10:19:28 +0100 Subject: [PATCH 28/43] Clear selections on other excerpted buffers when setting active selections --- crates/editor/src/multi_buffer.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index f399a53412..ed67491e24 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -496,6 +496,14 @@ impl MultiBuffer { } } + for (buffer_id, buffer_state) in self.buffers.borrow().iter() { + if !selections_by_buffer.contains_key(buffer_id) { + buffer_state + .buffer + .update(cx, |buffer, cx| buffer.remove_active_selections(cx)); + } + } + for (buffer_id, mut selections) in selections_by_buffer { self.buffers.borrow()[&buffer_id] .buffer From 089542c6f4c8132fb8f095c21c5a735b745c5355 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 7 Jan 2022 10:33:21 +0100 Subject: [PATCH 29/43] Avoid removing diagnostics from `Worktree` after opening a buffer This allows re-opening the same buffer and supplying the previous diagnostics. --- crates/project/src/worktree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 6108e9eb10..843a775993 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -1267,7 +1267,7 @@ impl LocalWorktree { let (diagnostics, language, language_server) = this.update(&mut cx, |this, cx| { let this = this.as_local_mut().unwrap(); - let diagnostics = this.diagnostics.remove(&path); + let diagnostics = this.diagnostics.get(&path).cloned(); let language = this .language_registry .select_language(file.full_path()) From 56496c25853184a45dc1b4453552115617557660 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 7 Jan 2022 13:38:20 +0100 Subject: [PATCH 30/43] Move back `diagnostic_summaries` into `Worktree` This fixes an issue where updating the snapshot's entries would override the diagnostic summaries received on the remote side. Co-Authored-By: Nathan Sobo --- crates/project/src/worktree.rs | 39 +++++++++++++--------------------- 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 843a775993..350908d0d5 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -34,7 +34,6 @@ use std::{ ffi::{OsStr, OsString}, fmt, future::Future, - mem, ops::{Deref, Range}, path::{Path, PathBuf}, sync::{ @@ -197,7 +196,6 @@ impl Worktree { entries_by_id, removed_entry_ids: Default::default(), next_entry_id: Default::default(), - diagnostic_summaries, }; let (updates_tx, mut updates_rx) = postage::mpsc::channel(64); @@ -241,6 +239,7 @@ impl Worktree { queued_operations: Default::default(), languages, user_store, + diagnostic_summaries, }) }) }); @@ -584,9 +583,7 @@ impl Worktree { match self { Self::Local(worktree) => { let is_fake_fs = worktree.fs.is_fake(); - worktree - .snapshot - .assign(worktree.background_snapshot.lock().clone()); + worktree.snapshot = worktree.background_snapshot.lock().clone(); if worktree.is_scanning() { if worktree.poll_task.is_none() { worktree.poll_task = Some(cx.spawn(|this, mut cx| async move { @@ -828,8 +825,7 @@ impl Worktree { let this = self.as_local_mut().unwrap(); let summary = DiagnosticSummary::new(&diagnostics); - this.snapshot - .diagnostic_summaries + this.diagnostic_summaries .insert(PathKey(worktree_path.clone()), summary.clone()); this.diagnostics.insert(worktree_path.clone(), diagnostics); @@ -943,7 +939,6 @@ pub struct Snapshot { entries_by_id: SumTree, removed_entry_ids: HashMap, next_entry_id: Arc, - diagnostic_summaries: TreeMap, } pub struct LocalWorktree { @@ -958,6 +953,7 @@ pub struct LocalWorktree { open_buffers: HashMap>, shared_buffers: HashMap>>, diagnostics: HashMap, Vec>>, + diagnostic_summaries: TreeMap, queued_operations: Vec<(u64, Operation)>, language_registry: Arc, client: Arc, @@ -984,6 +980,7 @@ pub struct RemoteWorktree { languages: Arc, user_store: ModelHandle, queued_operations: Vec<(u64, Operation)>, + diagnostic_summaries: TreeMap, } type LoadingBuffers = HashMap< @@ -1038,7 +1035,6 @@ impl LocalWorktree { entries_by_id: Default::default(), removed_entry_ids: Default::default(), next_entry_id: Arc::new(next_entry_id), - diagnostic_summaries: Default::default(), }; if let Some(metadata) = metadata { snapshot.insert_entry( @@ -1064,6 +1060,7 @@ impl LocalWorktree { open_buffers: Default::default(), shared_buffers: Default::default(), diagnostics: Default::default(), + diagnostic_summaries: Default::default(), queued_operations: Default::default(), language_registry: languages, client, @@ -1502,10 +1499,11 @@ impl LocalWorktree { }) .detach(); + let diagnostic_summaries = self.diagnostic_summaries.clone(); let share_message = cx.background().spawn(async move { proto::ShareWorktree { project_id, - worktree: Some(snapshot.to_proto()), + worktree: Some(snapshot.to_proto(&diagnostic_summaries)), } }); @@ -1680,7 +1678,7 @@ impl RemoteWorktree { ) { if let Some(summary) = envelope.payload.summary { let path: Arc = Path::new(&summary.path).into(); - self.snapshot.diagnostic_summaries.insert( + self.diagnostic_summaries.insert( PathKey(path.clone()), DiagnosticSummary { error_count: summary.error_count as usize, @@ -1726,7 +1724,10 @@ impl Snapshot { self.id } - pub fn to_proto(&self) -> proto::Worktree { + pub fn to_proto( + &self, + diagnostic_summaries: &TreeMap, + ) -> proto::Worktree { let root_name = self.root_name.clone(); proto::Worktree { id: self.id.0 as u64, @@ -1737,8 +1738,7 @@ impl Snapshot { .filter(|e| !e.is_ignored) .map(Into::into) .collect(), - diagnostic_summaries: self - .diagnostic_summaries + diagnostic_summaries: diagnostic_summaries .iter() .map(|(path, summary)| summary.to_proto(path.0.clone())) .collect(), @@ -1845,14 +1845,6 @@ impl Snapshot { Ok(()) } - fn assign(&mut self, mut other: Self) { - mem::swap( - &mut self.diagnostic_summaries, - &mut other.diagnostic_summaries, - ); - *self = other; - } - pub fn file_count(&self) -> usize { self.entries_by_path.summary().file_count } @@ -3358,7 +3350,7 @@ mod tests { let remote = Worktree::remote( 1, 1, - initial_snapshot.to_proto(), + initial_snapshot.to_proto(&Default::default()), Client::new(http_client.clone()), user_store, Default::default(), @@ -4205,7 +4197,6 @@ mod tests { root_name: Default::default(), root_char_bag: Default::default(), next_entry_id: next_entry_id.clone(), - diagnostic_summaries: Default::default(), }; initial_snapshot.insert_entry( Entry::new( From e39be35e174a8449e1a5d0ebf20150fce8160afe Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 7 Jan 2022 14:06:05 +0100 Subject: [PATCH 31/43] Show status bar item for project diagnostic summary Co-Authored-By: Nathan Sobo --- crates/diagnostics/src/diagnostics.rs | 10 ++-- crates/diagnostics/src/items.rs | 71 +++++++++++++++++++++++++++ crates/project/src/project.rs | 11 +++++ crates/theme/src/theme.rs | 1 + crates/zed/assets/themes/_base.toml | 3 +- crates/zed/src/zed.rs | 12 ++++- 6 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 crates/diagnostics/src/items.rs diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index f6cc659818..c7f1f9b868 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -1,3 +1,5 @@ +pub mod items; + use anyhow::Result; use collections::{HashMap, HashSet}; use editor::{ @@ -16,13 +18,13 @@ use std::{cmp::Ordering, ops::Range, path::Path, sync::Arc}; use util::TryFutureExt; use workspace::Workspace; -action!(Toggle); +action!(Deploy); const CONTEXT_LINE_COUNT: u32 = 1; pub fn init(cx: &mut MutableAppContext) { - cx.add_bindings([Binding::new("alt-shift-D", Toggle, None)]); - cx.add_action(ProjectDiagnosticsEditor::toggle); + cx.add_bindings([Binding::new("alt-shift-D", Deploy, None)]); + cx.add_action(ProjectDiagnosticsEditor::deploy); } type Event = editor::Event; @@ -148,7 +150,7 @@ impl ProjectDiagnosticsEditor { self.editor.read(cx).text(cx) } - fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { + fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext) { let diagnostics = cx.add_model(|_| ProjectDiagnostics::new(workspace.project().clone())); workspace.add_item(diagnostics, cx); } diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs new file mode 100644 index 0000000000..ae54b9879b --- /dev/null +++ b/crates/diagnostics/src/items.rs @@ -0,0 +1,71 @@ +use gpui::{ + elements::*, platform::CursorStyle, Entity, ModelHandle, RenderContext, View, ViewContext, +}; +use postage::watch; +use project::Project; +use workspace::{Settings, StatusItemView}; + +pub struct DiagnosticSummary { + settings: watch::Receiver, + summary: project::DiagnosticSummary, +} + +impl DiagnosticSummary { + pub fn new( + project: &ModelHandle, + settings: watch::Receiver, + cx: &mut ViewContext, + ) -> Self { + cx.subscribe(project, |this, project, event, cx| { + if let project::Event::DiskBasedDiagnosticsUpdated { .. } = event { + this.summary = project.read(cx).diagnostic_summary(cx); + cx.notify(); + } + }) + .detach(); + Self { + settings, + summary: project.read(cx).diagnostic_summary(cx), + } + } +} + +impl Entity for DiagnosticSummary { + type Event = (); +} + +impl View for DiagnosticSummary { + fn ui_name() -> &'static str { + "DiagnosticSummary" + } + + fn render(&mut self, cx: &mut RenderContext) -> ElementBox { + enum Tag {} + + let theme = &self.settings.borrow().theme.project_diagnostics; + MouseEventHandler::new::(0, cx, |_, _| { + Label::new( + format!( + "Errors: {}, Warnings: {}", + self.summary.error_count, self.summary.warning_count + ), + theme.status_bar_item.text.clone(), + ) + .contained() + .with_style(theme.status_bar_item.container) + .boxed() + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(|cx| cx.dispatch_action(crate::Deploy)) + .boxed() + } +} + +impl StatusItemView for DiagnosticSummary { + fn set_active_pane_item( + &mut self, + _: Option<&dyn workspace::ItemViewHandle>, + _: &mut ViewContext, + ) { + } +} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 325ac682eb..c1da5aa842 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -539,6 +539,17 @@ impl Project { } } + pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary { + let mut summary = DiagnosticSummary::default(); + for (_, path_summary) in self.diagnostic_summaries(cx) { + summary.error_count += path_summary.error_count; + summary.warning_count += path_summary.warning_count; + summary.info_count += path_summary.info_count; + summary.hint_count += path_summary.hint_count; + } + summary + } + pub fn diagnostic_summaries<'a>( &'a self, cx: &'a AppContext, diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index e91f0addb7..1ba2fc8faf 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -232,6 +232,7 @@ pub struct ProjectDiagnostics { #[serde(flatten)] pub container: ContainerStyle, pub empty_message: TextStyle, + pub status_bar_item: ContainedText, } #[derive(Clone, Deserialize, Default)] diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index 2fa2bde545..a8a5694978 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -185,7 +185,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" @@ -273,3 +273,4 @@ header = { padding = { left = 10 }, background = "#ffffff08" } [project_diagnostics] background = "$surface.1" empty_message = "$text.0" +status_bar_item = { extends = "$text.2", margin.right = 10 } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 1483982b60..0004668939 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -88,12 +88,20 @@ pub fn build_workspace( .into(), ); - let diagnostic = + let diagnostic_message = cx.add_view(|_| editor::items::DiagnosticMessage::new(app_state.settings.clone())); + let diagnostic_summary = cx.add_view(|cx| { + diagnostics::items::DiagnosticSummary::new( + workspace.project(), + app_state.settings.clone(), + cx, + ) + }); let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new(app_state.settings.clone())); workspace.status_bar().update(cx, |status_bar, cx| { - status_bar.add_left_item(diagnostic, cx); + status_bar.add_left_item(diagnostic_summary, cx); + status_bar.add_left_item(diagnostic_message, cx); status_bar.add_right_item(cursor_position, cx); }); From cf62d26ed8a3bbd9a0abeb534cf1c10ee674d01b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 7 Jan 2022 15:03:19 +0100 Subject: [PATCH 32/43] Display a "Checking..." message when running disk-based diagnostics Co-Authored-By: Nathan Sobo --- crates/diagnostics/src/items.rs | 40 +++++++++---- crates/project/src/project.rs | 46 ++++++++++++++- crates/project/src/worktree.rs | 99 +++++++++++++++++++++++++-------- crates/rpc/proto/zed.proto | 46 ++++++++------- crates/rpc/src/proto.rs | 2 + crates/server/src/rpc.rs | 17 ++++++ 6 files changed, 195 insertions(+), 55 deletions(-) diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index ae54b9879b..072738fa77 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -3,11 +3,13 @@ use gpui::{ }; use postage::watch; use project::Project; +use std::fmt::Write; use workspace::{Settings, StatusItemView}; pub struct DiagnosticSummary { settings: watch::Receiver, summary: project::DiagnosticSummary, + in_progress: bool, } impl DiagnosticSummary { @@ -16,16 +18,26 @@ impl DiagnosticSummary { settings: watch::Receiver, cx: &mut ViewContext, ) -> Self { - cx.subscribe(project, |this, project, event, cx| { - if let project::Event::DiskBasedDiagnosticsUpdated { .. } = event { + cx.subscribe(project, |this, project, event, cx| match event { + project::Event::DiskBasedDiagnosticsUpdated { .. } => { this.summary = project.read(cx).diagnostic_summary(cx); cx.notify(); } + project::Event::DiskBasedDiagnosticsStarted => { + this.in_progress = true; + cx.notify(); + } + project::Event::DiskBasedDiagnosticsFinished => { + this.in_progress = false; + cx.notify(); + } + _ => {} }) .detach(); Self { settings, summary: project.read(cx).diagnostic_summary(cx), + in_progress: project.read(cx).is_running_disk_based_diagnostics(), } } } @@ -43,17 +55,21 @@ impl View for DiagnosticSummary { enum Tag {} let theme = &self.settings.borrow().theme.project_diagnostics; + let mut message = String::new(); + if self.in_progress { + message.push_str("Checking... "); + } + write!( + message, + "Errors: {}, Warnings: {}", + self.summary.error_count, self.summary.warning_count + ) + .unwrap(); MouseEventHandler::new::(0, cx, |_, _| { - Label::new( - format!( - "Errors: {}, Warnings: {}", - self.summary.error_count, self.summary.warning_count - ), - theme.status_bar_item.text.clone(), - ) - .contained() - .with_style(theme.status_bar_item.container) - .boxed() + Label::new(message, theme.status_bar_item.text.clone()) + .contained() + .with_style(theme.status_bar_item.container) + .boxed() }) .with_cursor_style(CursorStyle::PointingHand) .on_click(|cx| cx.dispatch_action(crate::Deploy)) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index c1da5aa842..62ecf94b7e 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -33,6 +33,7 @@ pub struct Project { client_state: ProjectClientState, collaborators: HashMap, subscriptions: Vec, + pending_disk_based_diagnostics: isize, } enum ProjectClientState { @@ -60,7 +61,9 @@ pub struct Collaborator { pub enum Event { ActiveEntryChanged(Option), WorktreeRemoved(WorktreeId), + DiskBasedDiagnosticsStarted, DiskBasedDiagnosticsUpdated { worktree_id: WorktreeId }, + DiskBasedDiagnosticsFinished, DiagnosticsUpdated(ProjectPath), } @@ -187,6 +190,7 @@ impl Project { client, user_store, fs, + pending_disk_based_diagnostics: 0, } }) } @@ -259,6 +263,11 @@ impl Project { cx, Self::handle_update_diagnostic_summary, ), + client.subscribe_to_entity( + remote_id, + cx, + Self::handle_disk_based_diagnostics_updating, + ), client.subscribe_to_entity( remote_id, cx, @@ -273,6 +282,7 @@ impl Project { remote_id, replica_id, }, + pending_disk_based_diagnostics: 0, }; for worktree in worktrees { this.add_worktree(worktree, cx); @@ -506,17 +516,29 @@ impl Project { fn add_worktree(&mut self, worktree: ModelHandle, cx: &mut ModelContext) { cx.observe(&worktree, |_, _, cx| cx.notify()).detach(); - cx.subscribe(&worktree, |_, worktree, event, cx| match event { + cx.subscribe(&worktree, move |this, worktree, event, cx| match event { worktree::Event::DiagnosticsUpdated(path) => { cx.emit(Event::DiagnosticsUpdated(ProjectPath { worktree_id: worktree.read(cx).id(), path: path.clone(), })); } + worktree::Event::DiskBasedDiagnosticsUpdating => { + if this.pending_disk_based_diagnostics == 0 { + cx.emit(Event::DiskBasedDiagnosticsStarted); + } + this.pending_disk_based_diagnostics += 1; + } worktree::Event::DiskBasedDiagnosticsUpdated => { + this.pending_disk_based_diagnostics -= 1; cx.emit(Event::DiskBasedDiagnosticsUpdated { worktree_id: worktree.read(cx).id(), }); + if this.pending_disk_based_diagnostics == 0 { + if this.pending_disk_based_diagnostics == 0 { + cx.emit(Event::DiskBasedDiagnosticsFinished); + } + } } }) .detach(); @@ -539,6 +561,10 @@ impl Project { } } + pub fn is_running_disk_based_diagnostics(&self) -> bool { + self.pending_disk_based_diagnostics > 0 + } + pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary { let mut summary = DiagnosticSummary::default(); for (_, path_summary) in self.diagnostic_summaries(cx) { @@ -716,6 +742,24 @@ impl Project { Ok(()) } + fn handle_disk_based_diagnostics_updating( + &mut self, + envelope: TypedEnvelope, + _: Arc, + cx: &mut ModelContext, + ) -> Result<()> { + let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); + if let Some(worktree) = self.worktree_for_id(worktree_id, cx) { + worktree.update(cx, |worktree, cx| { + worktree + .as_remote() + .unwrap() + .disk_based_diagnostics_updating(cx); + }); + } + Ok(()) + } + fn handle_disk_based_diagnostics_updated( &mut self, envelope: TypedEnvelope, diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 350908d0d5..46caf8cd93 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -67,6 +67,7 @@ pub enum Worktree { #[derive(Clone, Debug, Eq, PartialEq)] pub enum Event { + DiskBasedDiagnosticsUpdating, DiskBasedDiagnosticsUpdated, DiagnosticsUpdated(Arc), } @@ -1133,6 +1134,11 @@ impl LocalWorktree { .log_err() .flatten() { + enum DiagnosticProgress { + Updating, + Updated, + } + let disk_based_sources = language .disk_based_diagnostic_sources() .cloned() @@ -1155,10 +1161,21 @@ impl LocalWorktree { while let Ok(diagnostics) = diagnostics_rx.recv().await { if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { handle.update(&mut cx, |this, cx| { + if !has_disk_based_diagnostic_progress_token { + smol::block_on( + disk_based_diagnostics_done_tx + .send(DiagnosticProgress::Updating), + ) + .ok(); + } this.update_diagnostics(diagnostics, &disk_based_sources, cx) .log_err(); if !has_disk_based_diagnostic_progress_token { - smol::block_on(disk_based_diagnostics_done_tx.send(())).ok(); + smol::block_on( + disk_based_diagnostics_done_tx + .send(DiagnosticProgress::Updated), + ) + .ok(); } }) } else { @@ -1181,13 +1198,23 @@ impl LocalWorktree { match params.value { lsp::ProgressParamsValue::WorkDone(progress) => match progress { lsp::WorkDoneProgress::Begin(_) => { + if pending_disk_based_diagnostics == 0 { + smol::block_on( + disk_based_diagnostics_done_tx + .send(DiagnosticProgress::Updating), + ) + .ok(); + } pending_disk_based_diagnostics += 1; } lsp::WorkDoneProgress::End(_) => { pending_disk_based_diagnostics -= 1; if pending_disk_based_diagnostics == 0 { - smol::block_on(disk_based_diagnostics_done_tx.send(())) - .ok(); + smol::block_on( + disk_based_diagnostics_done_tx + .send(DiagnosticProgress::Updated), + ) + .ok(); } } _ => {} @@ -1198,21 +1225,41 @@ impl LocalWorktree { .detach(); let rpc = self.client.clone(); cx.spawn_weak(|this, mut cx| async move { - while let Ok(()) = disk_based_diagnostics_done_rx.recv().await { + while let Ok(progress) = disk_based_diagnostics_done_rx.recv().await { if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { - let message = handle.update(&mut cx, |this, cx| { - cx.emit(Event::DiskBasedDiagnosticsUpdated); - let this = this.as_local().unwrap(); - this.share - .as_ref() - .map(|share| proto::DiskBasedDiagnosticsUpdated { - project_id: share.project_id, - worktree_id: this.id().to_proto(), - }) - }); + match progress { + DiagnosticProgress::Updating => { + let message = handle.update(&mut cx, |this, cx| { + cx.emit(Event::DiskBasedDiagnosticsUpdating); + let this = this.as_local().unwrap(); + this.share.as_ref().map(|share| { + proto::DiskBasedDiagnosticsUpdating { + project_id: share.project_id, + worktree_id: this.id().to_proto(), + } + }) + }); - if let Some(message) = message { - rpc.send(message).await.log_err(); + if let Some(message) = message { + rpc.send(message).await.log_err(); + } + } + DiagnosticProgress::Updated => { + let message = handle.update(&mut cx, |this, cx| { + cx.emit(Event::DiskBasedDiagnosticsUpdated); + let this = this.as_local().unwrap(); + this.share.as_ref().map(|share| { + proto::DiskBasedDiagnosticsUpdated { + project_id: share.project_id, + worktree_id: this.id().to_proto(), + } + }) + }); + + if let Some(message) = message { + rpc.send(message).await.log_err(); + } + } } } else { break; @@ -1691,6 +1738,10 @@ impl RemoteWorktree { } } + pub fn disk_based_diagnostics_updating(&self, cx: &mut ModelContext) { + cx.emit(Event::DiskBasedDiagnosticsUpdating); + } + pub fn disk_based_diagnostics_updated(&self, cx: &mut ModelContext) { cx.emit(Event::DiskBasedDiagnosticsUpdated); } @@ -3848,6 +3899,11 @@ mod tests { let mut events = subscribe(&tree, &mut cx); fake_server.start_progress(&progress_token).await; + assert_eq!( + events.next().await.unwrap(), + Event::DiskBasedDiagnosticsUpdating + ); + fake_server.start_progress(&progress_token).await; fake_server.end_progress(&progress_token).await; fake_server.start_progress(&progress_token).await; @@ -3864,18 +3920,17 @@ mod tests { }], }) .await; - - let event = events.next().await.unwrap(); assert_eq!( - event, + events.next().await.unwrap(), Event::DiagnosticsUpdated(Arc::from(Path::new("a.rs"))) ); fake_server.end_progress(&progress_token).await; fake_server.end_progress(&progress_token).await; - - let event = events.next().await.unwrap(); - assert_eq!(event, Event::DiskBasedDiagnosticsUpdated); + assert_eq!( + events.next().await.unwrap(), + Event::DiskBasedDiagnosticsUpdated + ); let buffer = tree .update(&mut cx, |tree, cx| tree.open_buffer("a.rs", cx)) diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 669ffdbd7e..f6300c4495 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -26,30 +26,31 @@ message Envelope { ShareWorktree share_worktree = 19; UpdateWorktree update_worktree = 20; UpdateDiagnosticSummary update_diagnostic_summary = 21; - DiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 22; + DiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 22; + DiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 23; - OpenBuffer open_buffer = 23; - OpenBufferResponse open_buffer_response = 24; - CloseBuffer close_buffer = 25; - UpdateBuffer update_buffer = 26; - SaveBuffer save_buffer = 27; - BufferSaved buffer_saved = 28; + OpenBuffer open_buffer = 24; + OpenBufferResponse open_buffer_response = 25; + CloseBuffer close_buffer = 26; + UpdateBuffer update_buffer = 27; + SaveBuffer save_buffer = 28; + BufferSaved buffer_saved = 29; - GetChannels get_channels = 29; - GetChannelsResponse get_channels_response = 30; - JoinChannel join_channel = 31; - JoinChannelResponse join_channel_response = 32; - LeaveChannel leave_channel = 33; - SendChannelMessage send_channel_message = 34; - SendChannelMessageResponse send_channel_message_response = 35; - ChannelMessageSent channel_message_sent = 36; - GetChannelMessages get_channel_messages = 37; - GetChannelMessagesResponse get_channel_messages_response = 38; + GetChannels get_channels = 30; + GetChannelsResponse get_channels_response = 31; + JoinChannel join_channel = 32; + JoinChannelResponse join_channel_response = 33; + LeaveChannel leave_channel = 34; + SendChannelMessage send_channel_message = 35; + SendChannelMessageResponse send_channel_message_response = 36; + ChannelMessageSent channel_message_sent = 37; + GetChannelMessages get_channel_messages = 38; + GetChannelMessagesResponse get_channel_messages_response = 39; - UpdateContacts update_contacts = 39; + UpdateContacts update_contacts = 40; - GetUsers get_users = 40; - GetUsersResponse get_users_response = 41; + GetUsers get_users = 41; + GetUsersResponse get_users_response = 42; } } @@ -181,6 +182,11 @@ message DiagnosticSummary { uint32 hint_count = 7; } +message DiskBasedDiagnosticsUpdating { + uint64 project_id = 1; + uint64 worktree_id = 2; +} + message DiskBasedDiagnosticsUpdated { uint64 project_id = 1; uint64 worktree_id = 2; diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 2cabdc9218..91abc2523d 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -126,6 +126,7 @@ messages!( ChannelMessageSent, CloseBuffer, DiskBasedDiagnosticsUpdated, + DiskBasedDiagnosticsUpdating, Error, GetChannelMessages, GetChannelMessagesResponse, @@ -183,6 +184,7 @@ entity_messages!( BufferSaved, CloseBuffer, DiskBasedDiagnosticsUpdated, + DiskBasedDiagnosticsUpdating, JoinProject, LeaveProject, OpenBuffer, diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 510b701ddd..76698c3a19 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -72,6 +72,7 @@ impl Server { .add_handler(Server::share_worktree) .add_handler(Server::update_worktree) .add_handler(Server::update_diagnostic_summary) + .add_handler(Server::disk_based_diagnostics_updating) .add_handler(Server::disk_based_diagnostics_updated) .add_handler(Server::open_buffer) .add_handler(Server::close_buffer) @@ -556,6 +557,22 @@ impl Server { Ok(()) } + async fn disk_based_diagnostics_updating( + self: Arc, + request: TypedEnvelope, + ) -> tide::Result<()> { + let receiver_ids = self + .state() + .project_connection_ids(request.payload.project_id, request.sender_id) + .ok_or_else(|| anyhow!(NO_SUCH_PROJECT))?; + broadcast(request.sender_id, receiver_ids, |connection_id| { + self.peer + .forward_send(request.sender_id, connection_id, request.payload.clone()) + }) + .await?; + Ok(()) + } + async fn disk_based_diagnostics_updated( self: Arc, request: TypedEnvelope, From 3cab32d2010841b8f691a5bd543025cdaef4d8a6 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 7 Jan 2022 16:12:50 +0100 Subject: [PATCH 33/43] WIP: Add keybinding to open buffers under cursors Co-Authored-By: Nathan Sobo --- crates/diagnostics/src/diagnostics.rs | 29 ++++++++++++++++++++++- crates/editor/src/editor.rs | 5 ++-- crates/editor/src/multi_buffer.rs | 27 +++++++++++++++++++++ crates/file_finder/src/file_finder.rs | 2 +- crates/project_panel/src/project_panel.rs | 2 +- crates/workspace/src/workspace.rs | 4 ++-- crates/zed/src/zed.rs | 16 ++++++------- 7 files changed, 70 insertions(+), 15 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index c7f1f9b868..c008b10399 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -19,12 +19,21 @@ use util::TryFutureExt; use workspace::Workspace; action!(Deploy); +action!(OpenExcerpts); const CONTEXT_LINE_COUNT: u32 = 1; pub fn init(cx: &mut MutableAppContext) { - cx.add_bindings([Binding::new("alt-shift-D", Deploy, None)]); + cx.add_bindings([ + Binding::new("alt-shift-D", Deploy, Some("Workspace")), + Binding::new( + "alt-shift-D", + OpenExcerpts, + Some("ProjectDiagnosticsEditor"), + ), + ]); cx.add_action(ProjectDiagnosticsEditor::deploy); + cx.add_action(ProjectDiagnosticsEditor::open_excerpts); } type Event = editor::Event; @@ -155,6 +164,24 @@ impl ProjectDiagnosticsEditor { workspace.add_item(diagnostics, cx); } + fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext) { + let editor = self.editor.read(cx); + let excerpts = self.excerpts.read(cx); + let mut new_selections_by_buffer = HashMap::default(); + for selection in editor.local_selections::(cx) { + for (buffer, range) in excerpts.excerpted_buffers(selection.start..selection.end, cx) { + new_selections_by_buffer + .entry(buffer) + .or_insert(Vec::new()) + .push((range.start, range.end, selection.reversed)) + } + } + + for (buffer, selections) in new_selections_by_buffer { + // buffer.read(cx). + } + } + fn update_excerpts(&self, paths: HashSet, cx: &mut ViewContext) { let project = self.project.clone(); cx.spawn(|this, mut cx| { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b9c57de130..7ec1a171fe 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1105,7 +1105,7 @@ impl Editor { T: ToOffset, { let buffer = self.buffer.read(cx).snapshot(cx); - let selections = ranges + let mut selections = ranges .into_iter() .map(|range| { let mut start = range.start.to_offset(&buffer); @@ -1124,7 +1124,8 @@ impl Editor { goal: SelectionGoal::None, } }) - .collect(); + .collect::>(); + selections.sort_unstable_by_key(|s| s.start); self.update_selections(selections, autoscroll, cx); } diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index ed67491e24..0fe155ff11 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -689,6 +689,33 @@ impl MultiBuffer { .map_or(Vec::new(), |state| state.excerpts.clone()) } + pub fn excerpted_buffers<'a, T: ToOffset>( + &'a self, + range: Range, + cx: &AppContext, + ) -> Vec<(ModelHandle, Range)> { + let snapshot = self.snapshot(cx); + let start = range.start.to_offset(&snapshot); + let end = range.end.to_offset(&snapshot); + + let mut result = Vec::new(); + let mut cursor = snapshot.excerpts.cursor::(); + cursor.seek(&start, Bias::Right, &()); + while let Some(excerpt) = cursor.item() { + if *cursor.start() > end { + break; + } + + let excerpt_start = excerpt.range.start.to_offset(&excerpt.buffer); + let start = excerpt_start + (cmp::max(start, *cursor.start()) - *cursor.start()); + let end = excerpt_start + (cmp::min(end, cursor.end(&())) - *cursor.start()); + let buffer = self.buffers.borrow()[&excerpt.buffer_id].buffer.clone(); + result.push((buffer, start..end)); + } + + result + } + pub fn remove_excerpts<'a>( &mut self, excerpt_ids: impl IntoIterator, diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index f317a18916..48a1acd03b 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -250,7 +250,7 @@ impl FileFinder { match event { Event::Selected(project_path) => { workspace - .open_entry(project_path.clone(), cx) + .open_path(project_path.clone(), cx) .map(|d| d.detach()); workspace.dismiss_modal(cx); } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 9ced3fbeb0..8e3f004e8d 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -124,7 +124,7 @@ impl ProjectPanel { if let Some(worktree) = project.read(cx).worktree_for_id(worktree_id, cx) { if let Some(entry) = worktree.read(cx).entry_for_id(entry_id) { workspace - .open_entry( + .open_path( ProjectPath { worktree_id, path: entry.path.clone(), diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 6bb6da2157..3c60254b70 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -561,7 +561,7 @@ impl Workspace { let project_path = project_path.await.ok()?; if fs.is_file(&abs_path).await { if let Some(entry) = - this.update(&mut cx, |this, cx| this.open_entry(project_path, cx)) + this.update(&mut cx, |this, cx| this.open_path(project_path, cx)) { return Some(entry.await); } @@ -665,7 +665,7 @@ impl Workspace { } #[must_use] - pub fn open_entry( + pub fn open_path( &mut self, project_path: ProjectPath, cx: &mut ViewContext, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 0004668939..04e21fc58c 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -264,7 +264,7 @@ mod tests { // Open the first entry let entry_1 = workspace - .update(&mut cx, |w, cx| w.open_entry(file1.clone(), cx)) + .update(&mut cx, |w, cx| w.open_path(file1.clone(), cx)) .unwrap() .await .unwrap(); @@ -279,7 +279,7 @@ mod tests { // Open the second entry workspace - .update(&mut cx, |w, cx| w.open_entry(file2.clone(), cx)) + .update(&mut cx, |w, cx| w.open_path(file2.clone(), cx)) .unwrap() .await .unwrap(); @@ -294,7 +294,7 @@ mod tests { // Open the first entry again. The existing pane item is activated. let entry_1b = workspace - .update(&mut cx, |w, cx| w.open_entry(file1.clone(), cx).unwrap()) + .update(&mut cx, |w, cx| w.open_path(file1.clone(), cx).unwrap()) .await .unwrap(); assert_eq!(entry_1.id(), entry_1b.id()); @@ -312,7 +312,7 @@ mod tests { workspace .update(&mut cx, |w, cx| { w.split_pane(w.active_pane().clone(), SplitDirection::Right, cx); - w.open_entry(file2.clone(), cx).unwrap() + w.open_path(file2.clone(), cx).unwrap() }) .await .unwrap(); @@ -331,8 +331,8 @@ mod tests { // Open the third entry twice concurrently. Only one pane item is added. let (t1, t2) = workspace.update(&mut cx, |w, cx| { ( - w.open_entry(file3.clone(), cx).unwrap(), - w.open_entry(file3.clone(), cx).unwrap(), + w.open_path(file3.clone(), cx).unwrap(), + w.open_path(file3.clone(), cx).unwrap(), ) }); t1.await.unwrap(); @@ -562,7 +562,7 @@ mod tests { .update(&mut cx, |workspace, cx| { workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx); workspace - .open_entry( + .open_path( ProjectPath { worktree_id: worktree.read(cx).id(), path: Path::new("the-new-name.rs").into(), @@ -666,7 +666,7 @@ mod tests { let pane_1 = cx.read(|cx| workspace.read(cx).active_pane().clone()); workspace - .update(&mut cx, |w, cx| w.open_entry(file1.clone(), cx)) + .update(&mut cx, |w, cx| w.open_path(file1.clone(), cx)) .unwrap() .await .unwrap(); From 794d214eee1ff1e7018ec46c5ede54aba2c64b1d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 7 Jan 2022 17:38:37 +0100 Subject: [PATCH 34/43] Refactor opening workspace items Co-Authored-By: Nathan Sobo --- Cargo.lock | 2 + crates/diagnostics/src/diagnostics.rs | 55 +++--- crates/editor/src/editor.rs | 6 +- crates/editor/src/items.rs | 58 +++--- crates/file_finder/src/file_finder.rs | 8 +- crates/gpui/src/app.rs | 4 + crates/gpui/src/executor.rs | 15 +- crates/project_panel/Cargo.toml | 1 + crates/project_panel/src/project_panel.rs | 2 +- crates/workspace/Cargo.toml | 1 + crates/workspace/src/pane.rs | 111 +++++------ crates/workspace/src/workspace.rs | 226 +++++++++++----------- crates/zed/src/main.rs | 6 +- crates/zed/src/test.rs | 6 +- crates/zed/src/zed.rs | 37 ++-- 15 files changed, 281 insertions(+), 257 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7df97ed637..ac662eb09b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3502,6 +3502,7 @@ dependencies = [ "project", "serde_json", "theme", + "util", "workspace", ] @@ -5650,6 +5651,7 @@ dependencies = [ "anyhow", "client", "clock", + "collections", "gpui", "language", "log", diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index c008b10399..7576d495aa 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -43,7 +43,7 @@ struct ProjectDiagnostics { } struct ProjectDiagnosticsEditor { - project: ModelHandle, + model: ModelHandle, editor: ViewHandle, excerpts: ModelHandle, path_states: Vec<(Arc, Vec)>, @@ -109,10 +109,11 @@ impl View for ProjectDiagnosticsEditor { impl ProjectDiagnosticsEditor { fn new( - project: ModelHandle, + model: ModelHandle, settings: watch::Receiver, cx: &mut ViewContext, ) -> Self { + let project = model.read(cx).project.clone(); cx.subscribe(&project, |this, _, event, cx| match event { project::Event::DiskBasedDiagnosticsUpdated { worktree_id } => { if let Some(paths) = this.paths_to_update.remove(&worktree_id) { @@ -142,7 +143,7 @@ impl ProjectDiagnosticsEditor { .map(|e| e.0) .collect(); let this = Self { - project, + model, excerpts, editor, build_settings, @@ -161,7 +162,7 @@ impl ProjectDiagnosticsEditor { fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext) { let diagnostics = cx.add_model(|_| ProjectDiagnostics::new(workspace.project().clone())); - workspace.add_item(diagnostics, cx); + workspace.open_item(diagnostics, cx); } fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext) { @@ -183,7 +184,7 @@ impl ProjectDiagnosticsEditor { } fn update_excerpts(&self, paths: HashSet, cx: &mut ViewContext) { - let project = self.project.clone(); + let project = self.model.read(cx).project.clone(); cx.spawn(|this, mut cx| { async move { for path in paths { @@ -461,8 +462,7 @@ impl workspace::Item for ProjectDiagnostics { settings: watch::Receiver, cx: &mut ViewContext, ) -> Self::View { - let project = handle.read(cx).project.clone(); - ProjectDiagnosticsEditor::new(project, settings, cx) + ProjectDiagnosticsEditor::new(handle, settings, cx) } fn project_path(&self) -> Option { @@ -471,6 +471,12 @@ impl workspace::Item for ProjectDiagnostics { } impl workspace::ItemView for ProjectDiagnosticsEditor { + type ItemHandle = ModelHandle; + + fn item_handle(&self, _: &AppContext) -> Self::ItemHandle { + self.model.clone() + } + fn title(&self, _: &AppContext) -> String { "Project Diagnostics".to_string() } @@ -479,10 +485,26 @@ impl workspace::ItemView for ProjectDiagnosticsEditor { None } + fn is_dirty(&self, cx: &AppContext) -> bool { + self.excerpts.read(cx).read(cx).is_dirty() + } + + fn has_conflict(&self, cx: &AppContext) -> bool { + self.excerpts.read(cx).read(cx).has_conflict() + } + + fn can_save(&self, _: &AppContext) -> bool { + true + } + fn save(&mut self, cx: &mut ViewContext) -> Result>> { self.excerpts.update(cx, |excerpts, cx| excerpts.save(cx)) } + fn can_save_as(&self, _: &AppContext) -> bool { + false + } + fn save_as( &mut self, _: ModelHandle, @@ -492,28 +514,12 @@ impl workspace::ItemView for ProjectDiagnosticsEditor { unreachable!() } - fn is_dirty(&self, cx: &AppContext) -> bool { - self.excerpts.read(cx).read(cx).is_dirty() - } - - fn has_conflict(&self, cx: &AppContext) -> bool { - self.excerpts.read(cx).read(cx).has_conflict() - } - fn should_update_tab_on_event(event: &Event) -> bool { matches!( event, Event::Saved | Event::Dirtied | Event::FileHandleChanged ) } - - fn can_save(&self, _: &AppContext) -> bool { - true - } - - fn can_save_as(&self, _: &AppContext) -> bool { - false - } } fn compare_diagnostics( @@ -677,8 +683,9 @@ mod tests { .unwrap(); }); + let model = cx.add_model(|_| ProjectDiagnostics::new(project.clone())); let view = cx.add_view(Default::default(), |cx| { - ProjectDiagnosticsEditor::new(project.clone(), settings, cx) + ProjectDiagnosticsEditor::new(model, settings, cx) }); view.condition(&mut cx, |view, cx| view.text(cx).contains("fn main()")) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 7ec1a171fe..589525fd33 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -111,8 +111,8 @@ action!(FoldSelectedRanges); action!(Scroll, Vector2F); action!(Select, SelectPhase); -pub fn init(cx: &mut MutableAppContext, entry_openers: &mut Vec>) { - entry_openers.push(Box::new(items::BufferOpener)); +pub fn init(cx: &mut MutableAppContext, path_openers: &mut Vec>) { + path_openers.push(Box::new(items::BufferOpener)); cx.add_bindings(vec![ Binding::new("escape", Cancel, Some("Editor")), Binding::new("backspace", Backspace, Some("Editor")), @@ -525,7 +525,7 @@ impl Editor { Buffer::new(0, "", cx).with_language(Some(language::PLAIN_TEXT.clone()), None, cx) }); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - workspace.add_item(BufferItemHandle(buffer), cx); + workspace.open_item(BufferItemHandle(buffer), cx); } pub fn replica_id(&self, cx: &AppContext) -> ReplicaId { diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 29acf348a8..30c7bd80b8 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -71,6 +71,10 @@ impl ItemHandle for BufferItemHandle { path: f.path().clone(), }) } + + fn id(&self) -> usize { + self.0.id() + } } impl WeakItemHandle for WeakBufferItemHandle { @@ -79,22 +83,17 @@ impl WeakItemHandle for WeakBufferItemHandle { .upgrade(cx) .map(|buffer| Box::new(BufferItemHandle(buffer)) as Box) } + + fn id(&self) -> usize { + self.0.id() + } } impl ItemView for Editor { - fn should_activate_item_on_event(event: &Event) -> bool { - matches!(event, Event::Activate) - } + type ItemHandle = BufferItemHandle; - fn should_close_item_on_event(event: &Event) -> bool { - matches!(event, Event::Closed) - } - - fn should_update_tab_on_event(event: &Event) -> bool { - matches!( - event, - Event::Saved | Event::Dirtied | Event::FileHandleChanged - ) + fn item_handle(&self, cx: &AppContext) -> Self::ItemHandle { + BufferItemHandle(self.buffer.clone()) } fn title(&self, cx: &AppContext) -> String { @@ -124,6 +123,18 @@ impl ItemView for Editor { Some(self.clone(cx)) } + fn is_dirty(&self, cx: &AppContext) -> bool { + self.buffer().read(cx).read(cx).is_dirty() + } + + fn has_conflict(&self, cx: &AppContext) -> bool { + self.buffer().read(cx).read(cx).has_conflict() + } + + fn can_save(&self, cx: &AppContext) -> bool { + self.project_path(cx).is_some() + } + fn save(&mut self, cx: &mut ViewContext) -> Result>> { let save = self.buffer().update(cx, |b, cx| b.save(cx))?; Ok(cx.spawn(|_, _| async move { @@ -132,6 +143,10 @@ impl ItemView for Editor { })) } + fn can_save_as(&self, _: &AppContext) -> bool { + true + } + fn save_as( &mut self, worktree: ModelHandle, @@ -180,20 +195,19 @@ impl ItemView for Editor { }) } - fn is_dirty(&self, cx: &AppContext) -> bool { - self.buffer().read(cx).read(cx).is_dirty() + fn should_activate_item_on_event(event: &Event) -> bool { + matches!(event, Event::Activate) } - fn has_conflict(&self, cx: &AppContext) -> bool { - self.buffer().read(cx).read(cx).has_conflict() + fn should_close_item_on_event(event: &Event) -> bool { + matches!(event, Event::Closed) } - fn can_save(&self, cx: &AppContext) -> bool { - self.project_path(cx).is_some() - } - - fn can_save_as(&self, _: &AppContext) -> bool { - true + fn should_update_tab_on_event(event: &Event) -> bool { + matches!( + event, + Event::Saved | Event::Dirtied | Event::FileHandleChanged + ) } } diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 48a1acd03b..707a3bfb20 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -251,7 +251,7 @@ impl FileFinder { Event::Selected(project_path) => { workspace .open_path(project_path.clone(), cx) - .map(|d| d.detach()); + .detach_and_log_err(cx); workspace.dismiss_modal(cx); } Event::Dismissed => { @@ -431,14 +431,14 @@ mod tests { #[gpui::test] async fn test_matching_paths(mut cx: gpui::TestAppContext) { - let mut entry_openers = Vec::new(); + let mut path_openers = Vec::new(); cx.update(|cx| { super::init(cx); - editor::init(cx, &mut entry_openers); + editor::init(cx, &mut path_openers); }); let mut params = cx.update(WorkspaceParams::test); - params.entry_openers = Arc::from(entry_openers); + params.path_openers = Arc::from(path_openers); params .fs .as_fake() diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index e57bcbc764..82ec8fbcea 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -2773,6 +2773,10 @@ impl WeakModelHandle { } } + pub fn id(&self) -> usize { + self.model_id + } + pub fn upgrade(self, cx: &impl UpgradeModelHandle) -> Option> { cx.upgrade_model_handle(self) } diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index 596b1be184..84efd2c6e0 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -7,7 +7,7 @@ use rand::prelude::*; use smol::{channel, prelude::*, Executor, Timer}; use std::{ any::Any, - fmt::{self, Debug}, + fmt::{self, Debug, Display}, marker::PhantomData, mem, ops::RangeInclusive, @@ -25,7 +25,7 @@ use waker_fn::waker_fn; use crate::{ platform::{self, Dispatcher}, - util, + util, MutableAppContext, }; pub enum Foreground { @@ -682,6 +682,17 @@ impl Task { } } +impl Task> { + pub fn detach_and_log_err(self, cx: &mut MutableAppContext) { + cx.spawn(|_| async move { + if let Err(err) = self.await { + log::error!("{}", err); + } + }) + .detach(); + } +} + impl Task { fn send(any_task: AnyTask) -> Self { Self::Send { diff --git a/crates/project_panel/Cargo.toml b/crates/project_panel/Cargo.toml index eb9d76eaa9..983b4b40fe 100644 --- a/crates/project_panel/Cargo.toml +++ b/crates/project_panel/Cargo.toml @@ -10,6 +10,7 @@ path = "src/project_panel.rs" gpui = { path = "../gpui" } project = { path = "../project" } theme = { path = "../theme" } +util = { path = "../util" } workspace = { path = "../workspace" } postage = { version = "0.4.1", features = ["futures-traits"] } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 8e3f004e8d..1ff1a33172 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -131,7 +131,7 @@ impl ProjectPanel { }, cx, ) - .map(|t| t.detach()); + .detach_and_log_err(cx); } } } diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 759f115a35..e85fefdabd 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -12,6 +12,7 @@ test-support = ["client/test-support", "project/test-support"] [dependencies] client = { path = "../client" } clock = { path = "../clock" } +collections = { path = "../collections" } gpui = { path = "../gpui" } language = { path = "../language" } project = { path = "../project" } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 2ef586a8fb..c8b2c5f868 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1,15 +1,14 @@ use super::{ItemViewHandle, SplitDirection}; -use crate::Settings; +use crate::{ItemHandle, Settings}; use gpui::{ action, elements::*, geometry::{rect::RectF, vector::vec2f}, keymap::Binding, platform::CursorStyle, - Entity, MutableAppContext, Quad, RenderContext, View, ViewContext, ViewHandle, + Entity, MutableAppContext, Quad, RenderContext, View, ViewContext, }; use postage::watch; -use project::ProjectPath; use std::cmp; action!(Split, SplitDirection); @@ -70,7 +69,7 @@ pub struct TabState { } pub struct Pane { - items: Vec>, + item_views: Vec>, active_item: usize, settings: watch::Receiver, } @@ -78,7 +77,7 @@ pub struct Pane { impl Pane { pub fn new(settings: watch::Receiver) -> Self { Self { - items: Vec::new(), + item_views: Vec::new(), active_item: 0, settings, } @@ -88,43 +87,53 @@ impl Pane { cx.emit(Event::Activate); } - pub fn add_item(&mut self, item: Box, cx: &mut ViewContext) -> usize { - let item_idx = cmp::min(self.active_item + 1, self.items.len()); - self.items.insert(item_idx, item); - cx.notify(); - item_idx + pub fn open_item( + &mut self, + item_handle: T, + cx: &mut ViewContext, + ) -> Box + where + T: 'static + ItemHandle, + { + for (ix, item_view) in self.item_views.iter().enumerate() { + if item_view.item_handle(cx).id() == item_handle.id() { + let item_view = item_view.boxed_clone(); + self.activate_item(ix, cx); + return item_view; + } + } + + let item_view = item_handle.add_view(cx.window_id(), self.settings.clone(), cx); + self.add_item_view(item_view.boxed_clone(), cx); + item_view } - pub fn items(&self) -> &[Box] { - &self.items + pub fn add_item_view( + &mut self, + item_view: Box, + cx: &mut ViewContext, + ) { + item_view.added_to_pane(cx); + let item_idx = cmp::min(self.active_item + 1, self.item_views.len()); + self.item_views.insert(item_idx, item_view); + self.activate_item(item_idx, cx); + cx.notify(); + } + + pub fn item_views(&self) -> &[Box] { + &self.item_views } pub fn active_item(&self) -> Option> { - self.items.get(self.active_item).cloned() - } - - pub fn activate_entry( - &mut self, - project_path: ProjectPath, - cx: &mut ViewContext, - ) -> Option> { - if let Some(index) = self.items.iter().position(|item| { - item.project_path(cx.as_ref()) - .map_or(false, |item_path| item_path == project_path) - }) { - self.activate_item(index, cx); - self.items.get(index).map(|handle| handle.boxed_clone()) - } else { - None - } + self.item_views.get(self.active_item).cloned() } pub fn item_index(&self, item: &dyn ItemViewHandle) -> Option { - self.items.iter().position(|i| i.id() == item.id()) + self.item_views.iter().position(|i| i.id() == item.id()) } pub fn activate_item(&mut self, index: usize, cx: &mut ViewContext) { - if index < self.items.len() { + if index < self.item_views.len() { self.active_item = index; self.focus_active_item(cx); cx.notify(); @@ -134,15 +143,15 @@ impl Pane { pub fn activate_prev_item(&mut self, cx: &mut ViewContext) { if self.active_item > 0 { self.active_item -= 1; - } else if self.items.len() > 0 { - self.active_item = self.items.len() - 1; + } else if self.item_views.len() > 0 { + self.active_item = self.item_views.len() - 1; } self.focus_active_item(cx); cx.notify(); } pub fn activate_next_item(&mut self, cx: &mut ViewContext) { - if self.active_item + 1 < self.items.len() { + if self.active_item + 1 < self.item_views.len() { self.active_item += 1; } else { self.active_item = 0; @@ -152,15 +161,15 @@ impl Pane { } pub fn close_active_item(&mut self, cx: &mut ViewContext) { - if !self.items.is_empty() { - self.close_item(self.items[self.active_item].id(), cx) + if !self.item_views.is_empty() { + self.close_item(self.item_views[self.active_item].id(), cx) } } pub fn close_item(&mut self, item_id: usize, cx: &mut ViewContext) { - self.items.retain(|item| item.id() != item_id); - self.active_item = cmp::min(self.active_item, self.items.len().saturating_sub(1)); - if self.items.is_empty() { + self.item_views.retain(|item| item.id() != item_id); + self.active_item = cmp::min(self.active_item, self.item_views.len().saturating_sub(1)); + if self.item_views.is_empty() { cx.emit(Event::Remove); } cx.notify(); @@ -183,11 +192,11 @@ impl Pane { enum Tabs {} let tabs = MouseEventHandler::new::(cx.view_id(), cx, |mouse_state, cx| { let mut row = Flex::row(); - for (ix, item) in self.items.iter().enumerate() { + for (ix, item_view) in self.item_views.iter().enumerate() { let is_active = ix == self.active_item; row.add_child({ - let mut title = item.title(cx); + let mut title = item_view.title(cx); if title.len() > MAX_TAB_TITLE_LEN { let mut truncated_len = MAX_TAB_TITLE_LEN; while !title.is_char_boundary(truncated_len) { @@ -212,9 +221,9 @@ impl Pane { .with_child( Align::new({ let diameter = 7.0; - let icon_color = if item.has_conflict(cx) { + let icon_color = if item_view.has_conflict(cx) { Some(style.icon_conflict) - } else if item.is_dirty(cx) { + } else if item_view.is_dirty(cx) { Some(style.icon_dirty) } else { None @@ -271,7 +280,7 @@ impl Pane { .with_child( Align::new( ConstrainedBox::new(if mouse_state.hovered { - let item_id = item.id(); + let item_id = item_view.id(); enum TabCloseButton {} let icon = Svg::new("icons/x.svg"); MouseEventHandler::new::( @@ -354,17 +363,3 @@ impl View for Pane { self.focus_active_item(cx); } } - -pub trait PaneHandle { - fn add_item_view(&self, item: Box, cx: &mut MutableAppContext); -} - -impl PaneHandle for ViewHandle { - fn add_item_view(&self, item: Box, cx: &mut MutableAppContext) { - item.set_parent_pane(self, cx); - self.update(cx, |pane, cx| { - let item_idx = pane.add_item(item, cx); - pane.activate_item(item_idx, cx); - }); - } -} diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 3c60254b70..1ff9d6aa39 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -7,6 +7,7 @@ mod status_bar; use anyhow::{anyhow, Result}; use client::{Authenticate, ChannelList, Client, User, UserStore}; use clock::ReplicaId; +use collections::HashSet; use gpui::{ action, color::Color, @@ -32,6 +33,7 @@ use status_bar::StatusBar; pub use status_bar::StatusItemView; use std::{ future::Future, + hash::{Hash, Hasher}, path::{Path, PathBuf}, sync::Arc, }; @@ -94,7 +96,7 @@ pub struct AppState { pub user_store: ModelHandle, pub fs: Arc, pub channel_list: ModelHandle, - pub entry_openers: Arc<[Box]>, + pub path_openers: Arc<[Box]>, pub build_window_options: &'static dyn Fn() -> WindowOptions<'static>, pub build_workspace: &'static dyn Fn( ModelHandle, @@ -137,6 +139,9 @@ pub trait Item: Entity + Sized { } pub trait ItemView: View { + type ItemHandle: ItemHandle; + + fn item_handle(&self, cx: &AppContext) -> Self::ItemHandle; fn title(&self, cx: &AppContext) -> String; fn project_path(&self, cx: &AppContext) -> Option; fn clone_on_split(&self, _: &mut ViewContext) -> Option @@ -172,6 +177,7 @@ pub trait ItemView: View { } pub trait ItemHandle: Send + Sync { + fn id(&self) -> usize; fn add_view( &self, window_id: usize, @@ -184,15 +190,17 @@ pub trait ItemHandle: Send + Sync { } pub trait WeakItemHandle { + fn id(&self) -> usize; fn upgrade(&self, cx: &AppContext) -> Option>; } pub trait ItemViewHandle { + fn item_handle(&self, cx: &AppContext) -> Box; fn title(&self, cx: &AppContext) -> String; fn project_path(&self, cx: &AppContext) -> Option; fn boxed_clone(&self) -> Box; fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option>; - fn set_parent_pane(&self, pane: &ViewHandle, cx: &mut MutableAppContext); + fn added_to_pane(&self, cx: &mut ViewContext); fn id(&self) -> usize; fn to_any(&self) -> AnyViewHandle; fn is_dirty(&self, cx: &AppContext) -> bool; @@ -209,6 +217,10 @@ pub trait ItemViewHandle { } impl ItemHandle for ModelHandle { + fn id(&self) -> usize { + self.id() + } + fn add_view( &self, window_id: usize, @@ -232,6 +244,10 @@ impl ItemHandle for ModelHandle { } impl ItemHandle for Box { + fn id(&self) -> usize { + ItemHandle::id(self.as_ref()) + } + fn add_view( &self, window_id: usize, @@ -255,12 +271,34 @@ impl ItemHandle for Box { } impl WeakItemHandle for WeakModelHandle { + fn id(&self) -> usize { + WeakModelHandle::id(self) + } + fn upgrade(&self, cx: &AppContext) -> Option> { WeakModelHandle::::upgrade(*self, cx).map(|i| Box::new(i) as Box) } } +impl Hash for Box { + fn hash(&self, state: &mut H) { + self.id().hash(state); + } +} + +impl PartialEq for Box { + fn eq(&self, other: &Self) -> bool { + self.id() == other.id() + } +} + +impl Eq for Box {} + impl ItemViewHandle for ViewHandle { + fn item_handle(&self, cx: &AppContext) -> Box { + Box::new(self.read(cx).item_handle(cx)) + } + fn title(&self, cx: &AppContext) -> String { self.read(cx).title(cx) } @@ -280,25 +318,23 @@ impl ItemViewHandle for ViewHandle { .map(|handle| Box::new(handle) as Box) } - fn set_parent_pane(&self, pane: &ViewHandle, cx: &mut MutableAppContext) { - pane.update(cx, |_, cx| { - cx.subscribe(self, |pane, item, event, cx| { - if T::should_close_item_on_event(event) { - pane.close_item(item.id(), cx); - return; + fn added_to_pane(&self, cx: &mut ViewContext) { + cx.subscribe(self, |pane, item, event, cx| { + if T::should_close_item_on_event(event) { + pane.close_item(item.id(), cx); + return; + } + if T::should_activate_item_on_event(event) { + if let Some(ix) = pane.item_index(&item) { + pane.activate_item(ix, cx); + pane.activate(cx); } - if T::should_activate_item_on_event(event) { - if let Some(ix) = pane.item_index(&item) { - pane.activate_item(ix, cx); - pane.activate(cx); - } - } - if T::should_update_tab_on_event(event) { - cx.notify() - } - }) - .detach(); - }); + } + if T::should_update_tab_on_event(event) { + cx.notify() + } + }) + .detach(); } fn save(&self, cx: &mut MutableAppContext) -> Result>> { @@ -360,7 +396,7 @@ pub struct WorkspaceParams { pub settings: watch::Receiver, pub user_store: ModelHandle, pub channel_list: ModelHandle, - pub entry_openers: Arc<[Box]>, + pub path_openers: Arc<[Box]>, } impl WorkspaceParams { @@ -392,7 +428,7 @@ impl WorkspaceParams { languages, settings: watch::channel_with(settings).1, user_store, - entry_openers: Arc::from([]), + path_openers: Arc::from([]), } } @@ -412,7 +448,7 @@ impl WorkspaceParams { settings: app_state.settings.clone(), user_store: app_state.user_store.clone(), channel_list: app_state.channel_list.clone(), - entry_openers: app_state.entry_openers.clone(), + path_openers: app_state.path_openers.clone(), } } } @@ -430,8 +466,8 @@ pub struct Workspace { active_pane: ViewHandle, status_bar: ViewHandle, project: ModelHandle, - entry_openers: Arc<[Box]>, - items: Vec>, + path_openers: Arc<[Box]>, + items: HashSet>, _observe_current_user: Task<()>, } @@ -484,7 +520,7 @@ impl Workspace { left_sidebar: Sidebar::new(Side::Left), right_sidebar: Sidebar::new(Side::Right), project: params.project.clone(), - entry_openers: params.entry_openers.clone(), + path_openers: params.path_openers.clone(), items: Default::default(), _observe_current_user, } @@ -560,13 +596,13 @@ impl Workspace { async move { let project_path = project_path.await.ok()?; if fs.is_file(&abs_path).await { - if let Some(entry) = + Some( this.update(&mut cx, |this, cx| this.open_path(project_path, cx)) - { - return Some(entry.await); - } + .await, + ) + } else { + None } - None } }) }) @@ -667,102 +703,51 @@ impl Workspace { #[must_use] pub fn open_path( &mut self, - project_path: ProjectPath, + path: ProjectPath, cx: &mut ViewContext, - ) -> Option, Arc>>> { - let pane = self.active_pane().clone(); - if let Some(existing_item) = - self.activate_or_open_existing_entry(project_path.clone(), &pane, cx) - { - return Some(cx.foreground().spawn(async move { Ok(existing_item) })); + ) -> Task, Arc>> { + if let Some(existing_item) = self.item_for_path(&path, cx) { + return Task::ready(Ok(self.open_item(existing_item, cx))); } - let worktree = match self - .project - .read(cx) - .worktree_for_id(project_path.worktree_id, cx) - { + let worktree = match self.project.read(cx).worktree_for_id(path.worktree_id, cx) { Some(worktree) => worktree, None => { - log::error!("worktree {} does not exist", project_path.worktree_id); - return None; + return Task::ready(Err(Arc::new(anyhow!( + "worktree {} does not exist", + path.worktree_id + )))); } }; - let project_path = project_path.clone(); - let entry_openers = self.entry_openers.clone(); - let task = worktree.update(cx, |worktree, cx| { - for opener in entry_openers.iter() { + let project_path = path.clone(); + let path_openers = self.path_openers.clone(); + let open_task = worktree.update(cx, |worktree, cx| { + for opener in path_openers.iter() { if let Some(task) = opener.open(worktree, project_path.clone(), cx) { - return Some(task); + return task; } } - log::error!("no opener for path {:?} found", project_path); - None - })?; + Task::ready(Err(anyhow!("no opener found for path {:?}", project_path))) + }); - let pane = pane.downgrade(); - Some(cx.spawn(|this, mut cx| async move { - let load_result = task.await; + let pane = self.active_pane().clone().downgrade(); + cx.spawn(|this, mut cx| async move { + let item = open_task.await?; this.update(&mut cx, |this, cx| { let pane = pane .upgrade(&cx) .ok_or_else(|| anyhow!("could not upgrade pane reference"))?; - let item = load_result?; - - // By the time loading finishes, the entry could have been already added - // to the pane. If it was, we activate it, otherwise we'll store the - // item and add a new view for it. - if let Some(existing) = - this.activate_or_open_existing_entry(project_path, &pane, cx) - { - Ok(existing) - } else { - Ok(this.add_item(item, cx)) - } + Ok(this.open_item_in_pane(item, &pane, cx)) }) - })) + }) } - fn activate_or_open_existing_entry( - &mut self, - project_path: ProjectPath, - pane: &ViewHandle, - cx: &mut ViewContext, - ) -> Option> { - // If the pane contains a view for this file, then activate - // that item view. - if let Some(existing_item_view) = - pane.update(cx, |pane, cx| pane.activate_entry(project_path.clone(), cx)) - { - return Some(existing_item_view); - } - - // Otherwise, if this file is already open somewhere in the workspace, - // then add another view for it. - let settings = self.settings.clone(); - let mut view_for_existing_item = None; - self.items.retain(|item| { - if let Some(item) = item.upgrade(cx) { - if view_for_existing_item.is_none() - && item - .project_path(cx) - .map_or(false, |item_project_path| item_project_path == project_path) - { - view_for_existing_item = - Some(item.add_view(cx.window_id(), settings.clone(), cx.as_mut())); - } - true - } else { - false - } - }); - if let Some(view) = view_for_existing_item { - pane.add_item_view(view.boxed_clone(), cx.as_mut()); - Some(view) - } else { - None - } + fn item_for_path(&self, path: &ProjectPath, cx: &AppContext) -> Option> { + self.items + .iter() + .filter_map(|i| i.upgrade(cx)) + .find(|i| i.project_path(cx).as_ref() == Some(path)) } pub fn active_item(&self, cx: &AppContext) -> Option> { @@ -908,19 +893,28 @@ impl Workspace { pane } - pub fn add_item( + pub fn open_item( &mut self, item_handle: T, cx: &mut ViewContext, ) -> Box where - T: ItemHandle, + T: 'static + ItemHandle, { - let view = item_handle.add_view(cx.window_id(), self.settings.clone(), cx); - self.items.push(item_handle.downgrade()); - self.active_pane() - .add_item_view(view.boxed_clone(), cx.as_mut()); - view + self.open_item_in_pane(item_handle, &self.active_pane().clone(), cx) + } + + pub fn open_item_in_pane( + &mut self, + item_handle: T, + pane: &ViewHandle, + cx: &mut ViewContext, + ) -> Box + where + T: 'static + ItemHandle, + { + self.items.insert(item_handle.downgrade()); + pane.update(cx, |pane, cx| pane.open_item(item_handle, cx)) } fn activate_pane(&mut self, pane: ViewHandle, cx: &mut ViewContext) { @@ -965,7 +959,7 @@ impl Workspace { self.activate_pane(new_pane.clone(), cx); if let Some(item) = pane.read(cx).active_item() { if let Some(clone) = item.clone_on_split(cx.as_mut()) { - new_pane.add_item_view(clone, cx.as_mut()); + new_pane.update(cx, |new_pane, cx| new_pane.add_item_view(clone, cx)); } } self.center diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 09b53603df..a06610967e 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -51,11 +51,11 @@ fn main() { let http = http::client(); let client = client::Client::new(http.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); - let mut entry_openers = Vec::new(); + let mut path_openers = Vec::new(); client::init(client.clone(), cx); workspace::init(cx); - editor::init(cx, &mut entry_openers); + editor::init(cx, &mut path_openers); go_to_line::init(cx); file_finder::init(cx); chat_panel::init(cx); @@ -72,7 +72,7 @@ fn main() { client, user_store, fs: Arc::new(RealFs), - entry_openers: Arc::from(entry_openers), + path_openers: Arc::from(path_openers), build_window_options: &build_window_options, build_workspace: &build_workspace, }); diff --git a/crates/zed/src/test.rs b/crates/zed/src/test.rs index c56e24dd1c..4f685415d0 100644 --- a/crates/zed/src/test.rs +++ b/crates/zed/src/test.rs @@ -16,8 +16,8 @@ fn init_logger() { } pub fn test_app_state(cx: &mut MutableAppContext) -> Arc { - let mut entry_openers = Vec::new(); - editor::init(cx, &mut entry_openers); + let mut path_openers = Vec::new(); + editor::init(cx, &mut path_openers); let (settings_tx, settings) = watch::channel_with(build_settings(cx)); let themes = ThemeRegistry::new(Assets, cx.font_cache().clone()); let http = FakeHttpClient::with_404_response(); @@ -41,7 +41,7 @@ pub fn test_app_state(cx: &mut MutableAppContext) -> Arc { client, user_store, fs: Arc::new(FakeFs::new()), - entry_openers: Arc::from(entry_openers), + path_openers: Arc::from(path_openers), build_window_options: &build_window_options, build_workspace: &build_workspace, }) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 04e21fc58c..d2da4fdd53 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -62,7 +62,7 @@ pub fn build_workspace( settings: app_state.settings.clone(), user_store: app_state.user_store.clone(), channel_list: app_state.channel_list.clone(), - entry_openers: app_state.entry_openers.clone(), + path_openers: app_state.path_openers.clone(), }; let mut workspace = Workspace::new(&workspace_params, cx); let project = workspace.project().clone(); @@ -265,7 +265,6 @@ mod tests { // Open the first entry let entry_1 = workspace .update(&mut cx, |w, cx| w.open_path(file1.clone(), cx)) - .unwrap() .await .unwrap(); cx.read(|cx| { @@ -274,13 +273,12 @@ mod tests { pane.active_item().unwrap().project_path(cx), Some(file1.clone()) ); - assert_eq!(pane.items().len(), 1); + assert_eq!(pane.item_views().len(), 1); }); // Open the second entry workspace .update(&mut cx, |w, cx| w.open_path(file2.clone(), cx)) - .unwrap() .await .unwrap(); cx.read(|cx| { @@ -289,12 +287,12 @@ mod tests { pane.active_item().unwrap().project_path(cx), Some(file2.clone()) ); - assert_eq!(pane.items().len(), 2); + assert_eq!(pane.item_views().len(), 2); }); // Open the first entry again. The existing pane item is activated. let entry_1b = workspace - .update(&mut cx, |w, cx| w.open_path(file1.clone(), cx).unwrap()) + .update(&mut cx, |w, cx| w.open_path(file1.clone(), cx)) .await .unwrap(); assert_eq!(entry_1.id(), entry_1b.id()); @@ -305,14 +303,14 @@ mod tests { pane.active_item().unwrap().project_path(cx), Some(file1.clone()) ); - assert_eq!(pane.items().len(), 2); + assert_eq!(pane.item_views().len(), 2); }); // Split the pane with the first entry, then open the second entry again. workspace .update(&mut cx, |w, cx| { w.split_pane(w.active_pane().clone(), SplitDirection::Right, cx); - w.open_path(file2.clone(), cx).unwrap() + w.open_path(file2.clone(), cx) }) .await .unwrap(); @@ -331,8 +329,8 @@ mod tests { // Open the third entry twice concurrently. Only one pane item is added. let (t1, t2) = workspace.update(&mut cx, |w, cx| { ( - w.open_path(file3.clone(), cx).unwrap(), - w.open_path(file3.clone(), cx).unwrap(), + w.open_path(file3.clone(), cx), + w.open_path(file3.clone(), cx), ) }); t1.await.unwrap(); @@ -344,7 +342,7 @@ mod tests { Some(file3.clone()) ); let pane_entries = pane - .items() + .item_views() .iter() .map(|i| i.project_path(cx).unwrap()) .collect::>(); @@ -561,15 +559,13 @@ mod tests { workspace .update(&mut cx, |workspace, cx| { workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx); - workspace - .open_path( - ProjectPath { - worktree_id: worktree.read(cx).id(), - path: Path::new("the-new-name.rs").into(), - }, - cx, - ) - .unwrap() + workspace.open_path( + ProjectPath { + worktree_id: worktree.read(cx).id(), + path: Path::new("the-new-name.rs").into(), + }, + cx, + ) }) .await .unwrap(); @@ -667,7 +663,6 @@ mod tests { workspace .update(&mut cx, |w, cx| w.open_path(file1.clone(), cx)) - .unwrap() .await .unwrap(); cx.read(|cx| { From e5c520a2652be1ba4deca0850a1586db3c509a8b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 7 Jan 2022 17:48:31 +0100 Subject: [PATCH 35/43] Use `Buffer` handles instead of `MultiBuffer` as editor workspace items Co-Authored-By: Nathan Sobo --- crates/editor/src/editor.rs | 5 ++--- crates/editor/src/items.rs | 26 +++++++++++--------------- crates/workspace/src/workspace.rs | 8 ++++---- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 589525fd33..f7babdf8af 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -46,7 +46,7 @@ use sum_tree::Bias; use text::rope::TextDimension; use theme::{DiagnosticStyle, EditorStyle}; use util::post_inc; -use workspace::{EntryOpener, Workspace}; +use workspace::{PathOpener, Workspace}; const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); const MAX_LINE_LEN: usize = 1024; @@ -111,7 +111,7 @@ action!(FoldSelectedRanges); action!(Scroll, Vector2F); action!(Select, SelectPhase); -pub fn init(cx: &mut MutableAppContext, path_openers: &mut Vec>) { +pub fn init(cx: &mut MutableAppContext, path_openers: &mut Vec>) { path_openers.push(Box::new(items::BufferOpener)); cx.add_bindings(vec![ Binding::new("escape", Cancel, Some("Editor")), @@ -524,7 +524,6 @@ impl Editor { let buffer = cx.add_model(|cx| { Buffer::new(0, "", cx).with_language(Some(language::PLAIN_TEXT.clone()), None, cx) }); - let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); workspace.open_item(BufferItemHandle(buffer), cx); } diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 30c7bd80b8..57615073f7 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -5,25 +5,25 @@ use gpui::{ elements::*, AppContext, Entity, ModelContext, ModelHandle, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakModelHandle, }; -use language::{Diagnostic, File as _}; +use language::{Buffer, Diagnostic, File as _}; use postage::watch; use project::{File, ProjectPath, Worktree}; use std::fmt::Write; use std::path::Path; use text::{Point, Selection}; use workspace::{ - EntryOpener, ItemHandle, ItemView, ItemViewHandle, Settings, StatusItemView, WeakItemHandle, + ItemHandle, ItemView, ItemViewHandle, PathOpener, Settings, StatusItemView, WeakItemHandle, }; pub struct BufferOpener; #[derive(Clone)] -pub struct BufferItemHandle(pub ModelHandle); +pub struct BufferItemHandle(pub ModelHandle); #[derive(Clone)] -struct WeakBufferItemHandle(WeakModelHandle); +struct WeakBufferItemHandle(WeakModelHandle); -impl EntryOpener for BufferOpener { +impl PathOpener for BufferOpener { fn open( &self, worktree: &mut Worktree, @@ -31,9 +31,8 @@ impl EntryOpener for BufferOpener { cx: &mut ModelContext, ) -> Option>>> { let buffer = worktree.open_buffer(project_path.path, cx); - let task = cx.spawn(|_, mut cx| async move { + let task = cx.spawn(|_, _| async move { let buffer = buffer.await?; - let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); Ok(Box::new(BufferItemHandle(buffer)) as Box) }); Some(task) @@ -47,13 +46,10 @@ impl ItemHandle for BufferItemHandle { settings: watch::Receiver, cx: &mut MutableAppContext, ) -> Box { - let buffer = self.0.downgrade(); + let buffer = cx.add_model(|cx| MultiBuffer::singleton(self.0.clone(), cx)); + let weak_buffer = buffer.downgrade(); Box::new(cx.add_view(window_id, |cx| { - Editor::for_buffer( - self.0.clone(), - crate::settings_builder(buffer, settings), - cx, - ) + Editor::for_buffer(buffer, crate::settings_builder(weak_buffer, settings), cx) })) } @@ -66,7 +62,7 @@ impl ItemHandle for BufferItemHandle { } fn project_path(&self, cx: &AppContext) -> Option { - File::from_dyn(self.0.read(cx).file(cx)).map(|f| ProjectPath { + File::from_dyn(self.0.read(cx).file()).map(|f| ProjectPath { worktree_id: f.worktree_id(cx), path: f.path().clone(), }) @@ -93,7 +89,7 @@ impl ItemView for Editor { type ItemHandle = BufferItemHandle; fn item_handle(&self, cx: &AppContext) -> Self::ItemHandle { - BufferItemHandle(self.buffer.clone()) + BufferItemHandle(self.buffer.read(cx).as_singleton().unwrap()) } fn title(&self, cx: &AppContext) -> String { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 1ff9d6aa39..055fa1169d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -96,7 +96,7 @@ pub struct AppState { pub user_store: ModelHandle, pub fs: Arc, pub channel_list: ModelHandle, - pub path_openers: Arc<[Box]>, + pub path_openers: Arc<[Box]>, pub build_window_options: &'static dyn Fn() -> WindowOptions<'static>, pub build_workspace: &'static dyn Fn( ModelHandle, @@ -117,7 +117,7 @@ pub struct JoinProjectParams { pub app_state: Arc, } -pub trait EntryOpener { +pub trait PathOpener { fn open( &self, worktree: &mut Worktree, @@ -396,7 +396,7 @@ pub struct WorkspaceParams { pub settings: watch::Receiver, pub user_store: ModelHandle, pub channel_list: ModelHandle, - pub path_openers: Arc<[Box]>, + pub path_openers: Arc<[Box]>, } impl WorkspaceParams { @@ -466,7 +466,7 @@ pub struct Workspace { active_pane: ViewHandle, status_bar: ViewHandle, project: ModelHandle, - path_openers: Arc<[Box]>, + path_openers: Arc<[Box]>, items: HashSet>, _observe_current_user: Task<()>, } From ea263822fad2fb76ea622fe4ed2b55e4afd807d8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 7 Jan 2022 09:59:27 -0800 Subject: [PATCH 36/43] Finish implementing ProjectDiagnostics::open_excerpts * Build workspace item views with a reference to the workspace * Add randomized test for MultiBuffer::excerpted_buffers and fix a small bug Co-Authored-By: Nathan Sobo --- crates/diagnostics/src/diagnostics.rs | 66 ++++++++++++++++++--------- crates/editor/src/items.rs | 9 +++- crates/editor/src/multi_buffer.rs | 46 ++++++++++++++----- crates/workspace/src/pane.rs | 5 +- crates/workspace/src/workspace.rs | 26 +++++++---- 5 files changed, 107 insertions(+), 45 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 7576d495aa..fd710d596b 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -5,16 +5,17 @@ use collections::{HashMap, HashSet}; use editor::{ context_header_renderer, diagnostic_block_renderer, diagnostic_header_renderer, display_map::{BlockDisposition, BlockId, BlockProperties}, - BuildSettings, Editor, ExcerptId, ExcerptProperties, MultiBuffer, + items::BufferItemHandle, + Autoscroll, BuildSettings, Editor, ExcerptId, ExcerptProperties, MultiBuffer, }; use gpui::{ action, elements::*, keymap::Binding, AppContext, Entity, ModelHandle, MutableAppContext, - RenderContext, Task, View, ViewContext, ViewHandle, + RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use language::{Bias, Buffer, Diagnostic, DiagnosticEntry, Point, Selection, SelectionGoal}; use postage::watch; use project::{Project, ProjectPath, WorktreeId}; -use std::{cmp::Ordering, ops::Range, path::Path, sync::Arc}; +use std::{cmp::Ordering, mem, ops::Range, path::Path, sync::Arc}; use util::TryFutureExt; use workspace::Workspace; @@ -44,6 +45,7 @@ struct ProjectDiagnostics { struct ProjectDiagnosticsEditor { model: ModelHandle, + workspace: WeakViewHandle, editor: ViewHandle, excerpts: ModelHandle, path_states: Vec<(Arc, Vec)>, @@ -110,6 +112,7 @@ impl View for ProjectDiagnosticsEditor { impl ProjectDiagnosticsEditor { fn new( model: ModelHandle, + workspace: WeakViewHandle, settings: watch::Receiver, cx: &mut ViewContext, ) -> Self { @@ -144,6 +147,7 @@ impl ProjectDiagnosticsEditor { .collect(); let this = Self { model, + workspace, excerpts, editor, build_settings, @@ -166,20 +170,37 @@ impl ProjectDiagnosticsEditor { } fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext) { - let editor = self.editor.read(cx); - let excerpts = self.excerpts.read(cx); - let mut new_selections_by_buffer = HashMap::default(); - for selection in editor.local_selections::(cx) { - for (buffer, range) in excerpts.excerpted_buffers(selection.start..selection.end, cx) { - new_selections_by_buffer - .entry(buffer) - .or_insert(Vec::new()) - .push((range.start, range.end, selection.reversed)) - } - } + if let Some(workspace) = self.workspace.upgrade(cx) { + let editor = self.editor.read(cx); + let excerpts = self.excerpts.read(cx); + let mut new_selections_by_buffer = HashMap::default(); - for (buffer, selections) in new_selections_by_buffer { - // buffer.read(cx). + for selection in editor.local_selections::(cx) { + for (buffer, mut range) in + excerpts.excerpted_buffers(selection.start..selection.end, cx) + { + if selection.reversed { + mem::swap(&mut range.start, &mut range.end); + } + new_selections_by_buffer + .entry(buffer) + .or_insert(Vec::new()) + .push(range) + } + } + + workspace.update(cx, |workspace, cx| { + for (buffer, ranges) in new_selections_by_buffer { + let editor = workspace + .open_item(BufferItemHandle(buffer), cx) + .to_any() + .downcast::() + .unwrap(); + editor.update(cx, |editor, cx| { + editor.select_ranges(ranges, Some(Autoscroll::Center), cx) + }); + } + }); } } @@ -459,10 +480,10 @@ impl workspace::Item for ProjectDiagnostics { fn build_view( handle: ModelHandle, - settings: watch::Receiver, + workspace: &Workspace, cx: &mut ViewContext, ) -> Self::View { - ProjectDiagnosticsEditor::new(handle, settings, cx) + ProjectDiagnosticsEditor::new(handle, workspace.weak_handle(), workspace.settings(), cx) } fn project_path(&self) -> Option { @@ -554,7 +575,8 @@ mod tests { #[gpui::test] async fn test_diagnostics(mut cx: TestAppContext) { - let settings = cx.update(WorkspaceParams::test).settings; + let workspace_params = cx.update(WorkspaceParams::test); + let settings = workspace_params.settings.clone(); let http_client = FakeHttpClient::new(|_| async move { Ok(ServerResponse::new(404)) }); let client = Client::new(http_client.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); @@ -684,8 +706,10 @@ mod tests { }); let model = cx.add_model(|_| ProjectDiagnostics::new(project.clone())); - let view = cx.add_view(Default::default(), |cx| { - ProjectDiagnosticsEditor::new(model, settings, cx) + let workspace = cx.add_view(0, |cx| Workspace::new(&workspace_params, cx)); + + let view = cx.add_view(0, |cx| { + ProjectDiagnosticsEditor::new(model, workspace.downgrade(), settings, cx) }); view.condition(&mut cx, |view, cx| view.text(cx).contains("fn main()")) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 57615073f7..8f5745cc2a 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -13,6 +13,7 @@ use std::path::Path; use text::{Point, Selection}; use workspace::{ ItemHandle, ItemView, ItemViewHandle, PathOpener, Settings, StatusItemView, WeakItemHandle, + Workspace, }; pub struct BufferOpener; @@ -43,13 +44,17 @@ impl ItemHandle for BufferItemHandle { fn add_view( &self, window_id: usize, - settings: watch::Receiver, + workspace: &Workspace, cx: &mut MutableAppContext, ) -> Box { let buffer = cx.add_model(|cx| MultiBuffer::singleton(self.0.clone(), cx)); let weak_buffer = buffer.downgrade(); Box::new(cx.add_view(window_id, |cx| { - Editor::for_buffer(buffer, crate::settings_builder(weak_buffer, settings), cx) + Editor::for_buffer( + buffer, + crate::settings_builder(weak_buffer, workspace.settings()), + cx, + ) })) } diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 0fe155ff11..47431c36fc 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -706,11 +706,16 @@ impl MultiBuffer { break; } + let mut end_before_newline = cursor.end(&()); + if excerpt.has_trailing_newline { + end_before_newline -= 1; + } let excerpt_start = excerpt.range.start.to_offset(&excerpt.buffer); let start = excerpt_start + (cmp::max(start, *cursor.start()) - *cursor.start()); - let end = excerpt_start + (cmp::min(end, cursor.end(&())) - *cursor.start()); + let end = excerpt_start + (cmp::min(end, end_before_newline) - *cursor.start()); let buffer = self.buffers.borrow()[&excerpt.buffer_id].buffer.clone(); result.push((buffer, start..end)); + cursor.next(&()); } result @@ -2582,7 +2587,7 @@ mod tests { .unwrap_or(10); let mut buffers: Vec> = Vec::new(); - let list = cx.add_model(|_| MultiBuffer::new(0)); + let multibuffer = cx.add_model(|_| MultiBuffer::new(0)); let mut excerpt_ids = Vec::new(); let mut expected_excerpts = Vec::<(ModelHandle, Range)>::new(); let mut old_versions = Vec::new(); @@ -2613,7 +2618,9 @@ mod tests { ); } ids_to_remove.sort_unstable(); - list.update(cx, |list, cx| list.remove_excerpts(&ids_to_remove, cx)); + multibuffer.update(cx, |multibuffer, cx| { + multibuffer.remove_excerpts(&ids_to_remove, cx) + }); } _ => { let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) { @@ -2645,8 +2652,8 @@ mod tests { &buffer.text()[start_ix..end_ix] ); - let excerpt_id = list.update(cx, |list, cx| { - list.insert_excerpt_after( + let excerpt_id = multibuffer.update(cx, |multibuffer, cx| { + multibuffer.insert_excerpt_after( &prev_excerpt_id, ExcerptProperties { buffer: &buffer_handle, @@ -2662,12 +2669,12 @@ mod tests { } if rng.gen_bool(0.3) { - list.update(cx, |list, cx| { - old_versions.push((list.snapshot(cx), list.subscribe())); + multibuffer.update(cx, |multibuffer, cx| { + old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe())); }) } - let snapshot = list.read(cx).snapshot(cx); + let snapshot = multibuffer.read(cx).snapshot(cx); let mut excerpt_starts = Vec::new(); let mut expected_text = String::new(); @@ -2862,15 +2869,30 @@ mod tests { let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right); let start_ix = text_rope.clip_offset(rng.gen_range(0..=end_ix), Bias::Left); + let text_for_range = snapshot + .text_for_range(start_ix..end_ix) + .collect::(); assert_eq!( - snapshot - .text_for_range(start_ix..end_ix) - .collect::(), + text_for_range, &expected_text[start_ix..end_ix], "incorrect text for range {:?}", start_ix..end_ix ); + let excerpted_buffer_ranges = + multibuffer.read(cx).excerpted_buffers(start_ix..end_ix, cx); + let excerpted_buffers_text = excerpted_buffer_ranges + .into_iter() + .map(|(buffer, buffer_range)| { + buffer + .read(cx) + .text_for_range(buffer_range) + .collect::() + }) + .collect::>() + .join("\n"); + assert_eq!(excerpted_buffers_text, text_for_range); + let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]); assert_eq!( snapshot.text_summary_for_range::(start_ix..end_ix), @@ -2904,7 +2926,7 @@ mod tests { } } - let snapshot = list.read(cx).snapshot(cx); + let snapshot = multibuffer.read(cx).snapshot(cx); for (old_snapshot, subscription) in old_versions { let edits = subscription.consume().into_inner(); diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index c8b2c5f868..67f92ad615 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1,5 +1,5 @@ use super::{ItemViewHandle, SplitDirection}; -use crate::{ItemHandle, Settings}; +use crate::{ItemHandle, Settings, Workspace}; use gpui::{ action, elements::*, @@ -90,6 +90,7 @@ impl Pane { pub fn open_item( &mut self, item_handle: T, + workspace: &Workspace, cx: &mut ViewContext, ) -> Box where @@ -103,7 +104,7 @@ impl Pane { } } - let item_view = item_handle.add_view(cx.window_id(), self.settings.clone(), cx); + let item_view = item_handle.add_view(cx.window_id(), workspace, cx); self.add_item_view(item_view.boxed_clone(), cx); item_view } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 055fa1169d..ccb5d9799c 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -18,7 +18,7 @@ use gpui::{ platform::{CursorStyle, WindowOptions}, AnyViewHandle, AppContext, ClipboardItem, Entity, ModelContext, ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, - WeakModelHandle, + WeakModelHandle, WeakViewHandle, }; use language::LanguageRegistry; use log::error; @@ -131,7 +131,7 @@ pub trait Item: Entity + Sized { fn build_view( handle: ModelHandle, - settings: watch::Receiver, + workspace: &Workspace, cx: &mut ViewContext, ) -> Self::View; @@ -181,7 +181,7 @@ pub trait ItemHandle: Send + Sync { fn add_view( &self, window_id: usize, - settings: watch::Receiver, + workspace: &Workspace, cx: &mut MutableAppContext, ) -> Box; fn boxed_clone(&self) -> Box; @@ -224,10 +224,10 @@ impl ItemHandle for ModelHandle { fn add_view( &self, window_id: usize, - settings: watch::Receiver, + workspace: &Workspace, cx: &mut MutableAppContext, ) -> Box { - Box::new(cx.add_view(window_id, |cx| T::build_view(self.clone(), settings, cx))) + Box::new(cx.add_view(window_id, |cx| T::build_view(self.clone(), workspace, cx))) } fn boxed_clone(&self) -> Box { @@ -251,10 +251,10 @@ impl ItemHandle for Box { fn add_view( &self, window_id: usize, - settings: watch::Receiver, + workspace: &Workspace, cx: &mut MutableAppContext, ) -> Box { - ItemHandle::add_view(self.as_ref(), window_id, settings, cx) + ItemHandle::add_view(self.as_ref(), window_id, workspace, cx) } fn boxed_clone(&self) -> Box { @@ -455,6 +455,7 @@ impl WorkspaceParams { pub struct Workspace { pub settings: watch::Receiver, + weak_self: WeakViewHandle, client: Arc, user_store: ModelHandle, fs: Arc, @@ -509,6 +510,7 @@ impl Workspace { Workspace { modal: None, + weak_self: cx.weak_handle(), center: PaneGroup::new(pane.id()), panes: vec![pane.clone()], active_pane: pane.clone(), @@ -526,6 +528,14 @@ impl Workspace { } } + pub fn weak_handle(&self) -> WeakViewHandle { + self.weak_self.clone() + } + + pub fn settings(&self) -> watch::Receiver { + self.settings.clone() + } + pub fn left_sidebar_mut(&mut self) -> &mut Sidebar { &mut self.left_sidebar } @@ -914,7 +924,7 @@ impl Workspace { T: 'static + ItemHandle, { self.items.insert(item_handle.downgrade()); - pane.update(cx, |pane, cx| pane.open_item(item_handle, cx)) + pane.update(cx, |pane, cx| pane.open_item(item_handle, self, cx)) } fn activate_pane(&mut self, pane: ViewHandle, cx: &mut ViewContext) { From ce6f3d7f3e0c8ff663bfd42e44771be659b7e919 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 7 Jan 2022 11:00:12 -0800 Subject: [PATCH 37/43] Reuse views when moving between diagnostic view and editors Co-Authored-By: Nathan Sobo --- crates/diagnostics/src/diagnostics.rs | 13 +++++++-- crates/editor/src/items.rs | 4 +++ crates/gpui/src/app.rs | 25 ++++++++++++++++ crates/workspace/src/pane.rs | 34 +++++++++++++++------- crates/workspace/src/workspace.rs | 41 +++++++++++++++++++++++++-- crates/zed/src/zed.rs | 7 ++--- 6 files changed, 103 insertions(+), 21 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index fd710d596b..99fa740451 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -165,8 +165,13 @@ impl ProjectDiagnosticsEditor { } fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext) { - let diagnostics = cx.add_model(|_| ProjectDiagnostics::new(workspace.project().clone())); - workspace.open_item(diagnostics, cx); + if let Some(existing) = workspace.item_of_type::(cx) { + workspace.activate_pane_for_item(&existing, cx); + } else { + let diagnostics = + cx.add_model(|_| ProjectDiagnostics::new(workspace.project().clone())); + workspace.open_item(diagnostics, cx); + } } fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext) { @@ -191,8 +196,10 @@ impl ProjectDiagnosticsEditor { workspace.update(cx, |workspace, cx| { for (buffer, ranges) in new_selections_by_buffer { + let buffer = BufferItemHandle(buffer); + workspace.activate_pane_for_item(&buffer, cx); let editor = workspace - .open_item(BufferItemHandle(buffer), cx) + .open_item(buffer, cx) .to_any() .downcast::() .unwrap(); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 8f5745cc2a..b97f01ce69 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -62,6 +62,10 @@ impl ItemHandle for BufferItemHandle { Box::new(self.clone()) } + fn to_any(&self) -> gpui::AnyModelHandle { + self.0.clone().into() + } + fn downgrade(&self) -> Box { Box::new(WeakBufferItemHandle(self.0.downgrade())) } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 82ec8fbcea..5b70981ba2 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -3097,14 +3097,39 @@ impl Drop for AnyViewHandle { pub struct AnyModelHandle { model_id: usize, + model_type: TypeId, ref_counts: Arc>, } +impl AnyModelHandle { + pub fn downcast(self) -> Option> { + if self.is::() { + let result = Some(ModelHandle { + model_id: self.model_id, + model_type: PhantomData, + ref_counts: self.ref_counts.clone(), + }); + unsafe { + Arc::decrement_strong_count(&self.ref_counts); + } + std::mem::forget(self); + result + } else { + None + } + } + + pub fn is(&self) -> bool { + self.model_type == TypeId::of::() + } +} + impl From> for AnyModelHandle { fn from(handle: ModelHandle) -> Self { handle.ref_counts.lock().inc_model(handle.model_id); Self { model_id: handle.model_id, + model_type: TypeId::of::(), ref_counts: handle.ref_counts.clone(), } } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 67f92ad615..e7e1383e17 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -69,7 +69,7 @@ pub struct TabState { } pub struct Pane { - item_views: Vec>, + item_views: Vec<(usize, Box)>, active_item: usize, settings: watch::Receiver, } @@ -96,8 +96,8 @@ impl Pane { where T: 'static + ItemHandle, { - for (ix, item_view) in self.item_views.iter().enumerate() { - if item_view.item_handle(cx).id() == item_handle.id() { + for (ix, (item_id, item_view)) in self.item_views.iter().enumerate() { + if *item_id == item_handle.id() { let item_view = item_view.boxed_clone(); self.activate_item(ix, cx); return item_view; @@ -116,21 +116,33 @@ impl Pane { ) { item_view.added_to_pane(cx); let item_idx = cmp::min(self.active_item + 1, self.item_views.len()); - self.item_views.insert(item_idx, item_view); + self.item_views + .insert(item_idx, (item_view.item_handle(cx).id(), item_view)); self.activate_item(item_idx, cx); cx.notify(); } - pub fn item_views(&self) -> &[Box] { - &self.item_views + pub fn contains_item(&self, item: &dyn ItemHandle) -> bool { + let item_id = item.id(); + self.item_views + .iter() + .any(|(existing_item_id, _)| *existing_item_id == item_id) + } + + pub fn item_views(&self) -> impl Iterator> { + self.item_views.iter().map(|(_, view)| view) } pub fn active_item(&self) -> Option> { - self.item_views.get(self.active_item).cloned() + self.item_views + .get(self.active_item) + .map(|(_, view)| view.clone()) } pub fn item_index(&self, item: &dyn ItemViewHandle) -> Option { - self.item_views.iter().position(|i| i.id() == item.id()) + self.item_views + .iter() + .position(|(_, i)| i.id() == item.id()) } pub fn activate_item(&mut self, index: usize, cx: &mut ViewContext) { @@ -163,12 +175,12 @@ impl Pane { pub fn close_active_item(&mut self, cx: &mut ViewContext) { if !self.item_views.is_empty() { - self.close_item(self.item_views[self.active_item].id(), cx) + self.close_item(self.item_views[self.active_item].1.id(), cx) } } pub fn close_item(&mut self, item_id: usize, cx: &mut ViewContext) { - self.item_views.retain(|item| item.id() != item_id); + self.item_views.retain(|(_, item)| item.id() != item_id); self.active_item = cmp::min(self.active_item, self.item_views.len().saturating_sub(1)); if self.item_views.is_empty() { cx.emit(Event::Remove); @@ -193,7 +205,7 @@ impl Pane { enum Tabs {} let tabs = MouseEventHandler::new::(cx.view_id(), cx, |mouse_state, cx| { let mut row = Flex::row(); - for (ix, item_view) in self.item_views.iter().enumerate() { + for (ix, (_, item_view)) in self.item_views.iter().enumerate() { let is_active = ix == self.active_item; row.add_child({ diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ccb5d9799c..55538c09dd 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -16,9 +16,9 @@ use gpui::{ json::{self, to_string_pretty, ToJson}, keymap::Binding, platform::{CursorStyle, WindowOptions}, - AnyViewHandle, AppContext, ClipboardItem, Entity, ModelContext, ModelHandle, MutableAppContext, - PathPromptOptions, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, - WeakModelHandle, WeakViewHandle, + AnyModelHandle, AnyViewHandle, AppContext, ClipboardItem, Entity, ModelContext, ModelHandle, + MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task, View, ViewContext, + ViewHandle, WeakModelHandle, WeakViewHandle, }; use language::LanguageRegistry; use log::error; @@ -186,6 +186,7 @@ pub trait ItemHandle: Send + Sync { ) -> Box; fn boxed_clone(&self) -> Box; fn downgrade(&self) -> Box; + fn to_any(&self) -> AnyModelHandle; fn project_path(&self, cx: &AppContext) -> Option; } @@ -238,6 +239,10 @@ impl ItemHandle for ModelHandle { Box::new(self.downgrade()) } + fn to_any(&self) -> AnyModelHandle { + self.clone().into() + } + fn project_path(&self, cx: &AppContext) -> Option { self.read(cx).project_path() } @@ -265,6 +270,10 @@ impl ItemHandle for Box { self.as_ref().downgrade() } + fn to_any(&self) -> AnyModelHandle { + self.as_ref().to_any() + } + fn project_path(&self, cx: &AppContext) -> Option { self.as_ref().project_path(cx) } @@ -760,6 +769,12 @@ impl Workspace { .find(|i| i.project_path(cx).as_ref() == Some(path)) } + pub fn item_of_type(&self, cx: &AppContext) -> Option> { + self.items + .iter() + .find_map(|i| i.upgrade(cx).and_then(|i| i.to_any().downcast())) + } + pub fn active_item(&self, cx: &AppContext) -> Option> { self.active_pane().read(cx).active_item() } @@ -927,6 +942,26 @@ impl Workspace { pane.update(cx, |pane, cx| pane.open_item(item_handle, self, cx)) } + pub fn activate_pane_for_item( + &mut self, + item: &dyn ItemHandle, + cx: &mut ViewContext, + ) -> bool { + let pane = self.panes.iter().find_map(|pane| { + if pane.read(cx).contains_item(item) { + Some(pane.clone()) + } else { + None + } + }); + if let Some(pane) = pane { + self.activate_pane(pane.clone(), cx); + true + } else { + false + } + } + fn activate_pane(&mut self, pane: ViewHandle, cx: &mut ViewContext) { self.active_pane = pane; self.status_bar.update(cx, |status_bar, cx| { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index d2da4fdd53..31fbc4ed78 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -273,7 +273,7 @@ mod tests { pane.active_item().unwrap().project_path(cx), Some(file1.clone()) ); - assert_eq!(pane.item_views().len(), 1); + assert_eq!(pane.item_views().count(), 1); }); // Open the second entry @@ -287,7 +287,7 @@ mod tests { pane.active_item().unwrap().project_path(cx), Some(file2.clone()) ); - assert_eq!(pane.item_views().len(), 2); + assert_eq!(pane.item_views().count(), 2); }); // Open the first entry again. The existing pane item is activated. @@ -303,7 +303,7 @@ mod tests { pane.active_item().unwrap().project_path(cx), Some(file1.clone()) ); - assert_eq!(pane.item_views().len(), 2); + assert_eq!(pane.item_views().count(), 2); }); // Split the pane with the first entry, then open the second entry again. @@ -343,7 +343,6 @@ mod tests { ); let pane_entries = pane .item_views() - .iter() .map(|i| i.project_path(cx).unwrap()) .collect::>(); assert_eq!(pane_entries, &[file1, file2, file3]); From f933d54469791ac360b0b9b7b50c5415f4db92eb Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 7 Jan 2022 14:53:33 -0800 Subject: [PATCH 38/43] When selections lose their excerpts, move them to the next primary diagnostic --- crates/diagnostics/src/diagnostics.rs | 64 ++++++++++++----- crates/editor/src/editor.rs | 31 ++++++-- crates/editor/src/multi_buffer.rs | 100 ++++++++++++++++++++------ 3 files changed, 149 insertions(+), 46 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 99fa740451..3d14aea1a6 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -6,7 +6,7 @@ use editor::{ context_header_renderer, diagnostic_block_renderer, diagnostic_header_renderer, display_map::{BlockDisposition, BlockId, BlockProperties}, items::BufferItemHandle, - Autoscroll, BuildSettings, Editor, ExcerptId, ExcerptProperties, MultiBuffer, + Autoscroll, BuildSettings, Editor, ExcerptId, ExcerptProperties, MultiBuffer, ToOffset, }; use gpui::{ action, elements::*, keymap::Binding, AppContext, Entity, ModelHandle, MutableAppContext, @@ -56,6 +56,7 @@ struct ProjectDiagnosticsEditor { struct DiagnosticGroupState { primary_diagnostic: DiagnosticEntry, + primary_excerpt_ix: usize, excerpts: Vec, blocks: HashMap, block_count: usize, @@ -300,6 +301,7 @@ impl ProjectDiagnosticsEditor { if let Some(group) = to_insert { let mut group_state = DiagnosticGroupState { primary_diagnostic: group.entries[group.primary_ix].clone(), + primary_excerpt_ix: 0, excerpts: Default::default(), blocks: Default::default(), block_count: 0, @@ -370,6 +372,7 @@ impl ProjectDiagnosticsEditor { for entry in &group.entries[*start_ix..ix] { let mut diagnostic = entry.diagnostic.clone(); if diagnostic.is_primary { + group_state.primary_excerpt_ix = group_state.excerpts.len() - 1; diagnostic.message = entry.diagnostic.message.split('\n').skip(1).collect(); } @@ -433,22 +436,6 @@ impl ProjectDiagnosticsEditor { for group_state in &mut groups_to_add { group_state.blocks = block_ids.by_ref().take(group_state.block_count).collect(); } - - if was_empty { - editor.update_selections( - vec![Selection { - id: 0, - start: 0, - end: 0, - reversed: false, - goal: SelectionGoal::None, - }], - None, - cx, - ); - } else { - editor.refresh_selections(cx); - } }); for ix in group_ixs_to_remove.into_iter().rev() { @@ -469,6 +456,49 @@ impl ProjectDiagnosticsEditor { self.path_states.remove(path_ix); } + self.editor.update(cx, |editor, cx| { + let groups = self.path_states.get(path_ix)?.1.as_slice(); + + let mut selections; + let new_excerpt_ids_by_selection_id; + if was_empty { + new_excerpt_ids_by_selection_id = [(0, ExcerptId::min())].into_iter().collect(); + selections = vec![Selection { + id: 0, + start: 0, + end: 0, + reversed: false, + goal: SelectionGoal::None, + }]; + } else { + new_excerpt_ids_by_selection_id = editor.refresh_selections(cx); + selections = editor.local_selections::(cx); + } + + // If any selection has lost its position, move it to start of the next primary diagnostic. + for selection in &mut selections { + if let Some(new_excerpt_id) = new_excerpt_ids_by_selection_id.get(&selection.id) { + let group_ix = match groups.binary_search_by(|probe| { + probe.excerpts.last().unwrap().cmp(&new_excerpt_id) + }) { + Ok(ix) | Err(ix) => ix, + }; + if let Some(group) = groups.get(group_ix) { + let offset = excerpts_snapshot + .anchor_in_excerpt( + group.excerpts[group.primary_excerpt_ix].clone(), + group.primary_diagnostic.range.start.clone(), + ) + .to_offset(&excerpts_snapshot); + selection.start = offset; + selection.end = offset; + } + } + } + editor.update_selections(selections, None, cx); + Some(()) + }); + if self.path_states.is_empty() { if self.editor.is_focused(cx) { cx.focus_self(); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index f7babdf8af..c8d872f67f 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -28,8 +28,8 @@ use language::{ BracketPair, Buffer, Diagnostic, DiagnosticSeverity, Language, Point, Selection, SelectionGoal, TransactionId, }; -pub use multi_buffer::{Anchor, ExcerptId, ExcerptProperties, MultiBuffer}; -use multi_buffer::{AnchorRangeExt, MultiBufferChunks, MultiBufferSnapshot, ToOffset, ToPoint}; +pub use multi_buffer::{Anchor, ExcerptId, ExcerptProperties, MultiBuffer, ToOffset, ToPoint}; +use multi_buffer::{AnchorRangeExt, MultiBufferChunks, MultiBufferSnapshot}; use postage::watch; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; @@ -3297,8 +3297,14 @@ impl Editor { ); } - pub fn refresh_selections(&mut self, cx: &mut ViewContext) { - let anchors = self.buffer.update(cx, |buffer, cx| { + /// Compute new ranges for any selections that were located in excerpts that have + /// since been removed. + /// + /// Returns a `HashMap` indicating which selections whose former head position + /// was no longer present. The keys of the map are selection ids. The values are + /// the id of the new excerpt where the head of the selection has been moved. + pub fn refresh_selections(&mut self, cx: &mut ViewContext) -> HashMap { + let anchors_with_status = self.buffer.update(cx, |buffer, cx| { let snapshot = buffer.read(cx); snapshot.refresh_anchors( self.selections @@ -3306,17 +3312,28 @@ impl Editor { .flat_map(|selection| [&selection.start, &selection.end]), ) }); + let mut selections_with_lost_position = HashMap::default(); self.selections = self .selections .iter() .cloned() - .zip(anchors.chunks(2)) + .zip(anchors_with_status.chunks(2)) .map(|(mut selection, anchors)| { - selection.start = anchors[0].clone(); - selection.end = anchors[1].clone(); + selection.start = anchors[0].0.clone(); + selection.end = anchors[1].0.clone(); + let kept_head_position = if selection.reversed { + anchors[0].1 + } else { + anchors[1].1 + }; + if !kept_head_position { + selections_with_lost_position + .insert(selection.id, selection.head().excerpt_id.clone()); + } selection }) .collect(); + selections_with_lost_position } fn set_selections(&mut self, selections: Arc<[Selection]>, cx: &mut ViewContext) { diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 47431c36fc..28d935d2ec 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -1342,7 +1342,7 @@ impl MultiBufferSnapshot { position } - pub fn refresh_anchors<'a, I>(&'a self, anchors: I) -> Vec + pub fn refresh_anchors<'a, I>(&'a self, anchors: I) -> Vec<(Anchor, bool)> where I: 'a + IntoIterator, { @@ -1352,7 +1352,7 @@ impl MultiBufferSnapshot { while let Some(anchor) = anchors.peek() { let old_excerpt_id = &anchor.excerpt_id; - // Find the location where this anchor's excerpt should be, + // Find the location where this anchor's excerpt should be. cursor.seek_forward(&Some(old_excerpt_id), Bias::Left, &()); if cursor.item().is_none() { cursor.next(&()); @@ -1366,32 +1366,58 @@ impl MultiBufferSnapshot { if anchor.excerpt_id != *old_excerpt_id { break; } + let mut kept_position = false; let mut anchor = anchors.next().unwrap().clone(); + // Leave min and max anchors unchanged. + if *old_excerpt_id == ExcerptId::max() || *old_excerpt_id == ExcerptId::min() { + kept_position = true; + } + // If the old excerpt still exists at this location, then leave + // the anchor unchanged. + else if next_excerpt.map_or(false, |excerpt| { + excerpt.id == *old_excerpt_id && excerpt.buffer_id == anchor.buffer_id + }) { + kept_position = true; + } // If the old excerpt no longer exists at this location, then attempt to // find an equivalent position for this anchor in an adjacent excerpt. - if next_excerpt.map_or(true, |e| e.id != *old_excerpt_id) { + else { for excerpt in [next_excerpt, prev_excerpt].iter().filter_map(|e| *e) { - if excerpt.buffer_id == anchor.buffer_id - && excerpt - .range - .start - .cmp(&anchor.text_anchor, &excerpt.buffer) - .unwrap() - .is_le() - && excerpt - .range - .end - .cmp(&anchor.text_anchor, &excerpt.buffer) - .unwrap() - .is_ge() - { + if excerpt.contains(&anchor) { anchor.excerpt_id = excerpt.id.clone(); + kept_position = true; + break; } } } + // If there's no adjacent excerpt that contains the anchor's position, + // then report that the anchor has lost its position. + if !kept_position { + anchor = if let Some(excerpt) = next_excerpt { + Anchor { + buffer_id: excerpt.buffer_id, + excerpt_id: excerpt.id.clone(), + text_anchor: excerpt + .buffer + .anchor_at(&excerpt.range.start, anchor.text_anchor.bias), + } + } else if let Some(excerpt) = prev_excerpt { + Anchor { + buffer_id: excerpt.buffer_id, + excerpt_id: excerpt.id.clone(), + text_anchor: excerpt + .buffer + .anchor_at(&excerpt.range.end, anchor.text_anchor.bias), + } + } else if anchor.text_anchor.bias == Bias::Left { + Anchor::min() + } else { + Anchor::max() + }; + } - result.push(anchor); + result.push((anchor, kept_position)); } } result @@ -1894,6 +1920,22 @@ impl Excerpt { text_anchor } } + + fn contains(&self, anchor: &Anchor) -> bool { + self.buffer_id == anchor.buffer_id + && self + .range + .start + .cmp(&anchor.text_anchor, &self.buffer) + .unwrap() + .is_le() + && self + .range + .end + .cmp(&anchor.text_anchor, &self.buffer) + .unwrap() + .is_ge() + } } impl fmt::Debug for Excerpt { @@ -2523,7 +2565,7 @@ mod tests { let snapshot_2 = multibuffer.read(cx).snapshot(cx); assert_eq!(snapshot_2.text(), "ABCD\nGHIJ\nMNOP"); - // And excerpt id has been reused. + // The old excerpt id has been reused. assert_eq!(excerpt_id_2, excerpt_id_1); // Resolve some anchors from the previous snapshot in the new snapshot. @@ -2541,6 +2583,15 @@ mod tests { ]), vec![0, 0] ); + let refresh = + snapshot_2.refresh_anchors(&[snapshot_1.anchor_before(2), snapshot_1.anchor_after(3)]); + assert_eq!( + refresh, + &[ + (snapshot_2.anchor_before(0), false), + (snapshot_2.anchor_after(0), false), + ] + ); // Replace the middle excerpt with a smaller excerpt in buffer 2, // that intersects the old excerpt. @@ -2564,19 +2615,24 @@ mod tests { // The anchor in the middle excerpt snaps to the beginning of the // excerpt, since it is not let anchors = [ + snapshot_2.anchor_before(0), snapshot_2.anchor_after(2), snapshot_2.anchor_after(6), snapshot_2.anchor_after(14), ]; assert_eq!( snapshot_3.summaries_for_anchors::(&anchors), - &[2, 9, 13] + &[0, 2, 9, 13] ); let new_anchors = snapshot_3.refresh_anchors(&anchors); assert_eq!( - snapshot_3.summaries_for_anchors::(&new_anchors), - &[2, 7, 13] + new_anchors.iter().map(|a| a.1).collect::>(), + &[true, true, true, true] + ); + assert_eq!( + snapshot_3.summaries_for_anchors::(new_anchors.iter().map(|a| &a.0)), + &[0, 2, 7, 13] ); } From 1a53d5b7ba61bc020f3f5ad624b5327391a13dca Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 10 Jan 2022 10:10:11 +0100 Subject: [PATCH 39/43] Use a new `Workspace::activate_item` API in project diagnostics Previously, we would only activate the pane without switching the pane's *active item*. --- crates/diagnostics/src/diagnostics.rs | 2 +- crates/workspace/src/pane.rs | 8 ++++++-- crates/workspace/src/workspace.rs | 19 ++++++++++++++++++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 3d14aea1a6..f9cbb68aab 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -167,7 +167,7 @@ impl ProjectDiagnosticsEditor { fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext) { if let Some(existing) = workspace.item_of_type::(cx) { - workspace.activate_pane_for_item(&existing, cx); + workspace.activate_item(&existing, cx); } else { let diagnostics = cx.add_model(|_| ProjectDiagnostics::new(workspace.project().clone())); diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index e7e1383e17..731db29d63 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -139,10 +139,14 @@ impl Pane { .map(|(_, view)| view.clone()) } - pub fn item_index(&self, item: &dyn ItemViewHandle) -> Option { + pub fn index_for_item_view(&self, item_view: &dyn ItemViewHandle) -> Option { self.item_views .iter() - .position(|(_, i)| i.id() == item.id()) + .position(|(_, i)| i.id() == item_view.id()) + } + + pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option { + self.item_views.iter().position(|(id, _)| *id == item.id()) } pub fn activate_item(&mut self, index: usize, cx: &mut ViewContext) { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 55538c09dd..ea6eb66031 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -334,7 +334,7 @@ impl ItemViewHandle for ViewHandle { return; } if T::should_activate_item_on_event(event) { - if let Some(ix) = pane.item_index(&item) { + if let Some(ix) = pane.index_for_item_view(&item) { pane.activate_item(ix, cx); pane.activate(cx); } @@ -962,6 +962,23 @@ impl Workspace { } } + pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext) -> bool { + let result = self.panes.iter().find_map(|pane| { + if let Some(ix) = pane.read(cx).index_for_item(item) { + Some((pane.clone(), ix)) + } else { + None + } + }); + if let Some((pane, ix)) = result { + self.activate_pane(pane.clone(), cx); + pane.update(cx, |pane, cx| pane.activate_item(ix, cx)); + true + } else { + false + } + } + fn activate_pane(&mut self, pane: ViewHandle, cx: &mut ViewContext) { self.active_pane = pane; self.status_bar.update(cx, |status_bar, cx| { From 0742640b393f7680d60fac93fb85d89780761390 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 10 Jan 2022 11:26:48 +0100 Subject: [PATCH 40/43] Correctly report line boundaries when a map contains both folds and wraps This fixes the randomized test failures that were occurring on main. --- crates/editor/src/display_map.rs | 10 ++++++++-- crates/editor/src/display_map/fold_map.rs | 2 -- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 342ef90b38..faf770cb1b 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -199,7 +199,10 @@ impl DisplaySnapshot { pub fn prev_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) { loop { - point.column = 0; + let mut fold_point = point.to_fold_point(&self.folds_snapshot, Bias::Left); + *fold_point.column_mut() = 0; + point = fold_point.to_buffer_point(&self.folds_snapshot); + let mut display_point = self.point_to_display_point(point, Bias::Left); *display_point.column_mut() = 0; let next_point = self.display_point_to_point(display_point, Bias::Left); @@ -212,7 +215,10 @@ impl DisplaySnapshot { pub fn next_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) { loop { - point.column = self.buffer_snapshot.line_len(point.row); + let mut fold_point = point.to_fold_point(&self.folds_snapshot, Bias::Right); + *fold_point.column_mut() = self.folds_snapshot.line_len(fold_point.row()); + point = fold_point.to_buffer_point(&self.folds_snapshot); + let mut display_point = self.point_to_display_point(point, Bias::Right); *display_point.column_mut() = self.line_len(display_point.row()); let next_point = self.display_point_to_point(display_point, Bias::Right); diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 60dd40c853..fd5f1de8f8 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -37,7 +37,6 @@ impl FoldPoint { &mut self.0.row } - #[cfg(test)] pub fn column_mut(&mut self) -> &mut u32 { &mut self.0.column } @@ -549,7 +548,6 @@ impl FoldSnapshot { FoldOffset(self.transforms.summary().output.bytes) } - #[cfg(test)] pub fn line_len(&self, row: u32) -> u32 { let line_start = FoldPoint::new(row, 0).to_offset(self).0; let line_end = if row >= self.max_point().row() { From a1597578ff1afc30517bcdacfe934b4ae362d69b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 10 Jan 2022 11:56:00 +0100 Subject: [PATCH 41/43] Compare singleton buffers in `test_open_and_save_new_file` --- crates/zed/src/zed.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 31fbc4ed78..61300d1f56 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -577,7 +577,10 @@ mod tests { .unwrap() }); cx.read(|cx| { - assert_eq!(editor2.read(cx).buffer(), editor.read(cx).buffer()); + assert_eq!( + editor2.read(cx).buffer().read(cx).as_singleton().unwrap(), + editor.read(cx).buffer().read(cx).as_singleton().unwrap() + ); }) } From eb353648e6fdb36d7febbe715f56bfe63523e0d8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 10 Jan 2022 12:14:52 +0100 Subject: [PATCH 42/43] :art: --- crates/diagnostics/src/diagnostics.rs | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index f9cbb68aab..c1877b95e8 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -12,7 +12,7 @@ use gpui::{ action, elements::*, keymap::Binding, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; -use language::{Bias, Buffer, Diagnostic, DiagnosticEntry, Point, Selection, SelectionGoal}; +use language::{Bias, Buffer, DiagnosticEntry, Point, Selection, SelectionGoal}; use postage::watch; use project::{Project, ProjectPath, WorktreeId}; use std::{cmp::Ordering, mem, ops::Range, path::Path, sync::Arc}; @@ -58,16 +58,10 @@ struct DiagnosticGroupState { primary_diagnostic: DiagnosticEntry, primary_excerpt_ix: usize, excerpts: Vec, - blocks: HashMap, + blocks: HashSet, block_count: usize, } -enum DiagnosticBlock { - Header(Diagnostic), - Inline(Diagnostic), - Context, -} - impl ProjectDiagnostics { fn new(project: ModelHandle) -> Self { Self { project } @@ -267,7 +261,6 @@ impl ProjectDiagnosticsEditor { let mut group_ixs_to_remove = Vec::new(); let mut blocks_to_add = Vec::new(); let mut blocks_to_remove = HashSet::default(); - let mut diagnostic_blocks = Vec::new(); let excerpts_snapshot = self.excerpts.update(cx, |excerpts, excerpts_cx| { let mut old_groups = groups.iter().enumerate().peekable(); let mut new_groups = snapshot @@ -346,7 +339,6 @@ impl ProjectDiagnosticsEditor { header.message = primary.message.split('\n').next().unwrap().to_string(); group_state.block_count += 1; - diagnostic_blocks.push(DiagnosticBlock::Header(header.clone())); blocks_to_add.push(BlockProperties { position: header_position, height: 3, @@ -360,7 +352,6 @@ impl ProjectDiagnosticsEditor { }); } else { group_state.block_count += 1; - diagnostic_blocks.push(DiagnosticBlock::Context); blocks_to_add.push(BlockProperties { position: header_position, height: 1, @@ -379,8 +370,6 @@ impl ProjectDiagnosticsEditor { if !diagnostic.message.is_empty() { group_state.block_count += 1; - diagnostic_blocks - .push(DiagnosticBlock::Inline(diagnostic.clone())); blocks_to_add.push(BlockProperties { position: (excerpt_id.clone(), entry.range.start.clone()), height: diagnostic.message.matches('\n').count() as u8 + 1, @@ -406,7 +395,7 @@ impl ProjectDiagnosticsEditor { } else if let Some((group_ix, group_state)) = to_invalidate { excerpts.remove_excerpts(group_state.excerpts.iter(), excerpts_cx); group_ixs_to_remove.push(group_ix); - blocks_to_remove.extend(group_state.blocks.keys().copied()); + blocks_to_remove.extend(group_state.blocks.iter().copied()); } else if let Some((_, group)) = to_keep { prev_excerpt_id = group.excerpts.last().unwrap().clone(); } @@ -430,8 +419,7 @@ impl ProjectDiagnosticsEditor { }), cx, ) - .into_iter() - .zip(diagnostic_blocks); + .into_iter(); for group_state in &mut groups_to_add { group_state.blocks = block_ids.by_ref().take(group_state.block_count).collect(); From 5c3ae8808ba74b6a672d97e6176e140b40939557 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 10 Jan 2022 14:28:25 +0100 Subject: [PATCH 43/43] Fix diagnostic unit test assertions --- crates/diagnostics/src/diagnostics.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index c1877b95e8..e70cd07f50 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -590,6 +590,7 @@ fn compare_diagnostics( mod tests { use super::*; use client::{http::ServerResponse, test::FakeHttpClient, Client, UserStore}; + use editor::DisplayPoint; use gpui::TestAppContext; use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, LanguageRegistry, PointUtf16}; use project::{worktree, FakeFs}; @@ -749,6 +750,7 @@ mod tests { // // main.rs, diagnostic group 1 // + "\n", // padding "\n", // primary message "\n", // filename " let x = vec![];\n", @@ -765,6 +767,7 @@ mod tests { // // main.rs, diagnostic group 2 // + "\n", // padding "\n", // primary message "\n", // filename "fn main() {\n", @@ -783,7 +786,10 @@ mod tests { ); view.editor.update(cx, |editor, cx| { - assert_eq!(editor.selected_ranges::(cx), [0..0]); + assert_eq!( + editor.selected_display_ranges(cx), + [DisplayPoint::new(11, 6)..DisplayPoint::new(11, 6)] + ); }); }); @@ -821,6 +827,7 @@ mod tests { // // a.rs // + "\n", // padding "\n", // primary message "\n", // filename "const a: i32 = 'a';\n", @@ -829,6 +836,7 @@ mod tests { // // main.rs, diagnostic group 1 // + "\n", // padding "\n", // primary message "\n", // filename " let x = vec![];\n", @@ -845,6 +853,7 @@ mod tests { // // main.rs, diagnostic group 2 // + "\n", // padding "\n", // primary message "\n", // filename "fn main() {\n",