Detailed changes
@@ -9970,7 +9970,7 @@ dependencies = [
[[package]]
name = "tree-sitter"
version = "0.20.10"
-source = "git+https://github.com/tree-sitter/tree-sitter?rev=3b0159d25559b603af566ade3c83d930bf466db1#3b0159d25559b603af566ade3c83d930bf466db1"
+source = "git+https://github.com/tree-sitter/tree-sitter?rev=b5f461a69bf3df7298b1903574d506179e6390b0#b5f461a69bf3df7298b1903574d506179e6390b0"
dependencies = [
"cc",
"regex",
@@ -201,7 +201,7 @@ tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", re
tree-sitter-uiua = {git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"}
[patch.crates-io]
-tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "3b0159d25559b603af566ade3c83d930bf466db1" }
+tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "b5f461a69bf3df7298b1903574d506179e6390b0" }
async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" }
# TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457
@@ -530,12 +530,17 @@
"alt-cmd-shift-c": "project_panel::CopyRelativePath",
"f2": "project_panel::Rename",
"enter": "project_panel::Rename",
- "space": "project_panel::Open",
"backspace": "project_panel::Delete",
"alt-cmd-r": "project_panel::RevealInFinder",
"alt-shift-f": "project_panel::NewSearchInDirectory"
}
},
+ {
+ "context": "ProjectPanel && not_editing",
+ "bindings": {
+ "space": "project_panel::Open"
+ }
+ },
{
"context": "CollabPanel && not_editing",
"bindings": {
@@ -1218,6 +1218,31 @@ impl View for AssistantPanel {
let style = &theme.assistant;
if let Some(api_key_editor) = self.api_key_editor.as_ref() {
Flex::column()
+ .with_child(
+ Text::new(
+ "To use the assistant panel or inline assistant, you need to add your OpenAI api key.",
+ style.api_key_prompt.text.clone(),
+ ),
+ )
+ .with_child(
+ Text::new(
+ " - Having a subscription for another service like GitHub Copilot won't work.",
+ style.api_key_prompt.text.clone(),
+ ),
+ )
+ .with_child(
+ Text::new(
+ " - You can create a api key at: platform.openai.com/api-keys",
+ style.api_key_prompt.text.clone(),
+ ),
+ )
+ .with_child(
+ Text::new(
+ " ",
+ style.api_key_prompt.text.clone(),
+ )
+ .aligned(),
+ )
.with_child(
Text::new(
"Paste your OpenAI API key and press Enter to use the assistant",
@@ -1231,6 +1256,20 @@ impl View for AssistantPanel {
.with_style(style.api_key_editor.container)
.aligned(),
)
+ .with_child(
+ Text::new(
+ " ",
+ style.api_key_prompt.text.clone(),
+ )
+ .aligned(),
+ )
+ .with_child(
+ Text::new(
+ "Click on the Z button in the status bar to close this panel.",
+ style.api_key_prompt.text.clone(),
+ )
+ .aligned(),
+ )
.contained()
.with_style(style.api_key_prompt.container)
.aligned()
@@ -3941,7 +3941,7 @@ async fn test_collaborating_with_diagnostics(
// Ensure client B observes the new diagnostics.
project_b.read_with(cx_b, |project, cx| {
assert_eq!(
- project.diagnostic_summaries(cx).collect::<Vec<_>>(),
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
&[(
ProjectPath {
worktree_id,
@@ -3961,14 +3961,14 @@ async fn test_collaborating_with_diagnostics(
let project_c = client_c.build_remote_project(project_id, cx_c).await;
let project_c_diagnostic_summaries =
Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
- project.diagnostic_summaries(cx).collect::<Vec<_>>()
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>()
})));
project_c.update(cx_c, |_, cx| {
let summaries = project_c_diagnostic_summaries.clone();
cx.subscribe(&project_c, {
move |p, _, event, cx| {
if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
- *summaries.borrow_mut() = p.diagnostic_summaries(cx).collect();
+ *summaries.borrow_mut() = p.diagnostic_summaries(false, cx).collect();
}
}
})
@@ -4018,7 +4018,7 @@ async fn test_collaborating_with_diagnostics(
deterministic.run_until_parked();
project_b.read_with(cx_b, |project, cx| {
assert_eq!(
- project.diagnostic_summaries(cx).collect::<Vec<_>>(),
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
[(
ProjectPath {
worktree_id,
@@ -4034,7 +4034,7 @@ async fn test_collaborating_with_diagnostics(
});
project_c.read_with(cx_c, |project, cx| {
assert_eq!(
- project.diagnostic_summaries(cx).collect::<Vec<_>>(),
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
[(
ProjectPath {
worktree_id,
@@ -4097,13 +4097,22 @@ async fn test_collaborating_with_diagnostics(
);
deterministic.run_until_parked();
project_a.read_with(cx_a, |project, cx| {
- assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
+ assert_eq!(
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
+ []
+ )
});
project_b.read_with(cx_b, |project, cx| {
- assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
+ assert_eq!(
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
+ []
+ )
});
project_c.read_with(cx_c, |project, cx| {
- assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
+ assert_eq!(
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
+ []
+ )
});
}
@@ -3688,7 +3688,7 @@ async fn test_collaborating_with_diagnostics(
project_b.read_with(cx_b, |project, cx| {
assert_eq!(
- project.diagnostic_summaries(cx).collect::<Vec<_>>(),
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
&[(
ProjectPath {
worktree_id,
@@ -3708,14 +3708,14 @@ async fn test_collaborating_with_diagnostics(
let project_c = client_c.build_remote_project(project_id, cx_c).await;
let project_c_diagnostic_summaries =
Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
- project.diagnostic_summaries(cx).collect::<Vec<_>>()
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>()
})));
project_c.update(cx_c, |_, cx| {
let summaries = project_c_diagnostic_summaries.clone();
cx.subscribe(&project_c, {
move |p, _, event, cx| {
if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
- *summaries.borrow_mut() = p.diagnostic_summaries(cx).collect();
+ *summaries.borrow_mut() = p.diagnostic_summaries(false, cx).collect();
}
}
})
@@ -3766,7 +3766,7 @@ async fn test_collaborating_with_diagnostics(
project_b.read_with(cx_b, |project, cx| {
assert_eq!(
- project.diagnostic_summaries(cx).collect::<Vec<_>>(),
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
[(
ProjectPath {
worktree_id,
@@ -3783,7 +3783,7 @@ async fn test_collaborating_with_diagnostics(
project_c.read_with(cx_c, |project, cx| {
assert_eq!(
- project.diagnostic_summaries(cx).collect::<Vec<_>>(),
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
[(
ProjectPath {
worktree_id,
@@ -3844,15 +3844,24 @@ async fn test_collaborating_with_diagnostics(
executor.run_until_parked();
project_a.read_with(cx_a, |project, cx| {
- assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
+ assert_eq!(
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
+ []
+ )
});
project_b.read_with(cx_b, |project, cx| {
- assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
+ assert_eq!(
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
+ []
+ )
});
project_c.read_with(cx_c, |project, cx| {
- assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
+ assert_eq!(
+ project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
+ []
+ )
});
}
@@ -2511,7 +2511,7 @@ impl CollabPanel {
} else {
el.child(
ListHeader::new(text)
- .when_some(button, |el, button| el.right_button(button))
+ .when_some(button, |el, button| el.meta(button))
.selected(is_selected),
)
}
@@ -37,7 +37,7 @@ use gpui::{
};
use project::Project;
use theme::ActiveTheme;
-use ui::{h_stack, prelude::*, Avatar, Button, ButtonStyle2, IconButton, KeyBinding, Tooltip};
+use ui::{h_stack, prelude::*, Avatar, Button, ButtonStyle, IconButton, KeyBinding, Tooltip};
use util::ResultExt;
use workspace::{notifications::NotifyResultExt, Workspace};
@@ -154,7 +154,7 @@ impl Render for CollabTitlebarItem {
.id("project_owner_indicator")
.child(
Button::new("player", "player")
- .style(ButtonStyle2::Subtle)
+ .style(ButtonStyle::Subtle)
.color(Some(Color::Player(0))),
)
.tooltip(move |cx| Tooltip::text("Toggle following", cx)),
@@ -167,7 +167,7 @@ impl Render for CollabTitlebarItem {
.id("titlebar_project_menu_button")
.child(
Button::new("project_name", "project_name")
- .style(ButtonStyle2::Subtle),
+ .style(ButtonStyle::Subtle),
)
.tooltip(move |cx| Tooltip::text("Recent Projects", cx)),
)
@@ -179,7 +179,7 @@ impl Render for CollabTitlebarItem {
.id("titlebar_git_menu_button")
.child(
Button::new("branch_name", "branch_name")
- .style(ButtonStyle2::Subtle)
+ .style(ButtonStyle::Subtle)
.color(Some(Color::Muted)),
)
.tooltip(move |cx| {
@@ -11,7 +11,7 @@ use gpui::{
};
use picker::{Picker, PickerDelegate};
-use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, ListItem};
+use ui::{h_stack, prelude::*, v_stack, HighlightedLabel, KeyBinding, ListItem};
use util::{
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
ResultExt,
@@ -126,7 +126,7 @@ impl View for ProjectDiagnosticsEditor {
json!({
"project": json!({
"language_servers": project.language_server_statuses().collect::<Vec<_>>(),
- "summary": project.diagnostic_summary(cx),
+ "summary": project.diagnostic_summary(false, cx),
}),
"summary": self.summary,
"paths_to_update": self.paths_to_update.iter().map(|(server_id, paths)|
@@ -195,7 +195,7 @@ impl ProjectDiagnosticsEditor {
});
let project = project_handle.read(cx);
- let summary = project.diagnostic_summary(cx);
+ let summary = project.diagnostic_summary(false, cx);
let mut this = Self {
project: project_handle,
summary,
@@ -241,7 +241,7 @@ impl ProjectDiagnosticsEditor {
let mut new_summaries: HashMap<LanguageServerId, HashSet<ProjectPath>> = self
.project
.read(cx)
- .diagnostic_summaries(cx)
+ .diagnostic_summaries(false, cx)
.fold(HashMap::default(), |mut summaries, (path, server_id, _)| {
summaries.entry(server_id).or_default().insert(path);
summaries
@@ -320,7 +320,7 @@ impl ProjectDiagnosticsEditor {
.context("rechecking diagnostics for paths")?;
this.update(&mut cx, |this, cx| {
- this.summary = this.project.read(cx).diagnostic_summary(cx);
+ this.summary = this.project.read(cx).diagnostic_summary(false, cx);
cx.emit(Event::TitleChanged);
})?;
anyhow::Ok(())
@@ -34,19 +34,19 @@ impl DiagnosticIndicator {
}
project::Event::DiskBasedDiagnosticsFinished { language_server_id }
| project::Event::LanguageServerRemoved(language_server_id) => {
- this.summary = project.read(cx).diagnostic_summary(cx);
+ this.summary = project.read(cx).diagnostic_summary(false, cx);
this.in_progress_checks.remove(language_server_id);
cx.notify();
}
project::Event::DiagnosticsUpdated { .. } => {
- this.summary = project.read(cx).diagnostic_summary(cx);
+ this.summary = project.read(cx).diagnostic_summary(false, cx);
cx.notify();
}
_ => {}
})
.detach();
Self {
- summary: project.read(cx).diagnostic_summary(cx),
+ summary: project.read(cx).diagnostic_summary(false, cx),
in_progress_checks: project
.read(cx)
.language_servers_running_disk_based_diagnostics()
@@ -165,7 +165,7 @@ impl ProjectDiagnosticsEditor {
});
let project = project_handle.read(cx);
- let summary = project.diagnostic_summary(cx);
+ let summary = project.diagnostic_summary(false, cx);
let mut this = Self {
project: project_handle,
summary,
@@ -252,7 +252,7 @@ impl ProjectDiagnosticsEditor {
let mut new_summaries: HashMap<LanguageServerId, HashSet<ProjectPath>> = self
.project
.read(cx)
- .diagnostic_summaries(cx)
+ .diagnostic_summaries(false, cx)
.fold(HashMap::default(), |mut summaries, (path, server_id, _)| {
summaries.entry(server_id).or_default().insert(path);
summaries
@@ -332,7 +332,7 @@ impl ProjectDiagnosticsEditor {
.context("rechecking diagnostics for paths")?;
this.update(&mut cx, |this, cx| {
- this.summary = this.project.read(cx).diagnostic_summary(cx);
+ this.summary = this.project.read(cx).diagnostic_summary(false, cx);
cx.emit(ItemEvent::UpdateTab);
cx.emit(ItemEvent::UpdateBreadcrumbs);
})?;
@@ -77,13 +77,13 @@ impl DiagnosticIndicator {
project::Event::DiskBasedDiagnosticsFinished { language_server_id }
| project::Event::LanguageServerRemoved(language_server_id) => {
- this.summary = project.read(cx).diagnostic_summary(cx);
+ this.summary = project.read(cx).diagnostic_summary(false, cx);
this.in_progress_checks.remove(language_server_id);
cx.notify();
}
project::Event::DiagnosticsUpdated { .. } => {
- this.summary = project.read(cx).diagnostic_summary(cx);
+ this.summary = project.read(cx).diagnostic_summary(false, cx);
cx.notify();
}
@@ -92,7 +92,7 @@ impl DiagnosticIndicator {
.detach();
Self {
- summary: project.read(cx).diagnostic_summary(cx),
+ summary: project.read(cx).diagnostic_summary(false, cx),
in_progress_checks: project
.read(cx)
.language_servers_running_disk_based_diagnostics()
@@ -8,9 +8,10 @@ use anyhow::{anyhow, Context as _, Result};
use collections::HashSet;
use futures::future::try_join_all;
use gpui::{
- div, point, AnyElement, AppContext, AsyncAppContext, AsyncWindowContext, Context, Entity,
- EntityId, EventEmitter, FocusHandle, Model, ParentElement, Pixels, SharedString, Styled,
- Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
+ div, point, AnyElement, AppContext, AsyncAppContext, AsyncWindowContext, Context, Div, Entity,
+ EntityId, EventEmitter, FocusHandle, IntoElement, Model, ParentElement, Pixels, Render,
+ SharedString, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
+ WindowContext,
};
use language::{
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt,
@@ -20,6 +21,7 @@ use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPat
use rpc::proto::{self, update_view, PeerId};
use settings::Settings;
use smallvec::SmallVec;
+use std::fmt::Write;
use std::{
borrow::Cow,
cmp::{self, Ordering},
@@ -31,8 +33,11 @@ use std::{
use text::Selection;
use theme::{ActiveTheme, Theme};
use ui::{Color, Label};
-use util::{paths::PathExt, ResultExt, TryFutureExt};
-use workspace::item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle};
+use util::{paths::PathExt, paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt};
+use workspace::{
+ item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle},
+ StatusItemView,
+};
use workspace::{
item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
@@ -1113,86 +1118,78 @@ pub struct CursorPosition {
_observe_active_editor: Option<Subscription>,
}
-// impl Default for CursorPosition {
-// fn default() -> Self {
-// Self::new()
-// }
-// }
-
-// impl CursorPosition {
-// pub fn new() -> Self {
-// Self {
-// position: None,
-// selected_count: 0,
-// _observe_active_editor: None,
-// }
-// }
-
-// fn update_position(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
-// let editor = editor.read(cx);
-// let buffer = editor.buffer().read(cx).snapshot(cx);
-
-// self.selected_count = 0;
-// let mut last_selection: Option<Selection<usize>> = None;
-// for selection in editor.selections.all::<usize>(cx) {
-// self.selected_count += selection.end - selection.start;
-// if last_selection
-// .as_ref()
-// .map_or(true, |last_selection| selection.id > last_selection.id)
-// {
-// last_selection = Some(selection);
-// }
-// }
-// self.position = last_selection.map(|s| s.head().to_point(&buffer));
-
-// cx.notify();
-// }
-// }
-
-// impl Entity for CursorPosition {
-// type Event = ();
-// }
-
-// impl View for CursorPosition {
-// fn ui_name() -> &'static str {
-// "CursorPosition"
-// }
-
-// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-// if let Some(position) = self.position {
-// let theme = &theme::current(cx).workspace.status_bar;
-// let mut text = format!(
-// "{}{FILE_ROW_COLUMN_DELIMITER}{}",
-// position.row + 1,
-// position.column + 1
-// );
-// if self.selected_count > 0 {
-// write!(text, " ({} selected)", self.selected_count).unwrap();
-// }
-// Label::new(text, theme.cursor_position.clone()).into_any()
-// } else {
-// Empty::new().into_any()
-// }
-// }
-// }
-
-// impl StatusItemView for CursorPosition {
-// fn set_active_pane_item(
-// &mut self,
-// active_pane_item: Option<&dyn ItemHandle>,
-// cx: &mut ViewContext<Self>,
-// ) {
-// if let Some(editor) = active_pane_item.and_then(|item| item.act_as::<Editor>(cx)) {
-// self._observe_active_editor = Some(cx.observe(&editor, Self::update_position));
-// self.update_position(editor, cx);
-// } else {
-// self.position = None;
-// self._observe_active_editor = None;
-// }
-
-// cx.notify();
-// }
-// }
+impl Default for CursorPosition {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl CursorPosition {
+ pub fn new() -> Self {
+ Self {
+ position: None,
+ selected_count: 0,
+ _observe_active_editor: None,
+ }
+ }
+
+ fn update_position(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
+ let editor = editor.read(cx);
+ let buffer = editor.buffer().read(cx).snapshot(cx);
+
+ self.selected_count = 0;
+ let mut last_selection: Option<Selection<usize>> = None;
+ for selection in editor.selections.all::<usize>(cx) {
+ self.selected_count += selection.end - selection.start;
+ if last_selection
+ .as_ref()
+ .map_or(true, |last_selection| selection.id > last_selection.id)
+ {
+ last_selection = Some(selection);
+ }
+ }
+ self.position = last_selection.map(|s| s.head().to_point(&buffer));
+
+ cx.notify();
+ }
+}
+
+impl Render for CursorPosition {
+ type Element = Div;
+
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+ div().when_some(self.position, |el, position| {
+ let mut text = format!(
+ "{}{FILE_ROW_COLUMN_DELIMITER}{}",
+ position.row + 1,
+ position.column + 1
+ );
+ if self.selected_count > 0 {
+ write!(text, " ({} selected)", self.selected_count).unwrap();
+ }
+
+ el.child(Label::new(text))
+ })
+ }
+}
+
+impl StatusItemView for CursorPosition {
+ fn set_active_pane_item(
+ &mut self,
+ active_pane_item: Option<&dyn ItemHandle>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ if let Some(editor) = active_pane_item.and_then(|item| item.act_as::<Editor>(cx)) {
+ self._observe_active_editor = Some(cx.observe(&editor, Self::update_position));
+ self.update_position(editor, cx);
+ } else {
+ self.position = None;
+ self._observe_active_editor = None;
+ }
+
+ cx.notify();
+ }
+}
fn path_for_buffer<'a>(
buffer: &Model<MultiBuffer>,
@@ -518,6 +518,7 @@ impl PickerDelegate for FileFinderDelegate {
}
fn update_matches(&mut self, raw_query: String, cx: &mut ViewContext<FileFinder>) -> Task<()> {
+ let raw_query = raw_query.trim();
if raw_query.is_empty() {
let project = self.project.read(cx);
self.latest_search_id = post_inc(&mut self.search_count);
@@ -539,7 +540,6 @@ impl PickerDelegate for FileFinderDelegate {
cx.notify();
Task::ready(())
} else {
- let raw_query = &raw_query;
let query = PathLikeWithPosition::parse_str(raw_query, |path_like_str| {
Ok::<_, std::convert::Infallible>(FileSearchQuery {
raw_query: raw_query.to_owned(),
@@ -735,6 +735,7 @@ mod tests {
cx.dispatch_action(window.into(), Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
+
finder
.update(cx, |finder, cx| {
finder.delegate_mut().update_matches("bna".to_string(), cx)
@@ -743,7 +744,6 @@ mod tests {
finder.read_with(cx, |finder, _| {
assert_eq!(finder.delegate().matches.len(), 2);
});
-
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
cx.dispatch_action(window.into(), SelectNext);
cx.dispatch_action(window.into(), Confirm);
@@ -762,6 +762,49 @@ mod tests {
"bandana"
);
});
+
+ for bandana_query in [
+ "bandana",
+ " bandana",
+ "bandana ",
+ " bandana ",
+ " ndan ",
+ " band ",
+ ] {
+ finder
+ .update(cx, |finder, cx| {
+ finder
+ .delegate_mut()
+ .update_matches(bandana_query.to_string(), cx)
+ })
+ .await;
+ finder.read_with(cx, |finder, _| {
+ assert_eq!(
+ finder.delegate().matches.len(),
+ 1,
+ "Wrong number of matches for bandana query '{bandana_query}'"
+ );
+ });
+ let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
+ cx.dispatch_action(window.into(), SelectNext);
+ cx.dispatch_action(window.into(), Confirm);
+ active_pane
+ .condition(cx, |pane, _| pane.active_item().is_some())
+ .await;
+ cx.read(|cx| {
+ let active_item = active_pane.read(cx).active_item().unwrap();
+ assert_eq!(
+ active_item
+ .as_any()
+ .downcast_ref::<Editor>()
+ .unwrap()
+ .read(cx)
+ .title(cx),
+ "bandana",
+ "Wrong match for bandana query '{bandana_query}'"
+ );
+ });
+ }
}
#[gpui::test]
@@ -15,7 +15,7 @@ use std::{
},
};
use text::Point;
-use ui::{v_stack, HighlightedLabel, ListItem};
+use ui::{prelude::*, v_stack, HighlightedLabel, ListItem};
use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
use workspace::Workspace;
@@ -552,6 +552,7 @@ impl PickerDelegate for FileFinderDelegate {
raw_query: String,
cx: &mut ViewContext<Picker<Self>>,
) -> Task<()> {
+ let raw_query = raw_query.trim();
if raw_query.is_empty() {
let project = self.project.read(cx);
self.latest_search_id = post_inc(&mut self.search_count);
@@ -573,7 +574,6 @@ impl PickerDelegate for FileFinderDelegate {
cx.notify();
Task::ready(())
} else {
- let raw_query = &raw_query;
let query = PathLikeWithPosition::parse_str(raw_query, |path_like_str| {
Ok::<_, std::convert::Infallible>(FileSearchQuery {
raw_query: raw_query.to_owned(),
@@ -766,18 +766,49 @@ mod tests {
let (picker, workspace, cx) = build_find_picker(project, cx);
cx.simulate_input("bna");
-
picker.update(cx, |picker, _| {
assert_eq!(picker.delegate.matches.len(), 2);
});
-
cx.dispatch_action(SelectNext);
cx.dispatch_action(Confirm);
-
cx.read(|cx| {
let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
assert_eq!(active_editor.read(cx).title(cx), "bandana");
});
+
+ for bandana_query in [
+ "bandana",
+ " bandana",
+ "bandana ",
+ " bandana ",
+ " ndan ",
+ " band ",
+ ] {
+ picker
+ .update(cx, |picker, cx| {
+ picker
+ .delegate
+ .update_matches(bandana_query.to_string(), cx)
+ })
+ .await;
+ picker.update(cx, |picker, _| {
+ assert_eq!(
+ picker.delegate.matches.len(),
+ 1,
+ "Wrong number of matches for bandana query '{bandana_query}'"
+ );
+ });
+ cx.dispatch_action(SelectNext);
+ cx.dispatch_action(Confirm);
+ cx.read(|cx| {
+ let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
+ assert_eq!(
+ active_editor.read(cx).title(cx),
+ "bandana",
+ "Wrong match for bandana query '{bandana_query}'"
+ );
+ });
+ }
}
#[gpui::test]
@@ -2,8 +2,8 @@ use crate::{
div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
BackgroundExecutor, Context, Div, Entity, EventEmitter, ForegroundExecutor, InputEvent,
KeyDownEvent, Keystroke, Model, ModelContext, Render, Result, Task, TestDispatcher,
- TestPlatform, TestWindow, View, ViewContext, VisualContext, WindowContext, WindowHandle,
- WindowOptions,
+ TestPlatform, TestWindow, TestWindowHandlers, View, ViewContext, VisualContext, WindowContext,
+ WindowHandle, WindowOptions,
};
use anyhow::{anyhow, bail};
use futures::{Stream, StreamExt};
@@ -502,6 +502,19 @@ impl<'a> VisualTestContext<'a> {
self.cx.dispatch_action(self.window, action)
}
+ pub fn window_title(&mut self) -> Option<String> {
+ self.cx
+ .update_window(self.window, |_, cx| {
+ cx.window
+ .platform_window
+ .as_test()
+ .unwrap()
+ .window_title
+ .clone()
+ })
+ .unwrap()
+ }
+
pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
self.cx.simulate_keystrokes(self.window, keystrokes)
}
@@ -509,6 +522,39 @@ impl<'a> VisualTestContext<'a> {
pub fn simulate_input(&mut self, input: &str) {
self.cx.simulate_input(self.window, input)
}
+
+ pub fn simulate_activation(&mut self) {
+ self.simulate_window_events(&mut |handlers| {
+ handlers
+ .active_status_change
+ .iter_mut()
+ .for_each(|f| f(true));
+ })
+ }
+
+ pub fn simulate_deactivation(&mut self) {
+ self.simulate_window_events(&mut |handlers| {
+ handlers
+ .active_status_change
+ .iter_mut()
+ .for_each(|f| f(false));
+ })
+ }
+
+ fn simulate_window_events(&mut self, f: &mut dyn FnMut(&mut TestWindowHandlers)) {
+ let handlers = self
+ .cx
+ .update_window(self.window, |_, cx| {
+ cx.window
+ .platform_window
+ .as_test()
+ .unwrap()
+ .handlers
+ .clone()
+ })
+ .unwrap();
+ f(&mut *handlers.lock());
+ }
}
impl<'a> Context for VisualTestContext<'a> {
@@ -158,6 +158,11 @@ pub(crate) trait PlatformWindow {
fn draw(&self, scene: Scene);
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
+
+ #[cfg(any(test, feature = "test-support"))]
+ fn as_test(&self) -> Option<&TestWindow> {
+ None
+ }
}
pub trait PlatformDispatcher: Send + Sync {
@@ -189,13 +189,9 @@ impl Platform for TestPlatform {
unimplemented!()
}
- fn on_become_active(&self, _callback: Box<dyn FnMut()>) {
- unimplemented!()
- }
+ fn on_become_active(&self, _callback: Box<dyn FnMut()>) {}
- fn on_resign_active(&self, _callback: Box<dyn FnMut()>) {
- unimplemented!()
- }
+ fn on_resign_active(&self, _callback: Box<dyn FnMut()>) {}
fn on_quit(&self, _callback: Box<dyn FnMut()>) {}
@@ -11,19 +11,20 @@ use std::{
};
#[derive(Default)]
-struct Handlers {
- active_status_change: Vec<Box<dyn FnMut(bool)>>,
- input: Vec<Box<dyn FnMut(crate::InputEvent) -> bool>>,
- moved: Vec<Box<dyn FnMut()>>,
- resize: Vec<Box<dyn FnMut(Size<Pixels>, f32)>>,
+pub(crate) struct TestWindowHandlers {
+ pub(crate) active_status_change: Vec<Box<dyn FnMut(bool)>>,
+ pub(crate) input: Vec<Box<dyn FnMut(crate::InputEvent) -> bool>>,
+ pub(crate) moved: Vec<Box<dyn FnMut()>>,
+ pub(crate) resize: Vec<Box<dyn FnMut(Size<Pixels>, f32)>>,
}
pub struct TestWindow {
bounds: WindowBounds,
current_scene: Mutex<Option<Scene>>,
display: Rc<dyn PlatformDisplay>,
+ pub(crate) window_title: Option<String>,
pub(crate) input_handler: Option<Arc<Mutex<Box<dyn PlatformInputHandler>>>>,
- handlers: Mutex<Handlers>,
+ pub(crate) handlers: Arc<Mutex<TestWindowHandlers>>,
platform: Weak<TestPlatform>,
sprite_atlas: Arc<dyn PlatformAtlas>,
}
@@ -42,6 +43,7 @@ impl TestWindow {
input_handler: None,
sprite_atlas: Arc::new(TestAtlas::new()),
handlers: Default::default(),
+ window_title: Default::default(),
}
}
}
@@ -100,8 +102,8 @@ impl PlatformWindow for TestWindow {
todo!()
}
- fn set_title(&mut self, _title: &str) {
- todo!()
+ fn set_title(&mut self, title: &str) {
+ self.window_title = Some(title.to_owned());
}
fn set_edited(&mut self, _edited: bool) {
@@ -167,6 +169,10 @@ impl PlatformWindow for TestWindow {
fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
self.sprite_atlas.clone()
}
+
+ fn as_test(&self) -> Option<&TestWindow> {
+ Some(self)
+ }
}
pub struct TestAtlasState {
@@ -238,6 +238,10 @@ impl Element for AnyView {
}
fn paint(self, _: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+ debug_assert!(
+ state.is_some(),
+ "state is None. Did you include an AnyView twice in the tree?"
+ );
(self.paint)(&self, state.take().unwrap(), cx)
}
}
@@ -685,6 +685,10 @@ impl<'a> WindowContext<'a> {
self.window.platform_window.zoom();
}
+ pub fn set_window_title(&mut self, title: &str) {
+ self.window.platform_window.set_title(title);
+ }
+
pub fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
self.platform
.displays()
@@ -197,8 +197,12 @@ impl CachedLspAdapter {
self.adapter.code_action_kinds()
}
- pub fn workspace_configuration(&self, cx: &mut AppContext) -> BoxFuture<'static, Value> {
- self.adapter.workspace_configuration(cx)
+ pub fn workspace_configuration(
+ &self,
+ workspace_root: &Path,
+ cx: &mut AppContext,
+ ) -> BoxFuture<'static, Value> {
+ self.adapter.workspace_configuration(workspace_root, cx)
}
pub fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
@@ -312,7 +316,7 @@ pub trait LspAdapter: 'static + Send + Sync {
None
}
- fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
+ fn workspace_configuration(&self, _: &Path, _: &mut AppContext) -> BoxFuture<'static, Value> {
futures::future::ready(serde_json::json!({})).boxed()
}
@@ -200,8 +200,12 @@ impl CachedLspAdapter {
self.adapter.code_action_kinds()
}
- pub fn workspace_configuration(&self, cx: &mut AppContext) -> BoxFuture<'static, Value> {
- self.adapter.workspace_configuration(cx)
+ pub fn workspace_configuration(
+ &self,
+ workspace_root: &Path,
+ cx: &mut AppContext,
+ ) -> BoxFuture<'static, Value> {
+ self.adapter.workspace_configuration(workspace_root, cx)
}
pub fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
@@ -315,7 +319,7 @@ pub trait LspAdapter: 'static + Send + Sync {
None
}
- fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
+ fn workspace_configuration(&self, _: &Path, _: &mut AppContext) -> BoxFuture<'static, Value> {
futures::future::ready(serde_json::json!({})).boxed()
}
@@ -429,8 +429,8 @@ impl LanguageServer {
let root_uri = Url::from_file_path(&self.root_path).unwrap();
#[allow(deprecated)]
let params = InitializeParams {
- process_id: Default::default(),
- root_path: Default::default(),
+ process_id: None,
+ root_path: None,
root_uri: Some(root_uri.clone()),
initialization_options: options,
capabilities: ClientCapabilities {
@@ -451,12 +451,15 @@ impl LanguageServer {
inlay_hint: Some(InlayHintWorkspaceClientCapabilities {
refresh_support: Some(true),
}),
+ diagnostic: Some(DiagnosticWorkspaceClientCapabilities {
+ refresh_support: None,
+ }),
..Default::default()
}),
text_document: Some(TextDocumentClientCapabilities {
definition: Some(GotoCapability {
link_support: Some(true),
- ..Default::default()
+ dynamic_registration: None,
}),
code_action: Some(CodeActionClientCapabilities {
code_action_literal_support: Some(CodeActionLiteralSupport {
@@ -501,7 +504,7 @@ impl LanguageServer {
}),
hover: Some(HoverClientCapabilities {
content_format: Some(vec![MarkupKind::Markdown]),
- ..Default::default()
+ dynamic_registration: None,
}),
inlay_hint: Some(InlayHintClientCapabilities {
resolve_support: Some(InlayHintResolveClientCapabilities {
@@ -515,6 +518,20 @@ impl LanguageServer {
}),
dynamic_registration: Some(false),
}),
+ publish_diagnostics: Some(PublishDiagnosticsClientCapabilities {
+ related_information: Some(true),
+ ..Default::default()
+ }),
+ formatting: Some(DynamicRegistrationClientCapabilities {
+ dynamic_registration: None,
+ }),
+ on_type_formatting: Some(DynamicRegistrationClientCapabilities {
+ dynamic_registration: None,
+ }),
+ diagnostic: Some(DiagnosticClientCapabilities {
+ related_document_support: Some(true),
+ dynamic_registration: None,
+ }),
..Default::default()
}),
experimental: Some(json!({
@@ -524,15 +541,15 @@ impl LanguageServer {
work_done_progress: Some(true),
..Default::default()
}),
- ..Default::default()
+ general: None,
},
- trace: Default::default(),
+ trace: None,
workspace_folders: Some(vec![WorkspaceFolder {
uri: root_uri,
name: Default::default(),
}]),
- client_info: Default::default(),
- locale: Default::default(),
+ client_info: None,
+ locale: None,
};
let response = self.request::<request::Initialize>(params).await?;
@@ -434,8 +434,8 @@ impl LanguageServer {
let root_uri = Url::from_file_path(&self.root_path).unwrap();
#[allow(deprecated)]
let params = InitializeParams {
- process_id: Default::default(),
- root_path: Default::default(),
+ process_id: None,
+ root_path: None,
root_uri: Some(root_uri.clone()),
initialization_options: options,
capabilities: ClientCapabilities {
@@ -456,12 +456,15 @@ impl LanguageServer {
inlay_hint: Some(InlayHintWorkspaceClientCapabilities {
refresh_support: Some(true),
}),
+ diagnostic: Some(DiagnosticWorkspaceClientCapabilities {
+ refresh_support: None,
+ }),
..Default::default()
}),
text_document: Some(TextDocumentClientCapabilities {
definition: Some(GotoCapability {
link_support: Some(true),
- ..Default::default()
+ dynamic_registration: None,
}),
code_action: Some(CodeActionClientCapabilities {
code_action_literal_support: Some(CodeActionLiteralSupport {
@@ -503,7 +506,7 @@ impl LanguageServer {
}),
hover: Some(HoverClientCapabilities {
content_format: Some(vec![MarkupKind::Markdown]),
- ..Default::default()
+ dynamic_registration: None,
}),
inlay_hint: Some(InlayHintClientCapabilities {
resolve_support: Some(InlayHintResolveClientCapabilities {
@@ -517,6 +520,20 @@ impl LanguageServer {
}),
dynamic_registration: Some(false),
}),
+ publish_diagnostics: Some(PublishDiagnosticsClientCapabilities {
+ related_information: Some(true),
+ ..Default::default()
+ }),
+ formatting: Some(DynamicRegistrationClientCapabilities {
+ dynamic_registration: None,
+ }),
+ on_type_formatting: Some(DynamicRegistrationClientCapabilities {
+ dynamic_registration: None,
+ }),
+ diagnostic: Some(DiagnosticClientCapabilities {
+ related_document_support: Some(true),
+ dynamic_registration: None,
+ }),
..Default::default()
}),
experimental: Some(json!({
@@ -526,15 +543,15 @@ impl LanguageServer {
work_done_progress: Some(true),
..Default::default()
}),
- ..Default::default()
+ general: None,
},
- trace: Default::default(),
+ trace: None,
workspace_folders: Some(vec![WorkspaceFolder {
uri: root_uri,
name: Default::default(),
}]),
- client_info: Default::default(),
- locale: Default::default(),
+ client_info: None,
+ locale: None,
};
let response = self.request::<request::Initialize>(params).await?;
@@ -2641,8 +2641,9 @@ impl Project {
});
for (adapter, server) in servers {
- let workspace_config =
- cx.update(|cx| adapter.workspace_configuration(cx)).await;
+ let workspace_config = cx
+ .update(|cx| adapter.workspace_configuration(server.root_path(), cx))
+ .await;
server
.notify::<lsp::notification::DidChangeConfiguration>(
lsp::DidChangeConfigurationParams {
@@ -2753,7 +2754,7 @@ impl Project {
stderr_capture.clone(),
language.clone(),
adapter.clone(),
- worktree_path,
+ Arc::clone(&worktree_path),
ProjectLspAdapterDelegate::new(self, cx),
cx,
) {
@@ -2776,6 +2777,7 @@ impl Project {
cx.spawn_weak(|this, mut cx| async move {
let result = Self::setup_and_insert_language_server(
this,
+ &worktree_path,
override_options,
pending_server,
adapter.clone(),
@@ -2891,6 +2893,7 @@ impl Project {
async fn setup_and_insert_language_server(
this: WeakModelHandle<Self>,
+ worktree_path: &Path,
override_initialization_options: Option<serde_json::Value>,
pending_server: PendingLanguageServer,
adapter: Arc<CachedLspAdapter>,
@@ -2903,6 +2906,7 @@ impl Project {
this,
override_initialization_options,
pending_server,
+ worktree_path,
adapter.clone(),
server_id,
cx,
@@ -2932,11 +2936,14 @@ impl Project {
this: WeakModelHandle<Self>,
override_options: Option<serde_json::Value>,
pending_server: PendingLanguageServer,
+ worktree_path: &Path,
adapter: Arc<CachedLspAdapter>,
server_id: LanguageServerId,
cx: &mut AsyncAppContext,
) -> Result<Arc<LanguageServer>> {
- let workspace_config = cx.update(|cx| adapter.workspace_configuration(cx)).await;
+ let workspace_config = cx
+ .update(|cx| adapter.workspace_configuration(worktree_path, cx))
+ .await;
let language_server = pending_server.task.await?;
language_server
@@ -2964,11 +2971,14 @@ impl Project {
language_server
.on_request::<lsp::request::WorkspaceConfiguration, _, _>({
let adapter = adapter.clone();
+ let worktree_path = worktree_path.to_path_buf();
move |params, mut cx| {
let adapter = adapter.clone();
+ let worktree_path = worktree_path.clone();
async move {
- let workspace_config =
- cx.update(|cx| adapter.workspace_configuration(cx)).await;
+ let workspace_config = cx
+ .update(|cx| adapter.workspace_configuration(&worktree_path, cx))
+ .await;
Ok(params
.items
.into_iter()
@@ -6523,9 +6533,15 @@ impl Project {
})
}
- pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary {
+ pub fn diagnostic_summary(&self, include_ignored: bool, cx: &AppContext) -> DiagnosticSummary {
let mut summary = DiagnosticSummary::default();
- for (_, _, path_summary) in self.diagnostic_summaries(cx) {
+ for (_, _, path_summary) in
+ self.diagnostic_summaries(include_ignored, cx)
+ .filter(|(path, _, _)| {
+ let worktree = self.entry_for_path(&path, cx).map(|entry| entry.is_ignored);
+ include_ignored || worktree == Some(false)
+ })
+ {
summary.error_count += path_summary.error_count;
summary.warning_count += path_summary.warning_count;
}
@@ -6534,6 +6550,7 @@ impl Project {
pub fn diagnostic_summaries<'a>(
&'a self,
+ include_ignored: bool,
cx: &'a AppContext,
) -> impl Iterator<Item = (ProjectPath, LanguageServerId, DiagnosticSummary)> + 'a {
self.visible_worktrees(cx).flat_map(move |worktree| {
@@ -6544,6 +6561,10 @@ impl Project {
.map(move |(path, server_id, summary)| {
(ProjectPath { worktree_id, path }, server_id, summary)
})
+ .filter(move |(path, _, _)| {
+ let worktree = self.entry_for_path(&path, cx).map(|entry| entry.is_ignored);
+ include_ignored || worktree == Some(false)
+ })
})
}
@@ -806,7 +806,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
}
#[gpui::test]
-async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
+async fn test_omitted_diagnostics(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.background());
@@ -814,7 +814,12 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
"/root",
json!({
"dir": {
+ ".git": {
+ "HEAD": "ref: refs/heads/main",
+ },
+ ".gitignore": "b.rs",
"a.rs": "let a = 1;",
+ "b.rs": "let b = 2;",
},
"other.rs": "let b = c;"
}),
@@ -822,6 +827,13 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
.await;
let project = Project::test(fs, ["/root/dir".as_ref()], cx).await;
+ let (worktree, _) = project
+ .update(cx, |project, cx| {
+ project.find_or_create_local_worktree("/root/dir", true, cx)
+ })
+ .await
+ .unwrap();
+ let main_worktree_id = worktree.read_with(cx, |tree, _| tree.id());
let (worktree, _) = project
.update(cx, |project, cx| {
@@ -829,12 +841,30 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
})
.await
.unwrap();
- let worktree_id = worktree.read_with(cx, |tree, _| tree.id());
+ let other_worktree_id = worktree.read_with(cx, |tree, _| tree.id());
+ let server_id = LanguageServerId(0);
project.update(cx, |project, cx| {
project
.update_diagnostics(
- LanguageServerId(0),
+ server_id,
+ lsp::PublishDiagnosticsParams {
+ uri: Url::from_file_path("/root/dir/b.rs").unwrap(),
+ version: None,
+ diagnostics: vec![lsp::Diagnostic {
+ range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 5)),
+ severity: Some(lsp::DiagnosticSeverity::ERROR),
+ message: "unused variable 'b'".to_string(),
+ ..Default::default()
+ }],
+ },
+ &[],
+ cx,
+ )
+ .unwrap();
+ project
+ .update_diagnostics(
+ server_id,
lsp::PublishDiagnosticsParams {
uri: Url::from_file_path("/root/other.rs").unwrap(),
version: None,
@@ -851,11 +881,34 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
.unwrap();
});
- let buffer = project
- .update(cx, |project, cx| project.open_buffer((worktree_id, ""), cx))
+ let main_ignored_buffer = project
+ .update(cx, |project, cx| {
+ project.open_buffer((main_worktree_id, "b.rs"), cx)
+ })
.await
.unwrap();
- buffer.read_with(cx, |buffer, _| {
+ main_ignored_buffer.read_with(cx, |buffer, _| {
+ let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
+ assert_eq!(
+ chunks
+ .iter()
+ .map(|(s, d)| (s.as_str(), *d))
+ .collect::<Vec<_>>(),
+ &[
+ ("let ", None),
+ ("b", Some(DiagnosticSeverity::ERROR)),
+ (" = 2;", None),
+ ],
+ "Gigitnored buffers should still get in-buffer diagnostics",
+ );
+ });
+ let other_buffer = project
+ .update(cx, |project, cx| {
+ project.open_buffer((other_worktree_id, ""), cx)
+ })
+ .await
+ .unwrap();
+ other_buffer.read_with(cx, |buffer, _| {
let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
assert_eq!(
chunks
@@ -866,13 +919,29 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
("let b = ", None),
("c", Some(DiagnosticSeverity::ERROR)),
(";", None),
- ]
+ ],
+ "Buffers from hidden projects should still get in-buffer diagnostics"
);
});
project.read_with(cx, |project, cx| {
- assert_eq!(project.diagnostic_summaries(cx).next(), None);
- assert_eq!(project.diagnostic_summary(cx).error_count, 0);
+ assert_eq!(project.diagnostic_summaries(false, cx).next(), None);
+ assert_eq!(
+ project.diagnostic_summaries(true, cx).collect::<Vec<_>>(),
+ vec![(
+ ProjectPath {
+ worktree_id: main_worktree_id,
+ path: Arc::from(Path::new("b.rs")),
+ },
+ server_id,
+ DiagnosticSummary {
+ error_count: 1,
+ warning_count: 0,
+ }
+ )]
+ );
+ assert_eq!(project.diagnostic_summary(false, cx).error_count, 0);
+ assert_eq!(project.diagnostic_summary(true, cx).error_count, 1);
});
}
@@ -1145,7 +1214,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
});
project.read_with(cx, |project, cx| {
assert_eq!(
- project.diagnostic_summary(cx),
+ project.diagnostic_summary(false, cx),
DiagnosticSummary {
error_count: 1,
warning_count: 0,
@@ -1171,7 +1240,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
});
project.read_with(cx, |project, cx| {
assert_eq!(
- project.diagnostic_summary(cx),
+ project.diagnostic_summary(false, cx),
DiagnosticSummary {
error_count: 0,
warning_count: 0,
@@ -1763,7 +1832,7 @@ async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppC
.unwrap();
assert_eq!(
- project.diagnostic_summary(cx),
+ project.diagnostic_summary(false, cx),
DiagnosticSummary {
error_count: 2,
warning_count: 0,
@@ -2677,8 +2677,9 @@ impl Project {
})?;
for (adapter, server) in servers {
- let workspace_config =
- cx.update(|cx| adapter.workspace_configuration(cx))?.await;
+ let workspace_config = cx
+ .update(|cx| adapter.workspace_configuration(server.root_path(), cx))?
+ .await;
server
.notify::<lsp::notification::DidChangeConfiguration>(
lsp::DidChangeConfigurationParams {
@@ -2790,7 +2791,7 @@ impl Project {
stderr_capture.clone(),
language.clone(),
adapter.clone(),
- worktree_path,
+ Arc::clone(&worktree_path),
ProjectLspAdapterDelegate::new(self, cx),
cx,
) {
@@ -2822,6 +2823,7 @@ impl Project {
cx.spawn(move |this, mut cx| async move {
let result = Self::setup_and_insert_language_server(
this.clone(),
+ &worktree_path,
initialization_options,
pending_server,
adapter.clone(),
@@ -2942,6 +2944,7 @@ impl Project {
async fn setup_and_insert_language_server(
this: WeakModel<Self>,
+ worktree_path: &Path,
initialization_options: Option<serde_json::Value>,
pending_server: PendingLanguageServer,
adapter: Arc<CachedLspAdapter>,
@@ -2954,6 +2957,7 @@ impl Project {
this.clone(),
initialization_options,
pending_server,
+ worktree_path,
adapter.clone(),
server_id,
cx,
@@ -2983,11 +2987,14 @@ impl Project {
this: WeakModel<Self>,
initialization_options: Option<serde_json::Value>,
pending_server: PendingLanguageServer,
+ worktree_path: &Path,
adapter: Arc<CachedLspAdapter>,
server_id: LanguageServerId,
cx: &mut AsyncAppContext,
) -> Result<Arc<LanguageServer>> {
- let workspace_config = cx.update(|cx| adapter.workspace_configuration(cx))?.await;
+ let workspace_config = cx
+ .update(|cx| adapter.workspace_configuration(worktree_path, cx))?
+ .await;
let language_server = pending_server.task.await?;
language_server
@@ -3016,11 +3023,14 @@ impl Project {
language_server
.on_request::<lsp::request::WorkspaceConfiguration, _, _>({
let adapter = adapter.clone();
+ let worktree_path = worktree_path.to_path_buf();
move |params, cx| {
let adapter = adapter.clone();
+ let worktree_path = worktree_path.clone();
async move {
- let workspace_config =
- cx.update(|cx| adapter.workspace_configuration(cx))?.await;
+ let workspace_config = cx
+ .update(|cx| adapter.workspace_configuration(&worktree_path, cx))?
+ .await;
Ok(params
.items
.into_iter()
@@ -6596,9 +6606,15 @@ impl Project {
})
}
- pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary {
+ pub fn diagnostic_summary(&self, include_ignored: bool, cx: &AppContext) -> DiagnosticSummary {
let mut summary = DiagnosticSummary::default();
- for (_, _, path_summary) in self.diagnostic_summaries(cx) {
+ for (_, _, path_summary) in
+ self.diagnostic_summaries(include_ignored, cx)
+ .filter(|(path, _, _)| {
+ let worktree = self.entry_for_path(&path, cx).map(|entry| entry.is_ignored);
+ include_ignored || worktree == Some(false)
+ })
+ {
summary.error_count += path_summary.error_count;
summary.warning_count += path_summary.warning_count;
}
@@ -6607,17 +6623,23 @@ impl Project {
pub fn diagnostic_summaries<'a>(
&'a self,
+ include_ignored: bool,
cx: &'a AppContext,
) -> impl Iterator<Item = (ProjectPath, LanguageServerId, DiagnosticSummary)> + 'a {
- self.visible_worktrees(cx).flat_map(move |worktree| {
- let worktree = worktree.read(cx);
- let worktree_id = worktree.id();
- worktree
- .diagnostic_summaries()
- .map(move |(path, server_id, summary)| {
- (ProjectPath { worktree_id, path }, server_id, summary)
- })
- })
+ self.visible_worktrees(cx)
+ .flat_map(move |worktree| {
+ let worktree = worktree.read(cx);
+ let worktree_id = worktree.id();
+ worktree
+ .diagnostic_summaries()
+ .map(move |(path, server_id, summary)| {
+ (ProjectPath { worktree_id, path }, server_id, summary)
+ })
+ })
+ .filter(move |(path, _, _)| {
+ let worktree = self.entry_for_path(&path, cx).map(|entry| entry.is_ignored);
+ include_ignored || worktree == Some(false)
+ })
}
pub fn disk_based_diagnostics_started(
@@ -823,7 +823,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
}
#[gpui::test]
-async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
+async fn test_omitted_diagnostics(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
@@ -831,7 +831,12 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
"/root",
json!({
"dir": {
+ ".git": {
+ "HEAD": "ref: refs/heads/main",
+ },
+ ".gitignore": "b.rs",
"a.rs": "let a = 1;",
+ "b.rs": "let b = 2;",
},
"other.rs": "let b = c;"
}),
@@ -839,6 +844,13 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
.await;
let project = Project::test(fs, ["/root/dir".as_ref()], cx).await;
+ let (worktree, _) = project
+ .update(cx, |project, cx| {
+ project.find_or_create_local_worktree("/root/dir", true, cx)
+ })
+ .await
+ .unwrap();
+ let main_worktree_id = worktree.read_with(cx, |tree, _| tree.id());
let (worktree, _) = project
.update(cx, |project, cx| {
@@ -846,12 +858,30 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
})
.await
.unwrap();
- let worktree_id = worktree.update(cx, |tree, _| tree.id());
+ let other_worktree_id = worktree.update(cx, |tree, _| tree.id());
+ let server_id = LanguageServerId(0);
project.update(cx, |project, cx| {
project
.update_diagnostics(
- LanguageServerId(0),
+ server_id,
+ lsp::PublishDiagnosticsParams {
+ uri: Url::from_file_path("/root/dir/b.rs").unwrap(),
+ version: None,
+ diagnostics: vec![lsp::Diagnostic {
+ range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 5)),
+ severity: Some(lsp::DiagnosticSeverity::ERROR),
+ message: "unused variable 'b'".to_string(),
+ ..Default::default()
+ }],
+ },
+ &[],
+ cx,
+ )
+ .unwrap();
+ project
+ .update_diagnostics(
+ server_id,
lsp::PublishDiagnosticsParams {
uri: Url::from_file_path("/root/other.rs").unwrap(),
version: None,
@@ -868,11 +898,34 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
.unwrap();
});
- let buffer = project
- .update(cx, |project, cx| project.open_buffer((worktree_id, ""), cx))
+ let main_ignored_buffer = project
+ .update(cx, |project, cx| {
+ project.open_buffer((main_worktree_id, "b.rs"), cx)
+ })
.await
.unwrap();
- buffer.update(cx, |buffer, _| {
+ main_ignored_buffer.update(cx, |buffer, _| {
+ let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
+ assert_eq!(
+ chunks
+ .iter()
+ .map(|(s, d)| (s.as_str(), *d))
+ .collect::<Vec<_>>(),
+ &[
+ ("let ", None),
+ ("b", Some(DiagnosticSeverity::ERROR)),
+ (" = 2;", None),
+ ],
+ "Gigitnored buffers should still get in-buffer diagnostics",
+ );
+ });
+ let other_buffer = project
+ .update(cx, |project, cx| {
+ project.open_buffer((other_worktree_id, ""), cx)
+ })
+ .await
+ .unwrap();
+ other_buffer.update(cx, |buffer, _| {
let chunks = chunks_with_diagnostics(buffer, 0..buffer.len());
assert_eq!(
chunks
@@ -883,13 +936,29 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
("let b = ", None),
("c", Some(DiagnosticSeverity::ERROR)),
(";", None),
- ]
+ ],
+ "Buffers from hidden projects should still get in-buffer diagnostics"
);
});
project.update(cx, |project, cx| {
- assert_eq!(project.diagnostic_summaries(cx).next(), None);
- assert_eq!(project.diagnostic_summary(cx).error_count, 0);
+ assert_eq!(project.diagnostic_summaries(false, cx).next(), None);
+ assert_eq!(
+ project.diagnostic_summaries(true, cx).collect::<Vec<_>>(),
+ vec![(
+ ProjectPath {
+ worktree_id: main_worktree_id,
+ path: Arc::from(Path::new("b.rs")),
+ },
+ server_id,
+ DiagnosticSummary {
+ error_count: 1,
+ warning_count: 0,
+ }
+ )]
+ );
+ assert_eq!(project.diagnostic_summary(false, cx).error_count, 0);
+ assert_eq!(project.diagnostic_summary(true, cx).error_count, 1);
});
}
@@ -1162,7 +1231,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
});
project.update(cx, |project, cx| {
assert_eq!(
- project.diagnostic_summary(cx),
+ project.diagnostic_summary(false, cx),
DiagnosticSummary {
error_count: 1,
warning_count: 0,
@@ -1188,7 +1257,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
});
project.update(cx, |project, cx| {
assert_eq!(
- project.diagnostic_summary(cx),
+ project.diagnostic_summary(false, cx),
DiagnosticSummary {
error_count: 0,
warning_count: 0,
@@ -1777,7 +1846,7 @@ async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppC
.unwrap();
assert_eq!(
- project.diagnostic_summary(cx),
+ project.diagnostic_summary(false, cx),
DiagnosticSummary {
error_count: 2,
warning_count: 0,
@@ -1627,9 +1627,21 @@ impl View for ProjectPanel {
}
}
- fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
+ fn update_keymap_context(&self, keymap: &mut KeymapContext, cx: &AppContext) {
Self::reset_to_default_keymap_context(keymap);
keymap.add_identifier("menu");
+
+ if let Some(window) = cx.active_window() {
+ window.read_with(cx, |cx| {
+ let identifier = if self.filename_editor.is_focused(cx) {
+ "editing"
+ } else {
+ "not_editing"
+ };
+
+ keymap.add_identifier(identifier);
+ });
+ }
}
fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
@@ -10,9 +10,9 @@ use anyhow::{anyhow, Result};
use gpui::{
actions, div, overlay, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,
ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle, Focusable, FocusableView,
- InteractiveElement, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point,
- PromptLevel, Render, Stateful, Styled, Subscription, Task, UniformListScrollHandle, View,
- ViewContext, VisualContext as _, WeakView, WindowContext,
+ InteractiveElement, KeyContext, Model, MouseButton, MouseDownEvent, ParentElement, Pixels,
+ Point, PromptLevel, Render, Stateful, Styled, Subscription, Task, UniformListScrollHandle,
+ View, ViewContext, VisualContext as _, WeakView, WindowContext,
};
use menu::{Confirm, SelectNext, SelectPrev};
use project::{
@@ -29,8 +29,7 @@ use std::{
path::Path,
sync::Arc,
};
-use theme::ActiveTheme as _;
-use ui::{v_stack, ContextMenu, IconElement, Label, ListItem};
+use ui::{prelude::*, v_stack, ContextMenu, IconElement, Label, ListItem};
use unicase::UniCase;
use util::{maybe, ResultExt, TryFutureExt};
use workspace::{
@@ -1421,6 +1420,22 @@ impl ProjectPanel {
// );
// })
}
+
+ fn dispatch_context(&self, cx: &ViewContext<Self>) -> KeyContext {
+ let mut dispatch_context = KeyContext::default();
+ dispatch_context.add("ProjectPanel");
+ dispatch_context.add("menu");
+
+ let identifier = if self.filename_editor.focus_handle(cx).is_focused(cx) {
+ "editing"
+ } else {
+ "not_editing"
+ };
+
+ dispatch_context.add(identifier);
+
+ dispatch_context
+ }
}
impl Render for ProjectPanel {
@@ -1434,7 +1449,7 @@ impl Render for ProjectPanel {
.id("project-panel")
.size_full()
.relative()
- .key_context("ProjectPanel")
+ .key_context(self.dispatch_context(cx))
.on_action(cx.listener(Self::select_next))
.on_action(cx.listener(Self::select_prev))
.on_action(cx.listener(Self::expand_selected_entry))
@@ -2845,7 +2860,7 @@ mod tests {
let worktree = worktree.read(cx);
if let Ok(relative_path) = path.strip_prefix(worktree.root_name()) {
let entry_id = worktree.entry_for_path(relative_path).unwrap().id;
- panel.selection = Some(Selection {
+ panel.selection = Some(crate::Selection {
worktree_id: worktree.id(),
entry_id,
});
@@ -4,7 +4,7 @@ use gpui::{actions, Action, AppContext, IntoElement};
pub use mode::SearchMode;
use project::search::SearchQuery;
use ui::prelude::*;
-use ui::{ButtonStyle2, Icon, IconButton};
+use ui::{ButtonStyle, Icon, IconButton};
//pub use project_search::{ProjectSearchBar, ProjectSearchView};
// use theme::components::{
// action_button::Button, svg::Svg, ComponentExt, IconButtonStyle, ToggleIconButtonStyle,
@@ -91,8 +91,8 @@ impl SearchOptions {
cx.dispatch_action(action.boxed_clone());
}
})
- .style(ButtonStyle2::Subtle)
- .when(active, |button| button.style(ButtonStyle2::Filled))
+ .style(ButtonStyle::Subtle)
+ .when(active, |button| button.style(ButtonStyle::Filled))
}
}
@@ -103,8 +103,8 @@ fn toggle_replace_button(active: bool) -> impl IntoElement {
cx.dispatch_action(Box::new(ToggleReplace));
cx.notify();
})
- .style(ButtonStyle2::Subtle)
- .when(active, |button| button.style(ButtonStyle2::Filled))
+ .style(ButtonStyle::Subtle)
+ .when(active, |button| button.style(ButtonStyle::Filled))
}
fn render_replace_button(
@@ -24,6 +24,7 @@ pub enum ComponentStory {
Keybinding,
Label,
List,
+ ListHeader,
ListItem,
Scroll,
Text,
@@ -46,6 +47,7 @@ impl ComponentStory {
Self::Keybinding => cx.build_view(|_| ui::KeybindingStory).into(),
Self::Label => cx.build_view(|_| ui::LabelStory).into(),
Self::List => cx.build_view(|_| ui::ListStory).into(),
+ Self::ListHeader => cx.build_view(|_| ui::ListHeaderStory).into(),
Self::ListItem => cx.build_view(|_| ui::ListItemStory).into(),
Self::Scroll => ScrollStory::view(cx).into(),
Self::Text => TextStory::view(cx).into(),
@@ -2,14 +2,14 @@ use feature_flags::FeatureFlagAppExt;
use fs::Fs;
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
use gpui::{
- actions, AppContext, DismissEvent, EventEmitter, FocusableView, ParentElement, Render,
- SharedString, View, ViewContext, VisualContext, WeakView,
+ actions, AppContext, DismissEvent, EventEmitter, FocusableView, Render, SharedString, View,
+ ViewContext, VisualContext, WeakView,
};
use picker::{Picker, PickerDelegate};
use settings::{update_settings_file, SettingsStore};
use std::sync::Arc;
-use theme::{ActiveTheme, Theme, ThemeRegistry, ThemeSettings};
-use ui::ListItem;
+use theme::{Theme, ThemeRegistry, ThemeSettings};
+use ui::{prelude::*, ListItem};
use util::ResultExt;
use workspace::{ui::HighlightedLabel, Workspace};
@@ -1,7 +1,7 @@
use gpui::AnyView;
use crate::prelude::*;
-use crate::{ButtonCommon, ButtonLike, ButtonSize2, ButtonStyle2, Label, LineHeightStyle};
+use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Label, LineHeightStyle};
#[derive(IntoElement)]
pub struct Button {
@@ -54,12 +54,12 @@ impl ButtonCommon for Button {
self.base.id()
}
- fn style(mut self, style: ButtonStyle2) -> Self {
+ fn style(mut self, style: ButtonStyle) -> Self {
self.base = self.base.style(style);
self
}
- fn size(mut self, size: ButtonSize2) -> Self {
+ fn size(mut self, size: ButtonSize) -> Self {
self.base = self.base.size(size);
self
}
@@ -79,7 +79,7 @@ impl RenderOnce for Button {
} else if self.base.selected {
Color::Selected
} else {
- Color::Default
+ self.label_color.unwrap_or_default()
};
self.base.child(
@@ -1,4 +1,4 @@
-use gpui::{rems, AnyElement, AnyView, ClickEvent, Div, Hsla, Rems, Stateful};
+use gpui::{rems, transparent_black, AnyElement, AnyView, ClickEvent, Div, Hsla, Rems, Stateful};
use smallvec::SmallVec;
use crate::h_stack;
@@ -6,13 +6,13 @@ use crate::prelude::*;
pub trait ButtonCommon: Clickable + Disableable {
fn id(&self) -> &ElementId;
- fn style(self, style: ButtonStyle2) -> Self;
- fn size(self, size: ButtonSize2) -> Self;
+ fn style(self, style: ButtonStyle) -> Self;
+ fn size(self, size: ButtonSize) -> Self;
fn tooltip(self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self;
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
-pub enum ButtonStyle2 {
+pub enum ButtonStyle {
#[default]
Filled,
// Tinted,
@@ -21,54 +21,57 @@ pub enum ButtonStyle2 {
}
#[derive(Debug, Clone)]
-pub struct ButtonStyle {
+pub(crate) struct ButtonLikeStyles {
pub background: Hsla,
+ #[allow(unused)]
pub border_color: Hsla,
+ #[allow(unused)]
pub label_color: Hsla,
+ #[allow(unused)]
pub icon_color: Hsla,
}
-impl ButtonStyle2 {
- pub fn enabled(self, cx: &mut WindowContext) -> ButtonStyle {
+impl ButtonStyle {
+ pub(crate) fn enabled(self, cx: &mut WindowContext) -> ButtonLikeStyles {
match self {
- ButtonStyle2::Filled => ButtonStyle {
+ ButtonStyle::Filled => ButtonLikeStyles {
background: cx.theme().colors().element_background,
- border_color: gpui::transparent_black(),
+ border_color: transparent_black(),
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
- ButtonStyle2::Subtle => ButtonStyle {
+ ButtonStyle::Subtle => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_background,
- border_color: gpui::transparent_black(),
+ border_color: transparent_black(),
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
- ButtonStyle2::Transparent => ButtonStyle {
- background: gpui::transparent_black(),
- border_color: gpui::transparent_black(),
+ ButtonStyle::Transparent => ButtonLikeStyles {
+ background: transparent_black(),
+ border_color: transparent_black(),
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
}
}
- pub fn hovered(self, cx: &mut WindowContext) -> ButtonStyle {
+ pub(crate) fn hovered(self, cx: &mut WindowContext) -> ButtonLikeStyles {
match self {
- ButtonStyle2::Filled => ButtonStyle {
+ ButtonStyle::Filled => ButtonLikeStyles {
background: cx.theme().colors().element_hover,
- border_color: gpui::transparent_black(),
+ border_color: transparent_black(),
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
- ButtonStyle2::Subtle => ButtonStyle {
+ ButtonStyle::Subtle => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_hover,
- border_color: gpui::transparent_black(),
+ border_color: transparent_black(),
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
- ButtonStyle2::Transparent => ButtonStyle {
- background: gpui::transparent_black(),
- border_color: gpui::transparent_black(),
+ ButtonStyle::Transparent => ButtonLikeStyles {
+ background: transparent_black(),
+ border_color: transparent_black(),
// TODO: These are not great
label_color: Color::Muted.color(cx),
// TODO: These are not great
@@ -77,23 +80,23 @@ impl ButtonStyle2 {
}
}
- pub fn active(self, cx: &mut WindowContext) -> ButtonStyle {
+ pub(crate) fn active(self, cx: &mut WindowContext) -> ButtonLikeStyles {
match self {
- ButtonStyle2::Filled => ButtonStyle {
+ ButtonStyle::Filled => ButtonLikeStyles {
background: cx.theme().colors().element_active,
- border_color: gpui::transparent_black(),
+ border_color: transparent_black(),
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
- ButtonStyle2::Subtle => ButtonStyle {
+ ButtonStyle::Subtle => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_active,
- border_color: gpui::transparent_black(),
+ border_color: transparent_black(),
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
- ButtonStyle2::Transparent => ButtonStyle {
- background: gpui::transparent_black(),
- border_color: gpui::transparent_black(),
+ ButtonStyle::Transparent => ButtonLikeStyles {
+ background: transparent_black(),
+ border_color: transparent_black(),
// TODO: These are not great
label_color: Color::Muted.color(cx),
// TODO: These are not great
@@ -102,22 +105,23 @@ impl ButtonStyle2 {
}
}
- pub fn focused(self, cx: &mut WindowContext) -> ButtonStyle {
+ #[allow(unused)]
+ pub(crate) fn focused(self, cx: &mut WindowContext) -> ButtonLikeStyles {
match self {
- ButtonStyle2::Filled => ButtonStyle {
+ ButtonStyle::Filled => ButtonLikeStyles {
background: cx.theme().colors().element_background,
border_color: cx.theme().colors().border_focused,
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
- ButtonStyle2::Subtle => ButtonStyle {
+ ButtonStyle::Subtle => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_background,
border_color: cx.theme().colors().border_focused,
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
- ButtonStyle2::Transparent => ButtonStyle {
- background: gpui::transparent_black(),
+ ButtonStyle::Transparent => ButtonLikeStyles {
+ background: transparent_black(),
border_color: cx.theme().colors().border_focused,
label_color: Color::Accent.color(cx),
icon_color: Color::Accent.color(cx),
@@ -125,23 +129,23 @@ impl ButtonStyle2 {
}
}
- pub fn disabled(self, cx: &mut WindowContext) -> ButtonStyle {
+ pub(crate) fn disabled(self, cx: &mut WindowContext) -> ButtonLikeStyles {
match self {
- ButtonStyle2::Filled => ButtonStyle {
+ ButtonStyle::Filled => ButtonLikeStyles {
background: cx.theme().colors().element_disabled,
border_color: cx.theme().colors().border_disabled,
label_color: Color::Disabled.color(cx),
icon_color: Color::Disabled.color(cx),
},
- ButtonStyle2::Subtle => ButtonStyle {
+ ButtonStyle::Subtle => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_disabled,
border_color: cx.theme().colors().border_disabled,
label_color: Color::Disabled.color(cx),
icon_color: Color::Disabled.color(cx),
},
- ButtonStyle2::Transparent => ButtonStyle {
- background: gpui::transparent_black(),
- border_color: gpui::transparent_black(),
+ ButtonStyle::Transparent => ButtonLikeStyles {
+ background: transparent_black(),
+ border_color: transparent_black(),
label_color: Color::Disabled.color(cx),
icon_color: Color::Disabled.color(cx),
},
@@ -150,19 +154,19 @@ impl ButtonStyle2 {
}
#[derive(Default, PartialEq, Clone, Copy)]
-pub enum ButtonSize2 {
+pub enum ButtonSize {
#[default]
Default,
Compact,
None,
}
-impl ButtonSize2 {
+impl ButtonSize {
fn height(self) -> Rems {
match self {
- ButtonSize2::Default => rems(22. / 16.),
- ButtonSize2::Compact => rems(18. / 16.),
- ButtonSize2::None => rems(16. / 16.),
+ ButtonSize::Default => rems(22. / 16.),
+ ButtonSize::Compact => rems(18. / 16.),
+ ButtonSize::None => rems(16. / 16.),
}
}
}
@@ -170,10 +174,10 @@ impl ButtonSize2 {
#[derive(IntoElement)]
pub struct ButtonLike {
id: ElementId,
- pub(super) style: ButtonStyle2,
+ pub(super) style: ButtonStyle,
pub(super) disabled: bool,
pub(super) selected: bool,
- size: ButtonSize2,
+ size: ButtonSize,
tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
children: SmallVec<[AnyElement; 2]>,
@@ -183,10 +187,10 @@ impl ButtonLike {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
- style: ButtonStyle2::default(),
+ style: ButtonStyle::default(),
disabled: false,
selected: false,
- size: ButtonSize2::Default,
+ size: ButtonSize::Default,
tooltip: None,
children: SmallVec::new(),
on_click: None,
@@ -220,12 +224,12 @@ impl ButtonCommon for ButtonLike {
&self.id
}
- fn style(mut self, style: ButtonStyle2) -> Self {
+ fn style(mut self, style: ButtonStyle) -> Self {
self.style = style;
self
}
- fn size(mut self, size: ButtonSize2) -> Self {
+ fn size(mut self, size: ButtonSize) -> Self {
self.size = size;
self
}
@@ -1,7 +1,7 @@
use gpui::{Action, AnyView};
use crate::prelude::*;
-use crate::{ButtonCommon, ButtonLike, ButtonSize2, ButtonStyle2, Icon, IconElement, IconSize};
+use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconElement, IconSize};
#[derive(IntoElement)]
pub struct IconButton {
@@ -65,12 +65,12 @@ impl ButtonCommon for IconButton {
self.base.id()
}
- fn style(mut self, style: ButtonStyle2) -> Self {
+ fn style(mut self, style: ButtonStyle) -> Self {
self.base = self.base.style(style);
self
}
- fn size(mut self, size: ButtonSize2) -> Self {
+ fn size(mut self, size: ButtonSize) -> Self {
self.base = self.base.size(size);
self
}
@@ -1,73 +1,11 @@
+mod list;
mod list_header;
mod list_item;
mod list_separator;
mod list_sub_header;
-use gpui::{AnyElement, Div};
-use smallvec::SmallVec;
-
-use crate::prelude::*;
-use crate::{v_stack, Label};
-
+pub use list::*;
pub use list_header::*;
pub use list_item::*;
pub use list_separator::*;
pub use list_sub_header::*;
-
-#[derive(IntoElement)]
-pub struct List {
- /// Message to display when the list is empty
- /// Defaults to "No items"
- empty_message: SharedString,
- header: Option<ListHeader>,
- toggle: Option<bool>,
- children: SmallVec<[AnyElement; 2]>,
-}
-
-impl List {
- pub fn new() -> Self {
- Self {
- empty_message: "No items".into(),
- header: None,
- toggle: None,
- children: SmallVec::new(),
- }
- }
-
- pub fn empty_message(mut self, empty_message: impl Into<SharedString>) -> Self {
- self.empty_message = empty_message.into();
- self
- }
-
- pub fn header(mut self, header: ListHeader) -> Self {
- self.header = Some(header);
- self
- }
-
- pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
- self.toggle = toggle.into();
- self
- }
-}
-
-impl ParentElement for List {
- fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
- &mut self.children
- }
-}
-
-impl RenderOnce for List {
- type Rendered = Div;
-
- fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
- v_stack()
- .w_full()
- .py_1()
- .children(self.header.map(|header| header))
- .map(|this| match (self.children.is_empty(), self.toggle) {
- (false, _) => this.children(self.children),
- (true, Some(false)) => this,
- (true, _) => this.child(Label::new(self.empty_message.clone()).color(Color::Muted)),
- })
- }
-}
@@ -0,0 +1,60 @@
+use gpui::{AnyElement, Div};
+use smallvec::SmallVec;
+
+use crate::{prelude::*, v_stack, Label, ListHeader};
+
+#[derive(IntoElement)]
+pub struct List {
+ /// Message to display when the list is empty
+ /// Defaults to "No items"
+ empty_message: SharedString,
+ header: Option<ListHeader>,
+ toggle: Option<bool>,
+ children: SmallVec<[AnyElement; 2]>,
+}
+
+impl List {
+ pub fn new() -> Self {
+ Self {
+ empty_message: "No items".into(),
+ header: None,
+ toggle: None,
+ children: SmallVec::new(),
+ }
+ }
+
+ pub fn empty_message(mut self, empty_message: impl Into<SharedString>) -> Self {
+ self.empty_message = empty_message.into();
+ self
+ }
+
+ pub fn header(mut self, header: impl Into<Option<ListHeader>>) -> Self {
+ self.header = header.into();
+ self
+ }
+
+ pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
+ self.toggle = toggle.into();
+ self
+ }
+}
+
+impl ParentElement for List {
+ fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
+ &mut self.children
+ }
+}
+
+impl RenderOnce for List {
+ type Rendered = Div;
+
+ fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
+ v_stack().w_full().py_1().children(self.header).map(|this| {
+ match (self.children.is_empty(), self.toggle) {
+ (false, _) => this.children(self.children),
+ (true, Some(false)) => this,
+ (true, _) => this.child(Label::new(self.empty_message.clone()).color(Color::Muted)),
+ }
+ })
+ }
+}
@@ -1,22 +1,16 @@
use std::rc::Rc;
-use gpui::{ClickEvent, Div};
+use gpui::{AnyElement, ClickEvent, Div};
+use smallvec::SmallVec;
use crate::prelude::*;
-use crate::{h_stack, Disclosure, Icon, IconButton, IconElement, IconSize, Label};
-
-pub enum ListHeaderMeta {
- Tools(Vec<IconButton>),
- // TODO: This should be a button
- Button(Label),
- Text(Label),
-}
+use crate::{h_stack, Disclosure, Icon, IconElement, IconSize, Label};
#[derive(IntoElement)]
pub struct ListHeader {
label: SharedString,
left_icon: Option<Icon>,
- meta: Option<ListHeaderMeta>,
+ meta: SmallVec<[AnyElement; 2]>,
toggle: Option<bool>,
on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
inset: bool,
@@ -28,7 +22,7 @@ impl ListHeader {
Self {
label: label.into(),
left_icon: None,
- meta: None,
+ meta: SmallVec::new(),
inset: false,
toggle: None,
on_toggle: None,
@@ -49,21 +43,19 @@ impl ListHeader {
self
}
- pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
- self.left_icon = left_icon;
+ pub fn left_icon(mut self, left_icon: impl Into<Option<Icon>>) -> Self {
+ self.left_icon = left_icon.into();
self
}
- pub fn right_button(self, button: IconButton) -> Self {
- self.meta(Some(ListHeaderMeta::Tools(vec![button])))
- }
-
- pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
- self.meta = meta;
+ pub fn meta(mut self, meta: impl IntoElement) -> Self {
+ self.meta.push(meta.into_any_element());
self
}
+}
- pub fn selected(mut self, selected: bool) -> Self {
+impl Selectable for ListHeader {
+ fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
@@ -73,18 +65,6 @@ impl RenderOnce for ListHeader {
type Rendered = Div;
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
- let meta = match self.meta {
- Some(ListHeaderMeta::Tools(icons)) => div().child(
- h_stack()
- .gap_2()
- .items_center()
- .children(icons.into_iter().map(|i| i.icon_color(Color::Muted))),
- ),
- Some(ListHeaderMeta::Button(label)) => div().child(label),
- Some(ListHeaderMeta::Text(label)) => div().child(label),
- None => div(),
- };
-
h_stack().w_full().relative().child(
div()
.h_5()
@@ -118,7 +98,7 @@ impl RenderOnce for ListHeader {
.map(|is_open| Disclosure::new(is_open).on_toggle(self.on_toggle)),
),
)
- .child(meta),
+ .child(h_stack().gap_2().items_center().children(self.meta)),
)
}
}
@@ -83,11 +83,6 @@ impl ListItem {
self
}
- pub fn selected(mut self, selected: bool) -> Self {
- self.selected = selected;
- self
- }
-
pub fn left_child(mut self, left_content: impl IntoElement) -> Self {
self.left_slot = Some(left_content.into_any_element());
self
@@ -109,6 +104,13 @@ impl ListItem {
}
}
+impl Selectable for ListItem {
+ fn selected(mut self, selected: bool) -> Self {
+ self.selected = selected;
+ self
+ }
+}
+
impl ParentElement for ListItem {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
&mut self.children
@@ -8,6 +8,7 @@ mod icon_button;
mod keybinding;
mod label;
mod list;
+mod list_header;
mod list_item;
pub use avatar::*;
@@ -20,4 +21,5 @@ pub use icon_button::*;
pub use keybinding::*;
pub use label::*;
pub use list::*;
+pub use list_header::*;
pub use list_item::*;
@@ -2,7 +2,7 @@ use gpui::{Div, Render};
use story::Story;
use crate::prelude::*;
-use crate::{Button, ButtonStyle2};
+use crate::{Button, ButtonStyle};
pub struct ButtonStory;
@@ -14,9 +14,13 @@ impl Render for ButtonStory {
.child(Story::title_for::<Button>())
.child(Story::label("Default"))
.child(Button::new("default_filled", "Click me"))
+ .child(Story::label("Selected"))
+ .child(Button::new("selected_filled", "Click me").selected(true))
+ .child(Story::label("With `label_color`"))
+ .child(Button::new("filled_with_label_color", "Click me").color(Color::Created))
.child(Story::label("Default (Subtle)"))
- .child(Button::new("default_subtle", "Click me").style(ButtonStyle2::Subtle))
+ .child(Button::new("default_subtle", "Click me").style(ButtonStyle::Subtle))
.child(Story::label("Default (Transparent)"))
- .child(Button::new("default_transparent", "Click me").style(ButtonStyle2::Transparent))
+ .child(Button::new("default_transparent", "Click me").style(ButtonStyle::Transparent))
}
}
@@ -22,12 +22,12 @@ impl Render for ListStory {
.child(Story::label("With sections"))
.child(
List::new()
- .child(ListHeader::new("Fruits"))
+ .header(ListHeader::new("Produce"))
+ .child(ListSubHeader::new("Fruits"))
.child(ListItem::new("apple").child("Apple"))
.child(ListItem::new("banana").child("Banana"))
.child(ListItem::new("cherry").child("Cherry"))
.child(ListSeparator)
- .child(ListHeader::new("Vegetables"))
.child(ListSubHeader::new("Root Vegetables"))
.child(ListItem::new("carrot").child("Carrot"))
.child(ListItem::new("potato").child("Potato"))
@@ -0,0 +1,33 @@
+use gpui::{Div, Render};
+use story::Story;
+
+use crate::{prelude::*, IconButton};
+use crate::{Icon, ListHeader};
+
+pub struct ListHeaderStory;
+
+impl Render for ListHeaderStory {
+ type Element = Div;
+
+ fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
+ Story::container()
+ .child(Story::title_for::<ListHeader>())
+ .child(Story::label("Default"))
+ .child(ListHeader::new("Section 1"))
+ .child(Story::label("With left icon"))
+ .child(ListHeader::new("Section 2").left_icon(Icon::Bell))
+ .child(Story::label("With left icon and meta"))
+ .child(
+ ListHeader::new("Section 3")
+ .left_icon(Icon::BellOff)
+ .meta(IconButton::new("action_1", Icon::Bolt)),
+ )
+ .child(Story::label("With multiple meta"))
+ .child(
+ ListHeader::new("Section 4")
+ .meta(IconButton::new("action_1", Icon::Bolt))
+ .meta(IconButton::new("action_2", Icon::ExclamationTriangle))
+ .meta(IconButton::new("action_3", Icon::Plus)),
+ )
+ }
+}
@@ -1,12 +0,0 @@
-use gpui::{ImageSource, SharedString};
-
-use crate::Icon;
-
-/// A slot utility that provides a way to to pass either
-/// an icon or an image to a component.
-#[derive(Debug, Clone)]
-pub enum GraphicSlot {
- Icon(Icon),
- Avatar(ImageSource),
- PublicActor(SharedString),
-}
@@ -18,7 +18,6 @@ mod disableable;
mod fixed;
pub mod prelude;
mod selectable;
-mod slot;
mod styled_ext;
mod styles;
pub mod utils;
@@ -29,6 +28,5 @@ pub use disableable::*;
pub use fixed::*;
pub use prelude::*;
pub use selectable::*;
-pub use slot::*;
pub use styled_ext::*;
pub use styles::*;
@@ -1,14 +1,14 @@
use super::base_keymap_setting::BaseKeymap;
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
use gpui::{
- actions, AppContext, DismissEvent, EventEmitter, FocusableView, ParentElement, Render, Task,
- View, ViewContext, VisualContext, WeakView,
+ actions, AppContext, DismissEvent, EventEmitter, FocusableView, Render, Task, View,
+ ViewContext, VisualContext, WeakView,
};
use picker::{Picker, PickerDelegate};
use project::Fs;
use settings::{update_settings_file, Settings};
use std::sync::Arc;
-use ui::ListItem;
+use ui::{prelude::*, ListItem};
use util::ResultExt;
use workspace::{ui::HighlightedLabel, Workspace};
@@ -10,29 +10,29 @@ doctest = false
[features]
test-support = [
- "call2/test-support",
- "client2/test-support",
- "project2/test-support",
- "settings2/test-support",
+ "call/test-support",
+ "client/test-support",
+ "project/test-support",
+ "settings/test-support",
"gpui/test-support",
- "fs2/test-support"
+ "fs/test-support"
]
[dependencies]
-db2 = { path = "../db2" }
-client2 = { path = "../client2" }
+db = { path = "../db2", package = "db2" }
+client = { path = "../client2", package = "client2" }
collections = { path = "../collections" }
# context_menu = { path = "../context_menu" }
-fs2 = { path = "../fs2" }
+fs = { path = "../fs2", package = "fs2" }
gpui = { package = "gpui2", path = "../gpui2" }
-install_cli2 = { path = "../install_cli2" }
-language2 = { path = "../language2" }
+install_cli = { path = "../install_cli2", package = "install_cli2" }
+language = { path = "../language2", package = "language2" }
#menu = { path = "../menu" }
node_runtime = { path = "../node_runtime" }
-project2 = { path = "../project2" }
-settings2 = { path = "../settings2" }
-terminal2 = { path = "../terminal2" }
-theme2 = { path = "../theme2" }
+project = { path = "../project2", package = "project2" }
+settings = { path = "../settings2", package = "settings2" }
+terminal = { path = "../terminal2", package = "terminal2" }
+theme = { path = "../theme2", package = "theme2" }
util = { path = "../util" }
ui = { package = "ui2", path = "../ui2" }
@@ -54,13 +54,13 @@ smallvec.workspace = true
uuid.workspace = true
[dev-dependencies]
-call2 = { path = "../call2", features = ["test-support"] }
-client2 = { path = "../client2", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-project2 = { path = "../project2", features = ["test-support"] }
-settings2 = { path = "../settings2", features = ["test-support"] }
-fs2 = { path = "../fs2", features = ["test-support"] }
-db2 = { path = "../db2", features = ["test-support"] }
+call = { path = "../call2", package = "call2", features = ["test-support"] }
+client = { path = "../client2", package = "client2", features = ["test-support"] }
+gpui = { path = "../gpui2", package = "gpui2", features = ["test-support"] }
+project = { path = "../project2", package = "project2", features = ["test-support"] }
+settings = { path = "../settings2", package = "settings2", features = ["test-support"] }
+fs = { path = "../fs2", package = "fs2", features = ["test-support"] }
+db = { path = "../db2", package = "db2", features = ["test-support"] }
indoc.workspace = true
env_logger.workspace = true
@@ -7,7 +7,7 @@ use crate::{
ViewId, Workspace, WorkspaceId,
};
use anyhow::Result;
-use client2::{
+use client::{
proto::{self, PeerId},
Client,
};
@@ -16,10 +16,10 @@ use gpui::{
HighlightStyle, Model, Pixels, Point, SharedString, Task, View, ViewContext, WeakView,
WindowContext,
};
-use project2::{Project, ProjectEntryId, ProjectPath};
+use project::{Project, ProjectEntryId, ProjectPath};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use settings2::Settings;
+use settings::Settings;
use smallvec::SmallVec;
use std::{
any::{Any, TypeId},
@@ -33,7 +33,7 @@ use std::{
},
time::Duration,
};
-use theme2::Theme;
+use theme::Theme;
#[derive(Deserialize)]
pub struct ItemSettings {
@@ -110,7 +110,7 @@ pub trait Item: FocusableView + EventEmitter<ItemEvent> {
fn for_each_project_item(
&self,
_: &AppContext,
- _: &mut dyn FnMut(EntityId, &dyn project2::Item),
+ _: &mut dyn FnMut(EntityId, &dyn project::Item),
) {
}
fn is_singleton(&self, _cx: &AppContext) -> bool {
@@ -222,7 +222,7 @@ pub trait ItemHandle: 'static + Send {
fn for_each_project_item(
&self,
_: &AppContext,
- _: &mut dyn FnMut(EntityId, &dyn project2::Item),
+ _: &mut dyn FnMut(EntityId, &dyn project::Item),
);
fn is_singleton(&self, cx: &AppContext) -> bool;
fn boxed_clone(&self) -> Box<dyn ItemHandle>;
@@ -347,7 +347,7 @@ impl<T: Item> ItemHandle for View<T> {
fn for_each_project_item(
&self,
cx: &AppContext,
- f: &mut dyn FnMut(EntityId, &dyn project2::Item),
+ f: &mut dyn FnMut(EntityId, &dyn project::Item),
) {
self.read(cx).for_each_project_item(cx, f)
}
@@ -375,6 +375,7 @@ impl<T: Item> ItemHandle for View<T> {
pane: View<Pane>,
cx: &mut ViewContext<Workspace>,
) {
+ let weak_item = self.downgrade();
let history = pane.read(cx).nav_history_for_item(self);
self.update(cx, |this, cx| {
this.set_nav_history(history, cx);
@@ -491,16 +492,15 @@ impl<T: Item> ItemHandle for View<T> {
}
}));
- // todo!()
- // cx.observe_focus(self, move |workspace, item, focused, cx| {
- // if !focused
- // && WorkspaceSettings::get_global(cx).autosave == AutosaveSetting::OnFocusChange
- // {
- // Pane::autosave_item(&item, workspace.project.clone(), cx)
- // .detach_and_log_err(cx);
- // }
- // })
- // .detach();
+ cx.on_blur(&self.focus_handle(cx), move |workspace, cx| {
+ if WorkspaceSettings::get_global(cx).autosave == AutosaveSetting::OnFocusChange {
+ if let Some(item) = weak_item.upgrade() {
+ Pane::autosave_item(&item, workspace.project.clone(), cx)
+ .detach_and_log_err(cx);
+ }
+ }
+ })
+ .detach();
let item_id = self.item_id();
cx.observe_release(self, move |workspace, _, _| {
@@ -640,7 +640,7 @@ impl<T: Item> WeakItemHandle for WeakView<T> {
}
pub trait ProjectItem: Item {
- type Item: project2::Item;
+ type Item: project::Item;
fn for_project_item(
project: Model<Project>,
@@ -759,300 +759,310 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
}
}
-// #[cfg(any(test, feature = "test-support"))]
-// pub mod test {
-// use super::{Item, ItemEvent};
-// use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
-// use gpui::{
-// elements::Empty, AnyElement, AppContext, Element, Entity, Model, Task, View,
-// ViewContext, View, WeakViewHandle,
-// };
-// use project2::{Project, ProjectEntryId, ProjectPath, WorktreeId};
-// use smallvec::SmallVec;
-// use std::{any::Any, borrow::Cow, cell::Cell, path::Path};
-
-// pub struct TestProjectItem {
-// pub entry_id: Option<ProjectEntryId>,
-// pub project_path: Option<ProjectPath>,
-// }
-
-// pub struct TestItem {
-// pub workspace_id: WorkspaceId,
-// pub state: String,
-// pub label: String,
-// pub save_count: usize,
-// pub save_as_count: usize,
-// pub reload_count: usize,
-// pub is_dirty: bool,
-// pub is_singleton: bool,
-// pub has_conflict: bool,
-// pub project_items: Vec<Model<TestProjectItem>>,
-// pub nav_history: Option<ItemNavHistory>,
-// pub tab_descriptions: Option<Vec<&'static str>>,
-// pub tab_detail: Cell<Option<usize>>,
-// }
-
-// impl Entity for TestProjectItem {
-// type Event = ();
-// }
-
-// impl project2::Item for TestProjectItem {
-// fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
-// self.entry_id
-// }
-
-// fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
-// self.project_path.clone()
-// }
-// }
-
-// pub enum TestItemEvent {
-// Edit,
-// }
-
-// impl Clone for TestItem {
-// fn clone(&self) -> Self {
-// Self {
-// state: self.state.clone(),
-// label: self.label.clone(),
-// save_count: self.save_count,
-// save_as_count: self.save_as_count,
-// reload_count: self.reload_count,
-// is_dirty: self.is_dirty,
-// is_singleton: self.is_singleton,
-// has_conflict: self.has_conflict,
-// project_items: self.project_items.clone(),
-// nav_history: None,
-// tab_descriptions: None,
-// tab_detail: Default::default(),
-// workspace_id: self.workspace_id,
-// }
-// }
-// }
-
-// impl TestProjectItem {
-// pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Model<Self> {
-// let entry_id = Some(ProjectEntryId::from_proto(id));
-// let project_path = Some(ProjectPath {
-// worktree_id: WorktreeId::from_usize(0),
-// path: Path::new(path).into(),
-// });
-// cx.add_model(|_| Self {
-// entry_id,
-// project_path,
-// })
-// }
-
-// pub fn new_untitled(cx: &mut AppContext) -> Model<Self> {
-// cx.add_model(|_| Self {
-// project_path: None,
-// entry_id: None,
-// })
-// }
-// }
-
-// impl TestItem {
-// pub fn new() -> Self {
-// Self {
-// state: String::new(),
-// label: String::new(),
-// save_count: 0,
-// save_as_count: 0,
-// reload_count: 0,
-// is_dirty: false,
-// has_conflict: false,
-// project_items: Vec::new(),
-// is_singleton: true,
-// nav_history: None,
-// tab_descriptions: None,
-// tab_detail: Default::default(),
-// workspace_id: 0,
-// }
-// }
-
-// pub fn new_deserialized(id: WorkspaceId) -> Self {
-// let mut this = Self::new();
-// this.workspace_id = id;
-// this
-// }
-
-// pub fn with_label(mut self, state: &str) -> Self {
-// self.label = state.to_string();
-// self
-// }
-
-// pub fn with_singleton(mut self, singleton: bool) -> Self {
-// self.is_singleton = singleton;
-// self
-// }
-
-// pub fn with_dirty(mut self, dirty: bool) -> Self {
-// self.is_dirty = dirty;
-// self
-// }
-
-// pub fn with_conflict(mut self, has_conflict: bool) -> Self {
-// self.has_conflict = has_conflict;
-// self
-// }
-
-// pub fn with_project_items(mut self, items: &[Model<TestProjectItem>]) -> Self {
-// self.project_items.clear();
-// self.project_items.extend(items.iter().cloned());
-// self
-// }
-
-// pub fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
-// self.push_to_nav_history(cx);
-// self.state = state;
-// }
-
-// fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
-// if let Some(history) = &mut self.nav_history {
-// history.push(Some(Box::new(self.state.clone())), cx);
-// }
-// }
-// }
-
-// impl Entity for TestItem {
-// type Event = TestItemEvent;
-// }
-
-// impl View for TestItem {
-// fn ui_name() -> &'static str {
-// "TestItem"
-// }
-
-// fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-// Empty::new().into_any()
-// }
-// }
-
-// impl Item for TestItem {
-// fn tab_description(&self, detail: usize, _: &AppContext) -> Option<Cow<str>> {
-// self.tab_descriptions.as_ref().and_then(|descriptions| {
-// let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
-// Some(description.into())
-// })
-// }
-
-// fn tab_content<V: 'static>(
-// &self,
-// detail: Option<usize>,
-// _: &theme2::Tab,
-// _: &AppContext,
-// ) -> AnyElement<V> {
-// self.tab_detail.set(detail);
-// Empty::new().into_any()
-// }
-
-// fn for_each_project_item(
-// &self,
-// cx: &AppContext,
-// f: &mut dyn FnMut(usize, &dyn project2::Item),
-// ) {
-// self.project_items
-// .iter()
-// .for_each(|item| f(item.id(), item.read(cx)))
-// }
-
-// fn is_singleton(&self, _: &AppContext) -> bool {
-// self.is_singleton
-// }
-
-// fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
-// self.nav_history = Some(history);
-// }
-
-// fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
-// let state = *state.downcast::<String>().unwrap_or_default();
-// if state != self.state {
-// self.state = state;
-// true
-// } else {
-// false
-// }
-// }
-
-// fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
-// self.push_to_nav_history(cx);
-// }
-
-// fn clone_on_split(
-// &self,
-// _workspace_id: WorkspaceId,
-// _: &mut ViewContext<Self>,
-// ) -> Option<Self>
-// where
-// Self: Sized,
-// {
-// Some(self.clone())
-// }
-
-// fn is_dirty(&self, _: &AppContext) -> bool {
-// self.is_dirty
-// }
-
-// fn has_conflict(&self, _: &AppContext) -> bool {
-// self.has_conflict
-// }
-
-// fn can_save(&self, cx: &AppContext) -> bool {
-// !self.project_items.is_empty()
-// && self
-// .project_items
-// .iter()
-// .all(|item| item.read(cx).entry_id.is_some())
-// }
-
-// fn save(
-// &mut self,
-// _: Model<Project>,
-// _: &mut ViewContext<Self>,
-// ) -> Task<anyhow::Result<()>> {
-// self.save_count += 1;
-// self.is_dirty = false;
-// Task::ready(Ok(()))
-// }
-
-// fn save_as(
-// &mut self,
-// _: Model<Project>,
-// _: std::path::PathBuf,
-// _: &mut ViewContext<Self>,
-// ) -> Task<anyhow::Result<()>> {
-// self.save_as_count += 1;
-// self.is_dirty = false;
-// Task::ready(Ok(()))
-// }
-
-// fn reload(
-// &mut self,
-// _: Model<Project>,
-// _: &mut ViewContext<Self>,
-// ) -> Task<anyhow::Result<()>> {
-// self.reload_count += 1;
-// self.is_dirty = false;
-// Task::ready(Ok(()))
-// }
-
-// fn to_item_events(_: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
-// [ItemEvent::UpdateTab, ItemEvent::Edit].into()
-// }
-
-// fn serialized_item_kind() -> Option<&'static str> {
-// Some("TestItem")
-// }
-
-// fn deserialize(
-// _project: Model<Project>,
-// _workspace: WeakViewHandle<Workspace>,
-// workspace_id: WorkspaceId,
-// _item_id: ItemId,
-// cx: &mut ViewContext<Pane>,
-// ) -> Task<anyhow::Result<View<Self>>> {
-// let view = cx.add_view(|_cx| Self::new_deserialized(workspace_id));
-// Task::Ready(Some(anyhow::Ok(view)))
-// }
-// }
-// }
+#[cfg(any(test, feature = "test-support"))]
+pub mod test {
+ use super::{Item, ItemEvent};
+ use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
+ use gpui::{
+ AnyElement, AppContext, Context as _, Div, EntityId, EventEmitter, FocusableView,
+ IntoElement, Model, Render, SharedString, Task, View, ViewContext, VisualContext, WeakView,
+ };
+ use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};
+ use std::{any::Any, cell::Cell, path::Path};
+
+ pub struct TestProjectItem {
+ pub entry_id: Option<ProjectEntryId>,
+ pub project_path: Option<ProjectPath>,
+ }
+
+ pub struct TestItem {
+ pub workspace_id: WorkspaceId,
+ pub state: String,
+ pub label: String,
+ pub save_count: usize,
+ pub save_as_count: usize,
+ pub reload_count: usize,
+ pub is_dirty: bool,
+ pub is_singleton: bool,
+ pub has_conflict: bool,
+ pub project_items: Vec<Model<TestProjectItem>>,
+ pub nav_history: Option<ItemNavHistory>,
+ pub tab_descriptions: Option<Vec<&'static str>>,
+ pub tab_detail: Cell<Option<usize>>,
+ focus_handle: gpui::FocusHandle,
+ }
+
+ impl project::Item for TestProjectItem {
+ fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
+ self.entry_id
+ }
+
+ fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
+ self.project_path.clone()
+ }
+ }
+
+ pub enum TestItemEvent {
+ Edit,
+ }
+
+ // impl Clone for TestItem {
+ // fn clone(&self) -> Self {
+ // Self {
+ // state: self.state.clone(),
+ // label: self.label.clone(),
+ // save_count: self.save_count,
+ // save_as_count: self.save_as_count,
+ // reload_count: self.reload_count,
+ // is_dirty: self.is_dirty,
+ // is_singleton: self.is_singleton,
+ // has_conflict: self.has_conflict,
+ // project_items: self.project_items.clone(),
+ // nav_history: None,
+ // tab_descriptions: None,
+ // tab_detail: Default::default(),
+ // workspace_id: self.workspace_id,
+ // focus_handle: self.focus_handle.clone(),
+ // }
+ // }
+ // }
+
+ impl TestProjectItem {
+ pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Model<Self> {
+ let entry_id = Some(ProjectEntryId::from_proto(id));
+ let project_path = Some(ProjectPath {
+ worktree_id: WorktreeId::from_usize(0),
+ path: Path::new(path).into(),
+ });
+ cx.build_model(|_| Self {
+ entry_id,
+ project_path,
+ })
+ }
+
+ pub fn new_untitled(cx: &mut AppContext) -> Model<Self> {
+ cx.build_model(|_| Self {
+ project_path: None,
+ entry_id: None,
+ })
+ }
+ }
+
+ impl TestItem {
+ pub fn new(cx: &mut ViewContext<Self>) -> Self {
+ Self {
+ state: String::new(),
+ label: String::new(),
+ save_count: 0,
+ save_as_count: 0,
+ reload_count: 0,
+ is_dirty: false,
+ has_conflict: false,
+ project_items: Vec::new(),
+ is_singleton: true,
+ nav_history: None,
+ tab_descriptions: None,
+ tab_detail: Default::default(),
+ workspace_id: 0,
+ focus_handle: cx.focus_handle(),
+ }
+ }
+
+ pub fn new_deserialized(id: WorkspaceId, cx: &mut ViewContext<Self>) -> Self {
+ let mut this = Self::new(cx);
+ this.workspace_id = id;
+ this
+ }
+
+ pub fn with_label(mut self, state: &str) -> Self {
+ self.label = state.to_string();
+ self
+ }
+
+ pub fn with_singleton(mut self, singleton: bool) -> Self {
+ self.is_singleton = singleton;
+ self
+ }
+
+ pub fn with_dirty(mut self, dirty: bool) -> Self {
+ self.is_dirty = dirty;
+ self
+ }
+
+ pub fn with_conflict(mut self, has_conflict: bool) -> Self {
+ self.has_conflict = has_conflict;
+ self
+ }
+
+ pub fn with_project_items(mut self, items: &[Model<TestProjectItem>]) -> Self {
+ self.project_items.clear();
+ self.project_items.extend(items.iter().cloned());
+ self
+ }
+
+ pub fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
+ self.push_to_nav_history(cx);
+ self.state = state;
+ }
+
+ fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
+ if let Some(history) = &mut self.nav_history {
+ history.push(Some(Box::new(self.state.clone())), cx);
+ }
+ }
+ }
+
+ impl Render for TestItem {
+ type Element = Div;
+
+ fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
+ gpui::div()
+ }
+ }
+
+ impl EventEmitter<ItemEvent> for TestItem {}
+
+ impl FocusableView for TestItem {
+ fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
+ self.focus_handle.clone()
+ }
+ }
+
+ impl Item for TestItem {
+ fn tab_description(&self, detail: usize, _: &AppContext) -> Option<SharedString> {
+ self.tab_descriptions.as_ref().and_then(|descriptions| {
+ let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
+ Some(description.into())
+ })
+ }
+
+ fn tab_content(
+ &self,
+ detail: Option<usize>,
+ cx: &ui::prelude::WindowContext,
+ ) -> AnyElement {
+ self.tab_detail.set(detail);
+ gpui::div().into_any_element()
+ }
+
+ fn for_each_project_item(
+ &self,
+ cx: &AppContext,
+ f: &mut dyn FnMut(EntityId, &dyn project::Item),
+ ) {
+ self.project_items
+ .iter()
+ .for_each(|item| f(item.entity_id(), item.read(cx)))
+ }
+
+ fn is_singleton(&self, _: &AppContext) -> bool {
+ self.is_singleton
+ }
+
+ fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
+ self.nav_history = Some(history);
+ }
+
+ fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
+ let state = *state.downcast::<String>().unwrap_or_default();
+ if state != self.state {
+ self.state = state;
+ true
+ } else {
+ false
+ }
+ }
+
+ fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
+ self.push_to_nav_history(cx);
+ }
+
+ fn clone_on_split(
+ &self,
+ _workspace_id: WorkspaceId,
+ cx: &mut ViewContext<Self>,
+ ) -> Option<View<Self>>
+ where
+ Self: Sized,
+ {
+ Some(cx.build_view(|cx| Self {
+ state: self.state.clone(),
+ label: self.label.clone(),
+ save_count: self.save_count,
+ save_as_count: self.save_as_count,
+ reload_count: self.reload_count,
+ is_dirty: self.is_dirty,
+ is_singleton: self.is_singleton,
+ has_conflict: self.has_conflict,
+ project_items: self.project_items.clone(),
+ nav_history: None,
+ tab_descriptions: None,
+ tab_detail: Default::default(),
+ workspace_id: self.workspace_id,
+ focus_handle: cx.focus_handle(),
+ }))
+ }
+
+ fn is_dirty(&self, _: &AppContext) -> bool {
+ self.is_dirty
+ }
+
+ fn has_conflict(&self, _: &AppContext) -> bool {
+ self.has_conflict
+ }
+
+ fn can_save(&self, cx: &AppContext) -> bool {
+ !self.project_items.is_empty()
+ && self
+ .project_items
+ .iter()
+ .all(|item| item.read(cx).entry_id.is_some())
+ }
+
+ fn save(
+ &mut self,
+ _: Model<Project>,
+ _: &mut ViewContext<Self>,
+ ) -> Task<anyhow::Result<()>> {
+ self.save_count += 1;
+ self.is_dirty = false;
+ Task::ready(Ok(()))
+ }
+
+ fn save_as(
+ &mut self,
+ _: Model<Project>,
+ _: std::path::PathBuf,
+ _: &mut ViewContext<Self>,
+ ) -> Task<anyhow::Result<()>> {
+ self.save_as_count += 1;
+ self.is_dirty = false;
+ Task::ready(Ok(()))
+ }
+
+ fn reload(
+ &mut self,
+ _: Model<Project>,
+ _: &mut ViewContext<Self>,
+ ) -> Task<anyhow::Result<()>> {
+ self.reload_count += 1;
+ self.is_dirty = false;
+ Task::ready(Ok(()))
+ }
+
+ fn serialized_item_kind() -> Option<&'static str> {
+ Some("TestItem")
+ }
+
+ fn deserialize(
+ _project: Model<Project>,
+ _workspace: WeakView<Workspace>,
+ workspace_id: WorkspaceId,
+ _item_id: ItemId,
+ cx: &mut ViewContext<Pane>,
+ ) -> Task<anyhow::Result<View<Self>>> {
+ let view = cx.build_view(|cx| Self::new_deserialized(workspace_id, cx));
+ Task::Ready(Some(anyhow::Ok(view)))
+ }
+ }
+}
@@ -12,9 +12,9 @@ use gpui::{
ViewContext, VisualContext, WeakView, WindowContext,
};
use parking_lot::Mutex;
-use project2::{Project, ProjectEntryId, ProjectPath};
+use project::{Project, ProjectEntryId, ProjectPath};
use serde::Deserialize;
-use settings2::Settings;
+use settings::Settings;
use std::{
any::Any,
cmp, fmt, mem,
@@ -457,6 +457,28 @@ impl Pane {
!self.nav_history.0.lock().forward_stack.is_empty()
}
+ fn navigate_backward(&mut self, cx: &mut ViewContext<Self>) {
+ if let Some(workspace) = self.workspace.upgrade() {
+ let pane = cx.view().downgrade();
+ cx.window_context().defer(move |cx| {
+ workspace.update(cx, |workspace, cx| {
+ workspace.go_back(pane, cx).detach_and_log_err(cx)
+ })
+ })
+ }
+ }
+
+ fn navigate_forward(&mut self, cx: &mut ViewContext<Self>) {
+ if let Some(workspace) = self.workspace.upgrade() {
+ let pane = cx.view().downgrade();
+ cx.window_context().defer(move |cx| {
+ workspace.update(cx, |workspace, cx| {
+ workspace.go_forward(pane, cx).detach_and_log_err(cx)
+ })
+ })
+ }
+ }
+
fn history_updated(&mut self, cx: &mut ViewContext<Self>) {
self.toolbar.update(cx, |_, cx| cx.notify());
}
@@ -1483,12 +1505,20 @@ impl Pane {
.child(
div().border().border_color(gpui::red()).child(
IconButton::new("navigate_backward", Icon::ArrowLeft)
+ .on_click({
+ let view = cx.view().clone();
+ move |_, cx| view.update(cx, Self::navigate_backward)
+ })
.disabled(!self.can_navigate_backward()),
),
)
.child(
div().border().border_color(gpui::red()).child(
IconButton::new("navigate_forward", Icon::ArrowRight)
+ .on_click({
+ let view = cx.view().clone();
+ move |_, cx| view.update(cx, Self::navigate_backward)
+ })
.disabled(!self.can_navigate_forward()),
),
),
@@ -1935,18 +1965,14 @@ impl Render for Pane {
v_stack()
.key_context("Pane")
.track_focus(&self.focus_handle)
- .on_action(cx.listener(|pane: &mut Pane, _: &SplitLeft, cx| {
- pane.split(SplitDirection::Left, cx)
- }))
+ .on_action(cx.listener(|pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx)))
+ .on_action(cx.listener(|pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx)))
.on_action(
- cx.listener(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx)),
+ cx.listener(|pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx)),
)
- .on_action(cx.listener(|pane: &mut Pane, _: &SplitRight, cx| {
- pane.split(SplitDirection::Right, cx)
- }))
- .on_action(cx.listener(|pane: &mut Pane, _: &SplitDown, cx| {
- pane.split(SplitDirection::Down, cx)
- }))
+ .on_action(cx.listener(|pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx)))
+ .on_action(cx.listener(|pane, _: &GoBack, cx| pane.navigate_backward(cx)))
+ .on_action(cx.listener(|pane, _: &GoForward, cx| pane.navigate_forward(cx)))
// cx.add_action(Pane::toggle_zoom);
// cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
// pane.activate_item(action.0, true, true, cx);
@@ -1,7 +1,7 @@
use crate::{AppState, FollowerState, Pane, Workspace};
use anyhow::{anyhow, bail, Result};
use collections::HashMap;
-use db2::sqlez::{
+use db::sqlez::{
bindable::{Bind, Column, StaticColumnCount},
statement::Statement,
};
@@ -9,7 +9,7 @@ use gpui::{
point, size, AnyWeakView, Bounds, Div, IntoElement, Model, Pixels, Point, View, ViewContext,
};
use parking_lot::Mutex;
-use project2::Project;
+use project::Project;
use serde::Deserialize;
use std::sync::Arc;
use ui::prelude::*;
@@ -5,7 +5,7 @@ pub mod model;
use std::path::Path;
use anyhow::{anyhow, bail, Context, Result};
-use db2::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
+use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
use gpui::WindowBounds;
use util::{unzip_option, ResultExt};
@@ -552,7 +552,7 @@ impl WorkspaceDb {
#[cfg(test)]
mod tests {
use super::*;
- use db2::open_test_db;
+ use db::open_test_db;
use gpui;
#[gpui::test]
@@ -3,12 +3,12 @@ use crate::{
};
use anyhow::{Context, Result};
use async_recursion::async_recursion;
-use db2::sqlez::{
+use db::sqlez::{
bindable::{Bind, Column, StaticColumnCount},
statement::Statement,
};
use gpui::{AsyncWindowContext, Model, Task, View, WeakView, WindowBounds};
-use project2::Project;
+use project::Project;
use std::{
path::{Path, PathBuf},
sync::Arc,
@@ -4,7 +4,7 @@ use gpui::{
AnyView, AppContext, EventEmitter, Subscription, Task, View, ViewContext, WeakView,
WindowContext,
};
-use project2::search::SearchQuery;
+use project::search::SearchQuery;
use crate::{
item::{Item, WeakItemHandle},
@@ -52,22 +52,13 @@ impl Render for StatusBar {
h_stack()
.gap_4()
.child(
- h_stack()
- .gap_1()
- .child(
- // TODO: Line / column numbers
- div()
- .border()
- .border_color(gpui::red())
- .child(Button::new("status_line_column_numbers", "15:22")),
- )
- .child(
- // TODO: Language picker
- div()
- .border()
- .border_color(gpui::red())
- .child(Button::new("status_buffer_language", "Rust")),
- ),
+ h_stack().gap_1().child(
+ // TODO: Language picker
+ div()
+ .border()
+ .border_color(gpui::red())
+ .child(Button::new("status_buffer_language", "Rust")),
+ ),
)
.child(
h_stack()
@@ -133,7 +124,7 @@ impl StatusBar {
h_stack()
.items_center()
.gap_2()
- .children(self.right_items.iter().map(|item| item.to_any()))
+ .children(self.right_items.iter().rev().map(|item| item.to_any()))
}
}
@@ -16,7 +16,7 @@ mod workspace_settings;
use anyhow::{anyhow, Context as _, Result};
use async_trait::async_trait;
-use client2::{
+use client::{
proto::{self, PeerId},
Client, TypedEnvelope, User, UserStore,
};
@@ -37,7 +37,7 @@ use gpui::{
};
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
use itertools::Itertools;
-use language2::{LanguageRegistry, Rope};
+use language::{LanguageRegistry, Rope};
use lazy_static::lazy_static;
pub use modal_layer::*;
use node_runtime::NodeRuntime;
@@ -49,9 +49,9 @@ pub use persistence::{
WorkspaceDb, DB,
};
use postage::stream::Stream;
-use project2::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
+use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
use serde::Deserialize;
-use settings2::Settings;
+use settings::Settings;
use status_bar::StatusBar;
pub use status_bar::StatusItemView;
use std::{
@@ -62,7 +62,7 @@ use std::{
sync::{atomic::AtomicUsize, Arc},
time::Duration,
};
-use theme2::{ActiveTheme, ThemeSettings};
+use theme::{ActiveTheme, ThemeSettings};
pub use toolbar::{ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
pub use ui;
use util::ResultExt;
@@ -301,7 +301,7 @@ pub struct AppState {
pub client: Arc<Client>,
pub user_store: Model<UserStore>,
pub workspace_store: Model<WorkspaceStore>,
- pub fs: Arc<dyn fs2::Fs>,
+ pub fs: Arc<dyn fs::Fs>,
pub call_factory: CallFactory,
pub build_window_options:
fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
@@ -312,7 +312,7 @@ pub struct WorkspaceStore {
workspaces: HashSet<WindowHandle<Workspace>>,
followers: Vec<Follower>,
client: Arc<Client>,
- _subscriptions: Vec<client2::Subscription>,
+ _subscriptions: Vec<client::Subscription>,
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
@@ -388,22 +388,22 @@ impl AppState {
#[cfg(any(test, feature = "test-support"))]
pub fn test(cx: &mut AppContext) -> Arc<Self> {
use node_runtime::FakeNodeRuntime;
- use settings2::SettingsStore;
+ use settings::SettingsStore;
if !cx.has_global::<SettingsStore>() {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
}
- let fs = fs2::FakeFs::new(cx.background_executor().clone());
+ let fs = fs::FakeFs::new(cx.background_executor().clone());
let languages = Arc::new(LanguageRegistry::test());
let http_client = util::http::FakeHttpClient::with_404_response();
let client = Client::new(http_client.clone(), cx);
let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http_client, cx));
let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
- theme2::init(theme2::LoadThemes::JustBase, cx);
- client2::init(&client, cx);
+ theme::init(theme::LoadThemes::JustBase, cx);
+ client::init(&client, cx);
crate::init_settings(cx);
Arc::new(Self {
@@ -567,29 +567,29 @@ impl Workspace {
cx.observe(&project, |_, _, cx| cx.notify()).detach();
cx.subscribe(&project, move |this, _, event, cx| {
match event {
- project2::Event::RemoteIdChanged(_) => {
+ project::Event::RemoteIdChanged(_) => {
this.update_window_title(cx);
}
- project2::Event::CollaboratorLeft(peer_id) => {
+ project::Event::CollaboratorLeft(peer_id) => {
this.collaborator_left(*peer_id, cx);
}
- project2::Event::WorktreeRemoved(_) | project2::Event::WorktreeAdded => {
+ project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
this.update_window_title(cx);
this.serialize_workspace(cx);
}
- project2::Event::DisconnectedFromHost => {
+ project::Event::DisconnectedFromHost => {
this.update_window_edited(cx);
cx.blur();
}
- project2::Event::Closed => {
+ project::Event::Closed => {
cx.remove_window();
}
- project2::Event::DeletedEntry(entry_id) => {
+ project::Event::DeletedEntry(entry_id) => {
for pane in this.panes.iter() {
pane.update(cx, |pane, cx| {
pane.handle_deleted_project_item(*entry_id, cx)
@@ -597,7 +597,7 @@ impl Workspace {
}
}
- project2::Event::Notification(message) => this.show_notification(0, cx, |cx| {
+ project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
cx.build_view(|_| MessageNotification::new(message.clone()))
}),
@@ -1450,7 +1450,7 @@ impl Workspace {
.map(|entry| entry.id);
if let Some(entry_id) = entry_id {
workspace.project.update(cx, |_, cx| {
- cx.emit(project2::Event::ActiveEntryChanged(Some(entry_id)));
+ cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
})
}
})
@@ -1812,8 +1812,7 @@ impl Workspace {
});
cx.subscribe(&pane, Self::handle_pane_event).detach();
self.panes.push(pane.clone());
- // todo!()
- // cx.focus(&pane);
+ cx.focus_view(&pane);
cx.emit(Event::PaneAdded(pane.clone()));
pane
}
@@ -1988,7 +1987,7 @@ impl Workspace {
where
T: ProjectItem,
{
- use project2::Item as _;
+ use project::Item as _;
let entry_id = project_item.read(cx).entry_id(cx);
if let Some(item) = entry_id
@@ -2013,7 +2012,7 @@ impl Workspace {
where
T: ProjectItem,
{
- use project2::Item as _;
+ use project::Item as _;
let entry_id = project_item.read(cx).entry_id(cx);
if let Some(item) = entry_id
@@ -2592,8 +2591,7 @@ impl Workspace {
title.push_str(" β");
}
- // todo!()
- // cx.set_window_title(&title);
+ cx.set_window_title(&title);
}
fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
@@ -3673,7 +3671,7 @@ fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncA
workspace
.update(cx, |workspace, cx| {
- if (*db2::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
+ if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
workspace.show_notification_once(0, cx, |cx| {
cx.build_view(|_| {
MessageNotification::new("Failed to load the database file.")
@@ -4554,960 +4552,950 @@ fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
Some(size((width as f64).into(), (height as f64).into()))
}
-// #[cfg(test)]
-// mod tests {
-// use super::*;
-// use crate::{
-// dock::test::TestPanel,
-// item::test::{TestItem, TestItemEvent, TestProjectItem},
-// };
-// use fs::FakeFs;
-// use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
-// use project::{Project, ProjectEntryId};
-// use serde_json::json;
-// use settings::SettingsStore;
-// use std::{cell::RefCell, rc::Rc};
-
-// #[gpui::test]
-// async fn test_tab_disambiguation(cx: &mut TestAppContext) {
-// init_test(cx);
-
-// let fs = FakeFs::new(cx.background());
-// let project = Project::test(fs, [], cx).await;
-// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-// let workspace = window.root(cx);
-
-// // Adding an item with no ambiguity renders the tab without detail.
-// let item1 = window.build_view(cx, |_| {
-// let mut item = TestItem::new();
-// item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
-// item
-// });
-// workspace.update(cx, |workspace, cx| {
-// workspace.add_item(Box::new(item1.clone()), cx);
-// });
-// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
-
-// // Adding an item that creates ambiguity increases the level of detail on
-// // both tabs.
-// let item2 = window.build_view(cx, |_| {
-// let mut item = TestItem::new();
-// item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
-// item
-// });
-// workspace.update(cx, |workspace, cx| {
-// workspace.add_item(Box::new(item2.clone()), cx);
-// });
-// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
-// item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
-
-// // Adding an item that creates ambiguity increases the level of detail only
-// // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
-// // we stop at the highest detail available.
-// let item3 = window.build_view(cx, |_| {
-// let mut item = TestItem::new();
-// item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
-// item
-// });
-// workspace.update(cx, |workspace, cx| {
-// workspace.add_item(Box::new(item3.clone()), cx);
-// });
-// item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
-// item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
-// item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
-// }
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::item::{
+ test::{TestItem, TestProjectItem},
+ ItemEvent,
+ };
+ use fs::FakeFs;
+ use gpui::TestAppContext;
+ use project::{Project, ProjectEntryId};
+ use serde_json::json;
+ use settings::SettingsStore;
+ use std::{cell::RefCell, rc::Rc};
+
+ #[gpui::test]
+ async fn test_tab_disambiguation(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ let project = Project::test(fs, [], cx).await;
+ let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
+
+ // Adding an item with no ambiguity renders the tab without detail.
+ let item1 = cx.build_view(|cx| {
+ let mut item = TestItem::new(cx);
+ item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
+ item
+ });
+ workspace.update(cx, |workspace, cx| {
+ workspace.add_item(Box::new(item1.clone()), cx);
+ });
+ item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
+
+ // Adding an item that creates ambiguity increases the level of detail on
+ // both tabs.
+ let item2 = cx.build_view(|cx| {
+ let mut item = TestItem::new(cx);
+ item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
+ item
+ });
+ workspace.update(cx, |workspace, cx| {
+ workspace.add_item(Box::new(item2.clone()), cx);
+ });
+ item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
+ item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
+
+ // Adding an item that creates ambiguity increases the level of detail only
+ // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
+ // we stop at the highest detail available.
+ let item3 = cx.build_view(|cx| {
+ let mut item = TestItem::new(cx);
+ item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
+ item
+ });
+ workspace.update(cx, |workspace, cx| {
+ workspace.add_item(Box::new(item3.clone()), cx);
+ });
+ item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
+ item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
+ item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
+ }
-// #[gpui::test]
-// async fn test_tracking_active_path(cx: &mut TestAppContext) {
-// init_test(cx);
-
-// let fs = FakeFs::new(cx.background());
-// fs.insert_tree(
-// "/root1",
-// json!({
-// "one.txt": "",
-// "two.txt": "",
-// }),
-// )
-// .await;
-// fs.insert_tree(
-// "/root2",
-// json!({
-// "three.txt": "",
-// }),
-// )
-// .await;
+ #[gpui::test]
+ async fn test_tracking_active_path(cx: &mut TestAppContext) {
+ init_test(cx);
-// let project = Project::test(fs, ["root1".as_ref()], cx).await;
-// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-// let workspace = window.root(cx);
-// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-// let worktree_id = project.read_with(cx, |project, cx| {
-// project.worktrees().next().unwrap().read(cx).id()
-// });
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(
+ "/root1",
+ json!({
+ "one.txt": "",
+ "two.txt": "",
+ }),
+ )
+ .await;
+ fs.insert_tree(
+ "/root2",
+ json!({
+ "three.txt": "",
+ }),
+ )
+ .await;
-// let item1 = window.build_view(cx, |cx| {
-// TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
-// });
-// let item2 = window.build_view(cx, |cx| {
-// TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
-// });
+ let project = Project::test(fs, ["root1".as_ref()], cx).await;
+ let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
+ let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
+ let worktree_id = project.read_with(cx, |project, cx| {
+ project.worktrees().next().unwrap().read(cx).id()
+ });
-// // Add an item to an empty pane
-// workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
-// project.read_with(cx, |project, cx| {
-// assert_eq!(
-// project.active_entry(),
-// project
-// .entry_for_path(&(worktree_id, "one.txt").into(), cx)
-// .map(|e| e.id)
-// );
-// });
-// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root1"));
-
-// // Add a second item to a non-empty pane
-// workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
-// assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β root1"));
-// project.read_with(cx, |project, cx| {
-// assert_eq!(
-// project.active_entry(),
-// project
-// .entry_for_path(&(worktree_id, "two.txt").into(), cx)
-// .map(|e| e.id)
-// );
-// });
+ let item1 = cx.build_view(|cx| {
+ TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
+ });
+ let item2 = cx.build_view(|cx| {
+ TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
+ });
-// // Close the active item
-// pane.update(cx, |pane, cx| {
-// pane.close_active_item(&Default::default(), cx).unwrap()
-// })
-// .await
-// .unwrap();
-// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root1"));
-// project.read_with(cx, |project, cx| {
-// assert_eq!(
-// project.active_entry(),
-// project
-// .entry_for_path(&(worktree_id, "one.txt").into(), cx)
-// .map(|e| e.id)
-// );
-// });
+ // Add an item to an empty pane
+ workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
+ project.read_with(cx, |project, cx| {
+ assert_eq!(
+ project.active_entry(),
+ project
+ .entry_for_path(&(worktree_id, "one.txt").into(), cx)
+ .map(|e| e.id)
+ );
+ });
+ assert_eq!(cx.window_title().as_deref(), Some("one.txt β root1"));
+
+ // Add a second item to a non-empty pane
+ workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
+ assert_eq!(cx.window_title().as_deref(), Some("two.txt β root1"));
+ project.read_with(cx, |project, cx| {
+ assert_eq!(
+ project.active_entry(),
+ project
+ .entry_for_path(&(worktree_id, "two.txt").into(), cx)
+ .map(|e| e.id)
+ );
+ });
-// // Add a project folder
-// project
-// .update(cx, |project, cx| {
-// project.find_or_create_local_worktree("/root2", true, cx)
-// })
-// .await
-// .unwrap();
-// assert_eq!(
-// window.current_title(cx).as_deref(),
-// Some("one.txt β root1, root2")
-// );
-
-// // Remove a project folder
-// project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
-// assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β root2"));
-// }
+ // Close the active item
+ pane.update(cx, |pane, cx| {
+ pane.close_active_item(&Default::default(), cx).unwrap()
+ })
+ .await
+ .unwrap();
+ assert_eq!(cx.window_title().as_deref(), Some("one.txt β root1"));
+ project.read_with(cx, |project, cx| {
+ assert_eq!(
+ project.active_entry(),
+ project
+ .entry_for_path(&(worktree_id, "one.txt").into(), cx)
+ .map(|e| e.id)
+ );
+ });
-// #[gpui::test]
-// async fn test_close_window(cx: &mut TestAppContext) {
-// init_test(cx);
-
-// let fs = FakeFs::new(cx.background());
-// fs.insert_tree("/root", json!({ "one": "" })).await;
-
-// let project = Project::test(fs, ["root".as_ref()], cx).await;
-// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-// let workspace = window.root(cx);
-
-// // When there are no dirty items, there's nothing to do.
-// let item1 = window.build_view(cx, |_| TestItem::new());
-// workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
-// let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
-// assert!(task.await.unwrap());
-
-// // When there are dirty untitled items, prompt to save each one. If the user
-// // cancels any prompt, then abort.
-// let item2 = window.build_view(cx, |_| TestItem::new().with_dirty(true));
-// let item3 = window.build_view(cx, |cx| {
-// TestItem::new()
-// .with_dirty(true)
-// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-// });
-// workspace.update(cx, |w, cx| {
-// w.add_item(Box::new(item2.clone()), cx);
-// w.add_item(Box::new(item3.clone()), cx);
-// });
-// let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
-// cx.foreground().run_until_parked();
-// window.simulate_prompt_answer(2, cx); // cancel save all
-// cx.foreground().run_until_parked();
-// window.simulate_prompt_answer(2, cx); // cancel save all
-// cx.foreground().run_until_parked();
-// assert!(!window.has_pending_prompt(cx));
-// assert!(!task.await.unwrap());
-// }
+ // Add a project folder
+ project
+ .update(cx, |project, cx| {
+ project.find_or_create_local_worktree("/root2", true, cx)
+ })
+ .await
+ .unwrap();
+ assert_eq!(cx.window_title().as_deref(), Some("one.txt β root1, root2"));
-// #[gpui::test]
-// async fn test_close_pane_items(cx: &mut TestAppContext) {
-// init_test(cx);
+ // Remove a project folder
+ project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
+ assert_eq!(cx.window_title().as_deref(), Some("one.txt β root2"));
+ }
-// let fs = FakeFs::new(cx.background());
+ #[gpui::test]
+ async fn test_close_window(cx: &mut TestAppContext) {
+ init_test(cx);
-// let project = Project::test(fs, None, cx).await;
-// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-// let workspace = window.root(cx);
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree("/root", json!({ "one": "" })).await;
-// let item1 = window.build_view(cx, |cx| {
-// TestItem::new()
-// .with_dirty(true)
-// .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-// });
-// let item2 = window.build_view(cx, |cx| {
-// TestItem::new()
-// .with_dirty(true)
-// .with_conflict(true)
-// .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
-// });
-// let item3 = window.build_view(cx, |cx| {
-// TestItem::new()
-// .with_dirty(true)
-// .with_conflict(true)
-// .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
-// });
-// let item4 = window.build_view(cx, |cx| {
-// TestItem::new()
-// .with_dirty(true)
-// .with_project_items(&[TestProjectItem::new_untitled(cx)])
-// });
-// let pane = workspace.update(cx, |workspace, cx| {
-// workspace.add_item(Box::new(item1.clone()), cx);
-// workspace.add_item(Box::new(item2.clone()), cx);
-// workspace.add_item(Box::new(item3.clone()), cx);
-// workspace.add_item(Box::new(item4.clone()), cx);
-// workspace.active_pane().clone()
-// });
+ let project = Project::test(fs, ["root".as_ref()], cx).await;
+ let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
-// let close_items = pane.update(cx, |pane, cx| {
-// pane.activate_item(1, true, true, cx);
-// assert_eq!(pane.active_item().unwrap().id(), item2.id());
-// let item1_id = item1.id();
-// let item3_id = item3.id();
-// let item4_id = item4.id();
-// pane.close_items(cx, SaveIntent::Close, move |id| {
-// [item1_id, item3_id, item4_id].contains(&id)
-// })
-// });
-// cx.foreground().run_until_parked();
-
-// assert!(window.has_pending_prompt(cx));
-// // Ignore "Save all" prompt
-// window.simulate_prompt_answer(2, cx);
-// cx.foreground().run_until_parked();
-// // There's a prompt to save item 1.
-// pane.read_with(cx, |pane, _| {
-// assert_eq!(pane.items_len(), 4);
-// assert_eq!(pane.active_item().unwrap().id(), item1.id());
-// });
-// // Confirm saving item 1.
-// window.simulate_prompt_answer(0, cx);
-// cx.foreground().run_until_parked();
-
-// // Item 1 is saved. There's a prompt to save item 3.
-// pane.read_with(cx, |pane, cx| {
-// assert_eq!(item1.read(cx).save_count, 1);
-// assert_eq!(item1.read(cx).save_as_count, 0);
-// assert_eq!(item1.read(cx).reload_count, 0);
-// assert_eq!(pane.items_len(), 3);
-// assert_eq!(pane.active_item().unwrap().id(), item3.id());
-// });
-// assert!(window.has_pending_prompt(cx));
-
-// // Cancel saving item 3.
-// window.simulate_prompt_answer(1, cx);
-// cx.foreground().run_until_parked();
-
-// // Item 3 is reloaded. There's a prompt to save item 4.
-// pane.read_with(cx, |pane, cx| {
-// assert_eq!(item3.read(cx).save_count, 0);
-// assert_eq!(item3.read(cx).save_as_count, 0);
-// assert_eq!(item3.read(cx).reload_count, 1);
-// assert_eq!(pane.items_len(), 2);
-// assert_eq!(pane.active_item().unwrap().id(), item4.id());
-// });
-// assert!(window.has_pending_prompt(cx));
-
-// // Confirm saving item 4.
-// window.simulate_prompt_answer(0, cx);
-// cx.foreground().run_until_parked();
-
-// // There's a prompt for a path for item 4.
-// cx.simulate_new_path_selection(|_| Some(Default::default()));
-// close_items.await.unwrap();
-
-// // The requested items are closed.
-// pane.read_with(cx, |pane, cx| {
-// assert_eq!(item4.read(cx).save_count, 0);
-// assert_eq!(item4.read(cx).save_as_count, 1);
-// assert_eq!(item4.read(cx).reload_count, 0);
-// assert_eq!(pane.items_len(), 1);
-// assert_eq!(pane.active_item().unwrap().id(), item2.id());
-// });
-// }
+ // When there are no dirty items, there's nothing to do.
+ let item1 = cx.build_view(|cx| TestItem::new(cx));
+ workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
+ let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
+ assert!(task.await.unwrap());
-// #[gpui::test]
-// async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
-// init_test(cx);
-
-// let fs = FakeFs::new(cx.background());
-
-// let project = Project::test(fs, [], cx).await;
-// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-// let workspace = window.root(cx);
-
-// // Create several workspace items with single project entries, and two
-// // workspace items with multiple project entries.
-// let single_entry_items = (0..=4)
-// .map(|project_entry_id| {
-// window.build_view(cx, |cx| {
-// TestItem::new()
-// .with_dirty(true)
-// .with_project_items(&[TestProjectItem::new(
-// project_entry_id,
-// &format!("{project_entry_id}.txt"),
-// cx,
-// )])
-// })
-// })
-// .collect::<Vec<_>>();
-// let item_2_3 = window.build_view(cx, |cx| {
-// TestItem::new()
-// .with_dirty(true)
-// .with_singleton(false)
-// .with_project_items(&[
-// single_entry_items[2].read(cx).project_items[0].clone(),
-// single_entry_items[3].read(cx).project_items[0].clone(),
-// ])
-// });
-// let item_3_4 = window.build_view(cx, |cx| {
-// TestItem::new()
-// .with_dirty(true)
-// .with_singleton(false)
-// .with_project_items(&[
-// single_entry_items[3].read(cx).project_items[0].clone(),
-// single_entry_items[4].read(cx).project_items[0].clone(),
-// ])
-// });
+ // When there are dirty untitled items, prompt to save each one. If the user
+ // cancels any prompt, then abort.
+ let item2 = cx.build_view(|cx| TestItem::new(cx).with_dirty(true));
+ let item3 = cx.build_view(|cx| {
+ TestItem::new(cx)
+ .with_dirty(true)
+ .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+ });
+ workspace.update(cx, |w, cx| {
+ w.add_item(Box::new(item2.clone()), cx);
+ w.add_item(Box::new(item3.clone()), cx);
+ });
+ let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
+ cx.executor().run_until_parked();
+ cx.simulate_prompt_answer(2); // cancel save all
+ cx.executor().run_until_parked();
+ cx.simulate_prompt_answer(2); // cancel save all
+ cx.executor().run_until_parked();
+ assert!(!cx.has_pending_prompt());
+ assert!(!task.await.unwrap());
+ }
-// // Create two panes that contain the following project entries:
-// // left pane:
-// // multi-entry items: (2, 3)
-// // single-entry items: 0, 1, 2, 3, 4
-// // right pane:
-// // single-entry items: 1
-// // multi-entry items: (3, 4)
-// let left_pane = workspace.update(cx, |workspace, cx| {
-// let left_pane = workspace.active_pane().clone();
-// workspace.add_item(Box::new(item_2_3.clone()), cx);
-// for item in single_entry_items {
-// workspace.add_item(Box::new(item), cx);
-// }
-// left_pane.update(cx, |pane, cx| {
-// pane.activate_item(2, true, true, cx);
-// });
+ #[gpui::test]
+ async fn test_close_pane_items(cx: &mut TestAppContext) {
+ init_test(cx);
-// workspace
-// .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
-// .unwrap();
+ let fs = FakeFs::new(cx.executor());
-// left_pane
-// });
+ let project = Project::test(fs, None, cx).await;
+ let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
-// //Need to cause an effect flush in order to respect new focus
-// workspace.update(cx, |workspace, cx| {
-// workspace.add_item(Box::new(item_3_4.clone()), cx);
-// cx.focus(&left_pane);
-// });
+ let item1 = cx.build_view(|cx| {
+ TestItem::new(cx)
+ .with_dirty(true)
+ .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+ });
+ let item2 = cx.build_view(|cx| {
+ TestItem::new(cx)
+ .with_dirty(true)
+ .with_conflict(true)
+ .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
+ });
+ let item3 = cx.build_view(|cx| {
+ TestItem::new(cx)
+ .with_dirty(true)
+ .with_conflict(true)
+ .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
+ });
+ let item4 = cx.build_view(|cx| {
+ TestItem::new(cx)
+ .with_dirty(true)
+ .with_project_items(&[TestProjectItem::new_untitled(cx)])
+ });
+ let pane = workspace.update(cx, |workspace, cx| {
+ workspace.add_item(Box::new(item1.clone()), cx);
+ workspace.add_item(Box::new(item2.clone()), cx);
+ workspace.add_item(Box::new(item3.clone()), cx);
+ workspace.add_item(Box::new(item4.clone()), cx);
+ workspace.active_pane().clone()
+ });
-// // When closing all of the items in the left pane, we should be prompted twice:
-// // once for project entry 0, and once for project entry 2. After those two
-// // prompts, the task should complete.
+ let close_items = pane.update(cx, |pane, cx| {
+ pane.activate_item(1, true, true, cx);
+ assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
+ let item1_id = item1.item_id();
+ let item3_id = item3.item_id();
+ let item4_id = item4.item_id();
+ pane.close_items(cx, SaveIntent::Close, move |id| {
+ [item1_id, item3_id, item4_id].contains(&id)
+ })
+ });
+ cx.executor().run_until_parked();
+
+ assert!(cx.has_pending_prompt());
+ // Ignore "Save all" prompt
+ cx.simulate_prompt_answer(2);
+ cx.executor().run_until_parked();
+ // There's a prompt to save item 1.
+ pane.update(cx, |pane, _| {
+ assert_eq!(pane.items_len(), 4);
+ assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
+ });
+ // Confirm saving item 1.
+ cx.simulate_prompt_answer(0);
+ cx.executor().run_until_parked();
+
+ // Item 1 is saved. There's a prompt to save item 3.
+ pane.update(cx, |pane, cx| {
+ assert_eq!(item1.read(cx).save_count, 1);
+ assert_eq!(item1.read(cx).save_as_count, 0);
+ assert_eq!(item1.read(cx).reload_count, 0);
+ assert_eq!(pane.items_len(), 3);
+ assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
+ });
+ assert!(cx.has_pending_prompt());
+
+ // Cancel saving item 3.
+ cx.simulate_prompt_answer(1);
+ cx.executor().run_until_parked();
+
+ // Item 3 is reloaded. There's a prompt to save item 4.
+ pane.update(cx, |pane, cx| {
+ assert_eq!(item3.read(cx).save_count, 0);
+ assert_eq!(item3.read(cx).save_as_count, 0);
+ assert_eq!(item3.read(cx).reload_count, 1);
+ assert_eq!(pane.items_len(), 2);
+ assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
+ });
+ assert!(cx.has_pending_prompt());
+
+ // Confirm saving item 4.
+ cx.simulate_prompt_answer(0);
+ cx.executor().run_until_parked();
+
+ // There's a prompt for a path for item 4.
+ cx.simulate_new_path_selection(|_| Some(Default::default()));
+ close_items.await.unwrap();
+
+ // The requested items are closed.
+ pane.update(cx, |pane, cx| {
+ assert_eq!(item4.read(cx).save_count, 0);
+ assert_eq!(item4.read(cx).save_as_count, 1);
+ assert_eq!(item4.read(cx).reload_count, 0);
+ assert_eq!(pane.items_len(), 1);
+ assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
+ });
+ }
-// let close = left_pane.update(cx, |pane, cx| {
-// pane.close_items(cx, SaveIntent::Close, move |_| true)
-// });
-// cx.foreground().run_until_parked();
-// // Discard "Save all" prompt
-// window.simulate_prompt_answer(2, cx);
-
-// cx.foreground().run_until_parked();
-// left_pane.read_with(cx, |pane, cx| {
-// assert_eq!(
-// pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
-// &[ProjectEntryId::from_proto(0)]
-// );
-// });
-// window.simulate_prompt_answer(0, cx);
+ #[gpui::test]
+ async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ let project = Project::test(fs, [], cx).await;
+ let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+
+ // Create several workspace items with single project entries, and two
+ // workspace items with multiple project entries.
+ let single_entry_items = (0..=4)
+ .map(|project_entry_id| {
+ cx.build_view(|cx| {
+ TestItem::new(cx)
+ .with_dirty(true)
+ .with_project_items(&[TestProjectItem::new(
+ project_entry_id,
+ &format!("{project_entry_id}.txt"),
+ cx,
+ )])
+ })
+ })
+ .collect::<Vec<_>>();
+ let item_2_3 = cx.build_view(|cx| {
+ TestItem::new(cx)
+ .with_dirty(true)
+ .with_singleton(false)
+ .with_project_items(&[
+ single_entry_items[2].read(cx).project_items[0].clone(),
+ single_entry_items[3].read(cx).project_items[0].clone(),
+ ])
+ });
+ let item_3_4 = cx.build_view(|cx| {
+ TestItem::new(cx)
+ .with_dirty(true)
+ .with_singleton(false)
+ .with_project_items(&[
+ single_entry_items[3].read(cx).project_items[0].clone(),
+ single_entry_items[4].read(cx).project_items[0].clone(),
+ ])
+ });
-// cx.foreground().run_until_parked();
-// left_pane.read_with(cx, |pane, cx| {
-// assert_eq!(
-// pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
-// &[ProjectEntryId::from_proto(2)]
-// );
-// });
-// window.simulate_prompt_answer(0, cx);
+ // Create two panes that contain the following project entries:
+ // left pane:
+ // multi-entry items: (2, 3)
+ // single-entry items: 0, 1, 2, 3, 4
+ // right pane:
+ // single-entry items: 1
+ // multi-entry items: (3, 4)
+ let left_pane = workspace.update(cx, |workspace, cx| {
+ let left_pane = workspace.active_pane().clone();
+ workspace.add_item(Box::new(item_2_3.clone()), cx);
+ for item in single_entry_items {
+ workspace.add_item(Box::new(item), cx);
+ }
+ left_pane.update(cx, |pane, cx| {
+ pane.activate_item(2, true, true, cx);
+ });
-// cx.foreground().run_until_parked();
-// close.await.unwrap();
-// left_pane.read_with(cx, |pane, _| {
-// assert_eq!(pane.items_len(), 0);
-// });
-// }
+ let right_pane = workspace
+ .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
+ .unwrap();
-// #[gpui::test]
-// async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
-// init_test(cx);
+ right_pane.update(cx, |pane, cx| {
+ pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
+ });
-// let fs = FakeFs::new(cx.background());
+ left_pane
+ });
-// let project = Project::test(fs, [], cx).await;
-// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-// let workspace = window.root(cx);
-// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
+ cx.focus_view(&left_pane);
-// let item = window.build_view(cx, |cx| {
-// TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-// });
-// let item_id = item.id();
-// workspace.update(cx, |workspace, cx| {
-// workspace.add_item(Box::new(item.clone()), cx);
-// });
+ // When closing all of the items in the left pane, we should be prompted twice:
+ // once for project entry 0, and once for project entry 2. Project entries 1,
+ // 3, and 4 are all still open in the other paten. After those two
+ // prompts, the task should complete.
-// // Autosave on window change.
-// item.update(cx, |item, cx| {
-// cx.update_global(|settings: &mut SettingsStore, cx| {
-// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-// settings.autosave = Some(AutosaveSetting::OnWindowChange);
-// })
-// });
-// item.is_dirty = true;
-// });
+ let close = left_pane.update(cx, |pane, cx| {
+ pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
+ });
+ cx.executor().run_until_parked();
-// // Deactivating the window saves the file.
-// window.simulate_deactivation(cx);
-// deterministic.run_until_parked();
-// item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
-
-// // Autosave on focus change.
-// item.update(cx, |item, cx| {
-// cx.focus_self();
-// cx.update_global(|settings: &mut SettingsStore, cx| {
-// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-// settings.autosave = Some(AutosaveSetting::OnFocusChange);
-// })
-// });
-// item.is_dirty = true;
-// });
+ // Discard "Save all" prompt
+ cx.simulate_prompt_answer(2);
-// // Blurring the item saves the file.
-// item.update(cx, |_, cx| cx.blur());
-// deterministic.run_until_parked();
-// item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
+ cx.executor().run_until_parked();
+ left_pane.update(cx, |pane, cx| {
+ assert_eq!(
+ pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
+ &[ProjectEntryId::from_proto(0)]
+ );
+ });
+ cx.simulate_prompt_answer(0);
-// // Deactivating the window still saves the file.
-// window.simulate_activation(cx);
-// item.update(cx, |item, cx| {
-// cx.focus_self();
-// item.is_dirty = true;
-// });
-// window.simulate_deactivation(cx);
+ cx.executor().run_until_parked();
+ left_pane.update(cx, |pane, cx| {
+ assert_eq!(
+ pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
+ &[ProjectEntryId::from_proto(2)]
+ );
+ });
+ cx.simulate_prompt_answer(0);
-// deterministic.run_until_parked();
-// item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
+ cx.executor().run_until_parked();
+ close.await.unwrap();
+ left_pane.update(cx, |pane, _| {
+ assert_eq!(pane.items_len(), 0);
+ });
+ }
-// // Autosave after delay.
-// item.update(cx, |item, cx| {
-// cx.update_global(|settings: &mut SettingsStore, cx| {
-// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-// settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
-// })
-// });
-// item.is_dirty = true;
-// cx.emit(TestItemEvent::Edit);
-// });
+ #[gpui::test]
+ async fn test_autosave(cx: &mut gpui::TestAppContext) {
+ init_test(cx);
-// // Delay hasn't fully expired, so the file is still dirty and unsaved.
-// deterministic.advance_clock(Duration::from_millis(250));
-// item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
+ let fs = FakeFs::new(cx.executor());
+ let project = Project::test(fs, [], cx).await;
+ let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+ let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
-// // After delay expires, the file is saved.
-// deterministic.advance_clock(Duration::from_millis(250));
-// item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
+ let item = cx.build_view(|cx| {
+ TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+ });
+ let item_id = item.entity_id();
+ workspace.update(cx, |workspace, cx| {
+ workspace.add_item(Box::new(item.clone()), cx);
+ });
-// // Autosave on focus change, ensuring closing the tab counts as such.
-// item.update(cx, |item, cx| {
-// cx.update_global(|settings: &mut SettingsStore, cx| {
-// settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-// settings.autosave = Some(AutosaveSetting::OnFocusChange);
-// })
-// });
-// item.is_dirty = true;
-// });
+ // Autosave on window change.
+ item.update(cx, |item, cx| {
+ cx.update_global(|settings: &mut SettingsStore, cx| {
+ settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+ settings.autosave = Some(AutosaveSetting::OnWindowChange);
+ })
+ });
+ item.is_dirty = true;
+ });
-// pane.update(cx, |pane, cx| {
-// pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
-// })
-// .await
-// .unwrap();
-// assert!(!window.has_pending_prompt(cx));
-// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
-
-// // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
-// workspace.update(cx, |workspace, cx| {
-// workspace.add_item(Box::new(item.clone()), cx);
-// });
-// item.update(cx, |item, cx| {
-// item.project_items[0].update(cx, |item, _| {
-// item.entry_id = None;
-// });
-// item.is_dirty = true;
-// cx.blur();
-// });
-// deterministic.run_until_parked();
-// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
+ // Deactivating the window saves the file.
+ cx.simulate_deactivation();
+ cx.executor().run_until_parked();
+ item.update(cx, |item, _| assert_eq!(item.save_count, 1));
+
+ // Autosave on focus change.
+ item.update(cx, |item, cx| {
+ cx.focus_self();
+ cx.update_global(|settings: &mut SettingsStore, cx| {
+ settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+ settings.autosave = Some(AutosaveSetting::OnFocusChange);
+ })
+ });
+ item.is_dirty = true;
+ });
-// // Ensure autosave is prevented for deleted files also when closing the buffer.
-// let _close_items = pane.update(cx, |pane, cx| {
-// pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
-// });
-// deterministic.run_until_parked();
-// assert!(window.has_pending_prompt(cx));
-// item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
-// }
+ // Blurring the item saves the file.
+ item.update(cx, |_, cx| cx.blur());
+ cx.executor().run_until_parked();
+ item.update(cx, |item, _| assert_eq!(item.save_count, 2));
-// #[gpui::test]
-// async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
-// init_test(cx);
+ // Deactivating the window still saves the file.
+ cx.simulate_activation();
+ item.update(cx, |item, cx| {
+ cx.focus_self();
+ item.is_dirty = true;
+ });
+ cx.simulate_deactivation();
-// let fs = FakeFs::new(cx.background());
+ cx.executor().run_until_parked();
+ item.update(cx, |item, _| assert_eq!(item.save_count, 3));
-// let project = Project::test(fs, [], cx).await;
-// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-// let workspace = window.root(cx);
+ // Autosave after delay.
+ item.update(cx, |item, cx| {
+ cx.update_global(|settings: &mut SettingsStore, cx| {
+ settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+ settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
+ })
+ });
+ item.is_dirty = true;
+ cx.emit(ItemEvent::Edit);
+ });
-// let item = window.build_view(cx, |cx| {
-// TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-// });
-// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-// let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
-// let toolbar_notify_count = Rc::new(RefCell::new(0));
-
-// workspace.update(cx, |workspace, cx| {
-// workspace.add_item(Box::new(item.clone()), cx);
-// let toolbar_notification_count = toolbar_notify_count.clone();
-// cx.observe(&toolbar, move |_, _, _| {
-// *toolbar_notification_count.borrow_mut() += 1
-// })
-// .detach();
-// });
+ // Delay hasn't fully expired, so the file is still dirty and unsaved.
+ cx.executor().advance_clock(Duration::from_millis(250));
+ item.update(cx, |item, _| assert_eq!(item.save_count, 3));
-// pane.read_with(cx, |pane, _| {
-// assert!(!pane.can_navigate_backward());
-// assert!(!pane.can_navigate_forward());
-// });
+ // After delay expires, the file is saved.
+ cx.executor().advance_clock(Duration::from_millis(250));
+ item.update(cx, |item, _| assert_eq!(item.save_count, 4));
-// item.update(cx, |item, cx| {
-// item.set_state("one".to_string(), cx);
-// });
+ // Autosave on focus change, ensuring closing the tab counts as such.
+ item.update(cx, |item, cx| {
+ cx.update_global(|settings: &mut SettingsStore, cx| {
+ settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+ settings.autosave = Some(AutosaveSetting::OnFocusChange);
+ })
+ });
+ item.is_dirty = true;
+ });
-// // Toolbar must be notified to re-render the navigation buttons
-// assert_eq!(*toolbar_notify_count.borrow(), 1);
+ pane.update(cx, |pane, cx| {
+ pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
+ })
+ .await
+ .unwrap();
+ assert!(!cx.has_pending_prompt());
+ item.update(cx, |item, _| assert_eq!(item.save_count, 5));
+
+ // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
+ workspace.update(cx, |workspace, cx| {
+ workspace.add_item(Box::new(item.clone()), cx);
+ });
+ item.update(cx, |item, cx| {
+ item.project_items[0].update(cx, |item, _| {
+ item.entry_id = None;
+ });
+ item.is_dirty = true;
+ cx.blur();
+ });
+ cx.executor().run_until_parked();
+ item.update(cx, |item, _| assert_eq!(item.save_count, 5));
-// pane.read_with(cx, |pane, _| {
-// assert!(pane.can_navigate_backward());
-// assert!(!pane.can_navigate_forward());
-// });
+ // Ensure autosave is prevented for deleted files also when closing the buffer.
+ let _close_items = pane.update(cx, |pane, cx| {
+ pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
+ });
+ cx.executor().run_until_parked();
+ assert!(cx.has_pending_prompt());
+ item.update(cx, |item, _| assert_eq!(item.save_count, 5));
+ }
-// workspace
-// .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
-// .await
-// .unwrap();
+ #[gpui::test]
+ async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
+ init_test(cx);
-// assert_eq!(*toolbar_notify_count.borrow(), 3);
-// pane.read_with(cx, |pane, _| {
-// assert!(!pane.can_navigate_backward());
-// assert!(pane.can_navigate_forward());
-// });
-// }
+ let fs = FakeFs::new(cx.executor());
-// #[gpui::test]
-// async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
-// init_test(cx);
-// let fs = FakeFs::new(cx.background());
+ let project = Project::test(fs, [], cx).await;
+ let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
-// let project = Project::test(fs, [], cx).await;
-// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-// let workspace = window.root(cx);
+ let item = cx.build_view(|cx| {
+ TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+ });
+ let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
+ let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
+ let toolbar_notify_count = Rc::new(RefCell::new(0));
+
+ workspace.update(cx, |workspace, cx| {
+ workspace.add_item(Box::new(item.clone()), cx);
+ let toolbar_notification_count = toolbar_notify_count.clone();
+ cx.observe(&toolbar, move |_, _, _| {
+ *toolbar_notification_count.borrow_mut() += 1
+ })
+ .detach();
+ });
-// let panel = workspace.update(cx, |workspace, cx| {
-// let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right));
-// workspace.add_panel(panel.clone(), cx);
+ pane.update(cx, |pane, _| {
+ assert!(!pane.can_navigate_backward());
+ assert!(!pane.can_navigate_forward());
+ });
-// workspace
-// .right_dock()
-// .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
+ item.update(cx, |item, cx| {
+ item.set_state("one".to_string(), cx);
+ });
-// panel
-// });
+ // Toolbar must be notified to re-render the navigation buttons
+ assert_eq!(*toolbar_notify_count.borrow(), 1);
-// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-// pane.update(cx, |pane, cx| {
-// let item = cx.build_view(|_| TestItem::new());
-// pane.add_item(Box::new(item), true, true, None, cx);
-// });
+ pane.update(cx, |pane, _| {
+ assert!(pane.can_navigate_backward());
+ assert!(!pane.can_navigate_forward());
+ });
-// // Transfer focus from center to panel
-// workspace.update(cx, |workspace, cx| {
-// workspace.toggle_panel_focus::<TestPanel>(cx);
-// });
+ workspace
+ .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
+ .await
+ .unwrap();
-// workspace.read_with(cx, |workspace, cx| {
-// assert!(workspace.right_dock().read(cx).is_open());
-// assert!(!panel.is_zoomed(cx));
-// assert!(panel.has_focus(cx));
-// });
+ assert_eq!(*toolbar_notify_count.borrow(), 2);
+ pane.update(cx, |pane, _| {
+ assert!(!pane.can_navigate_backward());
+ assert!(pane.can_navigate_forward());
+ });
+ }
-// // Transfer focus from panel to center
-// workspace.update(cx, |workspace, cx| {
-// workspace.toggle_panel_focus::<TestPanel>(cx);
-// });
+ // #[gpui::test]
+ // async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
+ // init_test(cx);
+ // let fs = FakeFs::new(cx.executor());
-// workspace.read_with(cx, |workspace, cx| {
-// assert!(workspace.right_dock().read(cx).is_open());
-// assert!(!panel.is_zoomed(cx));
-// assert!(!panel.has_focus(cx));
-// });
+ // let project = Project::test(fs, [], cx).await;
+ // let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ // let workspace = window.root(cx);
-// // Close the dock
-// workspace.update(cx, |workspace, cx| {
-// workspace.toggle_dock(DockPosition::Right, cx);
-// });
+ // let panel = workspace.update(cx, |workspace, cx| {
+ // let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right));
+ // workspace.add_panel(panel.clone(), cx);
-// workspace.read_with(cx, |workspace, cx| {
-// assert!(!workspace.right_dock().read(cx).is_open());
-// assert!(!panel.is_zoomed(cx));
-// assert!(!panel.has_focus(cx));
-// });
+ // workspace
+ // .right_dock()
+ // .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
-// // Open the dock
-// workspace.update(cx, |workspace, cx| {
-// workspace.toggle_dock(DockPosition::Right, cx);
-// });
+ // panel
+ // });
-// workspace.read_with(cx, |workspace, cx| {
-// assert!(workspace.right_dock().read(cx).is_open());
-// assert!(!panel.is_zoomed(cx));
-// assert!(panel.has_focus(cx));
-// });
+ // let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
+ // pane.update(cx, |pane, cx| {
+ // let item = cx.build_view(|_| TestItem::new(cx));
+ // pane.add_item(Box::new(item), true, true, None, cx);
+ // });
-// // Focus and zoom panel
-// panel.update(cx, |panel, cx| {
-// cx.focus_self();
-// panel.set_zoomed(true, cx)
-// });
+ // // Transfer focus from center to panel
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.toggle_panel_focus::<TestPanel>(cx);
+ // });
-// workspace.read_with(cx, |workspace, cx| {
-// assert!(workspace.right_dock().read(cx).is_open());
-// assert!(panel.is_zoomed(cx));
-// assert!(panel.has_focus(cx));
-// });
+ // workspace.update(cx, |workspace, cx| {
+ // assert!(workspace.right_dock().read(cx).is_open());
+ // assert!(!panel.is_zoomed(cx));
+ // assert!(panel.has_focus(cx));
+ // });
-// // Transfer focus to the center closes the dock
-// workspace.update(cx, |workspace, cx| {
-// workspace.toggle_panel_focus::<TestPanel>(cx);
-// });
+ // // Transfer focus from panel to center
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.toggle_panel_focus::<TestPanel>(cx);
+ // });
-// workspace.read_with(cx, |workspace, cx| {
-// assert!(!workspace.right_dock().read(cx).is_open());
-// assert!(panel.is_zoomed(cx));
-// assert!(!panel.has_focus(cx));
-// });
+ // workspace.update(cx, |workspace, cx| {
+ // assert!(workspace.right_dock().read(cx).is_open());
+ // assert!(!panel.is_zoomed(cx));
+ // assert!(!panel.has_focus(cx));
+ // });
-// // Transferring focus back to the panel keeps it zoomed
-// workspace.update(cx, |workspace, cx| {
-// workspace.toggle_panel_focus::<TestPanel>(cx);
-// });
+ // // Close the dock
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.toggle_dock(DockPosition::Right, cx);
+ // });
-// workspace.read_with(cx, |workspace, cx| {
-// assert!(workspace.right_dock().read(cx).is_open());
-// assert!(panel.is_zoomed(cx));
-// assert!(panel.has_focus(cx));
-// });
+ // workspace.update(cx, |workspace, cx| {
+ // assert!(!workspace.right_dock().read(cx).is_open());
+ // assert!(!panel.is_zoomed(cx));
+ // assert!(!panel.has_focus(cx));
+ // });
-// // Close the dock while it is zoomed
-// workspace.update(cx, |workspace, cx| {
-// workspace.toggle_dock(DockPosition::Right, cx)
-// });
+ // // Open the dock
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.toggle_dock(DockPosition::Right, cx);
+ // });
-// workspace.read_with(cx, |workspace, cx| {
-// assert!(!workspace.right_dock().read(cx).is_open());
-// assert!(panel.is_zoomed(cx));
-// assert!(workspace.zoomed.is_none());
-// assert!(!panel.has_focus(cx));
-// });
+ // workspace.update(cx, |workspace, cx| {
+ // assert!(workspace.right_dock().read(cx).is_open());
+ // assert!(!panel.is_zoomed(cx));
+ // assert!(panel.has_focus(cx));
+ // });
-// // Opening the dock, when it's zoomed, retains focus
-// workspace.update(cx, |workspace, cx| {
-// workspace.toggle_dock(DockPosition::Right, cx)
-// });
+ // // Focus and zoom panel
+ // panel.update(cx, |panel, cx| {
+ // cx.focus_self();
+ // panel.set_zoomed(true, cx)
+ // });
-// workspace.read_with(cx, |workspace, cx| {
-// assert!(workspace.right_dock().read(cx).is_open());
-// assert!(panel.is_zoomed(cx));
-// assert!(workspace.zoomed.is_some());
-// assert!(panel.has_focus(cx));
-// });
+ // workspace.update(cx, |workspace, cx| {
+ // assert!(workspace.right_dock().read(cx).is_open());
+ // assert!(panel.is_zoomed(cx));
+ // assert!(panel.has_focus(cx));
+ // });
-// // Unzoom and close the panel, zoom the active pane.
-// panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
-// workspace.update(cx, |workspace, cx| {
-// workspace.toggle_dock(DockPosition::Right, cx)
-// });
-// pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
+ // // Transfer focus to the center closes the dock
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.toggle_panel_focus::<TestPanel>(cx);
+ // });
-// // Opening a dock unzooms the pane.
-// workspace.update(cx, |workspace, cx| {
-// workspace.toggle_dock(DockPosition::Right, cx)
-// });
-// workspace.read_with(cx, |workspace, cx| {
-// let pane = pane.read(cx);
-// assert!(!pane.is_zoomed());
-// assert!(!pane.has_focus());
-// assert!(workspace.right_dock().read(cx).is_open());
-// assert!(workspace.zoomed.is_none());
-// });
-// }
+ // workspace.update(cx, |workspace, cx| {
+ // assert!(!workspace.right_dock().read(cx).is_open());
+ // assert!(panel.is_zoomed(cx));
+ // assert!(!panel.has_focus(cx));
+ // });
-// #[gpui::test]
-// async fn test_panels(cx: &mut gpui::TestAppContext) {
-// init_test(cx);
-// let fs = FakeFs::new(cx.background());
-
-// let project = Project::test(fs, [], cx).await;
-// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-// let workspace = window.root(cx);
-
-// let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
-// // Add panel_1 on the left, panel_2 on the right.
-// let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left));
-// workspace.add_panel(panel_1.clone(), cx);
-// workspace
-// .left_dock()
-// .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
-// let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right));
-// workspace.add_panel(panel_2.clone(), cx);
-// workspace
-// .right_dock()
-// .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
-
-// let left_dock = workspace.left_dock();
-// assert_eq!(
-// left_dock.read(cx).visible_panel().unwrap().id(),
-// panel_1.id()
-// );
-// assert_eq!(
-// left_dock.read(cx).active_panel_size(cx).unwrap(),
-// panel_1.size(cx)
-// );
+ // // Transferring focus back to the panel keeps it zoomed
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.toggle_panel_focus::<TestPanel>(cx);
+ // });
-// left_dock.update(cx, |left_dock, cx| {
-// left_dock.resize_active_panel(Some(1337.), cx)
-// });
-// assert_eq!(
-// workspace
-// .right_dock()
-// .read(cx)
-// .visible_panel()
-// .unwrap()
-// .id(),
-// panel_2.id()
-// );
+ // workspace.update(cx, |workspace, cx| {
+ // assert!(workspace.right_dock().read(cx).is_open());
+ // assert!(panel.is_zoomed(cx));
+ // assert!(panel.has_focus(cx));
+ // });
-// (panel_1, panel_2)
-// });
+ // // Close the dock while it is zoomed
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.toggle_dock(DockPosition::Right, cx)
+ // });
-// // Move panel_1 to the right
-// panel_1.update(cx, |panel_1, cx| {
-// panel_1.set_position(DockPosition::Right, cx)
-// });
+ // workspace.update(cx, |workspace, cx| {
+ // assert!(!workspace.right_dock().read(cx).is_open());
+ // assert!(panel.is_zoomed(cx));
+ // assert!(workspace.zoomed.is_none());
+ // assert!(!panel.has_focus(cx));
+ // });
-// workspace.update(cx, |workspace, cx| {
-// // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
-// // Since it was the only panel on the left, the left dock should now be closed.
-// assert!(!workspace.left_dock().read(cx).is_open());
-// assert!(workspace.left_dock().read(cx).visible_panel().is_none());
-// let right_dock = workspace.right_dock();
-// assert_eq!(
-// right_dock.read(cx).visible_panel().unwrap().id(),
-// panel_1.id()
-// );
-// assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
+ // // Opening the dock, when it's zoomed, retains focus
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.toggle_dock(DockPosition::Right, cx)
+ // });
-// // Now we move panel_2Β to the left
-// panel_2.set_position(DockPosition::Left, cx);
-// });
+ // workspace.update(cx, |workspace, cx| {
+ // assert!(workspace.right_dock().read(cx).is_open());
+ // assert!(panel.is_zoomed(cx));
+ // assert!(workspace.zoomed.is_some());
+ // assert!(panel.has_focus(cx));
+ // });
-// workspace.update(cx, |workspace, cx| {
-// // Since panel_2 was not visible on the right, we don't open the left dock.
-// assert!(!workspace.left_dock().read(cx).is_open());
-// // And the right dock is unaffected in it's displaying of panel_1
-// assert!(workspace.right_dock().read(cx).is_open());
-// assert_eq!(
-// workspace
-// .right_dock()
-// .read(cx)
-// .visible_panel()
-// .unwrap()
-// .id(),
-// panel_1.id()
-// );
-// });
+ // // Unzoom and close the panel, zoom the active pane.
+ // panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.toggle_dock(DockPosition::Right, cx)
+ // });
+ // pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
-// // Move panel_1 back to the left
-// panel_1.update(cx, |panel_1, cx| {
-// panel_1.set_position(DockPosition::Left, cx)
-// });
+ // // Opening a dock unzooms the pane.
+ // workspace.update(cx, |workspace, cx| {
+ // workspace.toggle_dock(DockPosition::Right, cx)
+ // });
+ // workspace.update(cx, |workspace, cx| {
+ // let pane = pane.read(cx);
+ // assert!(!pane.is_zoomed());
+ // assert!(!pane.has_focus());
+ // assert!(workspace.right_dock().read(cx).is_open());
+ // assert!(workspace.zoomed.is_none());
+ // });
+ // }
-// workspace.update(cx, |workspace, cx| {
-// // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
-// let left_dock = workspace.left_dock();
-// assert!(left_dock.read(cx).is_open());
-// assert_eq!(
-// left_dock.read(cx).visible_panel().unwrap().id(),
-// panel_1.id()
-// );
-// assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
-// // And right the dock should be closed as it no longer has any panels.
-// assert!(!workspace.right_dock().read(cx).is_open());
+ // #[gpui::test]
+ // async fn test_panels(cx: &mut gpui::TestAppContext) {
+ // init_test(cx);
+ // let fs = FakeFs::new(cx.executor());
+
+ // let project = Project::test(fs, [], cx).await;
+ // let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+ // let workspace = window.root(cx);
+
+ // let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
+ // // Add panel_1 on the left, panel_2 on the right.
+ // let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left));
+ // workspace.add_panel(panel_1.clone(), cx);
+ // workspace
+ // .left_dock()
+ // .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
+ // let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right));
+ // workspace.add_panel(panel_2.clone(), cx);
+ // workspace
+ // .right_dock()
+ // .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
+
+ // let left_dock = workspace.left_dock();
+ // assert_eq!(
+ // left_dock.read(cx).visible_panel().unwrap().id(),
+ // panel_1.id()
+ // );
+ // assert_eq!(
+ // left_dock.read(cx).active_panel_size(cx).unwrap(),
+ // panel_1.size(cx)
+ // );
+
+ // left_dock.update(cx, |left_dock, cx| {
+ // left_dock.resize_active_panel(Some(1337.), cx)
+ // });
+ // assert_eq!(
+ // workspace
+ // .right_dock()
+ // .read(cx)
+ // .visible_panel()
+ // .unwrap()
+ // .id(),
+ // panel_2.id()
+ // );
+
+ // (panel_1, panel_2)
+ // });
-// // Now we move panel_1 to the bottom
-// panel_1.set_position(DockPosition::Bottom, cx);
-// });
+ // // Move panel_1 to the right
+ // panel_1.update(cx, |panel_1, cx| {
+ // panel_1.set_position(DockPosition::Right, cx)
+ // });
-// workspace.update(cx, |workspace, cx| {
-// // Since panel_1 was visible on the left, we close the left dock.
-// assert!(!workspace.left_dock().read(cx).is_open());
-// // The bottom dock is sized based on the panel's default size,
-// // since the panel orientation changed from vertical to horizontal.
-// let bottom_dock = workspace.bottom_dock();
-// assert_eq!(
-// bottom_dock.read(cx).active_panel_size(cx).unwrap(),
-// panel_1.size(cx),
-// );
-// // Close bottom dock and move panel_1 back to the left.
-// bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
-// panel_1.set_position(DockPosition::Left, cx);
-// });
+ // workspace.update(cx, |workspace, cx| {
+ // // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
+ // // Since it was the only panel on the left, the left dock should now be closed.
+ // assert!(!workspace.left_dock().read(cx).is_open());
+ // assert!(workspace.left_dock().read(cx).visible_panel().is_none());
+ // let right_dock = workspace.right_dock();
+ // assert_eq!(
+ // right_dock.read(cx).visible_panel().unwrap().id(),
+ // panel_1.id()
+ // );
+ // assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
+
+ // // Now we move panel_2Β to the left
+ // panel_2.set_position(DockPosition::Left, cx);
+ // });
-// // Emit activated event on panel 1
-// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
+ // workspace.update(cx, |workspace, cx| {
+ // // Since panel_2 was not visible on the right, we don't open the left dock.
+ // assert!(!workspace.left_dock().read(cx).is_open());
+ // // And the right dock is unaffected in it's displaying of panel_1
+ // assert!(workspace.right_dock().read(cx).is_open());
+ // assert_eq!(
+ // workspace
+ // .right_dock()
+ // .read(cx)
+ // .visible_panel()
+ // .unwrap()
+ // .id(),
+ // panel_1.id()
+ // );
+ // });
-// // Now the left dock is open and panel_1 is active and focused.
-// workspace.read_with(cx, |workspace, cx| {
-// let left_dock = workspace.left_dock();
-// assert!(left_dock.read(cx).is_open());
-// assert_eq!(
-// left_dock.read(cx).visible_panel().unwrap().id(),
-// panel_1.id()
-// );
-// assert!(panel_1.is_focused(cx));
-// });
+ // // Move panel_1 back to the left
+ // panel_1.update(cx, |panel_1, cx| {
+ // panel_1.set_position(DockPosition::Left, cx)
+ // });
-// // Emit closed event on panel 2, which is not active
-// panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
+ // workspace.update(cx, |workspace, cx| {
+ // // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
+ // let left_dock = workspace.left_dock();
+ // assert!(left_dock.read(cx).is_open());
+ // assert_eq!(
+ // left_dock.read(cx).visible_panel().unwrap().id(),
+ // panel_1.id()
+ // );
+ // assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
+ // // And right the dock should be closed as it no longer has any panels.
+ // assert!(!workspace.right_dock().read(cx).is_open());
+
+ // // Now we move panel_1 to the bottom
+ // panel_1.set_position(DockPosition::Bottom, cx);
+ // });
-// // Wo don't close the left dock, because panel_2 wasn't the active panel
-// workspace.read_with(cx, |workspace, cx| {
-// let left_dock = workspace.left_dock();
-// assert!(left_dock.read(cx).is_open());
-// assert_eq!(
-// left_dock.read(cx).visible_panel().unwrap().id(),
-// panel_1.id()
-// );
-// });
+ // workspace.update(cx, |workspace, cx| {
+ // // Since panel_1 was visible on the left, we close the left dock.
+ // assert!(!workspace.left_dock().read(cx).is_open());
+ // // The bottom dock is sized based on the panel's default size,
+ // // since the panel orientation changed from vertical to horizontal.
+ // let bottom_dock = workspace.bottom_dock();
+ // assert_eq!(
+ // bottom_dock.read(cx).active_panel_size(cx).unwrap(),
+ // panel_1.size(cx),
+ // );
+ // // Close bottom dock and move panel_1 back to the left.
+ // bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
+ // panel_1.set_position(DockPosition::Left, cx);
+ // });
-// // Emitting a ZoomIn event shows the panel as zoomed.
-// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
-// workspace.read_with(cx, |workspace, _| {
-// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-// assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
-// });
+ // // Emit activated event on panel 1
+ // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
+
+ // // Now the left dock is open and panel_1 is active and focused.
+ // workspace.update(cx, |workspace, cx| {
+ // let left_dock = workspace.left_dock();
+ // assert!(left_dock.read(cx).is_open());
+ // assert_eq!(
+ // left_dock.read(cx).visible_panel().unwrap().id(),
+ // panel_1.id()
+ // );
+ // assert!(panel_1.is_focused(cx));
+ // });
-// // Move panel to another dock while it is zoomed
-// panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
-// workspace.read_with(cx, |workspace, _| {
-// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
-// });
+ // // Emit closed event on panel 2, which is not active
+ // panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
+
+ // // Wo don't close the left dock, because panel_2 wasn't the active panel
+ // workspace.update(cx, |workspace, cx| {
+ // let left_dock = workspace.left_dock();
+ // assert!(left_dock.read(cx).is_open());
+ // assert_eq!(
+ // left_dock.read(cx).visible_panel().unwrap().id(),
+ // panel_1.id()
+ // );
+ // });
-// // If focus is transferred to another view that's not a panel or another pane, we still show
-// // the panel as zoomed.
-// let focus_receiver = window.build_view(cx, |_| EmptyView);
-// focus_receiver.update(cx, |_, cx| cx.focus_self());
-// workspace.read_with(cx, |workspace, _| {
-// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
-// });
+ // // Emitting a ZoomIn event shows the panel as zoomed.
+ // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
+ // workspace.update(cx, |workspace, _| {
+ // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
+ // assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
+ // });
-// // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
-// workspace.update(cx, |_, cx| cx.focus_self());
-// workspace.read_with(cx, |workspace, _| {
-// assert_eq!(workspace.zoomed, None);
-// assert_eq!(workspace.zoomed_position, None);
-// });
+ // // Move panel to another dock while it is zoomed
+ // panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
+ // workspace.update(cx, |workspace, _| {
+ // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
+ // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
+ // });
-// // If focus is transferred again to another view that's not a panel or a pane, we won't
-// // show the panel as zoomed because it wasn't zoomed before.
-// focus_receiver.update(cx, |_, cx| cx.focus_self());
-// workspace.read_with(cx, |workspace, _| {
-// assert_eq!(workspace.zoomed, None);
-// assert_eq!(workspace.zoomed_position, None);
-// });
+ // // If focus is transferred to another view that's not a panel or another pane, we still show
+ // // the panel as zoomed.
+ // let focus_receiver = cx.build_view(|_| EmptyView);
+ // focus_receiver.update(cx, |_, cx| cx.focus_self());
+ // workspace.update(cx, |workspace, _| {
+ // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
+ // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
+ // });
-// // When focus is transferred back to the panel, it is zoomed again.
-// panel_1.update(cx, |_, cx| cx.focus_self());
-// workspace.read_with(cx, |workspace, _| {
-// assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-// assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
-// });
+ // // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
+ // workspace.update(cx, |_, cx| cx.focus_self());
+ // workspace.update(cx, |workspace, _| {
+ // assert_eq!(workspace.zoomed, None);
+ // assert_eq!(workspace.zoomed_position, None);
+ // });
-// // Emitting a ZoomOut event unzooms the panel.
-// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
-// workspace.read_with(cx, |workspace, _| {
-// assert_eq!(workspace.zoomed, None);
-// assert_eq!(workspace.zoomed_position, None);
-// });
+ // // If focus is transferred again to another view that's not a panel or a pane, we won't
+ // // show the panel as zoomed because it wasn't zoomed before.
+ // focus_receiver.update(cx, |_, cx| cx.focus_self());
+ // workspace.update(cx, |workspace, _| {
+ // assert_eq!(workspace.zoomed, None);
+ // assert_eq!(workspace.zoomed_position, None);
+ // });
-// // Emit closed event on panel 1, which is active
-// panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
+ // // When focus is transferred back to the panel, it is zoomed again.
+ // panel_1.update(cx, |_, cx| cx.focus_self());
+ // workspace.update(cx, |workspace, _| {
+ // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
+ // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
+ // });
-// // Now the left dock is closed, because panel_1 was the active panel
-// workspace.read_with(cx, |workspace, cx| {
-// let right_dock = workspace.right_dock();
-// assert!(!right_dock.read(cx).is_open());
-// });
-// }
+ // // Emitting a ZoomOut event unzooms the panel.
+ // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
+ // workspace.update(cx, |workspace, _| {
+ // assert_eq!(workspace.zoomed, None);
+ // assert_eq!(workspace.zoomed_position, None);
+ // });
-// pub fn init_test(cx: &mut TestAppContext) {
-// cx.foreground().forbid_parking();
-// cx.update(|cx| {
-// cx.set_global(SettingsStore::test(cx));
-// theme::init((), cx);
-// language::init(cx);
-// crate::init_settings(cx);
-// Project::init_settings(cx);
-// });
-// }
-// }
+ // // Emit closed event on panel 1, which is active
+ // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
+
+ // // Now the left dock is closed, because panel_1 was the active panel
+ // workspace.update(cx, |workspace, cx| {
+ // let right_dock = workspace.right_dock();
+ // assert!(!right_dock.read(cx).is_open());
+ // });
+ // }
+
+ pub fn init_test(cx: &mut TestAppContext) {
+ cx.update(|cx| {
+ let settings_store = SettingsStore::test(cx);
+ cx.set_global(settings_store);
+ theme::init(theme::LoadThemes::JustBase, cx);
+ language::init(cx);
+ crate::init_settings(cx);
+ Project::init_settings(cx);
+ });
+ }
+}
@@ -1,6 +1,6 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use settings2::Settings;
+use settings::Settings;
#[derive(Deserialize)]
pub struct WorkspaceSettings {
@@ -105,6 +105,7 @@ impl LspAdapter for JsonLspAdapter {
fn workspace_configuration(
&self,
+ _workspace_root: &Path,
cx: &mut AppContext,
) -> BoxFuture<'static, serde_json::Value> {
let action_names = cx.all_action_names().collect::<Vec<_>>();
@@ -29,7 +29,6 @@ pub struct IntelephenseLspAdapter {
impl IntelephenseLspAdapter {
const SERVER_PATH: &'static str = "node_modules/intelephense/lib/intelephense.js";
- #[allow(unused)]
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
Self { node }
}
@@ -107,7 +107,11 @@ impl LspAdapter for TailwindLspAdapter {
}))
}
- fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
+ fn workspace_configuration(
+ &self,
+ _workspace_root: &Path,
+ _: &mut AppContext,
+ ) -> BoxFuture<'static, Value> {
future::ready(json!({
"tailwindCSS": {
"emmetCompletions": true,
@@ -205,7 +205,6 @@ pub struct EsLintLspAdapter {
impl EsLintLspAdapter {
const SERVER_PATH: &'static str = "vscode-eslint/server/out/eslintServer.js";
- #[allow(unused)]
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
EsLintLspAdapter { node }
}
@@ -213,13 +212,23 @@ impl EsLintLspAdapter {
#[async_trait]
impl LspAdapter for EsLintLspAdapter {
- fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
+ fn workspace_configuration(
+ &self,
+ workspace_root: &Path,
+ _: &mut AppContext,
+ ) -> BoxFuture<'static, Value> {
future::ready(json!({
"": {
"validate": "on",
"rulesCustomizations": [],
"run": "onType",
"nodePath": null,
+ "workingDirectory": {"mode": "auto"},
+ "workspaceFolder": {
+ "uri": workspace_root,
+ "name": workspace_root.file_name()
+ .unwrap_or_else(|| workspace_root.as_os_str()),
+ },
}
}))
.boxed()
@@ -93,7 +93,11 @@ impl LspAdapter for YamlLspAdapter {
) -> Option<LanguageServerBinary> {
get_cached_server_binary(container_dir, &*self.node).await
}
- fn workspace_configuration(&self, cx: &mut AppContext) -> BoxFuture<'static, Value> {
+ fn workspace_configuration(
+ &self,
+ _workspace_root: &Path,
+ cx: &mut AppContext,
+ ) -> BoxFuture<'static, Value> {
let tab_size = all_language_settings(None, cx)
.language(Some("YAML"))
.tab_size;
@@ -105,6 +105,7 @@ impl LspAdapter for JsonLspAdapter {
fn workspace_configuration(
&self,
+ _workspace_root: &Path,
cx: &mut AppContext,
) -> BoxFuture<'static, serde_json::Value> {
let action_names = cx.all_action_names();
@@ -29,7 +29,6 @@ pub struct IntelephenseLspAdapter {
impl IntelephenseLspAdapter {
const SERVER_PATH: &'static str = "node_modules/intelephense/lib/intelephense.js";
- #[allow(unused)]
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
Self { node }
}
@@ -107,7 +107,11 @@ impl LspAdapter for TailwindLspAdapter {
}))
}
- fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
+ fn workspace_configuration(
+ &self,
+ _workspace_root: &Path,
+ _: &mut AppContext,
+ ) -> BoxFuture<'static, Value> {
future::ready(json!({
"tailwindCSS": {
"emmetCompletions": true,
@@ -205,7 +205,6 @@ pub struct EsLintLspAdapter {
impl EsLintLspAdapter {
const SERVER_PATH: &'static str = "vscode-eslint/server/out/eslintServer.js";
- #[allow(unused)]
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
EsLintLspAdapter { node }
}
@@ -213,13 +212,23 @@ impl EsLintLspAdapter {
#[async_trait]
impl LspAdapter for EsLintLspAdapter {
- fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
+ fn workspace_configuration(
+ &self,
+ workspace_root: &Path,
+ _: &mut AppContext,
+ ) -> BoxFuture<'static, Value> {
future::ready(json!({
"": {
"validate": "on",
"rulesCustomizations": [],
"run": "onType",
"nodePath": null,
+ "workingDirectory": {"mode": "auto"},
+ "workspaceFolder": {
+ "uri": workspace_root,
+ "name": workspace_root.file_name()
+ .unwrap_or_else(|| workspace_root.as_os_str()),
+ },
}
}))
.boxed()
@@ -93,7 +93,11 @@ impl LspAdapter for YamlLspAdapter {
) -> Option<LanguageServerBinary> {
get_cached_server_binary(container_dir, &*self.node).await
}
- fn workspace_configuration(&self, cx: &mut AppContext) -> BoxFuture<'static, Value> {
+ fn workspace_configuration(
+ &self,
+ _workspace_root: &Path,
+ cx: &mut AppContext,
+ ) -> BoxFuture<'static, Value> {
let tab_size = all_language_settings(None, cx)
.language(Some("YAML"))
.tab_size;
@@ -746,9 +746,9 @@ fn watch_file_types(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
}
#[cfg(not(debug_assertions))]
-async fn watch_languages(_: Arc<dyn Fs>, _: Arc<LanguageRegistry>) -> Option<()> {
+async fn watch_languages(_: Arc<dyn fs::Fs>, _: Arc<LanguageRegistry>) -> Option<()> {
None
}
#[cfg(not(debug_assertions))]
-fn watch_file_types(_fs: Arc<dyn Fs>, _cx: &mut AppContext) {}
+fn watch_file_types(_fs: Arc<dyn fs::Fs>, _cx: &mut AppContext) {}
@@ -147,7 +147,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
// let feedback_button = cx.add_view(|_| {
// feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)
// });
- // let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new());
+ let cursor_position = cx.build_view(|_| editor::items::CursorPosition::new());
workspace.status_bar().update(cx, |status_bar, cx| {
status_bar.add_left_item(diagnostic_summary, cx);
status_bar.add_left_item(activity_indicator, cx);
@@ -156,7 +156,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
// status_bar.add_right_item(copilot, cx);
// status_bar.add_right_item(active_buffer_language, cx);
// status_bar.add_right_item(vim_mode_indicator, cx);
- // status_bar.add_right_item(cursor_position, cx);
+ status_bar.add_right_item(cursor_position, cx);
});
auto_update::notify_of_any_new_update(cx);