Show error and warning indicators in tabs (#21383)
Closes #21179 Release Notes: - Add setting to display error and warning indicators in tabs. <img width="454" alt="demo_with_icons" src="https://github.com/user-attachments/assets/6002b4d4-dca8-4e2a-842d-1df3e281fcd2"> <img width="454" alt="demo_without_icons" src="https://github.com/user-attachments/assets/df4b67bd-1a6c-4354-847e-d7fea95c1b7e">
This commit is contained in:
parent
92dea066dd
commit
6ebd6c2893
5 changed files with 131 additions and 38 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -15195,7 +15195,6 @@ dependencies = [
|
|||
"env_logger 0.11.5",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"git",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"itertools 0.13.0",
|
||||
|
|
|
@ -567,7 +567,17 @@
|
|||
// "History"
|
||||
// 2. Activate the neighbour tab (prefers the right one, if present)
|
||||
// "Neighbour"
|
||||
"activate_on_close": "history"
|
||||
"activate_on_close": "history",
|
||||
/// Which files containing diagnostic errors/warnings to mark in the tabs.
|
||||
/// This setting can take the following three values:
|
||||
///
|
||||
/// 1. Do not mark any files:
|
||||
/// "off"
|
||||
/// 2. Only mark files with errors:
|
||||
/// "errors"
|
||||
/// 3. Mark files with errors and warnings:
|
||||
/// "all"
|
||||
"show_diagnostics": "all"
|
||||
},
|
||||
// Settings related to preview tabs.
|
||||
"preview_tabs": {
|
||||
|
|
|
@ -38,7 +38,6 @@ db.workspace = true
|
|||
derive_more.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
git.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
itertools.workspace = true
|
||||
|
|
|
@ -42,6 +42,7 @@ pub struct ItemSettings {
|
|||
pub close_position: ClosePosition,
|
||||
pub activate_on_close: ActivateOnClose,
|
||||
pub file_icons: bool,
|
||||
pub show_diagnostics: ShowDiagnostics,
|
||||
pub always_show_close_button: bool,
|
||||
}
|
||||
|
||||
|
@ -60,6 +61,15 @@ pub enum ClosePosition {
|
|||
Right,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ShowDiagnostics {
|
||||
Off,
|
||||
Errors,
|
||||
#[default]
|
||||
All,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ActivateOnClose {
|
||||
|
@ -86,6 +96,11 @@ pub struct ItemSettingsContent {
|
|||
///
|
||||
/// Default: history
|
||||
pub activate_on_close: Option<ActivateOnClose>,
|
||||
/// Which files containing diagnostic errors/warnings to mark in the tabs.
|
||||
/// This setting can take the following three values:
|
||||
///
|
||||
/// Default: all
|
||||
show_diagnostics: Option<ShowDiagnostics>,
|
||||
/// Whether to always show the close button on tabs.
|
||||
///
|
||||
/// Default: false
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
item::{
|
||||
ActivateOnClose, ClosePosition, Item, ItemHandle, ItemSettings, PreviewTabsSettings,
|
||||
TabContentParams, WeakItemHandle,
|
||||
ShowDiagnostics, TabContentParams, WeakItemHandle,
|
||||
},
|
||||
move_item,
|
||||
notifications::NotifyResultExt,
|
||||
|
@ -13,7 +13,6 @@ use crate::{
|
|||
use anyhow::Result;
|
||||
use collections::{BTreeSet, HashMap, HashSet, VecDeque};
|
||||
use futures::{stream::FuturesUnordered, StreamExt};
|
||||
use git::repository::GitFileStatus;
|
||||
use gpui::{
|
||||
actions, anchored, deferred, impl_actions, prelude::*, Action, AnchorCorner, AnyElement,
|
||||
AppContext, AsyncWindowContext, ClickEvent, ClipboardItem, Div, DragMoveEvent, EntityId,
|
||||
|
@ -23,6 +22,7 @@ use gpui::{
|
|||
WindowContext,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::DiagnosticSeverity;
|
||||
use parking_lot::Mutex;
|
||||
use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};
|
||||
use serde::Deserialize;
|
||||
|
@ -39,10 +39,10 @@ use std::{
|
|||
},
|
||||
};
|
||||
use theme::ThemeSettings;
|
||||
|
||||
use ui::{
|
||||
prelude::*, right_click_menu, ButtonSize, Color, IconButton, IconButtonShape, IconName,
|
||||
IconSize, Indicator, Label, PopoverMenu, PopoverMenuHandle, Tab, TabBar, TabPosition, Tooltip,
|
||||
prelude::*, right_click_menu, ButtonSize, Color, DecoratedIcon, IconButton, IconButtonShape,
|
||||
IconDecoration, IconDecorationKind, IconName, IconSize, Indicator, Label, PopoverMenu,
|
||||
PopoverMenuHandle, Tab, TabBar, TabPosition, Tooltip,
|
||||
};
|
||||
use ui::{v_flex, ContextMenu};
|
||||
use util::{debug_panic, maybe, truncate_and_remove_front, ResultExt};
|
||||
|
@ -305,6 +305,7 @@ pub struct Pane {
|
|||
pub new_item_context_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||
pub split_item_context_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||
pinned_tab_count: usize,
|
||||
diagnostics: HashMap<ProjectPath, DiagnosticSeverity>,
|
||||
}
|
||||
|
||||
pub struct ActivationHistoryEntry {
|
||||
|
@ -381,6 +382,7 @@ impl Pane {
|
|||
cx.on_focus_in(&focus_handle, Pane::focus_in),
|
||||
cx.on_focus_out(&focus_handle, Pane::focus_out),
|
||||
cx.observe_global::<SettingsStore>(Self::settings_changed),
|
||||
cx.subscribe(&project, Self::project_events),
|
||||
];
|
||||
|
||||
let handle = cx.view().downgrade();
|
||||
|
@ -504,6 +506,7 @@ impl Pane {
|
|||
split_item_context_menu_handle: Default::default(),
|
||||
new_item_context_menu_handle: Default::default(),
|
||||
pinned_tab_count: 0,
|
||||
diagnostics: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -598,6 +601,47 @@ impl Pane {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
fn project_events(
|
||||
this: &mut Pane,
|
||||
_project: Model<Project>,
|
||||
event: &project::Event,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
project::Event::DiskBasedDiagnosticsFinished { .. }
|
||||
| project::Event::DiagnosticsUpdated { .. } => {
|
||||
if ItemSettings::get_global(cx).show_diagnostics != ShowDiagnostics::Off {
|
||||
this.update_diagnostics(cx);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_diagnostics(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let show_diagnostics = ItemSettings::get_global(cx).show_diagnostics;
|
||||
self.diagnostics = if show_diagnostics != ShowDiagnostics::Off {
|
||||
self.project
|
||||
.read(cx)
|
||||
.diagnostic_summaries(false, cx)
|
||||
.filter_map(|(project_path, _, diagnostic_summary)| {
|
||||
if diagnostic_summary.error_count > 0 {
|
||||
Some((project_path, DiagnosticSeverity::ERROR))
|
||||
} else if diagnostic_summary.warning_count > 0
|
||||
&& show_diagnostics != ShowDiagnostics::Errors
|
||||
{
|
||||
Some((project_path, DiagnosticSeverity::WARNING))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<HashMap<_, _>>()
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn settings_changed(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if let Some(display_nav_history_buttons) = self.display_nav_history_buttons.as_mut() {
|
||||
*display_nav_history_buttons = TabBarSettings::get_global(cx).show_nav_history_buttons;
|
||||
|
@ -605,6 +649,7 @@ impl Pane {
|
|||
if !PreviewTabsSettings::get_global(cx).enabled {
|
||||
self.preview_item_id = None;
|
||||
}
|
||||
self.update_diagnostics(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
|
@ -1839,23 +1884,6 @@ impl Pane {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn git_aware_icon_color(
|
||||
git_status: Option<GitFileStatus>,
|
||||
ignored: bool,
|
||||
selected: bool,
|
||||
) -> Color {
|
||||
if ignored {
|
||||
Color::Ignored
|
||||
} else {
|
||||
match git_status {
|
||||
Some(GitFileStatus::Added) => Color::Created,
|
||||
Some(GitFileStatus::Modified) => Color::Modified,
|
||||
Some(GitFileStatus::Conflict) => Color::Conflict,
|
||||
None => Self::icon_color(selected),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_pin_tab(&mut self, _: &TogglePinTab, cx: &mut ViewContext<'_, Self>) {
|
||||
if self.items.is_empty() {
|
||||
return;
|
||||
|
@ -1919,8 +1947,6 @@ impl Pane {
|
|||
focus_handle: &FocusHandle,
|
||||
cx: &mut ViewContext<'_, Pane>,
|
||||
) -> impl IntoElement {
|
||||
let project_path = item.project_path(cx);
|
||||
|
||||
let is_active = ix == self.active_item_index;
|
||||
let is_preview = self
|
||||
.preview_item_id
|
||||
|
@ -1936,19 +1962,57 @@ impl Pane {
|
|||
cx,
|
||||
);
|
||||
|
||||
let icon_color = if ItemSettings::get_global(cx).git_status {
|
||||
project_path
|
||||
.as_ref()
|
||||
.and_then(|path| self.project.read(cx).entry_for_path(path, cx))
|
||||
.map(|entry| {
|
||||
Self::git_aware_icon_color(entry.git_status, entry.is_ignored, is_active)
|
||||
})
|
||||
.unwrap_or_else(|| Self::icon_color(is_active))
|
||||
let item_diagnostic = item
|
||||
.project_path(cx)
|
||||
.map_or(None, |project_path| self.diagnostics.get(&project_path));
|
||||
|
||||
let decorated_icon = item_diagnostic.map_or(None, |diagnostic| {
|
||||
let icon = match item.tab_icon(cx) {
|
||||
Some(icon) => icon,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
let knockout_item_color = if is_active {
|
||||
cx.theme().colors().tab_active_background
|
||||
} else {
|
||||
cx.theme().colors().tab_bar_background
|
||||
};
|
||||
|
||||
let (icon_decoration, icon_color) = if matches!(diagnostic, &DiagnosticSeverity::ERROR)
|
||||
{
|
||||
(IconDecorationKind::X, Color::Error)
|
||||
} else {
|
||||
(IconDecorationKind::Triangle, Color::Warning)
|
||||
};
|
||||
|
||||
Some(DecoratedIcon::new(
|
||||
icon.size(IconSize::Small).color(Color::Muted),
|
||||
Some(
|
||||
IconDecoration::new(icon_decoration, knockout_item_color, cx)
|
||||
.color(icon_color.color(cx))
|
||||
.position(Point {
|
||||
x: px(-2.),
|
||||
y: px(-2.),
|
||||
}),
|
||||
),
|
||||
))
|
||||
});
|
||||
|
||||
let icon = if decorated_icon.is_none() {
|
||||
match item_diagnostic {
|
||||
Some(&DiagnosticSeverity::ERROR) => {
|
||||
Some(Icon::new(IconName::X).color(Color::Error))
|
||||
}
|
||||
Some(&DiagnosticSeverity::WARNING) => {
|
||||
Some(Icon::new(IconName::Triangle).color(Color::Warning))
|
||||
}
|
||||
_ => item.tab_icon(cx).map(|icon| icon.color(Color::Muted)),
|
||||
}
|
||||
.map(|icon| icon.size(IconSize::Small))
|
||||
} else {
|
||||
Self::icon_color(is_active)
|
||||
None
|
||||
};
|
||||
|
||||
let icon = item.tab_icon(cx);
|
||||
let settings = ItemSettings::get_global(cx);
|
||||
let close_side = &settings.close_position;
|
||||
let always_show_close_button = settings.always_show_close_button;
|
||||
|
@ -2078,7 +2142,13 @@ impl Pane {
|
|||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.children(icon.map(|icon| icon.size(IconSize::Small).color(icon_color)))
|
||||
.child(if let Some(decorated_icon) = decorated_icon {
|
||||
div().child(decorated_icon.into_any_element())
|
||||
} else if let Some(icon) = icon {
|
||||
div().child(icon.into_any_element())
|
||||
} else {
|
||||
div()
|
||||
})
|
||||
.child(label),
|
||||
);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue