Compare commits

...

5 Commits

Author SHA1 Message Date
Antonio Scandurra
348145afdf zed 0.106.1 2023-09-29 16:05:29 +02:00
Antonio Scandurra
59f160a30a Introduce the ability to include or exclude warnings from project diagnostics (#3056)
![CleanShot 2023-09-27 at 18 09
37](https://github.com/zed-industries/zed/assets/482957/317d31e4-81f8-44d8-b94f-8ca7150d3fd2)

Release Notes:

- Added the ability to exclude warnings from project diagnostics. By
default, they will be on but they can be disabled temporarily by
clicking on the warnings icon. The default behavior can be changed by
changing the new `diagnostics.include_warnings` setting.
2023-09-29 16:04:42 +02:00
Joseph T. Lyons
098a6b1aae Enable semantic_index by default (#3061)
Release Notes:

- Enabled the `semantic_index` setting by default.
2023-09-28 17:34:03 -04:00
Conrad Irwin
63db1a97ed Don't prompt to save unchanged files (#3053)
- don't prompt to save a set of unchanged files when closing
(preview-only)
2023-09-27 19:21:19 -06:00
Joseph T. Lyons
e98be4600a v0.106.x preview 2023-09-27 12:26:31 -04:00
12 changed files with 240 additions and 46 deletions

5
Cargo.lock generated
View File

@@ -2210,6 +2210,9 @@ dependencies = [
"lsp",
"postage",
"project",
"schemars",
"serde",
"serde_derive",
"serde_json",
"settings",
"smallvec",
@@ -9831,7 +9834,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.106.0"
version = "0.106.1"
dependencies = [
"activity_indicator",
"anyhow",

View File

@@ -227,6 +227,11 @@
},
// Automatically update Zed
"auto_update": true,
// Diagnostics configuration.
"diagnostics": {
// Whether to show warnings or not by default.
"include_warnings": true
},
// Git gutter behavior configuration.
"git": {
// Control whether the git gutter is shown. May take 2 values:
@@ -370,7 +375,7 @@
},
// Difference settings for semantic_index
"semantic_index": {
"enabled": false
"enabled": true
},
// Settings specific to our elixir integration
"elixir": {

View File

@@ -21,6 +21,9 @@ util = { path = "../util" }
workspace = { path = "../workspace" }
anyhow.workspace = true
schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
smallvec.workspace = true
postage.workspace = true

View File

@@ -1,4 +1,6 @@
pub mod items;
mod project_diagnostics_settings;
mod toolbar_controls;
use anyhow::Result;
use collections::{BTreeSet, HashSet};
@@ -19,6 +21,7 @@ use language::{
};
use lsp::LanguageServerId;
use project::{DiagnosticSummary, Project, ProjectPath};
use project_diagnostics_settings::ProjectDiagnosticsSettings;
use serde_json::json;
use smallvec::SmallVec;
use std::{
@@ -30,18 +33,21 @@ use std::{
sync::Arc,
};
use theme::ThemeSettings;
pub use toolbar_controls::ToolbarControls;
use util::TryFutureExt;
use workspace::{
item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
ItemNavHistory, Pane, PaneBackdrop, ToolbarItemLocation, Workspace,
};
actions!(diagnostics, [Deploy]);
actions!(diagnostics, [Deploy, ToggleWarnings]);
const CONTEXT_LINE_COUNT: u32 = 1;
pub fn init(cx: &mut AppContext) {
settings::register::<ProjectDiagnosticsSettings>(cx);
cx.add_action(ProjectDiagnosticsEditor::deploy);
cx.add_action(ProjectDiagnosticsEditor::toggle_warnings);
items::init(cx);
}
@@ -55,6 +61,7 @@ struct ProjectDiagnosticsEditor {
excerpts: ModelHandle<MultiBuffer>,
path_states: Vec<PathState>,
paths_to_update: BTreeSet<(ProjectPath, LanguageServerId)>,
include_warnings: bool,
}
struct PathState {
@@ -187,6 +194,7 @@ impl ProjectDiagnosticsEditor {
editor,
path_states: Default::default(),
paths_to_update,
include_warnings: settings::get::<ProjectDiagnosticsSettings>(cx).include_warnings,
};
this.update_excerpts(None, cx);
this
@@ -204,6 +212,18 @@ impl ProjectDiagnosticsEditor {
}
}
fn toggle_warnings(&mut self, _: &ToggleWarnings, cx: &mut ViewContext<Self>) {
self.include_warnings = !self.include_warnings;
self.paths_to_update = self
.project
.read(cx)
.diagnostic_summaries(cx)
.map(|(path, server_id, _)| (path, server_id))
.collect();
self.update_excerpts(None, cx);
cx.notify();
}
fn update_excerpts(
&mut self,
language_server_id: Option<LanguageServerId>,
@@ -277,14 +297,18 @@ impl ProjectDiagnosticsEditor {
let mut blocks_to_add = Vec::new();
let mut blocks_to_remove = HashSet::default();
let mut first_excerpt_id = None;
let max_severity = if self.include_warnings {
DiagnosticSeverity::WARNING
} else {
DiagnosticSeverity::ERROR
};
let excerpts_snapshot = self.excerpts.update(cx, |excerpts, excerpts_cx| {
let mut old_groups = path_state.diagnostic_groups.iter().enumerate().peekable();
let mut new_groups = snapshot
.diagnostic_groups(language_server_id)
.into_iter()
.filter(|(_, group)| {
group.entries[group.primary_ix].diagnostic.severity
<= DiagnosticSeverity::WARNING
group.entries[group.primary_ix].diagnostic.severity <= max_severity
})
.peekable();
loop {
@@ -1501,6 +1525,7 @@ mod tests {
client::init_settings(cx);
workspace::init_settings(cx);
Project::init_settings(cx);
crate::init(cx);
});
}

View File

@@ -0,0 +1,28 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Debug)]
pub struct ProjectDiagnosticsSettings {
pub include_warnings: bool,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
pub struct ProjectDiagnosticsSettingsContent {
include_warnings: Option<bool>,
}
impl settings::Setting for ProjectDiagnosticsSettings {
const KEY: Option<&'static str> = Some("diagnostics");
type FileContent = ProjectDiagnosticsSettingsContent;
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
_cx: &gpui::AppContext,
) -> anyhow::Result<Self>
where
Self: Sized,
{
Self::load_via_json_merge(default_value, user_values)
}
}

View File

@@ -0,0 +1,115 @@
use crate::{ProjectDiagnosticsEditor, ToggleWarnings};
use gpui::{
elements::*,
platform::{CursorStyle, MouseButton},
Action, Entity, EventContext, View, ViewContext, WeakViewHandle,
};
use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
pub struct ToolbarControls {
editor: Option<WeakViewHandle<ProjectDiagnosticsEditor>>,
}
impl Entity for ToolbarControls {
type Event = ();
}
impl View for ToolbarControls {
fn ui_name() -> &'static str {
"ToolbarControls"
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let include_warnings = self
.editor
.as_ref()
.and_then(|editor| editor.upgrade(cx))
.map(|editor| editor.read(cx).include_warnings)
.unwrap_or(false);
let tooltip = if include_warnings {
"Exclude Warnings".into()
} else {
"Include Warnings".into()
};
Flex::row()
.with_child(render_toggle_button(
0,
"icons/warning.svg",
include_warnings,
(tooltip, Some(Box::new(ToggleWarnings))),
cx,
move |this, cx| {
if let Some(editor) = this.editor.and_then(|editor| editor.upgrade(cx)) {
editor.update(cx, |editor, cx| {
editor.toggle_warnings(&Default::default(), cx)
});
}
},
))
.into_any()
}
}
impl ToolbarItemView for ToolbarControls {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn ItemHandle>,
_: &mut ViewContext<Self>,
) -> ToolbarItemLocation {
if let Some(pane_item) = active_pane_item.as_ref() {
if let Some(editor) = pane_item.downcast::<ProjectDiagnosticsEditor>() {
self.editor = Some(editor.downgrade());
ToolbarItemLocation::PrimaryRight { flex: None }
} else {
ToolbarItemLocation::Hidden
}
} else {
ToolbarItemLocation::Hidden
}
}
}
impl ToolbarControls {
pub fn new() -> Self {
ToolbarControls { editor: None }
}
}
fn render_toggle_button<
F: 'static + Fn(&mut ToolbarControls, &mut EventContext<ToolbarControls>),
>(
index: usize,
icon: &'static str,
toggled: bool,
tooltip: (String, Option<Box<dyn Action>>),
cx: &mut ViewContext<ToolbarControls>,
on_click: F,
) -> AnyElement<ToolbarControls> {
enum Button {}
let theme = theme::current(cx);
let (tooltip_text, action) = tooltip;
MouseEventHandler::new::<Button, _>(index, cx, |mouse_state, _| {
let style = theme
.workspace
.toolbar
.toggleable_tool
.in_state(toggled)
.style_for(mouse_state);
Svg::new(icon)
.with_color(style.color)
.constrained()
.with_width(style.icon_width)
.aligned()
.constrained()
.with_width(style.button_width)
.with_height(style.button_width)
.contained()
.with_style(style.container)
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, view, cx| on_click(view, cx))
.with_tooltip::<Button>(index, tooltip_text, action, theme.tooltip.clone(), cx)
.into_any_named("quick action bar button")
}

View File

@@ -430,5 +430,9 @@ mod test {
cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 2));
cx.simulate_keystrokes([":", "q", "enter"]);
cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 1));
cx.simulate_keystrokes([":", "n", "e", "w", "enter"]);
cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 2));
cx.simulate_keystrokes([":", "q", "a", "enter"]);
cx.workspace(|workspace, cx| assert_eq!(workspace.items(cx).count(), 0));
}
}

