Merge branch 'project-diagnostics-pinned-tab' into style-project-diagnostics

This commit is contained in:
Max Brunsfeld 2022-01-25 12:20:37 -08:00
commit c9b4bb78f2
13 changed files with 118 additions and 70 deletions

1
Cargo.lock generated
View file

@ -1412,6 +1412,7 @@ dependencies = [
"postage", "postage",
"project", "project",
"serde_json", "serde_json",
"theme",
"unindent", "unindent",
"util", "util",
"workspace", "workspace",

View file

@ -13,6 +13,7 @@ editor = { path = "../editor" }
language = { path = "../language" } language = { path = "../language" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }
project = { path = "../project" } project = { path = "../project" }
theme = { path = "../theme" }
util = { path = "../util" } util = { path = "../util" }
workspace = { path = "../workspace" } workspace = { path = "../workspace" }
postage = { version = "0.4", features = ["futures-traits"] } postage = { version = "0.4", features = ["futures-traits"] }

View file

@ -17,7 +17,7 @@ use language::{
Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection, SelectionGoal, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection, SelectionGoal,
}; };
use postage::watch; use postage::watch;
use project::{Project, ProjectPath}; use project::{DiagnosticSummary, Project, ProjectPath};
use std::{ use std::{
any::{Any, TypeId}, any::{Any, TypeId},
cmp::Ordering, cmp::Ordering,
@ -57,6 +57,7 @@ struct ProjectDiagnosticsEditor {
model: ModelHandle<ProjectDiagnostics>, model: ModelHandle<ProjectDiagnostics>,
workspace: WeakViewHandle<Workspace>, workspace: WeakViewHandle<Workspace>,
editor: ViewHandle<Editor>, editor: ViewHandle<Editor>,
summary: DiagnosticSummary,
excerpts: ModelHandle<MultiBuffer>, excerpts: ModelHandle<MultiBuffer>,
path_states: Vec<PathState>, path_states: Vec<PathState>,
paths_to_update: BTreeSet<ProjectPath>, paths_to_update: BTreeSet<ProjectPath>,
@ -132,6 +133,7 @@ impl ProjectDiagnosticsEditor {
project::Event::DiskBasedDiagnosticsFinished => { project::Event::DiskBasedDiagnosticsFinished => {
let paths = mem::take(&mut this.paths_to_update); let paths = mem::take(&mut this.paths_to_update);
this.update_excerpts(paths, cx); this.update_excerpts(paths, cx);
cx.emit(Event::TitleChanged)
} }
project::Event::DiagnosticsUpdated(path) => { project::Event::DiagnosticsUpdated(path) => {
this.paths_to_update.insert(path.clone()); this.paths_to_update.insert(path.clone());
@ -147,13 +149,11 @@ impl ProjectDiagnosticsEditor {
cx.subscribe(&editor, |_, _, event, cx| cx.emit(*event)) cx.subscribe(&editor, |_, _, event, cx| cx.emit(*event))
.detach(); .detach();
let paths_to_update = project let project = project.read(cx);
.read(cx) let paths_to_update = project.diagnostic_summaries(cx).map(|e| e.0).collect();
.diagnostic_summaries(cx)
.map(|e| e.0)
.collect();
let this = Self { let this = Self {
model, model,
summary: project.diagnostic_summary(cx),
workspace, workspace,
excerpts, excerpts,
editor, editor,
@ -556,8 +556,38 @@ impl workspace::ItemView for ProjectDiagnosticsEditor {
self.model.clone() self.model.clone()
} }
fn title(&self, _: &AppContext) -> String { fn tab_content(&self, style: &theme::Tab, _: &AppContext) -> ElementBox {
"Project Diagnostics".to_string() let theme = &self.settings.borrow().theme.project_diagnostics;
let icon_width = theme.tab_icon_width;
let icon_spacing = theme.tab_icon_spacing;
let summary_spacing = theme.tab_summary_spacing;
Flex::row()
.with_children([
Svg::new("icons/no.svg")
.with_color(style.label.text.color)
.constrained()
.with_width(icon_width)
.aligned()
.contained()
.with_margin_right(icon_spacing)
.named("no-icon"),
Label::new(self.summary.error_count.to_string(), style.label.clone())
.aligned()
.boxed(),
Svg::new("icons/warning.svg")
.with_color(style.label.text.color)
.constrained()
.with_width(icon_width)
.aligned()
.contained()
.with_margin_left(summary_spacing)
.with_margin_right(icon_spacing)
.named("warn-icon"),
Label::new(self.summary.warning_count.to_string(), style.label.clone())
.aligned()
.boxed(),
])
.boxed()
} }
fn project_path(&self, _: &AppContext) -> Option<project::ProjectPath> { fn project_path(&self, _: &AppContext) -> Option<project::ProjectPath> {
@ -603,10 +633,7 @@ impl workspace::ItemView for ProjectDiagnosticsEditor {
} }
fn should_update_tab_on_event(event: &Event) -> bool { fn should_update_tab_on_event(event: &Event) -> bool {
matches!( matches!(event, Event::Saved | Event::Dirtied | Event::TitleChanged)
event,
Event::Saved | Event::Dirtied | Event::FileHandleChanged
)
} }
fn clone_on_split(&self, cx: &mut ViewContext<Self>) -> Option<Self> fn clone_on_split(&self, cx: &mut ViewContext<Self>) -> Option<Self>

View file

@ -551,6 +551,19 @@ impl Editor {
&self.buffer &self.buffer
} }
pub fn title(&self, cx: &AppContext) -> String {
let filename = self
.buffer()
.read(cx)
.file(cx)
.map(|file| file.file_name(cx));
if let Some(name) = filename {
name.to_string_lossy().into()
} else {
"untitled".into()
}
}
pub fn snapshot(&mut self, cx: &mut MutableAppContext) -> EditorSnapshot { pub fn snapshot(&mut self, cx: &mut MutableAppContext) -> EditorSnapshot {
EditorSnapshot { EditorSnapshot {
mode: self.mode, mode: self.mode,
@ -3762,8 +3775,8 @@ impl Editor {
language::Event::Edited => cx.emit(Event::Edited), language::Event::Edited => cx.emit(Event::Edited),
language::Event::Dirtied => cx.emit(Event::Dirtied), language::Event::Dirtied => cx.emit(Event::Dirtied),
language::Event::Saved => cx.emit(Event::Saved), language::Event::Saved => cx.emit(Event::Saved),
language::Event::FileHandleChanged => cx.emit(Event::FileHandleChanged), language::Event::FileHandleChanged => cx.emit(Event::TitleChanged),
language::Event::Reloaded => cx.emit(Event::FileHandleChanged), language::Event::Reloaded => cx.emit(Event::TitleChanged),
language::Event::Closed => cx.emit(Event::Closed), language::Event::Closed => cx.emit(Event::Closed),
_ => {} _ => {}
} }
@ -3903,7 +3916,7 @@ pub enum Event {
Blurred, Blurred,
Dirtied, Dirtied,
Saved, Saved,
FileHandleChanged, TitleChanged,
Closed, Closed,
} }

View file

@ -121,13 +121,9 @@ impl ItemView for Editor {
} }
} }
fn title(&self, cx: &AppContext) -> String { fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox {
let file = self.buffer().read(cx).file(cx); let title = self.title(cx);
if let Some(file) = file { Label::new(title, style.label.clone()).boxed()
file.file_name(cx).to_string_lossy().into()
} else {
"untitled".into()
}
} }
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> { fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
@ -207,10 +203,7 @@ impl ItemView for Editor {
} }
fn should_update_tab_on_event(event: &Event) -> bool { fn should_update_tab_on_event(event: &Event) -> bool {
matches!( matches!(event, Event::Saved | Event::Dirtied | Event::TitleChanged)
event,
Event::Saved | Event::Dirtied | Event::FileHandleChanged
)
} }
} }

View file

@ -492,7 +492,15 @@ mod tests {
.await; .await;
cx.read(|cx| { cx.read(|cx| {
let active_item = active_pane.read(cx).active_item().unwrap(); let active_item = active_pane.read(cx).active_item().unwrap();
assert_eq!(active_item.title(cx), "bandana"); assert_eq!(
active_item
.to_any()
.downcast::<Editor>()
.unwrap()
.read(cx)
.title(cx),
"bandana"
);
}); });
} }

View file

@ -235,6 +235,9 @@ pub struct ProjectDiagnostics {
pub container: ContainerStyle, pub container: ContainerStyle,
pub empty_message: TextStyle, pub empty_message: TextStyle,
pub status_bar_item: ContainedText, pub status_bar_item: ContainedText,
pub tab_icon_width: f32,
pub tab_icon_spacing: f32,
pub tab_summary_spacing: f32,
} }
#[derive(Clone, Deserialize, Default)] #[derive(Clone, Deserialize, Default)]

View file

@ -70,8 +70,6 @@ pub enum Event {
Split(SplitDirection), Split(SplitDirection),
} }
const MAX_TAB_TITLE_LEN: usize = 24;
pub struct Pane { pub struct Pane {
item_views: Vec<(usize, Box<dyn ItemViewHandle>)>, item_views: Vec<(usize, Box<dyn ItemViewHandle>)>,
active_item_index: usize, active_item_index: usize,
@ -79,6 +77,11 @@ pub struct Pane {
nav_history: Rc<RefCell<NavHistory>>, nav_history: Rc<RefCell<NavHistory>>,
} }
// #[derive(Debug, Eq, PartialEq)]
// pub struct State {
// pub tabs: Vec<TabState>,
// }
pub struct ItemNavHistory { pub struct ItemNavHistory {
history: Rc<RefCell<NavHistory>>, history: Rc<RefCell<NavHistory>>,
item_view: Rc<dyn WeakItemViewHandle>, item_view: Rc<dyn WeakItemViewHandle>,
@ -373,15 +376,12 @@ impl Pane {
let is_active = ix == self.active_item_index; let is_active = ix == self.active_item_index;
row.add_child({ row.add_child({
let mut title = item_view.title(cx); let tab_style = if is_active {
if title.len() > MAX_TAB_TITLE_LEN { theme.workspace.active_tab.clone()
let mut truncated_len = MAX_TAB_TITLE_LEN; } else {
while !title.is_char_boundary(truncated_len) { theme.workspace.tab.clone()
truncated_len -= 1; };
} let title = item_view.tab_content(&tab_style, cx);
title.truncate(truncated_len);
title.push('…');
}
let mut style = if is_active { let mut style = if is_active {
theme.workspace.active_tab.clone() theme.workspace.active_tab.clone()
@ -430,29 +430,16 @@ impl Pane {
.boxed(), .boxed(),
) )
.with_child( .with_child(
Container::new( Container::new(Align::new(title).boxed())
Align::new( .with_style(ContainerStyle {
Label::new( margin: Margin {
title, left: style.spacing,
if is_active { right: style.spacing,
theme.workspace.active_tab.label.clone() ..Default::default()
} else { },
theme.workspace.tab.label.clone()
},
)
.boxed(),
)
.boxed(),
)
.with_style(ContainerStyle {
margin: Margin {
left: style.spacing,
right: style.spacing,
..Default::default() ..Default::default()
}, })
..Default::default() .boxed(),
})
.boxed(),
) )
.with_child( .with_child(
Align::new( Align::new(

View file

@ -155,7 +155,7 @@ pub trait ItemView: View {
fn deactivated(&mut self, _: &mut ViewContext<Self>) {} fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) {} fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) {}
fn item_handle(&self, cx: &AppContext) -> Self::ItemHandle; fn item_handle(&self, cx: &AppContext) -> Self::ItemHandle;
fn title(&self, cx: &AppContext) -> String; fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox;
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>; fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self> fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
where where
@ -223,7 +223,7 @@ pub trait WeakItemHandle {
pub trait ItemViewHandle: 'static { pub trait ItemViewHandle: 'static {
fn item_handle(&self, cx: &AppContext) -> Box<dyn ItemHandle>; fn item_handle(&self, cx: &AppContext) -> Box<dyn ItemHandle>;
fn title(&self, cx: &AppContext) -> String; fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox;
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>; fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
fn boxed_clone(&self) -> Box<dyn ItemViewHandle>; fn boxed_clone(&self) -> Box<dyn ItemViewHandle>;
fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemViewHandle>>; fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemViewHandle>>;
@ -358,8 +358,8 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
Box::new(self.read(cx).item_handle(cx)) Box::new(self.read(cx).item_handle(cx))
} }
fn title(&self, cx: &AppContext) -> String { fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox {
self.read(cx).title(cx) self.read(cx).tab_content(style, cx)
} }
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> { fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {

View file

@ -0,0 +1,4 @@
<svg width="9" height="9" viewBox="0 0 9 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.5 9C4.11176 9 3.72353 8.95294 3.33529 8.85882C2.94706 8.75294 2.58235 8.6 2.24118 8.4C1.9 8.2 1.58824 7.96471 1.30588 7.69412C1.03529 7.41177 0.8 7.1 0.6 6.75882C0.4 6.41765 0.247059 6.05294 0.141177 5.66471C0.0470589 5.27647 0 4.88824 0 4.5C0 4.11176 0.0470589 3.72353 0.141177 3.33529C0.247059 2.94706 0.4 2.58235 0.6 2.24118C0.8 1.9 1.03529 1.59412 1.30588 1.32353C1.58824 1.04118 1.9 0.799999 2.24118 0.599999C2.58235 0.4 2.94706 0.252941 3.33529 0.158823C3.72353 0.0529408 4.11176 0 4.5 0C4.88824 0 5.27647 0.0529408 5.66471 0.158823C6.05294 0.252941 6.41765 0.4 6.75882 0.599999C7.1 0.799999 7.40588 1.04118 7.67647 1.32353C7.95882 1.59412 8.2 1.9 8.4 2.24118C8.6 2.58235 8.74706 2.94706 8.84118 3.33529C8.94706 3.72353 9 4.11176 9 4.5C9 4.88824 8.94706 5.27647 8.84118 5.66471C8.74706 6.05294 8.6 6.41765 8.4 6.75882C8.2 7.1 7.95882 7.41177 7.67647 7.69412C7.40588 7.96471 7.1 8.2 6.75882 8.4C6.41765 8.6 6.05294 8.75294 5.66471 8.85882C5.27647 8.95294 4.88824 9 4.5 9ZM1.97647 5.92941L6.03529 1.88823C5.81176 1.72353 5.56471 1.6 5.29412 1.51765C5.03529 1.43529 4.77059 1.39412 4.5 1.39412C4.11176 1.39412 3.73529 1.48235 3.37059 1.65882C3.01765 1.82353 2.71177 2.05294 2.45294 2.34706C2.19412 2.64118 1.99412 2.97647 1.85294 3.35294C1.72353 3.72941 1.65882 4.11176 1.65882 4.5C1.65882 4.74706 1.68235 4.99412 1.72941 5.24118C1.78824 5.47647 1.87059 5.70588 1.97647 5.92941ZM4.5 7.60588C4.88824 7.60588 5.25882 7.52353 5.61176 7.35882C5.97647 7.18235 6.28824 6.94706 6.54706 6.65294C6.80588 6.35882 7 6.02353 7.12941 5.64706C7.27059 5.27059 7.34118 4.88824 7.34118 4.5C7.34118 4.25294 7.31177 4.01177 7.25294 3.77647C7.20588 3.52941 7.12941 3.29412 7.02353 3.07059L2.96471 7.11177C3.18824 7.27647 3.42941 7.4 3.68824 7.48235C3.95882 7.56471 4.22941 7.60588 4.5 7.60588Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -1,3 +1,3 @@
<svg width="12" height="10" viewBox="0 0 12 10" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.1329 8.29967L6.76186 0.840494C6.59383 0.553602 6.29874 0.410156 6.00365 0.410156C5.70856 0.410156 5.41347 0.553602 5.22699 0.840494L0.858047 8.29967C0.540622 8.87141 0.959504 9.59068 1.63347 9.59068H10.3755C11.0468 9.59068 11.4669 8.87346 11.1329 8.29967ZM1.83512 8.60706L5.98521 1.49215L10.1718 8.60706H1.83512ZM6.00365 6.66234C5.64791 6.66234 5.35937 6.95087 5.35937 7.30662C5.35937 7.66236 5.64852 7.95089 6.00447 7.95089C6.36042 7.95089 6.64793 7.66236 6.64793 7.30662C6.64711 6.95128 6.36022 6.66234 6.00365 6.66234ZM5.51184 3.52498V5.49223C5.51184 5.76478 5.73315 5.98405 6.00365 5.98405C6.27415 5.98405 6.49546 5.76376 6.49546 5.49223V3.52498C6.49546 3.25448 6.2762 3.03316 6.00365 3.03316C5.7311 3.03316 5.51184 3.25448 5.51184 3.52498Z" fill="white"/> <path d="M0.577381 9.14286C0.414683 9.14286 0.277778 9.0873 0.166667 8.97619C0.0555556 8.86508 0 8.72817 0 8.56548C0 8.50992 0.00793651 8.45635 0.0238095 8.40476C0.0396825 8.35317 0.0595238 8.30556 0.0833333 8.2619L4.44643 0.369048C4.58135 0.123016 4.76587 0 5 0C5.23413 0 5.41865 0.123016 5.55357 0.369048L9.91667 8.2619C9.94048 8.30556 9.96032 8.35317 9.97619 8.40476C9.99206 8.45635 10 8.50992 10 8.56548C10 8.72817 9.94444 8.86508 9.83333 8.97619C9.72222 9.0873 9.58532 9.14286 9.42262 9.14286H0.577381ZM5.9881 2.40476H4.01786V3.77976L4.31548 5.61905H5.69048L5.9881 3.77976V2.40476ZM6 7.375C6 7.09722 5.90079 6.8631 5.70238 6.67262C5.50794 6.47817 5.27381 6.38095 5 6.38095C4.72619 6.38095 4.49206 6.47817 4.29762 6.67262C4.10714 6.8631 4.0119 7.09722 4.0119 7.375C4.0119 7.64881 4.10714 7.88095 4.29762 8.07143C4.49206 8.2619 4.72619 8.35714 5 8.35714C5.27381 8.35714 5.50794 8.2619 5.70238 8.07143C5.90079 7.88095 6 7.64881 6 7.375Z" fill="white"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 877 B

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -316,3 +316,6 @@ message.highlight_text.color = "$text.3.color"
background = "$surface.1" background = "$surface.1"
empty_message = "$text.0" empty_message = "$text.0"
status_bar_item = { extends = "$text.2", margin.right = 10 } status_bar_item = { extends = "$text.2", margin.right = 10 }
tab_icon_width = 9
tab_icon_spacing = 3
tab_summary_spacing = 10

View file

@ -378,6 +378,10 @@ mod tests {
.read(cx) .read(cx)
.active_item() .active_item()
.unwrap() .unwrap()
.to_any()
.downcast::<Editor>()
.unwrap()
.read(cx)
.title(cx), .title(cx),
"a.txt" "a.txt"
); );
@ -408,6 +412,10 @@ mod tests {
.read(cx) .read(cx)
.active_item() .active_item()
.unwrap() .unwrap()
.to_any()
.downcast::<Editor>()
.unwrap()
.read(cx)
.title(cx), .title(cx),
"b.txt" "b.txt"
); );
@ -491,14 +499,14 @@ mod tests {
}); });
editor.update(&mut cx, |editor, cx| { editor.update(&mut cx, |editor, cx| {
assert!(!editor.is_dirty(cx.as_ref())); assert!(!editor.is_dirty(cx));
assert_eq!(editor.title(cx.as_ref()), "untitled"); assert_eq!(editor.title(cx), "untitled");
assert!(Arc::ptr_eq( assert!(Arc::ptr_eq(
editor.language(cx).unwrap(), editor.language(cx).unwrap(),
&language::PLAIN_TEXT &language::PLAIN_TEXT
)); ));
editor.handle_input(&editor::Input("hi".into()), cx); editor.handle_input(&editor::Input("hi".into()), cx);
assert!(editor.is_dirty(cx.as_ref())); assert!(editor.is_dirty(cx));
}); });
// Save the buffer. This prompts for a filename. // Save the buffer. This prompts for a filename.
@ -509,7 +517,7 @@ mod tests {
}); });
cx.read(|cx| { cx.read(|cx| {
assert!(editor.is_dirty(cx)); assert!(editor.is_dirty(cx));
assert_eq!(editor.title(cx), "untitled"); assert_eq!(editor.read(cx).title(cx), "untitled");
}); });
// When the save completes, the buffer's title is updated and the language is assigned based // When the save completes, the buffer's title is updated and the language is assigned based