View File

@@ -893,9 +893,13 @@ impl Pane {
) -> Task<Result<()>> {
// Find the items to close.
let mut items_to_close = Vec::new();
let mut dirty_items = Vec::new();
for item in &self.items {
if should_close(item.id()) {
items_to_close.push(item.boxed_clone());
if item.is_dirty(cx) {
dirty_items.push(item.boxed_clone());
}
}
}
@@ -907,13 +911,10 @@ impl Pane {
let workspace = self.workspace.clone();
cx.spawn(|pane, mut cx| async move {
if save_intent == SaveIntent::Close && items_to_close.len() > 1 {
if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
let mut answer = pane.update(&mut cx, |_, cx| {
let prompt = Self::file_names_for_prompt(
&mut items_to_close.iter(),
items_to_close.len(),
cx,
);
let prompt =
Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx);
cx.prompt(
PromptLevel::Warning,
&prompt,
@@ -921,7 +922,7 @@ impl Pane {
)
})?;
match answer.next().await {
Some(0) => save_intent = SaveIntent::Save,
Some(0) => save_intent = SaveIntent::SaveAll,
Some(1) => save_intent = SaveIntent::Skip,
_ => {}
}
@@ -2537,14 +2538,12 @@ mod tests {
set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
let task = pane
.update(cx, |pane, cx| {
pane.close_inactive_items(&CloseInactiveItems, cx)
})
.unwrap();
cx.foreground().run_until_parked();
window.simulate_prompt_answer(2, cx);
task.await.unwrap();
pane.update(cx, |pane, cx| {
pane.close_inactive_items(&CloseInactiveItems, cx)
})
.unwrap()
.await
.unwrap();
assert_item_labels(&pane, ["C*"], cx);
}
@@ -2565,12 +2564,10 @@ mod tests {
add_labeled_item(&pane, "E", false, cx);
assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx);
let task = pane
.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx))
pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx))
.unwrap()
.await
.unwrap();
cx.foreground().run_until_parked();
window.simulate_prompt_answer(2, cx);
task.await.unwrap();
assert_item_labels(&pane, ["A^", "C*^"], cx);
}
@@ -2586,14 +2583,12 @@ mod tests {
set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
let task = pane
.update(cx, |pane, cx| {
pane.close_items_to_the_left(&CloseItemsToTheLeft, cx)
})
.unwrap();
cx.foreground().run_until_parked();
window.simulate_prompt_answer(2, cx);
task.await.unwrap();
pane.update(cx, |pane, cx| {
pane.close_items_to_the_left(&CloseItemsToTheLeft, cx)
})
.unwrap()
.await
.unwrap();
assert_item_labels(&pane, ["C*", "D", "E"], cx);
}
@@ -2609,14 +2604,12 @@ mod tests {
set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
let task = pane
.update(cx, |pane, cx| {
pane.close_items_to_the_right(&CloseItemsToTheRight, cx)
})
.unwrap();
cx.foreground().run_until_parked();
window.simulate_prompt_answer(2, cx);
task.await.unwrap();
pane.update(cx, |pane, cx| {
pane.close_items_to_the_right(&CloseItemsToTheRight, cx)
})
.unwrap()
.await
.unwrap();
assert_item_labels(&pane, ["A", "B", "C*"], cx);
}
@@ -2635,14 +2628,28 @@ mod tests {
add_labeled_item(&pane, "C", false, cx);
assert_item_labels(&pane, ["A", "B", "C*"], cx);
let t = pane
pane.update(cx, |pane, cx| {
pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
})
.unwrap()
.await
.unwrap();
assert_item_labels(&pane, [], cx);
add_labeled_item(&pane, "A", true, cx);
add_labeled_item(&pane, "B", true, cx);
add_labeled_item(&pane, "C", true, cx);
assert_item_labels(&pane, ["A^", "B^", "C*^"], cx);
let save = pane
.update(cx, |pane, cx| {
pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
})
.unwrap();
cx.foreground().run_until_parked();
window.simulate_prompt_answer(2, cx);
t.await.unwrap();
save.await.unwrap();
assert_item_labels(&pane, [], cx);
}

View File

@@ -1419,7 +1419,7 @@ impl Workspace {
)
})?;
match answer.next().await {
Some(0) => save_intent = SaveIntent::Save,
Some(0) => save_intent = SaveIntent::SaveAll,
Some(1) => save_intent = SaveIntent::Skip,
_ => {}
}

View File

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.106.0"
version = "0.106.1"
publish = false
[lib]

View File

@@ -1 +1 @@
dev
preview

View File

@@ -275,6 +275,10 @@ pub fn initialize_workspace(
QuickActionBar::new(buffer_search_bar, workspace)
});
toolbar.add_item(quick_action_bar, cx);
let diagnostic_editor_controls = cx.add_view(|_| {
diagnostics::ToolbarControls::new()
});
toolbar.add_item(diagnostic_editor_controls, cx);
let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
toolbar.add_item(project_search_bar, cx);
let submit_feedback_button =